From f6d4dfe826d2f0feffaa6b8bd5a5bb6ccc49988f Mon Sep 17 00:00:00 2001 From: nflnkr <105123049+nflnkr@users.noreply.github.com> Date: Fri, 2 Feb 2024 17:35:02 +0300 Subject: [PATCH] refator datafetching minor fixes --- package.json | 1 + src/App.tsx | 17 +- ...{ContextProviders.tsx => QuizAnswerer.tsx} | 23 +- src/WidgetApp.tsx | 17 +- src/model/api/getQuizData.ts | 6 +- src/model/settingsData.ts | 3 +- src/pages/ViewPublicationPage/ContactForm.tsx | 22 +- src/pages/ViewPublicationPage/Footer.tsx | 39 +- src/pages/ViewPublicationPage/Question.tsx | 13 +- src/pages/ViewPublicationPage/ResultForm.tsx | 15 +- .../StartPageViewPublication.tsx | 12 +- .../ViewPublicationPage.tsx | 53 +- .../ViewPublicationPage/questions/Date.tsx | 172 +++-- .../ViewPublicationPage/questions/Emoji.tsx | 262 ++++---- .../ViewPublicationPage/questions/File.tsx | 595 +++++++++--------- .../ViewPublicationPage/questions/Images.tsx | 260 ++++---- .../ViewPublicationPage/questions/Number.tsx | 6 +- .../ViewPublicationPage/questions/Rating.tsx | 222 ++++--- .../ViewPublicationPage/questions/Select.tsx | 114 ++-- .../ViewPublicationPage/questions/Text.tsx | 94 ++- .../ViewPublicationPage/questions/Variant.tsx | 18 +- .../ViewPublicationPage/questions/Varimg.tsx | 7 +- .../ViewPublicationPage/questions/gag.png | Bin 6050 -> 0 bytes src/pages/ViewPublicationPage/tools/File.tsx | 96 +-- src/stores/quizData/actions.ts | 12 - src/stores/quizData/hooks.ts | 0 src/stores/quizData/store.ts | 29 - src/utils/handleComponentError.ts | 43 ++ src/utils/hooks/useGetSettings.ts | 56 -- src/utils/hooks/useQuizData.ts | 34 + yarn.lock | 7 + 31 files changed, 1082 insertions(+), 1166 deletions(-) rename src/{ContextProviders.tsx => QuizAnswerer.tsx} (58%) delete mode 100644 src/pages/ViewPublicationPage/questions/gag.png delete mode 100644 src/stores/quizData/actions.ts delete mode 100644 src/stores/quizData/hooks.ts delete mode 100644 src/stores/quizData/store.ts create mode 100644 src/utils/handleComponentError.ts delete mode 100644 src/utils/hooks/useGetSettings.ts create mode 100644 src/utils/hooks/useQuizData.ts diff --git a/package.json b/package.json index 8d65845..5c14905 100755 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "notistack": "^3.0.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-error-boundary": "^4.0.12", "react-router-dom": "^6.21.3", "swr": "^2.2.4", "typescript": "^5.2.2", diff --git a/src/App.tsx b/src/App.tsx index 398a58d..5e2e7ad 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,8 +1,7 @@ import { Box } from "@mui/material"; -import ContextProviders from "./ContextProviders"; -import { QuizIdContext } from "./contexts/QuizIdContext"; import { useParams } from "react-router-dom"; -import { ViewPage } from "./pages/ViewPublicationPage/ViewPublicationPage"; +import QuizAnswerer from "./QuizAnswerer"; +import { QuizIdContext } from "./contexts/QuizIdContext"; const defaultQuizId = "ef836ff8-35b1-4031-9acf-af5766bac2b2"; @@ -12,13 +11,11 @@ export default function App() { return ( - - - - - + + + ); } diff --git a/src/ContextProviders.tsx b/src/QuizAnswerer.tsx similarity index 58% rename from src/ContextProviders.tsx rename to src/QuizAnswerer.tsx index ea17a51..3d3e935 100644 --- a/src/ContextProviders.tsx +++ b/src/QuizAnswerer.tsx @@ -2,20 +2,22 @@ import { CssBaseline, ThemeProvider } from "@mui/material"; import { LocalizationProvider } from "@mui/x-date-pickers"; import { AdapterMoment } from "@mui/x-date-pickers/AdapterMoment"; import { ruRU } from '@mui/x-date-pickers/locales'; +import LoadingSkeleton from "@ui_kit/LoadingSkeleton"; +import { handleComponentError } from "@utils/handleComponentError"; import moment from "moment"; import { SnackbarProvider } from 'notistack'; +import { Suspense } from "react"; +import { ErrorBoundary } from "react-error-boundary"; import { SWRConfig } from "swr"; +import { ApologyPage } from "./pages/ViewPublicationPage/ApologyPage"; +import { ViewPage } from "./pages/ViewPublicationPage/ViewPublicationPage"; import lightTheme from "./utils/themes/light"; moment.locale("ru"); const localeText = ruRU.components.MuiLocalizationProvider.defaultProps.localeText; -interface Props { - children: JSX.Element; -} - -export default function ContextProviders({ children }: Props) { +export default function QuizAnswerer() { return ( - {children} + } + onError={handleComponentError} + > + }> + + + ); -} +} diff --git a/src/WidgetApp.tsx b/src/WidgetApp.tsx index f13c684..a6c7215 100644 --- a/src/WidgetApp.tsx +++ b/src/WidgetApp.tsx @@ -1,7 +1,6 @@ import { Box } from "@mui/material"; -import ContextProviders from "./ContextProviders"; +import QuizAnswerer from "./QuizAnswerer"; import { QuizIdContext } from "./contexts/QuizIdContext"; -import { ViewPage } from "./pages/ViewPublicationPage/ViewPublicationPage"; interface Props { @@ -12,14 +11,12 @@ export default function WidgetApp({ quizId }: Props) { return ( - - - - - + + + ); } diff --git a/src/model/api/getQuizData.ts b/src/model/api/getQuizData.ts index 9fab68d..96c44fd 100644 --- a/src/model/api/getQuizData.ts +++ b/src/model/api/getQuizData.ts @@ -24,8 +24,8 @@ export interface GetQuizDataResponse { }[]; } -export function parseQuizData(quizDataResponse: GetQuizDataResponse, quizId: string): QuizSettings { - const items: QuizSettings["items"] = quizDataResponse.items.map((item) => { +export function parseQuizData(quizDataResponse: GetQuizDataResponse, quizId: string): Omit { + const items: QuizSettings["questions"] = quizDataResponse.items.map((item) => { const content = JSON.parse(item.c); return { @@ -51,5 +51,5 @@ export function parseQuizData(quizDataResponse: GetQuizDataResponse, quizId: str pausable: quizDataResponse.settings.pausable }; - return { cnt: quizDataResponse.cnt, settings, items }; + return { cnt: quizDataResponse.cnt, settings, questions: items }; } diff --git a/src/model/settingsData.ts b/src/model/settingsData.ts index 29d7fb1..bfbbd1d 100644 --- a/src/model/settingsData.ts +++ b/src/model/settingsData.ts @@ -30,7 +30,7 @@ export type FCField = { }; export type QuizSettings = { - items: AnyTypedQuizQuestion[]; + questions: AnyTypedQuizQuestion[]; settings: { qid: string; fp: boolean; @@ -43,6 +43,7 @@ export type QuizSettings = { cfg: QuizConfig; }; cnt: number; + recentlyCompleted: boolean; }; export interface QuizConfig { diff --git a/src/pages/ViewPublicationPage/ContactForm.tsx b/src/pages/ViewPublicationPage/ContactForm.tsx index 572b991..321423a 100644 --- a/src/pages/ViewPublicationPage/ContactForm.tsx +++ b/src/pages/ViewPublicationPage/ContactForm.tsx @@ -1,22 +1,21 @@ import AddressIcon from "@icons/ContactFormIcon/AddressIcon"; -import NameIcon from "@icons/ContactFormIcon/NameIcon"; import EmailIcon from "@icons/ContactFormIcon/EmailIcon"; +import NameIcon from "@icons/ContactFormIcon/NameIcon"; import PhoneIcon from "@icons/ContactFormIcon/PhoneIcon"; import TextIcon from "@icons/ContactFormIcon/TextIcon"; import { Box, Button, InputAdornment, Link, TextField as MuiTextField, TextFieldProps, Typography, useMediaQuery, useTheme } from "@mui/material"; - import CustomCheckbox from "@ui_kit/CustomCheckbox"; import { FC, useRef, useState } from "react"; import { sendFC } from "@api/quizRelase"; import { NameplateLogo } from "@icons/NameplateLogo"; import { QuizQuestionResult } from "@model/questionTypes/result"; -import { quizThemes } from "@utils/themes/Publication/themePublication"; +import { useQuizData } from "@utils/hooks/useQuizData"; import { enqueueSnackbar } from "notistack"; import { ApologyPage } from "./ApologyPage"; import { checkEmptyData } from "./tools/checkEmptyData"; -import { useQuestionsStore } from "@stores/quizData/store"; +import { quizThemes } from "@utils/themes/Publication/themePublication"; const TextField = MuiTextField as unknown as FC; // temporary fix ts(2590) @@ -74,7 +73,7 @@ export const ContactForm = ({ setShowResultForm, }: ContactFormProps) => { const theme = useTheme(); - const { settings, items } = useQuestionsStore(); + const { settings, questions } = useQuizData(); const [ready, setReady] = useState(false); const [name, setName] = useState(""); @@ -93,7 +92,7 @@ export const ContactForm = ({ }; //@ts-ignore - const resultQuestion: QuizQuestionResult = items.find((question) => { + const resultQuestion: QuizQuestionResult = questions.find((question) => { if (settings?.cfg.haveRoot) { //ветвимся return ( @@ -110,8 +109,6 @@ export const ContactForm = ({ }); const inputHC = async () => { - if (!settings) return; - //@ts-ignore const FC = settings?.cfg.formContact.fields || settings?.cfg.formContact; const body = {}; @@ -157,7 +154,6 @@ export const ContactForm = ({ } let isWide = Object.keys(filteredFC).length > 2; - if (!settings) throw new Error("settings is null"); if (!resultQuestion) return ( @@ -360,15 +356,13 @@ export const ContactForm = ({ @@ -393,7 +387,7 @@ const Inputs = ({ adress, setAdress, }: any) => { - const { settings } = useQuestionsStore(); + const { settings } = useQuizData(); // @ts-ignore const FC = settings?.cfg.formContact.fields || settings?.cfg.formContact; diff --git a/src/pages/ViewPublicationPage/Footer.tsx b/src/pages/ViewPublicationPage/Footer.tsx index 9dd9f3c..34866ca 100644 --- a/src/pages/ViewPublicationPage/Footer.tsx +++ b/src/pages/ViewPublicationPage/Footer.tsx @@ -1,16 +1,14 @@ import { Box, Button, Typography, useMediaQuery, useTheme } from "@mui/material"; import { useCallback, useMemo, useState } from "react"; -import { getQuestionById } from "@stores/quizData/actions"; - import { enqueueSnackbar } from "notistack"; import type { AnyTypedQuizQuestion, QuizQuestionBase } from "../../model/questionTypes/shared"; import { checkEmptyData } from "./tools/checkEmptyData"; import type { QuizQuestionResult } from "@model/questionTypes/result"; -import { useQuestionsStore } from "@stores/quizData/store"; import { useQuizViewStore } from "@stores/quizView/store"; +import { useQuizData } from "@utils/hooks/useQuizData"; type FooterProps = { setCurrentQuestion: (step: AnyTypedQuizQuestion) => void; @@ -22,13 +20,13 @@ type FooterProps = { export const Footer = ({ setCurrentQuestion, question, setShowContactForm, setShowResultForm }: FooterProps) => { const theme = useTheme(); - const { settings, items } = useQuestionsStore(); + const { settings, questions } = useQuizData(); const answers = useQuizViewStore(state => state.answers); const [stepNumber, setStepNumber] = useState(1); const isMobileMini = useMediaQuery(theme.breakpoints.down(382)); - const isLinear = !items.some(({ content }) => content.rule.parentId === "root"); + const isLinear = !questions.some(({ content }) => content.rule.parentId === "root"); const getNextQuestionId = useCallback(() => { console.log("Смотрим какой вопрос будет дальше. Что у нас сегодня вкусненького? Щя покажу от какого вопроса мы ищем следующий шаг"); @@ -86,27 +84,27 @@ export const Footer = ({ setCurrentQuestion, question, setShowContactForm, setSh } //ничё не нашли, ищем резулт console.log("ничё не нашли, ищем резулт "); - return items.find(q => { + return questions.find(q => { console.log('q.type === "result"', q.type === "result"); console.log('q.content.rule.parentId', q.content.rule.parentId); console.log('question.content.id', question.content.id); return q.type === "result" && q.content.rule.parentId === question.content.id; })?.id; - }, [answers, items, question]); + }, [answers, questions, question]); const isPreviousButtonDisabled = useMemo(() => { // Логика для аргумента disabled у кнопки "Назад" if (isLinear) { - const questionIndex = items.findIndex(({ id }) => id === question.id); + const questionIndex = questions.findIndex(({ id }) => id === question.id); - const previousQuestion = items[questionIndex - 1]; + const previousQuestion = questions[questionIndex - 1]; return previousQuestion ? false : true; } else { return question?.content.rule.parentId === "root" ? true : false; } - }, [items, isLinear, question?.content.rule.parentId, question.id]); + }, [questions, isLinear, question?.content.rule.parentId, question.id]); const isNextButtonDisabled = useMemo(() => { // Логика для аргумента disabled у кнопки "Далее" @@ -128,7 +126,8 @@ export const Footer = ({ setCurrentQuestion, question, setShowContactForm, setSh if (nextQuestionId) { return false; } else { - const nextQuestion = getQuestionById(question.content.rule.default); + const questionId = question.content.rule.default; + const nextQuestion = questions.find(q => q.id === questionId || q.content.id === questionId) || null; if (nextQuestion?.type) { return false; @@ -137,7 +136,6 @@ export const Footer = ({ setCurrentQuestion, question, setShowContactForm, setSh }, [answers, getNextQuestionId, isLinear, question.content, question.id]); const showResult = (nextQuestion: QuizQuestionResult) => { - if (!settings) return; if (!nextQuestion) return; const isEmpty = checkEmptyData({ resultData: nextQuestion }); @@ -168,9 +166,9 @@ export const Footer = ({ setCurrentQuestion, question, setShowContactForm, setSh if (isLinear) { setStepNumber(q => q - 1); - const questionIndex = items.findIndex(({ id }) => id === question.id); + const questionIndex = questions.findIndex(({ id }) => id === question.id); - const previousQuestion = items[questionIndex - 1]; + const previousQuestion = questions[questionIndex - 1]; if (previousQuestion) { setCurrentQuestion(previousQuestion); @@ -180,7 +178,8 @@ export const Footer = ({ setCurrentQuestion, question, setShowContactForm, setSh } if (question?.content.rule.parentId !== "root") { - const parent = getQuestionById(question?.content.rule.parentId); + const questionId = question?.content.rule.parentId; + const parent = questions.find(q => q.id === questionId || q.content.id === questionId) || null; if (parent?.type) { setCurrentQuestion(parent); } else { @@ -195,14 +194,14 @@ export const Footer = ({ setCurrentQuestion, question, setShowContactForm, setSh if (isLinear) { setStepNumber(q => q + 1); - const questionIndex = items.findIndex(({ id }) => id === question.id); - const nextQuestion = items[questionIndex + 1]; + const questionIndex = questions.findIndex(({ id }) => id === question.id); + const nextQuestion = questions[questionIndex + 1]; if (nextQuestion && nextQuestion.type !== "result") { setCurrentQuestion(nextQuestion); } else { //@ts-ignore - showResult(items.find(q => q.content.rule.parentId === "line")); + showResult(questions.find(q => q.content.rule.parentId === "line")); } return; @@ -211,7 +210,7 @@ export const Footer = ({ setCurrentQuestion, question, setShowContactForm, setSh const nextQuestionId = getNextQuestionId(); if (nextQuestionId) { - const nextQuestion = getQuestionById(nextQuestionId); + const nextQuestion = questions.find(q => q.id === nextQuestionId || q.content.id === nextQuestionId) || null; if (nextQuestion?.type && nextQuestion.type === "result") { showResult(nextQuestion); @@ -278,7 +277,7 @@ export const Footer = ({ setCurrentQuestion, question, setShowContactForm, setSh Из - {items.filter(q => q.type !== "result").length} + {questions.filter(q => q.type !== "result").length} } diff --git a/src/pages/ViewPublicationPage/Question.tsx b/src/pages/ViewPublicationPage/Question.tsx index 03f43ac..d3fe759 100644 --- a/src/pages/ViewPublicationPage/Question.tsx +++ b/src/pages/ViewPublicationPage/Question.tsx @@ -1,8 +1,6 @@ import { Box, useMediaQuery, useTheme } from "@mui/material"; import { useEffect, useState } from "react"; -import { getQuestionById } from "@stores/quizData/actions"; - import { ContactForm } from "./ContactForm"; import { Footer } from "./Footer"; import { ResultForm } from "./ResultForm"; @@ -23,23 +21,22 @@ import type { AnyTypedQuizQuestion } from "../../model/questionTypes/shared"; import { NameplateLogoFQ } from "@icons/NameplateLogoFQ"; import { NameplateLogoFQDark } from "@icons/NameplateLogoFQDark"; import { QuizQuestionResult } from "@model/questionTypes/result"; -import { useQuestionsStore } from "@stores/quizData/store"; import { notReachable } from "@utils/notReachable"; import { quizThemes } from "@utils/themes/Publication/themePublication"; +import { useQuizData } from "@utils/hooks/useQuizData"; export const Question = () => { const theme = useTheme(); - const settings = useQuestionsStore(state => state.settings); - const questions = useQuestionsStore(state => state.items); + const { settings, questions } = useQuizData(); const [currentQuestion, setCurrentQuestion] = useState(); const [showContactForm, setShowContactForm] = useState(false); const [showResultForm, setShowResultForm] = useState(false); const isMobile = useMediaQuery(theme.breakpoints.down(650)); useEffect(() => { - if (settings?.cfg.haveRoot) {//ветвимся - const nextQuestion = getQuestionById(settings?.cfg.haveRoot || ""); + const questionId = settings?.cfg.haveRoot || ""; + const nextQuestion = questions.find(q => q.id === questionId || q.content.id === questionId) || null; if (nextQuestion?.type) { setCurrentQuestion(nextQuestion); @@ -49,10 +46,8 @@ export const Question = () => { } else {//идём прямо setCurrentQuestion(questions[0]); } - }, []); - if (!settings) throw new Error("settings is null"); if (!currentQuestion || currentQuestion.type === "result") return "не смог отобразить вопрос"; return ( diff --git a/src/pages/ViewPublicationPage/ResultForm.tsx b/src/pages/ViewPublicationPage/ResultForm.tsx index 6666bd4..335d9f3 100644 --- a/src/pages/ViewPublicationPage/ResultForm.tsx +++ b/src/pages/ViewPublicationPage/ResultForm.tsx @@ -9,10 +9,10 @@ import { import { NameplateLogo } from "@icons/NameplateLogo"; import YoutubeEmbedIframe from "./tools/YoutubeEmbedIframe"; -import { useQuestionsStore } from "@stores/quizData/store"; import { quizThemes } from "@utils/themes/Publication/themePublication"; import { useCallback, useEffect, useMemo } from "react"; import type { QuizQuestionResult } from "../../model/questionTypes/result"; +import { useQuizData } from "@utils/hooks/useQuizData"; type ResultFormProps = { @@ -29,34 +29,33 @@ export const ResultForm = ({ }: ResultFormProps) => { const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down(650)); - const { settings, items } = useQuestionsStore(); - if (!settings) throw new Error("settings is null"); + const { settings, questions } = useQuizData(); const resultQuestion = useMemo(() => { if (settings?.cfg.haveRoot) { //ищём для ветвления - return (items.find( + return (questions.find( (question): question is QuizQuestionResult => question.type === "result" && question.content.rule.parentId === currentQuestion.content.id - ) || items.find( + ) || questions.find( (question): question is QuizQuestionResult => question.type === "result" && question.content.rule.parentId === "line" )); } else { - return items.find( + return questions.find( (question): question is QuizQuestionResult => question.type === "result" && question.content.rule.parentId === "line" ); } - }, [currentQuestion.content.id, items, settings?.cfg.haveRoot]); + }, [currentQuestion.content.id, questions, settings?.cfg.haveRoot]); const followNextForm = useCallback(() => { setShowResultForm(false); setShowContactForm(true); - },[setShowContactForm, setShowResultForm]); + }, [setShowContactForm, setShowResultForm]); useEffect(() => { if (!resultQuestion) { diff --git a/src/pages/ViewPublicationPage/StartPageViewPublication.tsx b/src/pages/ViewPublicationPage/StartPageViewPublication.tsx index 9546093..b80dba8 100644 --- a/src/pages/ViewPublicationPage/StartPageViewPublication.tsx +++ b/src/pages/ViewPublicationPage/StartPageViewPublication.tsx @@ -1,12 +1,12 @@ import { Box, Button, ButtonBase, Link, Paper, Typography, useMediaQuery, useTheme } from "@mui/material"; -import YoutubeEmbedIframe from "./tools/YoutubeEmbedIframe"; -import { notReachable } from "../../utils/notReachable"; import { useUADevice } from "../../utils/hooks/useUADevice"; +import { notReachable } from "../../utils/notReachable"; +import YoutubeEmbedIframe from "./tools/YoutubeEmbedIframe"; import { NameplateLogo } from "@icons/NameplateLogo"; -import { quizThemes } from "@utils/themes/Publication/themePublication"; import { QuizStartpageAlignType, QuizStartpageType } from "@model/settingsData"; -import { useQuestionsStore } from "@stores/quizData/store"; +import { useQuizData } from "@utils/hooks/useQuizData"; +import { quizThemes } from "@utils/themes/Publication/themePublication"; interface Props { @@ -15,12 +15,10 @@ interface Props { export const StartPageViewPublication = ({ setVisualStartPage }: Props) => { const theme = useTheme(); - const { settings } = useQuestionsStore(); + const { settings } = useQuizData(); const { isMobileDevice } = useUADevice(); const isMobile = useMediaQuery(theme.breakpoints.down(650)); - if (!settings) throw new Error("settings is null"); - const handleCopyNumber = () => { navigator.clipboard.writeText(settings.cfg.info.phonenumber); }; diff --git a/src/pages/ViewPublicationPage/ViewPublicationPage.tsx b/src/pages/ViewPublicationPage/ViewPublicationPage.tsx index 16ba51e..d2a9a0c 100644 --- a/src/pages/ViewPublicationPage/ViewPublicationPage.tsx +++ b/src/pages/ViewPublicationPage/ViewPublicationPage.tsx @@ -1,55 +1,33 @@ -import { getData } from "@api/quizRelase"; -import { QuizSettings } from "@model/settingsData"; import { Box, ThemeProvider } from "@mui/material"; -import { setQuizData } from "@stores/quizData/actions"; -import { useQuestionsStore } from "@stores/quizData/store"; -import LoadingSkeleton from "@ui_kit/LoadingSkeleton"; +import { useQuizData } from "@utils/hooks/useQuizData"; import { quizThemes } from "@utils/themes/Publication/themePublication"; -import { enqueueSnackbar } from "notistack"; import { useEffect, useState } from "react"; -import useSWR from "swr"; import { ApologyPage } from "./ApologyPage"; import { Question } from "./Question"; import { StartPageViewPublication } from "./StartPageViewPublication"; -import { parseQuizData } from "@model/api/getQuizData"; -import { replaceSpacesToEmptyLines } from "./tools/replaceSpacesToEmptyLines"; -import { useQuizId } from "../../contexts/QuizIdContext"; - export const ViewPage = () => { - const quizId = useQuizId(); - const { isLoading, error } = useSWR(["quizData", quizId], params => getQuizData(params[1]), { - onSuccess: setQuizData, - }); - const { settings, items, recentlyСompleted } = useQuestionsStore(); + const { settings, questions, recentlyCompleted } = useQuizData(); const [visualStartPage, setVisualStartPage] = useState(); - useEffect(() => {//установка фавиконки - if (!settings) return; - + useEffect(() => { const link = document.querySelector('link[rel="icon"]'); if (link && settings.cfg.startpage.favIcon) { link.setAttribute("href", settings?.cfg.startpage.favIcon); } - //установка заголовка страницы + document.title = settings.name; setVisualStartPage(!settings.cfg.noStartPage); }, [settings]); - const questionsCount = items.filter(({ type }) => type !== null && type !== "result").length; - - if (error) { - console.log(error); - return ; - } - if (isLoading || !settings) return ; + const questionsCount = questions.filter(({ type }) => type !== null && type !== "result").length; if (questionsCount === 0) return ; return ( - {recentlyСompleted ? ( + {recentlyCompleted ? ( ) : ( @@ -62,21 +40,4 @@ export const ViewPage = () => { )} ); -}; - -async function getQuizData(quizId: string) { - const response = await getData(quizId); - const quizDataResponse = response.data; - - if (response.error) { - enqueueSnackbar(response.error); - throw new Error(response.error); - } - if (!quizDataResponse) { - throw new Error("Quiz not found"); - } - - const quizSettings = replaceSpacesToEmptyLines(parseQuizData(quizDataResponse, quizId)); - - return JSON.parse(JSON.stringify({ data: quizSettings }).replaceAll(/\\" \\"/g, '""').replaceAll(/" "/g, '""')).data as QuizSettings & { recentlyСompleted: boolean; }; -} +}; diff --git a/src/pages/ViewPublicationPage/questions/Date.tsx b/src/pages/ViewPublicationPage/questions/Date.tsx index 6f80cd2..a85f769 100644 --- a/src/pages/ViewPublicationPage/questions/Date.tsx +++ b/src/pages/ViewPublicationPage/questions/Date.tsx @@ -8,103 +8,101 @@ import type { QuizQuestionDate } from "../../../model/questionTypes/date"; import CalendarIcon from "@icons/CalendarIcon"; import { enqueueSnackbar } from "notistack"; import { sendAnswer } from "@api/quizRelase"; - + import { quizThemes } from "@utils/themes/Publication/themePublication"; -import { useQuestionsStore } from "@stores/quizData/store"; +import { useQuizData } from "@utils/hooks/useQuizData"; type DateProps = { - currentQuestion: QuizQuestionDate; + currentQuestion: QuizQuestionDate; }; export const Date = ({ currentQuestion }: DateProps) => { - const theme = useTheme(); + const theme = useTheme(); - const { settings } = useQuestionsStore(); - const { answers } = useQuizViewStore(); - const answer = answers.find( - ({ questionId }) => questionId === currentQuestion.id - )?.answer as string; - const currentAnswer = moment(answer) || moment(); - - if (!settings) throw new Error("settings is null"); + const { settings } = useQuizData(); + const { answers } = useQuizViewStore(); + const answer = answers.find( + ({ questionId }) => questionId === currentQuestion.id + )?.answer as string; + const currentAnswer = moment(answer) || moment(); - return ( - - - {currentQuestion.title} - - - ( - + + {currentQuestion.title} + + - ), - }} - value={ currentAnswer } - onChange={async (date) => { - console.log(date) - if (!date) { - return; - } + > + ( + + ), + }} + value={currentAnswer} + onChange={async (date) => { + console.log(date); + if (!date) { + return; + } - try { - await sendAnswer({ - questionId: currentQuestion.id, - body: moment(date).format("YYYY.MM.DD"), - qid: settings.qid, - }); + try { + await sendAnswer({ + questionId: currentQuestion.id, + body: moment(date).format("YYYY.MM.DD"), + qid: settings.qid, + }); - updateAnswer( - currentQuestion.id, - date - ); - } catch (e) { - enqueueSnackbar("ответ не был засчитан"); - } - }} - slotProps={{ - openPickerButton: { - sx: { - p: 0, - }, - "data-cy": "open-datepicker", - }, - layout: { - sx: { backgroundColor: theme.palette.background.default }, - }, - }} - sx={{ - "& .MuiInputBase-root": { - backgroundColor: quizThemes[settings.cfg.theme].isLight - ? "white" - : theme.palette.background.default, - borderRadius: "10px", - maxWidth: "250px", - pr: "22px", - "& input": { - py: "11px", - pl: "20px", - lineHeight: "19px", - }, - "& fieldset": { - borderColor: "#9A9AAF", - }, - }, - }} - /> - - - ); + updateAnswer( + currentQuestion.id, + date + ); + } catch (e) { + enqueueSnackbar("ответ не был засчитан"); + } + }} + slotProps={{ + openPickerButton: { + sx: { + p: 0, + }, + "data-cy": "open-datepicker", + }, + layout: { + sx: { backgroundColor: theme.palette.background.default }, + }, + }} + sx={{ + "& .MuiInputBase-root": { + backgroundColor: quizThemes[settings.cfg.theme].isLight + ? "white" + : theme.palette.background.default, + borderRadius: "10px", + maxWidth: "250px", + pr: "22px", + "& input": { + py: "11px", + pl: "20px", + lineHeight: "19px", + }, + "& fieldset": { + borderColor: "#9A9AAF", + }, + }, + }} + /> + + + ); }; diff --git a/src/pages/ViewPublicationPage/questions/Emoji.tsx b/src/pages/ViewPublicationPage/questions/Emoji.tsx index dd8f12b..b6ba3eb 100644 --- a/src/pages/ViewPublicationPage/questions/Emoji.tsx +++ b/src/pages/ViewPublicationPage/questions/Emoji.tsx @@ -14,148 +14,146 @@ import RadioCheck from "@ui_kit/RadioCheck"; import RadioIcon from "@ui_kit/RadioIcon"; import { sendAnswer } from "@api/quizRelase"; - + import { enqueueSnackbar } from "notistack"; import type { QuizQuestionEmoji } from "../../../model/questionTypes/emoji"; -import { useQuestionsStore } from "@stores/quizData/store"; +import { useQuizData } from "@utils/hooks/useQuizData"; type EmojiProps = { - currentQuestion: QuizQuestionEmoji; + currentQuestion: QuizQuestionEmoji; }; export const Emoji = ({ currentQuestion }: EmojiProps) => { - const theme = useTheme(); - - const { settings } = useQuestionsStore() - const { answers } = useQuizViewStore(); - const { answer } = - answers.find( - ({ questionId }) => questionId === currentQuestion.id - ) ?? {}; + const theme = useTheme(); - if (!settings) throw new Error("settings is null"); + const { settings } = useQuizData(); + const { answers } = useQuizViewStore(); + const { answer } = + answers.find( + ({ questionId }) => questionId === currentQuestion.id + ) ?? {}; return ( - - {currentQuestion.title} - answer === id - )} - onChange={({ target }) =>{ - updateAnswer( - currentQuestion.id, - currentQuestion.content.variants[Number(target.value)].answer - ) - } - } - sx={{ - display: "flex", - flexWrap: "wrap", - flexDirection: "row", - justifyContent: "space-between", - marginTop: "20px", - }} - > - - {currentQuestion.content.variants.map((variant, index) => ( - - - - {variant.extendedText && ( - - {variant.extendedText} - - )} - - - { - event.preventDefault(); - - try { - - await sendAnswer({ - questionId: currentQuestion.id, - body: currentQuestion.content.variants[index].extendedText + " " + currentQuestion.content.variants[index].answer, - qid: settings.qid - }) - - updateAnswer( + + {currentQuestion.title} + answer === id + )} + onChange={({ target }) => { + updateAnswer( currentQuestion.id, - currentQuestion.content.variants[index].id - ); - - } catch (e) { - enqueueSnackbar("ответ не был засчитан") - } - - - - if (answer === currentQuestion.content.variants[index].id) { - deleteAnswer(currentQuestion.id); - try { - - await sendAnswer({ - questionId: currentQuestion.id, - body: "", - qid: settings.qid - }) - - } catch (e) { - enqueueSnackbar("ответ не был засчитан") - } - } - }} - - control={ - } icon={} /> - } - label={ - - {variant.answer} - - } - /> - - ))} - - - + currentQuestion.content.variants[Number(target.value)].answer + ); + } + } + sx={{ + display: "flex", + flexWrap: "wrap", + flexDirection: "row", + justifyContent: "space-between", + marginTop: "20px", + }} + > + + {currentQuestion.content.variants.map((variant, index) => ( + + + + {variant.extendedText && ( + + {variant.extendedText} + + )} + + + { + event.preventDefault(); + + try { + + await sendAnswer({ + questionId: currentQuestion.id, + body: currentQuestion.content.variants[index].extendedText + " " + currentQuestion.content.variants[index].answer, + qid: settings.qid + }); + + updateAnswer( + currentQuestion.id, + currentQuestion.content.variants[index].id + ); + + } catch (e) { + enqueueSnackbar("ответ не был засчитан"); + } + + + + if (answer === currentQuestion.content.variants[index].id) { + deleteAnswer(currentQuestion.id); + try { + + await sendAnswer({ + questionId: currentQuestion.id, + body: "", + qid: settings.qid + }); + + } catch (e) { + enqueueSnackbar("ответ не был засчитан"); + } + } + }} + + control={ + } icon={} /> + } + label={ + + {variant.answer} + + } + /> + + ))} + + + ); - + }; diff --git a/src/pages/ViewPublicationPage/questions/File.tsx b/src/pages/ViewPublicationPage/questions/File.tsx index cc4a53d..5336533 100644 --- a/src/pages/ViewPublicationPage/questions/File.tsx +++ b/src/pages/ViewPublicationPage/questions/File.tsx @@ -1,12 +1,11 @@ import { - Box, - Typography, - ButtonBase, - useTheme, - IconButton, useMediaQuery, Modal, + Box, + Typography, + ButtonBase, + useTheme, + IconButton, useMediaQuery, Modal, } from "@mui/material"; import { useQuizViewStore, updateAnswer } from "@stores/quizView/store"; -import { UPLOAD_FILE_TYPES_MAP } from "../tools/File"; import UploadIcon from "@icons/UploadIcon"; import CloseBold from "@icons/CloseBold"; @@ -17,328 +16,326 @@ import type { DragEvent } from "react"; import type { UploadFileType } from "@model/questionTypes/file"; import { enqueueSnackbar } from "notistack"; import { sendAnswer, sendFile } from "@api/quizRelase"; -import { useQuestionsStore } from "@stores/quizData/store" import Info from "@icons/Info"; +import { useQuizData } from "@utils/hooks/useQuizData"; type FileProps = { - currentQuestion: QuizQuestionFile; + currentQuestion: QuizQuestionFile; }; -const CurrentModal = ({ status }: { status: "errorType" | "errorSize" | "picture" | "video" | "audio" | "document" | "" }) => { - switch (status) { - case 'errorType': - return (<> - Выбран некорректный тип файла - ) - case 'errorSize': - return (<> - Файл слишком большой. Максимальный размер 50 МБ - ) - default: - return (<> - Допустимые расширения файлов: - { - //@ts-ignore - ACCEPT_SEND_FILE_TYPES_MAP[status].join(" ")} - ) - } -} +const CurrentModal = ({ status }: { status: "errorType" | "errorSize" | "picture" | "video" | "audio" | "document" | ""; }) => { + switch (status) { + case 'errorType': + return (<> + Выбран некорректный тип файла + ); + case 'errorSize': + return (<> + Файл слишком большой. Максимальный размер 50 МБ + ); + default: + return (<> + Допустимые расширения файлов: + { + //@ts-ignore + ACCEPT_SEND_FILE_TYPES_MAP[status].join(" ")} + ); + } +}; const ACCEPT_SEND_FILE_TYPES_MAP = { - picture: [ - ".jpeg", - ".jpg", - ".png", - ".ico", - ".gif", - ".tiff", - ".webp", - ".eps", - ".svg" - ], - video: [ - ".mp4", - ".mov", - ".wmv", - ".avi", - ".avchd", - ".flv", - ".f4v", - ".swf", - ".mkv", - ".webm", - ".mpeg-2" - ], - audio: [ - ".aac", - ".aiff", - ".dsd", - ".flac", - ".mp3", - ".mqa", - ".ogg", - ".wav", - ".wma" - ], - document: [ - ".doc", - ".docx", - ".dotx", - ".rtf", - ".odt", - ".pdf", - ".txt", - ".xls", - ".ppt", - ".xlsx", - ".pptx", - ".pages", - ], + picture: [ + ".jpeg", + ".jpg", + ".png", + ".ico", + ".gif", + ".tiff", + ".webp", + ".eps", + ".svg" + ], + video: [ + ".mp4", + ".mov", + ".wmv", + ".avi", + ".avchd", + ".flv", + ".f4v", + ".swf", + ".mkv", + ".webm", + ".mpeg-2" + ], + audio: [ + ".aac", + ".aiff", + ".dsd", + ".flac", + ".mp3", + ".mqa", + ".ogg", + ".wav", + ".wma" + ], + document: [ + ".doc", + ".docx", + ".dotx", + ".rtf", + ".odt", + ".pdf", + ".txt", + ".xls", + ".ppt", + ".xlsx", + ".pptx", + ".pages", + ], -} +}; const UPLOAD_FILE_DESCRIPTIONS_MAP: Record< - UploadFileType, - { title: string; description: string } + UploadFileType, + { title: string; description: string; } > = { - picture: { - title: "Добавить изображение", - description: "Принимает изображения", - }, - video: { - title: "Добавить видео", - description: "Принимает .mp4 и .mov формат — максимум 100мб", - }, - audio: { title: "Добавить аудиофайл", description: "Принимает аудиофайлы" }, - document: { title: "Добавить документ", description: "Принимает документы" }, + picture: { + title: "Добавить изображение", + description: "Принимает изображения", + }, + video: { + title: "Добавить видео", + description: "Принимает .mp4 и .mov формат — максимум 100мб", + }, + audio: { title: "Добавить аудиофайл", description: "Принимает аудиофайлы" }, + document: { title: "Добавить документ", description: "Принимает документы" }, } as const; export const File = ({ currentQuestion }: FileProps) => { - const theme = useTheme(); - const { settings } = useQuestionsStore() - const { answers } = useQuizViewStore(); + const theme = useTheme(); + const { settings } = useQuizData(); + const { answers } = useQuizViewStore(); - const [statusModal, setStatusModal] = useState<"errorType" | "errorSize" | "picture" | "video" | "audio" | "document" | "">("") + const [statusModal, setStatusModal] = useState<"errorType" | "errorSize" | "picture" | "video" | "audio" | "document" | "">(""); - const answer = answers.find( - ({ questionId }) => questionId === currentQuestion.id - )?.answer as string; - const isMobile = useMediaQuery(theme.breakpoints.down(500)); - const uploadFile = async ({ target }: ChangeEvent) => { - if (!settings) return; - - const file = target.files?.[0]; - if (file) { - if (file.size <= 52428800) { - //проверяем на соответствие - console.log(file.name.toLowerCase()) - if (ACCEPT_SEND_FILE_TYPES_MAP[currentQuestion.content.type].find((ednding => { - console.log(ednding) - console.log(file.name.toLowerCase().endsWith(ednding)) - return file.name.toLowerCase().endsWith(ednding) - }))) { + const answer = answers.find( + ({ questionId }) => questionId === currentQuestion.id + )?.answer as string; + const isMobile = useMediaQuery(theme.breakpoints.down(500)); + const uploadFile = async ({ target }: ChangeEvent) => { + const file = target.files?.[0]; + if (file) { + if (file.size <= 52428800) { + //проверяем на соответствие + console.log(file.name.toLowerCase()); + if (ACCEPT_SEND_FILE_TYPES_MAP[currentQuestion.content.type].find((ednding => { + console.log(ednding); + console.log(file.name.toLowerCase().endsWith(ednding)); + return file.name.toLowerCase().endsWith(ednding); + }))) { - //Нужный формат - console.log(file) - try { + //Нужный формат + console.log(file); + try { - const data = await sendFile({ - questionId: currentQuestion.id, - body: { - file: file, - name: file.name - }, - qid: settings.qid - }) - console.log(data) + const data = await sendFile({ + questionId: currentQuestion.id, + body: { + file: file, + name: file.name + }, + qid: settings.qid + }); + console.log(data); - await sendAnswer({ - questionId: currentQuestion.id, - //@ts-ignore - body: `https://storage.yandexcloud.net/squizanswer/${settings.qid}/${currentQuestion.id}/${data.data.fileIDMap[currentQuestion.id]}`, - //@ts-ignore - qid: settings.qid - }) + await sendAnswer({ + questionId: currentQuestion.id, + //@ts-ignore + body: `https://storage.yandexcloud.net/squizanswer/${settings.qid}/${currentQuestion.id}/${data.data.fileIDMap[currentQuestion.id]}`, + //@ts-ignore + qid: settings.qid + }); - updateAnswer( - currentQuestion.id, - `${file.name}|${URL.createObjectURL(file)}` - ); + updateAnswer( + currentQuestion.id, + `${file.name}|${URL.createObjectURL(file)}` + ); - } catch (e) { - console.log(e) - enqueueSnackbar("ответ не был засчитан") - } + } catch (e) { + console.log(e); + enqueueSnackbar("ответ не был засчитан"); + } - } else { + } else { + + //неподходящий формат + setStatusModal("errorType"); + } + } else { + + setStatusModal("errorSize"); + } - //неподходящий формат - setStatusModal("errorType") } - } else { + }; - setStatusModal("errorSize") - } - - } - }; - - return ( - <> - - {currentQuestion.title} - - {answer?.split("|")[0] && ( - - Вы загрузили: - - - {answer?.split("|")[0]} - { - updateAnswer(currentQuestion.id, ""); - }} - > - - - - - )} - - {!answer?.split("|")[0] && ( - - - + return ( + <> + + {currentQuestion.title} ) => - event.preventDefault() - } - sx={{ - width: "100%", - height: isMobile ? undefined : "120px", - display: "flex", - gap: "50px", - justifyContent: "flex-start", - alignItems: "center", - padding: "33px 44px 33px 55px", - backgroundColor: theme.palette.background.default, - border: `1px solid #9A9AAF`, - // border: `1px solid ${theme.palette.grey2.main}`, - borderRadius: "8px", - }} + sx={{ + display: "flex", + flexDirection: "column", + width: "100%", + marginTop: "20px", + maxWidth: answer?.split("|")[0] ? "640px" : "600px", + }} > - - - - { - UPLOAD_FILE_DESCRIPTIONS_MAP[currentQuestion.content.type] - .title - } - - - { - UPLOAD_FILE_DESCRIPTIONS_MAP[currentQuestion.content.type] - .description - } - - + {answer?.split("|")[0] && ( + + Вы загрузили: + + + {answer?.split("|")[0]} + { + updateAnswer(currentQuestion.id, ""); + }} + > + + + + + )} + + {!answer?.split("|")[0] && ( + + + + ) => + event.preventDefault() + } + sx={{ + width: "100%", + height: isMobile ? undefined : "120px", + display: "flex", + gap: "50px", + justifyContent: "flex-start", + alignItems: "center", + padding: "33px 44px 33px 55px", + backgroundColor: theme.palette.background.default, + border: `1px solid #9A9AAF`, + // border: `1px solid ${theme.palette.grey2.main}`, + borderRadius: "8px", + }} + > + + + + { + UPLOAD_FILE_DESCRIPTIONS_MAP[currentQuestion.content.type] + .title + } + + + { + UPLOAD_FILE_DESCRIPTIONS_MAP[currentQuestion.content.type] + .description + } + + + + + setStatusModal(currentQuestion.content.type)} /> + + )} + {answer && currentQuestion.content.type === "picture" && ( + + )} + {answer && currentQuestion.content.type === "video" && ( + - - setStatusModal(currentQuestion.content.type)} /> - )} - {answer && currentQuestion.content.type === "picture" && ( - - )} - {answer && currentQuestion.content.type === "video" && ( - - - setStatusModal("")} - > - - - - - - ); + setStatusModal("")} + > + + + + + + ); }; diff --git a/src/pages/ViewPublicationPage/questions/Images.tsx b/src/pages/ViewPublicationPage/questions/Images.tsx index cb7530a..6d0483c 100644 --- a/src/pages/ViewPublicationPage/questions/Images.tsx +++ b/src/pages/ViewPublicationPage/questions/Images.tsx @@ -1,11 +1,11 @@ import { - Box, - Typography, - RadioGroup, - FormControlLabel, - Radio, - useTheme, - useMediaQuery, + Box, + Typography, + RadioGroup, + FormControlLabel, + Radio, + useTheme, + useMediaQuery, } from "@mui/material"; import { useQuizViewStore, updateAnswer, deleteAnswer } from "@stores/quizView/store"; @@ -15,138 +15,136 @@ import RadioIcon from "@ui_kit/RadioIcon"; import type { QuizQuestionImages } from "../../../model/questionTypes/images"; import { enqueueSnackbar } from "notistack"; import { sendAnswer } from "@api/quizRelase"; -import { useQuestionsStore } from "@stores/quizData/store" +import { useQuizData } from "@utils/hooks/useQuizData"; type ImagesProps = { - currentQuestion: QuizQuestionImages; + currentQuestion: QuizQuestionImages; }; export const Images = ({ currentQuestion }: ImagesProps) => { - const { settings } = useQuestionsStore() - const { answers } = useQuizViewStore(); - const theme = useTheme(); - const { answer } = - answers.find( - ({ questionId }) => questionId === currentQuestion.id - ) ?? {}; - const isTablet = useMediaQuery(theme.breakpoints.down(1000)); - const isMobile = useMediaQuery(theme.breakpoints.down(500)); + const { settings } = useQuizData(); + const { answers } = useQuizViewStore(); + const theme = useTheme(); + const { answer } = + answers.find( + ({ questionId }) => questionId === currentQuestion.id + ) ?? {}; + const isTablet = useMediaQuery(theme.breakpoints.down(1000)); + const isMobile = useMediaQuery(theme.breakpoints.down(500)); - if (!settings) throw new Error("settings is null"); - - return ( - - {currentQuestion.title} - answer === id - )} - sx={{ - display: "flex", - flexWrap: "wrap", - flexDirection: "row", - justifyContent: "space-between", - marginTop: "20px", - }} - > - - {currentQuestion.content.variants.map((variant, index) => ( - { - event.preventDefault(); - - try { - - await sendAnswer({ - questionId: currentQuestion.id, - body: `${currentQuestion.content.variants[index].answer} `, - qid: settings.qid - }) - - updateAnswer( - currentQuestion.id, - currentQuestion.content.variants[index].id - ); - - } catch (e) { - enqueueSnackbar("ответ не был засчитан") - } - - - if (answer === currentQuestion.content.variants[index].id) { - deleteAnswer(currentQuestion.id); - try { - - await sendAnswer({ - questionId: currentQuestion.id, - body: "", - qid: settings.qid - }) - - } catch (e) { - enqueueSnackbar("ответ не был засчитан") - } - } - }} - > - - - {variant.extendedText && ( - - )} - - - + {currentQuestion.title} + answer === id + )} sx={{ - textAlign: "center", - color: theme.palette.text.primary, - marginTop: "10px", - marginLeft: 0, - padding: "10px", - "& .MuiFormControlLabel-label": { - wordBreak: "break-word", - }, + display: "flex", + flexWrap: "wrap", + flexDirection: "row", + justifyContent: "space-between", + marginTop: "20px", }} - value={index} - control={ - } icon={} /> - } - label={variant.answer} - /> - - ))} + > + + {currentQuestion.content.variants.map((variant, index) => ( + { + event.preventDefault(); + + try { + + await sendAnswer({ + questionId: currentQuestion.id, + body: `${currentQuestion.content.variants[index].answer} `, + qid: settings.qid + }); + + updateAnswer( + currentQuestion.id, + currentQuestion.content.variants[index].id + ); + + } catch (e) { + enqueueSnackbar("ответ не был засчитан"); + } + + + if (answer === currentQuestion.content.variants[index].id) { + deleteAnswer(currentQuestion.id); + try { + + await sendAnswer({ + questionId: currentQuestion.id, + body: "", + qid: settings.qid + }); + + } catch (e) { + enqueueSnackbar("ответ не был засчитан"); + } + } + }} + > + + + {variant.extendedText && ( + + )} + + + } icon={} /> + } + label={variant.answer} + /> + + ))} + + - - - ); + ); }; diff --git a/src/pages/ViewPublicationPage/questions/Number.tsx b/src/pages/ViewPublicationPage/questions/Number.tsx index e8d1025..1c1c920 100644 --- a/src/pages/ViewPublicationPage/questions/Number.tsx +++ b/src/pages/ViewPublicationPage/questions/Number.tsx @@ -12,14 +12,14 @@ import { enqueueSnackbar } from "notistack"; import { sendAnswer } from "@api/quizRelase"; import { quizThemes } from "@utils/themes/Publication/themePublication"; -import { useQuestionsStore } from "@stores/quizData/store"; +import { useQuizData } from "@utils/hooks/useQuizData"; type NumberProps = { currentQuestion: QuizQuestionNumber; }; export const Number = ({ currentQuestion }: NumberProps) => { - const { settings } = useQuestionsStore(); + const { settings } = useQuizData(); const [inputValue, setInputValue] = useState("0"); const [minRange, setMinRange] = useState("0"); const [maxRange, setMaxRange] = useState("100000000000"); @@ -105,8 +105,6 @@ export const Number = ({ currentQuestion }: NumberProps) => { } }, []); - if (!settings) throw new Error("settings is null"); - return ( diff --git a/src/pages/ViewPublicationPage/questions/Rating.tsx b/src/pages/ViewPublicationPage/questions/Rating.tsx index b22562b..bf89ab6 100644 --- a/src/pages/ViewPublicationPage/questions/Rating.tsx +++ b/src/pages/ViewPublicationPage/questions/Rating.tsx @@ -1,9 +1,9 @@ import { - Box, - Typography, - Rating as RatingComponent, - useTheme, - useMediaQuery + Box, + Typography, + Rating as RatingComponent, + useTheme, + useMediaQuery } from "@mui/material"; import { useQuizViewStore, updateAnswer } from "@stores/quizView/store"; @@ -19,127 +19,125 @@ import StarIconMini from "@icons/questionsPage/StarIconMini"; import type { QuizQuestionRating } from "../../../model/questionTypes/rating"; import { enqueueSnackbar } from "notistack"; import { sendAnswer } from "@api/quizRelase"; -import { useQuestionsStore } from "@stores/quizData/store"; +import { useQuizData } from "@utils/hooks/useQuizData"; type RatingProps = { - currentQuestion: QuizQuestionRating; + currentQuestion: QuizQuestionRating; }; const buttonRatingForm = [ - { - name: "star", - icon: (color: string) => , - }, - { - name: "trophie", - icon: (color: string) => , - }, - { - name: "flag", - icon: (color: string) => , - }, - { - name: "heart", - icon: (color: string) => , - }, - { - name: "like", - icon: (color: string) => , - }, - { - name: "bubble", - icon: (color: string) => , - }, - { - name: "hashtag", - icon: (color: string) => , - }, + { + name: "star", + icon: (color: string) => , + }, + { + name: "trophie", + icon: (color: string) => , + }, + { + name: "flag", + icon: (color: string) => , + }, + { + name: "heart", + icon: (color: string) => , + }, + { + name: "like", + icon: (color: string) => , + }, + { + name: "bubble", + icon: (color: string) => , + }, + { + name: "hashtag", + icon: (color: string) => , + }, ]; export const Rating = ({ currentQuestion }: RatingProps) => { - const { settings } = useQuestionsStore() - const { answers } = useQuizViewStore(); - const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down(650)); - const { answer } = - answers.find( - ({ questionId }) => questionId === currentQuestion.id - ) ?? {}; - const form = buttonRatingForm.find( - ({ name }) => name === currentQuestion.content.form - ); + const { settings } = useQuizData(); + const { answers } = useQuizViewStore(); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down(650)); + const { answer } = + answers.find( + ({ questionId }) => questionId === currentQuestion.id + ) ?? {}; + const form = buttonRatingForm.find( + ({ name }) => name === currentQuestion.content.form + ); - if (!settings) throw new Error("settings is null"); - - return ( - - {currentQuestion.title} - - - { + return ( + + {currentQuestion.title} + + + { - try { + try { - await sendAnswer({ - questionId: currentQuestion.id, - body: String(value) + " из " + currentQuestion.content.steps, - qid: settings.qid - }) + await sendAnswer({ + questionId: currentQuestion.id, + body: String(value) + " из " + currentQuestion.content.steps, + qid: settings.qid + }); - updateAnswer(currentQuestion.id, String(value)) + updateAnswer(currentQuestion.id, String(value)); - } catch (e) { - enqueueSnackbar("ответ не был засчитан") - } - }} - sx={{ - height: "50px", - gap: isMobile ? undefined : "15px", - justifyContent: isMobile ? "space-between" : undefined, - width: isMobile ? "100%" : undefined - }} - max={currentQuestion.content.steps} - icon={form?.icon(theme.palette.primary.main)} - emptyIcon={form?.icon("#9A9AAF")} - /> + } catch (e) { + enqueueSnackbar("ответ не был засчитан"); + } + }} + sx={{ + height: "50px", + gap: isMobile ? undefined : "15px", + justifyContent: isMobile ? "space-between" : undefined, + width: isMobile ? "100%" : undefined + }} + max={currentQuestion.content.steps} + icon={form?.icon(theme.palette.primary.main)} + emptyIcon={form?.icon("#9A9AAF")} + /> + + + + {currentQuestion.content.ratingNegativeDescription} + + + {currentQuestion.content.ratingPositiveDescription} + + + - - - {currentQuestion.content.ratingNegativeDescription} - - - {currentQuestion.content.ratingPositiveDescription} - - - - - ); + ); }; diff --git a/src/pages/ViewPublicationPage/questions/Select.tsx b/src/pages/ViewPublicationPage/questions/Select.tsx index f990c5b..14abd5b 100644 --- a/src/pages/ViewPublicationPage/questions/Select.tsx +++ b/src/pages/ViewPublicationPage/questions/Select.tsx @@ -7,75 +7,73 @@ import { useQuizViewStore, updateAnswer, deleteAnswer } from "@stores/quizView/s import type { QuizQuestionSelect } from "../../../model/questionTypes/select"; import { enqueueSnackbar } from "notistack"; import { sendAnswer } from "@api/quizRelase"; +import { useQuizData } from "@utils/hooks/useQuizData"; -import { useQuestionsStore } from "@stores/quizData/store" type SelectProps = { - currentQuestion: QuizQuestionSelect; + currentQuestion: QuizQuestionSelect; }; export const Select = ({ currentQuestion }: SelectProps) => { - const theme = useTheme(); + const theme = useTheme(); - const { settings } = useQuestionsStore() - const { answers } = useQuizViewStore(); - const { answer } = - answers.find( - ({ questionId }) => questionId === currentQuestion.id - ) ?? {}; + const { settings } = useQuizData(); + const { answers } = useQuizViewStore(); + const { answer } = + answers.find( + ({ questionId }) => questionId === currentQuestion.id + ) ?? {}; - if (!settings) throw new Error("settings is null"); + return ( + + {currentQuestion.title} + + answer)} + colorMain={theme.palette.primary.main} + onChange={async (_, value) => { + if (value < 0) { + deleteAnswer(currentQuestion.id); + try { - return ( - - {currentQuestion.title} - - answer)} - colorMain={theme.palette.primary.main} - onChange={async(_, value) => { - if (value < 0) { - deleteAnswer(currentQuestion.id); - try { - - await sendAnswer({ - questionId: currentQuestion.id, - body: "", - qid: settings.qid - }) - - } catch (e) { - enqueueSnackbar("ответ не был засчитан") - } - return; - } + await sendAnswer({ + questionId: currentQuestion.id, + body: "", + qid: settings.qid + }); - try { - - await sendAnswer({ - questionId: currentQuestion.id, - body: String(currentQuestion.content.variants[Number(value)].answer), - qid: settings.qid - }) + } catch (e) { + enqueueSnackbar("ответ не был засчитан"); + } + return; + } - updateAnswer(currentQuestion.id, String(value)); + try { - } catch (e) { - enqueueSnackbar("ответ не был засчитан") - } + await sendAnswer({ + questionId: currentQuestion.id, + body: String(currentQuestion.content.variants[Number(value)].answer), + qid: settings.qid + }); + + updateAnswer(currentQuestion.id, String(value)); + + } catch (e) { + enqueueSnackbar("ответ не был засчитан"); + } - }} - /> - - - ); + }} + /> + + + ); }; diff --git a/src/pages/ViewPublicationPage/questions/Text.tsx b/src/pages/ViewPublicationPage/questions/Text.tsx index 5609f07..e934418 100644 --- a/src/pages/ViewPublicationPage/questions/Text.tsx +++ b/src/pages/ViewPublicationPage/questions/Text.tsx @@ -6,63 +6,61 @@ import { useQuizViewStore, updateAnswer } from "@stores/quizView/store"; import type { QuizQuestionText } from "../../../model/questionTypes/text"; import { enqueueSnackbar } from "notistack"; -import { useQuestionsStore } from "@stores/quizData/store" import { sendAnswer } from "@api/quizRelase"; import { useDebouncedCallback } from "use-debounce"; +import { useQuizData } from "@utils/hooks/useQuizData"; type TextProps = { - currentQuestion: QuizQuestionText; + currentQuestion: QuizQuestionText; }; export const Text = ({ currentQuestion }: TextProps) => { - const theme = useTheme(); - const { settings } = useQuestionsStore() - const { answers } = useQuizViewStore(); - const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {}; + const theme = useTheme(); + const { settings } = useQuizData(); + const { answers } = useQuizViewStore(); + const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {}; - const inputHC = useDebouncedCallback(async (text) => { - if (!settings) return; - - try { + const inputHC = useDebouncedCallback(async (text) => { + try { - await sendAnswer({ - questionId: currentQuestion.id, - body: text, - qid: settings.qid - }) + await sendAnswer({ + questionId: currentQuestion.id, + body: text, + qid: settings.qid + }); - } catch (e) { - enqueueSnackbar("ответ не был засчитан") - } - }, 400); - return ( - - {currentQuestion.title} - - { - updateAnswer(currentQuestion.id, target.value) - inputHC(target.value) - } - } - sx={{ - "&:focus-visible": { - borderColor: theme.palette.primary.main - } - }} - /> - - - ); + } catch (e) { + enqueueSnackbar("ответ не был засчитан"); + } + }, 400); + return ( + + {currentQuestion.title} + + { + updateAnswer(currentQuestion.id, target.value); + inputHC(target.value); + } + } + sx={{ + "&:focus-visible": { + borderColor: theme.palette.primary.main + } + }} + /> + + + ); }; diff --git a/src/pages/ViewPublicationPage/questions/Variant.tsx b/src/pages/ViewPublicationPage/questions/Variant.tsx index a552c01..fee885a 100644 --- a/src/pages/ViewPublicationPage/questions/Variant.tsx +++ b/src/pages/ViewPublicationPage/questions/Variant.tsx @@ -29,7 +29,7 @@ import { quizThemes } from "@utils/themes/Publication/themePublication"; import { enqueueSnackbar } from "notistack"; import type { QuestionVariant } from "../../../model/questionTypes/shared"; import type { QuizQuestionVariant } from "../../../model/questionTypes/variant"; -import { useQuestionsStore } from "@stores/quizData/store"; +import { useQuizData } from "@utils/hooks/useQuizData"; const TextField = MuiTextField as unknown as FC; @@ -135,11 +135,9 @@ const VariantItem = ({ index, own = false, }: VariantItemProps) => { - const { settings } = useQuestionsStore() + const { settings } = useQuizData(); const theme = useTheme(); - if (!settings) throw new Error("settings is null"); - return ( item !== variantId) : [...currentAnswer, variantId], qid: settings.qid - }) + }); updateAnswer( currentQuestion.id, @@ -205,7 +203,7 @@ const VariantItem = ({ ); } catch (e) { - enqueueSnackbar("ответ не был засчитан") + enqueueSnackbar("ответ не был засчитан"); } @@ -218,12 +216,12 @@ const VariantItem = ({ questionId: currentQuestion.id, body: currentQuestion.content.variants[index].answer, qid: settings.qid - }) + }); updateAnswer(currentQuestion.id, variantId); } catch (e) { - enqueueSnackbar("ответ не был засчитан") + enqueueSnackbar("ответ не был засчитан"); } if (answer === variantId) { @@ -233,10 +231,10 @@ const VariantItem = ({ questionId: currentQuestion.id, body: "", qid: settings.qid - }) + }); } catch (e) { - enqueueSnackbar("ответ не был засчитан") + enqueueSnackbar("ответ не был засчитан"); } deleteAnswer(currentQuestion.id); } diff --git a/src/pages/ViewPublicationPage/questions/Varimg.tsx b/src/pages/ViewPublicationPage/questions/Varimg.tsx index 0a16837..ec8c79b 100644 --- a/src/pages/ViewPublicationPage/questions/Varimg.tsx +++ b/src/pages/ViewPublicationPage/questions/Varimg.tsx @@ -7,8 +7,6 @@ import { useTheme, useMediaQuery } from "@mui/material"; - -import { useQuestionsStore } from "@stores/quizData/store"; import { useQuizViewStore, updateAnswer, deleteAnswer } from "@stores/quizView/store"; import RadioCheck from "@ui_kit/RadioCheck"; @@ -19,13 +17,14 @@ import { enqueueSnackbar } from "notistack"; import { sendAnswer } from "@api/quizRelase"; import { quizThemes } from "@utils/themes/Publication/themePublication"; import BlankImage from "@icons/BlankImage"; +import { useQuizData } from "@utils/hooks/useQuizData"; type VarimgProps = { currentQuestion: QuizQuestionVarImg; }; export const Varimg = ({ currentQuestion }: VarimgProps) => { - const { settings } = useQuestionsStore(); + const { settings } = useQuizData(); const { answers } = useQuizViewStore(); const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down(650)); @@ -38,8 +37,6 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => { ({ id }) => answer === id ); - if (!settings) throw new Error("settings is null"); - return ( {currentQuestion.title} diff --git a/src/pages/ViewPublicationPage/questions/gag.png b/src/pages/ViewPublicationPage/questions/gag.png deleted file mode 100644 index 5bb379f7a237fbc170956968171a7bbd6f7ac5ba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6050 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 OLk#t<>Xzc{!~X}8 = { - picture: "image/*", - video: "video/*", - audio: "audio/*", - document: - ".doc,.docx,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,.pdf", +const UPLOAD_FILE_TYPES_MAP: Record = { + picture: "image/*", + video: "video/*", + audio: "audio/*", + document: + ".doc,.docx,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,.pdf", } as const; interface Props { - question: QuizQuestionFile; + question: QuizQuestionFile; } export default function File({ question }: Props) { - const fileInputRef = useRef(null); - const [file, setFile] = useState(null); - const [acceptedType, setAcceptedType] = useState( - UPLOAD_FILE_TYPES_MAP.picture - ); + const fileInputRef = useRef(null); + const [file, setFile] = useState(null); + const [acceptedType, setAcceptedType] = useState( + UPLOAD_FILE_TYPES_MAP.picture + ); - useEffect(() => { - setAcceptedType(UPLOAD_FILE_TYPES_MAP[question.content.type]); - }, [question.content.type]); + useEffect(() => { + setAcceptedType(UPLOAD_FILE_TYPES_MAP[question.content.type]); + }, [question.content.type]); - function handleFileChange(event: ChangeEvent) { - if (!event.target.files?.[0]) return setFile(null); - setFile(event.target.files[0]); - } + function handleFileChange(event: ChangeEvent) { + if (!event.target.files?.[0]) return setFile(null); + setFile(event.target.files[0]); + } - return ( - - {question.title} - - {file && Выбран файл: {file.name}} - - ); + return ( + + {question.title} + + {file && Выбран файл: {file.name}} + + ); } diff --git a/src/stores/quizData/actions.ts b/src/stores/quizData/actions.ts deleted file mode 100644 index 17f826a..0000000 --- a/src/stores/quizData/actions.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { AnyTypedQuizQuestion } from "@model/questionTypes/shared"; -import { useQuestionsStore } from "./store"; -import { QuizSettings } from "@model/settingsData"; - - -export const getQuestionById = (questionId: string | null): AnyTypedQuizQuestion | null => { - if (questionId === null) return null; - - return useQuestionsStore.getState().items.find(q => q.id === questionId || q.content.id === questionId) || null; -}; - -export const setQuizData = (quizData: QuizSettings) => useQuestionsStore.setState(quizData); diff --git a/src/stores/quizData/hooks.ts b/src/stores/quizData/hooks.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/stores/quizData/store.ts b/src/stores/quizData/store.ts deleted file mode 100644 index 88f530b..0000000 --- a/src/stores/quizData/store.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { QuizSettings } from "@model/settingsData"; -import { create } from "zustand"; -import { devtools } from "zustand/middleware"; - - -type QuizDataStore = { - settings: QuizSettings["settings"] | null; - items: QuizSettings["items"]; - cnt: QuizSettings["cnt"]; - recentlyСompleted: boolean; -}; - -const initialState: QuizDataStore = { - settings: null, - items: [], - cnt: 0, - recentlyСompleted: false, -}; - -export const useQuestionsStore = create()( - devtools( - () => initialState, - { - name: "QuizDataStore", - enabled: import.meta.env.DEV, - trace: import.meta.env.DEV, - } - ) -); diff --git a/src/utils/handleComponentError.ts b/src/utils/handleComponentError.ts new file mode 100644 index 0000000..8d21fd7 --- /dev/null +++ b/src/utils/handleComponentError.ts @@ -0,0 +1,43 @@ +import { ErrorInfo } from "react"; + + +interface ComponentError { + timestamp: number; + message: string; + callStack: string | undefined; + componentStack: string | null | undefined; +} + +export function handleComponentError(error: Error, info: ErrorInfo) { + const componentError: ComponentError = { + timestamp: Math.floor(Date.now() / 1000), + message: error.message, + callStack: error.stack, + componentStack: info.componentStack, + }; + + queueErrorRequest(componentError); +} + +let errorsQueue: ComponentError[] = []; +let timeoutId: ReturnType; + +function queueErrorRequest(error: ComponentError) { + errorsQueue.push(error); + + clearTimeout(timeoutId); + timeoutId = setTimeout(() => { + sendErrorsToServer(); + }, 1000); +} + +async function sendErrorsToServer() { + // makeRequest({ + // url: "", + // method: "POST", + // body: errorsQueue, + // useToken: true, + // }); + console.log(`Fake-sending ${errorsQueue.length} errors to server`, errorsQueue); + errorsQueue = []; +} diff --git a/src/utils/hooks/useGetSettings.ts b/src/utils/hooks/useGetSettings.ts deleted file mode 100644 index 822cd40..0000000 --- a/src/utils/hooks/useGetSettings.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { useEffect, useLayoutEffect, useRef, useState } from "react" -import { useQuestionsStore } from "@stores/quizData/store"; - -import { getData } from "@api/quizRelase" - -interface SettingsGetter { - quizId: string -} - -export function useGetSettings(quizId: string) { - - const [fetchState, setFetchState] = useState<"fetching" | "idle" | "all fetched">("idle") - - useEffect(() => { - async function get() { - const data = await getData(quizId) - //@ts-ignore - const settings = data.settings - const parseData = { - settings: { - fp: settings.fp, - rep: settings.rep, - name: settings.name, - cfg: JSON.parse(settings?.cfg), - lim: settings.lim, - due: settings.due, - delay: settings.delay, - pausable: settings.pausable - }, - //@ts-ignore - items: data.items.map((item) => { - const content = JSON.parse(item.c) - return { - description: item.desc, - id: item.id, - page: item.p, - required: item.req, - title: item.title, - type: item.typ, - content - } - }), - //@ts-ignore - cnt: data.cnt - } - //@ts-ignore - useQuestionsStore.setState(parseData) - } - get() - // const controller = new AbortController() - - - }, []) - return - // return -} diff --git a/src/utils/hooks/useQuizData.ts b/src/utils/hooks/useQuizData.ts new file mode 100644 index 0000000..1e180c7 --- /dev/null +++ b/src/utils/hooks/useQuizData.ts @@ -0,0 +1,34 @@ +import { getData } from "@api/quizRelase"; +import { parseQuizData } from "@model/api/getQuizData"; +import { QuizSettings } from "@model/settingsData"; +import { enqueueSnackbar } from "notistack"; +import useSWR from "swr"; +import { useQuizId } from "../../contexts/QuizIdContext"; +import { replaceSpacesToEmptyLines } from "../../pages/ViewPublicationPage/tools/replaceSpacesToEmptyLines"; + + +export function useQuizData() { + const quizId = useQuizId(); + const { data } = useSWR(["quizData", quizId], params => getQuizData(params[1]), { + suspense: true, + }); + + return data; +} + +async function getQuizData(quizId: string) { + const response = await getData(quizId); + const quizDataResponse = response.data; + + if (response.error) { + enqueueSnackbar(response.error); + throw new Error(response.error); + } + if (!quizDataResponse) { + throw new Error("Quiz not found"); + } + + const quizSettings = replaceSpacesToEmptyLines(parseQuizData(quizDataResponse, quizId)); + + return JSON.parse(JSON.stringify({ data: quizSettings }).replaceAll(/\\" \\"/g, '""').replaceAll(/" "/g, '""')).data as QuizSettings; +} diff --git a/yarn.lock b/yarn.lock index e1e30ce..ef80ce4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2714,6 +2714,13 @@ react-dom@^18.2.0: loose-envify "^1.1.0" scheduler "^0.23.0" +react-error-boundary@^4.0.12: + version "4.0.12" + resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-4.0.12.tgz#59f8f1dbc53bbbb34fc384c8db7cf4082cb63e2c" + integrity sha512-kJdxdEYlb7CPC1A0SeUY38cHpjuu6UkvzKiAmqmOFL21VRfMhOcWxTCBgLVCO0VEMh9JhFNcVaXlV4/BTpiwOA== + dependencies: + "@babel/runtime" "^7.12.5" + react-is@^16.13.1, react-is@^16.7.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"