init
This commit is contained in:
parent
1f2880f719
commit
03c1cec48f
@ -2,7 +2,7 @@ import useSWR from "swr";
|
|||||||
import { getQuizData } from "./quizRelase";
|
import { getQuizData } from "./quizRelase";
|
||||||
|
|
||||||
export function useQuizData(quizId: string, preview: boolean = false) {
|
export function useQuizData(quizId: string, preview: boolean = false) {
|
||||||
return useSWR(preview ? null : ["quizData", quizId], (params) => getQuizData(params[1]), {
|
return useSWR(preview ? null : ["quizData", quizId], (params) => getQuizData({ quizId: params[1] }), {
|
||||||
revalidateOnFocus: false,
|
revalidateOnFocus: false,
|
||||||
revalidateOnReconnect: false,
|
revalidateOnReconnect: false,
|
||||||
shouldRetryOnError: false,
|
shouldRetryOnError: false,
|
||||||
|
@ -120,7 +120,15 @@ export async function getData(quizId: string): Promise<{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getQuizData(quizId: string, status?: string): Promise<QuizSettings> {
|
export async function getQuizData({
|
||||||
|
quizId,
|
||||||
|
status,
|
||||||
|
type = "",
|
||||||
|
}: {
|
||||||
|
quizId: string;
|
||||||
|
status?: string;
|
||||||
|
type?: string;
|
||||||
|
}): Promise<QuizSettings> {
|
||||||
let maxRetries = 50;
|
let maxRetries = 50;
|
||||||
if (!quizId) throw new Error("No quiz id");
|
if (!quizId) throw new Error("No quiz id");
|
||||||
|
|
||||||
@ -197,7 +205,6 @@ export async function getQuizData(quizId: string, status?: string): Promise<Quiz
|
|||||||
).data as QuizSettings;
|
).data as QuizSettings;
|
||||||
|
|
||||||
res.recentlyCompleted = responseData.isRecentlyCompleted;
|
res.recentlyCompleted = responseData.isRecentlyCompleted;
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
32
lib/api/useQuizGetNext.ts
Normal file
32
lib/api/useQuizGetNext.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { useQuizSettings } from "@/contexts/QuizDataContext";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { getQuizData } from "./quizRelase";
|
||||||
|
|
||||||
|
export const useQuizGetNext = () => {
|
||||||
|
const { addQuestion, quizId, settings } = useQuizSettings();
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [error, setError] = useState<Error | null>(null);
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
|
||||||
|
const loadMoreQuestions = async () => {
|
||||||
|
setIsLoading(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await getQuizData({ quizId, type: settings.cfg.type || "", status: settings.status });
|
||||||
|
const newQuestion = data?.questions[0];
|
||||||
|
if (newQuestion) {
|
||||||
|
newQuestion.page = currentPage;
|
||||||
|
addQuestion(newQuestion);
|
||||||
|
setCurrentPage((old) => old++);
|
||||||
|
return newQuestion;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setError(err as Error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { loadMoreQuestions, isLoading, error, currentPage };
|
||||||
|
};
|
@ -3,7 +3,7 @@ import { QuizViewContext, createQuizViewStore } from "@/stores/quizView";
|
|||||||
import LoadingSkeleton from "@/ui_kit/LoadingSkeleton";
|
import LoadingSkeleton from "@/ui_kit/LoadingSkeleton";
|
||||||
import { useVkMetricsGoals } from "@/utils/hooks/metrics/useVkMetricsGoals";
|
import { useVkMetricsGoals } from "@/utils/hooks/metrics/useVkMetricsGoals";
|
||||||
import { useYandexMetricsGoals } from "@/utils/hooks/metrics/useYandexMetricsGoals";
|
import { useYandexMetricsGoals } from "@/utils/hooks/metrics/useYandexMetricsGoals";
|
||||||
import { QuizSettingsContext } from "@contexts/QuizDataContext";
|
import { QuizSettingsContext, QuizSettingsContextValue } from "@contexts/QuizDataContext";
|
||||||
import { RootContainerWidthContext } from "@contexts/RootContainerWidthContext";
|
import { RootContainerWidthContext } from "@contexts/RootContainerWidthContext";
|
||||||
import type { QuizSettings } from "@model/settingsData";
|
import type { QuizSettings } from "@model/settingsData";
|
||||||
import { Box, CssBaseline, ScopedCssBaseline, ThemeProvider } from "@mui/material";
|
import { Box, CssBaseline, ScopedCssBaseline, ThemeProvider } from "@mui/material";
|
||||||
@ -14,13 +14,15 @@ import { handleComponentError } from "@utils/handleComponentError";
|
|||||||
import lightTheme from "@utils/themes/light";
|
import lightTheme from "@utils/themes/light";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { SnackbarProvider } from "notistack";
|
import { SnackbarProvider } from "notistack";
|
||||||
import { startTransition, useEffect, useLayoutEffect, useRef, useState } from "react";
|
import { startTransition, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
||||||
import { ErrorBoundary } from "react-error-boundary";
|
import { ErrorBoundary } from "react-error-boundary";
|
||||||
import { ApologyPage } from "./ViewPublicationPage/ApologyPage";
|
import { ApologyPage } from "./ViewPublicationPage/ApologyPage";
|
||||||
import ViewPublicationPage from "./ViewPublicationPage/ViewPublicationPage";
|
import ViewPublicationPage from "./ViewPublicationPage/ViewPublicationPage";
|
||||||
import { HelmetProvider } from "react-helmet-async";
|
import { HelmetProvider } from "react-helmet-async";
|
||||||
|
|
||||||
import "moment/dist/locale/ru";
|
import "moment/dist/locale/ru";
|
||||||
|
import { AnyTypedQuizQuestion } from "..";
|
||||||
|
import { produce } from "immer";
|
||||||
moment.locale("ru");
|
moment.locale("ru");
|
||||||
const localeText = ruRU.components.MuiLocalizationProvider.defaultProps.localeText;
|
const localeText = ruRU.components.MuiLocalizationProvider.defaultProps.localeText;
|
||||||
|
|
||||||
@ -32,7 +34,16 @@ type Props = {
|
|||||||
className?: string;
|
className?: string;
|
||||||
disableGlobalCss?: boolean;
|
disableGlobalCss?: boolean;
|
||||||
};
|
};
|
||||||
|
function isQuizSettingsValid(data: any): data is QuizSettings {
|
||||||
|
return (
|
||||||
|
data &&
|
||||||
|
Array.isArray(data.questions) &&
|
||||||
|
data.settings &&
|
||||||
|
typeof data.cnt === "number" &&
|
||||||
|
typeof data.recentlyCompleted === "boolean" &&
|
||||||
|
typeof data.show_badge === "boolean"
|
||||||
|
);
|
||||||
|
}
|
||||||
function QuizAnswererInner({
|
function QuizAnswererInner({
|
||||||
quizSettings,
|
quizSettings,
|
||||||
quizId,
|
quizId,
|
||||||
@ -47,6 +58,14 @@ function QuizAnswererInner({
|
|||||||
const { data, error, isLoading } = useQuizData(quizId, preview);
|
const { data, error, isLoading } = useQuizData(quizId, preview);
|
||||||
const vkMetrics = useVkMetricsGoals(quizSettings?.settings.cfg.vkMetricsNumber);
|
const vkMetrics = useVkMetricsGoals(quizSettings?.settings.cfg.vkMetricsNumber);
|
||||||
const yandexMetrics = useYandexMetricsGoals(quizSettings?.settings.cfg.yandexMetricsNumber);
|
const yandexMetrics = useYandexMetricsGoals(quizSettings?.settings.cfg.yandexMetricsNumber);
|
||||||
|
const [localQuizSettings, setLocalQuizSettings] = useState(quizSettings);
|
||||||
|
|
||||||
|
// Добавляем эффект для обновления localQuizSettings при получении новых данных
|
||||||
|
useEffect(() => {
|
||||||
|
if (data && !quizSettings) {
|
||||||
|
setLocalQuizSettings(data);
|
||||||
|
}
|
||||||
|
}, [data, quizSettings]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -73,15 +92,36 @@ function QuizAnswererInner({
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const finalQuizSettings = quizSettings || localQuizSettings;
|
||||||
|
|
||||||
|
const contextValue = useMemo(
|
||||||
|
() => ({
|
||||||
|
...(finalQuizSettings as QuizSettings),
|
||||||
|
quizId,
|
||||||
|
preview,
|
||||||
|
changeFaviconAndTitle,
|
||||||
|
addQuestion: (newQuestion: AnyTypedQuizQuestion) => {
|
||||||
|
setLocalQuizSettings((prev) => {
|
||||||
|
if (!prev) return prev;
|
||||||
|
|
||||||
|
return produce(prev, (draft) => {
|
||||||
|
draft.questions.push(newQuestion);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[quizId, preview, changeFaviconAndTitle, finalQuizSettings]
|
||||||
|
);
|
||||||
|
|
||||||
if (isLoading) return <LoadingSkeleton />;
|
if (isLoading) return <LoadingSkeleton />;
|
||||||
if (error) return <ApologyPage error={error} />;
|
if (error) return <ApologyPage error={error} />;
|
||||||
// if (!data) return <LoadingSkeleton />;
|
|
||||||
quizSettings ??= data;
|
|
||||||
if (!quizSettings) return <ApologyPage error={new Error("quiz data is null")} />;
|
|
||||||
|
|
||||||
if (quizSettings.questions.length === 1 && quizSettings?.settings.cfg.noStartPage)
|
if (!finalQuizSettings) return <ApologyPage error={new Error("quiz data is null")} />;
|
||||||
|
if (!finalQuizSettings.questions || finalQuizSettings.questions.length === 0)
|
||||||
|
return <ApologyPage error={new Error("No questions found")} />;
|
||||||
|
|
||||||
|
if (finalQuizSettings.questions.length === 1 && finalQuizSettings?.settings.cfg.noStartPage)
|
||||||
return <ApologyPage error={new Error("quiz is empty")} />;
|
return <ApologyPage error={new Error("quiz is empty")} />;
|
||||||
// if (quizSettings.questions.length === 1) return <ApologyPage error={new Error("no questions found")} />;
|
|
||||||
if (!quizId) return <ApologyPage error={new Error("no quiz id")} />;
|
if (!quizId) return <ApologyPage error={new Error("no quiz id")} />;
|
||||||
|
|
||||||
const quizContainer = (
|
const quizContainer = (
|
||||||
@ -106,7 +146,7 @@ function QuizAnswererInner({
|
|||||||
return (
|
return (
|
||||||
<QuizViewContext.Provider value={quizViewStore}>
|
<QuizViewContext.Provider value={quizViewStore}>
|
||||||
<RootContainerWidthContext.Provider value={rootContainerWidth}>
|
<RootContainerWidthContext.Provider value={rootContainerWidth}>
|
||||||
<QuizSettingsContext.Provider value={{ ...quizSettings, quizId, preview, changeFaviconAndTitle }}>
|
<QuizSettingsContext.Provider value={contextValue}>
|
||||||
{disableGlobalCss ? (
|
{disableGlobalCss ? (
|
||||||
<ScopedCssBaseline
|
<ScopedCssBaseline
|
||||||
sx={{
|
sx={{
|
||||||
|
@ -4,6 +4,7 @@ import { FallbackProps } from "react-error-boundary";
|
|||||||
type Props = Partial<FallbackProps>;
|
type Props = Partial<FallbackProps>;
|
||||||
|
|
||||||
export const ApologyPage = ({ error }: Props) => {
|
export const ApologyPage = ({ error }: Props) => {
|
||||||
|
console.log(error);
|
||||||
let message = "Что-то пошло не так";
|
let message = "Что-то пошло не так";
|
||||||
|
|
||||||
if (error.response?.data === "quiz is inactive") message = "Квиз не активирован";
|
if (error.response?.data === "quiz is inactive") message = "Квиз не активирован";
|
||||||
|
@ -13,7 +13,7 @@ type FooterProps = {
|
|||||||
|
|
||||||
export const Footer = ({ stepNumber, nextButton, prevButton }: FooterProps) => {
|
export const Footer = ({ stepNumber, nextButton, prevButton }: FooterProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { questions, settings } = useQuizSettings();
|
const { questions, settings, cnt } = useQuizSettings();
|
||||||
const questionsAmount = questions.filter(({ type }) => type !== "result").length;
|
const questionsAmount = questions.filter(({ type }) => type !== "result").length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -41,7 +41,7 @@ export const Footer = ({ stepNumber, nextButton, prevButton }: FooterProps) => {
|
|||||||
{stepNumber !== null && settings.status !== "ai" && (
|
{stepNumber !== null && settings.status !== "ai" && (
|
||||||
<Box sx={{ flexGrow: 1 }}>
|
<Box sx={{ flexGrow: 1 }}>
|
||||||
<Typography sx={{ color: theme.palette.text.primary }}>
|
<Typography sx={{ color: theme.palette.text.primary }}>
|
||||||
Вопрос {stepNumber} из {questionsAmount}
|
Вопрос {stepNumber} из {cnt}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Stepper
|
<Stepper
|
||||||
activeStep={stepNumber}
|
activeStep={stepNumber}
|
||||||
|
@ -1,19 +1,18 @@
|
|||||||
import { QuizSettings } from "@model/settingsData";
|
import { QuizSettings } from "@model/settingsData";
|
||||||
import { createContext, useContext } from "react";
|
import { createContext, useContext, useMemo } from "react";
|
||||||
import { AnyTypedQuizQuestion } from "..";
|
import { AnyTypedQuizQuestion } from "..";
|
||||||
|
|
||||||
export const QuizSettingsContext = createContext<
|
export type QuizSettingsContextValue = QuizSettings & {
|
||||||
| (QuizSettings & {
|
quizId: string;
|
||||||
quizId: string;
|
preview: boolean;
|
||||||
preview: boolean;
|
changeFaviconAndTitle: boolean;
|
||||||
changeFaviconAndTitle: boolean;
|
addQuestion: (newQuestion: AnyTypedQuizQuestion) => void;
|
||||||
})
|
};
|
||||||
| null
|
|
||||||
>(null);
|
export const QuizSettingsContext = createContext<QuizSettingsContextValue | null>(null);
|
||||||
|
|
||||||
export const useQuizSettings = () => {
|
export const useQuizSettings = () => {
|
||||||
const quizSettings = useContext(QuizSettingsContext);
|
const quizSettings = useContext(QuizSettingsContext);
|
||||||
if (quizSettings === null) throw new Error("QuizSettings context is null");
|
if (quizSettings === null) throw new Error("QuizSettings context is null");
|
||||||
|
|
||||||
return quizSettings;
|
return quizSettings;
|
||||||
};
|
};
|
||||||
|
@ -11,17 +11,16 @@ import { useVkMetricsGoals } from "@/utils/hooks/metrics/useVkMetricsGoals";
|
|||||||
import { useYandexMetricsGoals } from "@/utils/hooks/metrics/useYandexMetricsGoals";
|
import { useYandexMetricsGoals } from "@/utils/hooks/metrics/useYandexMetricsGoals";
|
||||||
import { AnyTypedQuizQuestion } from "@/index";
|
import { AnyTypedQuizQuestion } from "@/index";
|
||||||
import { getQuizData } from "@/api/quizRelase";
|
import { getQuizData } from "@/api/quizRelase";
|
||||||
|
import { useQuizGetNext } from "@/api/useQuizGetNext";
|
||||||
|
|
||||||
let isgetting = false;
|
let isgetting = false;
|
||||||
|
|
||||||
export function useQuestionFlowControl() {
|
export function useQuestionFlowControl() {
|
||||||
//Получаем инфо о квизе и список вопросов.
|
//Получаем инфо о квизе и список вопросов.
|
||||||
const { settings, questions: initialQuestions, quizId } = useQuizSettings();
|
const { loadMoreQuestions } = useQuizGetNext();
|
||||||
const [questions, setQuestions] = useState(initialQuestions);
|
const { settings, questions, quizId, cnt } = useQuizSettings();
|
||||||
|
|
||||||
const addQuestion = (question: AnyTypedQuizQuestion) => {
|
console.log(questions);
|
||||||
setQuestions((prev) => [...prev, question]);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Когда квиз линейный, не ветвящийся, мы идём по вопросам по их порядковому номеру. Это их page.
|
//Когда квиз линейный, не ветвящийся, мы идём по вопросам по их порядковому номеру. Это их page.
|
||||||
//За корректность page отвечает конструктор квизов. Интересный факт, если в конструкторе удалить из середины вопрос, то случится куча запросов изменения вопросов с изменением этого page
|
//За корректность page отвечает конструктор квизов. Интересный факт, если в конструкторе удалить из середины вопрос, то случится куча запросов изменения вопросов с изменением этого page
|
||||||
@ -43,7 +42,7 @@ export function useQuestionFlowControl() {
|
|||||||
//Изменение стейта (переменной currentQuestionId) ведёт к пересчёту что же за объект сейчас используется. Мы каждый раз просто ищем в списке
|
//Изменение стейта (переменной currentQuestionId) ведёт к пересчёту что же за объект сейчас используется. Мы каждый раз просто ищем в списке
|
||||||
const currentQuestion = sortedQuestions.find((question) => question.id === currentQuestionId) ?? sortedQuestions[0];
|
const currentQuestion = sortedQuestions.find((question) => question.id === currentQuestionId) ?? sortedQuestions[0];
|
||||||
|
|
||||||
// console.log(currentQuestion)
|
console.log(currentQuestion);
|
||||||
|
|
||||||
//Индекс текущего вопроса только если квиз линейный
|
//Индекс текущего вопроса только если квиз линейный
|
||||||
const linearQuestionIndex = //: number | null
|
const linearQuestionIndex = //: number | null
|
||||||
@ -123,7 +122,7 @@ export function useQuestionFlowControl() {
|
|||||||
return nextQuestionIdPointsLogic();
|
return nextQuestionIdPointsLogic();
|
||||||
}
|
}
|
||||||
return nextQuestionIdMainLogic();
|
return nextQuestionIdMainLogic();
|
||||||
}, [nextQuestionIdMainLogic, nextQuestionIdPointsLogic, settings.cfg.score]);
|
}, [nextQuestionIdMainLogic, nextQuestionIdPointsLogic, settings.cfg.score, questions]);
|
||||||
|
|
||||||
//Поиск предыдущго вопроса либо по индексу либо по id родителя
|
//Поиск предыдущго вопроса либо по индексу либо по id родителя
|
||||||
const prevQuestion =
|
const prevQuestion =
|
||||||
@ -211,21 +210,39 @@ export function useQuestionFlowControl() {
|
|||||||
|
|
||||||
//рычаг управления из визуала в эту функцию
|
//рычаг управления из визуала в эту функцию
|
||||||
const moveToNextQuestion = useCallback(async () => {
|
const moveToNextQuestion = useCallback(async () => {
|
||||||
if (isgetting) return;
|
// Если есть следующий вопрос в уже загруженных - используем его
|
||||||
isgetting = true;
|
if (nextQuestion) {
|
||||||
const data = await getQuizData(quizId, settings.status);
|
vkMetrics.questionPassed(currentQuestion.id);
|
||||||
addQuestion(data.questions[0]);
|
yandexMetrics.questionPassed(currentQuestion.id);
|
||||||
isgetting = false;
|
|
||||||
if (!nextQuestion) throw new Error("Next question not found");
|
|
||||||
|
|
||||||
// Засчитываем переход с вопроса дальше
|
if (nextQuestion.type === "result") return showResult();
|
||||||
vkMetrics.questionPassed(currentQuestion.id);
|
setCurrentQuestionId(nextQuestion.id);
|
||||||
yandexMetrics.questionPassed(currentQuestion.id);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (nextQuestion.type === "result") return showResult();
|
// Если следующего нет - загружаем новый
|
||||||
|
try {
|
||||||
setCurrentQuestionId(nextQuestion.id);
|
const newQuestion = await loadMoreQuestions();
|
||||||
}, [currentQuestion.id, nextQuestion, showResult, vkMetrics, yandexMetrics]);
|
if (newQuestion) {
|
||||||
|
vkMetrics.questionPassed(currentQuestion.id);
|
||||||
|
yandexMetrics.questionPassed(currentQuestion.id);
|
||||||
|
console.log("МЫ ПАЛУЧИЛИ НОВЫЙ ВОПРОС");
|
||||||
|
console.log(newQuestion);
|
||||||
|
setCurrentQuestionId(newQuestion.id);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
enqueueSnackbar("Ошибка загрузки следующего вопроса");
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
currentQuestion.id,
|
||||||
|
nextQuestion,
|
||||||
|
showResult,
|
||||||
|
vkMetrics,
|
||||||
|
yandexMetrics,
|
||||||
|
linearQuestionIndex,
|
||||||
|
loadMoreQuestions,
|
||||||
|
questions,
|
||||||
|
]);
|
||||||
|
|
||||||
//рычаг управления из визуала в эту функцию
|
//рычаг управления из визуала в эту функцию
|
||||||
const setQuestion = useCallback(
|
const setQuestion = useCallback(
|
||||||
@ -249,6 +266,10 @@ export function useQuestionFlowControl() {
|
|||||||
return hasAnswer;
|
return hasAnswer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(linearQuestionIndex);
|
||||||
|
console.log(questions.length);
|
||||||
|
console.log(cnt);
|
||||||
|
if (linearQuestionIndex !== null && questions.length < cnt) return true;
|
||||||
return Boolean(nextQuestion);
|
return Boolean(nextQuestion);
|
||||||
}, [answers, currentQuestion, nextQuestion]);
|
}, [answers, currentQuestion, nextQuestion]);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user