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..f72ef9c 100644 --- a/lib/api/quizRelase.ts +++ b/lib/api/quizRelase.ts @@ -72,60 +72,129 @@ 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) { - throw response.error; + const axiosError = response.error as AxiosError; + if (axiosError.response?.data) { + throw new Error( + typeof axiosError.response.data === "string" + ? axiosError.response.data + : JSON.stringify(axiosError.response.data) + ); + } + throw axiosError; } if (!quizDataResponse) { throw new Error("Quiz not found"); @@ -142,6 +211,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 +337,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/assets/icons/NameplateLogo.tsx b/lib/assets/icons/NameplateLogo.tsx index 097c662..8558c5f 100644 --- a/lib/assets/icons/NameplateLogo.tsx +++ b/lib/assets/icons/NameplateLogo.tsx @@ -1,7 +1,77 @@ import { FC, SVGProps } from "react"; +import { useLocation } from "react-router-dom"; -export const NameplateLogo: FC> = (props) => ( - +export const NameplateLogo: FC> = (props) => { + const location = useLocation(); + const pathname = location.pathname; + + const getLanguageFromUrl = () => { + const parts = pathname.split("/"); + if (parts.length >= 2) { + const langSegment = parts[1].toLowerCase(); + if (langSegment === "en") return "en"; + if (langSegment === "uz") return "uz"; + } + return "ru"; + }; + + const lang = getLanguageFromUrl(); // Оптимизация - вызываем функцию один раз + + if (lang === "ru") return ; + if (lang === "en") return ; + if (lang === "uz") return ; + + return ; // Fallback +}; + +const UZ: FC> = (props) => ( + + + + + + + +); +const RU: FC> = (props) => ( + > = (props) => ( d="M8.54763 0.0219046C6.0113 -0.271892 4.26146 2.45258 2.63386 4.42198C1.20548 6.15032 0.14193 8.10248 0.0183964 10.3423C-0.113033 12.7252 0.319225 15.2068 1.94263 16.9545C3.61482 18.7547 6.1197 19.8922 8.54763 19.5244C10.774 19.1872 11.7591 16.7423 13.4166 15.2166C15.2164 13.56 18.5695 12.7894 18.523 10.3423C18.4764 7.89135 14.9376 7.41159 13.2348 5.64994C11.4711 3.8252 11.0672 0.313757 8.54763 0.0219046Z" fill="#7E2AEA" /> - - + + ); +const EN: FC> = (props) => ( + + + + + + + +); diff --git a/lib/assets/icons/NameplateLogoFQ.tsx b/lib/assets/icons/NameplateLogoFQ.tsx index 5aa4dd3..d94fcb7 100644 --- a/lib/assets/icons/NameplateLogoFQ.tsx +++ b/lib/assets/icons/NameplateLogoFQ.tsx @@ -1,8 +1,184 @@ import { FC, SVGProps } from "react"; +import { useLocation } from "react-router-dom"; -export const NameplateLogoFQ: FC> = (props) => ( - - +export const NameplateLogoFQ: FC> = (props) => { + const location = useLocation(); + const pathname = location.pathname; + + const getLanguageFromUrl = () => { + const parts = pathname.split("/"); + if (parts.length >= 2) { + const langSegment = parts[1].toLowerCase(); + if (langSegment === "en") return "en"; + if (langSegment === "uz") return "uz"; + } + return "ru"; + }; + + const lang = getLanguageFromUrl(); // Оптимизация - вызываем функцию один раз + + if (lang === "ru") return ; + if (lang === "en") return ; + if (lang === "uz") return ; + + return ; // Fallback +}; + +const UZ: FC> = (props) => ( + + + + + + + + + + + + + + + + + + + +); +const EN: FC> = (props) => ( + + + + + + + + + + + + + + + + + + + +); +const RU: FC> = (props) => ( + + > = (props) => ( d="M461.886 33.8256C453.446 32.8479 447.622 41.9149 442.205 48.4691C437.452 54.221 433.912 60.7178 433.501 68.1717C433.064 76.1021 434.502 84.3611 439.905 90.1773C445.47 96.1684 453.806 99.9538 461.886 98.7299C469.296 97.6077 472.574 89.4712 478.091 84.3936C484.08 78.8802 495.239 76.3159 495.084 68.1717C494.929 60.0151 483.152 58.4185 477.485 52.5557C471.616 46.483 470.272 34.7969 461.886 33.8256Z" fill="#7E2AEA" /> - - + + > = (props) => ( - + diff --git a/lib/assets/icons/NameplateLogoFQDark.tsx b/lib/assets/icons/NameplateLogoFQDark.tsx index a893b47..d3d0515 100644 --- a/lib/assets/icons/NameplateLogoFQDark.tsx +++ b/lib/assets/icons/NameplateLogoFQDark.tsx @@ -1,8 +1,184 @@ import { FC, SVGProps } from "react"; +import { useLocation } from "react-router-dom"; -export const NameplateLogoFQDark: FC> = (props) => ( - - +export const NameplateLogoFQDark: FC> = (props) => { + const location = useLocation(); + const pathname = location.pathname; + + const getLanguageFromUrl = () => { + const parts = pathname.split("/"); + if (parts.length >= 2) { + const langSegment = parts[1].toLowerCase(); + if (langSegment === "en") return "en"; + if (langSegment === "uz") return "uz"; + } + return "ru"; + }; + + const lang = getLanguageFromUrl(); // Оптимизация - вызываем функцию один раз + + if (lang === "ru") return ; + if (lang === "en") return ; + if (lang === "uz") return ; + + return ; // Fallback +}; + +const UZ: FC> = (props) => ( + + + + + + + + + + + + + + + + + + + +); +const EN: FC> = (props) => ( + + + + + + + + + + + + + + + + + + + +); +const RU: FC> = (props) => ( + + > = (props) => ( d="M461.886 33.8256C453.446 32.8478 447.622 41.9149 442.205 48.4691C437.452 54.2209 433.912 60.7178 433.501 68.1717C433.064 76.1021 434.502 84.3611 439.905 90.1773C445.47 96.1684 453.806 99.9538 461.886 98.7299C469.296 97.6077 472.574 89.4712 478.091 84.3936C484.08 78.8802 495.239 76.3159 495.084 68.1717C494.929 60.0151 483.152 58.4185 477.485 52.5557C471.616 46.483 470.272 34.7969 461.886 33.8256Z" fill="#7E2AEA" /> - - + + > = (props) => ( - + 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/ApologyPage.tsx b/lib/components/ViewPublicationPage/ApologyPage.tsx index 9694685..a6afa60 100644 --- a/lib/components/ViewPublicationPage/ApologyPage.tsx +++ b/lib/components/ViewPublicationPage/ApologyPage.tsx @@ -6,7 +6,11 @@ type Props = Partial; export const ApologyPage = ({ error }: Props) => { let message = error.message || error.response?.data; + console.log("message"); + console.log(message.toLowerCase()); const { t } = useTranslation(); + console.log("t"); + console.log(t(message.toLowerCase())); return ( { 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..49e9bce 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 = { @@ -25,6 +25,9 @@ type InputsProps = { }; }; +const iscrutch = "/cc006b40-ccbd-4600-a1d3-f902f85aa0a0"; +const pathOnly = window.location.pathname; + export const Inputs = ({ name, setName, @@ -38,7 +41,7 @@ export const Inputs = ({ setAdress, crutch, }: InputsProps) => { - const { settings } = useQuizSettings(); + const { settings } = useQuizStore(); const { t } = useTranslation(); const FC = settings.cfg.formContact.fields; @@ -47,7 +50,11 @@ export const Inputs = ({ setName(target.value)} id={name} - title={FC["name"].innerText || `${t("Enter")} ${t("Name").toLowerCase()}`} + title={ + pathOnly === iscrutch + ? "Введите имя и фамилию" + : FC["name"].innerText || `${t("Enter")} ${t("Name").toLowerCase()}` + } desc={FC["name"].text || t("Name")} Icon={NameIcon} /> 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..af8b8c6 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(); @@ -129,7 +129,7 @@ export const TextSpecial = ({ currentQuestion, answer, stepNumber }: TextSpecial diff --git a/lib/components/ViewPublicationPage/questions/Text/TextSpecialHorisontal.tsx b/lib/components/ViewPublicationPage/questions/Text/TextSpecialHorisontal.tsx index 7171a81..5da1cd1 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(); @@ -61,7 +61,7 @@ export const TextSpecialHorisontal = ({ currentQuestion, answer, stepNumber }: T @@ -102,7 +102,7 @@ export const TextSpecialHorisontal = ({ currentQuestion, answer, stepNumber }: T diff --git a/lib/components/ViewPublicationPage/questions/Text/index.tsx b/lib/components/ViewPublicationPage/questions/Text/index.tsx index f0bc528..081da54 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,11 +14,11 @@ 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) ?? {}; - if (pathOnly === "/92ed5e3e-8e6a-491e-87d0-d3197682d0e3") + if (pathOnly === "/92ed5e3e-8e6a-491e-87d0-d3197682d0e3" || pathOnly === "/cc006b40-ccbd-4600-a1d3-f902f85aa0a0") return ( { - 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 5e146cd..c402ef7 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -52,5 +52,6 @@ "familiarized": "acknowledged", "and": "and", "Get results": "Get results", - "Data sent successfully": "Data sent successfully" + "Data sent successfully": "Data sent successfully", + "Step": "Step" } diff --git a/public/locales/ru.json b/public/locales/ru.json index b324f6e..81a1bae 100644 --- a/public/locales/ru.json +++ b/public/locales/ru.json @@ -52,5 +52,6 @@ "familiarized": "ознакомлен", "and": "и", "Get results": "Получить результаты", - "Data sent successfully": "Данные успешно отправлены" + "Data sent successfully": "Данные успешно отправлены", + "Step": "Шаг" } diff --git a/public/locales/uz.json b/public/locales/uz.json index fbc9711..d5f52f0 100644 --- a/public/locales/uz.json +++ b/public/locales/uz.json @@ -52,5 +52,6 @@ "familiarized": "tanishdim", "and": "va", "Get results": "Natijalarni olish", - "Data sent successfully": "Ma'lumotlar muvaffaqiyatli yuborildi" + "Data sent successfully": "Ma'lumotlar muvaffaqiyatli yuborildi", + "Step": "Qadam" } 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);