diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 1684a63..491be2b 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,40 +1,30 @@ -module.exports = { - root: true, - env: { browser: true, es2020: true }, - extends: [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended", - "plugin:react-hooks/recommended", - ], - ignorePatterns: ["dist", ".eslintrc.cjs"], - parser: "@typescript-eslint/parser", - plugins: ["react-refresh"], - rules: { - "react-refresh/only-export-components": [ - "warn", - { allowConstantExport: true }, - ], - "@typescript-eslint/ban-ts-comment": "off", - "@typescript-eslint/no-empty-function": "off", - "@typescript-eslint/no-empty-interface": "off", - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-floating-promises": "off", - "@typescript-eslint/no-inferrable-types": "off", - "@typescript-eslint/no-misused-promises": "off", - "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/no-unsafe-argument": "off", - "@typescript-eslint/no-unsafe-assignment": "off", - "@typescript-eslint/no-unsafe-call": "off", - "@typescript-eslint/no-unsafe-member-access": "off", - "@typescript-eslint/no-unused-vars": [ - "warn", - { "vars": "all", "args": "none" } - ], - "@typescript-eslint/restrict-template-expressions": "off", - "no-debugger": "off", - "no-empty-function": "off", - "no-empty-pattern": "off", - "no-empty": "off", - "prefer-const": "warn", - }, -}; +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:react-hooks/recommended"], + ignorePatterns: ["dist", ".eslintrc.cjs"], + parser: "@typescript-eslint/parser", + plugins: ["react-refresh"], + rules: { + "react-refresh/only-export-components": ["warn", { allowConstantExport: true }], + "@typescript-eslint/ban-ts-comment": "off", + "@typescript-eslint/no-empty-function": "off", + "@typescript-eslint/no-empty-interface": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-floating-promises": "off", + "@typescript-eslint/no-inferrable-types": "off", + "@typescript-eslint/no-misused-promises": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/no-unsafe-argument": "off", + "@typescript-eslint/no-unsafe-assignment": "off", + "@typescript-eslint/no-unsafe-call": "off", + "@typescript-eslint/no-unsafe-member-access": "off", + "@typescript-eslint/no-unused-vars": ["warn", { vars: "all", args: "none" }], + "@typescript-eslint/restrict-template-expressions": "off", + "no-debugger": "off", + "no-empty-function": "off", + "no-empty-pattern": "off", + "no-empty": "off", + "prefer-const": "warn", + }, +}; diff --git a/.gitignore b/.gitignore index 5d68f75..0a42158 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,7 @@ node_modules dist dist-package dist-ssr -widget +/widget *.local # Editor directories and files diff --git a/.husky/.gitignore b/.husky/.gitignore new file mode 100644 index 0000000..31354ec --- /dev/null +++ b/.husky/.gitignore @@ -0,0 +1 @@ +_ diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..017e2a1 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +yarn lint-staged --allow-empty diff --git a/Dockerfile b/Dockerfile index 8ecc497..9112719 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:20.10-alpine3.18 as build +FROM penahub.gitlab.yandexcloud.net:5050/devops/dockerhub-backup/node as build RUN apk update && rm -rf /var/cache/apk/* WORKDIR /usr/app @@ -9,7 +9,7 @@ RUN yarn build RUN yarn build:widget -FROM nginx:latest as result +FROM penahub.gitlab.yandexcloud.net:5050/devops/dockerhub-backup/nginx as result WORKDIR /usr/share/nginx/html COPY --from=build /usr/app/dist/ /usr/share/nginx/html COPY --from=build /usr/app/widget/widget.js /usr/share/nginx/html/export/pub.js diff --git a/README.md b/README.md index 1d63886..f65f0e0 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,68 @@ ## Правила + - Запрещено использовать vh/vw css-юниты и их производные + ## Виджет + ### Сборка + ```bash yarn build:widget ``` + ### Использование + ```html ``` + ## Npm-пакет + ### Перед использованием и публикацией + ```bash npm config set //penahub.gitlab.yandexcloud.net/api/v4/packages/npm/:_authToken=INSTANCE_TOKEN npm config set //penahub.gitlab.yandexcloud.net/api/v4/projects/43/packages/npm/:_authToken=PROJECT_TOKEN ``` + ### Публикация + 1. Инкрементировать версию в package.json -2. +2. + ```bash yarn publish ``` + 3. Нажать enter при запросе версии + ### Установка + Добавить в корень проекта файл .yarnrc с содержимым + ``` "@frontend:registry" "https://penahub.gitlab.yandexcloud.net/api/v4/packages/npm/" ``` + ```bash yarn add @frontend/squzanswerer ``` + Peer dependencies: + ```bash yarn add @emoji-mart/data @emoji-mart/react @emotion/react @emotion/styled @mui/icons-material @mui/material @mui/x-date-pickers axios emoji-mart immer moment nanoid notistack react-dom react-error-boundary react-router-dom react swr use-debounce zustand ``` + ### Использование + ```ts import { QuizView } from "@frontend/squzanswerer"; diff --git a/cypress.config.ts b/cypress.config.ts index 87067ad..a5f6f74 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -1,10 +1,10 @@ -import { defineConfig } from "cypress"; - -export default defineConfig({ - e2e: { - baseUrl: 'http://localhost:3000', - viewportWidth: 1440, - viewportHeight: 900, - supportFile: false, - }, -}); +import { defineConfig } from "cypress"; + +export default defineConfig({ + e2e: { + baseUrl: "http://localhost:3000", + viewportWidth: 1440, + viewportHeight: 900, + supportFile: false, + }, +}); diff --git a/deployments/staging/docker-compose.yaml b/deployments/staging/docker-compose.yaml index d258d2e..202dea9 100644 --- a/deployments/staging/docker-compose.yaml +++ b/deployments/staging/docker-compose.yaml @@ -1,3 +1,4 @@ +version: "3" services: respondent: container_name: respondent @@ -5,4 +6,3 @@ services: image: $CI_REGISTRY_IMAGE/staging:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID hostname: respondent tty: true - diff --git a/lib/api/hooks.ts b/lib/api/hooks.ts new file mode 100644 index 0000000..633b773 --- /dev/null +++ b/lib/api/hooks.ts @@ -0,0 +1,11 @@ +import useSWR from "swr"; +import { getQuizData } from "./quizRelase"; + +export function useQuizData(quizId: string) { + return useSWR(["quizData", quizId], (params) => getQuizData(params[1]), { + revalidateOnFocus: false, + revalidateOnReconnect: false, + shouldRetryOnError: false, + refreshInterval: 0, + }); +} diff --git a/lib/api/quizRelase.ts b/lib/api/quizRelase.ts index 77b1e07..99fb4a9 100644 --- a/lib/api/quizRelase.ts +++ b/lib/api/quizRelase.ts @@ -1,6 +1,6 @@ import { GetQuizDataResponse, parseQuizData } from "@model/api/getQuizData"; import axios from "axios"; -import MobileDetect from 'mobile-detect'; +import MobileDetect from "mobile-detect"; import device from "current-device"; import type { AxiosError } from "axios"; @@ -10,226 +10,242 @@ import * as Bowser from "bowser"; import { domain } from "../utils/defineDomain"; let SESSIONS = ""; - const md = new MobileDetect(window.navigator.userAgent); const userAgent = navigator.userAgent; //операционная система let OSDevice: string | undefined; -if (userAgent.toLowerCase().includes("linux")) { OSDevice = "Linux"; } -if (userAgent.toLowerCase().includes("windows")) { OSDevice = "Windows"; } -if (/iPad|iPhone|iPod/.test(userAgent)) { OSDevice = "IOS"; } -if (userAgent.toLowerCase().includes("macintosh")) { OSDevice = "Mac OS"; } -if (OSDevice === undefined) { OSDevice = userAgent; } +if (userAgent.toLowerCase().includes("linux")) { + OSDevice = "Linux"; +} +if (userAgent.toLowerCase().includes("windows")) { + OSDevice = "Windows"; +} +if (/iPad|iPhone|iPod/.test(userAgent)) { + OSDevice = "IOS"; +} +if (userAgent.toLowerCase().includes("macintosh")) { + OSDevice = "Mac OS"; +} +if (OSDevice === undefined) { + OSDevice = userAgent; +} //браузер let browserUser: string; if (Bowser.name === "Chrome") { - browserUser = "Chrome"; + browserUser = "Chrome"; } else if (Bowser.name === "Firefox") { - browserUser = "Firefox"; + browserUser = "Firefox"; } else if (Bowser.name === "Safari") { - browserUser = "Safari"; + browserUser = "Safari"; } else if (Bowser.name === "Yandex Browser") { - browserUser = "Yandex Browser"; -} else { browserUser = userAgent; } + browserUser = "Yandex Browser"; +} else { + browserUser = userAgent; +} const DeviceType = device.type; let Device = md.mobile(); -if (Device === null) { Device = userAgent; } +if (Device === null) { + Device = userAgent; +} type PublicationMakeRequestParams = { - url: string; - body: FormData; - method: "POST"; + url: string; + body: FormData; + method: "POST"; }; export const publicationMakeRequest = ({ url, body }: PublicationMakeRequestParams) => { - return axios(url, { - data: body, - headers: { - "X-Sessionkey": SESSIONS, - "Content-Type": "multipart/form-data", - "DeviceType": DeviceType, - "Device": Device, - "OS": OSDevice, - "Browser": browserUser - }, - method: "POST", - }); + return axios(url, { + data: body, + headers: { + "X-Sessionkey": SESSIONS, + "Content-Type": "multipart/form-data", + DeviceType: DeviceType, + Device: Device, + OS: OSDevice, + Browser: browserUser, + }, + method: "POST", + }); }; export async function getData(quizId: string): Promise<{ - data: GetQuizDataResponse | null; - isRecentlyCompleted: boolean; - error?: AxiosError; + data: GetQuizDataResponse | null; + isRecentlyCompleted: boolean; + error?: AxiosError; }> { - try { - const { data, headers } = await axios( - domain + `/answer/settings`, - { - 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") || "{}"); + 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") || "{}"); - if (typeof sessions[quizId] === "number") { - // unix время. Если меньше суток прошло - выводить ошибку, иначе пустить дальше - if (Date.now() - sessions[quizId] < 86400000) { - return { data, isRecentlyCompleted: true }; - } - } - - SESSIONS = headers["x-sessionkey"] ? headers["x-sessionkey"] : SESSIONS; - - return { data, isRecentlyCompleted: false }; - } catch (nativeError) { - const error = nativeError as AxiosError; - - return { data: null, isRecentlyCompleted: false, error: error }; + //Тут ещё проверка на антифрод без парса конфига. Нам не интересно время если не нужно запрещать проходить чаще чем в сутки + if (typeof sessions[quizId] === "number" && data.settings.cfg.includes('antifraud":true')) { + // unix время. Если меньше суток прошло - выводить ошибку, иначе пустить дальше + if (Date.now() - sessions[quizId] < 86400000) { + return { data, isRecentlyCompleted: true }; + } } + + SESSIONS = headers["x-sessionkey"] ? headers["x-sessionkey"] : SESSIONS; + + return { data, isRecentlyCompleted: false }; + } catch (nativeError) { + const error = nativeError as AxiosError; + + return { data: null, isRecentlyCompleted: false, error: error }; + } } export async function getQuizData(quizId: string) { - if (!quizId) throw new Error("No quiz id"); + if (!quizId) throw new Error("No quiz id"); - const response = await getData(quizId); - const quizDataResponse = response.data; + const response = await getData(quizId); + const quizDataResponse = response.data; - if (response.error) { - throw response.error; - } - if (!quizDataResponse) { - throw new Error("Quiz not found"); - } + if (response.error) { + throw response.error; + } + if (!quizDataResponse) { + throw new Error("Quiz not found"); + } - const quizSettings = replaceSpacesToEmptyLines(parseQuizData(quizDataResponse)); + const quizSettings = replaceSpacesToEmptyLines(parseQuizData(quizDataResponse)); - const res = JSON.parse(JSON.stringify({ data: quizSettings }).replaceAll(/\\" \\"/g, '""').replaceAll(/" "/g, '""')).data as QuizSettings; - res.recentlyCompleted = response.isRecentlyCompleted; - return res; + const res = JSON.parse( + JSON.stringify({ data: quizSettings }) + .replaceAll(/\\" \\"/g, '""') + .replaceAll(/" "/g, '""') + ).data as QuizSettings; + res.recentlyCompleted = response.isRecentlyCompleted; + return res; } type SendAnswerProps = { - questionId: string; - body: string | string[]; - qid: string; - preview: boolean; + questionId: string; + body: string | string[]; + qid: string; + preview?: boolean; }; -export function sendAnswer({ questionId, body, qid, preview }: SendAnswerProps) { - if (preview) return; - const formData = new FormData(); +export function sendAnswer({ questionId, body, qid, preview = false }: SendAnswerProps) { + if (preview) return; + const formData = new FormData(); - const answers = [ - { - question_id: questionId, - content: body, //тут массив с ответом - }, - ]; - formData.append("answers", JSON.stringify(answers)); - console.log("QID", qid); - formData.append("qid", qid); + const answers = [ + { + question_id: questionId, + content: body, //тут массив с ответом + }, + ]; + formData.append("answers", JSON.stringify(answers)); + formData.append("qid", qid); - return publicationMakeRequest({ - url: domain + `/answer/answer`, - body: formData, - method: "POST", - }); + return publicationMakeRequest({ + url: domain + `/answer/v1.0.0/answer`, + body: formData, + method: "POST", + }); } //body ={file, filename} type SendFileParams = { - questionId: string; - body: { - name: string; - file: File; - preview: boolean; - }; - qid: string; + questionId: string; + body: { + name: string; + file: File; + preview: boolean; + }; + qid: string; }; type Answer = { - question_id: string; - content: string; + question_id: string; + content: string; }; export function sendFile({ questionId, body, qid }: SendFileParams) { - if (body.preview) return; - const formData = new FormData(); + if (body.preview) return; + const formData = new FormData(); - const answers: Answer[] = [ - { - question_id: questionId, - content: "file:" + body.name, - }, - ]; + const answers: Answer[] = [ + { + question_id: questionId, + content: "file:" + body.name, + }, + ]; - formData.append("answers", JSON.stringify(answers)); - formData.append(body.name, body.file); - console.log("QID", qid); - formData.append("qid", qid); + formData.append("answers", JSON.stringify(answers)); + formData.append(body.name, body.file); + formData.append("qid", qid); - return publicationMakeRequest({ - url: domain + `/answer/answer`, - body: formData, - method: "POST", - }); + return publicationMakeRequest({ + url: domain + `/answer/v1.0.0/answer`, + body: formData, + method: "POST", + }); } //форма контактов export type SendFCParams = { - questionId: string; - body: { - name?: string; - email?: string; - phone?: string; - address?: string; - customs?: Record; - }; - qid: string; - preview: boolean; + questionId: string; + body: { + name?: string; + email?: string; + phone?: string; + address?: string; + customs?: Record; + }; + qid: string; + preview: boolean; }; export function sendFC({ questionId, body, qid, preview }: SendFCParams) { - if (preview) return; - const formData = new FormData(); + if (preview) return; + const formData = new FormData(); - // const keysBody = Object.keys(body) - // const content:any = {} - // fields.forEach((key) => { - // if (keysBody.includes(key)) content[key] = body.key - // }) + // const keysBody = Object.keys(body) + // const content:any = {} + // fields.forEach((key) => { + // if (keysBody.includes(key)) content[key] = body.key + // }) - const answers = [ - { - question_id: questionId, - content: JSON.stringify(body), - result: true, - qid, - }, - ]; + const answers = [ + { + question_id: questionId, + content: JSON.stringify(body), + result: true, + qid, + }, + ]; - formData.append("answers", JSON.stringify(answers)); - formData.append("qid", qid); + formData.append("answers", JSON.stringify(answers)); + formData.append("qid", qid); - return publicationMakeRequest({ - url: domain + `/answer/answer`, - body: formData, - method: "POST", - }); + return publicationMakeRequest({ + url: domain + `/answer/v1.0.0/answer`, + body: formData, + method: "POST", + }); } diff --git a/lib/assets/icons/ArrowDownIcon.tsx b/lib/assets/icons/ArrowDownIcon.tsx index c62184b..2d6f602 100644 --- a/lib/assets/icons/ArrowDownIcon.tsx +++ b/lib/assets/icons/ArrowDownIcon.tsx @@ -1,29 +1,32 @@ -import {Box, SxProps, Theme, useTheme} from "@mui/material"; +import { Box, SxProps, Theme, useTheme } from "@mui/material"; -interface Color{ - color?: string +interface Color { + color?: string; } -export default function ArrowDownIcon( - props: any, - {color = "#7E2AEA"}: Color -) { - const theme = useTheme(); +export default function ArrowDownIcon(props: any, { color = "#7E2AEA" }: Color) { + const theme = useTheme(); - return ( - - - - - - ); -} \ No newline at end of file + return ( + + + + + + ); +} diff --git a/lib/assets/icons/BlankImage.tsx b/lib/assets/icons/BlankImage.tsx index 6e976bc..93f2630 100644 --- a/lib/assets/icons/BlankImage.tsx +++ b/lib/assets/icons/BlankImage.tsx @@ -1,10 +1,26 @@ export default function BlankImage() { - - return ( - - - - - - ); -} + return ( + + + + + + ); +} diff --git a/lib/assets/icons/CalendarIcon.tsx b/lib/assets/icons/CalendarIcon.tsx index 1e1a930..2a127cd 100644 --- a/lib/assets/icons/CalendarIcon.tsx +++ b/lib/assets/icons/CalendarIcon.tsx @@ -1,6 +1,6 @@ -import { Box, SxProps, Theme } from "@mui/material"; +import { Box, SxProps, Theme } from "@mui/material"; interface Props { - sx?: SxProps; + sx?: SxProps; } export default function CalendarIcon({ sx }: Props) { return ( @@ -22,7 +22,7 @@ export default function CalendarIcon({ sx }: Props) { "&:active rect": { stroke: "#FB5607", }, - ...sx + ...sx, }} > diff --git a/lib/assets/icons/Checkbox.tsx b/lib/assets/icons/Checkbox.tsx index 52f5a8a..d95ca7a 100644 --- a/lib/assets/icons/Checkbox.tsx +++ b/lib/assets/icons/Checkbox.tsx @@ -5,7 +5,7 @@ type CheckboxIconProps = { color?: string; }; -export const CheckboxIcon = ({ checked = false, color = "#7E2AEA", }: CheckboxIconProps) => { +export const CheckboxIcon = ({ checked = false, color = "#7E2AEA" }: CheckboxIconProps) => { const theme = useTheme(); return ( @@ -17,26 +17,13 @@ export const CheckboxIcon = ({ checked = false, color = "#7E2AEA", }: CheckboxIc display: "flex", justifyContent: "center", alignItems: "center", - backgroundColor: checked - ? color - : "#F2F3F7", + backgroundColor: checked ? color : "#F2F3F7", border: `1px solid #9A9AAF`, }} > {checked && ( - - + + )} diff --git a/lib/assets/icons/CloseBold.tsx b/lib/assets/icons/CloseBold.tsx index 84942f7..e58a885 100644 --- a/lib/assets/icons/CloseBold.tsx +++ b/lib/assets/icons/CloseBold.tsx @@ -8,13 +8,7 @@ export default function CloseBold({ width }: Props) { const theme = useTheme(); return ( - + @@ -55,30 +49,13 @@ export default function CloseBold({ width }: Props) { values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" /> - + - - - + + + diff --git a/lib/assets/icons/ContactFormIcon/AddressIcon.tsx b/lib/assets/icons/ContactFormIcon/AddressIcon.tsx index cd9abf0..fa22355 100644 --- a/lib/assets/icons/ContactFormIcon/AddressIcon.tsx +++ b/lib/assets/icons/ContactFormIcon/AddressIcon.tsx @@ -2,7 +2,7 @@ import { Box } from "@mui/material"; interface Props { color: string; - backgroundColor: string + backgroundColor: string; } export default function AddressIcon({ color, backgroundColor }: Props) { @@ -14,18 +14,12 @@ export default function AddressIcon({ color, backgroundColor }: Props) { justifyContent: "center", height: "58px", width: "45px", - backgroundColor: {backgroundColor}, + backgroundColor: { backgroundColor }, borderBottomLeftRadius: "12px", borderTopLeftRadius: "12px", }} > - + - - - - - + + + + ); } diff --git a/lib/assets/icons/ContactFormIcon/NameIcon.tsx b/lib/assets/icons/ContactFormIcon/NameIcon.tsx index 8aa0dbe..ae04cab 100644 --- a/lib/assets/icons/ContactFormIcon/NameIcon.tsx +++ b/lib/assets/icons/ContactFormIcon/NameIcon.tsx @@ -2,7 +2,7 @@ import { Box } from "@mui/material"; interface Props { color: string; - backgroundColor: string + backgroundColor: string; } export default function NameIcon({ color, backgroundColor }: Props) { @@ -14,15 +14,26 @@ export default function NameIcon({ color, backgroundColor }: Props) { justifyContent: "center", height: "58px", width: "45px", - backgroundColor: {backgroundColor}, + backgroundColor: { backgroundColor }, borderBottomLeftRadius: "12px", borderTopLeftRadius: "12px", }} > - - - - + + + + ); } diff --git a/lib/assets/icons/ContactFormIcon/PhoneIcon.tsx b/lib/assets/icons/ContactFormIcon/PhoneIcon.tsx index 394bb4f..89982b3 100644 --- a/lib/assets/icons/ContactFormIcon/PhoneIcon.tsx +++ b/lib/assets/icons/ContactFormIcon/PhoneIcon.tsx @@ -2,7 +2,7 @@ import { Box } from "@mui/material"; interface Props { color: string; - backgroundColor: string + backgroundColor: string; } export default function PhoneIcon({ color, backgroundColor }: Props) { @@ -14,14 +14,18 @@ export default function PhoneIcon({ color, backgroundColor }: Props) { justifyContent: "center", height: "58px", width: "45px", - backgroundColor: {backgroundColor}, + backgroundColor: { backgroundColor }, borderBottomLeftRadius: "12px", borderTopLeftRadius: "12px", }} > - - - + + + ); } diff --git a/lib/assets/icons/ContactFormIcon/TextIcon.tsx b/lib/assets/icons/ContactFormIcon/TextIcon.tsx index f9c644f..73527e4 100644 --- a/lib/assets/icons/ContactFormIcon/TextIcon.tsx +++ b/lib/assets/icons/ContactFormIcon/TextIcon.tsx @@ -2,7 +2,7 @@ import { Box } from "@mui/material"; interface Props { color: string; - backgroundColor: string + backgroundColor: string; } export default function TextIcon({ color, backgroundColor }: Props) { @@ -14,24 +14,13 @@ export default function TextIcon({ color, backgroundColor }: Props) { justifyContent: "center", height: "58px", width: "45px", - backgroundColor: {backgroundColor}, + backgroundColor: { backgroundColor }, borderBottomLeftRadius: "12px", borderTopLeftRadius: "12px", }} > - - + + - - - - + + + + ); diff --git a/lib/assets/icons/Info.tsx b/lib/assets/icons/Info.tsx index f319760..e3611fe 100644 --- a/lib/assets/icons/Info.tsx +++ b/lib/assets/icons/Info.tsx @@ -6,23 +6,13 @@ type InfoProps = { sx?: SxProps; onClick?: () => void; className?: string; - color?: string + color?: string; }; export default function Info({ width = 20, height = 20, sx, onClick, className, color = "#7e2aea" }: InfoProps) { return ( - - + + > = (props) => ( - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + ); diff --git a/lib/assets/icons/NameplateLogoFQ.tsx b/lib/assets/icons/NameplateLogoFQ.tsx index 1bbb299..5aa4dd3 100644 --- a/lib/assets/icons/NameplateLogoFQ.tsx +++ b/lib/assets/icons/NameplateLogoFQ.tsx @@ -3,16 +3,39 @@ import { FC, SVGProps } from "react"; export const NameplateLogoFQ: FC> = (props) => ( - + - + - - - - - + + + + + diff --git a/lib/assets/icons/NameplateLogoFQDark.tsx b/lib/assets/icons/NameplateLogoFQDark.tsx index 9916f39..a893b47 100644 --- a/lib/assets/icons/NameplateLogoFQDark.tsx +++ b/lib/assets/icons/NameplateLogoFQDark.tsx @@ -1,24 +1,46 @@ import { FC, SVGProps } from "react"; export const NameplateLogoFQDark: FC> = (props) => ( - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + ); diff --git a/lib/assets/icons/UploadIcon.tsx b/lib/assets/icons/UploadIcon.tsx index 573fcbf..8c6dc39 100644 --- a/lib/assets/icons/UploadIcon.tsx +++ b/lib/assets/icons/UploadIcon.tsx @@ -1,27 +1,39 @@ import { Box, useTheme } from "@mui/material"; -interface Props{ - color?: string +interface Props { + color?: string; } -export default function UploadIcon({color= "#9A9AAF"}: Props) { - const theme = useTheme(); +export default function UploadIcon({ color = "#9A9AAF" }: Props) { + const theme = useTheme(); - return ( - - - - - - - - ); -} \ No newline at end of file + return ( + + + + + + + + ); +} diff --git a/lib/assets/icons/questionsPage/FlagIcon.tsx b/lib/assets/icons/questionsPage/FlagIcon.tsx index 9eebf28..35c5265 100644 --- a/lib/assets/icons/questionsPage/FlagIcon.tsx +++ b/lib/assets/icons/questionsPage/FlagIcon.tsx @@ -16,24 +16,9 @@ export default function FlagIcon({ color, width = 30 }: Props) { justifyContent: "center", }} > - - - + + + - + - + - - + + - + - + - - + + ( - () => window.innerWidth - ); + const [rootContainerWidth, setRootContainerWidth] = useState(() => window.innerWidth); const rootContainerRef = useRef(null); - const { data, error, isLoading } = useSWR( - quizSettings ? null : ["quizData", quizId], - (params) => getQuizData(params[1]), - { - revalidateOnFocus: false, - revalidateOnReconnect: false, - shouldRetryOnError: false, - refreshInterval: 0, - } - ); - const vkMetrics = useVkMetricsGoals( - quizSettings?.settings.cfg.vkMetricsNumber - ); - const yandexMetrics = useYandexMetricsGoals( - quizSettings?.settings.cfg.yandexMetricsNumber - ); + const { data, error, isLoading } = useQuizData(quizId); + const vkMetrics = useVkMetricsGoals(quizSettings?.settings.cfg.vkMetricsNumber); + const yandexMetrics = useYandexMetricsGoals(quizSettings?.settings.cfg.yandexMetricsNumber); useEffect(() => { setTimeout(() => { vkMetrics.quizOpened(); yandexMetrics.quizOpened(); }, 4000); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useLayoutEffect(() => { - if (rootContainerRef.current) - setRootContainerWidth(rootContainerRef.current.clientWidth); + if (rootContainerRef.current) setRootContainerWidth(rootContainerRef.current.clientWidth); }, []); useEffect(() => { const handleWindowResize = () => { startTransition(() => { - if (rootContainerRef.current) - setRootContainerWidth(rootContainerRef.current.clientWidth); + if (rootContainerRef.current) setRootContainerWidth(rootContainerRef.current.clientWidth); }); }; window.addEventListener("resize", handleWindowResize); @@ -108,12 +74,12 @@ function QuizAnswererInner({ if (isLoading) return ; if (error) return ; + if (!data) return ; quizSettings ??= data; if (!quizSettings) throw new Error("Quiz data is null"); - if (quizSettings.questions.length === 0) - return ; + if (quizSettings.questions.length === 0) return ; if (!quizId) return ; const quizContainer = ( @@ -138,9 +104,7 @@ function QuizAnswererInner({ return ( - + {disableGlobalCss ? ( {quizContainer} )} - + ); @@ -162,19 +126,21 @@ function QuizAnswererInner({ export default function QuizAnswerer(props: Props) { return ( - - - - - - - + + + + + + + + + ); } diff --git a/lib/components/ViewPublicationPage/ApologyPage.tsx b/lib/components/ViewPublicationPage/ApologyPage.tsx index 1a983f2..a7c3e6b 100644 --- a/lib/components/ViewPublicationPage/ApologyPage.tsx +++ b/lib/components/ViewPublicationPage/ApologyPage.tsx @@ -4,29 +4,32 @@ import { FallbackProps } from "react-error-boundary"; type Props = Partial; export const ApologyPage = ({ error }: Props) => { - let message = "Что-то пошло не так"; + let message = "Что-то пошло не так"; - if (error.response?.data === "quiz is inactive") message = "Квиз не активирован"; - if (error.message === "No questions found") message = "Нет созданных вопросов"; - if (error.message === "Quiz already completed") message = "Вы уже прошли этот опрос"; - if (error.message === "No quiz id") message = "Отсутствует id квиза"; - if (error.response?.data === "Invalid request data") message = "Такого квиза не существует"; + if (error.response?.data === "quiz is inactive") message = "Квиз не активирован"; + if (error.message === "No questions found") message = "Нет созданных вопросов"; + if (error.message === "Quiz already completed") message = "Вы уже прошли этот опрос"; + if (error.message === "No quiz id") message = "Отсутствует id квиза"; + if (error.response?.data === "Invalid request data") message = "Такого квиза не существует"; - return ( - - {message} - - ); + return ( + + + {message} + + + ); }; diff --git a/lib/components/ViewPublicationPage/ContactForm/ContactForm.tsx b/lib/components/ViewPublicationPage/ContactForm/ContactForm.tsx index 3cfbe6e..b4c4700 100644 --- a/lib/components/ViewPublicationPage/ContactForm/ContactForm.tsx +++ b/lib/components/ViewPublicationPage/ContactForm/ContactForm.tsx @@ -8,7 +8,7 @@ import { Inputs } from "@/components/ViewPublicationPage/ContactForm/Inputs/Inpu import { ContactTextBlock } from "./ContactTextBlock"; import { useRootContainerSize } from "@contexts/RootContainerWidthContext"; -import { useQuizData } from "@contexts/QuizDataContext"; +import { useQuizSettings } from "@contexts/QuizDataContext"; import { sendFC, SendFCParams } from "@api/quizRelase"; @@ -21,10 +21,7 @@ import { DESIGN_LIST } from "@utils/designList"; import { NameplateLogo } from "@icons/NameplateLogo"; -import type { - FormContactFieldData, - FormContactFieldName, -} from "@model/settingsData"; +import type { FormContactFieldData, FormContactFieldName } from "@model/settingsData"; import type { QuizQuestionResult } from "@model/questionTypes/result"; import type { AnyTypedQuizQuestion } from "@model/questionTypes/shared"; @@ -35,7 +32,7 @@ type Props = { export const ContactForm = ({ currentQuestion, onShowResult }: Props) => { const theme = useTheme(); - const { settings, questions, quizId, show_badge, preview } = useQuizData(); + const { settings, questions, quizId, show_badge, preview } = useQuizSettings(); const [ready, setReady] = useState(false); const [name, setName] = useState(""); @@ -69,18 +66,12 @@ export const ContactForm = ({ currentQuestion, onShowResult }: Props) => { currentQuestion.type === "result" ? currentQuestion : questions.find((question): question is QuizQuestionResult => { - if (settings?.cfg.haveRoot) { - return ( - question.type === "result" && - question.content.rule.parentId === currentQuestion.content.id - ); - } else { - return ( - question.type === "result" && - question.content.rule.parentId === "line" - ); - } - }); + if (settings?.cfg.haveRoot) { + return question.type === "result" && question.content.rule.parentId === currentQuestion.content.id; + } else { + return question.type === "result" && question.content.rule.parentId === "line"; + } + }); if (!resultQuestion) throw new Error("Result question not found"); @@ -103,10 +94,7 @@ export const ContactForm = ({ currentQuestion, onShowResult }: Props) => { }); const sessions = JSON.parse(localStorage.getItem("sessions") || "{}"); - localStorage.setItem( - "sessions", - JSON.stringify({ ...sessions, [quizId]: new Date().getTime() }) - ); + localStorage.setItem("sessions", JSON.stringify({ ...sessions, [quizId]: new Date().getTime() })); } catch (e) { enqueueSnackbar("ответ не был засчитан"); } @@ -116,9 +104,7 @@ export const ContactForm = ({ currentQuestion, onShowResult }: Props) => { const FCcopy: Record = settings.cfg.formContact.fields || settings.cfg.formContact; - const filteredFC: Partial< - Record - > = {}; + const filteredFC: Partial> = {}; for (const i in FCcopy) { const field = FCcopy[i as keyof typeof FCcopy]; if (field.used) { @@ -128,18 +114,13 @@ export const ContactForm = ({ currentQuestion, onShowResult }: Props) => { async function handleShowResultsClick() { const FC = settings.cfg.formContact.fields; + console.log(phone); if (FC["email"].used !== EMAIL_REGEXP.test(email)) { return enqueueSnackbar("введена некорректная почта"); } if (fireOnce.current) { - if ( - name.length === 0 && - email.length === 0 && - phone.length === 0 && - text.length === 0 && - adress.length === 0 - ) + if (name.length === 0 && email.length === 0 && phone.length === 0 && text.length === 0 && adress.length === 0) return enqueueSnackbar("Пожалуйста, заполните поля"); //почта валидна, хоть одно поле заполнено @@ -187,6 +168,7 @@ export const ContactForm = ({ currentQuestion, onShowResult }: Props) => { useEffect(() => { vkMetrics.contactsFormOpened(); yandexMetrics.contactsFormOpened(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return ( @@ -211,8 +193,9 @@ export const ContactForm = ({ currentQuestion, onShowResult }: Props) => { settings.cfg.design && !isMobile ? quizThemes[settings.cfg.theme].isLight ? `url(${DESIGN_LIST[settings.cfg.theme]})` - : `linear-gradient(90deg, #272626, transparent), url(${DESIGN_LIST[settings.cfg.theme] - })` + : `linear-gradient(90deg, rgba(39, 38, 38, 0.95) 7.66%, rgba(42, 42, 46, 0.85) 42.12%, rgba(51, 54, 71, 0.4) 100%), url(${ + DESIGN_LIST[settings.cfg.theme] + })` : null, }} > @@ -222,17 +205,15 @@ export const ContactForm = ({ currentQuestion, onShowResult }: Props) => { borderRadius: "4px", height: isMobile ? "100%" : "auto", minHeight: "100%", - display: isMobile ? undefined : "flex", - background: - settings.cfg.design && !isMobile - ? undefined - : theme.palette.background.default, + display: "flex", + flexDirection: isMobile ? "column" : "row", + background: settings.cfg.design && !isMobile ? undefined : theme.palette.background.default, }} > { alignItems: isMobile ? undefined : "center", justifyContent: "center", flexDirection: "column", - p: isMobile - ? "0 20px" - : isTablet - ? "0px 40px 30px 60px" - : "125px 60px 30px 60px", + p: isMobile ? "0 20px" : isTablet ? "105px 40px 0 60px" : "105px 60px 0 60px", + margin: isMobile ? "0" : "auto 0", }} > { fontSize={"16px"} > С  - + Положением об обработке персональных данных{" "}  и  @@ -344,14 +325,13 @@ export const ContactForm = ({ currentQuestion, onShowResult }: Props) => { mb: isMobile ? "30px" : isTablet ? "40px" : "50px", gap: "10px", textDecoration: "none", + margitTop: "auto", }} > diff --git a/lib/components/ViewPublicationPage/ContactForm/ContactTextBlock/index.tsx b/lib/components/ViewPublicationPage/ContactForm/ContactTextBlock/index.tsx index b8263e6..f250b1a 100644 --- a/lib/components/ViewPublicationPage/ContactForm/ContactTextBlock/index.tsx +++ b/lib/components/ViewPublicationPage/ContactForm/ContactTextBlock/index.tsx @@ -1,66 +1,65 @@ -import {Box, Typography, useTheme} from "@mui/material"; -import {useRootContainerSize} from "@contexts/RootContainerWidthContext.ts"; -import {QuizSettingsConfig} from "@model/settingsData.ts"; -import {FC} from "react"; +import { Box, Typography, useTheme } from "@mui/material"; +import { useRootContainerSize } from "@contexts/RootContainerWidthContext.ts"; +import { QuizSettingsConfig } from "@model/settingsData.ts"; +import { FC } from "react"; type ContactTextBlockProps = { - settings: QuizSettingsConfig; -} + settings: QuizSettingsConfig; +}; -export const ContactTextBlock: FC = ({settings}) => { - const theme = useTheme(); - const isMobile = useRootContainerSize() < 850; - const isTablet = useRootContainerSize() < 1000; - return ( - = ({ settings }) => { + const theme = useTheme(); + const isMobile = useRootContainerSize() < 850; + const isTablet = useRootContainerSize() < 1000; + return ( + + + - - - {settings.cfg.formContact.title || - "Заполните форму, чтобы получить результаты теста"} - - {settings.cfg.formContact.desc && ( - - {settings.cfg.formContact.desc} - - )} - - - ) -} + {settings.cfg.formContact.title || "Заполните форму, чтобы получить результаты теста"} + + {settings.cfg.formContact.desc && ( + + {settings.cfg.formContact.desc} + + )} + + + ); +}; diff --git a/lib/components/ViewPublicationPage/ContactForm/CustomInput/CountrySelector/CountrySelector.tsx b/lib/components/ViewPublicationPage/ContactForm/CustomInput/CountrySelector/CountrySelector.tsx index 514ea4a..d1f7345 100644 --- a/lib/components/ViewPublicationPage/ContactForm/CustomInput/CountrySelector/CountrySelector.tsx +++ b/lib/components/ViewPublicationPage/ContactForm/CustomInput/CountrySelector/CountrySelector.tsx @@ -1,64 +1,66 @@ -import {MenuItem, Select, SelectChangeEvent, useTheme} from "@mui/material"; -import {Dispatch, FC, SetStateAction, useState} from "react"; -import {phoneMasksByCountry} from "@utils/phoneMasksByCountry.tsx"; -import {Value} from "react-phone-number-input"; +import { MenuItem, Select, SelectChangeEvent, useTheme } from "@mui/material"; +import { Dispatch, FC, SetStateAction, useState } from "react"; +import { phoneMasksByCountry } from "@utils/phoneMasksByCountry.tsx"; +import { Value } from "react-phone-number-input"; type CountrySelectorProps = { - setMask: Dispatch>; -} - -export const CountrySelector:FC = ({setMask}) => { - const theme = useTheme(); - const [country, setCountry] = useState('RU'); - - const handleChange = (e: SelectChangeEvent) => { - setCountry(e.target.value); - setMask(phoneMasksByCountry[e.target.value][1]); - }; - return ( - - ); + setMask: Dispatch>; }; +export const CountrySelector: FC = ({ setMask }) => { + const theme = useTheme(); + const [country, setCountry] = useState("RU"); + + const handleChange = (e: SelectChangeEvent) => { + setCountry(e.target.value); + setMask(phoneMasksByCountry[e.target.value][1]); + }; + return ( + + ); +}; diff --git a/lib/components/ViewPublicationPage/ContactForm/CustomInput/CustomInput.tsx b/lib/components/ViewPublicationPage/ContactForm/CustomInput/CustomInput.tsx index 23c4704..1549041 100644 --- a/lib/components/ViewPublicationPage/ContactForm/CustomInput/CustomInput.tsx +++ b/lib/components/ViewPublicationPage/ContactForm/CustomInput/CustomInput.tsx @@ -1,91 +1,103 @@ -import { - Box, - InputAdornment, - TextField as MuiTextField, - TextFieldProps, - Typography, - useTheme -} from "@mui/material"; -import {useRootContainerSize} from "@contexts/RootContainerWidthContext.ts"; -import {useQuizData} from "@contexts/QuizDataContext.ts"; -import {useIMask} from "react-imask"; -import {quizThemes} from "@utils/themes/Publication/themePublication.ts"; -import {FC, useState} from "react"; -import { - CountrySelector -} from "@/components/ViewPublicationPage/ContactForm/CustomInput/CountrySelector/CountrySelector.tsx"; -import {phoneMasksByCountry} from "@utils/phoneMasksByCountry.tsx"; +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"; type InputProps = { - title: string; - desc: string; - Icon: FC<{ color: string; backgroundColor: string }>; - onChange: TextFieldProps["onChange"]; - id: string; - isPhone?:boolean + title: string; + desc: string; + Icon: FC<{ color: string; backgroundColor: string }>; + onChange: TextFieldProps["onChange"]; + onChangePhone?: (phone: string) => void; + id: string; + isPhone?: boolean; + type?: HTMLInputTypeAttribute; + value?: string; }; const TextField = MuiTextField as unknown as FC; +let first = true; +function phoneChange(e: ChangeEvent, mask: string) { + console.log(e); + const masked = IMask.createMask({ + mask: "+7 (000) 000-00-00", + // ...and other options + }); + masked.value = e.target.value; + console.log(masked); + console.log(masked.typedValue); + console.log(masked.parse); + const a = IMask.pipe(e.target.value, { + mask, + }); + console.log(a); + return a || ""; +} -export const CustomInput = ({ title, desc, Icon, onChange ,isPhone}: InputProps) => { - const theme = useTheme(); - const isMobile = useRootContainerSize() < 600; - const { settings } = useQuizData(); - const [mask, setMask] = useState(phoneMasksByCountry['RU'][1]); - const { ref } = useIMask({mask}); - return ( - - - {title} - +export const CustomInput = ({ title, desc, Icon, onChange, onChangePhone, isPhone, type, value }: InputProps) => { + const theme = useTheme(); + const isMobile = useRootContainerSize() < 600; + const { settings } = useQuizSettings(); + const [mask, setMask] = useState(phoneMasksByCountry["RU"][1]); + console.log(mask); + // const { ref } = useIMask({ mask }); - - - - ), - endAdornment: ( - - {isPhone && ( - )} - - ), - }} - /> + return ( + + + {title} + - - ); + ) => onChangePhone(phoneChange(e, mask)) : onChange(e)} + type={isPhone ? "tel" : type} + value={value} + sx={{ + width: isMobile ? "100%" : "390px", + backgroundColor: theme.palette.background.default, + fontSize: "16px", + "& .MuiOutlinedInput-notchedOutline": { + borderColor: "#9A9AAF80", + borderRadius: "12px", + }, + "& .MuiInputBase-root": { + paddingLeft: 0, + }, + "& .MuiOutlinedInput-input": { + paddingLeft: "10px", + }, + "& .MuiOutlinedInput-root": { + "&:hover fieldset": { + borderColor: theme.palette.primary.main, + }, + }, + }} + placeholder={desc} + InputProps={{ + startAdornment: ( + + + + ), + endAdornment: ( + {isPhone && } + ), + }} + /> + + ); }; diff --git a/lib/components/ViewPublicationPage/ContactForm/Inputs/Inputs.tsx b/lib/components/ViewPublicationPage/ContactForm/Inputs/Inputs.tsx index 6f9b539..456a800 100644 --- a/lib/components/ViewPublicationPage/ContactForm/Inputs/Inputs.tsx +++ b/lib/components/ViewPublicationPage/ContactForm/Inputs/Inputs.tsx @@ -1,107 +1,117 @@ -import {useQuizData} from "@contexts/QuizDataContext.ts"; +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"; import AddressIcon from "@icons/ContactFormIcon/AddressIcon.tsx"; -import {Dispatch, SetStateAction} from "react"; -import { - CustomInput -} from "@/components/ViewPublicationPage/ContactForm/CustomInput/CustomInput.tsx"; +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"; type InputsProps = { - name: string; - setName: Dispatch>; - email: string; - setEmail: Dispatch>; - phone: string; - setPhone: Dispatch>; - text: string; - setText: Dispatch>; - adress: string; - setAdress: Dispatch>; + name: string; + setName: Dispatch>; + email: string; + setEmail: Dispatch>; + phone: string; + setPhone: Dispatch>; + text: string; + setText: Dispatch>; + adress: string; + setAdress: Dispatch>; }; export const Inputs = ({ - name, - setName, - email, - setEmail, - phone, - setPhone, - text, - setText, - adress, - setAdress, - }: InputsProps) => { - const { settings } = useQuizData(); - const FC = settings.cfg.formContact.fields; + name, + setName, + email, + setEmail, + phone, + setPhone, + text, + setText, + adress, + setAdress, +}: InputsProps) => { + const { settings } = useQuizSettings(); + const FC = settings.cfg.formContact.fields; - if (!FC) return null; - const Name = ( - setName(target.value)} - id={name} - title={FC["name"].innerText || "Введите имя"} - desc={FC["name"].text || "Имя"} - Icon={NameIcon} - /> - ); - const Email = ( - setEmail(target.value.replaceAll(/\s/g, ""))} - id={email} - title={FC["email"].innerText || "Введите Email"} - desc={FC["email"].text || "Email"} - Icon={EmailIcon} - /> - ); - const Phone = ( - setPhone(target.value)} - id={phone} - title={FC["phone"].innerText || "Введите номер телефона"} - desc={FC["phone"].text || "Номер телефона"} - Icon={PhoneIcon} - isPhone={true} - /> - ); - const Text = ( - setText(target.value)} - id={text} - title={FC["text"].text || "Введите фамилию"} - desc={FC["text"].innerText || "Фамилия"} - Icon={TextIcon} - /> - ); - const Adress = ( - setAdress(target.value)} - id={adress} - title={FC["address"].innerText || "Введите адрес"} - desc={FC["address"].text || "Адрес"} - Icon={AddressIcon} - /> - ); + if (!FC) return null; + const Name = ( + setName(target.value)} + id={name} + title={FC["name"].innerText || "Введите имя"} + desc={FC["name"].text || "Имя"} + Icon={NameIcon} + /> + ); + const Email = ( + { + console.log("onChange of email"); + console.log(target.value); + setEmail(target.value.replaceAll(/\s/g, "")); + }} + id={email} + title={FC["email"].innerText || "Введите Email"} + desc={FC["email"].text || "Email"} + Icon={EmailIcon} + type="email" + /> + ); + const Phone = ( + setText(target.value)} + onChangePhone={(phone: string) => { + console.log("onChange of phone"); + console.log(phone); + setPhone(phone); + }} + value={phone} + id={phone} + title={FC["phone"].innerText || "Введите номер телефона"} + desc={FC["phone"].text || "Номер телефона"} + Icon={PhoneIcon} + isPhone={true} + /> + ); + const Text = ( + setText(target.value)} + id={text} + title={FC["text"].text || "Введите фамилию"} + desc={FC["text"].innerText || "Фамилия"} + Icon={TextIcon} + /> + ); + const Adress = ( + setAdress(target.value)} + id={adress} + title={FC["address"].innerText || "Введите адрес"} + desc={FC["address"].text || "Адрес"} + Icon={AddressIcon} + /> + ); - if (Object.values(FC).some((data) => data.used)) { - return ( - <> - {FC["name"].used ? Name : <>} - {FC["email"].used ? Email : <>} - {FC["phone"].used ? Phone : <>} - {FC["text"].used ? Text : <>} - {FC["address"].used ? Adress : <>} - - ); - } else { - return ( - <> - {Name} - {Email} - {Phone} - - ); - } + if (Object.values(FC).some((data) => data.used)) { + return ( + <> + {FC["name"].used ? Name : <>} + {FC["email"].used ? Email : <>} + {FC["phone"].used ? Phone : <>} + {FC["text"].used ? Text : <>} + {FC["address"].used ? Adress : <>} + + ); + } else { + return ( + <> + {Name} + {Email} + {Phone} + + ); + } }; diff --git a/lib/components/ViewPublicationPage/Footer.tsx b/lib/components/ViewPublicationPage/Footer.tsx index fa5b6ac..ee5203e 100644 --- a/lib/components/ViewPublicationPage/Footer.tsx +++ b/lib/components/ViewPublicationPage/Footer.tsx @@ -1,58 +1,54 @@ import { ReactNode } from "react"; import { Box, Typography, useTheme } from "@mui/material"; -import { useQuizData } from "@contexts/QuizDataContext"; +import { useQuizSettings } from "@contexts/QuizDataContext"; import Stepper from "@ui_kit/Stepper"; type FooterProps = { - stepNumber: number | null; - nextButton: ReactNode; - prevButton: ReactNode; + stepNumber: number | null; + nextButton: ReactNode; + prevButton: ReactNode; }; export const Footer = ({ stepNumber, nextButton, prevButton }: FooterProps) => { - const theme = useTheme(); - const { questions, settings } = useQuizData(); - const questionsAmount = questions.filter( - ({ type }) => type !== "result" - ).length; + const theme = useTheme(); + const { questions, settings } = useQuizSettings(); + const questionsAmount = questions.filter(({ type }) => type !== "result").length; - return ( - - - {stepNumber !== null && ( - - - Вопрос {stepNumber} из {questionsAmount} - - - - )} - {prevButton} - {nextButton} - - - ); + return ( + + + {stepNumber !== null && ( + + + Вопрос {stepNumber} из {questionsAmount} + + + + )} + {prevButton} + {nextButton} + + + ); }; diff --git a/lib/components/ViewPublicationPage/Question.tsx b/lib/components/ViewPublicationPage/Question.tsx index 5a9f7bb..f63f1b3 100644 --- a/lib/components/ViewPublicationPage/Question.tsx +++ b/lib/components/ViewPublicationPage/Question.tsx @@ -15,7 +15,7 @@ import { Varimg } from "./questions/Varimg"; import type { RealTypedQuizQuestion } from "../../model/questionTypes/shared"; -import { useQuizData } from "@contexts/QuizDataContext"; +import { useQuizSettings } from "@contexts/QuizDataContext"; import { NameplateLogoFQ } from "@icons/NameplateLogoFQ"; import { NameplateLogoFQDark } from "@icons/NameplateLogoFQDark"; import { notReachable } from "@utils/notReachable"; @@ -40,7 +40,7 @@ export const Question = ({ questionSelect, }: Props) => { const theme = useTheme(); - const { settings, show_badge } = useQuizData(); + const { settings, show_badge, quizId } = useQuizSettings(); return ( {questionSelect} -
+
); }; -function QuestionByType({ - question, - stepNumber, -}: { - question: RealTypedQuizQuestion; - stepNumber: number | null; -}) { +function QuestionByType({ question, stepNumber }: { question: RealTypedQuizQuestion; stepNumber: number | null }) { switch (question.type) { case "variant": return ; diff --git a/lib/components/ViewPublicationPage/QuestionSelect.tsx b/lib/components/ViewPublicationPage/QuestionSelect.tsx index 3c4964b..c2dff7b 100644 --- a/lib/components/ViewPublicationPage/QuestionSelect.tsx +++ b/lib/components/ViewPublicationPage/QuestionSelect.tsx @@ -1,112 +1,112 @@ -import { useQuizData } from "@/contexts/QuizDataContext"; +import { useQuizSettings } from "@/contexts/QuizDataContext"; import { AnyTypedQuizQuestion } from "@/model/questionTypes/shared"; import { Box, FormControl, MenuItem, Select as MuiSelect, useTheme } from "@mui/material"; - interface Props { - selectedQuestion: AnyTypedQuizQuestion; - setQuestion: (questionIdF: string) => void; + selectedQuestion: AnyTypedQuizQuestion; + setQuestion: (questionIdF: string) => void; } export default function QuestionSelect({ selectedQuestion, setQuestion }: Props) { - const theme = useTheme(); - const { questions, preview } = useQuizData(); + const theme = useTheme(); + const { questions, preview } = useQuizSettings(); - if (!preview) return null; + if (!preview) return null; - return ( - - + + { + setQuestion(target.value); + }} + sx={{ + height: "48px", + borderRadius: "8px", + "& .MuiOutlinedInput-notchedOutline": { + border: `1px solid ${theme.palette.primary.main} !important`, + }, + "& .MuiSelect-icon": { + color: theme.palette.primary.main, + }, + }} + MenuProps={{ + PaperProps: { + sx: { + mt: "8px", + p: "4px", + borderRadius: "8px", + border: "1px solid #EEE4FC", + boxShadow: "0px 8px 24px rgba(210, 208, 225, 0.4)", + backgroundColor: theme.palette.background.default, + }, + }, + MenuListProps: { + sx: { + py: 0, + display: "flex", + flexDirection: "column", + gap: "8px", + "& .Mui-selected": { + backgroundColor: theme.palette.background.default, + color: theme.palette.primary.main, + }, + }, + }, + }} + inputProps={{ + sx: { + color: theme.palette.primary.main, + display: "block", + px: "9px", + gap: "20px", + width: "87%", + overflow: "hidden", + textOverflow: "ellipsis", + }, + }} + > + {questions + .filter((q) => q.type !== "result") + .map((question, index) => ( + - { - setQuestion(target.value); - }} - sx={{ - height: "48px", - borderRadius: "8px", - "& .MuiOutlinedInput-notchedOutline": { - border: `1px solid ${theme.palette.primary.main} !important`, - }, - "& .MuiSelect-icon": { - color: theme.palette.primary.main, - }, - }} - MenuProps={{ - PaperProps: { - sx: { - mt: "8px", - p: "4px", - borderRadius: "8px", - border: "1px solid #EEE4FC", - boxShadow: "0px 8px 24px rgba(210, 208, 225, 0.4)", - backgroundColor: theme.palette.background.default, - }, - }, - MenuListProps: { - sx: { - py: 0, - display: "flex", - flexDirection: "column", - gap: "8px", - "& .Mui-selected": { - backgroundColor: theme.palette.background.default, - color: theme.palette.primary.main, - }, - }, - }, - }} - inputProps={{ - sx: { - color: theme.palette.primary.main, - display: "block", - px: "9px", - gap: "20px", - width: "87%", - overflow: "hidden", - textOverflow: "ellipsis", - }, - }} - > - {questions.filter((q) => q.type !== "result").map( - (question, index) => ( - - {`${index + 1}. ${question.title}`} - - ), - )} - - - - ); + > + {`${index + 1}. ${question.title}`} + + ))} + + + + ); } diff --git a/lib/components/ViewPublicationPage/ResultForm.tsx b/lib/components/ViewPublicationPage/ResultForm.tsx index 22e0184..cd620c6 100644 --- a/lib/components/ViewPublicationPage/ResultForm.tsx +++ b/lib/components/ViewPublicationPage/ResultForm.tsx @@ -4,18 +4,16 @@ import { Box, Button, Link, Typography, useTheme } from "@mui/material"; import { useQuizViewStore } from "@/stores/quizView"; import { useRootContainerSize } from "@contexts/RootContainerWidthContext"; -import { useQuizData } from "@contexts/QuizDataContext"; +import { useQuizSettings } from "@contexts/QuizDataContext"; import { useVkMetricsGoals } from "@/utils/hooks/metrics/useVkMetricsGoals"; import { useYandexMetricsGoals } from "@/utils/hooks/metrics/useYandexMetricsGoals"; import { DESIGN_LIST } from "@/utils/designList"; import { quizThemes } from "@utils/themes/Publication/themePublication"; - -import YoutubeEmbedIframe from "./tools/YoutubeEmbedIframe"; - import { NameplateLogo } from "@icons/NameplateLogo"; import type { QuizQuestionResult } from "@/model/questionTypes/result"; +import QuizVideo from "@/ui_kit/VideoIframe/VideoIframe"; type ResultFormProps = { resultQuestion: QuizQuestionResult; @@ -25,10 +23,8 @@ export const ResultForm = ({ resultQuestion }: ResultFormProps) => { const theme = useTheme(); const isMobile = useRootContainerSize() < 650; const isTablet = useRootContainerSize() < 1000; - const { settings, show_badge, quizId } = useQuizData(); - const setCurrentQuizStep = useQuizViewStore( - (state) => state.setCurrentQuizStep - ); + const { settings, show_badge, quizId } = useQuizSettings(); + const setCurrentQuizStep = useQuizViewStore((state) => state.setCurrentQuizStep); const spec = settings.cfg.spec; const vkMetrics = useVkMetricsGoals(settings.cfg.vkMetricsNumber); const yandexMetrics = useYandexMetricsGoals(settings.cfg.yandexMetricsNumber); @@ -36,7 +32,7 @@ export const ResultForm = ({ resultQuestion }: ResultFormProps) => { useEffect(() => { vkMetrics.resultIdShown(resultQuestion.id); yandexMetrics.resultIdShown(resultQuestion.id); - }, []); + }, [resultQuestion.id, vkMetrics, yandexMetrics]); return ( { backgroundColor: theme.palette.background.default, backgroundPosition: "center", backgroundSize: "cover", - backgroundImage: - settings.cfg.design && !isMobile - ? `url(${DESIGN_LIST[settings.cfg.theme]})` - : null, + backgroundImage: settings.cfg.design && !isMobile ? `url(${DESIGN_LIST[settings.cfg.theme]})` : null, + position: "relative", }} > { flexDirection: "column", justifyContent: "space-between", alignItems: "center", - pt: "30px", width: "100%", height: "100%", - overflow: "auto", background: settings.cfg.design && !isMobile ? quizThemes[settings.cfg.theme].isLight ? "transparent" - : "linear-gradient(90deg,#272626, transparent)" + : "linear-gradient(90deg, rgba(39, 38, 38, 0.95) 7.66%, rgba(42, 42, 46, 0.85) 42.12%, rgba(51, 54, 71, 0.4) 100%)" : theme.palette.background.default, - scrollbarWidth: "none", - "&::-webkit-scrollbar": { - width: 0, - }, }} > - - Ваш результат: - - - - {!resultQuestion?.content.useImage && - resultQuestion.content.video && ( - + Ваш результат: + + + + {!resultQuestion?.content.useImage && resultQuestion.content.video && ( + )} - {resultQuestion?.content.useImage && resultQuestion.content.back && ( - + {resultQuestion?.content.useImage && resultQuestion.content.back && ( - - )} - {resultQuestion.description !== "" && - resultQuestion.description !== " " && ( + > +
resultImage + + )} + {resultQuestion.description !== "" && resultQuestion.description !== " " && ( { )} - - {resultQuestion.title} - + + {resultQuestion.title} + - {resultQuestion.content.text !== "" && - resultQuestion.content.text !== " " && ( + {resultQuestion.content.text !== "" && resultQuestion.content.text !== " " && ( { {resultQuestion.content.text} )} + - - + {show_badge && ( - {show_badge && ( - + + )} + + {settings.cfg.resultInfo.showResultForm === "before" && + settings.cfg.showfc !== false && + !settings.cfg.score && ( + )} - - - - {settings.cfg.resultInfo.showResultForm === "before" && - !settings.cfg.score && ( - - )} - {settings.cfg.resultInfo.showResultForm === "after" && - resultQuestion.content.redirect && ( - - )} - + {settings.cfg.resultInfo.showResultForm === "after" && resultQuestion.content.redirect && ( + + )} - + ); }; diff --git a/lib/components/ViewPublicationPage/StartPageViewPublication/QuizPreviewLayoutByType.tsx b/lib/components/ViewPublicationPage/StartPageViewPublication/QuizPreviewLayoutByType.tsx index bebcfb1..c643e99 100644 --- a/lib/components/ViewPublicationPage/StartPageViewPublication/QuizPreviewLayoutByType.tsx +++ b/lib/components/ViewPublicationPage/StartPageViewPublication/QuizPreviewLayoutByType.tsx @@ -3,10 +3,7 @@ import { StartPageMobile } from "./StartPageMobile"; import { useRootContainerSize } from "@contexts/RootContainerWidthContext"; -import type { - QuizStartpageAlignType, - QuizStartpageType, -} from "@model/settingsData"; +import type { QuizStartpageAlignType, QuizStartpageType } from "@model/settingsData"; type QuizPreviewLayoutByTypeProps = { quizHeaderBlock: JSX.Element; diff --git a/lib/components/ViewPublicationPage/StartPageViewPublication/StartPageDesktop.tsx b/lib/components/ViewPublicationPage/StartPageViewPublication/StartPageDesktop.tsx index 53790ff..3bf5d98 100644 --- a/lib/components/ViewPublicationPage/StartPageViewPublication/StartPageDesktop.tsx +++ b/lib/components/ViewPublicationPage/StartPageViewPublication/StartPageDesktop.tsx @@ -1,277 +1,261 @@ -import {Box} from "@mui/material"; +import { Box } from "@mui/material"; -import {useRootContainerSize} from "@contexts/RootContainerWidthContext"; -import {useQuizData} from "@contexts/QuizDataContext"; +import { useRootContainerSize } from "@contexts/RootContainerWidthContext"; +import { useQuizSettings } from "@contexts/QuizDataContext"; -import {notReachable} from "@utils/notReachable"; -import {quizThemes} from "@utils/themes/Publication/themePublication"; +import { notReachable } from "@utils/notReachable"; +import { quizThemes } from "@utils/themes/Publication/themePublication"; -import type { - QuizStartpageAlignType, - QuizStartpageType, -} from "@model/settingsData"; -import {DESIGN_LIST} from "@/utils/designList"; +import type { QuizStartpageAlignType, QuizStartpageType } from "@model/settingsData"; +import { DESIGN_LIST } from "@/utils/designList"; type StartPageDesktopProps = { - quizHeaderBlock: JSX.Element; - quizMainBlock: JSX.Element; - backgroundBlock: JSX.Element | null; - startpageType: QuizStartpageType; - alignType: QuizStartpageAlignType; + quizHeaderBlock: JSX.Element; + quizMainBlock: JSX.Element; + backgroundBlock: JSX.Element | null; + startpageType: QuizStartpageType; + alignType: QuizStartpageAlignType; }; type LayoutProps = Omit; -const StandartLayout = ({ - alignType, - quizHeaderBlock, - quizMainBlock, - backgroundBlock, - }: LayoutProps) => { - const size = useRootContainerSize(); - const isTablet = size >= 700 && size < 1100; - const {settings} = useQuizData(); +const StandartLayout = ({ alignType, quizHeaderBlock, quizMainBlock, backgroundBlock }: LayoutProps) => { + const size = useRootContainerSize(); + const isTablet = size >= 700 && size < 1100; + const { settings } = useQuizSettings(); - return ( + return ( + + - - - {quizHeaderBlock} - {quizMainBlock} - - {settings.cfg.startpage.background.desktop && ( - img": {width: "100%", borderRadius: "12px"}, - }} - >{backgroundBlock} - )} - + {quizHeaderBlock} + {quizMainBlock} - ); + {settings.cfg.startpage.background.desktop && ( + + img": { width: "100%", borderRadius: "12px" }, + }} + > + {backgroundBlock} + + + )} + + + ); }; -const ExpandedLayout = ({ - alignType, - quizHeaderBlock, - quizMainBlock, - backgroundBlock, - }: LayoutProps) => { - const size = useRootContainerSize(); - const isTablet = size >= 700 && size < 1100; - return ( - <> - - - {alignType !== "center" && quizHeaderBlock} - {quizMainBlock} - - - - {backgroundBlock} - - - ); -} - -const CenteredLayout = ({ - quizHeaderBlock, - quizMainBlock, - backgroundBlock, - }: LayoutProps) => { - const isTablet = useRootContainerSize() < 1100; - const {settings} = useQuizData(); - return ( +const ExpandedLayout = ({ alignType, quizHeaderBlock, quizMainBlock, backgroundBlock }: LayoutProps) => { + const size = useRootContainerSize(); + const isTablet = size >= 700 && size < 1100; + return ( + <> + - {quizHeaderBlock} - {backgroundBlock && settings.cfg.startpage.background.desktop && ( - img": {width: "100%", borderRadius: "12px"}, - }} - > - {backgroundBlock} - - )} - {quizMainBlock} + {alignType !== "center" && quizHeaderBlock} + {quizMainBlock} - ); + + + {backgroundBlock} + + + ); +}; + +const CenteredLayout = ({ quizHeaderBlock, quizMainBlock, backgroundBlock }: LayoutProps) => { + const isTablet = useRootContainerSize() < 1100; + const { settings } = useQuizSettings(); + return ( + + {quizHeaderBlock} + {backgroundBlock && settings.cfg.startpage.background.desktop && ( + img": { width: "100%", borderRadius: "12px" }, + }} + > + {backgroundBlock} + + )} + {quizMainBlock} + + ); }; export const StartPageDesktop = ({ - quizHeaderBlock, - quizMainBlock, - backgroundBlock, - startpageType, - alignType, - }: StartPageDesktopProps) => { - switch (startpageType) { - case null: - case "standard": { - return ( - - ); - } - - case "expanded": { - return ( - - ); - } - - case "centered": { - return ( - - ); - } - - default: - notReachable(startpageType); + quizHeaderBlock, + quizMainBlock, + backgroundBlock, + startpageType, + alignType, +}: StartPageDesktopProps) => { + switch (startpageType) { + case null: + case "standard": { + return ( + + ); } + + case "expanded": { + return ( + + ); + } + + case "centered": { + return ( + + ); + } + + default: + notReachable(startpageType); + } }; diff --git a/lib/components/ViewPublicationPage/StartPageViewPublication/StartPageMobile.tsx b/lib/components/ViewPublicationPage/StartPageViewPublication/StartPageMobile.tsx index 7b11f30..34444c5 100644 --- a/lib/components/ViewPublicationPage/StartPageViewPublication/StartPageMobile.tsx +++ b/lib/components/ViewPublicationPage/StartPageViewPublication/StartPageMobile.tsx @@ -1,283 +1,270 @@ -import {Box} from "@mui/material"; +import { Box } from "@mui/material"; -import {useQuizData} from "@contexts/QuizDataContext"; +import { useQuizSettings } from "@contexts/QuizDataContext"; -import {notReachable} from "@utils/notReachable"; -import {quizThemes} from "@utils/themes/Publication/themePublication"; +import { notReachable } from "@utils/notReachable"; +import { quizThemes } from "@utils/themes/Publication/themePublication"; -import type {QuizStartpageType} from "@model/settingsData"; -import {DESIGN_LIST} from "@/utils/designList"; +import type { QuizStartpageType } from "@model/settingsData"; +import { DESIGN_LIST } from "@/utils/designList"; type StartPageMobileProps = { - quizHeaderBlock: JSX.Element; - quizMainBlock: JSX.Element; - backgroundBlock: JSX.Element | null; - startpageType: QuizStartpageType; + quizHeaderBlock: JSX.Element; + quizMainBlock: JSX.Element; + backgroundBlock: JSX.Element | null; + startpageType: QuizStartpageType; }; type MobileLayoutProps = Omit; -const StandartMobileLayout = ({ - quizHeaderBlock, - quizMainBlock, - backgroundBlock, - }: MobileLayoutProps) => { - const {settings} = useQuizData(); +const StandartMobileLayout = ({ quizHeaderBlock, quizMainBlock, backgroundBlock }: MobileLayoutProps) => { + const { settings } = useQuizSettings(); - return ( - + return ( + + + {quizHeaderBlock} + {settings.cfg.startpage.background.desktop && ( + img": { + width: "100%", + borderRadius: "12px", + }, + }} > - - {quizHeaderBlock} - - {settings.cfg.startpage.background.desktop && ( - - img": { - width: "100%", - borderRadius: "12px" - }, - }} - > - {backgroundBlock} - - - )} - - {quizMainBlock} - + {backgroundBlock} + + )} + + {quizMainBlock} - ); + + + ); }; -const ExpandedMobileLayout = ({ - quizHeaderBlock, - quizMainBlock, - backgroundBlock, - }: MobileLayoutProps) => ( +const ExpandedMobileLayout = ({ quizHeaderBlock, quizMainBlock, backgroundBlock }: MobileLayoutProps) => ( + - - - {quizHeaderBlock} - {quizMainBlock} - - - img": { - display: "block", - minHeight: "100%", - }, - }} - > - {backgroundBlock} - + + {quizHeaderBlock} + {quizMainBlock} + + img": { + display: "block", + minHeight: "100%", + }, + }} + > + {backgroundBlock} + + ); -const CenteredMobileLayout = ({ - quizHeaderBlock, - quizMainBlock, - backgroundBlock, - }: MobileLayoutProps) => { - const {settings} = useQuizData(); - return ( - { + const { settings } = useQuizSettings(); + return ( + + + {quizHeaderBlock} + {settings.cfg.startpage.background.desktop && ( + img": { width: "100%", borderRadius: "12px" }, }} + > + {backgroundBlock} + + )} + - - {quizHeaderBlock} - {settings.cfg.startpage.background.desktop && ( - img": {width: "100%", borderRadius: "12px"}, - }} - > - {backgroundBlock} - - )} - - {quizMainBlock} - - + {quizMainBlock} - ); -} + + + ); +}; export const StartPageMobile = ({ - quizHeaderBlock, - quizMainBlock, - backgroundBlock, - startpageType, - }: StartPageMobileProps) => { - switch (startpageType) { - case null: - case "standard": { - return ( - - ); - } - - case "expanded": { - return ( - - ); - } - - case "centered": { - return ( - - ); - } - - default: - notReachable(startpageType); + quizHeaderBlock, + quizMainBlock, + backgroundBlock, + startpageType, +}: StartPageMobileProps) => { + switch (startpageType) { + case null: + case "standard": { + return ( + + ); } + + case "expanded": { + return ( + + ); + } + + case "centered": { + return ( + + ); + } + + default: + notReachable(startpageType); + } }; diff --git a/lib/components/ViewPublicationPage/StartPageViewPublication/index.tsx b/lib/components/ViewPublicationPage/StartPageViewPublication/index.tsx index 3c4669a..ecf067c 100644 --- a/lib/components/ViewPublicationPage/StartPageViewPublication/index.tsx +++ b/lib/components/ViewPublicationPage/StartPageViewPublication/index.tsx @@ -1,16 +1,8 @@ -import { - Box, - Button, - ButtonBase, - Link, - Paper, - Typography, - useTheme, -} from "@mui/material"; +import { Box, Button, ButtonBase, Link, Paper, Typography, useTheme } from "@mui/material"; import { QuizPreviewLayoutByType } from "./QuizPreviewLayoutByType"; -import { useQuizData } from "@contexts/QuizDataContext"; +import { useQuizSettings } from "@contexts/QuizDataContext"; import { useRootContainerSize } from "@contexts/RootContainerWidthContext"; import { useUADevice } from "@utils/hooks/useUADevice"; @@ -22,16 +14,13 @@ import { DESIGN_LIST } from "@/utils/designList"; import { useVkMetricsGoals } from "@/utils/hooks/metrics/useVkMetricsGoals"; import { useYandexMetricsGoals } from "@/utils/hooks/metrics/useYandexMetricsGoals"; - -import YoutubeEmbedIframe from "../tools/YoutubeEmbedIframe"; +import QuizVideo from "@/ui_kit/VideoIframe/VideoIframe"; export const StartPageViewPublication = () => { const theme = useTheme(); - const { settings, show_badge, quizId, questions } = useQuizData(); + const { settings, show_badge, quizId, questions } = useQuizSettings(); const { isMobileDevice } = useUADevice(); - const setCurrentQuizStep = useQuizViewStore( - (state) => state.setCurrentQuizStep - ); + const setCurrentQuizStep = useQuizViewStore((state) => state.setCurrentQuizStep); const size = useRootContainerSize(); const isMobile = size < 700; @@ -50,18 +39,11 @@ export const StartPageViewPublication = () => { const background = settings.cfg.startpage.background.type === "image" ? ( { /> ) : settings.cfg.startpage.background.type === "video" ? ( settings.cfg.startpage.background.video ? ( - { display: "flex", alignItems: "center", flexWrap: - settings.cfg.startpageType === "expanded" && - settings.cfg.startpage.position === "center" + settings.cfg.startpageType === "expanded" && settings.cfg.startpage.position === "center" ? "nowrap" : "wrap", gap: isMobile ? "20px" : "30px", @@ -116,15 +95,11 @@ export const StartPageViewPublication = () => { ? isMobile ? "20px" : "25px" - : settings.cfg.startpageType === "expanded" && - settings.cfg.startpage.position === "center" && - !isMobile + : settings.cfg.startpageType === "expanded" && settings.cfg.startpage.position === "center" && !isMobile ? 0 : "7px", justifyContent: - settings.cfg.startpageType === "expanded" && - settings.cfg.startpage.position === "center" && - isMobile + settings.cfg.startpageType === "expanded" && settings.cfg.startpage.position === "center" && isMobile ? "center" : undefined, }} @@ -143,13 +118,9 @@ export const StartPageViewPublication = () => { { textDecoration: "none", marginLeft: settings.cfg.startpageType === "expanded" && - settings.cfg.startpage.position === "center" && - !isTablet && - !isMobile + settings.cfg.startpage.position === "center" && + !isTablet && + !isMobile ? "61px" : undefined, }} @@ -223,14 +194,11 @@ export const StartPageViewPublication = () => { vkMetrics.emailOpened(); yandexMetrics.emailOpened(); - setTimeout(() => { location.href = ( - settings.cfg.info.site.includes("https") - ? settings.cfg.info.site - : `https://${settings.cfg.info.site}` + settings.cfg.info.site.includes("https") ? settings.cfg.info.site : `https://${settings.cfg.info.site}` ).replace(/\s+/g, ""); - }, 1000) + }, 1000); }; return ( @@ -242,8 +210,7 @@ export const StartPageViewPublication = () => { width: "100%", background: settings.cfg.startpageType === "expanded" - ? settings.cfg.startpage.position === "left" || - (isMobile && settings.cfg.startpage.position === "right") + ? settings.cfg.startpage.position === "left" || (isMobile && settings.cfg.startpage.position === "right") ? "linear-gradient(90deg, rgba(39, 38, 38, 0.95) 7.66%, rgba(42, 42, 46, 0.85) 42.12%, rgba(51, 54, 71, 0.4) 100%)" : settings.cfg.startpage.position === "center" ? "linear-gradient(0deg, rgba(39, 38, 38, 0.95) 7.66%, rgba(42, 42, 46, 0.85) 42.12%, rgba(51, 54, 71, 0.4) 100%)" @@ -261,10 +228,7 @@ export const StartPageViewPublication = () => { sx={{ display: "flex", flexDirection: "column", - justifyContent: - settings.cfg.startpageType === "standard" && isMobile - ? "start" - : "center", + justifyContent: settings.cfg.startpageType === "standard" && isMobile ? "start" : "center", flexGrow: settings.cfg.startpageType === "centered" ? 0 : 1, wordBreak: "break-word", alignItems: @@ -275,19 +239,14 @@ export const StartPageViewPublication = () => { ? "center" : "start" : "start", - marginTop: - settings.cfg.startpageType === "centered" - ? "30px" - : isMobile - ? "0px" - : "5px", + marginTop: settings.cfg.startpageType === "centered" ? "30px" : isMobile ? "0px" : "5px", maxWidth: isMobile ? "100%" : settings.cfg.startpageType === "centered" ? "700px" : isTablet && - settings.cfg.startpageType !== "expanded" && - settings.cfg.startpage.position !== "center" + settings.cfg.startpageType !== "expanded" && + settings.cfg.startpage.position !== "center" ? "380px" : "531px", }} @@ -302,14 +261,10 @@ export const StartPageViewPublication = () => { overflowWrap: "break-word", width: "100%", textAlign: - settings.cfg.startpageType === "centered" || - settings.cfg.startpage.position === "center" + settings.cfg.startpageType === "centered" || settings.cfg.startpage.position === "center" ? "center" : "-moz-initial", - color: - settings.cfg.startpageType === "expanded" - ? "white" - : theme.palette.text.primary, + color: settings.cfg.startpageType === "expanded" ? "white" : theme.palette.text.primary, }} > {settings.name} @@ -323,23 +278,15 @@ export const StartPageViewPublication = () => { overflowWrap: "break-word", width: "100%", textAlign: - settings.cfg.startpageType === "centered" || - settings.cfg.startpage.position === "center" + settings.cfg.startpageType === "centered" || settings.cfg.startpage.position === "center" ? "center" : "-moz-initial", - color: - settings.cfg.startpageType === "expanded" - ? "white" - : theme.palette.text.primary, + color: settings.cfg.startpageType === "expanded" ? "white" : theme.palette.text.primary, }} > {settings.cfg.startpage.description} - + { maxWidth: "300px", display: (settings.cfg.startpageType === "centered" && isMobile) || - (settings.cfg.startpageType === "expanded" && - settings.cfg.startpage.position === "center" && - isMobile) + (settings.cfg.startpageType === "expanded" && + settings.cfg.startpage.position === "center" && + isMobile) ? "flex" : "block", flexDirection: "column", alignItems: "center", order: - settings.cfg.startpageType === "expanded" && - settings.cfg.startpage.position === "center" + settings.cfg.startpageType === "expanded" && settings.cfg.startpage.position === "center" ? "2" : "0", }} @@ -418,8 +356,8 @@ export const StartPageViewPublication = () => { marginTop: "10px", marginLeft: settings.cfg.startpageType === "expanded" && - settings.cfg.startpage.position === "center" && - !isMobile + settings.cfg.startpage.position === "center" && + !isMobile ? "auto" : undefined, }} @@ -430,12 +368,14 @@ export const StartPageViewPublication = () => { fontSize: "16px", textAlign: settings.cfg.startpageType === "expanded" && - settings.cfg.startpage.position === "center" && - !isMobile - ? "end" : settings.cfg.startpageType === "expanded" && - settings.cfg.startpage.position === "center" && - isMobile || settings.cfg.startpageType === "centered" && - isMobile ? "center" + settings.cfg.startpage.position === "center" && + !isMobile + ? "end" + : (settings.cfg.startpageType === "expanded" && + settings.cfg.startpage.position === "center" && + isMobile) || + (settings.cfg.startpageType === "centered" && isMobile) + ? "center" : "start", color: theme.palette.primary.main, overflow: "hidden", @@ -454,15 +394,11 @@ export const StartPageViewPublication = () => { sx={{ lineHeight: "19px", textAlign: - settings.cfg.startpageType === "expanded" && - settings.cfg.startpage.position === "center" + settings.cfg.startpageType === "expanded" && settings.cfg.startpage.position === "center" ? "end" : "none", fontSize: "16px", - color: - settings.cfg.startpageType === "expanded" - ? "#FFFFFF" - : theme.palette.text.primary, + color: settings.cfg.startpageType === "expanded" ? "#FFFFFF" : theme.palette.text.primary, }} > {settings.cfg.info.phonenumber} @@ -476,8 +412,8 @@ export const StartPageViewPublication = () => { marginTop: "10px", marginLeft: settings.cfg.startpageType === "expanded" && - settings.cfg.startpage.position === "center" && - !isMobile + settings.cfg.startpage.position === "center" && + !isMobile ? "auto" : undefined, }} @@ -485,16 +421,12 @@ export const StartPageViewPublication = () => { {settings.cfg.info.phonenumber} @@ -506,16 +438,12 @@ export const StartPageViewPublication = () => { sx={{ lineHeight: "19px", textAlign: - settings.cfg.startpageType === "expanded" && - settings.cfg.startpage.position === "center" + settings.cfg.startpageType === "expanded" && settings.cfg.startpage.position === "center" ? "end" : "none", fontSize: "16px", marginTop: "10px", - color: - settings.cfg.startpageType === "expanded" - ? "#FFFFFF" - : theme.palette.text.primary, + color: settings.cfg.startpageType === "expanded" ? "#FFFFFF" : theme.palette.text.primary, }} > {settings.cfg.info.phonenumber} @@ -530,24 +458,20 @@ export const StartPageViewPublication = () => { fontSize: "12px", textAlign: settings.cfg.startpageType === "expanded" && - settings.cfg.startpage.position === "center" && - !isMobile + settings.cfg.startpage.position === "center" && + !isMobile ? "end" : (settings.cfg.startpageType === "expanded" && - settings.cfg.startpage.position === "center" && - isMobile) || - (settings.cfg.startpageType === "centered" && - isMobile) + settings.cfg.startpage.position === "center" && + isMobile) || + (settings.cfg.startpageType === "centered" && isMobile) ? "center" : "none", maxHeight: "120px", overflow: "auto", marginTop: "10px", "&::-webkit-scrollbar": { width: 0 }, - color: - settings.cfg.startpageType === "expanded" - ? "white" - : theme.palette.text.primary, + color: settings.cfg.startpageType === "expanded" ? "white" : theme.palette.text.primary, }} > {settings.cfg.info.law} diff --git a/lib/components/ViewPublicationPage/ViewPublicationPage.tsx b/lib/components/ViewPublicationPage/ViewPublicationPage.tsx index e73e768..b4c22cd 100644 --- a/lib/components/ViewPublicationPage/ViewPublicationPage.tsx +++ b/lib/components/ViewPublicationPage/ViewPublicationPage.tsx @@ -1,5 +1,9 @@ -import { sendAnswer } from "@api/quizRelase"; -import { useQuizData } from "@contexts/QuizDataContext"; +import { ContactForm } from "@/components/ViewPublicationPage/ContactForm/ContactForm.tsx"; +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"; @@ -7,29 +11,22 @@ import { notReachable } from "@utils/notReachable"; import { quizThemes } from "@utils/themes/Publication/themePublication"; import { enqueueSnackbar } from "notistack"; import { ReactElement, useEffect } from "react"; +import { Helmet } from "react-helmet-async"; import { Question } from "./Question"; +import QuestionSelect from "./QuestionSelect"; import { ResultForm } from "./ResultForm"; import { StartPageViewPublication } from "./StartPageViewPublication"; import NextButton from "./tools/NextButton"; import PrevButton from "./tools/PrevButton"; -import QuestionSelect from "./QuestionSelect"; -import { useYandexMetrics } from "@/utils/hooks/metrics/useYandexMetrics"; -import { useVKMetrics } from "@/utils/hooks/metrics/useVKMetrics"; -import { ContactForm } from "@/components/ViewPublicationPage/ContactForm/ContactForm.tsx"; export default function ViewPublicationPage() { - const { - settings, - recentlyCompleted, - quizId, - preview, - changeFaviconAndTitle, - } = useQuizData(); + const { settings, recentlyCompleted, quizId, preview, changeFaviconAndTitle } = useQuizSettings(); const answers = useQuizViewStore((state) => state.answers); let currentQuizStep = useQuizViewStore((state) => state.currentQuizStep); const { currentQuestion, currentQuestionStepNumber, + nextQuestion, isNextButtonEnabled, isPreviousButtonEnabled, moveToPrevQuestion, @@ -40,10 +37,6 @@ export default function ViewPublicationPage() { useYandexMetrics(settings?.cfg?.yandexMetricsNumber); useVKMetrics(settings?.cfg?.vkMetricsNumber); - const isAnswer = answers.some( - (ans) => ans.questionId === currentQuestion?.id - ); - useEffect( function setFaviconAndTitle() { if (!changeFaviconAndTitle) return; @@ -58,21 +51,23 @@ export default function ViewPublicationPage() { [changeFaviconAndTitle, settings.cfg.startpage.favIcon, settings.name] ); - if (recentlyCompleted) throw new Error("Quiz already completed"); - if (currentQuizStep === "startpage" && settings.cfg.noStartPage) - currentQuizStep = "question"; + if (settings.cfg.antifraud && recentlyCompleted) throw new Error("Quiz already completed"); + if (currentQuizStep === "startpage" && settings.cfg.noStartPage) currentQuizStep = "question"; if (!currentQuestion) return ( - - + + Вопрос не выбран ); + const currentAnswer = answers.find(({ questionId }) => questionId === currentQuestion.id); + let quizStepElement: ReactElement; switch (currentQuizStep) { case "startpage": { @@ -99,20 +94,15 @@ export default function ViewPublicationPage() { nextButton={ { - if (!isAnswer) { - try { - await sendAnswer({ - questionId: currentQuestion.id, - body: "", - qid: quizId, - preview, - }); - } catch (e) { - enqueueSnackbar("ответ не был засчитан"); - } - } + moveToNextQuestion={() => { moveToNextQuestion(); + + if (preview) return; + + sendQuestionAnswer(quizId, currentQuestion, currentAnswer)?.catch((e) => { + enqueueSnackbar("Ошибка при отправке ответа"); + console.error("Error sending answer", e); + }); }} /> } @@ -139,10 +129,23 @@ export default function ViewPublicationPage() { notReachable(currentQuizStep); } + const preloadLinks = new Set([ + ...extractImageLinksFromQuestion(currentQuestion), + ...extractImageLinksFromQuestion(nextQuestion), + ]); + return ( - + + + {Array.from(preloadLinks).map((link) => ( + + ))} + {quizStepElement} ); diff --git a/lib/components/ViewPublicationPage/questions/Date/index.tsx b/lib/components/ViewPublicationPage/questions/Date/index.tsx index 5f8daed..1e21a04 100644 --- a/lib/components/ViewPublicationPage/questions/Date/index.tsx +++ b/lib/components/ViewPublicationPage/questions/Date/index.tsx @@ -1,53 +1,29 @@ -import { useState } from "react"; -import moment from "moment"; -import { DatePicker } from "@mui/x-date-pickers"; -import { Box, Typography, useTheme } from "@mui/material"; -import { enqueueSnackbar } from "notistack"; - -import { sendAnswer } from "@api/quizRelase"; import { useQuizViewStore } from "@/stores/quizView"; -import { useQuizData } from "@contexts/QuizDataContext"; - -import { quizThemes } from "@utils/themes/Publication/themePublication"; - +import { useQuizSettings } from "@contexts/QuizDataContext"; import CalendarIcon from "@icons/CalendarIcon"; - -import type { Moment } from "moment"; import type { QuizQuestionDate } from "@model/questionTypes/date"; +import { Box, Typography, useTheme } from "@mui/material"; +import { DatePicker } from "@mui/x-date-pickers"; +import { quizThemes } from "@utils/themes/Publication/themePublication"; +import type { Moment } from "moment"; +import moment from "moment"; type DateProps = { currentQuestion: QuizQuestionDate; }; export const Date = ({ currentQuestion }: DateProps) => { - const [isSending, setIsSending] = useState(false); - const { settings, quizId, preview } = useQuizData(); + const { settings } = useQuizSettings(); const answers = useQuizViewStore((state) => state.answers); const { updateAnswer } = useQuizViewStore((state) => state); const theme = useTheme(); - const answer = answers.find( - ({ questionId }) => questionId === currentQuestion.id - )?.answer as string; + const answer = answers.find(({ questionId }) => questionId === currentQuestion.id)?.answer as string; const currentAnswer = moment(answer) || moment(); const onDateChange = async (date: Moment | null) => { - if (isSending || !date) return; + if (!date) return; - setIsSending(true); - try { - await sendAnswer({ - questionId: currentQuestion.id, - body: moment(date).format("YYYY.MM.DD"), - qid: quizId, - preview, - }); - - updateAnswer(currentQuestion.id, date, 0); - } catch (error) { - enqueueSnackbar("ответ не был засчитан"); - } - - setIsSending(false); + updateAnswer(currentQuestion.id, date, 0); }; return ( @@ -94,8 +70,8 @@ export const Date = ({ currentQuestion }: DateProps) => { ? "#F2F3F7" : "rgba(154,154,175, 0.2)" : quizThemes[settings.cfg.theme].isLight - ? "white" - : theme.palette.background.default, + ? "white" + : theme.palette.background.default, borderRadius: "10px", maxWidth: "250px", pr: "30px", diff --git a/lib/components/ViewPublicationPage/questions/Emoji/EmojiVariant.tsx b/lib/components/ViewPublicationPage/questions/Emoji/EmojiVariant.tsx index 1041e91..b32079b 100644 --- a/lib/components/ViewPublicationPage/questions/Emoji/EmojiVariant.tsx +++ b/lib/components/ViewPublicationPage/questions/Emoji/EmojiVariant.tsx @@ -1,101 +1,45 @@ -import { - Box, - FormControl, - FormControlLabel, - Radio, - Typography, - useTheme, -} from "@mui/material"; -import { polyfillCountryFlagEmojis } from "country-flag-emoji-polyfill"; -import { enqueueSnackbar } from "notistack"; - +import type { QuestionVariant } from "@/model/questionTypes/shared"; +import { useQuizSettings } from "@contexts/QuizDataContext"; +import { Box, FormControl, FormControlLabel, Radio, Typography, useTheme } from "@mui/material"; import { useQuizViewStore } from "@stores/quizView"; -import { sendAnswer } from "@api/quizRelase"; -import { useQuizData } from "@contexts/QuizDataContext"; -import { quizThemes } from "@utils/themes/Publication/themePublication"; - import RadioCheck from "@ui_kit/RadioCheck"; import RadioIcon from "@ui_kit/RadioIcon"; - +import { quizThemes } from "@utils/themes/Publication/themePublication"; +import { polyfillCountryFlagEmojis } from "country-flag-emoji-polyfill"; import type { MouseEvent } from "react"; -import type { QuestionVariant } from "@/model/questionTypes/shared"; -import type { QuizQuestionEmoji } from "@model/questionTypes/emoji"; polyfillCountryFlagEmojis(); type EmojiVariantProps = { - currentQuestion: QuizQuestionEmoji; + questionId: string; variant: QuestionVariant; index: number; - isSending: boolean; - setIsSending: (isSending: boolean) => void; }; -export const EmojiVariant = ({ - currentQuestion, - variant, - index, - isSending, - setIsSending, -}: EmojiVariantProps) => { - const { quizId, settings, preview } = useQuizData(); +export const EmojiVariant = ({ variant, index, questionId }: EmojiVariantProps) => { + const { settings } = useQuizSettings(); const answers = useQuizViewStore((state) => state.answers); const { updateAnswer, deleteAnswer } = useQuizViewStore((state) => state); const theme = useTheme(); - const { answer } = - answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {}; + const { answer } = answers.find((answer) => answer.questionId === questionId) ?? {}; const onVariantClick = async (event: MouseEvent) => { event.preventDefault(); - if (isSending) return; - setIsSending(true); - try { - await sendAnswer({ - questionId: currentQuestion.id, - body: - currentQuestion.content.variants[index].extendedText + - " " + - currentQuestion.content.variants[index].answer, - qid: quizId, - preview, - }); + updateAnswer(questionId, variant.id, variant.points || 0); - updateAnswer( - currentQuestion.id, - currentQuestion.content.variants[index].id, - currentQuestion.content.variants[index].points || 0 - ); - } catch (error) { - enqueueSnackbar("ответ не был засчитан"); + if (answer === variant.id) { + deleteAnswer(questionId); } - - if (answer === currentQuestion.content.variants[index].id) { - deleteAnswer(currentQuestion.id); - try { - await sendAnswer({ - questionId: currentQuestion.id, - body: "", - qid: quizId, - preview, - }); - } catch (error) { - enqueueSnackbar("ответ не был засчитан"); - } - } - - setIsSending(false); }; return ( - {variant.extendedText && ( - {variant.extendedText} - )} + {variant.extendedText && {variant.extendedText}} - - {variant.answer} - + {variant.answer} } /> diff --git a/lib/components/ViewPublicationPage/questions/Emoji/index.tsx b/lib/components/ViewPublicationPage/questions/Emoji/index.tsx index ae53dab..e179290 100644 --- a/lib/components/ViewPublicationPage/questions/Emoji/index.tsx +++ b/lib/components/ViewPublicationPage/questions/Emoji/index.tsx @@ -1,10 +1,7 @@ -import { useState } from "react"; -import { Box, RadioGroup, Typography, useTheme } from "@mui/material"; -import { polyfillCountryFlagEmojis } from "country-flag-emoji-polyfill"; - -import { useQuizViewStore } from "@stores/quizView"; - import type { QuizQuestionEmoji } from "@model/questionTypes/emoji"; +import { Box, RadioGroup, Typography, useTheme } from "@mui/material"; +import { useQuizViewStore } from "@stores/quizView"; +import { polyfillCountryFlagEmojis } from "country-flag-emoji-polyfill"; import { EmojiVariant } from "./EmojiVariant"; polyfillCountryFlagEmojis(); @@ -14,12 +11,10 @@ type EmojiProps = { }; export const Emoji = ({ currentQuestion }: EmojiProps) => { - const [isSending, setIsSending] = useState(false); const answers = useQuizViewStore((state) => state.answers); const { updateAnswer } = useQuizViewStore((state) => state); const theme = useTheme(); - const { answer } = - answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {}; + const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {}; return ( @@ -32,9 +27,7 @@ export const Emoji = ({ currentQuestion }: EmojiProps) => { answer === id - )} + value={currentQuestion.content.variants.findIndex(({ id }) => answer === id)} onChange={({ target }) => updateAnswer( currentQuestion.id, @@ -50,16 +43,12 @@ export const Emoji = ({ currentQuestion }: EmojiProps) => { marginTop: "20px", }} > - + {currentQuestion.content.variants.map((variant, index) => ( ))} diff --git a/lib/components/ViewPublicationPage/questions/File/UploadFile.tsx b/lib/components/ViewPublicationPage/questions/File/UploadFile.tsx index 288b75a..c7bb3d5 100644 --- a/lib/components/ViewPublicationPage/questions/File/UploadFile.tsx +++ b/lib/components/ViewPublicationPage/questions/File/UploadFile.tsx @@ -3,7 +3,7 @@ import { Box, ButtonBase, Skeleton, Typography, useTheme } from "@mui/material"; import { enqueueSnackbar } from "notistack"; import { sendAnswer, sendFile } from "@api/quizRelase"; -import { useQuizData } from "@contexts/QuizDataContext"; +import { useQuizSettings } from "@contexts/QuizDataContext"; import { useRootContainerSize } from "@contexts/RootContainerWidthContext"; import { useQuizViewStore } from "@stores/quizView"; @@ -26,32 +26,24 @@ type UploadFileProps = { setIsSending: (isSending: boolean) => void; }; -export const UploadFile = ({ - currentQuestion, - setModalWarningType, - isSending, - setIsSending, -}: UploadFileProps) => { - const { quizId, preview } = useQuizData(); - const [isDropzoneHighlighted, setIsDropzoneHighlighted] = - useState(false); +export const UploadFile = ({ currentQuestion, setModalWarningType, isSending, setIsSending }: UploadFileProps) => { + const { quizId, preview } = useQuizSettings(); + const [isDropzoneHighlighted, setIsDropzoneHighlighted] = useState(false); const theme = useTheme(); const answers = useQuizViewStore((state) => state.answers); const { updateAnswer } = useQuizViewStore((state) => state); const isMobile = useRootContainerSize() < 500; - const answer = answers.find( - ({ questionId }) => questionId === currentQuestion.id - )?.answer as string; + const answer = answers.find(({ questionId }) => questionId === currentQuestion.id)?.answer as string; const uploadFile = async (file: File | undefined) => { if (isSending) return; if (!file) return; if (file.size > MAX_FILE_SIZE) return setModalWarningType("errorSize"); - const isFileTypeAccepted = ACCEPT_SEND_FILE_TYPES_MAP[ - currentQuestion.content.type - ].some((fileType) => file.name.toLowerCase().endsWith(fileType)); + const isFileTypeAccepted = ACCEPT_SEND_FILE_TYPES_MAP[currentQuestion.content.type].some((fileType) => + file.name.toLowerCase().endsWith(fileType) + ); if (!isFileTypeAccepted) return setModalWarningType("errorType"); @@ -66,21 +58,15 @@ export const UploadFile = ({ }, qid: quizId, }); - + console.log(data); await sendAnswer({ questionId: currentQuestion.id, - body: `https://storage.yandexcloud.net/squizanswer/${quizId}/${ - currentQuestion.id - }/${data!.data.fileIDMap[currentQuestion.id]}`, + body: `${data!.data.fileIDMap[currentQuestion.id]}`, qid: quizId, preview, }); - updateAnswer( - currentQuestion.id, - `${file.name}|${URL.createObjectURL(file)}`, - 0 - ); + updateAnswer(currentQuestion.id, `${file.name}|${URL.createObjectURL(file)}`, 0); } catch (error) { console.error(error); enqueueSnackbar("ответ не был засчитан"); @@ -101,28 +87,18 @@ export const UploadFile = ({ return ( {isSending ? ( - + ) : ( - + uploadFile(target.files?.[0])} hidden - accept={ACCEPT_SEND_FILE_TYPES_MAP[ - currentQuestion.content.type - ].join(",")} + accept={ACCEPT_SEND_FILE_TYPES_MAP[currentQuestion.content.type].join(",")} multiple type="file" /> - !answer?.split("|")[0] && setIsDropzoneHighlighted(true) - } + onDragEnter={() => !answer?.split("|")[0] && setIsDropzoneHighlighted(true)} onDragLeave={() => setIsDropzoneHighlighted(false)} onDragOver={(event) => event.preventDefault()} onDrop={onDrop} @@ -142,10 +118,7 @@ export const UploadFile = ({ - { - UPLOAD_FILE_DESCRIPTIONS_MAP[currentQuestion.content.type] - .title - } + {UPLOAD_FILE_DESCRIPTIONS_MAP[currentQuestion.content.type].title} - { - UPLOAD_FILE_DESCRIPTIONS_MAP[currentQuestion.content.type] - .description - } + {UPLOAD_FILE_DESCRIPTIONS_MAP[currentQuestion.content.type].description} diff --git a/lib/components/ViewPublicationPage/questions/File/UploadedFile.tsx b/lib/components/ViewPublicationPage/questions/File/UploadedFile.tsx index f887b98..a14b5f5 100644 --- a/lib/components/ViewPublicationPage/questions/File/UploadedFile.tsx +++ b/lib/components/ViewPublicationPage/questions/File/UploadedFile.tsx @@ -1,7 +1,7 @@ import { Box, IconButton, Typography, useTheme } from "@mui/material"; import { sendAnswer } from "@api/quizRelase"; -import { useQuizData } from "@contexts/QuizDataContext"; +import { useQuizSettings } from "@contexts/QuizDataContext"; import { useQuizViewStore } from "@stores/quizView"; import CloseBold from "@icons/CloseBold"; @@ -13,18 +13,13 @@ type UploadedFileProps = { setIsSending: (isSending: boolean) => void; }; -export const UploadedFile = ({ - currentQuestion, - setIsSending, -}: UploadedFileProps) => { - const { quizId, preview } = useQuizData(); +export const UploadedFile = ({ currentQuestion, setIsSending }: UploadedFileProps) => { + const { quizId, preview } = useQuizSettings(); const answers = useQuizViewStore((state) => state.answers); const { updateAnswer } = useQuizViewStore((state) => state); const theme = useTheme(); - const answer = answers.find( - ({ questionId }) => questionId === currentQuestion.id - )?.answer as string; + const answer = answers.find(({ questionId }) => questionId === currentQuestion.id)?.answer as string; const deleteFile = async () => { if (answer.length > 0) { diff --git a/lib/components/ViewPublicationPage/questions/File/index.tsx b/lib/components/ViewPublicationPage/questions/File/index.tsx index 34cfb15..b1ad427 100644 --- a/lib/components/ViewPublicationPage/questions/File/index.tsx +++ b/lib/components/ViewPublicationPage/questions/File/index.tsx @@ -11,14 +11,7 @@ import { ACCEPT_SEND_FILE_TYPES_MAP } from "@/components/ViewPublicationPage/too import type { QuizQuestionFile } from "@model/questionTypes/file"; -export type ModalWarningType = - | "errorType" - | "errorSize" - | "picture" - | "video" - | "audio" - | "document" - | null; +export type ModalWarningType = "errorType" | "errorSize" | "picture" | "video" | "audio" | "document" | null; type FileProps = { currentQuestion: QuizQuestionFile; @@ -27,22 +20,15 @@ type FileProps = { export const File = ({ currentQuestion }: FileProps) => { const theme = useTheme(); const answers = useQuizViewStore((state) => state.answers); - const [modalWarningType, setModalWarningType] = - useState(null); + const [modalWarningType, setModalWarningType] = useState(null); const [isSending, setIsSending] = useState(false); const isMobile = useRootContainerSize() < 500; - const answer = answers.find( - ({ questionId }) => questionId === currentQuestion.id - )?.answer as string; + const answer = answers.find(({ questionId }) => questionId === currentQuestion.id)?.answer as string; return ( - + {currentQuestion.title} { }} > {answer?.split("|")[0] ? ( - + ) : ( { /> )} {answer && currentQuestion.content.type === "picture" && ( - + )} {answer && currentQuestion.content.type === "video" && ( - setModalWarningType(null)} - > + setModalWarningType(null)}> { case "errorType": return Выбран некорректный тип файла; case "errorSize": - return ( - Файл слишком большой. Максимальный размер 50 МБ - ); + return Файл слишком большой. Максимальный размер 50 МБ; default: return ( <> Допустимые расширения файлов: - - {ACCEPT_SEND_FILE_TYPES_MAP[status].join(" ")} - + {ACCEPT_SEND_FILE_TYPES_MAP[status].join(" ")} ); } diff --git a/lib/components/ViewPublicationPage/questions/Images/ImageVariant.tsx b/lib/components/ViewPublicationPage/questions/Images/ImageVariant.tsx index e30fc1a..1077def 100644 --- a/lib/components/ViewPublicationPage/questions/Images/ImageVariant.tsx +++ b/lib/components/ViewPublicationPage/questions/Images/ImageVariant.tsx @@ -1,78 +1,33 @@ +import type { QuestionVariant } from "@/model/questionTypes/shared"; +import { useQuizSettings } from "@contexts/QuizDataContext"; import { Box, FormControlLabel, Radio, useTheme } from "@mui/material"; -import { enqueueSnackbar } from "notistack"; - -import { sendAnswer } from "@api/quizRelase"; import { useQuizViewStore } from "@stores/quizView"; -import { useQuizData } from "@contexts/QuizDataContext"; -import { quizThemes } from "@utils/themes/Publication/themePublication"; - import RadioCheck from "@ui_kit/RadioCheck"; import RadioIcon from "@ui_kit/RadioIcon"; - +import { quizThemes } from "@utils/themes/Publication/themePublication"; import type { MouseEvent } from "react"; -import type { QuestionVariant } from "@/model/questionTypes/shared"; -import type { QuizQuestionImages } from "@model/questionTypes/images"; type ImagesProps = { - currentQuestion: QuizQuestionImages; + questionId: string; variant: QuestionVariant; - isSending: boolean; - setIsSending: (isSending: boolean) => void; index: number; }; -export const ImageVariant = ({ - currentQuestion, - variant, - isSending, - setIsSending, - index, -}: ImagesProps) => { - const { quizId, preview } = useQuizData(); - const { settings } = useQuizData(); +export const ImageVariant = ({ questionId, variant, index }: ImagesProps) => { + const { settings } = useQuizSettings(); const answers = useQuizViewStore((state) => state.answers); const { deleteAnswer, updateAnswer } = useQuizViewStore((state) => state); const theme = useTheme(); - const answer = answers.find( - ({ questionId }) => questionId === currentQuestion.id - )?.answer; + const answer = answers.find((answer) => answer.questionId === questionId)?.answer; const onVariantClick = async (event: MouseEvent) => { event.preventDefault(); - if (isSending) return; - setIsSending(true); - try { - await sendAnswer({ - questionId: currentQuestion.id, - body: `${currentQuestion.content.variants[index].answer} `, - qid: quizId, - preview, - }); - updateAnswer( - currentQuestion.id, - currentQuestion.content.variants[index].id, - currentQuestion.content.variants[index].points || 0 - ); - } catch (error) { - enqueueSnackbar("ответ не был засчитан"); + updateAnswer(questionId, variant.id, variant.points || 0); + + if (answer === variant.id) { + deleteAnswer(questionId); } - - if (answer === currentQuestion.content.variants[index].id) { - deleteAnswer(currentQuestion.id); - try { - await sendAnswer({ - questionId: currentQuestion.id, - body: "", - qid: quizId, - preview, - }); - } catch (error) { - enqueueSnackbar("ответ не был засчитан"); - } - } - - setIsSending(false); }; return ( @@ -81,16 +36,14 @@ export const ImageVariant = ({ cursor: "pointer", borderRadius: "12px", border: `1px solid`, - borderColor: - answer === variant.id ? theme.palette.primary.main : "#9A9AAF", + borderColor: answer === variant.id ? theme.palette.primary.main : "#9A9AAF", "&:hover": { borderColor: theme.palette.primary.main }, background: settings.cfg.design && !quizThemes[settings.cfg.theme].isLight ? "rgba(255,255,255, 0.3)" - : (settings.cfg.design && quizThemes[settings.cfg.theme].isLight) || - quizThemes[settings.cfg.theme].isLight - ? "#FFFFFF" - : "transparent", + : (settings.cfg.design && quizThemes[settings.cfg.theme].isLight) || quizThemes[settings.cfg.theme].isLight + ? "#FFFFFF" + : "transparent", }} onClick={onVariantClick} > diff --git a/lib/components/ViewPublicationPage/questions/Images/index.tsx b/lib/components/ViewPublicationPage/questions/Images/index.tsx index 5ecefd9..5387b01 100644 --- a/lib/components/ViewPublicationPage/questions/Images/index.tsx +++ b/lib/components/ViewPublicationPage/questions/Images/index.tsx @@ -1,24 +1,17 @@ -import { useState } from "react"; -import { Box, RadioGroup, Typography, useTheme } from "@mui/material"; - -import { ImageVariant } from "./ImageVariant"; - -import { useQuizViewStore } from "@stores/quizView"; import { useRootContainerSize } from "@contexts/RootContainerWidthContext"; - import type { QuizQuestionImages } from "@model/questionTypes/images"; +import { Box, RadioGroup, Typography, useTheme } from "@mui/material"; +import { useQuizViewStore } from "@stores/quizView"; +import { ImageVariant } from "./ImageVariant"; type ImagesProps = { currentQuestion: QuizQuestionImages; }; export const Images = ({ currentQuestion }: ImagesProps) => { - const [isSending, setIsSending] = useState(false); const answers = useQuizViewStore((state) => state.answers); const theme = useTheme(); - const answer = answers.find( - ({ questionId }) => questionId === currentQuestion.id - )?.answer; + const answer = answers.find(({ questionId }) => questionId === currentQuestion.id)?.answer; const isTablet = useRootContainerSize() < 1000; const isMobile = useRootContainerSize() < 500; @@ -33,9 +26,7 @@ export const Images = ({ currentQuestion }: ImagesProps) => { answer === id - )} + value={currentQuestion.content.variants.findIndex(({ id }) => answer === id)} sx={{ display: "flex", flexWrap: "wrap", @@ -48,21 +39,15 @@ export const Images = ({ currentQuestion }: ImagesProps) => { sx={{ display: "grid", gap: "15px", - gridTemplateColumns: isTablet - ? isMobile - ? "repeat(1, 1fr)" - : "repeat(2, 1fr)" - : "repeat(3, 1fr)", + gridTemplateColumns: isTablet ? (isMobile ? "repeat(1, 1fr)" : "repeat(2, 1fr)") : "repeat(3, 1fr)", width: "100%", }} > {currentQuestion.content.variants.map((variant, index) => ( ))} diff --git a/lib/components/ViewPublicationPage/questions/Number/index.tsx b/lib/components/ViewPublicationPage/questions/Number/index.tsx index f1484ee..fc403c5 100644 --- a/lib/components/ViewPublicationPage/questions/Number/index.tsx +++ b/lib/components/ViewPublicationPage/questions/Number/index.tsx @@ -1,78 +1,49 @@ +import { useQuizSettings } from "@contexts/QuizDataContext"; +import type { QuizQuestionNumber } from "@model/questionTypes/number"; import { Box, Typography, useTheme } from "@mui/material"; -import { useEffect, useState } from "react"; -import { useDebouncedCallback } from "use-debounce"; - +import { useQuizViewStore } from "@stores/quizView"; import { CustomSlider } from "@ui_kit/CustomSlider"; import CustomTextField from "@ui_kit/CustomTextField"; - -import { useQuizViewStore } from "@stores/quizView"; - -import { sendAnswer } from "@api/quizRelase"; -import { enqueueSnackbar } from "notistack"; -import type { QuizQuestionNumber } from "@model/questionTypes/number"; - -import { useQuizData } from "@contexts/QuizDataContext"; import { quizThemes } from "@utils/themes/Publication/themePublication"; - import type { ChangeEvent, SyntheticEvent } from "react"; +import { useEffect, useState } from "react"; +import { useDebouncedCallback } from "use-debounce"; type NumberProps = { currentQuestion: QuizQuestionNumber; }; export const Number = ({ currentQuestion }: NumberProps) => { - const [isSending, setIsSending] = useState(false); const [inputValue, setInputValue] = useState("0"); const [minRange, setMinRange] = useState("0"); const [maxRange, setMaxRange] = useState("100000000000"); const [reversedInputValue, setReversedInputValue] = useState("0"); const [reversedMinRange, setReversedMinRange] = useState("0"); - const [reversedMaxRange, setReversedMaxRange] = - useState("100000000000"); - const { settings, quizId, preview } = useQuizData(); + const [reversedMaxRange, setReversedMaxRange] = useState("100000000000"); + const { settings } = useQuizSettings(); const { updateAnswer } = useQuizViewStore((state) => state); const answers = useQuizViewStore((state) => state.answers); const theme = useTheme(); - const [minBorder, maxBorder] = currentQuestion.content.range - .split("—") - .map(window.Number); + const [minBorder, maxBorder] = currentQuestion.content.range.split("—").map(window.Number); const min = minBorder < maxBorder ? minBorder : maxBorder; const max = minBorder < maxBorder ? maxBorder : minBorder; const reversed = minBorder > maxBorder; - const answer = answers.find( - ({ questionId }) => questionId === currentQuestion.id - )?.answer as string; + const answer = answers.find(({ questionId }) => questionId === currentQuestion.id)?.answer as string; const sliderValue = answer || - (reversed - ? max + min - currentQuestion.content.start + "—" + max - : currentQuestion.content.start + "—" + max); + (reversed ? max + min - currentQuestion.content.start + "—" + max : currentQuestion.content.start + "—" + max); useEffect(() => { console.log("reversed:", reversed); }, [reversed]); const sendAnswerToBackend = async (value: string, noUpdate = false) => { - setIsSending(true); - try { - await sendAnswer({ - questionId: currentQuestion.id, - body: value, - qid: quizId, - preview, - }); - - if (!noUpdate) { - updateAnswer(currentQuestion.id, value, 0); - } - } catch (error) { - enqueueSnackbar("ответ не был засчитан"); + if (!noUpdate) { + updateAnswer(currentQuestion.id, value, 0); } - - setIsSending(false); }; const updateValueDebounced = useDebouncedCallback(async (value: string) => { @@ -81,15 +52,11 @@ export const Number = ({ currentQuestion }: NumberProps) => { window.Number(value) < window.Number(min) ? String(min) : window.Number(value) > window.Number(max) - ? String(max) - : value; + ? String(max) + : value; setReversedInputValue(newValue); - updateAnswer( - currentQuestion.id, - String(max + min - window.Number(newValue)), - 0 - ); + updateAnswer(currentQuestion.id, String(max + min - window.Number(newValue)), 0); await sendAnswerToBackend(String(window.Number(newValue)), true); return; @@ -99,107 +66,73 @@ export const Number = ({ currentQuestion }: NumberProps) => { window.Number(value) < window.Number(minRange) ? minRange : window.Number(value) > window.Number(maxRange) - ? maxRange - : value; + ? maxRange + : value; setInputValue(newValue); await sendAnswerToBackend(newValue); }, 1000); - const updateMinRangeDebounced = useDebouncedCallback( - async (value: string, crowded = false) => { - if (reversed) { - const newMinRange = crowded - ? window.Number(value.split("—")[1]) - : max + min - window.Number(value.split("—")[0]) < min + const updateMinRangeDebounced = useDebouncedCallback(async (value: string, crowded = false) => { + if (reversed) { + const newMinRange = crowded + ? window.Number(value.split("—")[1]) + : max + min - window.Number(value.split("—")[0]) < min ? min : max + min - window.Number(value.split("—")[0]); - const newMinValue = - window.Number(value.split("—")[0]) > max - ? String(max) - : value.split("—")[0]; + const newMinValue = window.Number(value.split("—")[0]) > max ? String(max) : value.split("—")[0]; - setReversedMinRange( - crowded ? String(max + min - window.Number(newMinValue)) : newMinValue - ); - updateAnswer( - currentQuestion.id, - `${newMinRange}—${value.split("—")[1]}`, - 0 - ); - await sendAnswerToBackend( - `${newMinValue}—${value.split("—")[1]}`, - true - ); + setReversedMinRange(crowded ? String(max + min - window.Number(newMinValue)) : newMinValue); + updateAnswer(currentQuestion.id, `${newMinRange}—${value.split("—")[1]}`, 0); + await sendAnswerToBackend(`${newMinValue}—${value.split("—")[1]}`, true); - return; - } + return; + } - const newMinValue = crowded - ? maxRange - : window.Number(value.split("—")[0]) < min + const newMinValue = crowded + ? maxRange + : window.Number(value.split("—")[0]) < min ? String(min) : value.split("—")[0]; - setMinRange(newMinValue); - await sendAnswerToBackend(`${newMinValue}—${value.split("—")[1]}`); - }, - 1000 - ); - - const updateMaxRangeDebounced = useDebouncedCallback( - async (value: string, crowded = false) => { - if (reversed) { - const newMaxRange = crowded - ? window.Number(value.split("—")[1]) - : max + min - window.Number(value.split("—")[1]) > max + setMinRange(newMinValue); + await sendAnswerToBackend(`${newMinValue}—${value.split("—")[1]}`); + }, 1000); + + const updateMaxRangeDebounced = useDebouncedCallback(async (value: string, crowded = false) => { + if (reversed) { + const newMaxRange = crowded + ? window.Number(value.split("—")[1]) + : max + min - window.Number(value.split("—")[1]) > max ? max : max + min - window.Number(value.split("—")[1]); - const newMaxValue = - window.Number(value.split("—")[1]) < min - ? String(min) - : value.split("—")[1]; + const newMaxValue = window.Number(value.split("—")[1]) < min ? String(min) : value.split("—")[1]; - setReversedMaxRange( - crowded ? String(max + min - window.Number(newMaxValue)) : newMaxValue - ); - updateAnswer( - currentQuestion.id, - `${value.split("—")[0]}—${newMaxRange}`, - 0 - ); - await sendAnswerToBackend( - `${value.split("—")[0]}—${newMaxValue}`, - true - ); + setReversedMaxRange(crowded ? String(max + min - window.Number(newMaxValue)) : newMaxValue); + updateAnswer(currentQuestion.id, `${value.split("—")[0]}—${newMaxRange}`, 0); + await sendAnswerToBackend(`${value.split("—")[0]}—${newMaxValue}`, true); - return; - } + return; + } - const newMaxValue = crowded - ? minRange - : window.Number(value.split("—")[1]) > max + const newMaxValue = crowded + ? minRange + : window.Number(value.split("—")[1]) > max ? String(max) : value.split("—")[1]; - setMaxRange(newMaxValue); - await sendAnswerToBackend(`${value.split("—")[0]}—${newMaxValue}`); - }, - 1000 - ); + setMaxRange(newMaxValue); + await sendAnswerToBackend(`${value.split("—")[0]}—${newMaxValue}`); + }, 1000); useEffect(() => { if (answer) { if (answer.includes("—")) { if (reversed) { - setReversedMinRange( - String(max + min - window.Number(answer.split("—")[0])) - ); - setReversedMaxRange( - String(max + min - window.Number(answer.split("—")[1])) - ); + setReversedMinRange(String(max + min - window.Number(answer.split("—")[0]))); + setReversedMaxRange(String(max + min - window.Number(answer.split("—")[1]))); } else { setMinRange(answer.split("—")[0]); setMaxRange(answer.split("—")[1]); @@ -225,20 +158,16 @@ export const Number = ({ currentQuestion }: NumberProps) => { setReversedInputValue(String(currentQuestion.content.start)); setInputValue(String(currentQuestion.content.start)); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const onSliderChange = (_: Event, value: number | number[]) => { - const range = Array.isArray(value) - ? `${value[0]}—${value[1]}` - : String(value); + const range = Array.isArray(value) ? `${value[0]}—${value[1]}` : String(value); updateAnswer(currentQuestion.id, range, 0); }; - const onChangeCommitted = async ( - _: Event | SyntheticEvent, - value: number | number[] - ) => { + const onChangeCommitted = async (_: Event | SyntheticEvent, value: number | number[]) => { if (currentQuestion.content.chooseRange && Array.isArray(value)) { if (reversed) { const newMinReversedValue = String(max + min - value[0]); @@ -248,10 +177,7 @@ export const Number = ({ currentQuestion }: NumberProps) => { setMaxRange(String(value[1])); setReversedMinRange(newMinReversedValue); setReversedMaxRange(newMaxReversedValue); - await sendAnswerToBackend( - `${newMinReversedValue}—${newMaxReversedValue}`, - true - ); + await sendAnswerToBackend(`${newMinReversedValue}—${newMaxReversedValue}`, true); return; } @@ -277,9 +203,7 @@ export const Number = ({ currentQuestion }: NumberProps) => { return value; } - const [minSliderBorder, maxSliderBorder] = sliderValue - .split("—") - .map(window.Number); + const [minSliderBorder, maxSliderBorder] = sliderValue.split("—").map(window.Number); if (value === minSliderBorder) { return max + min - minSliderBorder; @@ -313,9 +237,7 @@ export const Number = ({ currentQuestion }: NumberProps) => { return; } - updateMinRangeDebounced( - `${newValue}—${max + min - window.Number(reversedMaxRange)}` - ); + updateMinRangeDebounced(`${newValue}—${max + min - window.Number(reversedMaxRange)}`); return; } @@ -344,9 +266,7 @@ export const Number = ({ currentQuestion }: NumberProps) => { return; } - updateMaxRangeDebounced( - `${max + min - window.Number(reversedMinRange)}—${newValue}` - ); + updateMaxRangeDebounced(`${max + min - window.Number(reversedMinRange)}—${newValue}`); return; } @@ -417,9 +337,7 @@ export const Number = ({ currentQuestion }: NumberProps) => { "& .MuiOutlinedInput-root": { background: "transparent" }, "& .MuiInputBase-input": { textAlign: "center", zIndex: 1 }, "& .MuiOutlinedInput-notchedOutline": { - backgroundColor: quizThemes[settings.cfg.theme].isLight - ? "white" - : theme.palette.background.default, + backgroundColor: quizThemes[settings.cfg.theme].isLight ? "white" : theme.palette.background.default, borderColor: "#9A9AAF", }, }} @@ -445,9 +363,7 @@ export const Number = ({ currentQuestion }: NumberProps) => { "& .MuiOutlinedInput-root": { background: "transparent" }, "& .MuiInputBase-input": { textAlign: "center", zIndex: 1 }, "& .MuiOutlinedInput-notchedOutline": { - backgroundColor: quizThemes[settings.cfg.theme].isLight - ? "white" - : theme.palette.background.default, + backgroundColor: quizThemes[settings.cfg.theme].isLight ? "white" : theme.palette.background.default, borderColor: "#9A9AAF", }, }} @@ -462,9 +378,7 @@ export const Number = ({ currentQuestion }: NumberProps) => { "& .MuiOutlinedInput-root": { background: "transparent" }, "& .MuiInputBase-input": { textAlign: "center", zIndex: 1 }, "& .MuiOutlinedInput-notchedOutline": { - backgroundColor: quizThemes[settings.cfg.theme].isLight - ? "white" - : theme.palette.background.default, + backgroundColor: quizThemes[settings.cfg.theme].isLight ? "white" : theme.palette.background.default, borderColor: "#9A9AAF", }, }} diff --git a/lib/components/ViewPublicationPage/questions/Page/index.tsx b/lib/components/ViewPublicationPage/questions/Page/index.tsx index b27191e..333ee41 100644 --- a/lib/components/ViewPublicationPage/questions/Page/index.tsx +++ b/lib/components/ViewPublicationPage/questions/Page/index.tsx @@ -1,8 +1,6 @@ import { Box, Typography, useTheme } from "@mui/material"; - -import YoutubeEmbedIframe from "@/components/ViewPublicationPage/tools/YoutubeEmbedIframe"; - import type { QuizQuestionPage } from "@model/questionTypes/page"; +import QuizVideo from "@/ui_kit/VideoIframe/VideoIframe"; type PageProps = { currentQuestion: QuizQuestionPage; @@ -23,10 +21,7 @@ export const Page = ({ currentQuestion }: PageProps) => { > {currentQuestion.title} - + {currentQuestion.content.text} { }} > {currentQuestion.content.useImage ? ( - - - + > + + + ) ) : ( - diff --git a/lib/components/ViewPublicationPage/questions/Rating/index.tsx b/lib/components/ViewPublicationPage/questions/Rating/index.tsx index 7d12a80..2018bb7 100644 --- a/lib/components/ViewPublicationPage/questions/Rating/index.tsx +++ b/lib/components/ViewPublicationPage/questions/Rating/index.tsx @@ -1,17 +1,4 @@ -import { useState } from "react"; -import { - Box, - Rating as RatingComponent, - Typography, - useTheme, -} from "@mui/material"; -import { enqueueSnackbar } from "notistack"; - -import { sendAnswer } from "@api/quizRelase"; -import { useQuizViewStore } from "@stores/quizView"; import { useRootContainerSize } from "@contexts/RootContainerWidthContext"; -import { useQuizData } from "@contexts/QuizDataContext"; - import FlagIcon from "@icons/questionsPage/FlagIcon"; import StarIconMini from "@icons/questionsPage/StarIconMini"; import HashtagIcon from "@icons/questionsPage/hashtagIcon"; @@ -19,50 +6,72 @@ import HeartIcon from "@icons/questionsPage/heartIcon"; import LightbulbIcon from "@icons/questionsPage/lightbulbIcon"; import LikeIcon from "@icons/questionsPage/likeIcon"; import TropfyIcon from "@icons/questionsPage/tropfyIcon"; - import type { QuizQuestionRating } from "@model/questionTypes/rating"; +import { Box, Rating as RatingComponent, Typography, useTheme } from "@mui/material"; +import { useQuizViewStore } from "@stores/quizView"; const RATING_FORM_BUTTONS = [ { name: "star", icon: (color: string, width: number) => ( - + ), }, { name: "trophie", icon: (color: string, width: number) => ( - + ), }, { name: "flag", icon: (color: string, width: number) => ( - + ), }, { name: "heart", icon: (color: string, width: number) => ( - + ), }, { name: "like", icon: (color: string, width: number) => ( - + ), }, { name: "bubble", icon: (color: string, width: number) => ( - + ), }, { name: "hashtag", icon: (color: string, width: number) => ( - + ), }, ]; @@ -72,36 +81,17 @@ type RatingProps = { }; export const Rating = ({ currentQuestion }: RatingProps) => { - const [isSending, setIsSending] = useState(false); - const { quizId, preview } = useQuizData(); const { updateAnswer } = useQuizViewStore((state) => state); const answers = useQuizViewStore((state) => state.answers); const theme = useTheme(); const isMobile = useRootContainerSize() < 650; const isTablet = useRootContainerSize() < 750; - const { answer } = - answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {}; - const form = RATING_FORM_BUTTONS.find( - ({ name }) => name === currentQuestion.content.form - ); + + const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {}; + const form = RATING_FORM_BUTTONS.find(({ name }) => name === currentQuestion.content.form); const sendRating = async (value: number | null) => { - setIsSending(true); - - try { - await sendAnswer({ - questionId: currentQuestion.id, - body: String(value) + " из " + currentQuestion.content.steps, - qid: quizId, - preview, - }); - - updateAnswer(currentQuestion.id, String(value), 0); - } catch (error) { - enqueueSnackbar("ответ не был засчитан"); - } - - setIsSending(false); + updateAnswer(currentQuestion.id, String(value), 0); }; return ( @@ -124,7 +114,6 @@ export const Rating = ({ currentQuestion }: RatingProps) => { > sendRating(value)} sx={{ @@ -134,14 +123,8 @@ export const Rating = ({ currentQuestion }: RatingProps) => { "& .MuiRating-icon": { mr: isMobile ? undefined : "15px" }, }} max={currentQuestion.content.steps} - icon={form?.icon( - theme.palette.primary.main, - isMobile ? 30 : isTablet ? 40 : 50 - )} - emptyIcon={form?.icon( - "#9A9AAF", - isMobile ? 30 : isTablet ? 40 : 50 - )} + icon={form?.icon(theme.palette.primary.main, isMobile ? 30 : isTablet ? 40 : 50)} + emptyIcon={form?.icon("#9A9AAF", isMobile ? 30 : isTablet ? 40 : 50)} /> { width: "100%", }} > - - {currentQuestion.content.ratingNegativeDescription} - - - {currentQuestion.content.ratingPositiveDescription} - + {currentQuestion.content.ratingNegativeDescription} + {currentQuestion.content.ratingPositiveDescription} diff --git a/lib/components/ViewPublicationPage/questions/Select/index.tsx b/lib/components/ViewPublicationPage/questions/Select/index.tsx index 44c51c0..e624e7a 100644 --- a/lib/components/ViewPublicationPage/questions/Select/index.tsx +++ b/lib/components/ViewPublicationPage/questions/Select/index.tsx @@ -1,64 +1,29 @@ -import { useState } from "react"; -import { Box, Typography, useTheme } from "@mui/material"; -import { enqueueSnackbar } from "notistack"; - import { Select as SelectComponent } from "@/components/ViewPublicationPage/tools/Select"; - -import { sendAnswer } from "@api/quizRelase"; -import { useQuizViewStore } from "@stores/quizView"; -import { useQuizData } from "@contexts/QuizDataContext"; - -import { quizThemes } from "@utils/themes/Publication/themePublication"; - +import { useQuizSettings } from "@contexts/QuizDataContext"; import type { QuizQuestionSelect } from "@model/questionTypes/select"; +import { Box, Typography, useTheme } from "@mui/material"; +import { useQuizViewStore } from "@stores/quizView"; +import { quizThemes } from "@utils/themes/Publication/themePublication"; type SelectProps = { currentQuestion: QuizQuestionSelect; }; export const Select = ({ currentQuestion }: SelectProps) => { - const [isSending, setIsSending] = useState(false); - const { quizId, settings, preview } = useQuizData(); + const { settings } = useQuizSettings(); const { updateAnswer, deleteAnswer } = useQuizViewStore((state) => state); const answers = useQuizViewStore((state) => state.answers); const theme = useTheme(); - const { answer } = - answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {}; + const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {}; const sendSelectedAnswer = async (value: number) => { - setIsSending(true); - if (value < 0) { deleteAnswer(currentQuestion.id); - try { - await sendAnswer({ - questionId: currentQuestion.id, - body: "", - qid: quizId, - preview, - }); - } catch (error) { - enqueueSnackbar("ответ не был засчитан"); - } - - return setIsSending(false); + return; } - try { - await sendAnswer({ - questionId: currentQuestion.id, - body: String(currentQuestion.content.variants[Number(value)].answer), - qid: quizId, - preview, - }); - - updateAnswer(currentQuestion.id, String(value), 0); - } catch (error) { - enqueueSnackbar("ответ не был засчитан"); - } - - setIsSending(false); + updateAnswer(currentQuestion.id, String(value), 0); }; return ( @@ -79,7 +44,6 @@ export const Select = ({ currentQuestion }: SelectProps) => { }} > answer)} diff --git a/lib/components/ViewPublicationPage/questions/Text/TextNormal.tsx b/lib/components/ViewPublicationPage/questions/Text/TextNormal.tsx index 72c6947..3090c9a 100644 --- a/lib/components/ViewPublicationPage/questions/Text/TextNormal.tsx +++ b/lib/components/ViewPublicationPage/questions/Text/TextNormal.tsx @@ -3,7 +3,7 @@ import { Box, Typography, useTheme } from "@mui/material"; import CustomTextField from "@ui_kit/CustomTextField"; import { Answer, useQuizViewStore } from "@stores/quizView"; -import { useQuizData } from "@contexts/QuizDataContext"; +import { useQuizSettings } from "@contexts/QuizDataContext"; import { useRootContainerSize } from "@contexts/RootContainerWidthContext"; import { quizThemes } from "@utils/themes/Publication/themePublication"; @@ -14,23 +14,17 @@ import type { QuizQuestionText } from "@model/questionTypes/text"; interface TextNormalProps { currentQuestion: QuizQuestionText; answer?: Answer; - inputHC: (text: string) => void; stepNumber?: number | null; } -export const TextNormal = ({ - currentQuestion, - answer, - inputHC, -}: TextNormalProps) => { - const { settings } = useQuizData(); +export const TextNormal = ({ currentQuestion, answer }: TextNormalProps) => { + const { settings } = useQuizSettings(); const { updateAnswer } = useQuizViewStore((state) => state); const isMobile = useRootContainerSize() < 650; const theme = useTheme(); const onInputChange = async ({ target }: ChangeEvent) => { updateAnswer(currentQuestion.id, target.value, 0); - inputHC(target.value); }; return ( @@ -67,24 +61,23 @@ export const TextNormal = ({ "&:focus-visible": { borderColor: theme.palette.primary.main }, }} /> - {currentQuestion.content.back && - currentQuestion.content.back !== " " && ( - - - - )} + {currentQuestion.content.back && currentQuestion.content.back !== " " && ( + + + + )} ); diff --git a/lib/components/ViewPublicationPage/questions/Text/TextSpecial.tsx b/lib/components/ViewPublicationPage/questions/Text/TextSpecial.tsx index e228bb4..fd76b9b 100644 --- a/lib/components/ViewPublicationPage/questions/Text/TextSpecial.tsx +++ b/lib/components/ViewPublicationPage/questions/Text/TextSpecial.tsx @@ -1,13 +1,7 @@ -import { - Box, - TextField as MuiTextField, - TextFieldProps, - Typography, - useTheme, -} from "@mui/material"; +import { Box, TextField as MuiTextField, TextFieldProps, Typography, useTheme } from "@mui/material"; import { Answer, useQuizViewStore } from "@stores/quizView"; -import { useQuizData } from "@contexts/QuizDataContext"; +import { useQuizSettings } from "@contexts/QuizDataContext"; import { useRootContainerSize } from "@contexts/RootContainerWidthContext"; import { quizThemes } from "@utils/themes/Publication/themePublication"; @@ -47,17 +41,11 @@ const ORIENTATION = [ interface TextSpecialProps { currentQuestion: QuizQuestionText; answer?: Answer; - inputHC: (text: string) => void; stepNumber?: number | null; } -export const TextSpecial = ({ - currentQuestion, - answer, - inputHC, - stepNumber, -}: TextSpecialProps) => { - const { settings } = useQuizData(); +export const TextSpecial = ({ currentQuestion, answer, stepNumber }: TextSpecialProps) => { + const { settings } = useQuizSettings(); const { updateAnswer } = useQuizViewStore((state) => state); const isHorizontal = ORIENTATION[Number(stepNumber) - 1].horizontal; const theme = useTheme(); @@ -65,7 +53,6 @@ export const TextSpecial = ({ const onInputChange = async ({ target }: ChangeEvent) => { updateAnswer(currentQuestion.id, target.value, 0); - inputHC(target.value); }; return ( @@ -93,18 +80,16 @@ export const TextSpecial = ({ > {currentQuestion.title} - {isHorizontal && - currentQuestion.content.back && - currentQuestion.content.back !== " " && ( - - - - )} + {isHorizontal && currentQuestion.content.back && currentQuestion.content.back !== " " && ( + + + + )} { } - {!isHorizontal && - currentQuestion.content.back && - currentQuestion.content.back !== " " && ( - - - - )} + {!isHorizontal && currentQuestion.content.back && currentQuestion.content.back !== " " && ( + + + + )} ); }; diff --git a/lib/components/ViewPublicationPage/questions/Text/index.tsx b/lib/components/ViewPublicationPage/questions/Text/index.tsx index 47e16e3..208b8be 100644 --- a/lib/components/ViewPublicationPage/questions/Text/index.tsx +++ b/lib/components/ViewPublicationPage/questions/Text/index.tsx @@ -1,13 +1,7 @@ -import { useEffect, useState } from "react"; -import { useDebouncedCallback } from "use-debounce"; -import { enqueueSnackbar } from "notistack"; - -import { TextSpecial } from "./TextSpecial"; -import { TextNormal } from "./TextNormal"; - -import { sendAnswer } from "@api/quizRelase"; +import { useQuizSettings } from "@contexts/QuizDataContext"; import { useQuizViewStore } from "@stores/quizView"; -import { useQuizData } from "@contexts/QuizDataContext"; +import { TextNormal } from "./TextNormal"; +import { TextSpecial } from "./TextSpecial"; import type { QuizQuestionText } from "@model/questionTypes/text"; @@ -17,33 +11,9 @@ type TextProps = { }; export const Text = ({ currentQuestion, stepNumber }: TextProps) => { - const [isSending, setIsSending] = useState(false); - const { settings, preview } = useQuizData(); - const { quizId } = useQuizData(); + const { settings } = useQuizSettings(); const answers = useQuizViewStore((state) => state.answers); - const { answer } = - answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {}; - - const inputHC = useDebouncedCallback(async (text) => { - setIsSending(true); - - try { - await sendAnswer({ - questionId: currentQuestion.id, - body: text, - qid: quizId, - preview, - }); - } catch (error) { - enqueueSnackbar("ответ не был засчитан"); - } - - setIsSending(false); - }, 400); - - useEffect(() => { - inputHC.flush(); - }, [inputHC]); + const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {}; switch (settings.cfg.spec) { case true: @@ -51,7 +21,6 @@ export const Text = ({ currentQuestion, stepNumber }: TextProps) => { ); @@ -61,7 +30,6 @@ export const Text = ({ currentQuestion, stepNumber }: TextProps) => { ); @@ -70,7 +38,6 @@ export const Text = ({ currentQuestion, stepNumber }: TextProps) => { ); } diff --git a/lib/components/ViewPublicationPage/questions/Variant/VariantItem.tsx b/lib/components/ViewPublicationPage/questions/Variant/VariantItem.tsx index 9f48478..3835ed8 100644 --- a/lib/components/ViewPublicationPage/questions/Variant/VariantItem.tsx +++ b/lib/components/ViewPublicationPage/questions/Variant/VariantItem.tsx @@ -1,147 +1,75 @@ -import { - Checkbox, - FormControlLabel, - TextField as MuiTextField, - Radio, - TextFieldProps, - useTheme, -} from "@mui/material"; -import { enqueueSnackbar } from "notistack"; - -import { sendAnswer } from "@api/quizRelase"; -import { useQuizViewStore } from "@stores/quizView"; -import { useQuizData } from "@contexts/QuizDataContext"; - -import { quizThemes } from "@utils/themes/Publication/themePublication"; - +import { useQuizSettings } from "@contexts/QuizDataContext"; import { CheckboxIcon } from "@icons/Checkbox"; +import type { QuestionVariant } from "@model/questionTypes/shared"; +import { Checkbox, FormControlLabel, TextField as MuiTextField, Radio, TextFieldProps, useTheme } from "@mui/material"; +import { useQuizViewStore } from "@stores/quizView"; import RadioCheck from "@ui_kit/RadioCheck"; import RadioIcon from "@ui_kit/RadioIcon"; - +import { quizThemes } from "@utils/themes/Publication/themePublication"; import type { FC, MouseEvent } from "react"; -import type { QuestionVariant } from "@model/questionTypes/shared"; -import type { QuizQuestionVariant } from "@model/questionTypes/variant"; const TextField = MuiTextField as unknown as FC; export const VariantItem = ({ - currentQuestion, + questionId, + isMulti, variant, answer, index, own = false, - isSending, - setIsSending, }: { - currentQuestion: QuizQuestionVariant; + isMulti: boolean; + questionId: string; variant: QuestionVariant; answer: string | string[] | undefined; index: number; own?: boolean; - isSending: boolean; - setIsSending: (a: boolean) => void; }) => { - const { settings, quizId, preview } = useQuizData(); + const { settings } = useQuizSettings(); const theme = useTheme(); const { updateAnswer, deleteAnswer } = useQuizViewStore((state) => state); const sendVariant = async (event: MouseEvent) => { event.preventDefault(); - if (isSending) { - return; - } + const variantId = variant.id; - setIsSending(true); - - const variantId = currentQuestion.content.variants[index].id; - - if (currentQuestion.content.multi) { + if (isMulti) { const currentAnswer = typeof answer !== "string" ? answer || [] : []; - try { - await sendAnswer({ - questionId: currentQuestion.id, - body: currentAnswer.includes(variantId) - ? currentAnswer?.filter((item) => item !== variantId) - : [...currentAnswer, variantId], - qid: quizId, - preview, - }); - - updateAnswer( - currentQuestion.id, - currentAnswer.includes(variantId) - ? currentAnswer?.filter((item) => item !== variantId) - : [...currentAnswer, variantId], - currentQuestion.content.variants[index].points || 0 - ); - } catch (error) { - console.error(error); - enqueueSnackbar("ответ не был засчитан"); - } - - setIsSending(false); - return; - } - - try { - await sendAnswer({ - questionId: currentQuestion.id, - body: currentQuestion.content.variants[index].answer, - qid: quizId, - preview, - }); - - updateAnswer( - currentQuestion.id, - variantId, - answer === variantId - ? 0 - : currentQuestion.content.variants[index].points || 0 + return updateAnswer( + questionId, + currentAnswer.includes(variantId) + ? currentAnswer?.filter((item) => item !== variantId) + : [...currentAnswer, variantId], + variant.points || 0 ); - } catch (error) { - console.error(error); - enqueueSnackbar("ответ не был засчитан"); } + updateAnswer(questionId, variantId, answer === variantId ? 0 : variant.points || 0); + if (answer === variantId) { - try { - await sendAnswer({ - questionId: currentQuestion.id, - body: "", - qid: quizId, - preview, - }); - } catch (error) { - console.error(error); - enqueueSnackbar("ответ не был засчитан"); - } - deleteAnswer(currentQuestion.id); + deleteAnswer(questionId); } - - setIsSending(false); }; return ( + } icon={} /> diff --git a/lib/components/ViewPublicationPage/questions/Variant/index.tsx b/lib/components/ViewPublicationPage/questions/Variant/index.tsx index c4dfed2..68cd397 100644 --- a/lib/components/ViewPublicationPage/questions/Variant/index.tsx +++ b/lib/components/ViewPublicationPage/questions/Variant/index.tsx @@ -1,16 +1,10 @@ -import { useEffect, useState } from "react"; -import { - Box, - FormGroup, - RadioGroup, - Typography, - useTheme, -} from "@mui/material"; +import { Box, FormGroup, RadioGroup, Typography, useTheme } from "@mui/material"; +import { useEffect } from "react"; import { VariantItem } from "./VariantItem"; -import { useQuizViewStore } from "@stores/quizView"; import { useRootContainerSize } from "@contexts/RootContainerWidthContext"; +import { useQuizViewStore } from "@stores/quizView"; import type { QuizQuestionVariant } from "@model/questionTypes/variant"; import moment from "moment"; @@ -20,17 +14,14 @@ type VariantProps = { }; export const Variant = ({ currentQuestion }: VariantProps) => { - const [isSending, setIsSending] = useState(false); - const answers = useQuizViewStore((state) => state.answers); - const { ownVariants, updateOwnVariant } = useQuizViewStore((state) => state); const theme = useTheme(); const isMobile = useRootContainerSize() < 650; + const answers = useQuizViewStore((state) => state.answers); + const ownVariants = useQuizViewStore((state) => state.ownVariants); + const updateOwnVariant = useQuizViewStore((state) => state.updateOwnVariant); - const { answer } = - answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {}; - const ownVariant = ownVariants.find( - (variant) => variant.id === currentQuestion.id - ); + const answer = answers.find(({ questionId }) => questionId === currentQuestion.id)?.answer; + const ownVariant = ownVariants.find((variant) => variant.id === currentQuestion.id); const Group = currentQuestion.content.multi ? FormGroup : RadioGroup; @@ -38,6 +29,7 @@ export const Variant = ({ currentQuestion }: VariantProps) => { if (!ownVariant) { updateOwnVariant(currentQuestion.id, ""); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); if (moment.isMoment(answer)) throw new Error("Answer is Moment in Variant question"); @@ -61,9 +53,7 @@ export const Variant = ({ currentQuestion }: VariantProps) => { > answer === id - )} + value={currentQuestion.content.variants.findIndex(({ id }) => answer === id)} sx={{ display: "flex", flexWrap: "wrap", @@ -86,38 +76,35 @@ export const Variant = ({ currentQuestion }: VariantProps) => { {currentQuestion.content.variants.map((variant, index) => ( ))} {currentQuestion.content.own && ownVariant && ( )} - {currentQuestion.content.back && - currentQuestion.content.back !== " " && ( - - - - )} + {currentQuestion.content.back && currentQuestion.content.back !== " " && ( + + + + )} ); diff --git a/lib/components/ViewPublicationPage/questions/Varimg/VarimgVariant.tsx b/lib/components/ViewPublicationPage/questions/Varimg/VarimgVariant.tsx index c5ec48d..c50e1cf 100644 --- a/lib/components/ViewPublicationPage/questions/Varimg/VarimgVariant.tsx +++ b/lib/components/ViewPublicationPage/questions/Varimg/VarimgVariant.tsx @@ -1,82 +1,37 @@ +import type { QuestionVariant } from "@/model/questionTypes/shared"; +import { useQuizSettings } from "@contexts/QuizDataContext"; import { FormControlLabel, Radio, useTheme } from "@mui/material"; -import { enqueueSnackbar } from "notistack"; - import { useQuizViewStore } from "@stores/quizView"; - -import { sendAnswer } from "@api/quizRelase"; -import { useQuizData } from "@contexts/QuizDataContext"; - -import { quizThemes } from "@utils/themes/Publication/themePublication"; - import RadioCheck from "@ui_kit/RadioCheck"; import RadioIcon from "@ui_kit/RadioIcon"; - +import { quizThemes } from "@utils/themes/Publication/themePublication"; import type { MouseEvent } from "react"; -import type { QuestionVariant } from "@/model/questionTypes/shared"; -import type { QuizQuestionVarImg } from "@model/questionTypes/varimg"; type VarimgVariantProps = { - currentQuestion: QuizQuestionVarImg; + questionId: string; variant: QuestionVariant; index: number; isSending: boolean; setIsSending: (isSending: boolean) => void; }; -export const VarimgVariant = ({ - currentQuestion, - variant, - index, - isSending, - setIsSending, -}: VarimgVariantProps) => { - const { settings, quizId, preview } = useQuizData(); +export const VarimgVariant = ({ questionId, variant, index, isSending, setIsSending }: VarimgVariantProps) => { + const { settings } = useQuizSettings(); const { updateAnswer, deleteAnswer } = useQuizViewStore((state) => state); const answers = useQuizViewStore((state) => state.answers); const theme = useTheme(); - const { answer } = - answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {}; + const { answer } = answers.find((answer) => answer.questionId === questionId) ?? {}; const sendVariant = async (event: MouseEvent) => { event.preventDefault(); - setIsSending(true); + updateAnswer(questionId, variant.id, variant.points || 0); - try { - await sendAnswer({ - questionId: currentQuestion.id, - body: `${currentQuestion.content.variants[index].answer} `, - qid: quizId, - preview, - }); - - updateAnswer( - currentQuestion.id, - currentQuestion.content.variants[index].id, - currentQuestion.content.variants[index].points || 0 - ); - } catch (error) { - enqueueSnackbar("ответ не был засчитан"); + if (answer === variant.id) { + deleteAnswer(questionId); } - - if (answer === currentQuestion.content.variants[index].id) { - try { - await sendAnswer({ - questionId: currentQuestion.id, - body: "", - qid: quizId, - preview, - }); - } catch (error) { - enqueueSnackbar("ответ не был засчитан"); - } - - deleteAnswer(currentQuestion.id); - } - - setIsSending(false); }; return ( @@ -93,11 +48,10 @@ export const VarimgVariant = ({ ? "#FFFFFF" : "rgba(255,255,255, 0.3)" : quizThemes[settings.cfg.theme].isLight - ? "white" - : theme.palette.background.default, + ? "white" + : theme.palette.background.default, border: `1px solid`, - borderColor: - answer === variant.id ? theme.palette.primary.main : "#9A9AAF", + borderColor: answer === variant.id ? theme.palette.primary.main : "#9A9AAF", display: "flex", margin: 0, justifyContent: "space-between", diff --git a/lib/components/ViewPublicationPage/questions/Varimg/index.tsx b/lib/components/ViewPublicationPage/questions/Varimg/index.tsx index a186597..d0f9c3b 100644 --- a/lib/components/ViewPublicationPage/questions/Varimg/index.tsx +++ b/lib/components/ViewPublicationPage/questions/Varimg/index.tsx @@ -21,11 +21,8 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => { const theme = useTheme(); const isMobile = useRootContainerSize() < 650; - const { answer } = - answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {}; - const variant = currentQuestion.content.variants.find( - ({ id }) => answer === id - ); + const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {}; + const variant = currentQuestion.content.variants.find(({ id }) => answer === id); return ( @@ -47,9 +44,7 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => { > answer === id - )} + value={currentQuestion.content.variants.findIndex(({ id }) => answer === id)} sx={{ display: "flex", flexWrap: "wrap", @@ -72,7 +67,7 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => { {currentQuestion.content.variants.map((variant, index) => ( { style={{ width: "100%", height: "100%", objectFit: "cover" }} alt="" /> - ) : currentQuestion.content.replText !== " " && - currentQuestion.content.replText.length > 0 ? ( + ) : currentQuestion.content.replText !== " " && currentQuestion.content.replText.length > 0 ? ( currentQuestion.content.replText ) : variant?.extendedText || isMobile ? ( "Выберите вариант ответа ниже" diff --git a/lib/components/ViewPublicationPage/tools/NextButton.tsx b/lib/components/ViewPublicationPage/tools/NextButton.tsx index b5b310c..006c68d 100644 --- a/lib/components/ViewPublicationPage/tools/NextButton.tsx +++ b/lib/components/ViewPublicationPage/tools/NextButton.tsx @@ -1,28 +1,29 @@ -import {Button, useTheme} from "@mui/material"; +import { useQuizSettings } from "@contexts/QuizDataContext"; +import { Button } from "@mui/material"; import { quizThemes } from "@utils/themes/Publication/themePublication"; -import { useQuizData } from "@contexts/QuizDataContext"; -interface Props{ - isNextButtonEnabled: boolean, - moveToNextQuestion: () => void, +interface Props { + isNextButtonEnabled: boolean; + moveToNextQuestion: () => void; } -export default function NextButton ({isNextButtonEnabled, moveToNextQuestion}: Props) { - const theme = useTheme(); - const { settings } = useQuizData(); - return( - - )} +export default function NextButton({ isNextButtonEnabled, moveToNextQuestion }: Props) { + const { settings } = useQuizSettings(); + + return ( + + ); +} diff --git a/lib/components/ViewPublicationPage/tools/PrevButton.tsx b/lib/components/ViewPublicationPage/tools/PrevButton.tsx index 347c222..80621e8 100644 --- a/lib/components/ViewPublicationPage/tools/PrevButton.tsx +++ b/lib/components/ViewPublicationPage/tools/PrevButton.tsx @@ -1,39 +1,41 @@ import { Button, useTheme } from "@mui/material"; import { useRootContainerSize } from "../../../contexts/RootContainerWidthContext"; import { quizThemes } from "@utils/themes/Publication/themePublication"; -import { useQuizData } from "@contexts/QuizDataContext"; +import { useQuizSettings } from "@contexts/QuizDataContext"; interface Props { - isPreviousButtonEnabled: boolean, - moveToPrevQuestion: () => void, + isPreviousButtonEnabled: boolean; + moveToPrevQuestion: () => void; } export default function PrevButton({ isPreviousButtonEnabled, moveToPrevQuestion }: Props) { - const theme = useTheme(); - const { settings } = useQuizData(); - const isMobileMini = useRootContainerSize() < 382; - return ( - - ); + const theme = useTheme(); + const { settings } = useQuizSettings(); + const isMobileMini = useRootContainerSize() < 382; + return ( + + ); } diff --git a/lib/components/ViewPublicationPage/tools/Select.tsx b/lib/components/ViewPublicationPage/tools/Select.tsx index d12195b..770056d 100644 --- a/lib/components/ViewPublicationPage/tools/Select.tsx +++ b/lib/components/ViewPublicationPage/tools/Select.tsx @@ -1,11 +1,5 @@ import { useState, useEffect } from "react"; -import { - Select as MuiSelect, - MenuItem, - FormControl, - Typography, - useTheme, -} from "@mui/material"; +import { Select as MuiSelect, MenuItem, FormControl, Typography, useTheme } from "@mui/material"; import ArrowDown from "@icons/ArrowDownIcon"; @@ -20,7 +14,6 @@ type SelectProps = { colorMain?: string; colorPlaceholder?: string; placeholder?: string; - disabled?: boolean; }; export const Select = ({ @@ -32,11 +25,8 @@ export const Select = ({ placeholder = "", colorMain = "#7E2AEA", colorPlaceholder = "#9A9AAF", - disabled = false, }: SelectProps) => { - const [activeItem, setActiveItem] = useState( - empty ? -1 : activeItemIndex - ); + const [activeItem, setActiveItem] = useState(empty ? -1 : activeItemIndex); const theme = useTheme(); useEffect(() => { @@ -59,7 +49,6 @@ export const Select = ({ return ( - value ? ( - items[Number(value)] - ) : ( - - {placeholder} - - ) + value ? items[Number(value)] : {placeholder} } id="display-select" variant="outlined" @@ -88,8 +71,8 @@ export const Select = ({ borderRadius: "10px", }, "& .MuiSelect-icon": { - color: theme.palette.primary.main - } + color: theme.palette.primary.main, + }, }} MenuProps={{ PaperProps: { diff --git a/lib/components/ViewPublicationPage/tools/YoutubeEmbedIframe.tsx b/lib/components/ViewPublicationPage/tools/YoutubeEmbedIframe.tsx deleted file mode 100644 index 26d3864..0000000 --- a/lib/components/ViewPublicationPage/tools/YoutubeEmbedIframe.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { Box, SxProps } from "@mui/material"; - -interface Props { - videoUrl: string; - containerSX?: SxProps; -} - -export default function YoutubeEmbedIframe({ videoUrl, containerSX }: Props) { - const extractYoutubeVideoId = - /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/gi; - const videoId = extractYoutubeVideoId.exec(videoUrl)?.[1]; - // if (!videoId) return null; - - const embedUrl = `https://www.youtube.com/embed/${videoId}?controls=0&autoplay=1&modestbranding=0&showinfo=0&disablekb=1&mute=1&loop=1`; - // https://www.youtube.com/shorts/9VgqBPd6RPA - // https://www.youtube.com/watch?v=I2N8hTHhvGY - return ( - - - {/*