diff --git a/lib/api/hooks.ts b/lib/api/hooks.ts index e54c8c1..5550583 100644 --- a/lib/api/hooks.ts +++ b/lib/api/hooks.ts @@ -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, diff --git a/lib/api/quizRelase.ts b/lib/api/quizRelase.ts index e8ba0ca..5be0530 100644 --- a/lib/api/quizRelase.ts +++ b/lib/api/quizRelase.ts @@ -72,56 +72,117 @@ export const publicationMakeRequest = ({ url, body }: PublicationMakeRequestPara }); }; -export async function getData(quizId: string): Promise<{ +// Глобальные переменные для хранения состояния между вызовами +let globalStatus: string | null = null; +let isFirstRequest = true; + +export async function getData({ quizId, page }: { quizId: string; page?: number }): Promise<{ data: GetQuizDataResponse | null; isRecentlyCompleted: boolean; error?: AxiosError; }> { try { - const { data, headers } = await axios( - domain + `/answer/v1.0.0/settings${window.location.search}`, - { - method: "POST", - headers: { - "X-Sessionkey": SESSIONS, - "Content-Type": "application/json", - DeviceType: DeviceType, - Device: Device, - OS: OSDevice, - Browser: userAgent, - }, - data: { - quiz_id: quizId, - limit: 100, - page: 0, - need_config: true, - }, - } - ); - const sessions = JSON.parse(localStorage.getItem("sessions") || "{}"); + // Первый запрос: 1 вопрос + конфиг + if (isFirstRequest) { + const { data, headers } = await axios( + domain + `/answer/v1.0.0/settings${window.location.search}`, + { + method: "POST", + headers: { + "X-Sessionkey": SESSIONS, + "Content-Type": "application/json", + DeviceType: DeviceType, + Device: Device, + OS: OSDevice, + Browser: userAgent, + }, + data: { + quiz_id: quizId, + limit: 1, + page: 0, + need_config: true, + }, + } + ); - //Тут ещё проверка на антифрод без парса конфига. Нам не интересно время если не нужно запрещать проходить чаще чем в сутки - if (typeof sessions[quizId] === "number" && data.settings.cfg.includes('antifraud":true')) { - // unix время. Если меньше суток прошло - выводить ошибку, иначе пустить дальше - if (Date.now() - sessions[quizId] < 86400000) { - return { data, isRecentlyCompleted: true }; + globalStatus = data.settings.status; + isFirstRequest = false; + SESSIONS = headers["x-sessionkey"] || SESSIONS; + + // Проверка антифрода + const sessions = JSON.parse(localStorage.getItem("sessions") || "{}"); + if (typeof sessions[quizId] === "number" && data.settings.cfg.includes('antifraud":true')) { + if (Date.now() - sessions[quizId] < 86400000) { + return { data, isRecentlyCompleted: true }; + } } + + // Если статус не AI - сразу делаем запрос за всеми вопросами + if (globalStatus !== "ai") { + const secondResponse = await axios( + domain + `/answer/v1.0.0/settings${window.location.search}`, + { + method: "POST", + headers: { + "X-Sessionkey": SESSIONS, + "Content-Type": "application/json", + DeviceType: DeviceType, + Device: Device, + OS: OSDevice, + Browser: userAgent, + }, + data: { + quiz_id: quizId, + limit: 100, + page: 0, + need_config: false, + }, + } + ); + return { + data: { ...data, items: secondResponse.data.items }, + isRecentlyCompleted: false, + }; + } + + return { data, isRecentlyCompleted: false }; } - SESSIONS = headers["x-sessionkey"] ? headers["x-sessionkey"] : SESSIONS; + // Последующие запросы + const response = await axios(domain + `/answer/v1.0.0/settings${window.location.search}`, { + method: "POST", + headers: { + "X-Sessionkey": SESSIONS, + "Content-Type": "application/json", + DeviceType: DeviceType, + Device: Device, + OS: OSDevice, + Browser: userAgent, + }, + data: { + quiz_id: quizId, + limit: 1, + page: page, + need_config: false, + }, + }); - return { data, isRecentlyCompleted: false }; - } catch (nativeError) { - const error = nativeError as AxiosError; - - return { data: null, isRecentlyCompleted: false, error: error }; + return { + data: response.data, + isRecentlyCompleted: false, + }; + } catch (error) { + return { + data: null, + isRecentlyCompleted: false, + error: error as AxiosError, + }; } } - -export async function getQuizData(quizId: string) { +export async function getQuizData({ quizId, status = "" }: { quizId: string; status?: string }) { if (!quizId) throw new Error("No quiz id"); - const response = await getData(quizId); + const response = await getData({ quizId }); const quizDataResponse = response.data; if (response.error) { @@ -142,6 +203,124 @@ export async function getQuizData(quizId: string) { return res; } +let page = 1; + +export async function getQuizDataAI(quizId: string) { + console.log("[getQuizDataAI] Starting with quizId:", quizId); // Добавлено + let maxRetries = 50; + + if (!quizId) { + console.error("[getQuizDataAI] Error: No quiz id provided"); + throw new Error("No quiz id"); + } + + let lastError: Error | null = null; + let responseData: any = null; + + // Первый цикл - обработка result вопросов + console.log("[getQuizDataAI] Starting result retries loop"); // Добавлено + let resultRetryCount = 0; + while (resultRetryCount < maxRetries) { + try { + console.log(`[getQuizDataAI] Attempt ${resultRetryCount + 1} for result questions, page: ${page}`); + const response = await getData({ quizId, page }); + console.log("[getQuizDataAI] Response from getData:", response); + + if (response.error) { + console.error("[getQuizDataAI] Error in response:", response.error); + throw response.error; + } + if (!response.data) { + console.error("[getQuizDataAI] Error: Quiz not found"); + throw new Error("Quiz not found"); + } + + const hasAiResult = response.data.items.some((item) => item.typ === "result"); + console.log("[getQuizDataAI] Has AI result:", hasAiResult); + + if (hasAiResult) { + page++; + resultRetryCount++; + console.log(`[getQuizDataAI] Found result question, incrementing page to ${page}`); + continue; + } + + responseData = response; + console.log("[getQuizDataAI] Found non-result questions, breaking loop"); + break; + } catch (error) { + lastError = error as Error; + resultRetryCount++; + console.error(`[getQuizDataAI] Error in attempt ${resultRetryCount}:`, error); + + if (resultRetryCount >= maxRetries) { + console.error("[getQuizDataAI] Max retries reached for result questions"); + break; + } + + const delay = 1500 * resultRetryCount; + console.log(`[getQuizDataAI] Waiting ${delay}ms before next retry`); + await new Promise((resolve) => setTimeout(resolve, delay)); + } + } + + if (!responseData) { + console.error("[getQuizDataAI] Failed after result retries, throwing error"); + throw lastError || new Error("Failed to get quiz data after result retries"); + } + + // Второй цикл - обработка пустого массива + console.log("[getQuizDataAI] Starting empty items retry loop"); // Добавлено + let isEmpty = !responseData.data?.items.length; + let emptyRetryCount = 0; + + while (isEmpty && emptyRetryCount < maxRetries) { + try { + console.log(`[getQuizDataAI] Empty items retry ${emptyRetryCount + 1}`); + await new Promise((resolve) => setTimeout(resolve, 1000)); + const response = await getData({ quizId, page }); + + if (response.error) { + console.error("[getQuizDataAI] Error in empty items check:", response.error); + throw response.error; + } + if (!response.data) { + console.error("[getQuizDataAI] Error: Quiz not found in empty check"); + throw new Error("Quiz not found"); + } + + isEmpty = !response.data.items.length; + console.log("[getQuizDataAI] Is items empty:", isEmpty); + + if (!isEmpty) { + responseData = response; + console.log("[getQuizDataAI] Found non-empty items, updating responseData"); + } + emptyRetryCount++; + } catch (error) { + lastError = error as Error; + emptyRetryCount++; + console.error(`[getQuizDataAI] Error in empty check attempt ${emptyRetryCount}:`, error); + + if (emptyRetryCount >= maxRetries) { + console.error("[getQuizDataAI] Max empty retries reached"); + break; + } + } + } + + if (isEmpty) { + console.error("[getQuizDataAI] Items still empty after retries"); + throw new Error("Items array is empty after maximum retries"); + } + + // Финальная обработка + console.log("[getQuizDataAI] Processing final response data"); + + console.log("[getQuizDataAI] Final response before return:", responseData); + return responseData.data.items; +} + type SendAnswerProps = { questionId: string; body: string | string[]; @@ -150,6 +329,8 @@ type SendAnswerProps = { }; export function sendAnswer({ questionId, body, qid, preview = false }: SendAnswerProps) { + console.log("qid"); + console.log(qid); if (preview) return; const formData = new FormData(); diff --git a/lib/api/useQuizGetNext.ts b/lib/api/useQuizGetNext.ts new file mode 100644 index 0000000..6fa96ff --- /dev/null +++ b/lib/api/useQuizGetNext.ts @@ -0,0 +1,77 @@ +import { useState } from "react"; +import { getQuizDataAI } from "./quizRelase"; +import { addQuestion, useQuizStore } from "@/stores/useQuizStore"; +import { AnyTypedQuizQuestion } from ".."; + +function qparse(q: { desc: string; id: string; req: boolean; title: string; typ: string }) { + return { + description: q.desc, + id: q.id, + required: q.req, + title: q.title, + type: q.typ, + page: 0, + content: { + answerType: "single", + autofill: false, + back: "", + hint: { text: "", video: "" }, + id: "", + innerName: "", + innerNameCheck: false, + onlyNumbers: false, + originalBack: "", + placeholder: "", + required: false, + rule: { + children: [], + default: "", + main: [], + parentId: "", + }, + }, + }; +} + +export const useQuizGetNext = () => { + const { quizId, settings } = useQuizStore(); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const [currentPage, setCurrentPage] = useState(1); + + const loadMoreQuestions = async () => { + console.log("STATUS loadMoreQuestions"); + console.log(settings); + console.log(settings.status); + if (settings.status === "ai") { + console.log("STATUS after IF"); + setIsLoading(true); + setError(null); + + try { + console.log("STATUS after TRY TRY TRY"); + const data = await getQuizDataAI(quizId); + console.log("data"); + console.log(data); + const newQuestion = qparse(data[0]); + console.log("newQuestion"); + console.log(newQuestion); + if (newQuestion) { + newQuestion.page = currentPage; + //@ts-ignore + addQuestion(newQuestion as AnyTypedQuizQuestion); + setCurrentPage((old) => old++); + console.log("newQuestion + page"); + console.log(newQuestion); + return newQuestion; + } + } catch (err) { + setError(err as Error); + } finally { + setIsLoading(false); + } + } + }; + + return { loadMoreQuestions, isLoading, error, currentPage }; +}; diff --git a/lib/components/QuizAnswerer.tsx b/lib/components/QuizAnswerer.tsx index c733d39..7e8216b 100644 --- a/lib/components/QuizAnswerer.tsx +++ b/lib/components/QuizAnswerer.tsx @@ -3,7 +3,6 @@ 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 { RootContainerWidthContext } from "@contexts/RootContainerWidthContext"; import type { QuizSettings } from "@model/settingsData"; import { Box, CssBaseline, ScopedCssBaseline, ThemeProvider } from "@mui/material"; @@ -14,13 +13,14 @@ 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 { useQuizStore, setQuizData, addquizid } from "@/stores/useQuizStore"; moment.locale("ru"); const localeText = ruRU.components.MuiLocalizationProvider.defaultProps.localeText; @@ -32,7 +32,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 +56,19 @@ function QuizAnswererInner({ const { data, error, isLoading } = useQuizData(quizId, preview); const vkMetrics = useVkMetricsGoals(quizSettings?.settings.cfg.vkMetricsNumber); const yandexMetrics = useYandexMetricsGoals(quizSettings?.settings.cfg.yandexMetricsNumber); + const r = useQuizStore(); + const { settings, questions } = useQuizStore(); + + useEffect(() => { + addquizid(quizId); + }, []); + + useEffect(() => { + console.log(settings); + console.log(questions); + console.log("r"); + console.log(r); + }, [questions, settings]); useEffect(() => { setTimeout(() => { @@ -55,6 +77,18 @@ function QuizAnswererInner({ }, 4000); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + useEffect(() => { + console.log("got data"); + console.log(quizSettings); + console.log(data); + const quiz = quizSettings || data; + console.log("quiz"); + console.log(quiz); + if (quiz !== undefined) { + console.log("is not undefined"); + setQuizData(quiz); + } + }, [quizSettings, data]); useLayoutEffect(() => { if (rootContainerRef.current) setRootContainerWidth(rootContainerRef.current.clientWidth); @@ -73,15 +107,15 @@ function QuizAnswererInner({ }; }, []); + console.log("settings"); + console.log(settings); if (isLoading) return ; if (error) return ; - // if (!data) return ; - quizSettings ??= data; - if (!quizSettings) return ; - if (quizSettings.questions.length === 1 && quizSettings?.settings.cfg.noStartPage) - return ; - // if (quizSettings.questions.length === 1) return ; + if (Object.keys(settings).length == 0) return ; + if (questions.length === 0) return ; + + if (questions.length === 1 && settings.cfg.noStartPage) return ; if (!quizId) return ; const quizContainer = ( @@ -106,21 +140,19 @@ function QuizAnswererInner({ return ( - - {disableGlobalCss ? ( - - {quizContainer} - - ) : ( - {quizContainer} - )} - + {disableGlobalCss ? ( + + {quizContainer} + + ) : ( + {quizContainer} + )} ); diff --git a/lib/components/ViewPublicationPage/ContactForm/ContactForm.tsx b/lib/components/ViewPublicationPage/ContactForm/ContactForm.tsx index 73149a9..64d3317 100644 --- a/lib/components/ViewPublicationPage/ContactForm/ContactForm.tsx +++ b/lib/components/ViewPublicationPage/ContactForm/ContactForm.tsx @@ -8,7 +8,6 @@ import { Inputs } from "@/components/ViewPublicationPage/ContactForm/Inputs/Inpu import { ContactTextBlock } from "./ContactTextBlock"; import { useRootContainerSize } from "@contexts/RootContainerWidthContext"; -import { useQuizSettings } from "@contexts/QuizDataContext"; import { sendFC, SendFCParams } from "@api/quizRelase"; @@ -25,6 +24,7 @@ import type { FormContactFieldData, FormContactFieldName } from "@model/settings import type { QuizQuestionResult } from "@model/questionTypes/result"; import type { AnyTypedQuizQuestion } from "@model/questionTypes/shared"; import { isProduction } from "@/utils/defineDomain"; +import { useQuizStore } from "@/stores/useQuizStore"; import { useTranslation } from "react-i18next"; type Props = { @@ -36,7 +36,7 @@ const isDisableEmail = window.location.pathname.includes("/377c7570-1bee-4320-ac export const ContactForm = ({ currentQuestion, onShowResult }: Props) => { const theme = useTheme(); - const { settings, questions, quizId, show_badge, preview } = useQuizSettings(); + const { settings, questions, quizId, show_badge, preview } = useQuizStore(); const [ready, setReady] = useState(false); const [name, setName] = useState(""); diff --git a/lib/components/ViewPublicationPage/ContactForm/CustomInput/CustomInput.tsx b/lib/components/ViewPublicationPage/ContactForm/CustomInput/CustomInput.tsx index de71e9e..6d5cb6a 100644 --- a/lib/components/ViewPublicationPage/ContactForm/CustomInput/CustomInput.tsx +++ b/lib/components/ViewPublicationPage/ContactForm/CustomInput/CustomInput.tsx @@ -1,11 +1,11 @@ import { Box, InputAdornment, TextField as MuiTextField, TextFieldProps, Typography, useTheme } from "@mui/material"; import { useRootContainerSize } from "@contexts/RootContainerWidthContext.ts"; -import { useQuizSettings } from "@contexts/QuizDataContext.ts"; import { useIMask, IMask } from "react-imask"; import { quizThemes } from "@utils/themes/Publication/themePublication.ts"; import { ChangeEvent, FC, HTMLInputTypeAttribute, useEffect, useState } from "react"; import { CountrySelector } from "@/components/ViewPublicationPage/ContactForm/CustomInput/CountrySelector/CountrySelector.tsx"; import { phoneMasksByCountry } from "@utils/phoneMasksByCountry.tsx"; +import { useQuizStore } from "@/stores/useQuizStore"; type InputProps = { title: string; @@ -37,7 +37,7 @@ function phoneChange(e: ChangeEvent, mask: string) { export const CustomInput = ({ title, desc, Icon, onChange, onChangePhone, isPhone, type, value }: InputProps) => { const theme = useTheme(); const isMobile = useRootContainerSize() < 600; - const { settings } = useQuizSettings(); + const { settings } = useQuizStore(); const [mask, setMask] = useState(phoneMasksByCountry["RU"][1]); // const { ref } = useIMask({ mask }); diff --git a/lib/components/ViewPublicationPage/ContactForm/Inputs/Inputs.tsx b/lib/components/ViewPublicationPage/ContactForm/Inputs/Inputs.tsx index c338c34..0250121 100644 --- a/lib/components/ViewPublicationPage/ContactForm/Inputs/Inputs.tsx +++ b/lib/components/ViewPublicationPage/ContactForm/Inputs/Inputs.tsx @@ -1,4 +1,3 @@ -import { useQuizSettings } from "@contexts/QuizDataContext.ts"; import NameIcon from "@icons/ContactFormIcon/NameIcon.tsx"; import EmailIcon from "@icons/ContactFormIcon/EmailIcon.tsx"; import TextIcon from "@icons/ContactFormIcon/TextIcon.tsx"; @@ -7,6 +6,7 @@ import { Dispatch, SetStateAction } from "react"; import { CustomInput } from "@/components/ViewPublicationPage/ContactForm/CustomInput/CustomInput.tsx"; import PhoneIcon from "@icons/ContactFormIcon/PhoneIcon.tsx"; import PhoneInput from "react-phone-number-input"; +import { useQuizStore } from "@/stores/useQuizStore"; import { useTranslation } from "react-i18next"; type InputsProps = { @@ -38,7 +38,7 @@ export const Inputs = ({ setAdress, crutch, }: InputsProps) => { - const { settings } = useQuizSettings(); + const { settings } = useQuizStore(); const { t } = useTranslation(); const FC = settings.cfg.formContact.fields; diff --git a/lib/components/ViewPublicationPage/Footer.tsx b/lib/components/ViewPublicationPage/Footer.tsx index 0673610..adbeb27 100644 --- a/lib/components/ViewPublicationPage/Footer.tsx +++ b/lib/components/ViewPublicationPage/Footer.tsx @@ -1,10 +1,9 @@ import { ReactNode } from "react"; import { Box, Typography, useTheme } from "@mui/material"; -import { useQuizSettings } from "@contexts/QuizDataContext"; - import Stepper from "@ui_kit/Stepper"; import { useTranslation } from "react-i18next"; +import { useQuizStore } from "@/stores/useQuizStore"; type FooterProps = { stepNumber: number | null; @@ -14,7 +13,7 @@ type FooterProps = { export const Footer = ({ stepNumber, nextButton, prevButton }: FooterProps) => { const theme = useTheme(); - const { questions, settings } = useQuizSettings(); + const { questions, settings } = useQuizStore(); const questionsAmount = questions.filter(({ type }) => type !== "result").length; const { t } = useTranslation(); @@ -40,7 +39,7 @@ export const Footer = ({ stepNumber, nextButton, prevButton }: FooterProps) => { gap: "10px", }} > - {stepNumber !== null && ( + {stepNumber !== null && settings.status !== "ai" && ( {t("Step")} {stepNumber} {t("of")} {questionsAmount} diff --git a/lib/components/ViewPublicationPage/PointSystemResultList.tsx b/lib/components/ViewPublicationPage/PointSystemResultList.tsx index 27c76ae..63e4aeb 100644 --- a/lib/components/ViewPublicationPage/PointSystemResultList.tsx +++ b/lib/components/ViewPublicationPage/PointSystemResultList.tsx @@ -1,14 +1,14 @@ import { IncorrectAnswer } from "@/assets/icons/IncorrectAnswer"; import { CorrectAnswer } from "@/assets/icons/CorrectAnswer"; import { Box, Typography, useTheme } from "@mui/material"; -import { useQuizSettings } from "@/contexts/QuizDataContext"; import { useQuizViewStore } from "@/stores/quizView"; import { AnyTypedQuizQuestion, QuizQuestionVariant } from "@/index"; import { useTranslation } from "react-i18next"; +import { useQuizStore } from "@/stores/useQuizStore"; export const PointSystemResultList = () => { const theme = useTheme(); - const { questions } = useQuizSettings(); + const { questions } = useQuizStore(); const answers = useQuizViewStore((state) => state.answers); const { t } = useTranslation(); diff --git a/lib/components/ViewPublicationPage/Question.tsx b/lib/components/ViewPublicationPage/Question.tsx index 0be947f..eb45fa3 100644 --- a/lib/components/ViewPublicationPage/Question.tsx +++ b/lib/components/ViewPublicationPage/Question.tsx @@ -15,7 +15,6 @@ import { Varimg } from "./questions/Varimg"; import type { RealTypedQuizQuestion } from "../../model/questionTypes/shared"; -import { useQuizSettings } from "@contexts/QuizDataContext"; import { NameplateLogoFQ } from "@icons/NameplateLogoFQ"; import { NameplateLogoFQDark } from "@icons/NameplateLogoFQDark"; import { notReachable } from "@utils/notReachable"; @@ -24,6 +23,7 @@ import { quizThemes } from "@utils/themes/Publication/themePublication"; import { DESIGN_LIST } from "@/utils/designList"; import { type ReactNode } from "react"; import { isProduction } from "@/utils/defineDomain"; +import { useQuizStore } from "@/stores/useQuizStore"; type Props = { currentQuestion: RealTypedQuizQuestion; @@ -41,7 +41,7 @@ export const Question = ({ questionSelect, }: Props) => { const theme = useTheme(); - const { settings, show_badge, quizId } = useQuizSettings(); + const { settings, show_badge, quizId } = useQuizStore(); return ( { setQuestion(target.value); }} diff --git a/lib/components/ViewPublicationPage/ResultForm.tsx b/lib/components/ViewPublicationPage/ResultForm.tsx index 5289eac..1a992b0 100644 --- a/lib/components/ViewPublicationPage/ResultForm.tsx +++ b/lib/components/ViewPublicationPage/ResultForm.tsx @@ -4,7 +4,6 @@ import { Box, Button, Link, Typography, useTheme } from "@mui/material"; import { useQuizViewStore } from "@/stores/quizView"; import { useRootContainerSize } from "@contexts/RootContainerWidthContext"; -import { useQuizSettings } from "@contexts/QuizDataContext"; import { useVkMetricsGoals } from "@/utils/hooks/metrics/useVkMetricsGoals"; import { useYandexMetricsGoals } from "@/utils/hooks/metrics/useYandexMetricsGoals"; @@ -19,6 +18,7 @@ import { PointSystemResultList } from "./PointSystemResultList"; import { enqueueSnackbar } from "notistack"; import { sendFC, sendResult } from "@/api/quizRelase"; import { isProduction } from "@/utils/defineDomain"; +import { useQuizStore } from "@/stores/useQuizStore"; import { useTranslation } from "react-i18next"; type ResultFormProps = { @@ -29,7 +29,7 @@ export const ResultForm = ({ resultQuestion }: ResultFormProps) => { const theme = useTheme(); const isMobile = useRootContainerSize() < 650; const isTablet = useRootContainerSize() < 1000; - const { settings, show_badge, quizId, questions, preview } = useQuizSettings(); + const { settings, show_badge, quizId, questions, preview } = useQuizStore(); const setCurrentQuizStep = useQuizViewStore((state) => state.setCurrentQuizStep); //Список засчитанных баллов для балловых квизов const pointsSum = useQuizViewStore((state) => state.pointsSum); diff --git a/lib/components/ViewPublicationPage/StartPageViewPublication/StartPageDesktop.tsx b/lib/components/ViewPublicationPage/StartPageViewPublication/StartPageDesktop.tsx index 514dbce..6e8d3b6 100644 --- a/lib/components/ViewPublicationPage/StartPageViewPublication/StartPageDesktop.tsx +++ b/lib/components/ViewPublicationPage/StartPageViewPublication/StartPageDesktop.tsx @@ -1,7 +1,7 @@ import { Box } from "@mui/material"; import { useRootContainerSize } from "@contexts/RootContainerWidthContext"; -import { useQuizSettings } from "@contexts/QuizDataContext"; +import { useQuizStore } from "@/stores/useQuizStore"; import { notReachable } from "@utils/notReachable"; import { quizThemes } from "@utils/themes/Publication/themePublication"; @@ -22,7 +22,7 @@ type LayoutProps = Omit; const StandartLayout = ({ alignType, quizHeaderBlock, quizMainBlock, backgroundBlock }: LayoutProps) => { const size = useRootContainerSize(); const isTablet = size >= 700 && size < 1100; - const { settings } = useQuizSettings(); + const { settings } = useQuizStore(); return ( { const isTablet = useRootContainerSize() < 1100; - const { settings } = useQuizSettings(); + const { settings } = useQuizStore(); return ( ; const StandartMobileLayout = ({ quizHeaderBlock, quizMainBlock, backgroundBlock }: MobileLayoutProps) => { - const { settings } = useQuizSettings(); + const { settings } = useQuizStore(); return ( { - const { settings } = useQuizSettings(); + const { settings } = useQuizStore(); return ( { const theme = useTheme(); - const { settings, show_badge, quizId, questions } = useQuizSettings(); + const { settings, show_badge, quizId, questions } = useQuizStore(); const { isMobileDevice } = useUADevice(); const setCurrentQuizStep = useQuizViewStore((state) => state.setCurrentQuizStep); diff --git a/lib/components/ViewPublicationPage/ViewPublicationPage.tsx b/lib/components/ViewPublicationPage/ViewPublicationPage.tsx index 76e40e7..70763eb 100644 --- a/lib/components/ViewPublicationPage/ViewPublicationPage.tsx +++ b/lib/components/ViewPublicationPage/ViewPublicationPage.tsx @@ -3,7 +3,6 @@ import { extractImageLinksFromQuestion } from "@/utils/extractImageLinks"; import { useVKMetrics } from "@/utils/hooks/metrics/useVKMetrics"; import { useYandexMetrics } from "@/utils/hooks/metrics/useYandexMetrics"; import { sendQuestionAnswer } from "@/utils/sendQuestionAnswer"; -import { useQuizSettings } from "@contexts/QuizDataContext"; import { ThemeProvider, Typography } from "@mui/material"; import { useQuizViewStore } from "@stores/quizView"; import { useQuestionFlowControl } from "@utils/hooks/useQuestionFlowControl"; @@ -19,9 +18,10 @@ import { StartPageViewPublication } from "./StartPageViewPublication"; import NextButton from "./tools/NextButton"; import PrevButton from "./tools/PrevButton"; import unscreen from "@/ui_kit/unscreen"; +import { useQuizStore } from "@/stores/useQuizStore"; export default function ViewPublicationPage() { - const { settings, recentlyCompleted, quizId, preview, changeFaviconAndTitle, questions } = useQuizSettings(); + const { settings, recentlyCompleted, quizId, preview, changeFaviconAndTitle } = useQuizStore(); const answers = useQuizViewStore((state) => state.answers); const ownVariants = useQuizViewStore((state) => state.ownVariants); let currentQuizStep = useQuizViewStore((state) => state.currentQuizStep); @@ -106,16 +106,15 @@ export default function ViewPublicationPage() { } nextButton={ { + isNextButtonEnabled={settings.status === "ai" || isNextButtonEnabled} + moveToNextQuestion={async () => { + if (!preview) { + await sendQuestionAnswer(quizId, currentQuestion, currentAnswer, ownVariants)?.catch((e) => { + enqueueSnackbar("Ошибка при отправке ответа"); + console.error("Error sending answer", e); + }); + } moveToNextQuestion(); - - if (preview) return; - - sendQuestionAnswer(quizId, currentQuestion, currentAnswer, ownVariants)?.catch((e) => { - enqueueSnackbar("Ошибка при отправке ответа"); - console.error("Error sending answer", e); - }); }} /> } diff --git a/lib/components/ViewPublicationPage/questions/Date/DatePicker.tsx b/lib/components/ViewPublicationPage/questions/Date/DatePicker.tsx index 7a42ae0..a388055 100644 --- a/lib/components/ViewPublicationPage/questions/Date/DatePicker.tsx +++ b/lib/components/ViewPublicationPage/questions/Date/DatePicker.tsx @@ -1,5 +1,5 @@ import { useQuizViewStore } from "@/stores/quizView"; -import { useQuizSettings } from "@contexts/QuizDataContext"; +import { useQuizStore } from "@/stores/useQuizStore"; import CalendarIcon from "@icons/CalendarIcon"; import type { QuizQuestionDate } from "@model/questionTypes/date"; import { Box, Typography, useTheme } from "@mui/material"; @@ -13,7 +13,7 @@ type DateProps = { }; export default ({ currentQuestion }: DateProps) => { - const { settings } = useQuizSettings(); + const { settings } = useQuizStore(); const answers = useQuizViewStore((state) => state.answers); const { updateAnswer } = useQuizViewStore((state) => state); const theme = useTheme(); diff --git a/lib/components/ViewPublicationPage/questions/Date/DateRange.tsx b/lib/components/ViewPublicationPage/questions/Date/DateRange.tsx index 6e3e30a..881b16f 100644 --- a/lib/components/ViewPublicationPage/questions/Date/DateRange.tsx +++ b/lib/components/ViewPublicationPage/questions/Date/DateRange.tsx @@ -1,4 +1,3 @@ -import { useQuizSettings } from "@/contexts/QuizDataContext"; import { useQuizViewStore } from "@/stores/quizView"; import type { QuizQuestionDate } from "@model/questionTypes/date"; import { DateCalendar } from "@mui/x-date-pickers"; @@ -7,6 +6,7 @@ import type { Moment } from "moment"; import moment from "moment"; import { Box, Paper, TextField, useTheme } from "@mui/material"; import { useRootContainerSize } from "@/contexts/RootContainerWidthContext"; +import { useQuizStore } from "@/stores/useQuizStore"; import { useTranslation } from "react-i18next"; type DateProps = { @@ -17,7 +17,7 @@ export default ({ currentQuestion }: DateProps) => { const theme = useTheme(); const today = moment(); const isMobile = useRootContainerSize() < 690; - const { settings } = useQuizSettings(); + const { settings } = useQuizStore(); const { updateAnswer } = useQuizViewStore((state) => state); const { t } = useTranslation(); diff --git a/lib/components/ViewPublicationPage/questions/Emoji/EmojiVariant.tsx b/lib/components/ViewPublicationPage/questions/Emoji/EmojiVariant.tsx index 28c9392..081de0a 100644 --- a/lib/components/ViewPublicationPage/questions/Emoji/EmojiVariant.tsx +++ b/lib/components/ViewPublicationPage/questions/Emoji/EmojiVariant.tsx @@ -1,5 +1,5 @@ import type { QuestionVariant } from "@/model/questionTypes/shared"; -import { useQuizSettings } from "@contexts/QuizDataContext"; +import { useQuizStore } from "@/stores/useQuizStore"; import { Box, Checkbox, @@ -105,7 +105,7 @@ export const EmojiVariant = ({ questionLargeCheck, ownPlaceholder, }: EmojiVariantProps) => { - const { settings } = useQuizSettings(); + const { settings } = useQuizStore(); const answers = useQuizViewStore((state) => state.answers); const { updateAnswer, deleteAnswer } = useQuizViewStore((state) => state); const theme = useTheme(); diff --git a/lib/components/ViewPublicationPage/questions/File/UploadFile.tsx b/lib/components/ViewPublicationPage/questions/File/UploadFile.tsx index 810c78e..98be396 100644 --- a/lib/components/ViewPublicationPage/questions/File/UploadFile.tsx +++ b/lib/components/ViewPublicationPage/questions/File/UploadFile.tsx @@ -3,7 +3,6 @@ import { Box, ButtonBase, Skeleton, Typography, useTheme } from "@mui/material"; import { enqueueSnackbar } from "notistack"; import { sendAnswer, sendFile } from "@api/quizRelase"; -import { useQuizSettings } from "@contexts/QuizDataContext"; import { useRootContainerSize } from "@contexts/RootContainerWidthContext"; import { useQuizViewStore } from "@stores/quizView"; @@ -18,6 +17,7 @@ import UploadIcon from "@icons/UploadIcon"; import type { QuizQuestionFile } from "@model/questionTypes/file"; import type { ModalWarningType } from "./index"; +import { useQuizStore } from "@/stores/useQuizStore"; import { useTranslation } from "react-i18next"; type UploadFileProps = { @@ -28,7 +28,7 @@ type UploadFileProps = { }; export const UploadFile = ({ currentQuestion, setModalWarningType, isSending, setIsSending }: UploadFileProps) => { - const { quizId, preview } = useQuizSettings(); + const { quizId, preview } = useQuizStore(); const [isDropzoneHighlighted, setIsDropzoneHighlighted] = useState(false); const theme = useTheme(); const { t } = useTranslation(); diff --git a/lib/components/ViewPublicationPage/questions/File/UploadedFile.tsx b/lib/components/ViewPublicationPage/questions/File/UploadedFile.tsx index 3c08d31..54e167c 100644 --- a/lib/components/ViewPublicationPage/questions/File/UploadedFile.tsx +++ b/lib/components/ViewPublicationPage/questions/File/UploadedFile.tsx @@ -1,12 +1,12 @@ import { Box, IconButton, Typography, useTheme } from "@mui/material"; import { sendAnswer } from "@api/quizRelase"; -import { useQuizSettings } from "@contexts/QuizDataContext"; import { useQuizViewStore } from "@stores/quizView"; import CloseBold from "@icons/CloseBold"; import type { QuizQuestionFile } from "@model/questionTypes/file"; +import { useQuizStore } from "@/stores/useQuizStore"; import { useTranslation } from "react-i18next"; type UploadedFileProps = { @@ -15,7 +15,7 @@ type UploadedFileProps = { }; export const UploadedFile = ({ currentQuestion, setIsSending }: UploadedFileProps) => { - const { quizId, preview } = useQuizSettings(); + const { quizId, preview } = useQuizStore(); const answers = useQuizViewStore((state) => state.answers); const { updateAnswer } = useQuizViewStore((state) => state); const theme = useTheme(); diff --git a/lib/components/ViewPublicationPage/questions/Images/ImageVariant.tsx b/lib/components/ViewPublicationPage/questions/Images/ImageVariant.tsx index b25540e..3d98f6a 100644 --- a/lib/components/ViewPublicationPage/questions/Images/ImageVariant.tsx +++ b/lib/components/ViewPublicationPage/questions/Images/ImageVariant.tsx @@ -1,6 +1,5 @@ import { CheckboxIcon } from "@/assets/icons/Checkbox"; import type { QuestionVariant, QuestionVariantWithEditedImages } from "@/model/questionTypes/shared"; -import { useQuizSettings } from "@contexts/QuizDataContext"; import { Box, Checkbox, FormControlLabel, Input, Radio, TextareaAutosize, Typography, useTheme } from "@mui/material"; import { useQuizViewStore } from "@stores/quizView"; import RadioCheck from "@ui_kit/RadioCheck"; @@ -8,6 +7,7 @@ import RadioIcon from "@ui_kit/RadioIcon"; import { quizThemes } from "@utils/themes/Publication/themePublication"; import { useMemo, type MouseEvent, useRef, useEffect } from "react"; import { useRootContainerSize } from "@contexts/RootContainerWidthContext"; +import { useQuizStore } from "@/stores/useQuizStore"; import { useTranslation } from "react-i18next"; type ImagesProps = { @@ -93,7 +93,7 @@ export const ImageVariant = ({ questionLargeCheck, ownPlaceholder, }: ImagesProps) => { - const { settings } = useQuizSettings(); + const { settings } = useQuizStore(); const { deleteAnswer, updateAnswer } = useQuizViewStore((state) => state); const theme = useTheme(); const { t } = useTranslation(); diff --git a/lib/components/ViewPublicationPage/questions/Number/index.tsx b/lib/components/ViewPublicationPage/questions/Number/index.tsx index fc403c5..acdadd7 100644 --- a/lib/components/ViewPublicationPage/questions/Number/index.tsx +++ b/lib/components/ViewPublicationPage/questions/Number/index.tsx @@ -1,4 +1,4 @@ -import { useQuizSettings } from "@contexts/QuizDataContext"; +import { useQuizStore } from "@/stores/useQuizStore"; import type { QuizQuestionNumber } from "@model/questionTypes/number"; import { Box, Typography, useTheme } from "@mui/material"; import { useQuizViewStore } from "@stores/quizView"; @@ -20,7 +20,7 @@ export const Number = ({ currentQuestion }: NumberProps) => { const [reversedInputValue, setReversedInputValue] = useState("0"); const [reversedMinRange, setReversedMinRange] = useState("0"); const [reversedMaxRange, setReversedMaxRange] = useState("100000000000"); - const { settings } = useQuizSettings(); + const { settings } = useQuizStore(); const { updateAnswer } = useQuizViewStore((state) => state); const answers = useQuizViewStore((state) => state.answers); const theme = useTheme(); diff --git a/lib/components/ViewPublicationPage/questions/Select/index.tsx b/lib/components/ViewPublicationPage/questions/Select/index.tsx index e624e7a..437e4f3 100644 --- a/lib/components/ViewPublicationPage/questions/Select/index.tsx +++ b/lib/components/ViewPublicationPage/questions/Select/index.tsx @@ -1,5 +1,5 @@ import { Select as SelectComponent } from "@/components/ViewPublicationPage/tools/Select"; -import { useQuizSettings } from "@contexts/QuizDataContext"; +import { useQuizStore } from "@/stores/useQuizStore"; import type { QuizQuestionSelect } from "@model/questionTypes/select"; import { Box, Typography, useTheme } from "@mui/material"; import { useQuizViewStore } from "@stores/quizView"; @@ -10,7 +10,7 @@ type SelectProps = { }; export const Select = ({ currentQuestion }: SelectProps) => { - const { settings } = useQuizSettings(); + const { settings } = useQuizStore(); const { updateAnswer, deleteAnswer } = useQuizViewStore((state) => state); const answers = useQuizViewStore((state) => state.answers); const theme = useTheme(); diff --git a/lib/components/ViewPublicationPage/questions/Text/TextNormal.tsx b/lib/components/ViewPublicationPage/questions/Text/TextNormal.tsx index 21d08cc..9f40523 100644 --- a/lib/components/ViewPublicationPage/questions/Text/TextNormal.tsx +++ b/lib/components/ViewPublicationPage/questions/Text/TextNormal.tsx @@ -3,13 +3,13 @@ import { Box, Typography, useTheme } from "@mui/material"; import CustomTextField from "@ui_kit/CustomTextField"; import { Answer, useQuizViewStore } from "@stores/quizView"; -import { useQuizSettings } from "@contexts/QuizDataContext"; import { useRootContainerSize } from "@contexts/RootContainerWidthContext"; import { quizThemes } from "@utils/themes/Publication/themePublication"; import { useMemo, type ChangeEvent } from "react"; import type { QuizQuestionText } from "@model/questionTypes/text"; +import { useQuizStore } from "@/stores/useQuizStore"; interface TextNormalProps { currentQuestion: QuizQuestionText; @@ -18,7 +18,7 @@ interface TextNormalProps { } export const TextNormal = ({ currentQuestion, answer }: TextNormalProps) => { - const { settings } = useQuizSettings(); + const { settings } = useQuizStore(); const { updateAnswer } = useQuizViewStore((state) => state); const isMobile = useRootContainerSize() < 650; const isTablet = useRootContainerSize() < 850; diff --git a/lib/components/ViewPublicationPage/questions/Text/TextSpecial.tsx b/lib/components/ViewPublicationPage/questions/Text/TextSpecial.tsx index c58bf26..5710a2d 100644 --- a/lib/components/ViewPublicationPage/questions/Text/TextSpecial.tsx +++ b/lib/components/ViewPublicationPage/questions/Text/TextSpecial.tsx @@ -1,13 +1,13 @@ import { Box, TextField as MuiTextField, TextFieldProps, Typography, useTheme } from "@mui/material"; import { Answer, useQuizViewStore } from "@stores/quizView"; -import { useQuizSettings } from "@contexts/QuizDataContext"; import { useRootContainerSize } from "@contexts/RootContainerWidthContext"; import { quizThemes } from "@utils/themes/Publication/themePublication"; import type { ChangeEvent, FC } from "react"; import type { QuizQuestionText } from "@model/questionTypes/text"; +import { useQuizStore } from "@/stores/useQuizStore"; const TextField = MuiTextField as unknown as FC; // temporary fix ts(2590) @@ -45,7 +45,7 @@ interface TextSpecialProps { } export const TextSpecial = ({ currentQuestion, answer, stepNumber }: TextSpecialProps) => { - const { settings } = useQuizSettings(); + const { settings } = useQuizStore(); const { updateAnswer } = useQuizViewStore((state) => state); const isHorizontal = ORIENTATION[Number(stepNumber) - 1].horizontal; const theme = useTheme(); diff --git a/lib/components/ViewPublicationPage/questions/Text/TextSpecialHorisontal.tsx b/lib/components/ViewPublicationPage/questions/Text/TextSpecialHorisontal.tsx index 7171a81..2d06c2b 100644 --- a/lib/components/ViewPublicationPage/questions/Text/TextSpecialHorisontal.tsx +++ b/lib/components/ViewPublicationPage/questions/Text/TextSpecialHorisontal.tsx @@ -1,13 +1,13 @@ import { Box, TextField as MuiTextField, TextFieldProps, Typography, useTheme } from "@mui/material"; import { Answer, useQuizViewStore } from "@stores/quizView"; -import { useQuizSettings } from "@contexts/QuizDataContext"; import { useRootContainerSize } from "@contexts/RootContainerWidthContext"; import { quizThemes } from "@utils/themes/Publication/themePublication"; import type { ChangeEvent, FC } from "react"; import type { QuizQuestionText } from "@model/questionTypes/text"; +import { useQuizStore } from "@/stores/useQuizStore"; const TextField = MuiTextField as unknown as FC; // temporary fix ts(2590) @@ -18,7 +18,7 @@ interface TextSpecialProps { } export const TextSpecialHorisontal = ({ currentQuestion, answer, stepNumber }: TextSpecialProps) => { - const { settings } = useQuizSettings(); + const { settings } = useQuizStore(); const { updateAnswer } = useQuizViewStore((state) => state); const isHorizontal = true; const theme = useTheme(); diff --git a/lib/components/ViewPublicationPage/questions/Text/index.tsx b/lib/components/ViewPublicationPage/questions/Text/index.tsx index f0bc528..cabe54d 100644 --- a/lib/components/ViewPublicationPage/questions/Text/index.tsx +++ b/lib/components/ViewPublicationPage/questions/Text/index.tsx @@ -1,10 +1,10 @@ -import { useQuizSettings } from "@contexts/QuizDataContext"; import { useQuizViewStore } from "@stores/quizView"; import { TextNormal } from "./TextNormal"; import { TextSpecial } from "./TextSpecial"; import { TextSpecialHorisontal } from "./TextSpecialHorisontal"; import type { QuizQuestionText } from "@model/questionTypes/text"; +import { useQuizStore } from "@/stores/useQuizStore"; type TextProps = { currentQuestion: QuizQuestionText; @@ -14,7 +14,7 @@ type TextProps = { const pathOnly = window.location.pathname; export const Text = ({ currentQuestion, stepNumber }: TextProps) => { - const { settings } = useQuizSettings(); + const { settings } = useQuizStore(); const answers = useQuizViewStore((state) => state.answers); const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {}; diff --git a/lib/components/ViewPublicationPage/questions/Variant/VariantItem.tsx b/lib/components/ViewPublicationPage/questions/Variant/VariantItem.tsx index 5cc2258..a56ce2e 100644 --- a/lib/components/ViewPublicationPage/questions/Variant/VariantItem.tsx +++ b/lib/components/ViewPublicationPage/questions/Variant/VariantItem.tsx @@ -1,5 +1,4 @@ -import { useQuizSettings } from "@contexts/QuizDataContext"; -import { CheckboxIcon } from "@icons/Checkbox"; +import { useQuizStore } from "@/stores/useQuizStore"; import type { QuestionVariant } from "@model/questionTypes/shared"; import { Checkbox, @@ -101,7 +100,7 @@ export const VariantItem = ({ questionLargeCheck: boolean; ownPlaceholder: string; }) => { - const { settings } = useQuizSettings(); + const { settings } = useQuizStore(); const theme = useTheme(); const { updateAnswer, deleteAnswer } = useQuizViewStore((state) => state); const { t } = useTranslation(); diff --git a/lib/components/ViewPublicationPage/questions/Varimg/VarimgVariant.tsx b/lib/components/ViewPublicationPage/questions/Varimg/VarimgVariant.tsx index 6d10011..d1c5cfa 100644 --- a/lib/components/ViewPublicationPage/questions/Varimg/VarimgVariant.tsx +++ b/lib/components/ViewPublicationPage/questions/Varimg/VarimgVariant.tsx @@ -1,5 +1,5 @@ import type { QuestionVariant, QuestionVariantWithEditedImages } from "@/model/questionTypes/shared"; -import { useQuizSettings } from "@contexts/QuizDataContext"; +import { useQuizStore } from "@/stores/useQuizStore"; import { FormControlLabel, TextareaAutosize, @@ -104,8 +104,9 @@ export const VarimgVariant = ({ answer, }: VarimgVariantProps) => { const theme = useTheme(); + + const { settings } = useQuizStore(); const { t } = useTranslation(); - const { settings } = useQuizSettings(); const { updateAnswer, deleteAnswer } = useQuizViewStore((state) => state); const sendVariant = async (event: MouseEvent) => { diff --git a/lib/components/ViewPublicationPage/tools/NextButton.tsx b/lib/components/ViewPublicationPage/tools/NextButton.tsx index f109ec0..5ed3b93 100644 --- a/lib/components/ViewPublicationPage/tools/NextButton.tsx +++ b/lib/components/ViewPublicationPage/tools/NextButton.tsx @@ -1,4 +1,4 @@ -import { useQuizSettings } from "@contexts/QuizDataContext"; +import { useQuizStore } from "@/stores/useQuizStore"; import { Button } from "@mui/material"; import { quizThemes } from "@utils/themes/Publication/themePublication"; import { useTranslation } from "react-i18next"; @@ -9,7 +9,7 @@ interface Props { } export default function NextButton({ isNextButtonEnabled, moveToNextQuestion }: Props) { - const { settings } = useQuizSettings(); + const { settings } = useQuizStore(); const { t } = useTranslation(); return ( @@ -25,7 +25,7 @@ export default function NextButton({ isNextButtonEnabled, moveToNextQuestion }: }} onClick={moveToNextQuestion} > - {t("Next")} → + далее →{/* {t("Next")} → (*.*) */} ); } diff --git a/lib/components/ViewPublicationPage/tools/PrevButton.tsx b/lib/components/ViewPublicationPage/tools/PrevButton.tsx index 1f77569..81de738 100644 --- a/lib/components/ViewPublicationPage/tools/PrevButton.tsx +++ b/lib/components/ViewPublicationPage/tools/PrevButton.tsx @@ -1,7 +1,7 @@ import { Button, useTheme } from "@mui/material"; import { useRootContainerSize } from "../../../contexts/RootContainerWidthContext"; import { quizThemes } from "@utils/themes/Publication/themePublication"; -import { useQuizSettings } from "@contexts/QuizDataContext"; +import { useQuizStore } from "@/stores/useQuizStore"; import { useTranslation } from "react-i18next"; interface Props { @@ -11,7 +11,7 @@ interface Props { export default function PrevButton({ isPreviousButtonEnabled, moveToPrevQuestion }: Props) { const theme = useTheme(); - const { settings } = useQuizSettings(); + const { settings } = useQuizStore(); const isMobileMini = useRootContainerSize() < 382; const { t } = useTranslation(); return ( @@ -37,7 +37,8 @@ export default function PrevButton({ isPreviousButtonEnabled, moveToPrevQuestion }} onClick={moveToPrevQuestion} > - {isMobileMini ? "←" : `← ${t("Prev")}`} + {isMobileMini ? "←" : `← назад`} + {/* {isMobileMini ? "←" : `← ${t("Prev")}`} (*.*) */} ); } diff --git a/lib/contexts/QuizDataContext.ts b/lib/contexts/QuizDataContext.ts deleted file mode 100644 index 95d39fe..0000000 --- a/lib/contexts/QuizDataContext.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { QuizSettings } from "@model/settingsData"; -import { createContext, useContext } from "react"; - -export const QuizSettingsContext = createContext< - | (QuizSettings & { - quizId: string; - preview: boolean; - changeFaviconAndTitle: boolean; - }) - | null ->(null); - -export const useQuizSettings = () => { - const quizSettings = useContext(QuizSettingsContext); - if (quizSettings === null) throw new Error("QuizSettings context is null"); - - return quizSettings; -}; diff --git a/lib/model/api/getQuizData.ts b/lib/model/api/getQuizData.ts index 21f685c..384ef1b 100644 --- a/lib/model/api/getQuizData.ts +++ b/lib/model/api/getQuizData.ts @@ -12,6 +12,7 @@ export interface GetQuizDataResponse { due: number; delay: number; pausable: boolean; + status: "start" | "stop" | "ai"; }; items: { id: number; @@ -49,6 +50,7 @@ export function parseQuizData(quizDataResponse: GetQuizDataResponse): Omit(() => ({ + settings: {} as QuizSettingsConfig, + questions: [], + quizId: "", + preview: false, + changeFaviconAndTitle: false, + cnt: 0, + recentlyCompleted: false, + show_badge: false, +})); + +export const setQuizData = (data: QuizSettings) => { + console.log("zusstand"); + console.log(data); + useQuizStore.setState((state: QuizStore) => ({ ...state, ...data })); +}; +export const addQuestion = (newQuestion: AnyTypedQuizQuestion) => + useQuizStore.setState( + produce((state: QuizStore) => { + state.questions.push(newQuestion); + }) + ); +export const addquizid = (id: string) => + useQuizStore.setState( + produce((state: QuizStore) => { + state.quizId = id; + }) + ); diff --git a/lib/utils/hooks/useQuestionFlowControl.ts b/lib/utils/hooks/useQuestionFlowControl.ts index 821d6c3..7bda474 100644 --- a/lib/utils/hooks/useQuestionFlowControl.ts +++ b/lib/utils/hooks/useQuestionFlowControl.ts @@ -1,18 +1,31 @@ -import { useCallback, useDebugValue, useMemo, useState } from "react"; +import { useCallback, useDebugValue, useEffect, useMemo, useState } from "react"; import { enqueueSnackbar } from "notistack"; import moment from "moment"; import { isResultQuestionEmpty } from "@/components/ViewPublicationPage/tools/checkEmptyData"; -import { useQuizSettings } from "@contexts/QuizDataContext"; +import { useQuizStore } from "@/stores/useQuizStore"; import { useQuizViewStore } from "@stores/quizView"; 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 } = useQuizSettings(); + const { loadMoreQuestions } = useQuizGetNext(); + const { settings, questions, quizId, cnt } = useQuizStore(); + + useEffect(() => { + console.log("useQuestionFlowControl useEffect"); + console.log(questions); + }, [questions]); + console.log(questions); + //Когда квиз линейный, не ветвящийся, мы идём по вопросам по их порядковому номеру. Это их page. //За корректность page отвечает конструктор квизов. Интересный факт, если в конструкторе удалить из середины вопрос, то случится куча запросов изменения вопросов с изменением этого page const sortedQuestions = useMemo(() => { @@ -20,6 +33,7 @@ export function useQuestionFlowControl() { }, [questions]); //React сам будет менять визуал - главное говорить из какого вопроса ему брать инфо. Изменение этой переменной меняет визуал. const [currentQuestionId, setCurrentQuestionId] = useState(getFirstQuestionId); + const [headAI, setHeadAI] = useState(0); //Список ответов на вопрос. Мы записываем ответы локально, параллельно отправляя на бек информацию о ответах const answers = useQuizViewStore((state) => state.answers); //Список засчитанных баллов для балловых квизов @@ -33,7 +47,10 @@ export function useQuestionFlowControl() { //Изменение стейта (переменной currentQuestionId) ведёт к пересчёту что же за объект сейчас используется. Мы каждый раз просто ищем в списке const currentQuestion = sortedQuestions.find((question) => question.id === currentQuestionId) ?? sortedQuestions[0]; - // console.log(currentQuestion) + console.log("currentQuestion"); + console.log(currentQuestion); + console.log("filted"); + console.log(sortedQuestions.find((question) => question.id === currentQuestionId)); //Индекс текущего вопроса только если квиз линейный const linearQuestionIndex = //: number | null @@ -113,7 +130,7 @@ export function useQuestionFlowControl() { return nextQuestionIdPointsLogic(); } return nextQuestionIdMainLogic(); - }, [nextQuestionIdMainLogic, nextQuestionIdPointsLogic, settings.cfg.score]); + }, [nextQuestionIdMainLogic, nextQuestionIdPointsLogic, settings.cfg.score, questions]); //Поиск предыдущго вопроса либо по индексу либо по id родителя const prevQuestion = @@ -196,21 +213,51 @@ export function useQuestionFlowControl() { const moveToPrevQuestion = useCallback(() => { if (!prevQuestion) throw new Error("Previous question not found"); + if (settings.status === "ai" && headAI > 0) setHeadAI((old) => old--); setCurrentQuestionId(prevQuestion.id); }, [prevQuestion]); //рычаг управления из визуала в эту функцию - const moveToNextQuestion = useCallback(() => { - if (!nextQuestion) throw new Error("Next question not found"); + const moveToNextQuestion = useCallback(async () => { + // Если есть следующий вопрос в уже загруженных - используем его - // Засчитываем переход с вопроса дальше - vkMetrics.questionPassed(currentQuestion.id); - yandexMetrics.questionPassed(currentQuestion.id); + if (nextQuestion) { + vkMetrics.questionPassed(currentQuestion.id); + yandexMetrics.questionPassed(currentQuestion.id); - if (nextQuestion.type === "result") return showResult(); + if (nextQuestion.type === "result") return showResult(); + setCurrentQuestionId(nextQuestion.id); + return; + } - setCurrentQuestionId(nextQuestion.id); - }, [currentQuestion.id, nextQuestion, showResult, vkMetrics, yandexMetrics]); + // Если следующего нет - загружаем новый + try { + const newQuestion = await loadMoreQuestions(); + console.log("Ффункция некст вопрос получила его с бека: "); + console.log(newQuestion); + if (newQuestion) { + vkMetrics.questionPassed(currentQuestion.id); + yandexMetrics.questionPassed(currentQuestion.id); + console.log("МЫ ПАЛУЧИЛИ НОВЫЙ ВОПРОС"); + console.log(newQuestion); + console.log("typeof newQuestion.id"); + console.log(typeof newQuestion.id); + setCurrentQuestionId(newQuestion.id); + setHeadAI((old) => old++); + } + } catch (error) { + enqueueSnackbar("Ошибка загрузки следующего вопроса"); + } + }, [ + currentQuestion.id, + nextQuestion, + showResult, + vkMetrics, + yandexMetrics, + linearQuestionIndex, + loadMoreQuestions, + questions, + ]); //рычаг управления из визуала в эту функцию const setQuestion = useCallback( @@ -234,6 +281,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]); @@ -246,7 +297,8 @@ export function useQuestionFlowControl() { return { currentQuestion, - currentQuestionStepNumber: linearQuestionIndex === null ? null : linearQuestionIndex + 1, + currentQuestionStepNumber: + settings.status === "ai" ? null : linearQuestionIndex === null ? null : linearQuestionIndex + 1, nextQuestion, isNextButtonEnabled, isPreviousButtonEnabled, diff --git a/lib/utils/sendQuestionAnswer.ts b/lib/utils/sendQuestionAnswer.ts index 7ed7e48..4b018ac 100644 --- a/lib/utils/sendQuestionAnswer.ts +++ b/lib/utils/sendQuestionAnswer.ts @@ -4,7 +4,7 @@ import { OwnVariant, QuestionAnswer, createQuizViewStore } from "@/stores/quizVi import moment from "moment"; import { notReachable } from "./notReachable"; -export function sendQuestionAnswer( +export async function sendQuestionAnswer( quizId: string, question: RealTypedQuizQuestion, questionAnswer: QuestionAnswer | undefined, diff --git a/public/locales/en.json b/public/locales/en.json index e52ed5c..3d15931 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -52,5 +52,7 @@ "familiarized": "acknowledged", "and": "and", "Get results": "Get results", - "Step": "Step" + "Step": "Step", + "D": "-_-", + "d": "-_-" } diff --git a/public/locales/ru.json b/public/locales/ru.json index 030f191..7481429 100644 --- a/public/locales/ru.json +++ b/public/locales/ru.json @@ -52,5 +52,7 @@ "familiarized": "ознакомлен", "and": "и", "Get results": "Получить результаты", - "Step": "Шаг" + "Step": "Шаг", + "D": "-_-", + "d": "-_-" } diff --git a/public/locales/uz.json b/public/locales/uz.json index 0f5cde1..5ee2b01 100644 --- a/public/locales/uz.json +++ b/public/locales/uz.json @@ -52,5 +52,7 @@ "familiarized": "tanishdim", "and": "va", "Get results": "Natijalarni olish", - "Step": "Qadam" + "Step": "Qadam", + "D": "-_-", + "d": "-_-" } diff --git a/src/i18n/i18n.ts b/src/i18n/i18n.ts index b6bd614..e4f8067 100644 --- a/src/i18n/i18n.ts +++ b/src/i18n/i18n.ts @@ -42,6 +42,18 @@ i18n lookupFromPathIndex: 0, caches: [], // Не использовать localStorage }, + parseMissingKeyHandler: (key) => { + console.warn("Missing translation:", key); + return key; // Вернёт ключ вместо ошибки + }, + missingKeyHandler: (lngs, ns, key) => { + console.error("🚨 Missing i18n key:", { + key, + languages: lngs, + namespace: ns, + stack: new Error().stack, // Выведет стек вызовов + }); + }, }) .then(() => { //console.log("i18n инициализирован! Текущий язык:", i18n.language);