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