Merge remote-tracking branch 'origin/staging'

This commit is contained in:
skeris 2024-07-15 11:48:23 +03:00
commit bfc46d1b46
162 changed files with 5799 additions and 5031 deletions

@ -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",
},
};

2
.gitignore vendored

@ -11,7 +11,7 @@ node_modules
dist
dist-package
dist-ssr
widget
/widget
*.local
# Editor directories and files

1
.husky/.gitignore vendored Normal file

@ -0,0 +1 @@
_

1
.husky/pre-commit Normal file

@ -0,0 +1 @@
yarn lint-staged --allow-empty

@ -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

@ -1,47 +1,68 @@
## Правила
- Запрещено использовать vh/vw css-юниты и их производные
## Виджет
### Сборка
```bash
yarn build:widget
```
### Использование
```html
<script type="module">
import widget from "https://hbpn.link/export/pub.js";
import widget from "https://hbpn.link/export/pub.js";
widget.create({
selector: "widget-container",
quizId: "...",
})
widget.create({
selector: "widget-container",
quizId: "...",
});
</script>
```
## 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";

@ -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,
},
});

@ -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

11
lib/api/hooks.ts Normal file

@ -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,
});
}

@ -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<GetQuizDataResponse>(
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<GetQuizDataResponse>(
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<string, string>;
};
qid: string;
preview: boolean;
questionId: string;
body: {
name?: string;
email?: string;
phone?: string;
address?: string;
customs?: Record<string, string>;
};
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",
});
}

@ -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 (
<Box
{...props}
sx={{
top: "25% !important",
height: "24px",
width: "24px",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M19.5 9L12 16.5L4.5 9" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</Box>
);
}
return (
<Box
{...props}
sx={{
top: "25% !important",
height: "24px",
width: "24px",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<path
d="M19.5 9L12 16.5L4.5 9"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</Box>
);
}

@ -1,10 +1,26 @@
export default function BlankImage() {
return (
<svg width="100%" height="100%" viewBox="0 -70 800 535" fill="none" display="block" preserveAspectRatio="xMidYMax meet" xmlns="http://www.w3.org/2000/svg">
<path fill="#F0F0F0" d="M555 47a47.003 47.003 0 0 1 29.014-43.422 46.999 46.999 0 0 1 61.408 61.408 46.997 46.997 0 0 1-76.656 15.248A47 47 0 0 1 555 47Z" />
<path fill="#F3F3F3" d="M641.874 240.665c7.74-7.74 20.263-7.82 28.102-.181L1051 611.837 779.035 883.805 383.869 498.67l258.005-258.005Z" />
<path fill="#EDEDED" d="M183.393 61.546c7.692-7.037 19.499-6.985 27.129.12l677.42 630.746-690.929 382.738L-397 592.531 183.393 61.546Z" />
</svg>
);
}
return (
<svg
width="100%"
height="100%"
viewBox="0 -70 800 535"
fill="none"
display="block"
preserveAspectRatio="xMidYMax meet"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="#F0F0F0"
d="M555 47a47.003 47.003 0 0 1 29.014-43.422 46.999 46.999 0 0 1 61.408 61.408 46.997 46.997 0 0 1-76.656 15.248A47 47 0 0 1 555 47Z"
/>
<path
fill="#F3F3F3"
d="M641.874 240.665c7.74-7.74 20.263-7.82 28.102-.181L1051 611.837 779.035 883.805 383.869 498.67l258.005-258.005Z"
/>
<path
fill="#EDEDED"
d="M183.393 61.546c7.692-7.037 19.499-6.985 27.129.12l677.42 630.746-690.929 382.738L-397 592.531 183.393 61.546Z"
/>
</svg>
);
}

@ -1,6 +1,6 @@
import { Box, SxProps, Theme } from "@mui/material";
import { Box, SxProps, Theme } from "@mui/material";
interface Props {
sx?: SxProps<Theme>;
sx?: SxProps<Theme>;
}
export default function CalendarIcon({ sx }: Props) {
return (
@ -22,7 +22,7 @@ export default function CalendarIcon({ sx }: Props) {
"&:active rect": {
stroke: "#FB5607",
},
...sx
...sx,
}}
>
<svg width="20" height="22" viewBox="0 0 20 22" fill="none" xmlns="http://www.w3.org/2000/svg">

@ -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 && (
<svg
xmlns="http://www.w3.org/2000/svg"
width="15"
height="15"
viewBox="0 0 25 18"
fill="none"
>
<path
d="M2 9L10 16.5L22.5 1.5"
stroke="#ffffff"
strokeWidth="4"
strokeLinecap="round"
/>
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 25 18" fill="none">
<path d="M2 9L10 16.5L22.5 1.5" stroke="#ffffff" strokeWidth="4" strokeLinecap="round" />
</svg>
)}
</Box>

@ -8,13 +8,7 @@ export default function CloseBold({ width }: Props) {
const theme = useTheme();
return (
<svg
width="35"
height="33"
viewBox="0 0 35 33"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<svg width="35" height="33" viewBox="0 0 35 33" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Close">
<g id="Rectangle 57" opacity="0.3" filter="url(#filter0_d_4080_12482)">
<rect x="6" y="4" width="24" height="24" rx="12" fill="#9A9AAF" />
@ -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"
/>
<feMorphology
radius="1"
operator="dilate"
in="SourceAlpha"
result="effect1_dropShadow_4080_12482"
/>
<feMorphology radius="1" operator="dilate" in="SourceAlpha" result="effect1_dropShadow_4080_12482" />
<feOffset dy="2" />
<feGaussianBlur stdDeviation="2.5" />
<feComposite in2="hardAlpha" operator="out" />
<feColorMatrix
type="matrix"
values="0 0 0 0 0.416562 0 0 0 0 0.452406 0 0 0 0 0.775 0 0 0 0.18 0"
/>
<feBlend
mode="normal"
in2="BackgroundImageFix"
result="effect1_dropShadow_4080_12482"
/>
<feBlend
mode="normal"
in="SourceGraphic"
in2="effect1_dropShadow_4080_12482"
result="shape"
/>
<feColorMatrix type="matrix" values="0 0 0 0 0.416562 0 0 0 0 0.452406 0 0 0 0 0.775 0 0 0 0.18 0" />
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_4080_12482" />
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_4080_12482" result="shape" />
</filter>
</defs>
</svg>

@ -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",
}}
>
<svg
width="15"
height="15"
viewBox="0 0 15 15"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M7.5 7.96875C8.53553 7.96875 9.375 7.12928 9.375 6.09375C9.375 5.05822 8.53553 4.21875 7.5 4.21875C6.46447 4.21875 5.625 5.05822 5.625 6.09375C5.625 7.12928 6.46447 7.96875 7.5 7.96875Z"
stroke={color}

@ -2,7 +2,7 @@ import { Box } from "@mui/material";
interface Props {
color: string;
backgroundColor: string
backgroundColor: string;
}
export default function EmailIcon({ color, backgroundColor }: Props) {
@ -14,16 +14,21 @@ export default function EmailIcon({ color, backgroundColor }: Props) {
justifyContent: "center",
height: "58px",
width: "45px",
backgroundColor: {backgroundColor},
backgroundColor: { backgroundColor },
borderBottomLeftRadius: "12px",
borderTopLeftRadius: "12px",
}}
>
<svg width="17" height="18" viewBox="0 0 17 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="1" y="3" width="15" height="12" rx="3" stroke="#9A9AAF" strokeWidth="1.5"/>
<path d="M4 6.75L7.3 9.225C8.01111 9.75833 8.98889 9.75833 9.7 9.225L13 6.75" stroke="#9A9AAF" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
<svg width="17" height="18" viewBox="0 0 17 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="1" y="3" width="15" height="12" rx="3" stroke="#9A9AAF" strokeWidth="1.5" />
<path
d="M4 6.75L7.3 9.225C8.01111 9.75833 8.98889 9.75833 9.7 9.225L13 6.75"
stroke="#9A9AAF"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</Box>
);
}

@ -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",
}}
>
<svg width="17" height="18" viewBox="0 0 17 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="2.97143" cy="2.97143" r="2.97143" transform="matrix(-1 0 0 1 11.4688 2.5)" stroke="#9A9AAF" strokeWidth="1.5"/>
<path d="M3.29688 12.8526C3.29688 12.2135 3.69865 11.6433 4.30054 11.4284V11.4284C7.01416 10.4592 9.97959 10.4592 12.6932 11.4284V11.4284C13.2951 11.6433 13.6969 12.2135 13.6969 12.8526V13.8298C13.6969 14.7119 12.9156 15.3895 12.0424 15.2648L11.7512 15.2232C9.59262 14.9148 7.40113 14.9148 5.24252 15.2232L4.95137 15.2648C4.07814 15.3895 3.29688 14.7119 3.29688 13.8298V12.8526Z" stroke="#9A9AAF" strokeWidth="1.5"/>
</svg>
<svg width="17" height="18" viewBox="0 0 17 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle
cx="2.97143"
cy="2.97143"
r="2.97143"
transform="matrix(-1 0 0 1 11.4688 2.5)"
stroke="#9A9AAF"
strokeWidth="1.5"
/>
<path
d="M3.29688 12.8526C3.29688 12.2135 3.69865 11.6433 4.30054 11.4284V11.4284C7.01416 10.4592 9.97959 10.4592 12.6932 11.4284V11.4284C13.2951 11.6433 13.6969 12.2135 13.6969 12.8526V13.8298C13.6969 14.7119 12.9156 15.3895 12.0424 15.2648L11.7512 15.2232C9.59262 14.9148 7.40113 14.9148 5.24252 15.2232L4.95137 15.2648C4.07814 15.3895 3.29688 14.7119 3.29688 13.8298V12.8526Z"
stroke="#9A9AAF"
strokeWidth="1.5"
/>
</svg>
</Box>
);
}

@ -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",
}}
>
<svg width="17" height="18" viewBox="0 0 17 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.6893 15.0369C12.2537 16.4725 8.57727 15.1236 5.47772 12.024C2.37818 8.9245 1.02926 5.24806 2.46484 3.81247L3.41006 2.86725C4.06259 2.21472 5.13781 2.23197 5.81163 2.90578L7.27569 4.36984C7.9495 5.04365 7.96675 6.11888 7.31421 6.77141L7.11119 6.97443C6.75888 7.32675 6.72441 7.8951 7.05178 8.29181C7.36754 8.67446 7.70796 9.0556 8.07707 9.4247C8.44617 9.7938 8.82731 10.1342 9.20996 10.45C9.60666 10.7774 10.175 10.7429 10.5273 10.3906L10.7304 10.1875C11.3829 9.53501 12.4581 9.55226 13.1319 10.2261L14.596 11.6901C15.2698 12.364 15.287 13.4392 14.6345 14.0917L13.6893 15.0369Z" stroke="#9A9AAF" strokeWidth="1.5"/>
</svg>
<svg width="17" height="18" viewBox="0 0 17 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M13.6893 15.0369C12.2537 16.4725 8.57727 15.1236 5.47772 12.024C2.37818 8.9245 1.02926 5.24806 2.46484 3.81247L3.41006 2.86725C4.06259 2.21472 5.13781 2.23197 5.81163 2.90578L7.27569 4.36984C7.9495 5.04365 7.96675 6.11888 7.31421 6.77141L7.11119 6.97443C6.75888 7.32675 6.72441 7.8951 7.05178 8.29181C7.36754 8.67446 7.70796 9.0556 8.07707 9.4247C8.44617 9.7938 8.82731 10.1342 9.20996 10.45C9.60666 10.7774 10.175 10.7429 10.5273 10.3906L10.7304 10.1875C11.3829 9.53501 12.4581 9.55226 13.1319 10.2261L14.596 11.6901C15.2698 12.364 15.287 13.4392 14.6345 14.0917L13.6893 15.0369Z"
stroke="#9A9AAF"
strokeWidth="1.5"
/>
</svg>
</Box>
);
}

@ -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",
}}
>
<svg
width="15"
height="15"
viewBox="0 0 15 15"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.67188 1.60156L8.67188 13.3838"
stroke={color}
strokeLinecap="round"
strokeLinejoin="round"
/>
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.67188 1.60156L8.67188 13.3838" stroke={color} strokeLinecap="round" strokeLinejoin="round" />
<path
d="M10.6016 4.21875H13.7768C13.8534 4.21875 13.9268 4.26814 13.9809 4.35604C14.035 4.44395 14.0655 4.56318 14.0655 4.6875V10.3125C14.0655 10.4368 14.035 10.556 13.9809 10.644C13.9268 10.7319 13.8534 10.7812 13.7768 10.7812H10.6016"
stroke={color}
@ -44,30 +33,10 @@ export default function TextIcon({ color, backgroundColor }: Props) {
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M3.98438 6.5625H5.625"
stroke={color}
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M7.85156 1.60156H9.49219"
stroke={color}
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M7.85156 13.3828H9.49219"
stroke={color}
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M4.80469 6.5625V8.67188"
stroke={color}
strokeLinecap="round"
strokeLinejoin="round"
/>
<path d="M3.98438 6.5625H5.625" stroke={color} strokeLinecap="round" strokeLinejoin="round" />
<path d="M7.85156 1.60156H9.49219" stroke={color} strokeLinecap="round" strokeLinejoin="round" />
<path d="M7.85156 13.3828H9.49219" stroke={color} strokeLinecap="round" strokeLinejoin="round" />
<path d="M4.80469 6.5625V8.67188" stroke={color} strokeLinecap="round" strokeLinejoin="round" />
</svg>
</Box>
);

@ -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 (
<IconButton
sx={sx}
className={className}
onClick={onClick}
>
<svg
width={width}
height={height}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<IconButton sx={sx} className={className} onClick={onClick}>
<svg width={width} height={height} viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M10 19C14.9706 19 19 14.9706 19 10C19 5.02944 14.9706 1 10 1C5.02944 1 1 5.02944 1 10C1 14.9706 5.02944 19 10 19Z"
stroke={color}

@ -2,27 +2,85 @@ import { FC, SVGProps } from "react";
export const NameplateLogo: FC<SVGProps<SVGSVGElement>> = (props) => (
<svg {...props} width="168" height="20" viewBox="0 0 168 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M162.063 14.9047C161.972 14.9047 161.895 14.8775 161.832 14.823C161.777 14.7595 161.75 14.6824 161.75 14.5917V14.1153C161.75 14.0337 161.768 13.9565 161.804 13.884C161.85 13.8114 161.895 13.7433 161.941 13.6798L165.697 8.9028H162.186C162.095 8.9028 162.018 8.87558 161.954 8.82115C161.9 8.76671 161.872 8.68958 161.872 8.58978V8.14066C161.872 8.04993 161.9 7.97734 161.954 7.92291C162.018 7.85939 162.095 7.82764 162.186 7.82764H167.031C167.121 7.82764 167.194 7.85939 167.248 7.92291C167.312 7.97734 167.344 8.04993 167.344 8.14066V8.64422C167.344 8.72588 167.321 8.79846 167.276 8.86197C167.239 8.92549 167.198 8.99353 167.153 9.06612L163.438 13.8295H167.303C167.393 13.8295 167.466 13.8567 167.521 13.9112C167.584 13.9656 167.616 14.0427 167.616 14.1425V14.5917C167.616 14.6824 167.584 14.7595 167.521 14.823C167.466 14.8775 167.393 14.9047 167.303 14.9047H162.063Z" fill="currentColor"/>
<path d="M159.148 14.9037C159.058 14.9037 158.981 14.8764 158.917 14.822C158.863 14.7585 158.835 14.6814 158.835 14.5906V8.13964C158.835 8.04891 158.863 7.97633 158.917 7.92189C158.981 7.85837 159.058 7.82662 159.148 7.82662H159.802C159.892 7.82662 159.965 7.85837 160.019 7.92189C160.074 7.97633 160.101 8.04891 160.101 8.13964V14.5906C160.101 14.6814 160.074 14.7585 160.019 14.822C159.965 14.8764 159.892 14.9037 159.802 14.9037H159.148ZM159.04 6.50648C158.949 6.50648 158.872 6.47926 158.808 6.42482C158.754 6.36131 158.727 6.28419 158.727 6.19345V5.45853C158.727 5.3678 158.754 5.29521 158.808 5.24078C158.872 5.17726 158.949 5.14551 159.04 5.14551H159.897C159.988 5.14551 160.06 5.17726 160.115 5.24078C160.178 5.29521 160.21 5.3678 160.21 5.45853V6.19345C160.21 6.28419 160.178 6.36131 160.115 6.42482C160.06 6.47926 159.988 6.50648 159.897 6.50648H159.04Z" fill="currentColor"/>
<path d="M153.294 15.0408C152.714 15.0408 152.219 14.9138 151.811 14.6597C151.412 14.3966 151.108 14.0337 150.899 13.5709C150.69 13.1082 150.586 12.5774 150.586 11.9786V8.14066C150.586 8.04993 150.613 7.97734 150.668 7.92291C150.731 7.85939 150.808 7.82764 150.899 7.82764H151.579C151.67 7.82764 151.743 7.85939 151.797 7.92291C151.861 7.97734 151.892 8.04993 151.892 8.14066V11.9105C151.892 13.2624 152.482 13.9384 153.662 13.9384C154.224 13.9384 154.673 13.7615 155.009 13.4076C155.354 13.0447 155.526 12.5457 155.526 11.9105V8.14066C155.526 8.04993 155.553 7.97734 155.608 7.92291C155.671 7.85939 155.749 7.82764 155.839 7.82764H156.506C156.606 7.82764 156.683 7.85939 156.738 7.92291C156.792 7.97734 156.819 8.04993 156.819 8.14066V14.5917C156.819 14.6824 156.792 14.7595 156.738 14.823C156.683 14.8775 156.606 14.9047 156.506 14.9047H155.88C155.789 14.9047 155.712 14.8775 155.649 14.823C155.594 14.7595 155.567 14.6824 155.567 14.5917V13.9928C155.322 14.3104 155.023 14.5644 154.669 14.755C154.324 14.9455 153.866 15.0408 153.294 15.0408Z" fill="currentColor"/>
<path d="M147.755 15.7912C147.637 15.7912 147.542 15.7503 147.47 15.6687C147.397 15.5961 147.338 15.5326 147.293 15.4781L146.735 14.716C146.245 14.9338 145.678 15.0426 145.033 15.0426C144.253 15.0426 143.586 14.9111 143.033 14.648C142.488 14.3758 142.062 13.9629 141.754 13.4095C141.454 12.8469 141.291 12.1438 141.264 11.3C141.255 10.9008 141.25 10.5197 141.25 10.1568C141.25 9.78476 141.255 9.39915 141.264 8.99993C141.291 8.1652 141.463 7.47111 141.781 6.91765C142.098 6.35511 142.534 5.93775 143.087 5.66555C143.65 5.38429 144.299 5.24365 145.033 5.24365C145.777 5.24365 146.426 5.38429 146.98 5.66555C147.542 5.93775 147.982 6.35511 148.3 6.91765C148.617 7.47111 148.785 8.1652 148.803 8.99993C148.822 9.39915 148.831 9.78476 148.831 10.1568C148.831 10.5197 148.822 10.9008 148.803 11.3C148.758 12.5521 148.431 13.4821 147.823 14.09L148.722 15.3693C148.731 15.3783 148.74 15.392 148.749 15.4101C148.758 15.4373 148.763 15.4736 148.763 15.519C148.772 15.5916 148.749 15.6551 148.695 15.7095C148.64 15.764 148.572 15.7912 148.49 15.7912H147.755ZM145.033 13.8858C145.723 13.8858 146.29 13.6771 146.735 13.2598C147.179 12.8424 147.42 12.1665 147.456 11.2319C147.474 10.8236 147.483 10.4607 147.483 10.1431C147.483 9.81651 147.474 9.45359 147.456 9.05437C147.438 8.42832 147.32 7.92023 147.102 7.53008C146.893 7.13994 146.612 6.85413 146.258 6.67267C145.905 6.49121 145.496 6.40048 145.033 6.40048C144.589 6.40048 144.185 6.49121 143.822 6.67267C143.468 6.85413 143.187 7.13994 142.978 7.53008C142.77 7.92023 142.652 8.42832 142.625 9.05437C142.616 9.45359 142.611 9.81651 142.611 10.1431C142.611 10.4607 142.616 10.8236 142.625 11.2319C142.661 12.1665 142.901 12.8424 143.346 13.2598C143.79 13.6771 144.353 13.8858 145.033 13.8858Z" fill="currentColor"/>
<path d="M135.907 15.0367C135.453 15.0367 135.04 14.946 134.668 14.7645C134.296 14.5831 133.997 14.3381 133.77 14.0296C133.543 13.7211 133.43 13.3718 133.43 12.9817C133.43 12.3556 133.684 11.8566 134.192 11.4846C134.7 11.1126 135.362 10.8676 136.179 10.7497L138.207 10.4639V10.0692C138.207 9.63369 138.08 9.29344 137.826 9.04847C137.581 8.8035 137.177 8.68101 136.614 8.68101C136.206 8.68101 135.875 8.76267 135.621 8.92598C135.376 9.0893 135.203 9.29798 135.104 9.55203C135.049 9.68813 134.954 9.75617 134.818 9.75617H134.205C134.106 9.75617 134.029 9.72895 133.974 9.67452C133.929 9.611 133.906 9.53842 133.906 9.45676C133.906 9.32066 133.956 9.15281 134.056 8.9532C134.165 8.75359 134.328 8.55852 134.546 8.36798C134.763 8.17745 135.04 8.01867 135.376 7.89165C135.721 7.75555 136.138 7.6875 136.628 7.6875C137.172 7.6875 137.631 7.76009 138.003 7.90526C138.375 8.04135 138.665 8.22735 138.874 8.46325C139.091 8.69915 139.246 8.96681 139.336 9.26622C139.436 9.56564 139.486 9.86959 139.486 10.1781V14.5876C139.486 14.6783 139.454 14.7555 139.391 14.819C139.336 14.8734 139.264 14.9006 139.173 14.9006H138.547C138.447 14.9006 138.37 14.8734 138.316 14.819C138.261 14.7555 138.234 14.6783 138.234 14.5876V14.0024C138.116 14.1657 137.957 14.329 137.758 14.4923C137.558 14.6466 137.308 14.7782 137.009 14.887C136.71 14.9868 136.342 15.0367 135.907 15.0367ZM136.192 14.016C136.564 14.016 136.905 13.9389 137.213 13.7846C137.522 13.6213 137.762 13.3718 137.934 13.0361C138.116 12.7004 138.207 12.2785 138.207 11.7704V11.3893L136.628 11.6207C135.984 11.7114 135.498 11.8657 135.172 12.0834C134.845 12.2921 134.682 12.5598 134.682 12.8864C134.682 13.1405 134.754 13.3537 134.9 13.5261C135.054 13.6894 135.244 13.8119 135.471 13.8935C135.707 13.9752 135.947 14.016 136.192 14.016Z" fill="currentColor"/>
<path d="M125.993 14.9006C125.902 14.9006 125.825 14.8734 125.761 14.819C125.707 14.7555 125.68 14.6783 125.68 14.5876V8.13662C125.68 8.04589 125.707 7.9733 125.761 7.91887C125.825 7.85535 125.902 7.8236 125.993 7.8236H126.632C126.723 7.8236 126.796 7.85535 126.85 7.91887C126.914 7.9733 126.945 8.04589 126.945 8.13662V8.73545C127.181 8.42696 127.476 8.17745 127.83 7.98691C128.193 7.7873 128.656 7.6875 129.218 7.6875C129.808 7.6875 130.307 7.81906 130.715 8.08218C131.133 8.33623 131.446 8.69462 131.654 9.15735C131.863 9.611 131.967 10.1418 131.967 10.7497V14.5876C131.967 14.6783 131.94 14.7555 131.886 14.819C131.831 14.8734 131.759 14.9006 131.668 14.9006H130.987C130.897 14.9006 130.82 14.8734 130.756 14.819C130.702 14.7555 130.674 14.6783 130.674 14.5876V10.8177C130.674 10.1826 130.52 9.68813 130.212 9.33427C129.903 8.97135 129.45 8.78989 128.851 8.78989C128.288 8.78989 127.835 8.97135 127.49 9.33427C127.154 9.68813 126.986 10.1826 126.986 10.8177V14.5876C126.986 14.6783 126.954 14.7555 126.891 14.819C126.837 14.8734 126.764 14.9006 126.673 14.9006H125.993Z" fill="currentColor"/>
<path d="M120.884 15.0367C119.949 15.0367 119.205 14.7509 118.652 14.1793C118.099 13.5986 117.795 12.8093 117.74 11.8112C117.731 11.6933 117.727 11.5436 117.727 11.3621C117.727 11.1716 117.731 11.0173 117.74 10.8994C117.776 10.2552 117.926 9.69266 118.189 9.21179C118.452 8.72184 118.811 8.3453 119.264 8.08218C119.727 7.81906 120.267 7.6875 120.884 7.6875C121.574 7.6875 122.15 7.83267 122.612 8.12301C123.084 8.41335 123.443 8.82618 123.688 9.36149C123.933 9.89681 124.055 10.5229 124.055 11.2396V11.471C124.055 11.5708 124.023 11.6479 123.96 11.7024C123.905 11.7568 123.833 11.784 123.742 11.784H119.033C119.033 11.7931 119.033 11.8112 119.033 11.8385C119.033 11.8657 119.033 11.8884 119.033 11.9065C119.051 12.2785 119.133 12.6278 119.278 12.9545C119.423 13.272 119.632 13.5306 119.904 13.7302C120.176 13.9298 120.503 14.0296 120.884 14.0296C121.211 14.0296 121.483 13.9797 121.701 13.8799C121.918 13.7801 122.095 13.6712 122.231 13.5533C122.367 13.4263 122.458 13.331 122.504 13.2675C122.585 13.1495 122.649 13.0815 122.694 13.0633C122.739 13.0361 122.812 13.0225 122.912 13.0225H123.565C123.656 13.0225 123.728 13.0497 123.783 13.1042C123.846 13.1495 123.874 13.2176 123.865 13.3083C123.855 13.4444 123.783 13.6123 123.647 13.8119C123.511 14.0024 123.316 14.1929 123.062 14.3835C122.808 14.574 122.499 14.7328 122.136 14.8598C121.773 14.9778 121.356 15.0367 120.884 15.0367ZM119.033 10.845H122.762V10.8041C122.762 10.3958 122.685 10.0329 122.531 9.71535C122.386 9.39779 122.172 9.14827 121.891 8.96681C121.61 8.77628 121.274 8.68101 120.884 8.68101C120.494 8.68101 120.158 8.77628 119.877 8.96681C119.605 9.14827 119.396 9.39779 119.251 9.71535C119.106 10.0329 119.033 10.3958 119.033 10.8041V10.845Z" fill="currentColor"/>
<path d="M109.915 14.9033C109.815 14.9033 109.738 14.876 109.683 14.8216C109.629 14.7581 109.602 14.681 109.602 14.5902V5.7031C109.602 5.60329 109.629 5.52617 109.683 5.47173C109.738 5.40822 109.815 5.37646 109.915 5.37646H113.371C114.043 5.37646 114.624 5.48534 115.113 5.7031C115.613 5.92085 115.998 6.24749 116.27 6.683C116.543 7.10943 116.679 7.64475 116.679 8.28894C116.679 8.93313 116.543 9.46845 116.27 9.89489C115.998 10.3213 115.613 10.6434 115.113 10.8612C114.624 11.0789 114.043 11.1878 113.371 11.1878H110.963V14.5902C110.963 14.681 110.931 14.7581 110.867 14.8216C110.813 14.876 110.736 14.9033 110.636 14.9033H109.915ZM110.949 10.0446H113.303C113.975 10.0446 114.478 9.89489 114.814 9.59547C115.15 9.29606 115.318 8.86055 115.318 8.28894C115.318 7.72641 115.154 7.2909 114.828 6.98241C114.501 6.67392 113.993 6.51968 113.303 6.51968H110.949V10.0446Z" fill="currentColor"/>
<path d="M100.555 15.0367C100.101 15.0367 99.6886 14.946 99.3166 14.7645C98.9446 14.5831 98.6452 14.3381 98.4184 14.0296C98.1915 13.7211 98.0781 13.3718 98.0781 12.9817C98.0781 12.3556 98.3322 11.8566 98.8403 11.4846C99.3484 11.1126 100.011 10.8676 100.827 10.7497L102.855 10.4639V10.0692C102.855 9.63369 102.728 9.29344 102.474 9.04847C102.229 8.8035 101.825 8.68101 101.263 8.68101C100.855 8.68101 100.523 8.76267 100.269 8.92598C100.024 9.0893 99.8519 9.29798 99.7521 9.55203C99.6977 9.68813 99.6024 9.75617 99.4663 9.75617H98.8539C98.7541 9.75617 98.6769 9.72895 98.6225 9.67452C98.5771 9.611 98.5545 9.53842 98.5545 9.45676C98.5545 9.32066 98.6044 9.15281 98.7042 8.9532C98.813 8.75359 98.9764 8.55852 99.1941 8.36798C99.4119 8.17745 99.6886 8.01867 100.024 7.89165C100.369 7.75555 100.786 7.6875 101.276 7.6875C101.821 7.6875 102.279 7.76009 102.651 7.90526C103.023 8.04135 103.313 8.22735 103.522 8.46325C103.74 8.69915 103.894 8.96681 103.985 9.26622C104.085 9.56564 104.134 9.86959 104.134 10.1781V14.5876C104.134 14.6783 104.103 14.7555 104.039 14.819C103.985 14.8734 103.912 14.9006 103.821 14.9006H103.195C103.096 14.9006 103.018 14.8734 102.964 14.819C102.91 14.7555 102.882 14.6783 102.882 14.5876V14.0024C102.764 14.1657 102.606 14.329 102.406 14.4923C102.206 14.6466 101.957 14.7782 101.657 14.887C101.358 14.9868 100.991 15.0367 100.555 15.0367ZM100.841 14.016C101.213 14.016 101.553 13.9389 101.862 13.7846C102.17 13.6213 102.411 13.3718 102.583 13.0361C102.764 12.7004 102.855 12.2785 102.855 11.7704V11.3893L101.276 11.6207C100.632 11.7114 100.147 11.8657 99.8202 12.0834C99.4935 12.2921 99.3302 12.5598 99.3302 12.8864C99.3302 13.1405 99.4028 13.3537 99.548 13.5261C99.7022 13.6894 99.8928 13.8119 100.12 13.8935C100.355 13.9752 100.596 14.016 100.841 14.016Z" fill="currentColor"/>
<path d="M90.6021 14.9047C90.5114 14.9047 90.4342 14.8775 90.3707 14.823C90.3163 14.7595 90.2891 14.6824 90.2891 14.5917V8.14066C90.2891 8.04993 90.3163 7.97734 90.3707 7.92291C90.4342 7.85939 90.5114 7.82764 90.6021 7.82764H91.2554C91.3461 7.82764 91.4187 7.85939 91.4731 7.92291C91.5275 7.97734 91.5548 8.04993 91.5548 8.14066V10.7537H95.2702V8.14066C95.2702 8.04993 95.2974 7.97734 95.3519 7.92291C95.4154 7.85939 95.4925 7.82764 95.5832 7.82764H96.2229C96.3136 7.82764 96.3862 7.85939 96.4406 7.92291C96.5042 7.97734 96.5359 8.04993 96.5359 8.14066V14.5917C96.5359 14.6824 96.5042 14.7595 96.4406 14.823C96.3862 14.8775 96.3136 14.9047 96.2229 14.9047H95.5832C95.4925 14.9047 95.4154 14.8775 95.3519 14.823C95.2974 14.7595 95.2702 14.6824 95.2702 14.5917V11.8425H91.5548V14.5917C91.5548 14.6824 91.5275 14.7595 91.4731 14.823C91.4187 14.8775 91.3461 14.9047 91.2554 14.9047H90.6021Z" fill="currentColor"/>
<path d="M82.0322 15.0367C81.3426 15.0367 80.7665 14.9052 80.3038 14.6421C79.841 14.3789 79.4872 14.016 79.2422 13.5533C78.9972 13.0815 78.8611 12.5462 78.8339 11.9473C78.8249 11.7931 78.8203 11.598 78.8203 11.3621C78.8203 11.1171 78.8249 10.9221 78.8339 10.7769C78.8611 10.169 78.9972 9.63369 79.2422 9.17096C79.4963 8.70823 79.8547 8.3453 80.3174 8.08218C80.7801 7.81906 81.3517 7.6875 82.0322 7.6875C82.7127 7.6875 83.2843 7.81906 83.747 8.08218C84.2098 8.3453 84.5636 8.70823 84.8086 9.17096C85.0626 9.63369 85.2033 10.169 85.2305 10.7769C85.2396 10.9221 85.2441 11.1171 85.2441 11.3621C85.2441 11.598 85.2396 11.7931 85.2305 11.9473C85.2033 12.5462 85.0672 13.0815 84.8222 13.5533C84.5772 14.016 84.2234 14.3789 83.7606 14.6421C83.2979 14.9052 82.7218 15.0367 82.0322 15.0367ZM82.0322 13.9888C82.5947 13.9888 83.0439 13.8119 83.3796 13.458C83.7153 13.0951 83.8967 12.5688 83.924 11.8793C83.933 11.7432 83.9376 11.5708 83.9376 11.3621C83.9376 11.1534 83.933 10.981 83.924 10.845C83.8967 10.1554 83.7153 9.63369 83.3796 9.27984C83.0439 8.91691 82.5947 8.73545 82.0322 8.73545C81.4697 8.73545 81.016 8.91691 80.6712 9.27984C80.3355 9.63369 80.1586 10.1554 80.1405 10.845C80.1314 10.981 80.1268 11.1534 80.1268 11.3621C80.1268 11.5708 80.1314 11.7432 80.1405 11.8793C80.1586 12.5688 80.3355 13.0951 80.6712 13.458C81.016 13.8119 81.4697 13.9888 82.0322 13.9888Z" fill="currentColor"/>
<path d="M71.1568 14.9047C71.066 14.9047 70.9889 14.8775 70.9254 14.823C70.871 14.7595 70.8438 14.6824 70.8438 14.5917V8.14066C70.8438 8.04993 70.871 7.97734 70.9254 7.92291C70.9889 7.85939 71.066 7.82764 71.1568 7.82764H71.81C71.9008 7.82764 71.9734 7.85939 72.0278 7.92291C72.0822 7.97734 72.1094 8.04993 72.1094 8.14066V10.7537H75.8249V8.14066C75.8249 8.04993 75.8521 7.97734 75.9066 7.92291C75.9701 7.85939 76.0472 7.82764 76.1379 7.82764H76.7776C76.8683 7.82764 76.9409 7.85939 76.9953 7.92291C77.0588 7.97734 77.0906 8.04993 77.0906 8.14066V14.5917C77.0906 14.6824 77.0588 14.7595 76.9953 14.823C76.9409 14.8775 76.8683 14.9047 76.7776 14.9047H76.1379C76.0472 14.9047 75.9701 14.8775 75.9066 14.823C75.8521 14.7595 75.8249 14.6824 75.8249 14.5917V11.8425H72.1094V14.5917C72.1094 14.6824 72.0822 14.7595 72.0278 14.823C71.9734 14.8775 71.9008 14.9047 71.81 14.9047H71.1568Z" fill="currentColor"/>
<path d="M65.3051 15.0367C64.8514 15.0367 64.4386 14.946 64.0666 14.7645C63.6946 14.5831 63.3952 14.3381 63.1684 14.0296C62.9415 13.7211 62.8281 13.3718 62.8281 12.9817C62.8281 12.3556 63.0822 11.8566 63.5903 11.4846C64.0984 11.1126 64.7607 10.8676 65.5773 10.7497L67.6051 10.4639V10.0692C67.6051 9.63369 67.4781 9.29344 67.2241 9.04847C66.9791 8.8035 66.5753 8.68101 66.0128 8.68101C65.6045 8.68101 65.2733 8.76267 65.0193 8.92598C64.7743 9.0893 64.6019 9.29798 64.5021 9.55203C64.4477 9.68813 64.3524 9.75617 64.2163 9.75617H63.6039C63.5041 9.75617 63.427 9.72895 63.3725 9.67452C63.3271 9.611 63.3045 9.53842 63.3045 9.45676C63.3045 9.32066 63.3544 9.15281 63.4542 8.9532C63.563 8.75359 63.7264 8.55852 63.9441 8.36798C64.1619 8.17745 64.4386 8.01867 64.7743 7.89165C65.1191 7.75555 65.5365 7.6875 66.0264 7.6875C66.5708 7.6875 67.029 7.76009 67.401 7.90526C67.773 8.04135 68.0633 8.22735 68.272 8.46325C68.4898 8.69915 68.644 8.96681 68.7347 9.26622C68.8345 9.56564 68.8844 9.86959 68.8844 10.1781V14.5876C68.8844 14.6783 68.8527 14.7555 68.7892 14.819C68.7347 14.8734 68.6621 14.9006 68.5714 14.9006H67.9454C67.8456 14.9006 67.7684 14.8734 67.714 14.819C67.6596 14.7555 67.6323 14.6783 67.6323 14.5876V14.0024C67.5144 14.1657 67.3556 14.329 67.156 14.4923C66.9564 14.6466 66.7069 14.7782 66.4075 14.887C66.1081 14.9868 65.7406 15.0367 65.3051 15.0367ZM65.5909 14.016C65.9629 14.016 66.3031 13.9389 66.6116 13.7846C66.9201 13.6213 67.1605 13.3718 67.3329 13.0361C67.5144 12.7004 67.6051 12.2785 67.6051 11.7704V11.3893L66.0264 11.6207C65.3822 11.7114 64.8968 11.8657 64.5702 12.0834C64.2435 12.2921 64.0802 12.5598 64.0802 12.8864C64.0802 13.1405 64.1528 13.3537 64.298 13.5261C64.4522 13.6894 64.6428 13.8119 64.8696 13.8935C65.1055 13.9752 65.3459 14.016 65.5909 14.016Z" fill="currentColor"/>
<path d="M55.1001 14.9047C55.0093 14.9047 54.9322 14.8729 54.8687 14.8094C54.8052 14.7459 54.7734 14.6688 54.7734 14.578V14.0745C54.7734 13.884 54.8687 13.7841 55.0592 13.7751C55.3405 13.766 55.5764 13.6344 55.7669 13.3804C55.9575 13.1173 56.1027 12.7044 56.2025 12.1419C56.3023 11.5703 56.3522 10.8172 56.3522 9.8827V8.14066C56.3522 8.04993 56.3794 7.97734 56.4338 7.92291C56.4973 7.85939 56.5745 7.82764 56.6652 7.82764H60.9931C61.0838 7.82764 61.1564 7.85939 61.2108 7.92291C61.2743 7.97734 61.3061 8.04993 61.3061 8.14066V14.5917C61.3061 14.6824 61.2743 14.7595 61.2108 14.823C61.1564 14.8775 61.0838 14.9047 60.9931 14.9047H60.3398C60.2491 14.9047 60.1765 14.8775 60.122 14.823C60.0676 14.7595 60.0404 14.6824 60.0404 14.5917V8.91641H57.5906V10.0324C57.5906 10.9034 57.5407 11.652 57.4409 12.278C57.3411 12.895 57.1869 13.3985 56.9782 13.7887C56.7786 14.1698 56.52 14.451 56.2025 14.6325C55.894 14.814 55.5265 14.9047 55.1001 14.9047Z" fill="currentColor"/>
<path d="M50.6418 15.0367C49.7073 15.0367 48.9633 14.7509 48.4098 14.1793C47.8564 13.5986 47.5524 12.8093 47.498 11.8112C47.4889 11.6933 47.4844 11.5436 47.4844 11.3621C47.4844 11.1716 47.4889 11.0173 47.498 10.8994C47.5343 10.2552 47.684 9.69266 47.9471 9.21179C48.2102 8.72184 48.5686 8.3453 49.0223 8.08218C49.485 7.81906 50.0249 7.6875 50.6418 7.6875C51.3314 7.6875 51.9075 7.83267 52.3703 8.12301C52.8421 8.41335 53.2004 8.82618 53.4454 9.36149C53.6904 9.89681 53.8129 10.5229 53.8129 11.2396V11.471C53.8129 11.5708 53.7811 11.6479 53.7176 11.7024C53.6632 11.7568 53.5906 11.784 53.4999 11.784H48.7909C48.7909 11.7931 48.7909 11.8112 48.7909 11.8385C48.7909 11.8657 48.7909 11.8884 48.7909 11.9065C48.8091 12.2785 48.8907 12.6278 49.0359 12.9545C49.181 13.272 49.3897 13.5306 49.6619 13.7302C49.9341 13.9298 50.2608 14.0296 50.6418 14.0296C50.9685 14.0296 51.2407 13.9797 51.4584 13.8799C51.6762 13.7801 51.8531 13.6712 51.9892 13.5533C52.1253 13.4263 52.216 13.331 52.2614 13.2675C52.343 13.1495 52.4065 13.0815 52.4519 13.0633C52.4973 13.0361 52.5699 13.0225 52.6697 13.0225H53.3229C53.4137 13.0225 53.4863 13.0497 53.5407 13.1042C53.6042 13.1495 53.6314 13.2176 53.6223 13.3083C53.6133 13.4444 53.5407 13.6123 53.4046 13.8119C53.2685 14.0024 53.0734 14.1929 52.8194 14.3835C52.5653 14.574 52.2568 14.7328 51.8939 14.8598C51.531 14.9778 51.1136 15.0367 50.6418 15.0367ZM48.7909 10.845H52.52V10.8041C52.52 10.3958 52.4428 10.0329 52.2886 9.71535C52.1434 9.39779 51.9302 9.14827 51.6489 8.96681C51.3677 8.77628 51.032 8.68101 50.6418 8.68101C50.2517 8.68101 49.916 8.77628 49.6347 8.96681C49.3625 9.14827 49.1538 9.39779 49.0087 9.71535C48.8635 10.0329 48.7909 10.3958 48.7909 10.8041V10.845Z" fill="currentColor"/>
<path d="M39.2505 16.4017C39.1598 16.4017 39.0827 16.37 39.0192 16.3065C38.9647 16.252 38.9375 16.1795 38.9375 16.0887V14.1289C38.9375 14.0382 38.9647 13.9656 39.0192 13.9112C39.0827 13.8477 39.1598 13.8159 39.2505 13.8159H39.3186C39.5635 13.8068 39.7722 13.6707 39.9446 13.4076C40.117 13.1354 40.2486 12.7135 40.3393 12.1419C40.43 11.5612 40.4754 10.8082 40.4754 9.8827V8.14066C40.4754 8.04993 40.5026 7.97734 40.5571 7.92291C40.6206 7.85939 40.6977 7.82764 40.7884 7.82764H45.1299C45.2206 7.82764 45.2932 7.85939 45.3477 7.92291C45.4112 7.97734 45.4429 8.04993 45.4429 8.14066V13.7887H46.0826C46.1824 13.7887 46.2595 13.8204 46.314 13.884C46.3684 13.9384 46.3956 14.011 46.3956 14.1017V16.0887C46.3956 16.1795 46.3684 16.252 46.314 16.3065C46.2595 16.37 46.1824 16.4017 46.0826 16.4017H45.4429C45.3522 16.4017 45.2751 16.37 45.2116 16.3065C45.1571 16.252 45.1299 16.1795 45.1299 16.0887V14.9047H40.2032V16.0887C40.2032 16.1795 40.1714 16.252 40.1079 16.3065C40.0535 16.37 39.9809 16.4017 39.8902 16.4017H39.2505ZM40.9381 13.8159L44.1772 13.7887V8.91641H41.7139V10.0324C41.7139 10.9942 41.6458 11.7835 41.5097 12.4005C41.3736 13.0084 41.1831 13.4802 40.9381 13.8159Z" fill="currentColor"/>
<path d="M34.2034 15.0426C33.3686 15.0426 32.6745 14.8884 32.1211 14.5799C31.5767 14.2714 31.1639 13.8404 30.8826 13.287C30.6013 12.7335 30.4471 12.0893 30.4199 11.3544C30.4108 10.9824 30.4062 10.5832 30.4062 10.1568C30.4062 9.73032 30.4108 9.32203 30.4199 8.93188C30.4471 8.19696 30.6013 7.55276 30.8826 6.9993C31.1639 6.44584 31.5767 6.01487 32.1211 5.70638C32.6745 5.3979 33.3686 5.24365 34.2034 5.24365C34.8294 5.24365 35.3783 5.32985 35.8501 5.50224C36.3219 5.67463 36.7121 5.90599 37.0206 6.19633C37.3381 6.48667 37.5786 6.8133 37.7419 7.17623C37.9052 7.53008 37.9959 7.89754 38.0141 8.27862C38.0231 8.36027 37.9959 8.42832 37.9324 8.48276C37.878 8.5372 37.8099 8.56442 37.7283 8.56442H36.9797C36.8981 8.56442 36.8255 8.54174 36.762 8.49637C36.7075 8.45101 36.6667 8.36935 36.6395 8.2514C36.4852 7.54369 36.1949 7.05828 35.7685 6.79516C35.3511 6.53204 34.8249 6.40048 34.1897 6.40048C33.4639 6.40048 32.8878 6.60916 32.4613 7.02652C32.0349 7.43481 31.8035 8.09262 31.7672 8.99993C31.74 9.74393 31.74 10.5061 31.7672 11.2864C31.8035 12.1937 32.0349 12.856 32.4613 13.2734C32.8878 13.6817 33.4639 13.8858 34.1897 13.8858C34.8249 13.8858 35.3511 13.7543 35.7685 13.4911C36.1949 13.228 36.4852 12.7426 36.6395 12.0349C36.6667 11.9169 36.7075 11.8353 36.762 11.7899C36.8255 11.7446 36.8981 11.7219 36.9797 11.7219H37.7283C37.8099 11.7219 37.878 11.7491 37.9324 11.8035C37.9959 11.858 38.0231 11.926 38.0141 12.0077C37.9959 12.3887 37.9052 12.7607 37.7419 13.1237C37.5786 13.4775 37.3381 13.7996 37.0206 14.09C36.7121 14.3803 36.3219 14.6117 35.8501 14.7841C35.3783 14.9564 34.8294 15.0426 34.2034 15.0426Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.54763 0.0219046C6.0113 -0.271892 4.26146 2.45258 2.63386 4.42198C1.20548 6.15032 0.14193 8.10248 0.0183964 10.3423C-0.113033 12.7252 0.319225 15.2068 1.94263 16.9545C3.61482 18.7547 6.1197 19.8922 8.54763 19.5244C10.774 19.1872 11.7591 16.7423 13.4166 15.2166C15.2164 13.56 18.5695 12.7894 18.523 10.3423C18.4764 7.89135 14.9376 7.41159 13.2348 5.64994C11.4711 3.8252 11.0672 0.313757 8.54763 0.0219046Z" fill="#7E2AEA"/>
<circle cx="14.8605" cy="18.5983" r="1.39953" fill="#7E2AEA"/>
<circle cx="13.4628" cy="3.06831" r="0.533154" fill="#7E2AEA"/>
<path d="M21.9866 9.76883C21.8311 8.25747 21.1201 6.85765 19.9913 5.84071C18.8625 4.82376 17.3963 4.26212 15.877 4.26466C15.6613 4.26475 15.4457 4.27604 15.2312 4.29846C13.7205 4.45709 12.3221 5.16948 11.3056 6.29824C10.2891 7.42699 9.72661 8.89216 9.72656 10.4111V10.4111V19.1918H12.3608V15.46C13.3907 16.1766 14.6158 16.5597 15.8704 16.5576C16.0861 16.5575 16.3017 16.5463 16.5162 16.5238C17.319 16.4394 18.0973 16.1978 18.8066 15.8126C19.516 15.4275 20.1426 14.9064 20.6505 14.2791C21.1585 13.6518 21.538 12.9306 21.7672 12.1566C21.9964 11.3827 22.071 10.5712 21.9866 9.76839V9.76883ZM18.6034 12.6226C18.314 12.982 17.9562 13.2805 17.5507 13.5007C17.1452 13.7209 16.7 13.8585 16.241 13.9054C16.1179 13.9182 15.9942 13.9246 15.8704 13.9247C15.0738 13.924 14.301 13.6525 13.679 13.1547C13.057 12.6569 12.6228 11.9624 12.4475 11.1853C12.2722 10.4082 12.3663 9.59455 12.7143 8.87796C13.0624 8.16136 13.6437 7.58437 14.3629 7.2417C15.0821 6.89903 15.8964 6.81104 16.6722 6.99216C17.448 7.17329 18.1391 7.61277 18.6323 8.23848C19.1254 8.86418 19.3911 9.63893 19.3858 10.4356C19.3806 11.2322 19.1047 12.0034 18.6034 12.6226Z" fill="currentColor"/>
<path
d="M162.063 14.9047C161.972 14.9047 161.895 14.8775 161.832 14.823C161.777 14.7595 161.75 14.6824 161.75 14.5917V14.1153C161.75 14.0337 161.768 13.9565 161.804 13.884C161.85 13.8114 161.895 13.7433 161.941 13.6798L165.697 8.9028H162.186C162.095 8.9028 162.018 8.87558 161.954 8.82115C161.9 8.76671 161.872 8.68958 161.872 8.58978V8.14066C161.872 8.04993 161.9 7.97734 161.954 7.92291C162.018 7.85939 162.095 7.82764 162.186 7.82764H167.031C167.121 7.82764 167.194 7.85939 167.248 7.92291C167.312 7.97734 167.344 8.04993 167.344 8.14066V8.64422C167.344 8.72588 167.321 8.79846 167.276 8.86197C167.239 8.92549 167.198 8.99353 167.153 9.06612L163.438 13.8295H167.303C167.393 13.8295 167.466 13.8567 167.521 13.9112C167.584 13.9656 167.616 14.0427 167.616 14.1425V14.5917C167.616 14.6824 167.584 14.7595 167.521 14.823C167.466 14.8775 167.393 14.9047 167.303 14.9047H162.063Z"
fill="currentColor"
/>
<path
d="M159.148 14.9037C159.058 14.9037 158.981 14.8764 158.917 14.822C158.863 14.7585 158.835 14.6814 158.835 14.5906V8.13964C158.835 8.04891 158.863 7.97633 158.917 7.92189C158.981 7.85837 159.058 7.82662 159.148 7.82662H159.802C159.892 7.82662 159.965 7.85837 160.019 7.92189C160.074 7.97633 160.101 8.04891 160.101 8.13964V14.5906C160.101 14.6814 160.074 14.7585 160.019 14.822C159.965 14.8764 159.892 14.9037 159.802 14.9037H159.148ZM159.04 6.50648C158.949 6.50648 158.872 6.47926 158.808 6.42482C158.754 6.36131 158.727 6.28419 158.727 6.19345V5.45853C158.727 5.3678 158.754 5.29521 158.808 5.24078C158.872 5.17726 158.949 5.14551 159.04 5.14551H159.897C159.988 5.14551 160.06 5.17726 160.115 5.24078C160.178 5.29521 160.21 5.3678 160.21 5.45853V6.19345C160.21 6.28419 160.178 6.36131 160.115 6.42482C160.06 6.47926 159.988 6.50648 159.897 6.50648H159.04Z"
fill="currentColor"
/>
<path
d="M153.294 15.0408C152.714 15.0408 152.219 14.9138 151.811 14.6597C151.412 14.3966 151.108 14.0337 150.899 13.5709C150.69 13.1082 150.586 12.5774 150.586 11.9786V8.14066C150.586 8.04993 150.613 7.97734 150.668 7.92291C150.731 7.85939 150.808 7.82764 150.899 7.82764H151.579C151.67 7.82764 151.743 7.85939 151.797 7.92291C151.861 7.97734 151.892 8.04993 151.892 8.14066V11.9105C151.892 13.2624 152.482 13.9384 153.662 13.9384C154.224 13.9384 154.673 13.7615 155.009 13.4076C155.354 13.0447 155.526 12.5457 155.526 11.9105V8.14066C155.526 8.04993 155.553 7.97734 155.608 7.92291C155.671 7.85939 155.749 7.82764 155.839 7.82764H156.506C156.606 7.82764 156.683 7.85939 156.738 7.92291C156.792 7.97734 156.819 8.04993 156.819 8.14066V14.5917C156.819 14.6824 156.792 14.7595 156.738 14.823C156.683 14.8775 156.606 14.9047 156.506 14.9047H155.88C155.789 14.9047 155.712 14.8775 155.649 14.823C155.594 14.7595 155.567 14.6824 155.567 14.5917V13.9928C155.322 14.3104 155.023 14.5644 154.669 14.755C154.324 14.9455 153.866 15.0408 153.294 15.0408Z"
fill="currentColor"
/>
<path
d="M147.755 15.7912C147.637 15.7912 147.542 15.7503 147.47 15.6687C147.397 15.5961 147.338 15.5326 147.293 15.4781L146.735 14.716C146.245 14.9338 145.678 15.0426 145.033 15.0426C144.253 15.0426 143.586 14.9111 143.033 14.648C142.488 14.3758 142.062 13.9629 141.754 13.4095C141.454 12.8469 141.291 12.1438 141.264 11.3C141.255 10.9008 141.25 10.5197 141.25 10.1568C141.25 9.78476 141.255 9.39915 141.264 8.99993C141.291 8.1652 141.463 7.47111 141.781 6.91765C142.098 6.35511 142.534 5.93775 143.087 5.66555C143.65 5.38429 144.299 5.24365 145.033 5.24365C145.777 5.24365 146.426 5.38429 146.98 5.66555C147.542 5.93775 147.982 6.35511 148.3 6.91765C148.617 7.47111 148.785 8.1652 148.803 8.99993C148.822 9.39915 148.831 9.78476 148.831 10.1568C148.831 10.5197 148.822 10.9008 148.803 11.3C148.758 12.5521 148.431 13.4821 147.823 14.09L148.722 15.3693C148.731 15.3783 148.74 15.392 148.749 15.4101C148.758 15.4373 148.763 15.4736 148.763 15.519C148.772 15.5916 148.749 15.6551 148.695 15.7095C148.64 15.764 148.572 15.7912 148.49 15.7912H147.755ZM145.033 13.8858C145.723 13.8858 146.29 13.6771 146.735 13.2598C147.179 12.8424 147.42 12.1665 147.456 11.2319C147.474 10.8236 147.483 10.4607 147.483 10.1431C147.483 9.81651 147.474 9.45359 147.456 9.05437C147.438 8.42832 147.32 7.92023 147.102 7.53008C146.893 7.13994 146.612 6.85413 146.258 6.67267C145.905 6.49121 145.496 6.40048 145.033 6.40048C144.589 6.40048 144.185 6.49121 143.822 6.67267C143.468 6.85413 143.187 7.13994 142.978 7.53008C142.77 7.92023 142.652 8.42832 142.625 9.05437C142.616 9.45359 142.611 9.81651 142.611 10.1431C142.611 10.4607 142.616 10.8236 142.625 11.2319C142.661 12.1665 142.901 12.8424 143.346 13.2598C143.79 13.6771 144.353 13.8858 145.033 13.8858Z"
fill="currentColor"
/>
<path
d="M135.907 15.0367C135.453 15.0367 135.04 14.946 134.668 14.7645C134.296 14.5831 133.997 14.3381 133.77 14.0296C133.543 13.7211 133.43 13.3718 133.43 12.9817C133.43 12.3556 133.684 11.8566 134.192 11.4846C134.7 11.1126 135.362 10.8676 136.179 10.7497L138.207 10.4639V10.0692C138.207 9.63369 138.08 9.29344 137.826 9.04847C137.581 8.8035 137.177 8.68101 136.614 8.68101C136.206 8.68101 135.875 8.76267 135.621 8.92598C135.376 9.0893 135.203 9.29798 135.104 9.55203C135.049 9.68813 134.954 9.75617 134.818 9.75617H134.205C134.106 9.75617 134.029 9.72895 133.974 9.67452C133.929 9.611 133.906 9.53842 133.906 9.45676C133.906 9.32066 133.956 9.15281 134.056 8.9532C134.165 8.75359 134.328 8.55852 134.546 8.36798C134.763 8.17745 135.04 8.01867 135.376 7.89165C135.721 7.75555 136.138 7.6875 136.628 7.6875C137.172 7.6875 137.631 7.76009 138.003 7.90526C138.375 8.04135 138.665 8.22735 138.874 8.46325C139.091 8.69915 139.246 8.96681 139.336 9.26622C139.436 9.56564 139.486 9.86959 139.486 10.1781V14.5876C139.486 14.6783 139.454 14.7555 139.391 14.819C139.336 14.8734 139.264 14.9006 139.173 14.9006H138.547C138.447 14.9006 138.37 14.8734 138.316 14.819C138.261 14.7555 138.234 14.6783 138.234 14.5876V14.0024C138.116 14.1657 137.957 14.329 137.758 14.4923C137.558 14.6466 137.308 14.7782 137.009 14.887C136.71 14.9868 136.342 15.0367 135.907 15.0367ZM136.192 14.016C136.564 14.016 136.905 13.9389 137.213 13.7846C137.522 13.6213 137.762 13.3718 137.934 13.0361C138.116 12.7004 138.207 12.2785 138.207 11.7704V11.3893L136.628 11.6207C135.984 11.7114 135.498 11.8657 135.172 12.0834C134.845 12.2921 134.682 12.5598 134.682 12.8864C134.682 13.1405 134.754 13.3537 134.9 13.5261C135.054 13.6894 135.244 13.8119 135.471 13.8935C135.707 13.9752 135.947 14.016 136.192 14.016Z"
fill="currentColor"
/>
<path
d="M125.993 14.9006C125.902 14.9006 125.825 14.8734 125.761 14.819C125.707 14.7555 125.68 14.6783 125.68 14.5876V8.13662C125.68 8.04589 125.707 7.9733 125.761 7.91887C125.825 7.85535 125.902 7.8236 125.993 7.8236H126.632C126.723 7.8236 126.796 7.85535 126.85 7.91887C126.914 7.9733 126.945 8.04589 126.945 8.13662V8.73545C127.181 8.42696 127.476 8.17745 127.83 7.98691C128.193 7.7873 128.656 7.6875 129.218 7.6875C129.808 7.6875 130.307 7.81906 130.715 8.08218C131.133 8.33623 131.446 8.69462 131.654 9.15735C131.863 9.611 131.967 10.1418 131.967 10.7497V14.5876C131.967 14.6783 131.94 14.7555 131.886 14.819C131.831 14.8734 131.759 14.9006 131.668 14.9006H130.987C130.897 14.9006 130.82 14.8734 130.756 14.819C130.702 14.7555 130.674 14.6783 130.674 14.5876V10.8177C130.674 10.1826 130.52 9.68813 130.212 9.33427C129.903 8.97135 129.45 8.78989 128.851 8.78989C128.288 8.78989 127.835 8.97135 127.49 9.33427C127.154 9.68813 126.986 10.1826 126.986 10.8177V14.5876C126.986 14.6783 126.954 14.7555 126.891 14.819C126.837 14.8734 126.764 14.9006 126.673 14.9006H125.993Z"
fill="currentColor"
/>
<path
d="M120.884 15.0367C119.949 15.0367 119.205 14.7509 118.652 14.1793C118.099 13.5986 117.795 12.8093 117.74 11.8112C117.731 11.6933 117.727 11.5436 117.727 11.3621C117.727 11.1716 117.731 11.0173 117.74 10.8994C117.776 10.2552 117.926 9.69266 118.189 9.21179C118.452 8.72184 118.811 8.3453 119.264 8.08218C119.727 7.81906 120.267 7.6875 120.884 7.6875C121.574 7.6875 122.15 7.83267 122.612 8.12301C123.084 8.41335 123.443 8.82618 123.688 9.36149C123.933 9.89681 124.055 10.5229 124.055 11.2396V11.471C124.055 11.5708 124.023 11.6479 123.96 11.7024C123.905 11.7568 123.833 11.784 123.742 11.784H119.033C119.033 11.7931 119.033 11.8112 119.033 11.8385C119.033 11.8657 119.033 11.8884 119.033 11.9065C119.051 12.2785 119.133 12.6278 119.278 12.9545C119.423 13.272 119.632 13.5306 119.904 13.7302C120.176 13.9298 120.503 14.0296 120.884 14.0296C121.211 14.0296 121.483 13.9797 121.701 13.8799C121.918 13.7801 122.095 13.6712 122.231 13.5533C122.367 13.4263 122.458 13.331 122.504 13.2675C122.585 13.1495 122.649 13.0815 122.694 13.0633C122.739 13.0361 122.812 13.0225 122.912 13.0225H123.565C123.656 13.0225 123.728 13.0497 123.783 13.1042C123.846 13.1495 123.874 13.2176 123.865 13.3083C123.855 13.4444 123.783 13.6123 123.647 13.8119C123.511 14.0024 123.316 14.1929 123.062 14.3835C122.808 14.574 122.499 14.7328 122.136 14.8598C121.773 14.9778 121.356 15.0367 120.884 15.0367ZM119.033 10.845H122.762V10.8041C122.762 10.3958 122.685 10.0329 122.531 9.71535C122.386 9.39779 122.172 9.14827 121.891 8.96681C121.61 8.77628 121.274 8.68101 120.884 8.68101C120.494 8.68101 120.158 8.77628 119.877 8.96681C119.605 9.14827 119.396 9.39779 119.251 9.71535C119.106 10.0329 119.033 10.3958 119.033 10.8041V10.845Z"
fill="currentColor"
/>
<path
d="M109.915 14.9033C109.815 14.9033 109.738 14.876 109.683 14.8216C109.629 14.7581 109.602 14.681 109.602 14.5902V5.7031C109.602 5.60329 109.629 5.52617 109.683 5.47173C109.738 5.40822 109.815 5.37646 109.915 5.37646H113.371C114.043 5.37646 114.624 5.48534 115.113 5.7031C115.613 5.92085 115.998 6.24749 116.27 6.683C116.543 7.10943 116.679 7.64475 116.679 8.28894C116.679 8.93313 116.543 9.46845 116.27 9.89489C115.998 10.3213 115.613 10.6434 115.113 10.8612C114.624 11.0789 114.043 11.1878 113.371 11.1878H110.963V14.5902C110.963 14.681 110.931 14.7581 110.867 14.8216C110.813 14.876 110.736 14.9033 110.636 14.9033H109.915ZM110.949 10.0446H113.303C113.975 10.0446 114.478 9.89489 114.814 9.59547C115.15 9.29606 115.318 8.86055 115.318 8.28894C115.318 7.72641 115.154 7.2909 114.828 6.98241C114.501 6.67392 113.993 6.51968 113.303 6.51968H110.949V10.0446Z"
fill="currentColor"
/>
<path
d="M100.555 15.0367C100.101 15.0367 99.6886 14.946 99.3166 14.7645C98.9446 14.5831 98.6452 14.3381 98.4184 14.0296C98.1915 13.7211 98.0781 13.3718 98.0781 12.9817C98.0781 12.3556 98.3322 11.8566 98.8403 11.4846C99.3484 11.1126 100.011 10.8676 100.827 10.7497L102.855 10.4639V10.0692C102.855 9.63369 102.728 9.29344 102.474 9.04847C102.229 8.8035 101.825 8.68101 101.263 8.68101C100.855 8.68101 100.523 8.76267 100.269 8.92598C100.024 9.0893 99.8519 9.29798 99.7521 9.55203C99.6977 9.68813 99.6024 9.75617 99.4663 9.75617H98.8539C98.7541 9.75617 98.6769 9.72895 98.6225 9.67452C98.5771 9.611 98.5545 9.53842 98.5545 9.45676C98.5545 9.32066 98.6044 9.15281 98.7042 8.9532C98.813 8.75359 98.9764 8.55852 99.1941 8.36798C99.4119 8.17745 99.6886 8.01867 100.024 7.89165C100.369 7.75555 100.786 7.6875 101.276 7.6875C101.821 7.6875 102.279 7.76009 102.651 7.90526C103.023 8.04135 103.313 8.22735 103.522 8.46325C103.74 8.69915 103.894 8.96681 103.985 9.26622C104.085 9.56564 104.134 9.86959 104.134 10.1781V14.5876C104.134 14.6783 104.103 14.7555 104.039 14.819C103.985 14.8734 103.912 14.9006 103.821 14.9006H103.195C103.096 14.9006 103.018 14.8734 102.964 14.819C102.91 14.7555 102.882 14.6783 102.882 14.5876V14.0024C102.764 14.1657 102.606 14.329 102.406 14.4923C102.206 14.6466 101.957 14.7782 101.657 14.887C101.358 14.9868 100.991 15.0367 100.555 15.0367ZM100.841 14.016C101.213 14.016 101.553 13.9389 101.862 13.7846C102.17 13.6213 102.411 13.3718 102.583 13.0361C102.764 12.7004 102.855 12.2785 102.855 11.7704V11.3893L101.276 11.6207C100.632 11.7114 100.147 11.8657 99.8202 12.0834C99.4935 12.2921 99.3302 12.5598 99.3302 12.8864C99.3302 13.1405 99.4028 13.3537 99.548 13.5261C99.7022 13.6894 99.8928 13.8119 100.12 13.8935C100.355 13.9752 100.596 14.016 100.841 14.016Z"
fill="currentColor"
/>
<path
d="M90.6021 14.9047C90.5114 14.9047 90.4342 14.8775 90.3707 14.823C90.3163 14.7595 90.2891 14.6824 90.2891 14.5917V8.14066C90.2891 8.04993 90.3163 7.97734 90.3707 7.92291C90.4342 7.85939 90.5114 7.82764 90.6021 7.82764H91.2554C91.3461 7.82764 91.4187 7.85939 91.4731 7.92291C91.5275 7.97734 91.5548 8.04993 91.5548 8.14066V10.7537H95.2702V8.14066C95.2702 8.04993 95.2974 7.97734 95.3519 7.92291C95.4154 7.85939 95.4925 7.82764 95.5832 7.82764H96.2229C96.3136 7.82764 96.3862 7.85939 96.4406 7.92291C96.5042 7.97734 96.5359 8.04993 96.5359 8.14066V14.5917C96.5359 14.6824 96.5042 14.7595 96.4406 14.823C96.3862 14.8775 96.3136 14.9047 96.2229 14.9047H95.5832C95.4925 14.9047 95.4154 14.8775 95.3519 14.823C95.2974 14.7595 95.2702 14.6824 95.2702 14.5917V11.8425H91.5548V14.5917C91.5548 14.6824 91.5275 14.7595 91.4731 14.823C91.4187 14.8775 91.3461 14.9047 91.2554 14.9047H90.6021Z"
fill="currentColor"
/>
<path
d="M82.0322 15.0367C81.3426 15.0367 80.7665 14.9052 80.3038 14.6421C79.841 14.3789 79.4872 14.016 79.2422 13.5533C78.9972 13.0815 78.8611 12.5462 78.8339 11.9473C78.8249 11.7931 78.8203 11.598 78.8203 11.3621C78.8203 11.1171 78.8249 10.9221 78.8339 10.7769C78.8611 10.169 78.9972 9.63369 79.2422 9.17096C79.4963 8.70823 79.8547 8.3453 80.3174 8.08218C80.7801 7.81906 81.3517 7.6875 82.0322 7.6875C82.7127 7.6875 83.2843 7.81906 83.747 8.08218C84.2098 8.3453 84.5636 8.70823 84.8086 9.17096C85.0626 9.63369 85.2033 10.169 85.2305 10.7769C85.2396 10.9221 85.2441 11.1171 85.2441 11.3621C85.2441 11.598 85.2396 11.7931 85.2305 11.9473C85.2033 12.5462 85.0672 13.0815 84.8222 13.5533C84.5772 14.016 84.2234 14.3789 83.7606 14.6421C83.2979 14.9052 82.7218 15.0367 82.0322 15.0367ZM82.0322 13.9888C82.5947 13.9888 83.0439 13.8119 83.3796 13.458C83.7153 13.0951 83.8967 12.5688 83.924 11.8793C83.933 11.7432 83.9376 11.5708 83.9376 11.3621C83.9376 11.1534 83.933 10.981 83.924 10.845C83.8967 10.1554 83.7153 9.63369 83.3796 9.27984C83.0439 8.91691 82.5947 8.73545 82.0322 8.73545C81.4697 8.73545 81.016 8.91691 80.6712 9.27984C80.3355 9.63369 80.1586 10.1554 80.1405 10.845C80.1314 10.981 80.1268 11.1534 80.1268 11.3621C80.1268 11.5708 80.1314 11.7432 80.1405 11.8793C80.1586 12.5688 80.3355 13.0951 80.6712 13.458C81.016 13.8119 81.4697 13.9888 82.0322 13.9888Z"
fill="currentColor"
/>
<path
d="M71.1568 14.9047C71.066 14.9047 70.9889 14.8775 70.9254 14.823C70.871 14.7595 70.8438 14.6824 70.8438 14.5917V8.14066C70.8438 8.04993 70.871 7.97734 70.9254 7.92291C70.9889 7.85939 71.066 7.82764 71.1568 7.82764H71.81C71.9008 7.82764 71.9734 7.85939 72.0278 7.92291C72.0822 7.97734 72.1094 8.04993 72.1094 8.14066V10.7537H75.8249V8.14066C75.8249 8.04993 75.8521 7.97734 75.9066 7.92291C75.9701 7.85939 76.0472 7.82764 76.1379 7.82764H76.7776C76.8683 7.82764 76.9409 7.85939 76.9953 7.92291C77.0588 7.97734 77.0906 8.04993 77.0906 8.14066V14.5917C77.0906 14.6824 77.0588 14.7595 76.9953 14.823C76.9409 14.8775 76.8683 14.9047 76.7776 14.9047H76.1379C76.0472 14.9047 75.9701 14.8775 75.9066 14.823C75.8521 14.7595 75.8249 14.6824 75.8249 14.5917V11.8425H72.1094V14.5917C72.1094 14.6824 72.0822 14.7595 72.0278 14.823C71.9734 14.8775 71.9008 14.9047 71.81 14.9047H71.1568Z"
fill="currentColor"
/>
<path
d="M65.3051 15.0367C64.8514 15.0367 64.4386 14.946 64.0666 14.7645C63.6946 14.5831 63.3952 14.3381 63.1684 14.0296C62.9415 13.7211 62.8281 13.3718 62.8281 12.9817C62.8281 12.3556 63.0822 11.8566 63.5903 11.4846C64.0984 11.1126 64.7607 10.8676 65.5773 10.7497L67.6051 10.4639V10.0692C67.6051 9.63369 67.4781 9.29344 67.2241 9.04847C66.9791 8.8035 66.5753 8.68101 66.0128 8.68101C65.6045 8.68101 65.2733 8.76267 65.0193 8.92598C64.7743 9.0893 64.6019 9.29798 64.5021 9.55203C64.4477 9.68813 64.3524 9.75617 64.2163 9.75617H63.6039C63.5041 9.75617 63.427 9.72895 63.3725 9.67452C63.3271 9.611 63.3045 9.53842 63.3045 9.45676C63.3045 9.32066 63.3544 9.15281 63.4542 8.9532C63.563 8.75359 63.7264 8.55852 63.9441 8.36798C64.1619 8.17745 64.4386 8.01867 64.7743 7.89165C65.1191 7.75555 65.5365 7.6875 66.0264 7.6875C66.5708 7.6875 67.029 7.76009 67.401 7.90526C67.773 8.04135 68.0633 8.22735 68.272 8.46325C68.4898 8.69915 68.644 8.96681 68.7347 9.26622C68.8345 9.56564 68.8844 9.86959 68.8844 10.1781V14.5876C68.8844 14.6783 68.8527 14.7555 68.7892 14.819C68.7347 14.8734 68.6621 14.9006 68.5714 14.9006H67.9454C67.8456 14.9006 67.7684 14.8734 67.714 14.819C67.6596 14.7555 67.6323 14.6783 67.6323 14.5876V14.0024C67.5144 14.1657 67.3556 14.329 67.156 14.4923C66.9564 14.6466 66.7069 14.7782 66.4075 14.887C66.1081 14.9868 65.7406 15.0367 65.3051 15.0367ZM65.5909 14.016C65.9629 14.016 66.3031 13.9389 66.6116 13.7846C66.9201 13.6213 67.1605 13.3718 67.3329 13.0361C67.5144 12.7004 67.6051 12.2785 67.6051 11.7704V11.3893L66.0264 11.6207C65.3822 11.7114 64.8968 11.8657 64.5702 12.0834C64.2435 12.2921 64.0802 12.5598 64.0802 12.8864C64.0802 13.1405 64.1528 13.3537 64.298 13.5261C64.4522 13.6894 64.6428 13.8119 64.8696 13.8935C65.1055 13.9752 65.3459 14.016 65.5909 14.016Z"
fill="currentColor"
/>
<path
d="M55.1001 14.9047C55.0093 14.9047 54.9322 14.8729 54.8687 14.8094C54.8052 14.7459 54.7734 14.6688 54.7734 14.578V14.0745C54.7734 13.884 54.8687 13.7841 55.0592 13.7751C55.3405 13.766 55.5764 13.6344 55.7669 13.3804C55.9575 13.1173 56.1027 12.7044 56.2025 12.1419C56.3023 11.5703 56.3522 10.8172 56.3522 9.8827V8.14066C56.3522 8.04993 56.3794 7.97734 56.4338 7.92291C56.4973 7.85939 56.5745 7.82764 56.6652 7.82764H60.9931C61.0838 7.82764 61.1564 7.85939 61.2108 7.92291C61.2743 7.97734 61.3061 8.04993 61.3061 8.14066V14.5917C61.3061 14.6824 61.2743 14.7595 61.2108 14.823C61.1564 14.8775 61.0838 14.9047 60.9931 14.9047H60.3398C60.2491 14.9047 60.1765 14.8775 60.122 14.823C60.0676 14.7595 60.0404 14.6824 60.0404 14.5917V8.91641H57.5906V10.0324C57.5906 10.9034 57.5407 11.652 57.4409 12.278C57.3411 12.895 57.1869 13.3985 56.9782 13.7887C56.7786 14.1698 56.52 14.451 56.2025 14.6325C55.894 14.814 55.5265 14.9047 55.1001 14.9047Z"
fill="currentColor"
/>
<path
d="M50.6418 15.0367C49.7073 15.0367 48.9633 14.7509 48.4098 14.1793C47.8564 13.5986 47.5524 12.8093 47.498 11.8112C47.4889 11.6933 47.4844 11.5436 47.4844 11.3621C47.4844 11.1716 47.4889 11.0173 47.498 10.8994C47.5343 10.2552 47.684 9.69266 47.9471 9.21179C48.2102 8.72184 48.5686 8.3453 49.0223 8.08218C49.485 7.81906 50.0249 7.6875 50.6418 7.6875C51.3314 7.6875 51.9075 7.83267 52.3703 8.12301C52.8421 8.41335 53.2004 8.82618 53.4454 9.36149C53.6904 9.89681 53.8129 10.5229 53.8129 11.2396V11.471C53.8129 11.5708 53.7811 11.6479 53.7176 11.7024C53.6632 11.7568 53.5906 11.784 53.4999 11.784H48.7909C48.7909 11.7931 48.7909 11.8112 48.7909 11.8385C48.7909 11.8657 48.7909 11.8884 48.7909 11.9065C48.8091 12.2785 48.8907 12.6278 49.0359 12.9545C49.181 13.272 49.3897 13.5306 49.6619 13.7302C49.9341 13.9298 50.2608 14.0296 50.6418 14.0296C50.9685 14.0296 51.2407 13.9797 51.4584 13.8799C51.6762 13.7801 51.8531 13.6712 51.9892 13.5533C52.1253 13.4263 52.216 13.331 52.2614 13.2675C52.343 13.1495 52.4065 13.0815 52.4519 13.0633C52.4973 13.0361 52.5699 13.0225 52.6697 13.0225H53.3229C53.4137 13.0225 53.4863 13.0497 53.5407 13.1042C53.6042 13.1495 53.6314 13.2176 53.6223 13.3083C53.6133 13.4444 53.5407 13.6123 53.4046 13.8119C53.2685 14.0024 53.0734 14.1929 52.8194 14.3835C52.5653 14.574 52.2568 14.7328 51.8939 14.8598C51.531 14.9778 51.1136 15.0367 50.6418 15.0367ZM48.7909 10.845H52.52V10.8041C52.52 10.3958 52.4428 10.0329 52.2886 9.71535C52.1434 9.39779 51.9302 9.14827 51.6489 8.96681C51.3677 8.77628 51.032 8.68101 50.6418 8.68101C50.2517 8.68101 49.916 8.77628 49.6347 8.96681C49.3625 9.14827 49.1538 9.39779 49.0087 9.71535C48.8635 10.0329 48.7909 10.3958 48.7909 10.8041V10.845Z"
fill="currentColor"
/>
<path
d="M39.2505 16.4017C39.1598 16.4017 39.0827 16.37 39.0192 16.3065C38.9647 16.252 38.9375 16.1795 38.9375 16.0887V14.1289C38.9375 14.0382 38.9647 13.9656 39.0192 13.9112C39.0827 13.8477 39.1598 13.8159 39.2505 13.8159H39.3186C39.5635 13.8068 39.7722 13.6707 39.9446 13.4076C40.117 13.1354 40.2486 12.7135 40.3393 12.1419C40.43 11.5612 40.4754 10.8082 40.4754 9.8827V8.14066C40.4754 8.04993 40.5026 7.97734 40.5571 7.92291C40.6206 7.85939 40.6977 7.82764 40.7884 7.82764H45.1299C45.2206 7.82764 45.2932 7.85939 45.3477 7.92291C45.4112 7.97734 45.4429 8.04993 45.4429 8.14066V13.7887H46.0826C46.1824 13.7887 46.2595 13.8204 46.314 13.884C46.3684 13.9384 46.3956 14.011 46.3956 14.1017V16.0887C46.3956 16.1795 46.3684 16.252 46.314 16.3065C46.2595 16.37 46.1824 16.4017 46.0826 16.4017H45.4429C45.3522 16.4017 45.2751 16.37 45.2116 16.3065C45.1571 16.252 45.1299 16.1795 45.1299 16.0887V14.9047H40.2032V16.0887C40.2032 16.1795 40.1714 16.252 40.1079 16.3065C40.0535 16.37 39.9809 16.4017 39.8902 16.4017H39.2505ZM40.9381 13.8159L44.1772 13.7887V8.91641H41.7139V10.0324C41.7139 10.9942 41.6458 11.7835 41.5097 12.4005C41.3736 13.0084 41.1831 13.4802 40.9381 13.8159Z"
fill="currentColor"
/>
<path
d="M34.2034 15.0426C33.3686 15.0426 32.6745 14.8884 32.1211 14.5799C31.5767 14.2714 31.1639 13.8404 30.8826 13.287C30.6013 12.7335 30.4471 12.0893 30.4199 11.3544C30.4108 10.9824 30.4062 10.5832 30.4062 10.1568C30.4062 9.73032 30.4108 9.32203 30.4199 8.93188C30.4471 8.19696 30.6013 7.55276 30.8826 6.9993C31.1639 6.44584 31.5767 6.01487 32.1211 5.70638C32.6745 5.3979 33.3686 5.24365 34.2034 5.24365C34.8294 5.24365 35.3783 5.32985 35.8501 5.50224C36.3219 5.67463 36.7121 5.90599 37.0206 6.19633C37.3381 6.48667 37.5786 6.8133 37.7419 7.17623C37.9052 7.53008 37.9959 7.89754 38.0141 8.27862C38.0231 8.36027 37.9959 8.42832 37.9324 8.48276C37.878 8.5372 37.8099 8.56442 37.7283 8.56442H36.9797C36.8981 8.56442 36.8255 8.54174 36.762 8.49637C36.7075 8.45101 36.6667 8.36935 36.6395 8.2514C36.4852 7.54369 36.1949 7.05828 35.7685 6.79516C35.3511 6.53204 34.8249 6.40048 34.1897 6.40048C33.4639 6.40048 32.8878 6.60916 32.4613 7.02652C32.0349 7.43481 31.8035 8.09262 31.7672 8.99993C31.74 9.74393 31.74 10.5061 31.7672 11.2864C31.8035 12.1937 32.0349 12.856 32.4613 13.2734C32.8878 13.6817 33.4639 13.8858 34.1897 13.8858C34.8249 13.8858 35.3511 13.7543 35.7685 13.4911C36.1949 13.228 36.4852 12.7426 36.6395 12.0349C36.6667 11.9169 36.7075 11.8353 36.762 11.7899C36.8255 11.7446 36.8981 11.7219 36.9797 11.7219H37.7283C37.8099 11.7219 37.878 11.7491 37.9324 11.8035C37.9959 11.858 38.0231 11.926 38.0141 12.0077C37.9959 12.3887 37.9052 12.7607 37.7419 13.1237C37.5786 13.4775 37.3381 13.7996 37.0206 14.09C36.7121 14.3803 36.3219 14.6117 35.8501 14.7841C35.3783 14.9564 34.8294 15.0426 34.2034 15.0426Z"
fill="currentColor"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M8.54763 0.0219046C6.0113 -0.271892 4.26146 2.45258 2.63386 4.42198C1.20548 6.15032 0.14193 8.10248 0.0183964 10.3423C-0.113033 12.7252 0.319225 15.2068 1.94263 16.9545C3.61482 18.7547 6.1197 19.8922 8.54763 19.5244C10.774 19.1872 11.7591 16.7423 13.4166 15.2166C15.2164 13.56 18.5695 12.7894 18.523 10.3423C18.4764 7.89135 14.9376 7.41159 13.2348 5.64994C11.4711 3.8252 11.0672 0.313757 8.54763 0.0219046Z"
fill="#7E2AEA"
/>
<circle cx="14.8605" cy="18.5983" r="1.39953" fill="#7E2AEA" />
<circle cx="13.4628" cy="3.06831" r="0.533154" fill="#7E2AEA" />
<path
d="M21.9866 9.76883C21.8311 8.25747 21.1201 6.85765 19.9913 5.84071C18.8625 4.82376 17.3963 4.26212 15.877 4.26466C15.6613 4.26475 15.4457 4.27604 15.2312 4.29846C13.7205 4.45709 12.3221 5.16948 11.3056 6.29824C10.2891 7.42699 9.72661 8.89216 9.72656 10.4111V10.4111V19.1918H12.3608V15.46C13.3907 16.1766 14.6158 16.5597 15.8704 16.5576C16.0861 16.5575 16.3017 16.5463 16.5162 16.5238C17.319 16.4394 18.0973 16.1978 18.8066 15.8126C19.516 15.4275 20.1426 14.9064 20.6505 14.2791C21.1585 13.6518 21.538 12.9306 21.7672 12.1566C21.9964 11.3827 22.071 10.5712 21.9866 9.76839V9.76883ZM18.6034 12.6226C18.314 12.982 17.9562 13.2805 17.5507 13.5007C17.1452 13.7209 16.7 13.8585 16.241 13.9054C16.1179 13.9182 15.9942 13.9246 15.8704 13.9247C15.0738 13.924 14.301 13.6525 13.679 13.1547C13.057 12.6569 12.6228 11.9624 12.4475 11.1853C12.2722 10.4082 12.3663 9.59455 12.7143 8.87796C13.0624 8.16136 13.6437 7.58437 14.3629 7.2417C15.0821 6.89903 15.8964 6.81104 16.6722 6.99216C17.448 7.17329 18.1391 7.61277 18.6323 8.23848C19.1254 8.86418 19.3911 9.63893 19.3858 10.4356C19.3806 11.2322 19.1047 12.0034 18.6034 12.6226Z"
fill="currentColor"
/>
</svg>
);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -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 (
<Box
sx={{
height: "32px",
width: "32px",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
<path d="M10.75 10.25L16 5L21.25 10.25" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M16 19V5" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M27 19V26C27 26.2652 26.8946 26.5196 26.7071 26.7071C26.5196 26.8946 26.2652 27 26 27H6C5.73478 27 5.48043 26.8946 5.29289 26.7071C5.10536 26.5196 5 26.2652 5 26V19" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</Box>
);
}
return (
<Box
sx={{
height: "32px",
width: "32px",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
<path
d="M10.75 10.25L16 5L21.25 10.25"
stroke={color}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path d="M16 19V5" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path
d="M27 19V26C27 26.2652 26.8946 26.5196 26.7071 26.7071C26.5196 26.8946 26.2652 27 26 27H6C5.73478 27 5.48043 26.8946 5.29289 26.7071C5.10536 26.5196 5 26.2652 5 26V19"
stroke={color}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</Box>
);
}

@ -16,24 +16,9 @@ export default function FlagIcon({ color, width = 30 }: Props) {
justifyContent: "center",
}}
>
<svg
width={width}
height={width}
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M5 27V6"
stroke={color}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M5 21.0016C13 15.0016 19 27.0016 27 21.0016V6.00158C19 12.0016 13 0.00158215 5 6.00158"
fill={color}
/>
<svg width={width} height={width} viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 27V6" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M5 21.0016C13 15.0016 19 27.0016 27 21.0016V6.00158C19 12.0016 13 0.00158215 5 6.00158" fill={color} />
<path
d="M5 21.0016C13 15.0016 19 27.0016 27 21.0016V6.00158C19 12.0016 13 0.00158203 5 6.00158"
stroke={color}

@ -5,7 +5,7 @@ interface Props {
width?: number;
}
export default function HashtagIcon({ color, width = 30, }: Props) {
export default function HashtagIcon({ color, width = 30 }: Props) {
return (
<Box
sx={{
@ -16,13 +16,7 @@ export default function HashtagIcon({ color, width = 30, }: Props) {
justifyContent: "center",
}}
>
<svg
width={width}
height={width}
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<svg width={width} height={width} viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M3 11.3333H29M3 20.6667H29M13.6364 2L8.90909 30M23.0909 2L18.3636 30"
stroke={color}

@ -2,7 +2,7 @@ import { Box } from "@mui/material";
interface Props {
color: string;
width: number
width: number;
}
export default function HeartIcon({ color, width }: Props) {
@ -16,13 +16,7 @@ export default function HeartIcon({ color, width }: Props) {
justifyContent: "center",
}}
>
<svg
width={width}
height={width}
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<svg width={width} height={width} viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M16 27C16 27 3.5 20 3.5 11.5C3.5 9.99737 4.02062 8.54114 4.97328 7.37908C5.92593 6.21703 7.25178 5.42093 8.72525 5.12624C10.1987 4.83154 11.7288 5.05646 13.0551 5.76272C14.3814 6.46898 15.4221 7.61296 16 9.00001C16.5779 7.61296 17.6186 6.46898 18.9449 5.76272C20.2712 5.05646 21.8013 4.83154 23.2748 5.12624C24.7482 5.42093 26.0741 6.21703 27.0267 7.37908C27.9794 8.54114 28.5 9.99737 28.5 11.5C28.5 20 16 27 16 27Z"
fill={color}

@ -2,10 +2,10 @@ import { Box } from "@mui/material";
interface Props {
color: string;
width: number
width: number;
}
export default function LightbulbIcon({ color , width }: Props) {
export default function LightbulbIcon({ color, width }: Props) {
return (
<Box
sx={{
@ -16,20 +16,8 @@ export default function LightbulbIcon({ color , width }: Props) {
justifyContent: "center",
}}
>
<svg
width={width}
height={width}
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11 29H21"
stroke={color}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<svg width={width} height={width} viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11 29H21" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path
d="M9.83761 20.8739C8.6471 19.9482 7.68288 18.7637 7.01801 17.4102C6.35313 16.0566 6.00504 14.5694 6.00011 13.0614C5.97511 7.6364 10.3376 3.1239 15.7626 2.9989C17.8622 2.94913 19.9242 3.56166 21.6561 4.74961C23.388 5.93756 24.7019 7.64064 25.4115 9.6173C26.1211 11.594 26.1904 13.7439 25.6094 15.7621C25.0285 17.7803 23.827 19.5644 22.1751 20.8614C21.8102 21.1435 21.5146 21.5052 21.311 21.919C21.1073 22.3328 21.001 22.7877 21.0001 23.2489V23.9989C21.0001 24.2641 20.8948 24.5185 20.7072 24.706C20.5197 24.8935 20.2653 24.9989 20.0001 24.9989H12.0001C11.7349 24.9989 11.4805 24.8935 11.293 24.706C11.1055 24.5185 11.0001 24.2641 11.0001 23.9989V23.2489C10.997 22.7912 10.8909 22.34 10.6896 21.9289C10.4884 21.5177 10.1972 21.1572 9.83761 20.8739V20.8739Z"
stroke={color}

@ -2,7 +2,7 @@ import { Box } from "@mui/material";
interface Props {
color: string;
width: number
width: number;
}
export default function LikeIcon({ color, width }: Props) {
@ -16,13 +16,7 @@ export default function LikeIcon({ color, width }: Props) {
justifyContent: "center",
}}
>
<svg
width={width}
height={width}
viewBox="0 0 40 40"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<svg width={width} height={width} viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M8 17H14V30H8C7.73478 30 7.48043 29.8946 7.29289 29.7071C7.10536 29.5196 7 29.2652 7 29V18C7 17.7348 7.10536 17.4804 7.29289 17.2929C7.48043 17.1054 7.73478 17 8 17V17Z"
stroke={color}

@ -2,7 +2,7 @@ import { Box } from "@mui/material";
interface Props {
color: string;
width: number
width: number;
}
export default function TropfyIcon({ color, width }: Props) {
@ -16,13 +16,7 @@ export default function TropfyIcon({ color, width }: Props) {
justifyContent: "center",
}}
>
<svg
width={width}
height={width}
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<svg width={width} height={width} viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M7 7V13.8875C7 18.85 10.975 22.9625 15.9375 23C17.1246 23.0082 18.3017 22.7815 19.4008 22.3329C20.5 21.8843 21.4995 21.2227 22.3419 20.3862C23.1843 19.5496 23.8528 18.5547 24.309 17.4586C24.7652 16.3626 25 15.1872 25 14V7C25 6.73478 24.8946 6.48043 24.7071 6.29289C24.5196 6.10536 24.2652 6 24 6H8C7.73478 6 7.48043 6.10536 7.29289 6.29289C7.10536 6.48043 7 6.73478 7 7Z"
fill={color}
@ -31,20 +25,8 @@ export default function TropfyIcon({ color, width }: Props) {
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M12 28H20"
stroke={color}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M16 23V28"
stroke={color}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path d="M12 28H20" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M16 23V28" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path
d="M24.7754 16H26.0004C27.0613 16 28.0787 15.5786 28.8288 14.8284C29.579 14.0783 30.0004 13.0609 30.0004 12V10C30.0004 9.73478 29.895 9.48043 29.7075 9.29289C29.52 9.10536 29.2656 9 29.0004 9H25.0004"
stroke={color}

@ -1,45 +1,27 @@
import {
startTransition,
useEffect,
useLayoutEffect,
useRef,
useState,
} from "react";
import { ErrorBoundary } from "react-error-boundary";
import useSWR from "swr";
import { SnackbarProvider } from "notistack";
import moment from "moment";
import {
Box,
ScopedCssBaseline,
CssBaseline,
ThemeProvider,
} from "@mui/material";
import { useQuizData } from "@/api/hooks";
import { QuizViewContext, createQuizViewStore } from "@/stores/quizView";
import LoadingSkeleton from "@/ui_kit/LoadingSkeleton";
import { useVkMetricsGoals } from "@/utils/hooks/metrics/useVkMetricsGoals";
import { useYandexMetricsGoals } from "@/utils/hooks/metrics/useYandexMetricsGoals";
import { QuizSettingsContext } from "@contexts/QuizDataContext";
import { RootContainerWidthContext } from "@contexts/RootContainerWidthContext";
import type { QuizSettings } from "@model/settingsData";
import { Box, CssBaseline, ScopedCssBaseline, ThemeProvider } from "@mui/material";
import { LocalizationProvider } from "@mui/x-date-pickers";
import { AdapterMoment } from "@mui/x-date-pickers/AdapterMoment";
import { ruRU } from "@mui/x-date-pickers/locales";
import ViewPublicationPage from "./ViewPublicationPage/ViewPublicationPage";
import { ApologyPage } from "./ViewPublicationPage/ApologyPage";
import { QuizViewContext, createQuizViewStore } from "@/stores/quizView";
import { getQuizData } from "@/api/quizRelase";
import { QuizDataContext } from "@contexts/QuizDataContext";
import { RootContainerWidthContext } from "@contexts/RootContainerWidthContext";
import LoadingSkeleton from "@/ui_kit/LoadingSkeleton";
import { useVkMetricsGoals } from "@/utils/hooks/metrics/useVkMetricsGoals";
import { useYandexMetricsGoals } from "@/utils/hooks/metrics/useYandexMetricsGoals";
import { handleComponentError } from "@utils/handleComponentError";
import lightTheme from "@utils/themes/light";
import type { QuizSettings } from "@model/settingsData";
import moment from "moment";
import { SnackbarProvider } from "notistack";
import { startTransition, useEffect, useLayoutEffect, useRef, useState } from "react";
import { ErrorBoundary } from "react-error-boundary";
import { ApologyPage } from "./ViewPublicationPage/ApologyPage";
import ViewPublicationPage from "./ViewPublicationPage/ViewPublicationPage";
import { HelmetProvider } from "react-helmet-async";
moment.locale("ru");
const localeText =
ruRU.components.MuiLocalizationProvider.defaultProps.localeText;
const localeText = ruRU.components.MuiLocalizationProvider.defaultProps.localeText;
type Props = {
quizSettings?: QuizSettings;
@ -59,44 +41,28 @@ function QuizAnswererInner({
disableGlobalCss = false,
}: Props) {
const [quizViewStore] = useState(createQuizViewStore);
const [rootContainerWidth, setRootContainerWidth] = useState<number>(
() => window.innerWidth
);
const [rootContainerWidth, setRootContainerWidth] = useState<number>(() => window.innerWidth);
const rootContainerRef = useRef<HTMLDivElement>(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 <LoadingSkeleton />;
if (error) return <ApologyPage error={error} />;
if (!data) return <LoadingSkeleton />;
quizSettings ??= data;
if (!quizSettings) throw new Error("Quiz data is null");
if (quizSettings.questions.length === 0)
return <ApologyPage error={new Error("No questions found")} />;
if (quizSettings.questions.length === 0) return <ApologyPage error={new Error("No questions found")} />;
if (!quizId) return <ApologyPage error={new Error("No quiz id")} />;
const quizContainer = (
@ -138,9 +104,7 @@ function QuizAnswererInner({
return (
<QuizViewContext.Provider value={quizViewStore}>
<RootContainerWidthContext.Provider value={rootContainerWidth}>
<QuizDataContext.Provider
value={{ ...quizSettings, quizId, preview, changeFaviconAndTitle }}
>
<QuizSettingsContext.Provider value={{ ...quizSettings, quizId, preview, changeFaviconAndTitle }}>
{disableGlobalCss ? (
<ScopedCssBaseline
sx={{
@ -154,7 +118,7 @@ function QuizAnswererInner({
) : (
<CssBaseline>{quizContainer}</CssBaseline>
)}
</QuizDataContext.Provider>
</QuizSettingsContext.Provider>
</RootContainerWidthContext.Provider>
</QuizViewContext.Provider>
);
@ -162,19 +126,21 @@ function QuizAnswererInner({
export default function QuizAnswerer(props: Props) {
return (
<LocalizationProvider
dateAdapter={AdapterMoment}
adapterLocale="ru"
localeText={localeText}
>
<ThemeProvider theme={lightTheme}>
<SnackbarProvider
preventDuplicate={true}
style={{ backgroundColor: lightTheme.palette.brightPurple.main }}
>
<QuizAnswererInner {...props} />
</SnackbarProvider>
</ThemeProvider>
</LocalizationProvider>
<HelmetProvider>
<LocalizationProvider
dateAdapter={AdapterMoment}
adapterLocale="ru"
localeText={localeText}
>
<ThemeProvider theme={lightTheme}>
<SnackbarProvider
preventDuplicate={true}
style={{ backgroundColor: lightTheme.palette.brightPurple.main }}
>
<QuizAnswererInner {...props} />
</SnackbarProvider>
</ThemeProvider>
</LocalizationProvider>
</HelmetProvider>
);
}

@ -4,29 +4,32 @@ import { FallbackProps } from "react-error-boundary";
type Props = Partial<FallbackProps>;
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 (
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "center",
height: "100%",
}}
>
<Typography
sx={{
textAlign: "center",
color: "text.primary",
}}
>{message}</Typography>
</Box>
);
return (
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "center",
height: "100%",
backgroundColor: "#F2F3F7",
}}
>
<Typography
sx={{
textAlign: "center",
color: "text.primary",
}}
>
{message}
</Typography>
</Box>
);
};

@ -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<FormContactFieldName, FormContactFieldData> =
settings.cfg.formContact.fields || settings.cfg.formContact;
const filteredFC: Partial<
Record<FormContactFieldName, FormContactFieldData>
> = {};
const filteredFC: Partial<Record<FormContactFieldName, FormContactFieldData>> = {};
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,
}}
>
<ContactTextBlock settings={settings} />
<Box
sx={{
flexGrow: 0,
flexGrow: isMobile ? 1 : 0,
display: "flex",
alignItems: "center",
justifyContent: "space-between",
@ -247,11 +228,8 @@ export const ContactForm = ({ currentQuestion, onShowResult }: Props) => {
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",
}}
>
<Box
@ -298,7 +276,10 @@ export const ContactForm = ({ currentQuestion, onShowResult }: Props) => {
fontSize={"16px"}
>
С&ensp;
<Link href={"https://shub.pena.digital/ppdd"} target="_blank">
<Link
href={"https://shub.pena.digital/ppdd"}
target="_blank"
>
Положением об обработке персональных данных{" "}
</Link>
&ensp;и&ensp;
@ -344,14 +325,13 @@ export const ContactForm = ({ currentQuestion, onShowResult }: Props) => {
mb: isMobile ? "30px" : isTablet ? "40px" : "50px",
gap: "10px",
textDecoration: "none",
margitTop: "auto",
}}
>
<NameplateLogo
style={{
fontSize: "20px",
color: quizThemes[settings.cfg.theme].isLight
? "#151515"
: "#FFFFFF",
color: quizThemes[settings.cfg.theme].isLight ? "#151515" : "#FFFFFF",
}}
/>
</Box>

@ -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<ContactTextBlockProps> = ({settings}) => {
const theme = useTheme();
const isMobile = useRootContainerSize() < 850;
const isTablet = useRootContainerSize() < 1000;
return (
<Box
sx={{
flexGrow: 1,
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
borderRight: isMobile ? undefined : "1px solid #9A9AAF80",
margin: isMobile ? 0 : "40px 0",
padding: isMobile ? "0" : "0 40px"
}}
export const ContactTextBlock: FC<ContactTextBlockProps> = ({ settings }) => {
const theme = useTheme();
const isMobile = useRootContainerSize() < 850;
const isTablet = useRootContainerSize() < 1000;
return (
<Box
sx={{
flexGrow: isMobile ? 0 : 1,
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
borderRight: isMobile ? undefined : "1px solid #9A9AAF80",
margin: isMobile ? 0 : "40px 0",
padding: isMobile ? "0" : "0 40px",
}}
>
<Box
sx={{
maxWidth: isMobile ? "100%" : isTablet ? "410px" : "630px",
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
justifyContent: "center",
padding: isMobile ? "40px 20px 0 20px" : "0",
mt: isMobile ? 0 : isTablet ? "-180px" : "-47px",
}}
>
<Typography
sx={{
textAlign: isTablet ? undefined : "center",
fontSize: "24px",
lineHeight: "normal",
fontWeight: 501,
color: theme.palette.text.primary,
wordBreak: "break-word",
}}
>
<Box
sx={{
maxWidth: isMobile? "100%" : isTablet? "410px" : "630px",
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
justifyContent: "center",
padding: isMobile ? "40px 20px 0 20px" : "0",
mt: isMobile ? 0 : isTablet ? "-180px" : "-47px",
}}
>
<Typography
sx={{
textAlign: isTablet ? undefined : "center",
fontSize: "24px",
lineHeight: "normal",
fontWeight: 501,
color: theme.palette.text.primary,
wordBreak: "break-word",
}}
>
{settings.cfg.formContact.title ||
"Заполните форму, чтобы получить результаты теста"}
</Typography>
{settings.cfg.formContact.desc && (
<Typography
sx={{
color: theme.palette.text.primary,
m: "20px 0",
fontSize: "18px",
wordBreak: "break-word",
}}
>
{settings.cfg.formContact.desc}
</Typography>
)}
</Box>
</Box>
)
}
{settings.cfg.formContact.title || "Заполните форму, чтобы получить результаты теста"}
</Typography>
{settings.cfg.formContact.desc && (
<Typography
sx={{
color: theme.palette.text.primary,
m: "20px 0",
fontSize: "18px",
wordBreak: "break-word",
}}
>
{settings.cfg.formContact.desc}
</Typography>
)}
</Box>
</Box>
);
};

@ -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<SetStateAction<string>>;
}
export const CountrySelector:FC<CountrySelectorProps> = ({setMask}) => {
const theme = useTheme();
const [country, setCountry] = useState('RU');
const handleChange = (e: SelectChangeEvent<Value>) => {
setCountry(e.target.value);
setMask(phoneMasksByCountry[e.target.value][1]);
};
return (
<Select
//@ts-ignore
value={country}
onChange={handleChange}
renderValue={(value) => value}
MenuProps={{
PaperProps: {
style: {
backgroundColor: theme.palette.background.default,
},
},
}}
sx={{
minWidth: 50,
backgroundColor: theme.palette.background.default,
"& .MuiSelect-select": {
paddingLeft: "5px",
paddingRight: "5px",
color: 'gray',
fontSize: "12px",
border: "none",
},
"& .MuiOutlinedInput-notchedOutline": {
border: "none",
},
"&:hover .MuiOutlinedInput-notchedOutline": {
border: "none",
},
"&:hover:before": {
border: "none",
},
"&.Mui-focused .MuiOutlinedInput-notchedOutline": {
border: "none",
},
"&.Mui-focused:hover .MuiOutlinedInput-notchedOutline": {
border: "none",
},
}}
>
{Object.keys(phoneMasksByCountry).map((countryCode) => {
return <MenuItem value={countryCode}>{phoneMasksByCountry[countryCode][0]}</MenuItem>
})}
</Select>
);
setMask: Dispatch<SetStateAction<string>>;
};
export const CountrySelector: FC<CountrySelectorProps> = ({ setMask }) => {
const theme = useTheme();
const [country, setCountry] = useState("RU");
const handleChange = (e: SelectChangeEvent<Value>) => {
setCountry(e.target.value);
setMask(phoneMasksByCountry[e.target.value][1]);
};
return (
<Select
//@ts-ignore
value={country}
onChange={handleChange}
renderValue={(value) => value}
// autoComplete={true}
MenuProps={{
PaperProps: {
style: {
backgroundColor: theme.palette.background.default,
borderRadius: "12px",
scrollbarWidth: "none",
},
},
}}
sx={{
minWidth: 50,
backgroundColor: theme.palette.background.default,
"& .MuiSelect-select": {
paddingLeft: "5px",
paddingRight: "5px",
color: "gray",
fontSize: "12px",
border: "none",
},
"& .MuiOutlinedInput-notchedOutline": {
border: "none",
},
"&:hover .MuiOutlinedInput-notchedOutline": {
border: "none",
},
"&:hover:before": {
border: "none",
},
"&.Mui-focused .MuiOutlinedInput-notchedOutline": {
border: "none",
},
"&.Mui-focused:hover .MuiOutlinedInput-notchedOutline": {
border: "none",
},
}}
>
{Object.keys(phoneMasksByCountry).map((countryCode) => {
return <MenuItem value={countryCode}>{phoneMasksByCountry[countryCode][0]}</MenuItem>;
})}
</Select>
);
};

@ -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<TextFieldProps>;
let first = true;
function phoneChange(e: ChangeEvent<HTMLInputElement>, 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 (
<Box m="10px 0">
<Typography mb="7px" color={theme.palette.text.primary} fontSize={"16px"}>
{title}
</Typography>
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 });
<TextField
inputRef={isPhone? ref : null}
onChange={onChange}
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: (
<InputAdornment position="start" >
<Icon
color="gray"
backgroundColor={
quizThemes[settings.cfg.theme].isLight
? "#F2F3F7"
: "#F2F3F71A"
}
/>
</InputAdornment>
),
endAdornment: (
<InputAdornment position="end">
{isPhone && (
<CountrySelector setMask={setMask} />)}
</InputAdornment>
),
}}
/>
return (
<Box m="10px 0">
<Typography
mb="7px"
color={theme.palette.text.primary}
fontSize={"16px"}
>
{title}
</Typography>
</Box>
);
<TextField
// inputRef={isPhone ? ref : null}
//@ts-ignore
onChange={isPhone ? (e: ChangeEvent<HTMLInputElement>) => 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: (
<InputAdornment position="start">
<Icon
color="gray"
backgroundColor={quizThemes[settings.cfg.theme].isLight ? "#F2F3F7" : "#F2F3F71A"}
/>
</InputAdornment>
),
endAdornment: (
<InputAdornment position="end">{isPhone && <CountrySelector setMask={setMask} />}</InputAdornment>
),
}}
/>
</Box>
);
};

@ -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<SetStateAction<string>>;
email: string;
setEmail: Dispatch<SetStateAction<string>>;
phone: string;
setPhone: Dispatch<SetStateAction<string>>;
text: string;
setText: Dispatch<SetStateAction<string>>;
adress: string;
setAdress: Dispatch<SetStateAction<string>>;
name: string;
setName: Dispatch<SetStateAction<string>>;
email: string;
setEmail: Dispatch<SetStateAction<string>>;
phone: string;
setPhone: Dispatch<SetStateAction<string>>;
text: string;
setText: Dispatch<SetStateAction<string>>;
adress: string;
setAdress: Dispatch<SetStateAction<string>>;
};
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 = (
<CustomInput
onChange={({ target }) => setName(target.value)}
id={name}
title={FC["name"].innerText || "Введите имя"}
desc={FC["name"].text || "Имя"}
Icon={NameIcon}
/>
);
const Email = (
<CustomInput
onChange={({ target }) => setEmail(target.value.replaceAll(/\s/g, ""))}
id={email}
title={FC["email"].innerText || "Введите Email"}
desc={FC["email"].text || "Email"}
Icon={EmailIcon}
/>
);
const Phone = (
<CustomInput
onChange={({ target }) => setPhone(target.value)}
id={phone}
title={FC["phone"].innerText || "Введите номер телефона"}
desc={FC["phone"].text || "Номер телефона"}
Icon={PhoneIcon}
isPhone={true}
/>
);
const Text = (
<CustomInput
onChange={({ target }) => setText(target.value)}
id={text}
title={FC["text"].text || "Введите фамилию"}
desc={FC["text"].innerText || "Фамилия"}
Icon={TextIcon}
/>
);
const Adress = (
<CustomInput
onChange={({ target }) => setAdress(target.value)}
id={adress}
title={FC["address"].innerText || "Введите адрес"}
desc={FC["address"].text || "Адрес"}
Icon={AddressIcon}
/>
);
if (!FC) return null;
const Name = (
<CustomInput
onChange={({ target }) => setName(target.value)}
id={name}
title={FC["name"].innerText || "Введите имя"}
desc={FC["name"].text || "Имя"}
Icon={NameIcon}
/>
);
const Email = (
<CustomInput
onChange={({ target }) => {
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 = (
<CustomInput
onChange={({ target }) => 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 = (
<CustomInput
onChange={({ target }) => setText(target.value)}
id={text}
title={FC["text"].text || "Введите фамилию"}
desc={FC["text"].innerText || "Фамилия"}
Icon={TextIcon}
/>
);
const Adress = (
<CustomInput
onChange={({ target }) => 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}
</>
);
}
};

@ -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 (
<Box
sx={{
position: "relative",
padding: "15px 0",
borderTop: `1px solid #9A9AAF80`,
height: "75px",
display: "flex",
background: settings.cfg.design
? "rgba(154,154,175, 0.2)"
: "transparent",
}}
>
<Box
sx={{
width: "100%",
maxWidth: "1410px",
padding: "10px",
margin: "0 auto",
display: "flex",
alignItems: "center",
gap: "10px",
}}
>
{stepNumber !== null && (
<Box sx={{ flexGrow: 1 }}>
<Typography sx={{ color: theme.palette.text.primary }}>
Вопрос {stepNumber} из {questionsAmount}
</Typography>
<Stepper activeStep={stepNumber} steps={questionsAmount} />
</Box>
)}
{prevButton}
{nextButton}
</Box>
</Box>
);
return (
<Box
sx={{
position: "relative",
padding: "15px 0",
borderTop: `1px solid #9A9AAF80`,
height: "75px",
display: "flex",
background: settings.cfg.design ? "rgba(154,154,175, 0.2)" : "transparent",
}}
>
<Box
sx={{
width: "100%",
maxWidth: "1410px",
padding: "10px",
margin: "0 auto",
display: "flex",
alignItems: "center",
gap: "10px",
}}
>
{stepNumber !== null && (
<Box sx={{ flexGrow: 1 }}>
<Typography sx={{ color: theme.palette.text.primary }}>
Вопрос {stepNumber} из {questionsAmount}
</Typography>
<Stepper activeStep={stepNumber} steps={questionsAmount} />
</Box>
)}
{prevButton}
{nextButton}
</Box>
</Box>
);
};

@ -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 (
<Box
@ -48,9 +48,7 @@ export const Question = ({
height: "100%",
backgroundPosition: "center",
backgroundSize: "cover",
backgroundImage: settings.cfg.design
? `url(${DESIGN_LIST[settings.cfg.theme]})`
: null,
backgroundImage: settings.cfg.design ? `url(${DESIGN_LIST[settings.cfg.theme]})` : null,
}}
>
<Box
@ -97,7 +95,7 @@ export const Question = ({
{show_badge && (
<Link
target="_blank"
href="https://quiz.pena.digital"
href={`https://${window.location.hostname.includes("s") ? "s" : ""}quiz.pena.digital/answer/v1.0.0/logo?q=${quizId}`}
sx={{
mt: "20px",
alignSelf: "end",
@ -125,23 +123,13 @@ export const Question = ({
</Box>
</Box>
{questionSelect}
<Footer
stepNumber={currentQuestionStepNumber}
prevButton={prevButton}
nextButton={nextButton}
/>
<Footer stepNumber={currentQuestionStepNumber} prevButton={prevButton} nextButton={nextButton} />
</Box>
</Box>
);
};
function QuestionByType({
question,
stepNumber,
}: {
question: RealTypedQuizQuestion;
stepNumber: number | null;
}) {
function QuestionByType({ question, stepNumber }: { question: RealTypedQuizQuestion; stepNumber: number | null }) {
switch (question.type) {
case "variant":
return <Variant currentQuestion={question} />;

@ -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 (
<Box sx={{
p: "20px",
display: "flex",
justifyContent: "center",
}}>
<FormControl
fullWidth
size="small"
return (
<Box
sx={{
p: "20px",
display: "flex",
justifyContent: "center",
}}
>
<FormControl
fullWidth
size="small"
sx={{
maxWidth: "500px",
minWidth: "200px",
height: "48px",
}}
className="cancel"
>
<MuiSelect
id="category-select"
variant="outlined"
value={selectedQuestion.id}
placeholder="Заголовок вопроса"
onChange={({ target }) => {
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) => (
<MenuItem
key={question.id}
value={question.id}
sx={{
maxWidth: "500px",
minWidth: "200px",
height: "48px",
display: "flex",
alignItems: "center",
gap: "20px",
p: "4px",
borderRadius: "5px",
color: "#9A9AAF",
wordBreak: "break-word",
whiteSpace: "normal",
}}
className="cancel"
>
<MuiSelect
id="category-select"
variant="outlined"
value={selectedQuestion.id}
placeholder="Заголовок вопроса"
onChange={({ target }) => {
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) => (
<MenuItem
key={question.id}
value={question.id}
sx={{
display: "flex",
alignItems: "center",
gap: "20px",
p: "4px",
borderRadius: "5px",
color: "#9A9AAF",
wordBreak: "break-word",
whiteSpace: "normal",
}}
>
{`${index + 1}. ${question.title}`}
</MenuItem>
),
)}
</MuiSelect>
</FormControl>
</Box>
);
>
{`${index + 1}. ${question.title}`}
</MenuItem>
))}
</MuiSelect>
</FormControl>
</Box>
);
}

@ -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 (
<Box
@ -51,10 +47,8 @@ export const ResultForm = ({ resultQuestion }: ResultFormProps) => {
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",
}}
>
<Box
@ -63,86 +57,101 @@ export const ResultForm = ({ resultQuestion }: ResultFormProps) => {
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,
},
}}
>
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
flexWrap: "wrap",
gap: "30px",
mb: "7px",
width: "100%",
height: "100%",
overflow: "auto",
padding: "0 20px 20px",
scrollbarWidth: "none",
"&::-webkit-scrollbar": {
width: 0,
},
}}
>
<Typography
<Box
sx={{
fontSize: "14px",
color: theme.palette.text.primary,
wordBreak: "break-word",
display: "flex",
alignItems: "center",
flexWrap: "wrap",
mb: "10px",
width: "100%",
maxWidth: "700px",
backgroundColor: "#9A9AAF1A",
borderRadius: "0 0 12px 12px",
padding: "20px 20px 15px",
}}
>
Ваш результат:
</Typography>
</Box>
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
width: "100%",
maxWidth: "844px",
padding: isMobile ? "0 16px" : isTablet ? "0 78px" : undefined,
}}
>
{!resultQuestion?.content.useImage &&
resultQuestion.content.video && (
<YoutubeEmbedIframe
<Typography
sx={{
fontSize: "17px",
color: "#9A9AAF",
wordBreak: "break-word",
}}
>
Ваш результат:
</Typography>
</Box>
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
width: "100%",
maxWidth: "700px",
}}
>
{!resultQuestion?.content.useImage && resultQuestion.content.video && (
<QuizVideo
videoUrl={resultQuestion.content.video}
containerSX={{
width: "100%",
maxWidth: "844px",
maxWidth: "700px",
height: isMobile ? "100%" : "306px",
}}
/>
)}
{resultQuestion?.content.useImage && resultQuestion.content.back && (
<Box
sx={{ width: "100%", display: "flex", justifyContent: "center" }}
>
{resultQuestion?.content.useImage && resultQuestion.content.back && (
<Box
component="img"
src={resultQuestion.content.back}
sx={{
width: "100%",
maxWidth: "306px",
height: spec ? "auto" : isMobile ? "236px" : "306px",
borderRadius: "8px",
objectFit: "contain",
display: "flex",
justifyContent: "center",
}}
/>
</Box>
)}
{resultQuestion.description !== "" &&
resultQuestion.description !== " " && (
>
<img
alt="resultImage"
src={resultQuestion.content.back}
style={{
width: "100%",
height: spec ? "auto" : isMobile ? "236px" : "306px",
borderRadius: "12px",
objectFit: "cover",
overflow: "hidden",
}}
/>
</Box>
)}
{resultQuestion.description !== "" && resultQuestion.description !== " " && (
<Typography
sx={{
fontSize: "24px",
fontSize: "27px",
lineHeight: "32px",
fontWeight: 700,
mt: "25px",
mt: "30px",
color: theme.palette.text.primary,
wordBreak: "break-word",
}}
@ -151,22 +160,24 @@ export const ResultForm = ({ resultQuestion }: ResultFormProps) => {
</Typography>
)}
<Typography
sx={{
mt: "20px",
color: theme.palette.text.primary,
wordBreak: "break-word",
}}
>
{resultQuestion.title}
</Typography>
<Typography
sx={{
mt: "12px",
fontSize: "17px",
lineHeight: "20px",
color: theme.palette.text.primary,
wordBreak: "break-word",
}}
>
{resultQuestion.title}
</Typography>
{resultQuestion.content.text !== "" &&
resultQuestion.content.text !== " " && (
{resultQuestion.content.text !== "" && resultQuestion.content.text !== " " && (
<Typography
sx={{
fontSize: "18px",
mt: "30px ",
fontSize: "17px",
lineHeight: "20px",
mt: "25px ",
wordBreak: "break-word",
color: theme.palette.text.primary,
}}
@ -174,98 +185,87 @@ export const ResultForm = ({ resultQuestion }: ResultFormProps) => {
{resultQuestion.content.text}
</Typography>
)}
</Box>
</Box>
<Box width="100%">
{show_badge && (
<Box
component={Link}
target={"_blank"}
href={`https://${
window.location.hostname.includes("s") ? "s" : ""
}quiz.pena.digital/answer/v1.0.0/logo?q=${quizId}`}
sx={{
display: "flex",
width: "100%",
justifyContent: "end",
px: "20px",
alignItems: "center",
alignSelf: isMobile ? "center" : "end",
margin: isMobile ? "15px 0 0" : "15px 25px 0 0",
gap: "10px",
textDecoration: "none",
mb: "15px",
position: isTablet || isMobile ? "sticky" : "absolute",
bottom: "90px",
}}
>
{show_badge && (
<Box
component={Link}
target={"_blank"}
href={`https://${window.location.hostname[0] === "s" ? "s" : ""
}quiz.pena.digital/squiz/quiz/logo?q=${quizId}`}
<NameplateLogo
style={{
fontSize: "23px",
color: quizThemes[settings.cfg.theme].isLight ? "#000000" : "#F5F7FF",
}}
/>
</Box>
)}
<Box
sx={{
width: "100%",
flexDirection: "column",
display: "flex",
justifyContent: "center",
alignItems: "center",
borderTop: "1px solid #9A9AAF80",
p: "20px",
position: "sticky",
bottom: 0,
}}
>
{settings.cfg.resultInfo.showResultForm === "before" &&
settings.cfg.showfc !== false &&
!settings.cfg.score && (
<Button
onClick={() => setCurrentQuizStep("contactform")}
variant="contained"
sx={{
display: "flex",
alignItems: "center",
mt: "15px",
gap: "10px",
textDecoration: "none",
mb: "15px",
p: "10px 20px",
width: "auto",
height: "50px",
}}
>
<NameplateLogo
style={{
fontSize: "23px",
color: quizThemes[settings.cfg.theme].isLight
? "#000000"
: "#F5F7FF",
}}
/>
</Box>
{resultQuestion.content.hint.text || "Узнать подробнее"}
</Button>
)}
</Box>
<Box
sx={{
// boxShadow: "0 0 15px 0 rgba(0,0,0,.08)",
width: "100%",
flexDirection: "column",
display: "flex",
justifyContent: "center",
alignItems: "center",
borderTop: "1px solid #9A9AAF80",
p:
(settings.cfg.resultInfo.showResultForm === "before" &&
!settings.cfg.score) ||
(settings.cfg.resultInfo.showResultForm === "after" &&
resultQuestion.content.redirect)
? "20px"
: "0",
}}
>
{settings.cfg.resultInfo.showResultForm === "before" &&
!settings.cfg.score && (
<Button
onClick={() => setCurrentQuizStep("contactform")}
variant="contained"
sx={{
p: "10px 20px",
width: "auto",
height: "50px",
}}
>
{resultQuestion.content.hint.text || "Узнать подробнее"}
</Button>
)}
{settings.cfg.resultInfo.showResultForm === "after" &&
resultQuestion.content.redirect && (
<Button
onClick={() => {
vkMetrics.resultLink();
yandexMetrics.resultLink();
setTimeout(() => {
location.href =
(resultQuestion.content.redirect.includes("https")
? resultQuestion.content.redirect
: `https://${resultQuestion.content.redirect}`).replace(/\s+/g, "");
}, 1000)
}}
variant="contained"
sx={{ p: "10px 20px", width: "auto", height: "50px" }}
>
{resultQuestion.content.hint.text || "Перейти на сайт"}
</Button>
)}
</Box>
{settings.cfg.resultInfo.showResultForm === "after" && resultQuestion.content.redirect && (
<Button
onClick={() => {
vkMetrics.resultLink();
yandexMetrics.resultLink();
setTimeout(() => {
location.href = (
resultQuestion.content.redirect.includes("https")
? resultQuestion.content.redirect
: `https://${resultQuestion.content.redirect}`
).replace(/\s+/g, "");
}, 1000);
}}
variant="contained"
sx={{
p: "10px 20px",
width: "auto",
}}
>
{resultQuestion.content.hint.text || "Перейти на сайт"}
</Button>
)}
</Box>
</Box>
</Box >
</Box>
);
};

@ -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;

@ -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<StartPageDesktopProps, "startpageType">;
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 (
<Box
id="pain"
sx={{
display: "flex",
flexDirection: alignType === "left" ? "row" : "row-reverse",
height: "100%",
backgroundPosition: "center",
backgroundSize: "cover",
backgroundImage: settings.cfg.design ? `url(${DESIGN_LIST[settings.cfg.theme]})` : null,
scrollbarWidth: "none",
"&::-webkit-scrollbar": {
width: 0,
},
overflowY: "auto",
}}
>
<Box
sx={{
display: "flex",
flexDirection: alignType === "left" ? "row" : "row-reverse",
padding: isTablet ? "15px" : "0",
width: "100%",
background:
settings.cfg.design && !quizThemes[settings.cfg.theme].isLight
? alignType === "left"
? "linear-gradient(90deg, #272626, transparent)"
: alignType === "right"
? "linear-gradient(-90deg, #272626, transparent)"
: "linear-gradient(0deg, #272626, transparent)"
: null,
}}
>
<Box
id="pain"
sx={{
display: "flex",
flexDirection: alignType === "left" ? "row" : "row-reverse",
height: "100%",
backgroundPosition: "center",
backgroundSize: "cover",
backgroundImage: settings.cfg.design
? `url(${DESIGN_LIST[settings.cfg.theme]})`
: null,
scrollbarWidth: "none",
"&::-webkit-scrollbar": {
width: 0,
},
overflowY: "auto",
}}
sx={{
width: settings.cfg.startpage.background.desktop ? "40%" : undefined,
height: "100%",
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
alignItems: "flex-start",
p: isTablet ? "25px" : alignType === "left" ? "25px 25px 25px 35px" : "25px 35px 25px 25px",
overflowY: "auto",
scrollbarWidth: "none",
"&::-webkit-scrollbar": {
width: 0,
},
}}
>
<Box
sx={{
display: "flex",
flexDirection: alignType === "left" ? "row" : "row-reverse",
padding: isTablet ? "15px" : "0",
width: "100%",
background: settings.cfg.design && !quizThemes[settings.cfg.theme].isLight
? alignType === 'left'
? "linear-gradient(90deg, #272626, transparent)"
: alignType === 'right'
? "linear-gradient(-90deg, #272626, transparent)"
: "linear-gradient(0deg, #272626, transparent)"
: null,
}}
>
<Box
sx={{
width: settings.cfg.startpage.background.desktop ? "40%" : undefined,
height: "100%",
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
alignItems: "flex-start",
p: isTablet ? "25px" : alignType === 'left' ? "25px 25px 25px 35px" : "25px 35px 25px 25px",
overflowY: "auto",
scrollbarWidth: "none",
"&::-webkit-scrollbar": {
width: 0,
},
}}
>
{quizHeaderBlock}
{quizMainBlock}
</Box>
{settings.cfg.startpage.background.desktop && (
<Box sx={{width: "60%", overflow: "hidden"}}><Box
sx={{
width: "100%",
height: "100%",
padding: alignType === "left" ? "25px 25px 25px 15px" : "25px 15px 25px 25px",
display: "flex",
justifyContent: "center",
"& > img": {width: "100%", borderRadius: "12px"},
}}
>{backgroundBlock}</Box></Box>
)}
</Box>
{quizHeaderBlock}
{quizMainBlock}
</Box>
);
{settings.cfg.startpage.background.desktop && (
<Box sx={{ width: "60%", overflow: "hidden" }}>
<Box
sx={{
width: "100%",
height: "100%",
padding: alignType === "left" ? "25px 25px 25px 15px" : "25px 15px 25px 25px",
display: "flex",
justifyContent: "center",
"& > img": { width: "100%", borderRadius: "12px" },
}}
>
{backgroundBlock}
</Box>
</Box>
)}
</Box>
</Box>
);
};
const ExpandedLayout = ({
alignType,
quizHeaderBlock,
quizMainBlock,
backgroundBlock,
}: LayoutProps) => {
const size = useRootContainerSize();
const isTablet = size >= 700 && size < 1100;
return (
<>
<Box
sx={{
height: "100%",
width: alignType === "center" ? "100%" : isTablet ? "46%" : "42%",
display: "flex",
padding:
alignType === "center"
? isTablet ? "30px 40px" : "30px 35px"
: alignType === "left"
? isTablet ? "25px 0 31px 40px" : "25px 0 31px 35px"
: isTablet ? "25px 40px 31px 0" : "25px 35px 31px 0",
margin:
alignType === "center"
? "0 auto"
: alignType === "left"
? "0"
: "0 0 0 auto",
scrollbarWidth: "none",
"&::-webkit-scrollbar": {
width: 0,
},
overflowY: "auto",
}}
>
<Box
sx={{
minHeight: "calc(100% - 32px)",
position: "relative",
width: "100%",
padding:
alignType === "center"
? "0"
: alignType === "left"
? "0 40px 0 0"
: "0 0 0 40px",
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
alignItems: alignType === "center" ? "center" : "start",
borderRight: alignType === "left" ? "1px solid #9A9AAF80" : null,
borderLeft: alignType === "right" ? "1px solid #9A9AAF80" : null,
scrollbarWidth: "none",
"&::-webkit-scrollbar": {
width: 0,
},
}}
>
{alignType !== "center" && quizHeaderBlock}
{quizMainBlock}
</Box>
</Box>
<Box
sx={{
position: "absolute",
zIndex: -1,
left: 0,
top: 0,
height: "100%",
width: "100%",
overflow: "hidden",
}}
>
{backgroundBlock}
</Box>
</>
);
}
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 (
<>
<Box
sx={{
height: "100%",
width: alignType === "center" ? "100%" : isTablet ? "46%" : "42%",
display: "flex",
padding:
alignType === "center"
? isTablet
? "30px 40px"
: "30px 35px"
: alignType === "left"
? isTablet
? "25px 0 31px 40px"
: "25px 0 31px 35px"
: isTablet
? "25px 40px 31px 0"
: "25px 35px 31px 0",
margin: alignType === "center" ? "0 auto" : alignType === "left" ? "0" : "0 0 0 auto",
scrollbarWidth: "none",
"&::-webkit-scrollbar": {
width: 0,
},
overflowY: "auto",
}}
>
<Box
sx={{
overflow: "auto",
padding: isTablet ? "25px 40px 40px" : "25px 25px 25px",
display: "flex",
flexDirection: "column",
alignItems: "center",
height: "100%",
backgroundPosition: "center",
backgroundSize: "cover",
backgroundImage: !settings.cfg.design ? null : settings.cfg.design && !quizThemes[settings.cfg.theme].isLight
? `linear-gradient(0deg, #272626, transparent), url(${DESIGN_LIST[settings.cfg.theme]})` : `url(${DESIGN_LIST[settings.cfg.theme]})`,
scrollbarWidth: "none",
"&::-webkit-scrollbar": {
width: 0,
},
overflowY: "auto",
}}
sx={{
minHeight: "calc(100% - 32px)",
position: "relative",
width: "100%",
padding: alignType === "center" ? "0" : alignType === "left" ? "0 40px 0 0" : "0 0 0 40px",
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
alignItems: alignType === "center" ? "center" : "start",
borderRight: alignType === "left" ? "1px solid #9A9AAF80" : null,
borderLeft: alignType === "right" ? "1px solid #9A9AAF80" : null,
scrollbarWidth: "none",
"&::-webkit-scrollbar": {
width: 0,
},
}}
>
{quizHeaderBlock}
{backgroundBlock && settings.cfg.startpage.background.desktop && (
<Box
sx={{
width: "100%",
maxWidth: "844px",
height: isTablet ? "530px" : "306px",
display: "flex",
justifyContent: "center",
"& > img": {width: "100%", borderRadius: "12px"},
}}
>
{backgroundBlock}
</Box>
)}
{quizMainBlock}
{alignType !== "center" && quizHeaderBlock}
{quizMainBlock}
</Box>
);
</Box>
<Box
sx={{
position: "absolute",
zIndex: -1,
left: 0,
top: 0,
height: "100%",
width: "100%",
overflow: "hidden",
}}
>
{backgroundBlock}
</Box>
</>
);
};
const CenteredLayout = ({ quizHeaderBlock, quizMainBlock, backgroundBlock }: LayoutProps) => {
const isTablet = useRootContainerSize() < 1100;
const { settings } = useQuizSettings();
return (
<Box
sx={{
overflow: "auto",
padding: isTablet ? "25px 40px 40px" : "25px 25px 25px",
display: "flex",
flexDirection: "column",
alignItems: "center",
height: "100%",
backgroundPosition: "center",
backgroundSize: "cover",
backgroundImage: !settings.cfg.design
? null
: settings.cfg.design && !quizThemes[settings.cfg.theme].isLight
? `linear-gradient(0deg, #272626, transparent), url(${DESIGN_LIST[settings.cfg.theme]})`
: `url(${DESIGN_LIST[settings.cfg.theme]})`,
scrollbarWidth: "none",
"&::-webkit-scrollbar": {
width: 0,
},
overflowY: "auto",
}}
>
{quizHeaderBlock}
{backgroundBlock && settings.cfg.startpage.background.desktop && (
<Box
sx={{
width: "100%",
maxWidth: "844px",
height: isTablet ? "530px" : "306px",
display: "flex",
justifyContent: "center",
"& > img": { width: "100%", borderRadius: "12px" },
}}
>
{backgroundBlock}
</Box>
)}
{quizMainBlock}
</Box>
);
};
export const StartPageDesktop = ({
quizHeaderBlock,
quizMainBlock,
backgroundBlock,
startpageType,
alignType,
}: StartPageDesktopProps) => {
switch (startpageType) {
case null:
case "standard": {
return (
<StandartLayout
alignType={alignType}
quizHeaderBlock={quizHeaderBlock}
quizMainBlock={quizMainBlock}
backgroundBlock={backgroundBlock}
/>
);
}
case "expanded": {
return (
<ExpandedLayout
alignType={alignType}
quizHeaderBlock={quizHeaderBlock}
quizMainBlock={quizMainBlock}
backgroundBlock={backgroundBlock}
/>
);
}
case "centered": {
return (
<CenteredLayout
alignType={alignType}
quizHeaderBlock={quizHeaderBlock}
quizMainBlock={quizMainBlock}
backgroundBlock={backgroundBlock}
/>
);
}
default:
notReachable(startpageType);
quizHeaderBlock,
quizMainBlock,
backgroundBlock,
startpageType,
alignType,
}: StartPageDesktopProps) => {
switch (startpageType) {
case null:
case "standard": {
return (
<StandartLayout
alignType={alignType}
quizHeaderBlock={quizHeaderBlock}
quizMainBlock={quizMainBlock}
backgroundBlock={backgroundBlock}
/>
);
}
case "expanded": {
return (
<ExpandedLayout
alignType={alignType}
quizHeaderBlock={quizHeaderBlock}
quizMainBlock={quizMainBlock}
backgroundBlock={backgroundBlock}
/>
);
}
case "centered": {
return (
<CenteredLayout
alignType={alignType}
quizHeaderBlock={quizHeaderBlock}
quizMainBlock={quizMainBlock}
backgroundBlock={backgroundBlock}
/>
);
}
default:
notReachable(startpageType);
}
};

@ -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<StartPageMobileProps, "startpageType">;
const StandartMobileLayout = ({
quizHeaderBlock,
quizMainBlock,
backgroundBlock,
}: MobileLayoutProps) => {
const {settings} = useQuizData();
const StandartMobileLayout = ({ quizHeaderBlock, quizMainBlock, backgroundBlock }: MobileLayoutProps) => {
const { settings } = useQuizSettings();
return (
<Box
sx={{
display: "flex",
flexDirection: "column",
flexGrow: 1,
justifyContent: "flex-end",
minHeight: "100%",
height: "100%",
"&::-webkit-scrollbar": {width: 0},
backgroundPosition: "center",
backgroundSize: "cover",
backgroundImage: settings.cfg.design
? `url(${DESIGN_LIST[settings.cfg.theme]})`
: null,
}}
>
return (
<Box
sx={{
display: "flex",
flexDirection: "column",
flexGrow: 1,
justifyContent: "flex-end",
minHeight: "100%",
height: "100%",
"&::-webkit-scrollbar": { width: 0 },
backgroundPosition: "center",
backgroundSize: "cover",
backgroundImage: settings.cfg.design ? `url(${DESIGN_LIST[settings.cfg.theme]})` : null,
}}
>
<Box
sx={{
width: "100%",
display: "flex",
flexGrow: 1,
flexDirection: "column",
justifyContent: "space-between",
alignItems: "flex-start",
p: "20px",
height: "100%",
overflowY: "auto",
overflowX: "hidden",
background:
settings.cfg.design && !quizThemes[settings.cfg.theme].isLight
? "linear-gradient(90deg,#272626,transparent)"
: null,
"&::-webkit-scrollbar": {
width: "4px",
},
"&::-webkit-scrollbar-thumb": {
backgroundColor: "#b8babf",
},
}}
>
<Box sx={{ marginBottom: "13px" }}>{quizHeaderBlock}</Box>
{settings.cfg.startpage.background.desktop && (
<Box sx={{ width: "100%", overflow: "hidden" }}>
<Box
sx={{
width: "100%",
display: "flex",
flexGrow: 1,
flexDirection: "column",
justifyContent: "space-between",
alignItems: "flex-start",
p: "20px",
height: "100%",
overflowY: "auto",
overflowX: "hidden",
background:
settings.cfg.design && !quizThemes[settings.cfg.theme].isLight
? "linear-gradient(90deg,#272626,transparent)"
: null,
"&::-webkit-scrollbar": {
width: "4px",
},
"&::-webkit-scrollbar-thumb": {
backgroundColor: "#b8babf",
},
}}
sx={{
width: "100%",
height: "100%",
display: "flex",
justifyContent: "center",
"& > img": {
width: "100%",
borderRadius: "12px",
},
}}
>
<Box sx={{marginBottom: "13px"}}>
{quizHeaderBlock}
</Box>
{settings.cfg.startpage.background.desktop && (
<Box sx={{width: "100%", overflow: "hidden"}}>
<Box
sx={{
width: "100%",
height: "100%",
display: "flex",
justifyContent: "center",
"& > img": {
width: "100%",
borderRadius: "12px"
},
}}
>
{backgroundBlock}
</Box>
</Box>
)}
<Box
sx={{
height: "80%",
display: "flex",
flexGrow: 1,
flexDirection: "column",
justifyContent: "space-between",
width: "100%",
marginTop: "30px"
}}
>
{quizMainBlock}
</Box>
{backgroundBlock}
</Box>
</Box>
)}
<Box
sx={{
height: "80%",
display: "flex",
flexGrow: 1,
flexDirection: "column",
justifyContent: "space-between",
width: "100%",
marginTop: "30px",
}}
>
{quizMainBlock}
</Box>
);
</Box>
</Box>
);
};
const ExpandedMobileLayout = ({
quizHeaderBlock,
quizMainBlock,
backgroundBlock,
}: MobileLayoutProps) => (
const ExpandedMobileLayout = ({ quizHeaderBlock, quizMainBlock, backgroundBlock }: MobileLayoutProps) => (
<Box
sx={{
display: "flex",
flexDirection: "column-reverse",
flexGrow: 1,
justifyContent: "flex-end",
minHeight: "100%",
height: "100%",
"&::-webkit-scrollbar": { width: 0 },
}}
>
<Box
sx={{
display: "flex",
flexDirection: "column-reverse",
flexGrow: 1,
justifyContent: "flex-end",
minHeight: "100%",
height: "100%",
"&::-webkit-scrollbar": {width: 0},
}}
sx={{
zIndex: 3,
width: "100%",
display: "flex",
flexGrow: 1,
flexDirection: "column",
justifyContent: "space-between",
alignItems: "flex-start",
height: "100%",
overflowY: "auto",
overflowX: "hidden",
"&::-webkit-scrollbar": { width: "4px" },
"&::-webkit-scrollbar-thumb": { backgroundColor: "#b8babf" },
}}
>
<Box
sx={{
zIndex: 3,
width: "100%",
display: "flex",
flexGrow: 1,
flexDirection: "column",
justifyContent: "space-between",
alignItems: "flex-start",
height: "100%",
overflowY: "auto",
overflowX: "hidden",
"&::-webkit-scrollbar": {width: "4px"},
"&::-webkit-scrollbar-thumb": {backgroundColor: "#b8babf"},
}}
>
<Box
sx={{
padding: "20px",
height: "80%",
display: "flex",
flexGrow: 1,
flexDirection: "column",
justifyContent: "space-between",
width: "100%",
}}
>
{quizHeaderBlock}
{quizMainBlock}
</Box>
</Box>
<Box
sx={{
zIndex: -1,
position: "absolute",
left: 0,
top: 0,
width: "100%",
height: "100%",
// minHeight: "100%",
overflow: "hidden",
"& > img": {
display: "block",
minHeight: "100%",
},
}}
>
{backgroundBlock}
</Box>
<Box
sx={{
padding: "20px",
height: "80%",
display: "flex",
flexGrow: 1,
flexDirection: "column",
justifyContent: "space-between",
width: "100%",
}}
>
{quizHeaderBlock}
{quizMainBlock}
</Box>
</Box>
<Box
sx={{
zIndex: -1,
position: "absolute",
left: 0,
top: 0,
width: "100%",
height: "100%",
// minHeight: "100%",
overflow: "hidden",
"& > img": {
display: "block",
minHeight: "100%",
},
}}
>
{backgroundBlock}
</Box>
</Box>
);
const CenteredMobileLayout = ({
quizHeaderBlock,
quizMainBlock,
backgroundBlock,
}: MobileLayoutProps) => {
const {settings} = useQuizData();
return (
<Box
const CenteredMobileLayout = ({ quizHeaderBlock, quizMainBlock, backgroundBlock }: MobileLayoutProps) => {
const { settings } = useQuizSettings();
return (
<Box
sx={{
display: "flex",
flexDirection: "column-reverse",
flexGrow: 1,
justifyContent: "flex-end",
minHeight: "100%",
height: "100%",
backgroundPosition: "center",
backgroundSize: "cover",
backgroundImage: !settings.cfg.design
? null
: settings.cfg.design && !quizThemes[settings.cfg.theme].isLight
? `linear-gradient(0deg, #272626, transparent), url(${DESIGN_LIST[settings.cfg.theme]})`
: `url(${DESIGN_LIST[settings.cfg.theme]})`,
"&::-webkit-scrollbar": { width: 0 },
}}
>
<Box
sx={{
width: "100%",
display: "flex",
flexGrow: 1,
flexDirection: "column",
justifyContent: "space-between",
alignItems: "flex-start",
padding: "20px",
height: "100%",
overflowY: "auto",
overflowX: "hidden",
"&::-webkit-scrollbar": { width: "4px" },
"&::-webkit-scrollbar-thumb": { backgroundColor: "#b8babf" },
}}
>
{quizHeaderBlock}
{settings.cfg.startpage.background.desktop && (
<Box
sx={{
display: "flex",
flexDirection: "column-reverse",
flexGrow: 1,
justifyContent: "flex-end",
minHeight: "100%",
height: "100%",
backgroundPosition: "center",
backgroundSize: "cover",
backgroundImage: !settings.cfg.design ? null : settings.cfg.design && !quizThemes[settings.cfg.theme].isLight
? `linear-gradient(0deg, #272626, transparent), url(${DESIGN_LIST[settings.cfg.theme]})` : `url(${DESIGN_LIST[settings.cfg.theme]})`,
"&::-webkit-scrollbar": {width: 0},
width: "100%",
overflow: "hidden",
"& > img": { width: "100%", borderRadius: "12px" },
}}
>
{backgroundBlock}
</Box>
)}
<Box
sx={{
height: "80%",
display: "flex",
flexGrow: 1,
flexDirection: "column",
justifyContent: "space-between",
width: "100%",
}}
>
<Box
sx={{
width: "100%",
display: "flex",
flexGrow: 1,
flexDirection: "column",
justifyContent: "space-between",
alignItems: "flex-start",
padding: "20px",
height: "100%",
overflowY: "auto",
overflowX: "hidden",
"&::-webkit-scrollbar": {width: "4px"},
"&::-webkit-scrollbar-thumb": {backgroundColor: "#b8babf"},
}}
>
{quizHeaderBlock}
{settings.cfg.startpage.background.desktop && (
<Box
sx={{
width: "100%",
overflow: "hidden",
"& > img": {width: "100%", borderRadius: "12px"},
}}
>
{backgroundBlock}
</Box>
)}
<Box
sx={{
height: "80%",
display: "flex",
flexGrow: 1,
flexDirection: "column",
justifyContent: "space-between",
width: "100%",
}}
>
{quizMainBlock}
</Box>
</Box>
{quizMainBlock}
</Box>
);
}
</Box>
</Box>
);
};
export const StartPageMobile = ({
quizHeaderBlock,
quizMainBlock,
backgroundBlock,
startpageType,
}: StartPageMobileProps) => {
switch (startpageType) {
case null:
case "standard": {
return (
<StandartMobileLayout
quizHeaderBlock={quizHeaderBlock}
quizMainBlock={quizMainBlock}
backgroundBlock={backgroundBlock}
/>
);
}
case "expanded": {
return (
<ExpandedMobileLayout
quizHeaderBlock={quizHeaderBlock}
quizMainBlock={quizMainBlock}
backgroundBlock={backgroundBlock}
/>
);
}
case "centered": {
return (
<CenteredMobileLayout
quizHeaderBlock={quizHeaderBlock}
quizMainBlock={quizMainBlock}
backgroundBlock={backgroundBlock}
/>
);
}
default:
notReachable(startpageType);
quizHeaderBlock,
quizMainBlock,
backgroundBlock,
startpageType,
}: StartPageMobileProps) => {
switch (startpageType) {
case null:
case "standard": {
return (
<StandartMobileLayout
quizHeaderBlock={quizHeaderBlock}
quizMainBlock={quizMainBlock}
backgroundBlock={backgroundBlock}
/>
);
}
case "expanded": {
return (
<ExpandedMobileLayout
quizHeaderBlock={quizHeaderBlock}
quizMainBlock={quizMainBlock}
backgroundBlock={backgroundBlock}
/>
);
}
case "centered": {
return (
<CenteredMobileLayout
quizHeaderBlock={quizHeaderBlock}
quizMainBlock={quizMainBlock}
backgroundBlock={backgroundBlock}
/>
);
}
default:
notReachable(startpageType);
}
};

@ -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" ? (
<img
src={
settings.cfg.startpage.background.desktop ||
DESIGN_LIST[settings.cfg.theme] ||
""
}
src={settings.cfg.startpage.background.desktop || DESIGN_LIST[settings.cfg.theme] || ""}
alt=""
style={{
display: "block",
width:
isMobile || settings.cfg.startpageType === "expanded"
? "100%"
: undefined,
width: isMobile || settings.cfg.startpageType === "expanded" ? "100%" : undefined,
height: "100%",
minWidth: "100%",
maxHeight: "100%",
@ -71,14 +53,12 @@ export const StartPageViewPublication = () => {
/>
) : settings.cfg.startpage.background.type === "video" ? (
settings.cfg.startpage.background.video ? (
<YoutubeEmbedIframe
<QuizVideo
videoUrl={settings.cfg.startpage.background.video}
containerSX={{
width: settings.cfg.startpageType === "centered" ? "550px" : "100%",
height:
settings.cfg.startpageType === "centered" ? "275px" : "100%",
borderRadius:
settings.cfg.startpageType === "centered" ? "10px" : "0",
height: settings.cfg.startpageType === "centered" ? "275px" : "100%",
borderRadius: settings.cfg.startpageType === "centered" ? "10px" : "0",
overflow: "hidden",
"& iframe": {
width: "100%",
@ -106,8 +86,7 @@ export const StartPageViewPublication = () => {
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 = () => {
<Typography
sx={{
fontSize: "12px",
color:
settings.cfg.startpageType === "expanded"
? "white"
: theme.palette.text.primary,
color: settings.cfg.startpageType === "expanded" ? "white" : theme.palette.text.primary,
wordBreak:
settings.cfg.startpageType === "expanded" &&
settings.cfg.startpage.position === "center"
settings.cfg.startpageType === "expanded" && settings.cfg.startpage.position === "center"
? "normal"
: "break-word",
}}
@ -173,9 +144,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}
</Typography>
<Box
width={
settings.cfg.startpageType === "standard" ? "100%" : "auto"
}
>
<Box width={settings.cfg.startpageType === "standard" ? "100%" : "auto"}>
<Button
variant="contained"
disabled={realQuestionsCount === 0}
@ -352,34 +299,26 @@ export const StartPageViewPublication = () => {
}}
onClick={onQuizStart}
>
{settings.cfg.startpage.button.trim()
? settings.cfg.startpage.button
: "Пройти тест"}
{settings.cfg.startpage.button.trim() ? settings.cfg.startpage.button : "Пройти тест"}
</Button>
</Box>
</Box>
<Box
sx={{
display: "flex",
flexGrow:
settings.cfg.startpageType === "centered"
? isMobile
? 0
: 1
: 0,
flexGrow: settings.cfg.startpageType === "centered" ? (isMobile ? 0 : 1) : 0,
gap: isMobile ? "30px" : "40px",
alignItems: "flex-end",
justifyContent:
(settings.cfg.startpageType === "expanded" &&
settings.cfg.startpage.position === "center" &&
isMobile) ||
(settings.cfg.startpageType === "centered" && isMobile)
(settings.cfg.startpageType === "centered" && isMobile)
? "center"
: "space-between",
width: "100%",
flexWrap:
settings.cfg.startpageType === "expanded" &&
settings.cfg.startpage.position === "center"
settings.cfg.startpageType === "expanded" && settings.cfg.startpage.position === "center"
? isMobile
? "wrap-reverse"
: "nowrap"
@ -395,16 +334,15 @@ export const StartPageViewPublication = () => {
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 = () => {
<Typography
sx={{
textAlign:
settings.cfg.startpageType === "expanded" &&
settings.cfg.startpage.position === "center"
settings.cfg.startpageType === "expanded" && settings.cfg.startpage.position === "center"
? "end"
: "none",
fontSize: "16px",
lineHeight: "19px",
color:
settings.cfg.startpageType === "expanded"
? "#FFFFFF"
: theme.palette.text.primary,
color: settings.cfg.startpageType === "expanded" ? "#FFFFFF" : theme.palette.text.primary,
}}
>
{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}

@ -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 (
<ThemeProvider
theme={quizThemes[settings.cfg.theme || "StandardTheme"].theme}
>
<Typography textAlign={"center"} mt="50px">
<ThemeProvider theme={quizThemes[settings.cfg.theme || "StandardTheme"].theme}>
<Typography
textAlign={"center"}
mt="50px"
>
Вопрос не выбран
</Typography>
</ThemeProvider>
);
const currentAnswer = answers.find(({ questionId }) => questionId === currentQuestion.id);
let quizStepElement: ReactElement;
switch (currentQuizStep) {
case "startpage": {
@ -99,20 +94,15 @@ export default function ViewPublicationPage() {
nextButton={
<NextButton
isNextButtonEnabled={isNextButtonEnabled}
moveToNextQuestion={async () => {
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 (
<ThemeProvider
theme={quizThemes[settings.cfg.theme || "StandardTheme"].theme}
>
<ThemeProvider theme={quizThemes[settings.cfg.theme || "StandardTheme"].theme}>
<Helmet>
{Array.from(preloadLinks).map((link) => (
<link
key={link}
rel="preload"
as="image"
href={link}
/>
))}
</Helmet>
{quizStepElement}
</ThemeProvider>
);

@ -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<boolean>(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",

@ -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<HTMLDivElement>) => {
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 (
<FormControl
key={index}
disabled={isSending}
sx={{
borderRadius: "12px",
border: `1px solid`,
borderColor:
answer === variant.id ? theme.palette.primary.main : "#9A9AAF",
borderColor: answer === variant.id ? theme.palette.primary.main : "#9A9AAF",
overflow: "hidden",
maxWidth: "317px",
width: "100%",
@ -103,10 +47,9 @@ export const EmojiVariant = ({
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",
"&:hover": { borderColor: theme.palette.primary.main },
}}
// value={index}
@ -128,9 +71,7 @@ export const EmojiVariant = ({
justifyContent: "center",
}}
>
{variant.extendedText && (
<Typography fontSize="100px">{variant.extendedText}</Typography>
)}
{variant.extendedText && <Typography fontSize="100px">{variant.extendedText}</Typography>}
</Box>
</Box>
<FormControlLabel
@ -168,9 +109,7 @@ export const EmojiVariant = ({
}
label={
<Box sx={{ display: "flex", gap: "10px" }}>
<Typography sx={{ wordBreak: "break-word", lineHeight: "normal" }}>
{variant.answer}
</Typography>
<Typography sx={{ wordBreak: "break-word", lineHeight: "normal" }}>{variant.answer}</Typography>
</Box>
}
/>

@ -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<boolean>(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 (
<Box>
@ -32,9 +27,7 @@ export const Emoji = ({ currentQuestion }: EmojiProps) => {
</Typography>
<RadioGroup
name={currentQuestion.id}
value={currentQuestion.content.variants.findIndex(
({ id }) => 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",
}}
>
<Box
sx={{ display: "flex", width: "100%", gap: "42px", flexWrap: "wrap" }}
>
<Box sx={{ display: "flex", width: "100%", gap: "42px", flexWrap: "wrap" }}>
{currentQuestion.content.variants.map((variant, index) => (
<EmojiVariant
key={variant.id}
currentQuestion={currentQuestion}
questionId={currentQuestion.id}
variant={variant}
isSending={isSending}
setIsSending={setIsSending}
index={index}
/>
))}

@ -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<boolean>(false);
export const UploadFile = ({ currentQuestion, setModalWarningType, isSending, setIsSending }: UploadFileProps) => {
const { quizId, preview } = useQuizSettings();
const [isDropzoneHighlighted, setIsDropzoneHighlighted] = useState<boolean>(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 (
<Box sx={{ display: "flex", alignItems: "center" }}>
{isSending ? (
<Skeleton
variant="rounded"
sx={{ width: "100%", height: "120px", maxWidth: "560px" }}
/>
<Skeleton variant="rounded" sx={{ width: "100%", height: "120px", maxWidth: "560px" }} />
) : (
<ButtonBase
component="label"
sx={{ justifyContent: "flex-start", width: "100%" }}
>
<ButtonBase component="label" sx={{ justifyContent: "flex-start", width: "100%" }}>
<input
onChange={({ target }) => 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"
/>
<Box
onDragEnter={() =>
!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 = ({
<UploadIcon />
<Box>
<Typography sx={{ color: "#9A9AAF", fontWeight: 500 }}>
{
UPLOAD_FILE_DESCRIPTIONS_MAP[currentQuestion.content.type]
.title
}
{UPLOAD_FILE_DESCRIPTIONS_MAP[currentQuestion.content.type].title}
</Typography>
<Typography
sx={{
@ -154,10 +127,7 @@ export const UploadFile = ({
lineHeight: "19px",
}}
>
{
UPLOAD_FILE_DESCRIPTIONS_MAP[currentQuestion.content.type]
.description
}
{UPLOAD_FILE_DESCRIPTIONS_MAP[currentQuestion.content.type].description}
</Typography>
</Box>
</Box>

@ -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) {

@ -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<ModalWarningType>(null);
const [modalWarningType, setModalWarningType] = useState<ModalWarningType>(null);
const [isSending, setIsSending] = useState<boolean>(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 (
<Box>
<Typography
variant="h5"
color={theme.palette.text.primary}
sx={{ wordBreak: "break-word" }}
>
<Typography variant="h5" color={theme.palette.text.primary} sx={{ wordBreak: "break-word" }}>
{currentQuestion.title}
</Typography>
<Box
@ -55,10 +41,7 @@ export const File = ({ currentQuestion }: FileProps) => {
}}
>
{answer?.split("|")[0] ? (
<UploadedFile
currentQuestion={currentQuestion}
setIsSending={setIsSending}
/>
<UploadedFile currentQuestion={currentQuestion} setIsSending={setIsSending} />
) : (
<UploadFile
currentQuestion={currentQuestion}
@ -68,11 +51,7 @@ export const File = ({ currentQuestion }: FileProps) => {
/>
)}
{answer && currentQuestion.content.type === "picture" && (
<img
src={answer.split("|")[1]}
style={{ marginTop: "15px", maxWidth: "300px", maxHeight: "300px" }}
alt=""
/>
<img src={answer.split("|")[1]} style={{ marginTop: "15px", maxWidth: "300px", maxHeight: "300px" }} alt="" />
)}
{answer && currentQuestion.content.type === "video" && (
<video
@ -86,10 +65,7 @@ export const File = ({ currentQuestion }: FileProps) => {
/>
)}
</Box>
<Modal
open={modalWarningType !== null}
onClose={() => setModalWarningType(null)}
>
<Modal open={modalWarningType !== null} onClose={() => setModalWarningType(null)}>
<Box
sx={{
position: "absolute",
@ -117,17 +93,13 @@ const CurrentModal = ({ status }: { status: ModalWarningType }) => {
case "errorType":
return <Typography>Выбран некорректный тип файла</Typography>;
case "errorSize":
return (
<Typography>Файл слишком большой. Максимальный размер 50 МБ</Typography>
);
return <Typography>Файл слишком большой. Максимальный размер 50 МБ</Typography>;
default:
return (
<>
<Typography>Допустимые расширения файлов:</Typography>
<Typography>
{ACCEPT_SEND_FILE_TYPES_MAP[status].join(" ")}
</Typography>
<Typography>{ACCEPT_SEND_FILE_TYPES_MAP[status].join(" ")}</Typography>
</>
);
}

@ -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<HTMLDivElement>) => {
event.preventDefault();
if (isSending) return;
setIsSending(true);
try {
await sendAnswer({
questionId: currentQuestion.id,
body: `${currentQuestion.content.variants[index].answer} <img style="width:100%; max-width:250px; max-height:250px" src="${currentQuestion.content.variants[index].extendedText}"/>`,
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}
>

@ -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<boolean>(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) => {
</Typography>
<RadioGroup
name={currentQuestion.id}
value={currentQuestion.content.variants.findIndex(
({ id }) => 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) => (
<ImageVariant
key={variant.id}
currentQuestion={currentQuestion}
questionId={currentQuestion.id}
variant={variant}
isSending={isSending}
setIsSending={setIsSending}
index={index}
/>
))}

@ -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<boolean>(false);
const [inputValue, setInputValue] = useState<string>("0");
const [minRange, setMinRange] = useState<string>("0");
const [maxRange, setMaxRange] = useState<string>("100000000000");
const [reversedInputValue, setReversedInputValue] = useState<string>("0");
const [reversedMinRange, setReversedMinRange] = useState<string>("0");
const [reversedMaxRange, setReversedMaxRange] =
useState<string>("100000000000");
const { settings, quizId, preview } = useQuizData();
const [reversedMaxRange, setReversedMaxRange] = useState<string>("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<Element, Event>,
value: number | number[]
) => {
const onChangeCommitted = async (_: Event | SyntheticEvent<Element, Event>, 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",
},
}}

@ -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}
</Typography>
<Typography
color={theme.palette.text.primary}
sx={{ wordBreak: "break-word" }}
>
<Typography color={theme.palette.text.primary} sx={{ wordBreak: "break-word" }}>
{currentQuestion.content.text}
</Typography>
<Box
@ -38,32 +33,35 @@ export const Page = ({ currentQuestion }: PageProps) => {
}}
>
{currentQuestion.content.useImage ? (
<Box
sx={{
borderRadius: "12px",
border: "1px solid #9A9AAF",
overflow: "hidden",
}}
>
<img
key={currentQuestion.id}
src={currentQuestion.content.back}
alt=""
style={{
display: "block",
width: "100%",
height: "100%",
objectFit: "contain",
currentQuestion.content.back && (
<Box
sx={{
borderRadius: "12px",
border: "1px solid #9A9AAF",
overflow: "hidden",
}}
/>
</Box>
>
<img
key={currentQuestion.id}
src={currentQuestion.content.back}
alt=""
style={{
display: "block",
width: "100%",
height: "100%",
objectFit: "contain",
}}
/>
</Box>
)
) : (
<YoutubeEmbedIframe
<QuizVideo
containerSX={{
width: "100%",
height: "calc(100% - 270px)",
maxHeight: "80%",
objectFit: "contain",
aspectRatio: "16 / 9",
}}
videoUrl={currentQuestion.content.video}
/>

@ -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) => (
<StarIconMini width={width} color={color} />
<StarIconMini
width={width}
color={color}
/>
),
},
{
name: "trophie",
icon: (color: string, width: number) => (
<TropfyIcon width={width} color={color} />
<TropfyIcon
width={width}
color={color}
/>
),
},
{
name: "flag",
icon: (color: string, width: number) => (
<FlagIcon width={width} color={color} />
<FlagIcon
width={width}
color={color}
/>
),
},
{
name: "heart",
icon: (color: string, width: number) => (
<HeartIcon width={width} color={color} />
<HeartIcon
width={width}
color={color}
/>
),
},
{
name: "like",
icon: (color: string, width: number) => (
<LikeIcon width={width} color={color} />
<LikeIcon
width={width}
color={color}
/>
),
},
{
name: "bubble",
icon: (color: string, width: number) => (
<LightbulbIcon width={width} color={color} />
<LightbulbIcon
width={width}
color={color}
/>
),
},
{
name: "hashtag",
icon: (color: string, width: number) => (
<HashtagIcon width={width} color={color} />
<HashtagIcon
width={width}
color={color}
/>
),
},
];
@ -72,36 +81,17 @@ type RatingProps = {
};
export const Rating = ({ currentQuestion }: RatingProps) => {
const [isSending, setIsSending] = useState<boolean>(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) => {
>
<Box sx={{ display: "inline-block", width: "100%" }}>
<RatingComponent
disabled={isSending}
value={Number(answer || 0)}
onChange={(_, value) => 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)}
/>
</Box>
<Box
@ -152,12 +135,8 @@ export const Rating = ({ currentQuestion }: RatingProps) => {
width: "100%",
}}
>
<Typography sx={{ color: "#9A9AAF" }}>
{currentQuestion.content.ratingNegativeDescription}
</Typography>
<Typography sx={{ color: "#9A9AAF" }}>
{currentQuestion.content.ratingPositiveDescription}
</Typography>
<Typography sx={{ color: "#9A9AAF" }}>{currentQuestion.content.ratingNegativeDescription}</Typography>
<Typography sx={{ color: "#9A9AAF" }}>{currentQuestion.content.ratingPositiveDescription}</Typography>
</Box>
</Box>
</Box>

@ -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<boolean>(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) => {
}}
>
<SelectComponent
disabled={isSending}
placeholder={currentQuestion.content.default}
activeItemIndex={answer ? Number(answer) : -1}
items={currentQuestion.content.variants.map(({ answer }) => answer)}

@ -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<HTMLInputElement>) => {
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 !== " " && (
<Box
sx={{
maxWidth: "400px",
width: "100%",
height: "300px",
margin: "15px",
}}
>
<img
key={currentQuestion.id}
src={currentQuestion.content.back}
style={{ width: "100%", height: "100%", objectFit: "cover" }}
alt=""
/>
</Box>
)}
{currentQuestion.content.back && currentQuestion.content.back !== " " && (
<Box
sx={{
maxWidth: "400px",
width: "100%",
height: "300px",
margin: "15px",
}}
>
<img
key={currentQuestion.id}
src={currentQuestion.content.back}
style={{ width: "100%", height: "100%", objectFit: "cover" }}
alt=""
/>
</Box>
)}
</Box>
</Box>
);

@ -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<HTMLInputElement>) => {
updateAnswer(currentQuestion.id, target.value, 0);
inputHC(target.value);
};
return (
@ -93,18 +80,16 @@ export const TextSpecial = ({
>
{currentQuestion.title}
</Typography>
{isHorizontal &&
currentQuestion.content.back &&
currentQuestion.content.back !== " " && (
<Box sx={{ margin: "30px", width: "50vw", maxHeight: "550px" }}>
<img
key={currentQuestion.id}
src={currentQuestion.content.back}
style={{ width: "100%", height: "100%", objectFit: "cover" }}
alt=""
/>
</Box>
)}
{isHorizontal && currentQuestion.content.back && currentQuestion.content.back !== " " && (
<Box sx={{ margin: "30px", width: "50vw", maxHeight: "550px" }}>
<img
key={currentQuestion.id}
src={currentQuestion.content.back}
style={{ width: "100%", height: "100%", objectFit: "cover" }}
alt=""
/>
</Box>
)}
{
<TextField
autoFocus={true}
@ -124,9 +109,7 @@ export const TextSpecial = ({
sx={{
width: "100%",
"& .MuiOutlinedInput-root": {
backgroundColor: settings.cfg.design
? "rgba(154,154,175, 0.2)"
: "#FFFFFF",
backgroundColor: settings.cfg.design ? "rgba(154,154,175, 0.2)" : "#FFFFFF",
},
"&:focus-visible": {
borderColor: theme.palette.primary.main,
@ -135,18 +118,16 @@ export const TextSpecial = ({
/>
}
</Box>
{!isHorizontal &&
currentQuestion.content.back &&
currentQuestion.content.back !== " " && (
<Box sx={{ margin: "15px", width: "40vw" }}>
<img
key={currentQuestion.id}
src={currentQuestion.content.back}
style={{ width: "100%", height: "100%", objectFit: "cover" }}
alt=""
/>
</Box>
)}
{!isHorizontal && currentQuestion.content.back && currentQuestion.content.back !== " " && (
<Box sx={{ margin: "15px", width: "40vw" }}>
<img
key={currentQuestion.id}
src={currentQuestion.content.back}
style={{ width: "100%", height: "100%", objectFit: "cover" }}
alt=""
/>
</Box>
)}
</Box>
);
};

@ -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<boolean>(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) => {
<TextSpecial
currentQuestion={currentQuestion}
answer={answer}
inputHC={inputHC}
stepNumber={stepNumber}
/>
);
@ -61,7 +30,6 @@ export const Text = ({ currentQuestion, stepNumber }: TextProps) => {
<TextNormal
currentQuestion={currentQuestion}
answer={answer}
inputHC={inputHC}
/>
);
@ -70,7 +38,6 @@ export const Text = ({ currentQuestion, stepNumber }: TextProps) => {
<TextNormal
currentQuestion={currentQuestion}
answer={answer}
inputHC={inputHC}
/>
);
}

@ -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<TextFieldProps>;
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<HTMLLabelElement>) => {
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 (
<FormControlLabel
key={variant.id}
disabled={isSending}
sx={{
margin: "0",
borderRadius: "12px",
color: theme.palette.text.primary,
padding: "15px",
border: `1px solid`,
borderColor:
answer === variant.id ? theme.palette.primary.main : "#9A9AAF",
borderColor: answer === variant.id ? theme.palette.primary.main : "#9A9AAF",
backgroundColor: settings.cfg.design
? quizThemes[settings.cfg.theme].isLight
? "#FFFFFF"
: "rgba(255,255,255, 0.3)"
: quizThemes[settings.cfg.theme].isLight
? "white"
: theme.palette.background.default,
? "white"
: theme.palette.background.default,
display: "flex",
maxWidth: "685px",
maxHeight: "85px",
@ -164,11 +92,14 @@ export const VariantItem = ({
value={index}
labelPlacement="start"
control={
currentQuestion.content.multi ? (
isMulti ? (
<Checkbox
checked={!!answer?.includes(variant.id)}
checkedIcon={
<CheckboxIcon checked color={theme.palette.primary.main} />
<CheckboxIcon
checked
color={theme.palette.primary.main}
/>
}
icon={<CheckboxIcon />}
/>

@ -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) => {
>
<Group
name={currentQuestion.id.toString()}
value={currentQuestion.content.variants.findIndex(
({ id }) => 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) => (
<VariantItem
key={variant.id}
currentQuestion={currentQuestion}
questionId={currentQuestion.id}
isMulti={currentQuestion.content.multi}
variant={variant}
answer={answer}
index={index}
isSending={isSending}
setIsSending={setIsSending}
/>
))}
{currentQuestion.content.own && ownVariant && (
<VariantItem
own
currentQuestion={currentQuestion}
questionId={currentQuestion.id}
isMulti={currentQuestion.content.multi}
variant={ownVariant.variant}
answer={answer}
index={currentQuestion.content.variants.length + 2}
isSending={isSending}
setIsSending={setIsSending}
/>
)}
</Box>
</Group>
{currentQuestion.content.back &&
currentQuestion.content.back !== " " && (
<Box sx={{ maxWidth: "400px", width: "100%", height: "300px" }}>
<img
key={currentQuestion.id}
src={currentQuestion.content.back}
style={{ width: "100%", height: "100%", objectFit: "cover" }}
alt=""
/>
</Box>
)}
{currentQuestion.content.back && currentQuestion.content.back !== " " && (
<Box sx={{ maxWidth: "400px", width: "100%", height: "300px" }}>
<img
key={currentQuestion.id}
src={currentQuestion.content.back}
style={{ width: "100%", height: "100%", objectFit: "cover" }}
alt=""
/>
</Box>
)}
</Box>
</Box>
);

@ -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<HTMLLabelElement>) => {
event.preventDefault();
setIsSending(true);
updateAnswer(questionId, variant.id, variant.points || 0);
try {
await sendAnswer({
questionId: currentQuestion.id,
body: `${currentQuestion.content.variants[index].answer} <img style="width:100%; max-width:250px; max-height:250px" src="${currentQuestion.content.variants[index].extendedText}"/>`,
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",

@ -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 (
<Box>
@ -47,9 +44,7 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
>
<RadioGroup
name={currentQuestion.id}
value={currentQuestion.content.variants.findIndex(
({ id }) => 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) => (
<VarimgVariant
key={variant.id}
currentQuestion={currentQuestion}
questionId={currentQuestion.id}
variant={variant}
isSending={isSending}
setIsSending={setIsSending}
@ -116,8 +111,7 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
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 ? (
"Выберите вариант ответа ниже"

@ -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(
<Button
disabled={!isNextButtonEnabled}
variant="contained"
sx={{
fontSize: "16px",
padding: "10px 15px",
"&:disabled": {
background: quizThemes[settings.cfg.theme].isLight ? "#F2F3F7" : "#FFFFFF26"
},
}}
onClick={moveToNextQuestion}
>
Далее
</Button>
)}
export default function NextButton({ isNextButtonEnabled, moveToNextQuestion }: Props) {
const { settings } = useQuizSettings();
return (
<Button
disabled={!isNextButtonEnabled}
variant="contained"
sx={{
fontSize: "16px",
padding: "10px 15px",
"&:disabled": {
background: quizThemes[settings.cfg.theme].isLight ? "#F2F3F7" : "#FFFFFF26",
},
}}
onClick={moveToNextQuestion}
>
Далее
</Button>
);
}

@ -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 (
<Button
disabled={!isPreviousButtonEnabled}
variant="contained"
sx={{
ml: "auto",
fontSize: "16px",
padding: "10px 15px",
color: quizThemes[settings.cfg.theme].isLight ? theme.palette.primary.main : "#FFFFFF",
border: quizThemes[settings.cfg.theme].isLight ? `1px solid ${theme.palette.primary.main}` : "1px solid #9A9AAF",
background: quizThemes[settings.cfg.theme].isLight ? "#FFFFFF" : "#FFFFFF26",
"&:hover": {
color: "#FFFFFF",
border: `1px solid ${theme.palette.primary.dark}`,
},
"&:disabled": {
background: quizThemes[settings.cfg.theme].isLight ? "#F2F3F7" : "#FFFFFF26"
},
}}
onClick={moveToPrevQuestion}
>
{isMobileMini ? "←" : "← Назад"}
</Button>
);
const theme = useTheme();
const { settings } = useQuizSettings();
const isMobileMini = useRootContainerSize() < 382;
return (
<Button
disabled={!isPreviousButtonEnabled}
variant="contained"
sx={{
ml: "auto",
fontSize: "16px",
padding: "10px 15px",
color: quizThemes[settings.cfg.theme].isLight ? theme.palette.primary.main : "#FFFFFF",
border: quizThemes[settings.cfg.theme].isLight
? `1px solid ${theme.palette.primary.main}`
: "1px solid #9A9AAF",
background: quizThemes[settings.cfg.theme].isLight ? "#FFFFFF" : "#FFFFFF26",
"&:hover": {
color: "#FFFFFF",
border: `1px solid ${theme.palette.primary.dark}`,
},
"&:disabled": {
background: quizThemes[settings.cfg.theme].isLight ? "#F2F3F7" : "#FFFFFF26",
},
}}
onClick={moveToPrevQuestion}
>
{isMobileMini ? "←" : "← Назад"}
</Button>
);
}

@ -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<number>(
empty ? -1 : activeItemIndex
);
const [activeItem, setActiveItem] = useState<number>(empty ? -1 : activeItemIndex);
const theme = useTheme();
useEffect(() => {
@ -59,7 +49,6 @@ export const Select = ({
return (
<FormControl
disabled={disabled}
fullWidth
size="small"
sx={{ width: "100%", height: "48px", ...sx }}
@ -67,13 +56,7 @@ export const Select = ({
<MuiSelect
displayEmpty
renderValue={(value) =>
value ? (
items[Number(value)]
) : (
<Typography sx={{ color: colorPlaceholder }}>
{placeholder}
</Typography>
)
value ? items[Number(value)] : <Typography sx={{ color: colorPlaceholder }}>{placeholder}</Typography>
}
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: {

@ -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 (
<Box
sx={{
width: "100%",
height: "100%",
pointerEvents: "none",
"& iframe": {
width: "100%",
height: "100%",
},
...containerSX,
}}
>
<Box
component="video"
sx={{
width: "100%",
height: "100%",
}}
autoPlay
muted
src={videoUrl}
/>
{/* <iframe
src={embedUrl}
title="YouTube video player"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowFullScreen
/> */}
</Box>
);
}

@ -1,16 +1,21 @@
import { QuizQuestionResult } from "@model/questionTypes/result";
export const isResultQuestionEmpty = (resultQuestion: QuizQuestionResult) => {
if (
(resultQuestion.title.length > 0 && resultQuestion.title !== " ")
|| (resultQuestion.description.length > 0 && resultQuestion.description !== " ")
|| (resultQuestion.content.back.length > 0 && resultQuestion.content.back !== " ")
|| (resultQuestion.content.originalBack.length > 0 && resultQuestion.content.originalBack !== " ")
|| (resultQuestion.content.innerName.length > 0 && resultQuestion.content.innerName !== " ")
|| (resultQuestion.content.text.length > 0 && resultQuestion.content.text !== " ")
|| (resultQuestion.content.video.length > 0 && resultQuestion.content.video !== " ")
|| (resultQuestion.content.hint.text.length > 0 && resultQuestion.content.hint.text !== " ")
) return false;
if (
(resultQuestion.title.length > 0 && resultQuestion.title !== " ") ||
(resultQuestion.description.length > 0 && resultQuestion.description !== " ") ||
(resultQuestion.content.back !== null &&
resultQuestion.content.back.length > 0 &&
resultQuestion.content.back !== " ") ||
(resultQuestion.content.originalBack &&
resultQuestion.content.originalBack.length > 0 &&
resultQuestion.content.originalBack !== " ") ||
(resultQuestion.content.innerName.length > 0 && resultQuestion.content.innerName !== " ") ||
(resultQuestion.content.text.length > 0 && resultQuestion.content.text !== " ") ||
(resultQuestion.content.video.length > 0 && resultQuestion.content.video !== " ") ||
(resultQuestion.content.hint.text.length > 0 && resultQuestion.content.hint.text !== " ")
)
return false;
return true;
return true;
};

@ -1,69 +1,23 @@
import { UploadFileType } from "@model/questionTypes/file";
export const MAX_FILE_SIZE = 419430400;
export const UPLOAD_FILE_DESCRIPTIONS_MAP = {
picture: {
title: "Добавить изображение",
description: "Принимает изображения",
},
video: {
title: "Добавить видео",
description: "Принимает .mp4 и .mov формат — максимум 50мб",
},
audio: { title: "Добавить аудиофайл", description: "Принимает аудиофайлы" },
document: { title: "Добавить документ", description: "Принимает документы" },
} as const satisfies Record<UploadFileType, { title: string; description: string; }>;
picture: {
title: "Добавить изображение",
description: "Принимает изображения",
},
video: {
title: "Добавить видео",
description: "Принимает .mp4 и .mov формат — максимум 50мб",
},
audio: { title: "Добавить аудиофайл", description: "Принимает аудиофайлы" },
document: { title: "Добавить документ", description: "Принимает документы" },
} as const satisfies Record<UploadFileType, { title: string; description: string }>;
export const ACCEPT_SEND_FILE_TYPES_MAP = {
picture: [
".jpeg",
".jpg",
".png",
".ico",
".gif",
".tiff",
".webp",
".eps",
".svg"
],
video: [
".mp4",
".mov",
".wmv",
".avi",
".avchd",
".flv",
".f4v",
".swf",
".mkv",
".webm",
".mpeg-2"
],
audio: [
".aac",
".aiff",
".dsd",
".flac",
".mp3",
".mqa",
".ogg",
".wav",
".wma"
],
document: [
".doc",
".docx",
".dotx",
".rtf",
".odt",
".pdf",
".txt",
".xls",
".ppt",
".xlsx",
".pptx",
".pages",
],
picture: [".jpeg", ".jpg", ".png", ".ico", ".gif", ".tiff", ".webp", ".eps", ".svg"],
video: [".mp4", ".mov", ".wmv", ".avi", ".avchd", ".flv", ".f4v", ".swf", ".mkv", ".webm", ".mpeg-2"],
audio: [".aac", ".aiff", ".dsd", ".flac", ".mp3", ".mqa", ".ogg", ".wav", ".wma"],
document: [".doc", ".docx", ".dotx", ".rtf", ".odt", ".pdf", ".txt", ".xls", ".ppt", ".xlsx", ".pptx", ".pages"],
} as const;

@ -11,11 +11,7 @@ export const replaceSpacesToEmptyLines = <T = unknown>(object: T): T => {
for (const [key, value] of Object.entries(object)) {
if (typeof value === "string") {
result[key] = value.replace(
"squiz.pena.digital",
"storage.yandexcloud.net"
);
result[key] = value.replace("squiz.pena.digital", "storage.yandexcloud.net");
continue;
}

@ -1,17 +1,18 @@
import { QuizSettings } from "@model/settingsData";
import { createContext, useContext } from "react";
type QuizData = QuizSettings & {
quizId: string;
preview: boolean;
changeFaviconAndTitle: boolean;
};
export const QuizDataContext = createContext<QuizData | null>(null);
export const useQuizData = () => {
const quizData = useContext(QuizDataContext);
if (quizData === null) throw new Error("QuizData context is null");
return quizData;
export const QuizSettingsContext = createContext<
| (QuizSettings & {
quizId: string;
preview: boolean;
changeFaviconAndTitle: boolean;
})
| null
>(null);
export const useQuizSettings = () => {
const quizSettings = useContext(QuizSettingsContext);
if (quizSettings === null) throw new Error("QuizSettings context is null");
return quizSettings;
};

@ -1,11 +1,10 @@
import { createContext, useContext } from "react";
export const RootContainerWidthContext = createContext<number | null>(null);
export const useRootContainerSize = () => {
const rootContainerSize = useContext(RootContainerWidthContext);
if (rootContainerSize === null) throw new Error("rootContainerSize context is null");
const rootContainerSize = useContext(RootContainerWidthContext);
if (rootContainerSize === null) throw new Error("rootContainerSize context is null");
return rootContainerSize;
return rootContainerSize;
};

@ -1,5 +1,8 @@
import QuizAnswerer from "./components/QuizAnswerer";
import type { QuizSettings } from "@model/settingsData";
import QuizVideo from "./ui_kit/VideoIframe/VideoIframe";
export { QuizAnswerer };
export type { QuizSettings };
export type { QuizSettings, QuizConfig } from "@model/settingsData";
export type * from "./model/widget";
export type * from "@model/questionTypes";
export { QuizAnswerer, QuizVideo };

@ -2,54 +2,54 @@ import { AnyTypedQuizQuestion } from "@model/questionTypes/shared";
import { QuizSettings } from "@model/settingsData";
export interface GetQuizDataResponse {
cnt: number;
settings: {
fp: boolean;
rep: boolean;
name: string;
cfg: string;
lim: number;
due: number;
delay: number;
pausable: boolean;
};
items: {
id: number;
title: string;
desc: string;
typ: string;
req: boolean;
p: number;
c: string;
}[];
show_badge: boolean;
cnt: number;
settings: {
fp: boolean;
rep: boolean;
name: string;
cfg: string;
lim: number;
due: number;
delay: number;
pausable: boolean;
};
items: {
id: number;
title: string;
desc: string;
typ: string;
req: boolean;
p: number;
c: string;
}[];
show_badge: boolean;
}
export function parseQuizData(quizDataResponse: GetQuizDataResponse): Omit<QuizSettings, "recentlyCompleted"> {
const items: QuizSettings["questions"] = quizDataResponse.items.map((item) => {
const content = JSON.parse(item.c);
const items: QuizSettings["questions"] = quizDataResponse.items.map((item) => {
const content = JSON.parse(item.c);
return {
description: item.desc,
id: item.id,
page: item.p,
required: item.req,
title: item.title,
type: item.typ,
content
} as unknown as AnyTypedQuizQuestion;
});
return {
description: item.desc,
id: item.id,
page: item.p,
required: item.req,
title: item.title,
type: item.typ,
content,
} as unknown as AnyTypedQuizQuestion;
});
const settings: QuizSettings["settings"] = {
fp: quizDataResponse.settings.fp,
rep: quizDataResponse.settings.rep,
name: quizDataResponse.settings.name,
cfg: JSON.parse(quizDataResponse?.settings.cfg),
lim: quizDataResponse.settings.lim,
due: quizDataResponse.settings.due,
delay: quizDataResponse.settings.delay,
pausable: quizDataResponse.settings.pausable
};
const settings: QuizSettings["settings"] = {
fp: quizDataResponse.settings.fp,
rep: quizDataResponse.settings.rep,
name: quizDataResponse.settings.name,
cfg: JSON.parse(quizDataResponse?.settings.cfg),
lim: quizDataResponse.settings.lim,
due: quizDataResponse.settings.due,
delay: quizDataResponse.settings.delay,
pausable: quizDataResponse.settings.pausable,
};
return { cnt: quizDataResponse.cnt, settings, questions: items, show_badge: quizDataResponse.show_badge };
return { cnt: quizDataResponse.cnt, settings, questions: items, show_badge: quizDataResponse.show_badge };
}

@ -1,8 +1 @@
export type MetricsMessengers =
| "telegram"
| "viber"
| "whatsapp"
| "vkontakte"
| "messenger"
| "skype"
| "instagram";
export type MetricsMessengers = "telegram" | "viber" | "whatsapp" | "vkontakte" | "messenger" | "skype" | "instagram";

@ -1,8 +1,4 @@
import type {
QuizQuestionBase,
QuestionHint,
QuestionBranchingRule,
} from "./shared";
import type { QuizQuestionBase, QuestionHint, QuestionBranchingRule } from "./shared";
export interface QuizQuestionDate extends QuizQuestionBase {
type: "date";
@ -18,8 +14,8 @@ export interface QuizQuestionDate extends QuizQuestionBase {
time: boolean;
hint: QuestionHint;
rule: QuestionBranchingRule;
back: string;
originalBack: string;
back: string | null;
originalBack: string | null;
autofill: boolean;
};
}

@ -1,9 +1,4 @@
import type {
QuizQuestionBase,
QuestionVariant,
QuestionHint,
QuestionBranchingRule,
} from "./shared";
import type { QuizQuestionBase, QuestionVariant, QuestionHint, QuestionBranchingRule } from "./shared";
export interface QuizQuestionEmoji extends QuizQuestionBase {
type: "emoji";
@ -22,8 +17,8 @@ export interface QuizQuestionEmoji extends QuizQuestionBase {
variants: QuestionVariant[];
hint: QuestionHint;
rule: QuestionBranchingRule;
back: string;
originalBack: string;
back: string | null;
originalBack: string | null;
autofill: boolean;
};
}

@ -1,8 +1,4 @@
import type {
QuizQuestionBase,
QuestionHint,
QuestionBranchingRule,
} from "./shared";
import type { QuizQuestionBase, QuestionHint, QuestionBranchingRule } from "./shared";
export const UPLOAD_FILE_TYPES_MAP = {
picture: "Изображения",
@ -28,7 +24,7 @@ export interface QuizQuestionFile extends QuizQuestionBase {
type: UploadFileType;
hint: QuestionHint;
rule: QuestionBranchingRule;
back: string;
originalBack: string;
back: string | null;
originalBack: string | null;
};
}

@ -1,37 +1,32 @@
import type {
QuestionHint,
QuestionVariant,
QuizQuestionBase,
QuestionBranchingRule,
} from "./shared";
import type { QuestionHint, QuestionVariant, QuizQuestionBase, QuestionBranchingRule } from "./shared";
export interface QuizQuestionImages extends QuizQuestionBase {
type: "images";
content: {
id: string;
/** Чекбокс "Вариант "свой ответ"" */
own: boolean;
/** Чекбокс "Можно несколько" */
multi: boolean;
/** Пропорции */
xy: "1:1" | "1:2" | "2:1";
/** Чекбокс "Внутреннее название вопроса" */
innerNameCheck: boolean;
/** Поле "Внутреннее название вопроса" */
innerName: string;
/** Чекбокс "Большие картинки" */
large: boolean;
/** Форма */
format: "carousel" | "masonry";
/** Чекбокс "Необязательный вопрос" */
required: boolean;
/** Варианты (картинки) */
variants: QuestionVariant[];
hint: QuestionHint;
rule: QuestionBranchingRule;
back: string | null;
originalBack: string | null;
autofill: boolean;
largeCheck: boolean;
};
type: "images";
content: {
id: string;
/** Чекбокс "Вариант "свой ответ"" */
own: boolean;
/** Чекбокс "Можно несколько" */
multi: boolean;
/** Пропорции */
xy: "1:1" | "1:2" | "2:1";
/** Чекбокс "Внутреннее название вопроса" */
innerNameCheck: boolean;
/** Поле "Внутреннее название вопроса" */
innerName: string;
/** Чекбокс "Большие картинки" */
large: boolean;
/** Форма */
format: "carousel" | "masonry";
/** Чекбокс "Необязательный вопрос" */
required: boolean;
/** Варианты (картинки) */
variants: QuestionVariant[];
hint: QuestionHint;
rule: QuestionBranchingRule;
back: string | null;
originalBack: string | null;
autofill: boolean;
largeCheck: boolean;
};
}

@ -0,0 +1,13 @@
export * from "./date";
export * from "./emoji";
export * from "./file";
export * from "./images";
export * from "./number";
export * from "./page";
export * from "./rating";
export * from "./result";
export * from "./select";
export * from "./shared";
export * from "./text";
export * from "./variant";
export * from "./varimg";

@ -1,8 +1,4 @@
import type {
QuizQuestionBase,
QuestionHint,
QuestionBranchingRule,
} from "./shared";
import type { QuizQuestionBase, QuestionHint, QuestionBranchingRule } from "./shared";
export interface QuizQuestionNumber extends QuizQuestionBase {
type: "number";
@ -27,8 +23,8 @@ export interface QuizQuestionNumber extends QuizQuestionBase {
chooseRange: boolean;
hint: QuestionHint;
rule: QuestionBranchingRule;
back: string;
originalBack: string;
back: string | null;
originalBack: string | null;
autofill: boolean;
form: "star" | "trophie" | "flag" | "heart" | "like" | "bubble" | "hashtag";
};

@ -1,8 +1,4 @@
import type {
QuizQuestionBase,
QuestionHint,
QuestionBranchingRule,
} from "./shared";
import type { QuizQuestionBase, QuestionHint, QuestionBranchingRule } from "./shared";
export interface QuizQuestionPage extends QuizQuestionBase {
type: "page";
@ -19,8 +15,8 @@ export interface QuizQuestionPage extends QuizQuestionBase {
video: string;
hint: QuestionHint;
rule: QuestionBranchingRule;
back: string;
originalBack: string;
back: string | null;
originalBack: string | null;
autofill: boolean;
};
}

@ -1,8 +1,4 @@
import type {
QuizQuestionBase,
QuestionHint,
QuestionBranchingRule,
} from "./shared";
import type { QuizQuestionBase, QuestionHint, QuestionBranchingRule } from "./shared";
export interface QuizQuestionRating extends QuizQuestionBase {
type: "rating";
@ -20,8 +16,8 @@ export interface QuizQuestionRating extends QuizQuestionBase {
form: string;
hint: QuestionHint;
rule: QuestionBranchingRule;
back: string;
originalBack: string;
back: string | null;
originalBack: string | null;
autofill: boolean;
/** Позитивное описание рейтинга */
ratingPositiveDescription: string;

@ -1,26 +1,23 @@
import type {
QuizQuestionBase,
QuestionBranchingRule,
QuestionHint,
} from "./shared";
import type { QuizQuestionBase, QuestionBranchingRule, QuestionHint } from "./shared";
interface ResultQuestionBranchingRule extends QuestionBranchingRule {
minScore?: number
minScore?: number;
}
export interface QuizQuestionResult extends QuizQuestionBase {
type: "result";
content: {
id: string;
back: string;
originalBack: string;
back: string | null;
originalBack: string | null;
video: string;
innerName: string;
text: string;
price: [number] | [number, number];
useImage: boolean;
rule: ResultQuestionBranchingRule,
rule: ResultQuestionBranchingRule;
hint: QuestionHint;
autofill: boolean;
redirect: string
usage: boolean;
redirect: string;
};
}

@ -1,9 +1,4 @@
import type {
QuizQuestionBase,
QuestionVariant,
QuestionHint,
QuestionBranchingRule,
} from "./shared";
import type { QuizQuestionBase, QuestionVariant, QuestionHint, QuestionBranchingRule } from "./shared";
export interface QuizQuestionSelect extends QuizQuestionBase {
type: "select";
@ -22,8 +17,8 @@ export interface QuizQuestionSelect extends QuizQuestionBase {
variants: QuestionVariant[];
rule: QuestionBranchingRule;
hint: QuestionHint;
back: string;
originalBack: string;
back: string | null;
originalBack: string | null;
autofill: boolean;
};
}

@ -1,4 +1,3 @@
import { nanoid } from "nanoid";
import type { QuizQuestionDate } from "./date";
import type { QuizQuestionEmoji } from "./emoji";
import type { QuizQuestionFile } from "./file";
@ -13,115 +12,100 @@ import type { QuizQuestionVariant } from "./variant";
import type { QuizQuestionVarImg } from "./varimg";
export interface QuestionBranchingRuleMain {
next: string;
or: boolean;
rules: {
question: string; //id родителя (пока что)
answers: string[];
}[];
next: string;
or: boolean;
rules: {
question: string; //id родителя (пока что)
answers: string[];
}[];
}
export interface QuestionBranchingRule {
children: string[];
//список условий
main: QuestionBranchingRuleMain[];
parentId: string | null | "root";
default: string;
children: string[];
//список условий
main: QuestionBranchingRuleMain[];
parentId: string | null | "root";
default: string;
}
export interface QuestionHint {
/** Текст подсказки */
text: string;
/** URL видео подсказки */
video: string;
/** Текст подсказки */
text: string;
/** URL видео подсказки */
video: string;
}
export type QuestionVariant = {
id: string;
/** Текст */
answer: string;
/** Текст подсказки */
hints: string;
/** Дополнительное поле для текста, emoji, ссылки на картинку */
extendedText: string;
/** Оригинал изображения (до кропа) */
originalImageUrl: string;
points?: number;
id: string;
/** Текст */
answer: string;
/** Текст подсказки */
hints: string;
/** Дополнительное поле для текста, emoji, ссылки на картинку */
extendedText: string;
/** Оригинал изображения (до кропа) */
originalImageUrl: string;
points?: number;
};
export type QuestionType =
| "variant"
| "images"
| "varimg"
| "emoji"
| "text"
| "select"
| "date"
| "number"
| "file"
| "page"
| "rating"
| "result";
| "variant"
| "images"
| "varimg"
| "emoji"
| "text"
| "select"
| "date"
| "number"
| "file"
| "page"
| "rating"
| "result";
export interface QuizQuestionBase {
/** Stable id, generated on client */
backendId: number;
/** Stable id, generated on client */
id: string;
quizId: number;
title: string;
description: string;
page: number;
type?: QuestionType | null;
expanded: boolean;
openedModalSettings: boolean;
deleted: boolean;
required: boolean;
deleteTimeoutId: number;
content: {
id: string;
quizId: number;
title: string;
description: string;
page: number;
type?: QuestionType | null;
expanded: boolean;
openedModalSettings: boolean;
deleted: boolean;
required: boolean;
deleteTimeoutId: number;
content: {
id: string;
hint: QuestionHint;
rule: QuestionBranchingRule;
back: string | null;
originalBack: string | null;
autofill: boolean;
};
hint: QuestionHint;
rule: QuestionBranchingRule;
back: string | null;
originalBack: string | null;
autofill: boolean;
};
}
export type AnyTypedQuizQuestion =
| QuizQuestionVariant
| QuizQuestionImages
| QuizQuestionVarImg
| QuizQuestionEmoji
| QuizQuestionText
| QuizQuestionSelect
| QuizQuestionDate
| QuizQuestionNumber
| QuizQuestionFile
| QuizQuestionPage
| QuizQuestionRating
| QuizQuestionResult;
| QuizQuestionVariant
| QuizQuestionImages
| QuizQuestionVarImg
| QuizQuestionEmoji
| QuizQuestionText
| QuizQuestionSelect
| QuizQuestionDate
| QuizQuestionNumber
| QuizQuestionFile
| QuizQuestionPage
| QuizQuestionRating
| QuizQuestionResult;
export type RealTypedQuizQuestion = Exclude<AnyTypedQuizQuestion, QuizQuestionResult>;
type FilterQuestionsWithVariants<T> = T extends {
content: { variants: QuestionVariant[]; };
} ? T : never;
content: { variants: QuestionVariant[] };
}
? T
: never;
export type QuizQuestionsWithVariants = FilterQuestionsWithVariants<AnyTypedQuizQuestion>;
export const createBranchingRuleMain: (targetId: string, parentId: string) => QuestionBranchingRuleMain = (targetId, parentId) => ({
next: targetId,
or: false,
rules: [{
question: parentId,
answers: [] as string[],
}]
});
export const createQuestionVariant: () => QuestionVariant = () => ({
id: nanoid(),
answer: "",
extendedText: "",
hints: "",
originalImageUrl: "",
});

@ -1,8 +1,4 @@
import type {
QuizQuestionBase,
QuestionHint,
QuestionBranchingRule,
} from "./shared";
import type { QuizQuestionBase, QuestionHint, QuestionBranchingRule } from "./shared";
export interface QuizQuestionText extends QuizQuestionBase {
type: "text";
@ -17,11 +13,11 @@ export interface QuizQuestionText extends QuizQuestionBase {
required: boolean;
/** Чекбокс "Автозаполнение адреса" */
autofill: boolean;
answerType: "single" | "multi";
answerType: "single" | "multi" | "numberOnly";
hint: QuestionHint;
rule: QuestionBranchingRule;
back: string;
originalBack: string;
back: string | null;
originalBack: string | null;
onlyNumbers: boolean;
};
}

@ -1,9 +1,4 @@
import type {
QuizQuestionBase,
QuestionVariant,
QuestionHint,
QuestionBranchingRule,
} from "./shared";
import type { QuizQuestionBase, QuestionVariant, QuestionHint, QuestionBranchingRule } from "./shared";
export interface QuizQuestionVariant extends QuizQuestionBase {
type: "variant";
@ -25,8 +20,8 @@ export interface QuizQuestionVariant extends QuizQuestionBase {
variants: QuestionVariant[];
hint: QuestionHint;
rule: QuestionBranchingRule;
back: string;
originalBack: string;
back: string | null;
originalBack: string | null;
autofill: boolean;
};
}

@ -1,29 +1,24 @@
import type {
QuestionHint,
QuestionVariant,
QuizQuestionBase,
QuestionBranchingRule,
} from "./shared";
import type { QuestionHint, QuestionVariant, QuizQuestionBase, QuestionBranchingRule } from "./shared";
export interface QuizQuestionVarImg extends QuizQuestionBase {
type: "varimg";
content: {
id: string;
/** Чекбокс "Вариант "свой ответ"" */
own: boolean;
/** Чекбокс "Внутреннее название вопроса" */
innerNameCheck: boolean;
/** Поле "Внутреннее название вопроса" */
innerName: string;
/** Чекбокс "Необязательный вопрос" */
required: boolean;
variants: QuestionVariant[];
hint: QuestionHint;
rule: QuestionBranchingRule;
back: string;
originalBack: string;
autofill: boolean;
largeCheck: boolean;
replText: string;
};
type: "varimg";
content: {
id: string;
/** Чекбокс "Вариант "свой ответ"" */
own: boolean;
/** Чекбокс "Внутреннее название вопроса" */
innerNameCheck: boolean;
/** Поле "Внутреннее название вопроса" */
innerName: string;
/** Чекбокс "Необязательный вопрос" */
required: boolean;
variants: QuestionVariant[];
hint: QuestionHint;
rule: QuestionBranchingRule;
back: string | null;
originalBack: string | null;
autofill: boolean;
largeCheck: boolean;
replText: string;
};
}

@ -1,4 +1,4 @@
import {AnyTypedQuizQuestion} from "./questionTypes/shared";
import { AnyTypedQuizQuestion } from "./questionTypes/shared";
export type QuizStartpageType = "standard" | "expanded" | "centered" | null;
@ -50,7 +50,7 @@ export type QuizSettingsConfig = {
delay: number;
pausable: boolean;
cfg: QuizConfig;
}
};
export type QuizSettings = {
questions: AnyTypedQuizQuestion[];
@ -110,16 +110,13 @@ export interface QuizConfig {
law?: string;
};
meta: string;
yandexMetricsNumber: number | undefined;
vkMetricsNumber: number | undefined;
antifraud?: boolean;
showfc?: boolean;
yandexMetricsNumber?: number;
vkMetricsNumber?: number;
}
export type FormContactFieldName =
| "name"
| "email"
| "phone"
| "text"
| "address";
export type FormContactFieldName = "name" | "email" | "phone" | "text" | "address";
export type FormContactFieldData = {
text: string;

@ -0,0 +1,28 @@
export interface BannerWidgetComponentProps {
quizId: string;
position: "topleft" | "topright" | "bottomleft" | "bottomright";
onWidgetClose?: () => void;
dialogDimensions?: { width: string; height: string };
appealText?: string;
quizHeaderText?: string;
buttonTextColor?: string;
buttonBackgroundColor?: string;
/**
* Показывать виджет через X секунд
*/
autoShowWidgetTime?: number;
/**
* Открыть квиз через X секунд, 0 - сразу, null - не открывать
*/
autoShowQuizTime?: number | null;
openOnLeaveAttempt?: boolean;
buttonFlash?: boolean;
hideOnMobile?: boolean;
withShadow?: boolean;
rounded?: boolean;
bannerFullWidth?: boolean;
pulsation?: boolean;
fullScreen?: boolean;
}
export type BannerWidgetParams = Omit<BannerWidgetComponentProps, "onWidgetClose">;

@ -0,0 +1,30 @@
export interface ButtonWidgetComponentProps {
quizId: string;
fixedSide?: "left" | "right";
dialogDimensions?: { width: string; height: string };
/**
* Открыть квиз через X секунд, 0 - сразу, null - не открывать
*/
autoShowQuizTime?: number | null;
hideOnMobile?: boolean;
openOnLeaveAttempt?: boolean;
buttonFlash?: boolean;
withShadow?: boolean;
rounded?: boolean;
buttonText?: string;
buttonTextColor?: string;
buttonBackgroundColor?: string;
fullScreen?: boolean;
}
export type ButtonWidgetParams = Omit<ButtonWidgetComponentProps, "fixedSide"> & {
selector: string;
/**
* In seconds, null - polling disabled
*/
selectorPollingTimeLimit?: number | null;
};
export type ButtonWidgetFixedParams = Omit<ButtonWidgetComponentProps, "selector"> & {
fixedSide: "left" | "right";
};

@ -0,0 +1,15 @@
import { ButtonWidgetComponentProps } from "./button";
export type ContainerWidgetComponentProps = ButtonWidgetComponentProps & {
quizId: string;
showButtonOnMobile?: boolean;
dimensions?: { width: string; height: string };
};
export type ContainerWidgetParams = ContainerWidgetComponentProps & {
selector: string;
/**
* In seconds, null - polling disabled
*/
selectorPollingTimeLimit?: number | null;
};

@ -0,0 +1,7 @@
export * from "./banner";
export * from "./button";
export * from "./container";
export * from "./popup";
export * from "./side";
export type WidgetType = "container" | "button" | "popup" | "banner" | "side";

13
lib/model/widget/popup.ts Normal file

@ -0,0 +1,13 @@
export interface PopupWidgetComponentProps {
quizId: string;
dialogDimensions?: { width: string; height: string };
/**
* Открыть квиз через X секунд, 0 - сразу, null - не открывать
*/
autoShowQuizTime?: number | null;
hideOnMobile?: boolean;
openOnLeaveAttempt?: boolean;
fullScreen?: boolean;
}
export type PopupWidgetParams = PopupWidgetComponentProps;

20
lib/model/widget/side.ts Normal file

@ -0,0 +1,20 @@
export interface SideWidgetComponentProps {
quizId: string;
position: "left" | "right";
buttonBackgroundColor?: string;
buttonTextColor?: string;
dialogDimensions?: { width: string; height: string };
fullScreen?: boolean;
buttonFlash?: boolean;
/**
* Показывать виджет через X секунд
*/
autoShowWidgetTime?: number;
/**
* Открыть квиз через X секунд, 0 - сразу, null - не открывать
*/
autoShowQuizTime?: number | null;
hideOnMobile?: boolean;
}
export type SideWidgetParams = SideWidgetComponentProps;

@ -5,100 +5,143 @@ import { nanoid } from "nanoid";
import { createContext, useContext } from "react";
import { createStore, useStore } from "zustand";
import { immer } from "zustand/middleware/immer";
import { devtools } from "zustand/middleware";
export type Answer = string | string[] | Moment;
type QuestionAnswer = {
questionId: string;
answer: Answer
export type QuestionAnswer = {
questionId: string;
answer: Answer;
};
type OwnVariant = {
id: string;
variant: QuestionVariant;
id: string;
variant: QuestionVariant;
};
interface QuizViewStore {
answers: QuestionAnswer[];
ownVariants: OwnVariant[];
pointsSum: number;
points: Record<string, number>;
currentQuizStep: QuizStep;
answers: QuestionAnswer[];
ownVariants: OwnVariant[];
pointsSum: number;
points: Record<string, number>;
currentQuizStep: QuizStep;
}
interface QuizViewActions {
updateAnswer: (questionId: string, answer: string | string[] | Moment, points: number) => void;
deleteAnswer: (questionId: string) => void;
updateOwnVariant: (id: string, answer: string) => void;
deleteOwnVariant: (id: string) => void;
setCurrentQuizStep: (step: QuizStep) => void;
updateAnswer: (questionId: string, answer: string | string[] | Moment, points: number) => void;
deleteAnswer: (questionId: string) => void;
updateOwnVariant: (id: string, answer: string) => void;
deleteOwnVariant: (id: string) => void;
setCurrentQuizStep: (step: QuizStep) => void;
}
export const QuizViewContext = createContext<ReturnType<typeof createQuizViewStore> | null>(null);
export function useQuizViewStore<U>(selector: (state: QuizViewStore & QuizViewActions) => U): U {
const store = useContext(QuizViewContext);
if (!store) throw new Error("QuizViewStore context is null");
const store = useContext(QuizViewContext);
if (!store) throw new Error("QuizViewStore context is null");
return useStore(store, selector);
return useStore(store, selector);
}
export const createQuizViewStore = () => createStore<QuizViewStore & QuizViewActions>()(
export const createQuizViewStore = () =>
createStore<QuizViewStore & QuizViewActions>()(
immer(
devtools(
(set, get) => ({
answers: [],
ownVariants: [],
points: {},
pointsSum: 0,
currentQuizStep: "startpage",
updateAnswer(questionId, answer, points) {
set(state => {
const index = state.answers.findIndex(answer => questionId === answer.questionId);
answers: [],
ownVariants: [],
points: {},
pointsSum: 0,
currentQuizStep: "startpage",
updateAnswer(questionId, answer, points) {
set(
(state) => {
const index = state.answers.findIndex((answer) => questionId === answer.questionId);
if (index < 0) {
state.answers.push({ questionId, answer });
} else {
state.answers[index] = { questionId, answer };
}
if (index < 0) {
state.answers.push({ questionId, answer });
} else {
state.answers[index] = { questionId, answer };
}
state.points = { ...state.points, ...{ [questionId]: points } };
state.points = { ...state.points, ...{ [questionId]: points } };
state.pointsSum = Object.values(state.points).reduce((sum, value) => sum + value);
});
},
deleteAnswer(questionId) {
set(state => {
state.answers = state.answers.filter(answer => questionId !== answer.questionId);
});
},
updateOwnVariant(id, answer) {
set(state => {
const index = state.ownVariants.findIndex((variant) => variant.id === id);
state.pointsSum = Object.values(state.points).reduce((sum, value) => sum + value);
},
false,
{
type: "updateAnswer",
questionId,
answer,
points,
}
);
},
deleteAnswer(questionId) {
set(
(state) => {
state.answers = state.answers.filter((answer) => questionId !== answer.questionId);
},
false,
{
type: "deleteAnswer",
questionId,
}
);
},
updateOwnVariant(id, answer) {
set(
(state) => {
const index = state.ownVariants.findIndex((variant) => variant.id === id);
if (index < 0) {
state.ownVariants.push({
id,
variant: {
id: nanoid(),
answer,
extendedText: "",
hints: "",
originalImageUrl: "",
},
});
} else {
state.ownVariants[index].variant.answer = answer;
}
});
},
deleteOwnVariant(id) {
set(state => {
state.ownVariants = state.ownVariants.filter((variant) => variant.id !== id);
});
},
setCurrentQuizStep(step) {
set({ currentQuizStep: step });
},
})
if (index < 0) {
state.ownVariants.push({
id,
variant: {
id: nanoid(),
answer,
extendedText: "",
hints: "",
originalImageUrl: "",
},
});
} else {
state.ownVariants[index].variant.answer = answer;
}
},
false,
{
type: "updateOwnVariant",
id,
answer,
}
);
},
deleteOwnVariant(id) {
set(
(state) => {
state.ownVariants = state.ownVariants.filter((variant) => variant.id !== id);
},
false,
{
type: "deleteOwnVariant",
id,
}
);
},
setCurrentQuizStep(step) {
set({ currentQuizStep: step }, false, {
type: "setCurrentQuizStep",
step,
});
},
}),
{
name: "QuizViewStore-" + nanoid(4),
enabled: import.meta.env.DEV,
trace: import.meta.env.DEV,
}
)
)
);
);

Some files were not shown because too many files have changed in this diff Show More