обрезано
This commit is contained in:
parent
e5d5119628
commit
c6d8cc3e68
@ -32,19 +32,14 @@ export function useQuizData(quizId: string, preview: boolean = false) {
|
|||||||
needConfig: true,
|
needConfig: true,
|
||||||
});
|
});
|
||||||
//firstData.settings.status = "ai";
|
//firstData.settings.status = "ai";
|
||||||
console.log("useQuizData: firstData received:", firstData);
|
|
||||||
console.log("useQuizData: firstData.settings:", firstData.settings);
|
|
||||||
|
|
||||||
initDataManager({
|
initDataManager({
|
||||||
status: firstData.settings.status,
|
status: firstData.settings.status,
|
||||||
haveRoot: firstData.settings.cfg.haveRoot,
|
haveRoot: firstData.settings.cfg.haveRoot,
|
||||||
});
|
});
|
||||||
console.log("useQuizData: calling setQuizData with firstData");
|
|
||||||
setQuizData(firstData);
|
setQuizData(firstData);
|
||||||
|
|
||||||
// Определяем нужно ли загружать все данные
|
// Определяем нужно ли загружать все данные
|
||||||
console.log("Определяем нужно ли загружать все данные");
|
|
||||||
console.log(firstData.settings.status);
|
|
||||||
if (!["ai"].includes(firstData.settings.status)) {
|
if (!["ai"].includes(firstData.settings.status)) {
|
||||||
setNeedFullLoad(true); // Триггерит новый запрос через изменение ключа
|
setNeedFullLoad(true); // Триггерит новый запрос через изменение ключа
|
||||||
return firstData;
|
return firstData;
|
||||||
@ -74,15 +69,10 @@ export function useQuizData(quizId: string, preview: boolean = false) {
|
|||||||
limit: 1,
|
limit: 1,
|
||||||
needConfig: false,
|
needConfig: false,
|
||||||
});
|
});
|
||||||
console.log(
|
|
||||||
"AI RESPONSE AI RESPONSE AI RESPONSE AI RESPONSE AI RESPONSE AI RESPONSE AI RESPONSE AI RESPONSE AI RESPONSE AI RESPONSE AI RESPONSE AI RESPONSE AI RESPONSE AI RESPONSE AI RESPONSE AI RESPONSE AI RESPONSE AI RESPONSE AI RESPONSE AI RESPONSE AI RESPONSE AI RESPONSE AI RESPONSE AI RESPONSE AI RESPONSE AI RESPONSE AI RESPONSE AI RESPONSE AI RESPONSE AI RESPONSE "
|
|
||||||
);
|
|
||||||
console.log(data);
|
|
||||||
addQuestions(data.questions);
|
addQuestions(data.questions);
|
||||||
changeNextLoading(false);
|
changeNextLoading(false);
|
||||||
return data;
|
return data;
|
||||||
} catch (p) {
|
} catch (p) {
|
||||||
console.log(p);
|
|
||||||
setPage(questions.length);
|
setPage(questions.length);
|
||||||
changeNextLoading(false);
|
changeNextLoading(false);
|
||||||
}
|
}
|
||||||
|
@ -168,10 +168,7 @@ export async function getAndParceData(props: GetDataProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Парсим строки в строках
|
//Парсим строки в строках
|
||||||
console.log("до парса_______________________");
|
|
||||||
const quizSettings = replaceSpacesToEmptyLines(parseQuizData(quizDataResponse));
|
const quizSettings = replaceSpacesToEmptyLines(parseQuizData(quizDataResponse));
|
||||||
console.log("после парса_______________________");
|
|
||||||
console.log(quizSettings);
|
|
||||||
//Единоразово стрингифаим ВСЁ распаршенное и удаляем лишние пробелы
|
//Единоразово стрингифаим ВСЁ распаршенное и удаляем лишние пробелы
|
||||||
const res = JSON.parse(
|
const res = JSON.parse(
|
||||||
JSON.stringify({ data: quizSettings })
|
JSON.stringify({ data: quizSettings })
|
||||||
|
@ -55,13 +55,6 @@ function QuizAnswererInner({
|
|||||||
addquizid(quizId);
|
addquizid(quizId);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log(settings);
|
|
||||||
console.log(questions);
|
|
||||||
console.log("r");
|
|
||||||
console.log(r);
|
|
||||||
}, [questions, settings]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
vkMetrics.quizOpened();
|
vkMetrics.quizOpened();
|
||||||
@ -72,7 +65,6 @@ function QuizAnswererInner({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
//Хук на случай если данные переданы нам сразу, а не "нам нужно их запросить"
|
//Хук на случай если данные переданы нам сразу, а не "нам нужно их запросить"
|
||||||
if (quizSettings !== undefined) {
|
if (quizSettings !== undefined) {
|
||||||
console.log("QuizAnswerer: calling setQuizData with quizSettings");
|
|
||||||
setQuizData(quizSettings);
|
setQuizData(quizSettings);
|
||||||
initDataManager({
|
initDataManager({
|
||||||
status: quizSettings.settings.status,
|
status: quizSettings.settings.status,
|
||||||
@ -98,11 +90,7 @@ function QuizAnswererInner({
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
console.log("settings");
|
|
||||||
console.log(settings);
|
|
||||||
if (isLoading && !questions.length) return <LoadingSkeleton />;
|
if (isLoading && !questions.length) return <LoadingSkeleton />;
|
||||||
console.log("error");
|
|
||||||
console.log(error);
|
|
||||||
if (error) return <ApologyPage error={error} />;
|
if (error) return <ApologyPage error={error} />;
|
||||||
|
|
||||||
if (Object.keys(settings).length == 0) return <ApologyPage error={new Error("quiz data is null")} />;
|
if (Object.keys(settings).length == 0) return <ApologyPage error={new Error("quiz data is null")} />;
|
||||||
|
@ -6,11 +6,7 @@ type Props = Partial<FallbackProps>;
|
|||||||
|
|
||||||
export const ApologyPage = ({ error }: Props) => {
|
export const ApologyPage = ({ error }: Props) => {
|
||||||
let message = error.message || error.response?.data || " ";
|
let message = error.message || error.response?.data || " ";
|
||||||
console.log("message");
|
|
||||||
console.log(message.toLowerCase());
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
console.log("t");
|
|
||||||
console.log(t(message.toLowerCase()));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
|
@ -1,351 +0,0 @@
|
|||||||
import { useEffect, useRef, useState } from "react";
|
|
||||||
import { Box, Button, Link, Typography, useTheme } from "@mui/material";
|
|
||||||
import { enqueueSnackbar } from "notistack";
|
|
||||||
|
|
||||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
|
||||||
|
|
||||||
import { Inputs } from "@/components/ViewPublicationPage/ContactForm/Inputs/Inputs";
|
|
||||||
import { ContactTextBlock } from "./ContactTextBlock";
|
|
||||||
|
|
||||||
import { useRootContainerSize } from "@contexts/RootContainerWidthContext";
|
|
||||||
|
|
||||||
import { sendFC, SendFCParams } from "@api/quizRelase";
|
|
||||||
|
|
||||||
import { useVkMetricsGoals } from "@/utils/hooks/metrics/useVkMetricsGoals";
|
|
||||||
import { useYandexMetricsGoals } from "@/utils/hooks/metrics/useYandexMetricsGoals";
|
|
||||||
|
|
||||||
import { EMAIL_REGEXP } from "@utils/emailRegexp";
|
|
||||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
|
||||||
import { DESIGN_LIST } from "@utils/designList";
|
|
||||||
|
|
||||||
import { NameplateLogo } from "@icons/NameplateLogo";
|
|
||||||
|
|
||||||
import type { FormContactFieldData, FormContactFieldName } from "@model/settingsData";
|
|
||||||
import type { QuizQuestionResult } from "@model/questionTypes/result";
|
|
||||||
import type { AnyTypedQuizQuestion } from "@model/questionTypes/shared";
|
|
||||||
import { isProduction } from "@/utils/defineDomain";
|
|
||||||
import { useQuizStore } from "@/stores/useQuizStore";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
currentQuestion: AnyTypedQuizQuestion;
|
|
||||||
onShowResult: () => void;
|
|
||||||
};
|
|
||||||
//Костыль для особого квиза. Для него не нужно показывать email адрес
|
|
||||||
const isDisableEmail = window.location.pathname.includes("/377c7570-1bee-4320-ac1e-d731b6223ce8");
|
|
||||||
|
|
||||||
export const ContactForm = ({ currentQuestion, onShowResult }: Props) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const { settings, questions, quizId, show_badge, preview } = useQuizStore();
|
|
||||||
|
|
||||||
const [ready, setReady] = useState(false);
|
|
||||||
const [name, setName] = useState("");
|
|
||||||
const [email, setEmail] = useState("");
|
|
||||||
const [phone, setPhone] = useState("");
|
|
||||||
const [text, setText] = useState("");
|
|
||||||
const [adress, setAdress] = useState("");
|
|
||||||
const [screenHeight, setScreenHeight] = useState<number>(window.innerHeight);
|
|
||||||
|
|
||||||
const fireOnce = useRef(true);
|
|
||||||
const [fire, setFire] = useState(false);
|
|
||||||
const isMobile = useRootContainerSize() < 850;
|
|
||||||
const isTablet = useRootContainerSize() < 1000;
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const vkMetrics = useVkMetricsGoals(settings.cfg.vkMetricsNumber);
|
|
||||||
const yandexMetrics = useYandexMetricsGoals(settings.cfg.yandexMetricsNumber);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
function handleResize() {
|
|
||||||
setScreenHeight(window.innerHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener("resize", handleResize);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener("resize", handleResize);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const resultQuestion =
|
|
||||||
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 (!resultQuestion) throw new Error("Result question not found");
|
|
||||||
|
|
||||||
const inputHC = async () => {
|
|
||||||
const FC = settings.cfg.formContact.fields || settings.cfg.formContact;
|
|
||||||
const body: SendFCParams["body"] = {};
|
|
||||||
if (name.length > 0) body.name = name;
|
|
||||||
if (email.length > 0) body.email = email;
|
|
||||||
if (phone.length > 0) body.phone = phone;
|
|
||||||
if (adress.length > 0) body.address = adress;
|
|
||||||
if (text.length > 0) body.customs = { [FC.text.text || t("Last name")]: text };
|
|
||||||
|
|
||||||
if (Object.keys(body).length > 0) {
|
|
||||||
try {
|
|
||||||
await sendFC({
|
|
||||||
questionId: currentQuestion.id,
|
|
||||||
body: body,
|
|
||||||
qid: quizId,
|
|
||||||
preview,
|
|
||||||
});
|
|
||||||
|
|
||||||
const sessions = JSON.parse(localStorage.getItem("sessions") || "{}");
|
|
||||||
localStorage.setItem("sessions", JSON.stringify({ ...sessions, [quizId]: new Date().getTime() }));
|
|
||||||
} catch (e) {
|
|
||||||
enqueueSnackbar(t("The answer was not counted"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const FCcopy: Record<FormContactFieldName, FormContactFieldData> =
|
|
||||||
settings.cfg.formContact.fields || settings.cfg.formContact;
|
|
||||||
|
|
||||||
const filteredFC: Partial<Record<FormContactFieldName, FormContactFieldData>> = {};
|
|
||||||
for (const i in FCcopy) {
|
|
||||||
const field = FCcopy[i as keyof typeof FCcopy];
|
|
||||||
if (field.used) {
|
|
||||||
filteredFC[i as FormContactFieldName] = field;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleShowResultsClick() {
|
|
||||||
const FC = settings.cfg.formContact.fields;
|
|
||||||
|
|
||||||
if (!isDisableEmail && FC["email"].used !== EMAIL_REGEXP.test(email)) {
|
|
||||||
return enqueueSnackbar("Incorrect email entered");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fireOnce.current) {
|
|
||||||
if (name.length === 0 && email.length === 0 && phone.length === 0 && text.length === 0 && adress.length === 0)
|
|
||||||
return enqueueSnackbar(t("Please fill in the fields"));
|
|
||||||
|
|
||||||
//почта валидна, хоть одно поле заполнено
|
|
||||||
setFire(true);
|
|
||||||
try {
|
|
||||||
await inputHC();
|
|
||||||
fireOnce.current = false;
|
|
||||||
const sessions = JSON.parse(localStorage.getItem("sessions") || "{}");
|
|
||||||
sessions[quizId] = Date.now();
|
|
||||||
localStorage.setItem("sessions", JSON.stringify(sessions));
|
|
||||||
|
|
||||||
vkMetrics.contactsFormFilled();
|
|
||||||
yandexMetrics.contactsFormFilled();
|
|
||||||
//Оповещаем какие поля были заполнены
|
|
||||||
if (name.length !== 0) {
|
|
||||||
vkMetrics.contactsFormField("name");
|
|
||||||
yandexMetrics.contactsFormField("name");
|
|
||||||
}
|
|
||||||
if (email.length !== 0) {
|
|
||||||
vkMetrics.contactsFormField("email");
|
|
||||||
yandexMetrics.contactsFormField("email");
|
|
||||||
}
|
|
||||||
if (phone.length !== 0) {
|
|
||||||
vkMetrics.contactsFormField("phone");
|
|
||||||
yandexMetrics.contactsFormField("phone");
|
|
||||||
}
|
|
||||||
if (text.length !== 0) {
|
|
||||||
vkMetrics.contactsFormField("text");
|
|
||||||
yandexMetrics.contactsFormField("text");
|
|
||||||
}
|
|
||||||
if (adress.length !== 0) {
|
|
||||||
vkMetrics.contactsFormField("address");
|
|
||||||
yandexMetrics.contactsFormField("address");
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
enqueueSnackbar(t("Please try again later"));
|
|
||||||
}
|
|
||||||
if (settings.cfg.resultInfo.showResultForm === "after") {
|
|
||||||
onShowResult();
|
|
||||||
}
|
|
||||||
enqueueSnackbar(t("Data sent successfully"));
|
|
||||||
}
|
|
||||||
|
|
||||||
setFire(false);
|
|
||||||
}
|
|
||||||
useEffect(() => {
|
|
||||||
vkMetrics.contactsFormOpened();
|
|
||||||
yandexMetrics.contactsFormOpened();
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
backgroundColor: theme.palette.background.default,
|
|
||||||
height: screenHeight > 500 ? "100%" : "auto",
|
|
||||||
overflow: "auto",
|
|
||||||
"&::-webkit-scrollbar": {
|
|
||||||
width: "0",
|
|
||||||
display: "none",
|
|
||||||
msOverflowStyle: "none",
|
|
||||||
},
|
|
||||||
scrollbarWidth: "none",
|
|
||||||
msOverflowStyle: "none",
|
|
||||||
backgroundPosition: "center",
|
|
||||||
backgroundSize: "cover",
|
|
||||||
backgroundImage:
|
|
||||||
settings.cfg.design && !isMobile
|
|
||||||
? quizThemes[settings.cfg.theme].isLight
|
|
||||||
? `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,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
width: !isMobile ? "100%" : isMobile ? undefined : "530px",
|
|
||||||
borderRadius: "4px",
|
|
||||||
height: isMobile ? "100%" : "auto",
|
|
||||||
minHeight: "100%",
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: isMobile ? "column" : "row",
|
|
||||||
background: settings.cfg.design && !isMobile ? undefined : theme.palette.background.default,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ContactTextBlock settings={settings} />
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
flexGrow: isMobile ? 1 : 0,
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
flexDirection: "column",
|
|
||||||
backgroundColor: theme.palette.background.default,
|
|
||||||
height: "auto",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: isMobile ? undefined : "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
flexDirection: "column",
|
|
||||||
p: isMobile ? "0 20px" : isTablet ? "105px 40px 0 60px" : "105px 60px 0 60px",
|
|
||||||
margin: isMobile ? "0" : "auto 0",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
mt: isMobile ? "10px" : "20px",
|
|
||||||
mb: "20px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Inputs
|
|
||||||
name={name}
|
|
||||||
setName={setName}
|
|
||||||
email={email}
|
|
||||||
setEmail={setEmail}
|
|
||||||
phone={phone}
|
|
||||||
setPhone={setPhone}
|
|
||||||
text={text}
|
|
||||||
setText={setText}
|
|
||||||
adress={adress}
|
|
||||||
setAdress={setAdress}
|
|
||||||
crutch={{
|
|
||||||
disableEmail: isDisableEmail,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
width: isMobile ? "300px" : "390px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CustomCheckbox
|
|
||||||
label=""
|
|
||||||
handleChange={({ target }) => {
|
|
||||||
setReady(target.checked);
|
|
||||||
}}
|
|
||||||
checked={ready}
|
|
||||||
colorIcon={theme.palette.primary.main}
|
|
||||||
sx={{ marginRight: "0" }}
|
|
||||||
/>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
lineHeight: "18.96px",
|
|
||||||
}}
|
|
||||||
fontSize={"16px"}
|
|
||||||
>
|
|
||||||
С 
|
|
||||||
<Link
|
|
||||||
href={"https://shub.pena.digital/ppdd"}
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
{`${t("Regulation on the processing of personal data")} `}
|
|
||||||
</Link>
|
|
||||||
 {t("and")} 
|
|
||||||
<Link
|
|
||||||
href={"https://shub.pena.digital/docs/privacy"}
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
{" "}
|
|
||||||
{`${t("Privacy Policy")} `}
|
|
||||||
</Link>
|
|
||||||
 {t("familiarized")}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
disabled={!(ready && !fire)}
|
|
||||||
variant="contained"
|
|
||||||
onClick={handleShowResultsClick}
|
|
||||||
sx={{
|
|
||||||
border: `1px solid ${theme.palette.primary.main}`,
|
|
||||||
margin: isMobile ? "auto" : undefined,
|
|
||||||
mt: "20px",
|
|
||||||
p: "10px 20px",
|
|
||||||
"&:disabled": {
|
|
||||||
border: "1px solid #9A9AAF",
|
|
||||||
color: "#9A9AAF",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{settings.cfg.formContact?.button || t("Get results")}
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
{show_badge && (
|
|
||||||
<Box
|
|
||||||
component={Link}
|
|
||||||
target={"_blank"}
|
|
||||||
href={`https://${isProduction ? "" : "s"}quiz.pena.digital/answer/v1.0.0/logo?q=${quizId}`}
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
mt: "55px",
|
|
||||||
mb: isMobile ? "30px" : isTablet ? "40px" : "50px",
|
|
||||||
gap: "10px",
|
|
||||||
textDecoration: "none",
|
|
||||||
margitTop: "auto",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<NameplateLogo
|
|
||||||
style={{
|
|
||||||
fontSize: "20px",
|
|
||||||
color: quizThemes[settings.cfg.theme].isLight ? "#151515" : "#FFFFFF",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,67 +0,0 @@
|
|||||||
import { Box, Typography, useTheme } from "@mui/material";
|
|
||||||
import { useRootContainerSize } from "@contexts/RootContainerWidthContext.ts";
|
|
||||||
import { QuizSettingsConfig } from "@model/settingsData.ts";
|
|
||||||
import { FC } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
type ContactTextBlockProps = {
|
|
||||||
settings: QuizSettingsConfig;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ContactTextBlock: FC<ContactTextBlockProps> = ({ settings }) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const isMobile = useRootContainerSize() < 850;
|
|
||||||
const isTablet = useRootContainerSize() < 1000;
|
|
||||||
const { t } = useTranslation();
|
|
||||||
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",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{settings.cfg.formContact.title || t("Fill out the form to receive your test results")}
|
|
||||||
</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,66 +0,0 @@
|
|||||||
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}
|
|
||||||
// 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,99 +0,0 @@
|
|||||||
import { Box, InputAdornment, TextField as MuiTextField, TextFieldProps, Typography, useTheme } from "@mui/material";
|
|
||||||
import { useRootContainerSize } from "@contexts/RootContainerWidthContext.ts";
|
|
||||||
import { useIMask, IMask } from "react-imask";
|
|
||||||
import { quizThemes } from "@utils/themes/Publication/themePublication.ts";
|
|
||||||
import { ChangeEvent, FC, HTMLInputTypeAttribute, useEffect, useState } from "react";
|
|
||||||
import { CountrySelector } from "@/components/ViewPublicationPage/ContactForm/CustomInput/CountrySelector/CountrySelector.tsx";
|
|
||||||
import { phoneMasksByCountry } from "@utils/phoneMasksByCountry.tsx";
|
|
||||||
import { useQuizStore } from "@/stores/useQuizStore";
|
|
||||||
|
|
||||||
type InputProps = {
|
|
||||||
title: string;
|
|
||||||
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) {
|
|
||||||
const masked = IMask.createMask({
|
|
||||||
mask: "+7 (000) 000-00-00",
|
|
||||||
// ...and other options
|
|
||||||
});
|
|
||||||
masked.value = e.target.value;
|
|
||||||
const a = IMask.pipe(e.target.value, {
|
|
||||||
mask,
|
|
||||||
});
|
|
||||||
return a || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CustomInput = ({ title, desc, Icon, onChange, onChangePhone, isPhone, type, value }: InputProps) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const isMobile = useRootContainerSize() < 600;
|
|
||||||
const { settings } = useQuizStore();
|
|
||||||
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>
|
|
||||||
|
|
||||||
<TextField
|
|
||||||
// inputRef={isPhone ? ref : null}
|
|
||||||
//@ts-ignore
|
|
||||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
|
||||||
isPhone ? 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,126 +0,0 @@
|
|||||||
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 PhoneIcon from "@icons/ContactFormIcon/PhoneIcon.tsx";
|
|
||||||
import PhoneInput from "react-phone-number-input";
|
|
||||||
import { useQuizStore } from "@/stores/useQuizStore";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
type InputsProps = {
|
|
||||||
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>>;
|
|
||||||
crutch: {
|
|
||||||
disableEmail: boolean;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const iscrutch = "/cc006b40-ccbd-4600-a1d3-f902f85aa0a0";
|
|
||||||
const pathOnly = window.location.pathname;
|
|
||||||
|
|
||||||
export const Inputs = ({
|
|
||||||
name,
|
|
||||||
setName,
|
|
||||||
email,
|
|
||||||
setEmail,
|
|
||||||
phone,
|
|
||||||
setPhone,
|
|
||||||
text,
|
|
||||||
setText,
|
|
||||||
adress,
|
|
||||||
setAdress,
|
|
||||||
crutch,
|
|
||||||
}: InputsProps) => {
|
|
||||||
const { settings } = useQuizStore();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const FC = settings.cfg.formContact.fields;
|
|
||||||
|
|
||||||
if (!FC) return null;
|
|
||||||
const Name = (
|
|
||||||
<CustomInput
|
|
||||||
onChange={({ target }) => setName(target.value)}
|
|
||||||
id={name}
|
|
||||||
title={
|
|
||||||
pathOnly === iscrutch
|
|
||||||
? "Введите имя и фамилию"
|
|
||||||
: FC["name"].innerText || `${t("Enter")} ${t("Name").toLowerCase()}`
|
|
||||||
}
|
|
||||||
desc={FC["name"].text || t("Name")}
|
|
||||||
Icon={NameIcon}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
const Email = (
|
|
||||||
<CustomInput
|
|
||||||
onChange={({ target }) => {
|
|
||||||
setEmail(target.value.replaceAll(/\s/g, ""));
|
|
||||||
}}
|
|
||||||
id={email}
|
|
||||||
title={FC["email"].innerText || `${t("Enter")} Email`}
|
|
||||||
desc={FC["email"].text || "Email"}
|
|
||||||
Icon={EmailIcon}
|
|
||||||
type="email"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
const Phone = (
|
|
||||||
<CustomInput
|
|
||||||
onChange={({ target }) => setText(target.value)}
|
|
||||||
onChangePhone={(phone: string) => {
|
|
||||||
setPhone(phone);
|
|
||||||
}}
|
|
||||||
value={phone}
|
|
||||||
id={phone}
|
|
||||||
title={FC["phone"].innerText || `${t("Enter")} ${t("Phone number").toLowerCase()}`}
|
|
||||||
desc={FC["phone"].text || t("Phone number")}
|
|
||||||
Icon={PhoneIcon}
|
|
||||||
isPhone={true}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
const Text = (
|
|
||||||
<CustomInput
|
|
||||||
onChange={({ target }) => setText(target.value)}
|
|
||||||
id={text}
|
|
||||||
title={FC["text"].text || `${t("Enter")} ${t("Last name").toLowerCase()}`}
|
|
||||||
desc={FC["text"].innerText || t("Last name")}
|
|
||||||
Icon={TextIcon}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
const Adress = (
|
|
||||||
<CustomInput
|
|
||||||
onChange={({ target }) => setAdress(target.value)}
|
|
||||||
id={adress}
|
|
||||||
title={FC["address"].innerText || `${t("Enter")} ${t("Address").toLowerCase()}`}
|
|
||||||
desc={FC["address"].text || t("Address")}
|
|
||||||
Icon={AddressIcon}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (Object.values(FC).some((data) => data.used)) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{FC["name"].used ? Name : <></>}
|
|
||||||
{FC["email"].used && !crutch.disableEmail ? Email : <></>}
|
|
||||||
{FC["phone"].used ? Phone : <></>}
|
|
||||||
{FC["text"].used ? Text : <></>}
|
|
||||||
{FC["address"].used ? Adress : <></>}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{Name}
|
|
||||||
{Email}
|
|
||||||
{Phone}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,144 +0,0 @@
|
|||||||
import { IncorrectAnswer } from "@/assets/icons/IncorrectAnswer";
|
|
||||||
import { CorrectAnswer } from "@/assets/icons/CorrectAnswer";
|
|
||||||
import { Box, Typography, useTheme } from "@mui/material";
|
|
||||||
import { useQuizViewStore } from "@/stores/quizView";
|
|
||||||
import { AnyTypedQuizQuestion, QuizQuestionVariant } from "@/index";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { useQuizStore } from "@/stores/useQuizStore";
|
|
||||||
|
|
||||||
export const PointSystemResultList = () => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const { questions } = useQuizStore();
|
|
||||||
const answers = useQuizViewStore((state) => state.answers);
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const questionsWothoutResult = questions.filter<QuizQuestionVariant>(
|
|
||||||
(q: AnyTypedQuizQuestion): q is QuizQuestionVariant => q.type === "variant"
|
|
||||||
);
|
|
||||||
|
|
||||||
return questionsWothoutResult.map((currentQuestion) => {
|
|
||||||
let answerIndex = 0;
|
|
||||||
let currentVariants = currentQuestion.content.variants;
|
|
||||||
|
|
||||||
const currentAnswer = answers.find((a) => a.questionId === currentQuestion.id);
|
|
||||||
const answeredVariant = currentVariants.find((v, i) => {
|
|
||||||
if (v.id === currentAnswer?.answer) {
|
|
||||||
answerIndex = i;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "inline-flex",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
width: "100%",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "inline-flex",
|
|
||||||
gap: "16px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
color: theme.palette.grey[500],
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{currentQuestion.page + 1}.
|
|
||||||
</Typography>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{currentQuestion.title || t("Question without a title")}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
color: answeredVariant?.points ? theme.palette.primary.main : theme.palette.grey[500],
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{answeredVariant?.points || "0"}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "inline-flex",
|
|
||||||
mt: "15px",
|
|
||||||
gap: "10px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
color: theme.palette.grey[500],
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("Your answer")}:
|
|
||||||
</Typography>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Line
|
|
||||||
checkTrue={Boolean(answeredVariant?.points)}
|
|
||||||
text={answeredVariant?.answer}
|
|
||||||
/>
|
|
||||||
{/* {Boolean(answeredVariant?.points) ? <CorrectAnswer /> : <IncorrectAnswer />}
|
|
||||||
<Typography>{answeredVariant?.answer || "не выбрано"}</Typography> */}
|
|
||||||
{currentVariants.map((v) => {
|
|
||||||
if (v.id === currentAnswer?.answer) {
|
|
||||||
return <></>;
|
|
||||||
} else
|
|
||||||
return (
|
|
||||||
<Line
|
|
||||||
checkTrue={Boolean(v?.points)}
|
|
||||||
text={v.answer}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
interface LineProps {
|
|
||||||
checkTrue: boolean;
|
|
||||||
text?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Line = ({ checkTrue, text }: LineProps) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "inline-flex",
|
|
||||||
gap: "10px",
|
|
||||||
mb: "10px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{checkTrue ? <CorrectAnswer /> : <IncorrectAnswer />}
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
color: theme.palette.grey[500],
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{text || "не выбрано"}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,23 +1,12 @@
|
|||||||
import { Box, Link, useTheme } from "@mui/material";
|
import { Box, Link, useTheme } from "@mui/material";
|
||||||
|
|
||||||
import { Footer } from "./Footer";
|
import { Footer } from "./Footer";
|
||||||
import { Date } from "./questions/Date";
|
|
||||||
import { Emoji } from "./questions/Emoji";
|
|
||||||
import { File } from "./questions/File";
|
|
||||||
import { Images } from "./questions/Images";
|
|
||||||
import { Number } from "./questions/Number";
|
|
||||||
import { Page } from "./questions/Page";
|
|
||||||
import { Rating } from "./questions/Rating";
|
|
||||||
import { Select } from "./questions/Select";
|
|
||||||
import { Text } from "./questions/Text";
|
import { Text } from "./questions/Text";
|
||||||
import { Variant } from "./questions/Variant";
|
|
||||||
import { Varimg } from "./questions/Varimg";
|
|
||||||
|
|
||||||
import type { RealTypedQuizQuestion } from "../../model/questionTypes/shared";
|
import type { RealTypedQuizQuestion } from "../../model/questionTypes/shared";
|
||||||
|
|
||||||
import { NameplateLogoFQ } from "@icons/NameplateLogoFQ";
|
import { NameplateLogoFQ } from "@icons/NameplateLogoFQ";
|
||||||
import { NameplateLogoFQDark } from "@icons/NameplateLogoFQDark";
|
import { NameplateLogoFQDark } from "@icons/NameplateLogoFQDark";
|
||||||
import { notReachable } from "@utils/notReachable";
|
|
||||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||||
|
|
||||||
import { DESIGN_LIST } from "@/utils/designList";
|
import { DESIGN_LIST } from "@/utils/designList";
|
||||||
@ -88,9 +77,8 @@ export const Question = ({
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<QuestionByType
|
<Text
|
||||||
key={currentQuestion.id}
|
currentQuestion={currentQuestion}
|
||||||
question={currentQuestion}
|
|
||||||
stepNumber={currentQuestionStepNumber}
|
stepNumber={currentQuestionStepNumber}
|
||||||
/>
|
/>
|
||||||
{show_badge && (
|
{show_badge && (
|
||||||
@ -133,37 +121,3 @@ export const Question = ({
|
|||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function QuestionByType({ question, stepNumber }: { question: RealTypedQuizQuestion; stepNumber: number | null }) {
|
|
||||||
switch (question.type) {
|
|
||||||
case "variant":
|
|
||||||
return <Variant currentQuestion={question} />;
|
|
||||||
case "images":
|
|
||||||
return <Images currentQuestion={question} />;
|
|
||||||
case "varimg":
|
|
||||||
return <Varimg currentQuestion={question} />;
|
|
||||||
case "emoji":
|
|
||||||
return <Emoji currentQuestion={question} />;
|
|
||||||
case "text":
|
|
||||||
return (
|
|
||||||
<Text
|
|
||||||
currentQuestion={question}
|
|
||||||
stepNumber={stepNumber}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
case "select":
|
|
||||||
return <Select currentQuestion={question} />;
|
|
||||||
case "date":
|
|
||||||
return <Date currentQuestion={question} />;
|
|
||||||
case "number":
|
|
||||||
return <Number currentQuestion={question} />;
|
|
||||||
case "file":
|
|
||||||
return <File currentQuestion={question} />;
|
|
||||||
case "page":
|
|
||||||
return <Page currentQuestion={question} />;
|
|
||||||
case "rating":
|
|
||||||
return <Rating currentQuestion={question} />;
|
|
||||||
default:
|
|
||||||
notReachable(question);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -13,8 +13,6 @@ import { NameplateLogo } from "@icons/NameplateLogo";
|
|||||||
|
|
||||||
import type { QuizQuestionResult } from "@/model/questionTypes/result";
|
import type { QuizQuestionResult } from "@/model/questionTypes/result";
|
||||||
import QuizVideo from "@/ui_kit/VideoIframe/VideoIframe";
|
import QuizVideo from "@/ui_kit/VideoIframe/VideoIframe";
|
||||||
import { TextAccordion } from "./tools/TextAccordion";
|
|
||||||
import { PointSystemResultList } from "./PointSystemResultList";
|
|
||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
import { sendFC, sendResult } from "@/api/quizRelase";
|
import { sendFC, sendResult } from "@/api/quizRelase";
|
||||||
import { isProduction } from "@/utils/defineDomain";
|
import { isProduction } from "@/utils/defineDomain";
|
||||||
@ -240,55 +238,6 @@ export const ResultForm = ({ resultQuestion }: ResultFormProps) => {
|
|||||||
{resultQuestion.content.text}
|
{resultQuestion.content.text}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
{settings.cfg?.score && (
|
|
||||||
<>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
fontSize: "30px",
|
|
||||||
m: "30px 0",
|
|
||||||
fontWeight: 600,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("Your points")}
|
|
||||||
</Typography>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
fontSize: "30px",
|
|
||||||
fontWeight: 600,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{pointsSum} {t("of")} {questions.filter((e) => e.type != "result").length}
|
|
||||||
</Typography>
|
|
||||||
<TextAccordion
|
|
||||||
headerText={
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
"&:hover": {
|
|
||||||
color: theme.palette.primary.dark,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("View answers")}
|
|
||||||
</Typography>
|
|
||||||
}
|
|
||||||
sx={{
|
|
||||||
mt: "60px",
|
|
||||||
width: "100%",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
mt: "25px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<PointSystemResultList />
|
|
||||||
</Box>
|
|
||||||
</TextAccordion>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
{show_badge && (
|
{show_badge && (
|
||||||
|
@ -1,41 +0,0 @@
|
|||||||
import { StartPageDesktop } from "./StartPageDesktop";
|
|
||||||
import { StartPageMobile } from "./StartPageMobile";
|
|
||||||
|
|
||||||
import { useRootContainerSize } from "@contexts/RootContainerWidthContext";
|
|
||||||
|
|
||||||
import type { QuizStartpageAlignType, QuizStartpageType } from "@model/settingsData";
|
|
||||||
|
|
||||||
type QuizPreviewLayoutByTypeProps = {
|
|
||||||
quizHeaderBlock: JSX.Element;
|
|
||||||
quizMainBlock: JSX.Element;
|
|
||||||
backgroundBlock: JSX.Element | null;
|
|
||||||
startpageType: QuizStartpageType;
|
|
||||||
alignType: QuizStartpageAlignType;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const QuizPreviewLayoutByType = ({
|
|
||||||
quizHeaderBlock,
|
|
||||||
quizMainBlock,
|
|
||||||
backgroundBlock,
|
|
||||||
startpageType,
|
|
||||||
alignType,
|
|
||||||
}: QuizPreviewLayoutByTypeProps) => {
|
|
||||||
const isMobile = useRootContainerSize() < 700;
|
|
||||||
|
|
||||||
return isMobile ? (
|
|
||||||
<StartPageMobile
|
|
||||||
quizHeaderBlock={quizHeaderBlock}
|
|
||||||
quizMainBlock={quizMainBlock}
|
|
||||||
backgroundBlock={backgroundBlock}
|
|
||||||
startpageType={startpageType}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<StartPageDesktop
|
|
||||||
alignType={alignType}
|
|
||||||
startpageType={startpageType}
|
|
||||||
quizHeaderBlock={quizHeaderBlock}
|
|
||||||
quizMainBlock={quizMainBlock}
|
|
||||||
backgroundBlock={backgroundBlock}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,263 +0,0 @@
|
|||||||
import { Box } from "@mui/material";
|
|
||||||
|
|
||||||
import { useRootContainerSize } from "@contexts/RootContainerWidthContext";
|
|
||||||
import { useQuizStore } from "@/stores/useQuizStore";
|
|
||||||
|
|
||||||
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";
|
|
||||||
|
|
||||||
type StartPageDesktopProps = {
|
|
||||||
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 } = useQuizStore();
|
|
||||||
|
|
||||||
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
|
|
||||||
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" },
|
|
||||||
}}
|
|
||||||
onClick={(event) => event.preventDefault()}
|
|
||||||
>
|
|
||||||
{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 } = useQuizStore();
|
|
||||||
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" },
|
|
||||||
}}
|
|
||||||
onClick={(event) => event.preventDefault()}
|
|
||||||
>
|
|
||||||
{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);
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,273 +0,0 @@
|
|||||||
import { Box } from "@mui/material";
|
|
||||||
|
|
||||||
import { useQuizStore } from "@/stores/useQuizStore";
|
|
||||||
|
|
||||||
import { notReachable } from "@utils/notReachable";
|
|
||||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
type MobileLayoutProps = Omit<StartPageMobileProps, "startpageType">;
|
|
||||||
|
|
||||||
const StandartMobileLayout = ({ quizHeaderBlock, quizMainBlock, backgroundBlock }: MobileLayoutProps) => {
|
|
||||||
const { settings } = useQuizStore();
|
|
||||||
|
|
||||||
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%",
|
|
||||||
height: "100%",
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "center",
|
|
||||||
"& > img": {
|
|
||||||
width: "100%",
|
|
||||||
borderRadius: "12px",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
onClick={(event) => event.preventDefault()}
|
|
||||||
>
|
|
||||||
{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) => (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column-reverse",
|
|
||||||
flexGrow: 1,
|
|
||||||
justifyContent: "flex-end",
|
|
||||||
minHeight: "100%",
|
|
||||||
height: "100%",
|
|
||||||
"&::-webkit-scrollbar": { width: 0 },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<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%",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
onClick={(event) => event.preventDefault()}
|
|
||||||
>
|
|
||||||
{backgroundBlock}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
|
|
||||||
const CenteredMobileLayout = ({ quizHeaderBlock, quizMainBlock, backgroundBlock }: MobileLayoutProps) => {
|
|
||||||
const { settings } = useQuizStore();
|
|
||||||
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={{
|
|
||||||
width: "100%",
|
|
||||||
overflow: "hidden",
|
|
||||||
"& > img": { width: "100%", borderRadius: "12px" },
|
|
||||||
}}
|
|
||||||
onClick={(event) => event.preventDefault()}
|
|
||||||
>
|
|
||||||
{backgroundBlock}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
height: "80%",
|
|
||||||
display: "flex",
|
|
||||||
flexGrow: 1,
|
|
||||||
flexDirection: "column",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
width: "100%",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{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);
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,480 +0,0 @@
|
|||||||
import { Box, Button, ButtonBase, Link, Paper, Typography, useTheme } from "@mui/material";
|
|
||||||
|
|
||||||
import { QuizPreviewLayoutByType } from "./QuizPreviewLayoutByType";
|
|
||||||
|
|
||||||
import { useQuizStore } from "@/stores/useQuizStore";
|
|
||||||
import { useRootContainerSize } from "@contexts/RootContainerWidthContext";
|
|
||||||
|
|
||||||
import { useUADevice } from "@utils/hooks/useUADevice";
|
|
||||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
|
||||||
|
|
||||||
import { NameplateLogo } from "@icons/NameplateLogo";
|
|
||||||
import { useQuizViewStore } from "@/stores/quizView";
|
|
||||||
import { DESIGN_LIST } from "@/utils/designList";
|
|
||||||
|
|
||||||
import { useVkMetricsGoals } from "@/utils/hooks/metrics/useVkMetricsGoals";
|
|
||||||
import { useYandexMetricsGoals } from "@/utils/hooks/metrics/useYandexMetricsGoals";
|
|
||||||
import QuizVideo from "@/ui_kit/VideoIframe/VideoIframe";
|
|
||||||
|
|
||||||
import { isProduction } from "@/utils/defineDomain";
|
|
||||||
|
|
||||||
export const StartPageViewPublication = () => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const { settings, show_badge, quizId, questions } = useQuizStore();
|
|
||||||
const { isMobileDevice } = useUADevice();
|
|
||||||
const setCurrentQuizStep = useQuizViewStore((state) => state.setCurrentQuizStep);
|
|
||||||
|
|
||||||
const size = useRootContainerSize();
|
|
||||||
const isMobile = size < 700;
|
|
||||||
const isTablet = size >= 700 && size < 1100;
|
|
||||||
|
|
||||||
const vkMetrics = useVkMetricsGoals(settings.cfg.vkMetricsNumber);
|
|
||||||
const yandexMetrics = useYandexMetricsGoals(settings.cfg.yandexMetricsNumber);
|
|
||||||
|
|
||||||
const handleCopyNumber = () => {
|
|
||||||
navigator.clipboard.writeText(settings.cfg.info.phonenumber);
|
|
||||||
|
|
||||||
vkMetrics.phoneNumberOpened();
|
|
||||||
yandexMetrics.phoneNumberOpened();
|
|
||||||
};
|
|
||||||
|
|
||||||
const background =
|
|
||||||
settings.cfg.startpage.background.type === "image" ? (
|
|
||||||
<img
|
|
||||||
src={settings.cfg.startpage.background.desktop || DESIGN_LIST[settings.cfg.theme] || ""}
|
|
||||||
alt=""
|
|
||||||
style={{
|
|
||||||
display: "block",
|
|
||||||
width: isMobile || settings.cfg.startpageType === "expanded" ? "100%" : undefined,
|
|
||||||
height: "100%",
|
|
||||||
minWidth: "100%",
|
|
||||||
maxHeight: "100%",
|
|
||||||
objectFit: "cover",
|
|
||||||
overflow: "hidden",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : settings.cfg.startpage.background.type === "video" ? (
|
|
||||||
settings.cfg.startpage.background.video ? (
|
|
||||||
<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",
|
|
||||||
overflow: "hidden",
|
|
||||||
"& iframe": {
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
transform:
|
|
||||||
settings.cfg.startpageType === "centered"
|
|
||||||
? ""
|
|
||||||
: settings.cfg.startpageType === "expanded"
|
|
||||||
? "scale(1.5)"
|
|
||||||
: "scale(2.4)",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : null
|
|
||||||
) : null;
|
|
||||||
|
|
||||||
const quizHeaderBlock = (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
margin: settings.cfg.startpageType === "centered" ? "0 auto" : null,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
flexWrap:
|
|
||||||
settings.cfg.startpageType === "expanded" && settings.cfg.startpage.position === "center"
|
|
||||||
? "nowrap"
|
|
||||||
: "wrap",
|
|
||||||
gap: isMobile ? "20px" : "30px",
|
|
||||||
mb:
|
|
||||||
settings.cfg.startpageType === "centered"
|
|
||||||
? isMobile
|
|
||||||
? "20px"
|
|
||||||
: "25px"
|
|
||||||
: settings.cfg.startpageType === "expanded" && settings.cfg.startpage.position === "center" && !isMobile
|
|
||||||
? 0
|
|
||||||
: "7px",
|
|
||||||
justifyContent:
|
|
||||||
settings.cfg.startpageType === "expanded" && settings.cfg.startpage.position === "center" && isMobile
|
|
||||||
? "center"
|
|
||||||
: undefined,
|
|
||||||
}}
|
|
||||||
onClick={(event) => event.preventDefault()}
|
|
||||||
>
|
|
||||||
{settings.cfg.startpage.logo && (
|
|
||||||
<img
|
|
||||||
src={settings.cfg.startpage.logo}
|
|
||||||
style={{
|
|
||||||
maxHeight: isMobile ? "30px" : "40px",
|
|
||||||
maxWidth: isMobile ? "100px" : "110px",
|
|
||||||
objectFit: "cover",
|
|
||||||
}}
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
fontSize: "12px",
|
|
||||||
color: settings.cfg.startpageType === "expanded" ? "white" : theme.palette.text.primary,
|
|
||||||
wordBreak:
|
|
||||||
settings.cfg.startpageType === "expanded" && settings.cfg.startpage.position === "center"
|
|
||||||
? "normal"
|
|
||||||
: "break-word",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{settings.cfg.info.orgname}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
|
|
||||||
const PenaBadge = (
|
|
||||||
<Box
|
|
||||||
component={Link}
|
|
||||||
target={"_blank"}
|
|
||||||
href={`https://${isProduction ? "" : "s"}quiz.pena.digital/answer/v1.0.0/logo?q=${quizId}`}
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: "7px",
|
|
||||||
textDecoration: "none",
|
|
||||||
marginLeft:
|
|
||||||
settings.cfg.startpageType === "expanded" &&
|
|
||||||
settings.cfg.startpage.position === "center" &&
|
|
||||||
!isTablet &&
|
|
||||||
!isMobile
|
|
||||||
? "61px"
|
|
||||||
: undefined,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<NameplateLogo
|
|
||||||
style={{
|
|
||||||
fontSize: "23px",
|
|
||||||
color:
|
|
||||||
settings.cfg.startpageType === "expanded"
|
|
||||||
? "#FFFFFF"
|
|
||||||
: quizThemes[settings.cfg.theme].isLight
|
|
||||||
? "#151515"
|
|
||||||
: "#FFFFFF",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
|
|
||||||
const realQuestionsCount = questions.filter(
|
|
||||||
(question) => question.type !== null && question.type !== "result"
|
|
||||||
).length;
|
|
||||||
|
|
||||||
const onQuizStart = () => {
|
|
||||||
setCurrentQuizStep("question");
|
|
||||||
|
|
||||||
vkMetrics.firstPageOpened();
|
|
||||||
yandexMetrics.firstPageOpened();
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSiteClick = () => {
|
|
||||||
vkMetrics.emailOpened();
|
|
||||||
yandexMetrics.emailOpened();
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
location.href = (
|
|
||||||
settings.cfg.info.site.includes("https") ? settings.cfg.info.site : `https://${settings.cfg.info.site}`
|
|
||||||
).replace(/\s+/g, "");
|
|
||||||
}, 1000);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Paper
|
|
||||||
className="settings-preview-draghandle"
|
|
||||||
sx={{
|
|
||||||
borderRadius: 0,
|
|
||||||
height: "100%",
|
|
||||||
width: "100%",
|
|
||||||
background:
|
|
||||||
settings.cfg.startpageType === "expanded"
|
|
||||||
? 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%)"
|
|
||||||
: "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,
|
|
||||||
|
|
||||||
color: settings.cfg.startpageType === "expanded" ? "white" : "black",
|
|
||||||
}}
|
|
||||||
onClick={(event) => event.preventDefault()}
|
|
||||||
>
|
|
||||||
<QuizPreviewLayoutByType
|
|
||||||
quizHeaderBlock={quizHeaderBlock}
|
|
||||||
quizMainBlock={
|
|
||||||
<>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
justifyContent: settings.cfg.startpageType === "standard" && isMobile ? "start" : "center",
|
|
||||||
flexGrow: settings.cfg.startpageType === "centered" ? 0 : 1,
|
|
||||||
wordBreak: "break-word",
|
|
||||||
alignItems:
|
|
||||||
settings.cfg.startpageType === "centered"
|
|
||||||
? "center"
|
|
||||||
: settings.cfg.startpageType === "expanded"
|
|
||||||
? settings.cfg.startpage.position === "center"
|
|
||||||
? "center"
|
|
||||||
: "start"
|
|
||||||
: "start",
|
|
||||||
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"
|
|
||||||
? "380px"
|
|
||||||
: "531px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
fontWeight: "700",
|
|
||||||
fontSize: isMobile ? "24px" : "27px",
|
|
||||||
fontStyle: "normal",
|
|
||||||
fontStretch: "normal",
|
|
||||||
lineHeight: isMobile ? "26.4px" : "normal",
|
|
||||||
overflowWrap: "break-word",
|
|
||||||
width: "100%",
|
|
||||||
textAlign:
|
|
||||||
settings.cfg.startpageType === "centered" || settings.cfg.startpage.position === "center"
|
|
||||||
? "center"
|
|
||||||
: "-moz-initial",
|
|
||||||
color: settings.cfg.startpageType === "expanded" ? "white" : theme.palette.text.primary,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{settings.name}
|
|
||||||
</Typography>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
fontSize: isMobile ? "16px" : "17px",
|
|
||||||
fontWeight: "400",
|
|
||||||
lineHeight: isMobile ? "19.2px" : "normal",
|
|
||||||
margin: "12px 0 30px",
|
|
||||||
overflowWrap: "break-word",
|
|
||||||
width: "100%",
|
|
||||||
textAlign:
|
|
||||||
settings.cfg.startpageType === "centered" || settings.cfg.startpage.position === "center"
|
|
||||||
? "center"
|
|
||||||
: "-moz-initial",
|
|
||||||
color: settings.cfg.startpageType === "expanded" ? "white" : theme.palette.text.primary,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{settings.cfg.startpage.description}
|
|
||||||
</Typography>
|
|
||||||
<Box width={settings.cfg.startpageType === "standard" ? "100%" : "auto"}>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
disabled={realQuestionsCount === 0}
|
|
||||||
sx={{
|
|
||||||
fontSize: "18px",
|
|
||||||
padding: "10px 20px",
|
|
||||||
width: "auto",
|
|
||||||
background: theme.palette.primary.main,
|
|
||||||
borderRadius: "12px",
|
|
||||||
}}
|
|
||||||
onClick={onQuizStart}
|
|
||||||
>
|
|
||||||
{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,
|
|
||||||
gap: isMobile ? "30px" : "40px",
|
|
||||||
alignItems: "flex-end",
|
|
||||||
justifyContent:
|
|
||||||
(settings.cfg.startpageType === "expanded" &&
|
|
||||||
settings.cfg.startpage.position === "center" &&
|
|
||||||
isMobile) ||
|
|
||||||
(settings.cfg.startpageType === "centered" && isMobile)
|
|
||||||
? "center"
|
|
||||||
: "space-between",
|
|
||||||
width: "100%",
|
|
||||||
flexWrap:
|
|
||||||
settings.cfg.startpageType === "expanded" && settings.cfg.startpage.position === "center"
|
|
||||||
? isMobile
|
|
||||||
? "wrap-reverse"
|
|
||||||
: "nowrap"
|
|
||||||
: "wrap",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{settings.cfg.startpageType === "expanded" &&
|
|
||||||
settings.cfg.startpage.position === "center" &&
|
|
||||||
!isMobile &&
|
|
||||||
quizHeaderBlock}
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
maxWidth: "300px",
|
|
||||||
display:
|
|
||||||
(settings.cfg.startpageType === "centered" && 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"
|
|
||||||
? "2"
|
|
||||||
: "0",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{settings.cfg.info.site && (
|
|
||||||
<ButtonBase
|
|
||||||
onClick={onSiteClick}
|
|
||||||
sx={{
|
|
||||||
display: "block",
|
|
||||||
width: "100%",
|
|
||||||
marginTop: "10px",
|
|
||||||
marginLeft:
|
|
||||||
settings.cfg.startpageType === "expanded" &&
|
|
||||||
settings.cfg.startpage.position === "center" &&
|
|
||||||
!isMobile
|
|
||||||
? "auto"
|
|
||||||
: undefined,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
lineHeight: "19px",
|
|
||||||
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"
|
|
||||||
: "start",
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
overflow: "hidden",
|
|
||||||
textOverflow: "ellipsis",
|
|
||||||
whiteSpace: "nowrap",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{settings.cfg.info.site}
|
|
||||||
</Typography>
|
|
||||||
</ButtonBase>
|
|
||||||
)}
|
|
||||||
{settings.cfg.info.clickable ? (
|
|
||||||
isMobileDevice ? (
|
|
||||||
<Link href={`tel:${settings.cfg.info.phonenumber}`}>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
lineHeight: "19px",
|
|
||||||
textAlign:
|
|
||||||
settings.cfg.startpageType === "expanded" && settings.cfg.startpage.position === "center"
|
|
||||||
? "end"
|
|
||||||
: "none",
|
|
||||||
fontSize: "16px",
|
|
||||||
color: settings.cfg.startpageType === "expanded" ? "#FFFFFF" : theme.palette.text.primary,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{settings.cfg.info.phonenumber}
|
|
||||||
</Typography>
|
|
||||||
</Link>
|
|
||||||
) : (
|
|
||||||
<ButtonBase
|
|
||||||
onClick={handleCopyNumber}
|
|
||||||
sx={{
|
|
||||||
display: "block",
|
|
||||||
marginTop: "10px",
|
|
||||||
marginLeft:
|
|
||||||
settings.cfg.startpageType === "expanded" &&
|
|
||||||
settings.cfg.startpage.position === "center" &&
|
|
||||||
!isMobile
|
|
||||||
? "auto"
|
|
||||||
: undefined,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
textAlign:
|
|
||||||
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,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{settings.cfg.info.phonenumber}
|
|
||||||
</Typography>
|
|
||||||
</ButtonBase>
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
lineHeight: "19px",
|
|
||||||
textAlign:
|
|
||||||
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,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{settings.cfg.info.phonenumber}
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
lineHeight: "14px",
|
|
||||||
width: "100%",
|
|
||||||
overflowWrap: "break-word",
|
|
||||||
fontSize: "12px",
|
|
||||||
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"
|
|
||||||
: "none",
|
|
||||||
maxHeight: "120px",
|
|
||||||
overflow: "auto",
|
|
||||||
marginTop: "10px",
|
|
||||||
"&::-webkit-scrollbar": { width: 0 },
|
|
||||||
color: settings.cfg.startpageType === "expanded" ? "white" : theme.palette.text.primary,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{settings.cfg.info.law}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{show_badge && PenaBadge}
|
|
||||||
</Box>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
backgroundBlock={background}
|
|
||||||
startpageType={settings.cfg.startpageType}
|
|
||||||
alignType={settings.cfg.startpage.position}
|
|
||||||
/>
|
|
||||||
</Paper>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,4 +1,3 @@
|
|||||||
import { ContactForm } from "@/components/ViewPublicationPage/ContactForm/ContactForm.tsx";
|
|
||||||
import { extractImageLinksFromQuestion } from "@/utils/extractImageLinks";
|
import { extractImageLinksFromQuestion } from "@/utils/extractImageLinks";
|
||||||
import { useVKMetrics } from "@/utils/hooks/metrics/useVKMetrics";
|
import { useVKMetrics } from "@/utils/hooks/metrics/useVKMetrics";
|
||||||
import { useYandexMetrics } from "@/utils/hooks/metrics/useYandexMetrics";
|
import { useYandexMetrics } from "@/utils/hooks/metrics/useYandexMetrics";
|
||||||
@ -14,7 +13,6 @@ import { Helmet } from "react-helmet-async";
|
|||||||
import { Question } from "./Question";
|
import { Question } from "./Question";
|
||||||
import QuestionSelect from "./QuestionSelect";
|
import QuestionSelect from "./QuestionSelect";
|
||||||
import { ResultForm } from "./ResultForm";
|
import { ResultForm } from "./ResultForm";
|
||||||
import { StartPageViewPublication } from "./StartPageViewPublication";
|
|
||||||
import NextButton from "./tools/NextButton";
|
import NextButton from "./tools/NextButton";
|
||||||
import PrevButton from "./tools/PrevButton";
|
import PrevButton from "./tools/PrevButton";
|
||||||
import unscreen from "@/ui_kit/unscreen";
|
import unscreen from "@/ui_kit/unscreen";
|
||||||
@ -85,17 +83,9 @@ export default function ViewPublicationPage() {
|
|||||||
const currentAnswer = answers.find(({ questionId }) => questionId === currentQuestion.id);
|
const currentAnswer = answers.find(({ questionId }) => questionId === currentQuestion.id);
|
||||||
|
|
||||||
let quizStepElement: ReactElement;
|
let quizStepElement: ReactElement;
|
||||||
switch (currentQuizStep) {
|
|
||||||
case "startpage": {
|
|
||||||
quizStepElement = <StartPageViewPublication />;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "question": {
|
|
||||||
if (currentQuestion.type === "result") {
|
if (currentQuestion.type === "result") {
|
||||||
quizStepElement = <ResultForm resultQuestion={currentQuestion} />;
|
quizStepElement = <ResultForm resultQuestion={currentQuestion} />;
|
||||||
break;
|
} else {
|
||||||
}
|
|
||||||
|
|
||||||
quizStepElement = (
|
quizStepElement = (
|
||||||
<Question
|
<Question
|
||||||
key={currentQuestion.id}
|
key={currentQuestion.id}
|
||||||
@ -130,19 +120,6 @@ export default function ViewPublicationPage() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "contactform": {
|
|
||||||
quizStepElement = (
|
|
||||||
<ContactForm
|
|
||||||
currentQuestion={currentQuestion}
|
|
||||||
onShowResult={showResultAfterContactForm}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
notReachable(currentQuizStep);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const preloadLinks = new Set([
|
const preloadLinks = new Set([
|
||||||
|
@ -1,77 +0,0 @@
|
|||||||
import { useQuizViewStore } from "@/stores/quizView";
|
|
||||||
import { useQuizStore } from "@/stores/useQuizStore";
|
|
||||||
import CalendarIcon from "@icons/CalendarIcon";
|
|
||||||
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 default ({ currentQuestion }: DateProps) => {
|
|
||||||
const { settings } = useQuizStore();
|
|
||||||
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 currentAnswer = moment(answer) || moment();
|
|
||||||
|
|
||||||
const onDateChange = async (date: Moment | null) => {
|
|
||||||
if (!date) return;
|
|
||||||
|
|
||||||
updateAnswer(currentQuestion.id, date, 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
width: "100%",
|
|
||||||
marginTop: "20px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<DatePicker
|
|
||||||
format="DD/MM/YYYY"
|
|
||||||
slots={{
|
|
||||||
openPickerIcon: () => (
|
|
||||||
<CalendarIcon
|
|
||||||
sx={{
|
|
||||||
"& path": { stroke: theme.palette.primary.main },
|
|
||||||
"& rect": { stroke: theme.palette.primary.main },
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
value={currentAnswer}
|
|
||||||
onChange={onDateChange}
|
|
||||||
slotProps={{
|
|
||||||
openPickerButton: { sx: { p: 0 }, "data-cy": "open-datepicker" },
|
|
||||||
layout: {
|
|
||||||
sx: { backgroundColor: theme.palette.background.default },
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
sx={{
|
|
||||||
"& .MuiInputBase-root": {
|
|
||||||
backgroundColor: settings.cfg.design
|
|
||||||
? quizThemes[settings.cfg.theme].isLight
|
|
||||||
? "#F2F3F7"
|
|
||||||
: "rgba(154,154,175, 0.2)"
|
|
||||||
: quizThemes[settings.cfg.theme].isLight
|
|
||||||
? "white"
|
|
||||||
: theme.palette.background.default,
|
|
||||||
borderRadius: "10px",
|
|
||||||
maxWidth: "250px",
|
|
||||||
pr: "30px",
|
|
||||||
"& input": { py: "11px", pl: "20px", lineHeight: "19px" },
|
|
||||||
"& fieldset": { borderColor: "#9A9AAF" },
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,104 +0,0 @@
|
|||||||
import { useQuizViewStore } from "@/stores/quizView";
|
|
||||||
import type { QuizQuestionDate } from "@model/questionTypes/date";
|
|
||||||
import { DateCalendar } from "@mui/x-date-pickers";
|
|
||||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
|
||||||
import type { Moment } from "moment";
|
|
||||||
import moment from "moment";
|
|
||||||
import { Box, Paper, TextField, useTheme } from "@mui/material";
|
|
||||||
import { useRootContainerSize } from "@/contexts/RootContainerWidthContext";
|
|
||||||
import { useQuizStore } from "@/stores/useQuizStore";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
type DateProps = {
|
|
||||||
currentQuestion: QuizQuestionDate;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ({ currentQuestion }: DateProps) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const today = moment();
|
|
||||||
const isMobile = useRootContainerSize() < 690;
|
|
||||||
const { settings } = useQuizStore();
|
|
||||||
const { updateAnswer } = useQuizViewStore((state) => state);
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const answers = useQuizViewStore((state) => state.answers);
|
|
||||||
const answer = (answers.find(({ questionId }) => questionId === currentQuestion.id)?.answer as string) || ["0", "0"];
|
|
||||||
|
|
||||||
const currentFrom = Number(answer[0]) ? moment(Number(answer[0])) : moment().utc();
|
|
||||||
const currentTo = Number(answer[1]) ? moment(Number(answer[1])) : moment().utc();
|
|
||||||
|
|
||||||
const onDateChange = async (date: Moment | null, index: number) => {
|
|
||||||
if (!date) return;
|
|
||||||
let newAnswer = [...answer];
|
|
||||||
newAnswer[index] = (moment(date).unix() * 1000).toString();
|
|
||||||
|
|
||||||
updateAnswer(currentQuestion.id, newAnswer, 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Paper
|
|
||||||
sx={{
|
|
||||||
backgroundColor: settings.cfg.design
|
|
||||||
? quizThemes[settings.cfg.theme].isLight
|
|
||||||
? "#F2F3F7"
|
|
||||||
: "rgba(154,154,175, 0.2)"
|
|
||||||
: quizThemes[settings.cfg.theme].isLight
|
|
||||||
? "white"
|
|
||||||
: theme.palette.background.default,
|
|
||||||
width: isMobile ? "min-content" : "auto",
|
|
||||||
display: "inline-flex",
|
|
||||||
flexWrap: "wrap",
|
|
||||||
marginTop: "20px",
|
|
||||||
p: "20px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box>
|
|
||||||
<span style={{ marginLeft: "25px", color: theme.palette.text.primary }}>{t("From")}</span>
|
|
||||||
<DateCalendar
|
|
||||||
sx={{
|
|
||||||
"& .MuiInputBase-root": {
|
|
||||||
backgroundColor: settings.cfg.design
|
|
||||||
? quizThemes[settings.cfg.theme].isLight
|
|
||||||
? "#F2F3F7"
|
|
||||||
: "rgba(154,154,175, 0.2)"
|
|
||||||
: quizThemes[settings.cfg.theme].isLight
|
|
||||||
? "white"
|
|
||||||
: theme.palette.background.default,
|
|
||||||
borderRadius: "10px",
|
|
||||||
maxWidth: "250px",
|
|
||||||
pr: "30px",
|
|
||||||
"& input": { py: "11px", pl: "20px", lineHeight: "19px" },
|
|
||||||
"& fieldset": { borderColor: "#9A9AAF" },
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
value={currentFrom}
|
|
||||||
onChange={(data) => onDateChange(data, 0)}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<span style={{ marginLeft: "25px", color: theme.palette.text.primary }}>{t("До")}</span>
|
|
||||||
<DateCalendar
|
|
||||||
minDate={today}
|
|
||||||
sx={{
|
|
||||||
"& .MuiInputBase-root": {
|
|
||||||
backgroundColor: settings.cfg.design
|
|
||||||
? quizThemes[settings.cfg.theme].isLight
|
|
||||||
? "#F2F3F7"
|
|
||||||
: "rgba(154,154,175, 0.2)"
|
|
||||||
: quizThemes[settings.cfg.theme].isLight
|
|
||||||
? "white"
|
|
||||||
: theme.palette.background.default,
|
|
||||||
borderRadius: "10px",
|
|
||||||
maxWidth: "250px",
|
|
||||||
pr: "30px",
|
|
||||||
"& input": { py: "11px", pl: "20px", lineHeight: "19px" },
|
|
||||||
"& fieldset": { borderColor: "#9A9AAF" },
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
value={currentTo}
|
|
||||||
onChange={(data) => onDateChange(data, 1)}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Paper>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,29 +0,0 @@
|
|||||||
import type { QuizQuestionDate } from "@model/questionTypes/date";
|
|
||||||
import DateRange from "./DateRange";
|
|
||||||
import DatePicker from "./DatePicker";
|
|
||||||
import { Box, Typography, useTheme } from "@mui/material";
|
|
||||||
|
|
||||||
type DateProps = {
|
|
||||||
currentQuestion: QuizQuestionDate;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Date = ({ currentQuestion }: DateProps) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Typography
|
|
||||||
variant="h5"
|
|
||||||
color={theme.palette.text.primary}
|
|
||||||
sx={{ wordBreak: "break-word" }}
|
|
||||||
>
|
|
||||||
{currentQuestion.title}
|
|
||||||
</Typography>
|
|
||||||
{currentQuestion.content.isRange ? (
|
|
||||||
<DateRange currentQuestion={currentQuestion} />
|
|
||||||
) : (
|
|
||||||
<DatePicker currentQuestion={currentQuestion} />
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,49 +0,0 @@
|
|||||||
import EmojiPickerOriginal from "@emoji-mart/react";
|
|
||||||
import { Box } from "@mui/material";
|
|
||||||
|
|
||||||
type Emoji = {
|
|
||||||
emoticons: string[];
|
|
||||||
id: string;
|
|
||||||
keywords: string[];
|
|
||||||
name: string;
|
|
||||||
native: string;
|
|
||||||
shortcodes: string;
|
|
||||||
unified: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type EmojiPickerProps = {
|
|
||||||
onEmojiSelect: (emoji: Emoji) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const EmojiPicker = ({ onEmojiSelect }: EmojiPickerProps) => (
|
|
||||||
<Box sx={{ minWidth: "352px" }}>
|
|
||||||
<EmojiPickerOriginal
|
|
||||||
onEmojiSelect={onEmojiSelect}
|
|
||||||
theme="light"
|
|
||||||
locale="ru"
|
|
||||||
exceptEmojis={ignoreEmojis}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
|
|
||||||
const ignoreEmojis = [
|
|
||||||
"two_men_holding_hands",
|
|
||||||
"two_women_holding_hands",
|
|
||||||
"man-kiss-man",
|
|
||||||
"woman-kiss-woman",
|
|
||||||
"man-heart-man",
|
|
||||||
"woman-heart-woman",
|
|
||||||
"man-man-boy",
|
|
||||||
"man-man-girl",
|
|
||||||
"man-man-girl-boy",
|
|
||||||
"man-man-girl-girl",
|
|
||||||
"man-man-boy-boy",
|
|
||||||
"woman-woman-boy",
|
|
||||||
"woman-woman-girl",
|
|
||||||
"woman-woman-girl-boy",
|
|
||||||
"woman-woman-girl-girl",
|
|
||||||
"woman-woman-boy-boy",
|
|
||||||
"rainbow-flag",
|
|
||||||
"transgender_flag",
|
|
||||||
"transgender_symbol",
|
|
||||||
];
|
|
@ -1,271 +0,0 @@
|
|||||||
import type { QuestionVariant } from "@/model/questionTypes/shared";
|
|
||||||
import { useQuizStore } from "@/stores/useQuizStore";
|
|
||||||
import { useQuizViewStore, type OwnVariant } from "@stores/quizView";
|
|
||||||
import {
|
|
||||||
Box,
|
|
||||||
Checkbox,
|
|
||||||
FormControl,
|
|
||||||
FormControlLabel,
|
|
||||||
Input,
|
|
||||||
Radio,
|
|
||||||
TextareaAutosize,
|
|
||||||
Typography,
|
|
||||||
useTheme,
|
|
||||||
} from "@mui/material";
|
|
||||||
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 { useTranslation } from "react-i18next";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { OwnEmojiPicker } from "./OwnEmojiPicker";
|
|
||||||
|
|
||||||
polyfillCountryFlagEmojis();
|
|
||||||
|
|
||||||
type EmojiVariantProps = {
|
|
||||||
questionId: string;
|
|
||||||
variant: QuestionVariant;
|
|
||||||
index: number;
|
|
||||||
isMulti: boolean;
|
|
||||||
own: boolean;
|
|
||||||
questionLargeCheck: boolean;
|
|
||||||
ownPlaceholder: string;
|
|
||||||
answer: string | string[] | undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface OwnInputProps {
|
|
||||||
questionId: string;
|
|
||||||
variant: QuestionVariant;
|
|
||||||
largeCheck: boolean;
|
|
||||||
ownPlaceholder: string;
|
|
||||||
}
|
|
||||||
const OwnInput = ({ questionId, variant, largeCheck, ownPlaceholder }: OwnInputProps) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const ownVariants = useQuizViewStore((state) => state.ownVariants);
|
|
||||||
const { updateOwnVariant } = useQuizViewStore((state) => state);
|
|
||||||
|
|
||||||
const ownAnswer = ownVariants[ownVariants.findIndex((v: OwnVariant) => v.id === variant.id)]?.variant.answer || "";
|
|
||||||
|
|
||||||
return largeCheck ? (
|
|
||||||
<Box sx={{ overflow: "auto" }}>
|
|
||||||
<TextareaAutosize
|
|
||||||
placeholder={ownPlaceholder || "|"}
|
|
||||||
style={{
|
|
||||||
resize: "none",
|
|
||||||
width: "100%",
|
|
||||||
fontSize: "16px",
|
|
||||||
color: ownAnswer.length === 0 ? "ownPlaceholder" : theme.palette.text.primary,
|
|
||||||
letterSpacing: "-0.4px",
|
|
||||||
wordSpacing: "-3px",
|
|
||||||
outline: "0px none",
|
|
||||||
backgroundColor: "inherit",
|
|
||||||
border: "none",
|
|
||||||
//@ts-ignore
|
|
||||||
"&::-webkit-scrollbar": {
|
|
||||||
width: "4px",
|
|
||||||
},
|
|
||||||
"&::-webkit-scrollbar-thumb": {
|
|
||||||
backgroundColor: theme.palette.primary.main,
|
|
||||||
},
|
|
||||||
scrollbarColor: theme.palette.primary.main,
|
|
||||||
overflow: "auto",
|
|
||||||
}}
|
|
||||||
value={ownAnswer}
|
|
||||||
onClick={(e: React.MouseEvent<HTMLTextAreaElement>) => e.stopPropagation()}
|
|
||||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
||||||
updateOwnVariant(variant.id, e.target.value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
) : (
|
|
||||||
<Input
|
|
||||||
placeholder={ownPlaceholder || "|"}
|
|
||||||
sx={{
|
|
||||||
backgroundColor: "inherit",
|
|
||||||
width: "100%",
|
|
||||||
fontSize: "18px",
|
|
||||||
color: ownAnswer.length === 0 ? "ownPlaceholder" : theme.palette.text.primary,
|
|
||||||
}}
|
|
||||||
value={ownAnswer}
|
|
||||||
disableUnderline
|
|
||||||
onClick={(e: React.MouseEvent<HTMLElement>) => e.stopPropagation()}
|
|
||||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
||||||
updateOwnVariant(variant.id, e.target.value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const EmojiVariant = ({
|
|
||||||
answer,
|
|
||||||
variant,
|
|
||||||
index,
|
|
||||||
questionId,
|
|
||||||
isMulti,
|
|
||||||
own,
|
|
||||||
questionLargeCheck,
|
|
||||||
ownPlaceholder,
|
|
||||||
}: EmojiVariantProps) => {
|
|
||||||
const { settings } = useQuizStore();
|
|
||||||
const { updateAnswer, deleteAnswer, updateOwnVariant, ownVariants } = useQuizViewStore((state) => state);
|
|
||||||
const theme = useTheme();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const customEmoji = ownVariants.find((v: OwnVariant) => v.id === variant.id)?.variant.extendedText || "";
|
|
||||||
|
|
||||||
const onVariantClick = async (event: MouseEvent<HTMLDivElement>) => {
|
|
||||||
event.preventDefault();
|
|
||||||
const variantId = variant.id;
|
|
||||||
|
|
||||||
if (isMulti) {
|
|
||||||
const currentAnswer = Array.isArray(answer) ? answer : [];
|
|
||||||
const newAnswer = currentAnswer.includes(variantId)
|
|
||||||
? currentAnswer.filter((item) => item !== variantId)
|
|
||||||
: [...currentAnswer, variantId];
|
|
||||||
updateAnswer(questionId, newAnswer, variant.points || 0);
|
|
||||||
} else {
|
|
||||||
if (answer === variant.id) {
|
|
||||||
deleteAnswer(questionId);
|
|
||||||
} else {
|
|
||||||
updateAnswer(questionId, variant.id, variant.points || 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEmojiSelect = (emoji: string) => {
|
|
||||||
// We store custom emoji in ownVariants store, with a specific field to differentiate
|
|
||||||
const currentOwnAnswer = ownVariants.find((v: OwnVariant) => v.id === variant.id)?.variant.answer || "";
|
|
||||||
updateOwnVariant(variant.id, currentOwnAnswer, emoji);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEmojiRemove = () => {
|
|
||||||
// Сохраняем текущий answer, очищаем только extendedText (эмодзи)
|
|
||||||
const currentOwnAnswer = ownVariants.find((v: OwnVariant) => v.id === variant.id)?.variant.answer || "";
|
|
||||||
updateOwnVariant(variant.id, currentOwnAnswer, "");
|
|
||||||
};
|
|
||||||
|
|
||||||
const isSelected = isMulti ? Array.isArray(answer) && answer.includes(variant.id) : answer === variant.id;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormControl
|
|
||||||
key={index}
|
|
||||||
sx={{
|
|
||||||
borderRadius: "12px",
|
|
||||||
border: `1px solid`,
|
|
||||||
borderColor: isSelected ? theme.palette.primary.main : "#9A9AAF",
|
|
||||||
overflow: "hidden",
|
|
||||||
maxWidth: "317px",
|
|
||||||
width: "100%",
|
|
||||||
height: "255px",
|
|
||||||
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",
|
|
||||||
"&:hover": { borderColor: theme.palette.primary.main },
|
|
||||||
}}
|
|
||||||
onClick={onVariantClick}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
height: "193px",
|
|
||||||
background: "#ffffff",
|
|
||||||
cursor: "pointer",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{own ? (
|
|
||||||
<OwnEmojiPicker
|
|
||||||
emoji={customEmoji || variant.extendedText}
|
|
||||||
onEmojiSelect={handleEmojiSelect}
|
|
||||||
onEmojiRemove={customEmoji ? handleEmojiRemove : undefined}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
width: "100%",
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{variant.extendedText && <Typography fontSize="100px">{variant.extendedText}</Typography>}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
{own && (
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
fontSize: "14px",
|
|
||||||
pl: "15px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("Enter your answer")}
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
<FormControlLabel
|
|
||||||
key={variant.id}
|
|
||||||
sx={{
|
|
||||||
textAlign: "center",
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
margin: 0,
|
|
||||||
padding: "15px",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: variant.answer.length <= 60 ? "center" : "flex-start",
|
|
||||||
position: "relative",
|
|
||||||
height: "80px",
|
|
||||||
justifyContent: "center",
|
|
||||||
"& .MuiFormControlLabel-label": {
|
|
||||||
wordBreak: "break-word",
|
|
||||||
height: variant.answer.length <= 60 ? "100%" : "60px",
|
|
||||||
overflow: "auto",
|
|
||||||
"&::-webkit-scrollbar": { width: "4px" },
|
|
||||||
"&::-webkit-scrollbar-thumb": {
|
|
||||||
backgroundColor: theme.palette.primary.main,
|
|
||||||
},
|
|
||||||
scrollbarColor: theme.palette.primary.main,
|
|
||||||
width: "100%",
|
|
||||||
},
|
|
||||||
"& .MuiFormControlLabel-label.Mui-disabled": {
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
value={index}
|
|
||||||
control={
|
|
||||||
isMulti ? (
|
|
||||||
<Checkbox
|
|
||||||
checked={isSelected}
|
|
||||||
checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
|
|
||||||
icon={<RadioIcon />}
|
|
||||||
sx={{ position: "absolute", top: "-162px", right: "12px" }}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Radio
|
|
||||||
checked={isSelected}
|
|
||||||
checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
|
|
||||||
icon={<RadioIcon />}
|
|
||||||
sx={{ position: "absolute", top: "-162px", right: "12px" }}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
label={
|
|
||||||
own ? (
|
|
||||||
<OwnInput
|
|
||||||
questionId={questionId}
|
|
||||||
variant={variant}
|
|
||||||
largeCheck={questionLargeCheck}
|
|
||||||
ownPlaceholder={ownPlaceholder || "|"}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Box sx={{ display: "flex", gap: "10px" }}>
|
|
||||||
<Typography sx={{ wordBreak: "break-word", lineHeight: "normal" }}>{variant.answer}</Typography>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,103 +0,0 @@
|
|||||||
import { Box, ButtonBase, Typography, useTheme, Modal, IconButton } from "@mui/material";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { EmojiPicker } from "./EmojiPicker";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import CloseIcon from "@mui/icons-material/Close";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
emoji: string;
|
|
||||||
onEmojiSelect?: (emoji: string) => void;
|
|
||||||
onEmojiRemove?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const OwnEmojiPicker = ({ emoji = "", onEmojiSelect, onEmojiRemove }: Props) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [isPickerOpen, setIsPickerOpen] = useState(false);
|
|
||||||
|
|
||||||
const handleEmojiSelect = (emojiData: any) => {
|
|
||||||
onEmojiSelect?.(emojiData.native);
|
|
||||||
setIsPickerOpen(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClick = (e: React.MouseEvent) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
setIsPickerOpen(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClose = (e: React.MouseEvent) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
setIsPickerOpen(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRemoveEmoji = (e: React.MouseEvent) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
onEmojiRemove?.();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Box sx={{ width: "100%", height: "100%", position: "relative" }}>
|
|
||||||
<ButtonBase
|
|
||||||
onClick={handleClick}
|
|
||||||
sx={{
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
"&:hover": {
|
|
||||||
bgcolor: theme.palette.grey[100],
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography fontSize={emoji ? "100px" : "18px"}>{emoji || t("select emoji")}</Typography>
|
|
||||||
</ButtonBase>
|
|
||||||
|
|
||||||
{onEmojiRemove && (
|
|
||||||
<IconButton
|
|
||||||
onClick={handleRemoveEmoji}
|
|
||||||
sx={{
|
|
||||||
position: "absolute",
|
|
||||||
top: 8,
|
|
||||||
left: 8,
|
|
||||||
zIndex: 1,
|
|
||||||
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
|
||||||
color: "white",
|
|
||||||
height: "25px",
|
|
||||||
width: "25px",
|
|
||||||
"&:hover": {
|
|
||||||
backgroundColor: "rgba(0, 0, 0, 0.7)",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CloseIcon />
|
|
||||||
</IconButton>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Modal
|
|
||||||
open={isPickerOpen}
|
|
||||||
onClose={handleClose}
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
}}
|
|
||||||
keepMounted
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
sx={{
|
|
||||||
bgcolor: "background.paper",
|
|
||||||
borderRadius: 2,
|
|
||||||
p: 2,
|
|
||||||
boxShadow: 24,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<EmojiPicker onEmojiSelect={handleEmojiSelect} />
|
|
||||||
</Box>
|
|
||||||
</Modal>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,66 +0,0 @@
|
|||||||
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";
|
|
||||||
import moment from "moment";
|
|
||||||
|
|
||||||
polyfillCountryFlagEmojis();
|
|
||||||
|
|
||||||
type EmojiProps = {
|
|
||||||
currentQuestion: QuizQuestionEmoji;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Emoji = ({ currentQuestion }: EmojiProps) => {
|
|
||||||
const answers = useQuizViewStore((state) => state.answers);
|
|
||||||
const theme = useTheme();
|
|
||||||
const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {};
|
|
||||||
|
|
||||||
const selectedVariantId = Array.isArray(answer) ? answer[0] : answer;
|
|
||||||
|
|
||||||
if (moment.isMoment(answer)) throw new Error("Answer is Moment in Variant question");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Typography
|
|
||||||
variant="h5"
|
|
||||||
color={theme.palette.text.primary}
|
|
||||||
sx={{ wordBreak: "break-word" }}
|
|
||||||
>
|
|
||||||
{currentQuestion.title}
|
|
||||||
</Typography>
|
|
||||||
<RadioGroup
|
|
||||||
name={currentQuestion.id}
|
|
||||||
value={selectedVariantId}
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexWrap: "wrap",
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
marginTop: "20px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box sx={{ display: "flex", width: "100%", gap: "42px", flexWrap: "wrap" }}>
|
|
||||||
{currentQuestion.content.variants
|
|
||||||
.filter((v) => {
|
|
||||||
if (!v.isOwn) return true;
|
|
||||||
return v.isOwn && currentQuestion.content.own;
|
|
||||||
})
|
|
||||||
.map((variant, index) => (
|
|
||||||
<EmojiVariant
|
|
||||||
key={variant.id}
|
|
||||||
questionId={currentQuestion.id}
|
|
||||||
variant={variant}
|
|
||||||
index={index}
|
|
||||||
isMulti={Boolean(currentQuestion.content.multi)}
|
|
||||||
own={Boolean(variant.isOwn)}
|
|
||||||
questionLargeCheck={true}
|
|
||||||
answer={answer}
|
|
||||||
ownPlaceholder={currentQuestion.content?.ownPlaceholder || ""}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
</RadioGroup>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,150 +0,0 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import { Box, ButtonBase, Skeleton, Typography, useTheme } from "@mui/material";
|
|
||||||
import { enqueueSnackbar } from "notistack";
|
|
||||||
|
|
||||||
import { sendAnswer, sendFile } from "@api/quizRelase";
|
|
||||||
import { useRootContainerSize } from "@contexts/RootContainerWidthContext";
|
|
||||||
import { useQuizViewStore } from "@stores/quizView";
|
|
||||||
|
|
||||||
import {
|
|
||||||
ACCEPT_SEND_FILE_TYPES_MAP,
|
|
||||||
MAX_FILE_SIZE,
|
|
||||||
UPLOAD_FILE_DESCRIPTIONS_MAP,
|
|
||||||
} from "@/components/ViewPublicationPage/tools/fileUpload";
|
|
||||||
|
|
||||||
import Info from "@icons/Info";
|
|
||||||
import UploadIcon from "@icons/UploadIcon";
|
|
||||||
|
|
||||||
import type { QuizQuestionFile } from "@model/questionTypes/file";
|
|
||||||
import type { ModalWarningType } from "./index";
|
|
||||||
import { useQuizStore } from "@/stores/useQuizStore";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
type UploadFileProps = {
|
|
||||||
currentQuestion: QuizQuestionFile;
|
|
||||||
setModalWarningType: (modalType: ModalWarningType) => void;
|
|
||||||
isSending: boolean;
|
|
||||||
setIsSending: (isSending: boolean) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const UploadFile = ({ currentQuestion, setModalWarningType, isSending, setIsSending }: UploadFileProps) => {
|
|
||||||
const { quizId, preview } = useQuizStore();
|
|
||||||
const [isDropzoneHighlighted, setIsDropzoneHighlighted] = useState<boolean>(false);
|
|
||||||
const theme = useTheme();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
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 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)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!isFileTypeAccepted) return setModalWarningType("errorType");
|
|
||||||
|
|
||||||
setIsSending(true);
|
|
||||||
try {
|
|
||||||
const data = await sendFile({
|
|
||||||
questionId: currentQuestion.id,
|
|
||||||
body: {
|
|
||||||
file: file,
|
|
||||||
name: file.name,
|
|
||||||
preview,
|
|
||||||
},
|
|
||||||
qid: quizId,
|
|
||||||
});
|
|
||||||
await sendAnswer({
|
|
||||||
questionId: currentQuestion.id,
|
|
||||||
body: `${data!.data.fileIDMap[currentQuestion.id]}`,
|
|
||||||
qid: quizId,
|
|
||||||
preview,
|
|
||||||
});
|
|
||||||
|
|
||||||
updateAnswer(currentQuestion.id, `${file.name}|${URL.createObjectURL(file)}`, 0);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
enqueueSnackbar(t("The answer was not counted"));
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsSending(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onDrop = (event: React.DragEvent<HTMLDivElement>) => {
|
|
||||||
event.preventDefault();
|
|
||||||
setIsDropzoneHighlighted(false);
|
|
||||||
|
|
||||||
const file = event.dataTransfer.files[0];
|
|
||||||
|
|
||||||
uploadFile(file);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
|
||||||
{isSending ? (
|
|
||||||
<Skeleton
|
|
||||||
variant="rounded"
|
|
||||||
sx={{ width: "100%", height: "120px", maxWidth: "560px" }}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<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(",")}
|
|
||||||
multiple
|
|
||||||
type="file"
|
|
||||||
/>
|
|
||||||
<Box
|
|
||||||
onDragEnter={() => !answer?.split("|")[0] && setIsDropzoneHighlighted(true)}
|
|
||||||
onDragLeave={() => setIsDropzoneHighlighted(false)}
|
|
||||||
onDragOver={(event) => event.preventDefault()}
|
|
||||||
onDrop={onDrop}
|
|
||||||
sx={{
|
|
||||||
width: "100%",
|
|
||||||
height: isMobile ? undefined : "120px",
|
|
||||||
display: "flex",
|
|
||||||
gap: "50px",
|
|
||||||
justifyContent: "flex-start",
|
|
||||||
alignItems: "center",
|
|
||||||
padding: "33px 44px 33px 55px",
|
|
||||||
backgroundColor: "#F2F3F7",
|
|
||||||
border: `1px solid ${isDropzoneHighlighted ? "red" : "#9A9AAF"}`,
|
|
||||||
borderRadius: "8px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<UploadIcon />
|
|
||||||
<Box>
|
|
||||||
<Typography sx={{ color: "#9A9AAF", fontWeight: 500 }}>
|
|
||||||
{t(UPLOAD_FILE_DESCRIPTIONS_MAP[currentQuestion.content.type].title)}
|
|
||||||
</Typography>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
color: "#9A9AAF",
|
|
||||||
fontSize: "16px",
|
|
||||||
lineHeight: "19px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t(UPLOAD_FILE_DESCRIPTIONS_MAP[currentQuestion.content.type].description)}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</ButtonBase>
|
|
||||||
)}
|
|
||||||
<Info
|
|
||||||
sx={{ width: "40px", height: "40px" }}
|
|
||||||
color={theme.palette.primary.main}
|
|
||||||
onClick={() => setModalWarningType(currentQuestion.content.type)}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,75 +0,0 @@
|
|||||||
import { Box, IconButton, Typography, useTheme } from "@mui/material";
|
|
||||||
|
|
||||||
import { sendAnswer } from "@api/quizRelase";
|
|
||||||
import { useQuizViewStore } from "@stores/quizView";
|
|
||||||
|
|
||||||
import CloseBold from "@icons/CloseBold";
|
|
||||||
|
|
||||||
import type { QuizQuestionFile } from "@model/questionTypes/file";
|
|
||||||
import { useQuizStore } from "@/stores/useQuizStore";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
type UploadedFileProps = {
|
|
||||||
currentQuestion: QuizQuestionFile;
|
|
||||||
setIsSending: (isSending: boolean) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const UploadedFile = ({ currentQuestion, setIsSending }: UploadedFileProps) => {
|
|
||||||
const { quizId, preview } = useQuizStore();
|
|
||||||
const answers = useQuizViewStore((state) => state.answers);
|
|
||||||
const { updateAnswer } = useQuizViewStore((state) => state);
|
|
||||||
const theme = useTheme();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const answer = answers.find(({ questionId }) => questionId === currentQuestion.id)?.answer as string;
|
|
||||||
|
|
||||||
const deleteFile = async () => {
|
|
||||||
if (answer.length > 0) {
|
|
||||||
setIsSending(true);
|
|
||||||
|
|
||||||
await sendAnswer({
|
|
||||||
questionId: currentQuestion.id,
|
|
||||||
body: "",
|
|
||||||
qid: quizId,
|
|
||||||
preview,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
updateAnswer(currentQuestion.id, "", 0);
|
|
||||||
setIsSending(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box sx={{ display: "flex", alignItems: "center", gap: "15px" }}>
|
|
||||||
<Typography color={theme.palette.text.primary}>{t("You have uploaded")}:</Typography>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
padding: "5px 5px 5px 16px",
|
|
||||||
backgroundColor: theme.palette.primary.main,
|
|
||||||
borderRadius: "8px",
|
|
||||||
color: "#FFFFFF",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
overflow: "hidden",
|
|
||||||
gap: "15px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
whiteSpace: "nowrap",
|
|
||||||
textOverflow: "ellipsis",
|
|
||||||
overflow: "hidden",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{answer?.split("|")[0]}
|
|
||||||
</Typography>
|
|
||||||
<IconButton
|
|
||||||
sx={{ p: 0 }}
|
|
||||||
onClick={deleteFile}
|
|
||||||
>
|
|
||||||
<CloseBold />
|
|
||||||
</IconButton>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,122 +0,0 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import { Box, Modal, Typography, useTheme } from "@mui/material";
|
|
||||||
|
|
||||||
import { UploadFile } from "./UploadFile";
|
|
||||||
import { UploadedFile } from "./UploadedFile";
|
|
||||||
|
|
||||||
import { useRootContainerSize } from "@contexts/RootContainerWidthContext";
|
|
||||||
import { useQuizViewStore } from "@stores/quizView";
|
|
||||||
|
|
||||||
import { ACCEPT_SEND_FILE_TYPES_MAP } from "@/components/ViewPublicationPage/tools/fileUpload";
|
|
||||||
|
|
||||||
import type { QuizQuestionFile } from "@model/questionTypes/file";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
export type ModalWarningType = "errorType" | "errorSize" | "picture" | "video" | "audio" | "document" | null;
|
|
||||||
|
|
||||||
type FileProps = {
|
|
||||||
currentQuestion: QuizQuestionFile;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const File = ({ currentQuestion }: FileProps) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const answers = useQuizViewStore((state) => state.answers);
|
|
||||||
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;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Typography
|
|
||||||
variant="h5"
|
|
||||||
color={theme.palette.text.primary}
|
|
||||||
sx={{ wordBreak: "break-word" }}
|
|
||||||
>
|
|
||||||
{currentQuestion.title}
|
|
||||||
</Typography>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
width: "100%",
|
|
||||||
marginTop: "20px",
|
|
||||||
maxWidth: answer?.split("|")[0] ? "640px" : "600px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{answer?.split("|")[0] ? (
|
|
||||||
<UploadedFile
|
|
||||||
currentQuestion={currentQuestion}
|
|
||||||
setIsSending={setIsSending}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<UploadFile
|
|
||||||
currentQuestion={currentQuestion}
|
|
||||||
setModalWarningType={setModalWarningType}
|
|
||||||
isSending={isSending}
|
|
||||||
setIsSending={setIsSending}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{answer && currentQuestion.content.type === "picture" && (
|
|
||||||
<img
|
|
||||||
src={answer.split("|")[1]}
|
|
||||||
style={{ marginTop: "15px", maxWidth: "300px", maxHeight: "300px" }}
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{answer && currentQuestion.content.type === "video" && (
|
|
||||||
<video
|
|
||||||
src={answer.split("|")[1]}
|
|
||||||
style={{
|
|
||||||
marginTop: "15px",
|
|
||||||
maxWidth: "300px",
|
|
||||||
maxHeight: "300px",
|
|
||||||
objectFit: "cover",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
<Modal
|
|
||||||
open={modalWarningType !== null}
|
|
||||||
onClose={() => setModalWarningType(null)}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
position: "absolute",
|
|
||||||
top: "50%",
|
|
||||||
left: "50%",
|
|
||||||
transform: "translate(-50%, -50%)",
|
|
||||||
width: isMobile ? 300 : 400,
|
|
||||||
bgcolor: "background.paper",
|
|
||||||
borderRadius: 3,
|
|
||||||
boxShadow: 24,
|
|
||||||
p: 4,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CurrentModal status={modalWarningType} />
|
|
||||||
</Box>
|
|
||||||
</Modal>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const CurrentModal = ({ status }: { status: ModalWarningType }) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
switch (status) {
|
|
||||||
case null:
|
|
||||||
return null;
|
|
||||||
case "errorType":
|
|
||||||
return <Typography>{t("Incorrect file type selected")}</Typography>;
|
|
||||||
case "errorSize":
|
|
||||||
return <Typography>{t("File is too big. Maximum size is 50 MB")}</Typography>;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Typography>{t("Acceptable file extensions")}:</Typography>
|
|
||||||
<Typography>{ACCEPT_SEND_FILE_TYPES_MAP[status].join(" ")}</Typography>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,282 +0,0 @@
|
|||||||
import type { QuestionVariant, QuestionVariantWithEditedImages } from "@/model/questionTypes/shared";
|
|
||||||
import { Box, Checkbox, FormControlLabel, Input, Radio, TextareaAutosize, Typography, 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 { useMemo, type MouseEvent, useRef, useEffect } from "react";
|
|
||||||
import { useRootContainerSize } from "@contexts/RootContainerWidthContext";
|
|
||||||
import { useQuizStore } from "@/stores/useQuizStore";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { OwnImage } from "./OwnImage";
|
|
||||||
import { useSnackbar } from "notistack";
|
|
||||||
|
|
||||||
type ImagesProps = {
|
|
||||||
questionId: string;
|
|
||||||
variant: QuestionVariantWithEditedImages;
|
|
||||||
index: number;
|
|
||||||
answer: string | string[] | undefined;
|
|
||||||
isMulti: boolean;
|
|
||||||
own: boolean;
|
|
||||||
questionLargeCheck: boolean;
|
|
||||||
ownPlaceholder: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface OwnInputProps {
|
|
||||||
questionId: string;
|
|
||||||
variant: QuestionVariant;
|
|
||||||
largeCheck: boolean;
|
|
||||||
ownPlaceholder: string;
|
|
||||||
}
|
|
||||||
const OwnInput = ({ variant, largeCheck, ownPlaceholder }: OwnInputProps) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const ownVariants = useQuizViewStore((state) => state.ownVariants);
|
|
||||||
const { updateOwnVariant } = useQuizViewStore((state) => state);
|
|
||||||
|
|
||||||
const ownAnswer = ownVariants[ownVariants.findIndex((v) => v.id === variant.id)]?.variant.answer || "";
|
|
||||||
|
|
||||||
return largeCheck ? (
|
|
||||||
<Box sx={{ overflow: "auto" }}>
|
|
||||||
<TextareaAutosize
|
|
||||||
placeholder={ownPlaceholder || "|"}
|
|
||||||
style={{
|
|
||||||
resize: "none",
|
|
||||||
width: "100%",
|
|
||||||
fontSize: "16px",
|
|
||||||
color: ownAnswer.length === 0 ? "ownPlaceholder" : theme.palette.text.primary,
|
|
||||||
letterSpacing: "-0.4px",
|
|
||||||
wordSpacing: "-3px",
|
|
||||||
outline: "0px none",
|
|
||||||
backgroundColor: "inherit",
|
|
||||||
border: "none",
|
|
||||||
//@ts-ignore
|
|
||||||
"&::-webkit-scrollbar": {
|
|
||||||
width: "4px",
|
|
||||||
},
|
|
||||||
"&::-webkit-scrollbar-thumb": {
|
|
||||||
backgroundColor: theme.palette.primary.main,
|
|
||||||
},
|
|
||||||
scrollbarColor: theme.palette.primary.main,
|
|
||||||
}}
|
|
||||||
value={ownAnswer}
|
|
||||||
onClick={(e: React.MouseEvent<HTMLTextAreaElement>) => e.stopPropagation()}
|
|
||||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
||||||
updateOwnVariant(variant.id, e.target.value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
) : (
|
|
||||||
<Input
|
|
||||||
placeholder={ownPlaceholder || "|"}
|
|
||||||
sx={{
|
|
||||||
backgroundColor: "inherit",
|
|
||||||
width: "100%",
|
|
||||||
fontSize: "18px",
|
|
||||||
color: ownAnswer.length === 0 ? "ownPlaceholder" : theme.palette.text.primary,
|
|
||||||
}}
|
|
||||||
value={ownAnswer}
|
|
||||||
disableUnderline
|
|
||||||
onClick={(e: React.MouseEvent<HTMLElement>) => e.stopPropagation()}
|
|
||||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
||||||
updateOwnVariant(variant.id, e.target.value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ImageVariant = ({
|
|
||||||
questionId,
|
|
||||||
answer,
|
|
||||||
isMulti,
|
|
||||||
variant,
|
|
||||||
index,
|
|
||||||
own,
|
|
||||||
questionLargeCheck,
|
|
||||||
ownPlaceholder,
|
|
||||||
}: ImagesProps) => {
|
|
||||||
const { settings } = useQuizStore();
|
|
||||||
const { deleteAnswer, updateAnswer } = useQuizViewStore((state) => state);
|
|
||||||
const theme = useTheme();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const isMobile = useRootContainerSize() < 450;
|
|
||||||
const isTablet = useRootContainerSize() < 850;
|
|
||||||
const { enqueueSnackbar } = useSnackbar();
|
|
||||||
|
|
||||||
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
|
||||||
|
|
||||||
const onVariantClick = async (event: MouseEvent<HTMLDivElement>) => {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
const variantId = variant.id;
|
|
||||||
if (isMulti) {
|
|
||||||
const currentAnswer = typeof answer !== "string" ? answer || [] : [];
|
|
||||||
|
|
||||||
return updateAnswer(
|
|
||||||
questionId,
|
|
||||||
currentAnswer.includes(variantId)
|
|
||||||
? currentAnswer?.filter((item) => item !== variantId)
|
|
||||||
: [...currentAnswer, variantId],
|
|
||||||
variant.points || 0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateAnswer(questionId, variantId, variant.points || 0);
|
|
||||||
|
|
||||||
if (answer === variantId) {
|
|
||||||
deleteAnswer(questionId);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const choiceImgUrl = useMemo(() => {
|
|
||||||
if (variant.editedUrlImagesList !== undefined && variant.editedUrlImagesList !== null) {
|
|
||||||
return variant.editedUrlImagesList[isMobile ? "mobile" : isTablet ? "tablet" : "desktop"];
|
|
||||||
} else {
|
|
||||||
return variant.extendedText;
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (canvasRef.current !== null) {
|
|
||||||
const canvas = canvasRef.current;
|
|
||||||
const ctx = canvas.getContext("2d");
|
|
||||||
if (ctx !== null) {
|
|
||||||
const img = new Image();
|
|
||||||
img.src = choiceImgUrl;
|
|
||||||
|
|
||||||
img.onload = () => {
|
|
||||||
canvas.width = img.width;
|
|
||||||
canvas.height = img.height;
|
|
||||||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
position: "relative",
|
|
||||||
cursor: "pointer",
|
|
||||||
borderRadius: "12px",
|
|
||||||
border: `1px solid`,
|
|
||||||
borderColor: !!answer?.includes(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",
|
|
||||||
}}
|
|
||||||
onClick={onVariantClick}
|
|
||||||
>
|
|
||||||
<Box sx={{ display: "flex", alignItems: "center", gap: "10px" }}>
|
|
||||||
<Box sx={{ width: "100%", height: "300px" }}>
|
|
||||||
{own ? (
|
|
||||||
<OwnImage
|
|
||||||
imageUrl={choiceImgUrl}
|
|
||||||
questionId={questionId}
|
|
||||||
variantId={variant.id}
|
|
||||||
onValidationError={(errorType) => {
|
|
||||||
enqueueSnackbar(errorType === "size" ? t("file is too big") : t("file type is not supported"), {
|
|
||||||
variant: "warning",
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
variant.extendedText && (
|
|
||||||
<canvas
|
|
||||||
ref={canvasRef}
|
|
||||||
style={{
|
|
||||||
display: "block",
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
objectFit: "cover",
|
|
||||||
borderRadius: "12px 12px 0 0",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
{own && (
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
fontSize: "14px",
|
|
||||||
pl: "15px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("Enter your answer")}
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
<FormControlLabel
|
|
||||||
key={variant.id}
|
|
||||||
sx={{
|
|
||||||
textAlign: "center",
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
marginTop: "10px",
|
|
||||||
marginLeft: 0,
|
|
||||||
padding: "10px",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: variant.answer.length <= 60 ? "center" : "flex-start",
|
|
||||||
justifyContent: "center",
|
|
||||||
position: "relative",
|
|
||||||
height: "80px",
|
|
||||||
"& .MuiFormControlLabel-label": {
|
|
||||||
wordBreak: "break-word",
|
|
||||||
height: variant.answer.length <= 60 ? undefined : "60px",
|
|
||||||
lineHeight: "normal",
|
|
||||||
overflow: "auto",
|
|
||||||
maxHeight: "100%",
|
|
||||||
width: "100%",
|
|
||||||
"&::-webkit-scrollbar": {
|
|
||||||
width: "4px",
|
|
||||||
},
|
|
||||||
"&::-webkit-scrollbar-thumb": {
|
|
||||||
backgroundColor: theme.palette.primary.main,
|
|
||||||
},
|
|
||||||
scrollbarColor: theme.palette.primary.main,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
value={index}
|
|
||||||
control={
|
|
||||||
isMulti ? (
|
|
||||||
<Checkbox
|
|
||||||
checked={!!answer?.includes(variant.id)}
|
|
||||||
checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
|
|
||||||
icon={<RadioIcon />}
|
|
||||||
sx={{
|
|
||||||
position: "absolute",
|
|
||||||
top: "-297px",
|
|
||||||
right: 0,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Radio
|
|
||||||
checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
|
|
||||||
icon={<RadioIcon />}
|
|
||||||
sx={{
|
|
||||||
position: "absolute",
|
|
||||||
top: "-297px",
|
|
||||||
right: 0,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
label={
|
|
||||||
own ? (
|
|
||||||
<OwnInput
|
|
||||||
questionId={questionId}
|
|
||||||
variant={variant}
|
|
||||||
largeCheck={questionLargeCheck}
|
|
||||||
ownPlaceholder={ownPlaceholder || "|"}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
variant.answer
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,187 +0,0 @@
|
|||||||
import { Box, ButtonBase, IconButton, Typography, useTheme } from "@mui/material";
|
|
||||||
import { useState, useRef } from "react";
|
|
||||||
import CloseIcon from "@mui/icons-material/Close";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { useQuizStore } from "@/stores/useQuizStore";
|
|
||||||
import { useQuizViewStore } from "@/stores/quizView";
|
|
||||||
import { useSnackbar } from "notistack";
|
|
||||||
import { Skeleton } from "@mui/material";
|
|
||||||
import UploadIcon from "@/assets/icons/UploadIcon";
|
|
||||||
import { sendFile } from "@/api/quizRelase";
|
|
||||||
import { ACCEPT_SEND_FILE_TYPES_MAP, MAX_FILE_SIZE } from "../../tools/fileUpload";
|
|
||||||
|
|
||||||
// Пропсы компонента
|
|
||||||
export type OwnImageProps = {
|
|
||||||
imageUrl?: string;
|
|
||||||
questionId: string;
|
|
||||||
variantId: string;
|
|
||||||
onValidationError: (error: "size" | "type") => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const OwnImage = ({ imageUrl, questionId, variantId, onValidationError }: OwnImageProps) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { quizId, preview } = useQuizStore();
|
|
||||||
const { ownVariants, updateOwnVariant } = useQuizViewStore((state) => state);
|
|
||||||
const { enqueueSnackbar } = useSnackbar();
|
|
||||||
|
|
||||||
const [isUploading, setIsUploading] = useState(false);
|
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
||||||
|
|
||||||
// Получаем ownVariant для этого варианта
|
|
||||||
const ownVariantData = ownVariants.find((v) => v.id === variantId);
|
|
||||||
|
|
||||||
// Загрузка файла
|
|
||||||
const uploadImage = async (file: File) => {
|
|
||||||
if (isUploading) return;
|
|
||||||
if (!file) return;
|
|
||||||
if (file.size > MAX_FILE_SIZE) {
|
|
||||||
onValidationError("size");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const isFileTypeAccepted = ACCEPT_SEND_FILE_TYPES_MAP.picture.some((fileType) =>
|
|
||||||
file.name.toLowerCase().endsWith(fileType)
|
|
||||||
);
|
|
||||||
if (!isFileTypeAccepted) {
|
|
||||||
onValidationError("type");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setIsUploading(true);
|
|
||||||
try {
|
|
||||||
const data = await sendFile({
|
|
||||||
questionId,
|
|
||||||
body: { file, name: file.name, preview },
|
|
||||||
qid: quizId,
|
|
||||||
});
|
|
||||||
const fileId = data?.data.fileIDMap[questionId];
|
|
||||||
const localImageUrl = URL.createObjectURL(file);
|
|
||||||
updateOwnVariant(variantId, "", "", fileId, localImageUrl);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error uploading image:", error);
|
|
||||||
enqueueSnackbar(t("The answer was not counted"));
|
|
||||||
} finally {
|
|
||||||
setIsUploading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Обработчик выбора файла
|
|
||||||
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const file = event.target.files?.[0];
|
|
||||||
if (file) {
|
|
||||||
uploadImage(file);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Открытие диалога выбора файла
|
|
||||||
const handleClick = (e: React.MouseEvent) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
if (fileInputRef.current) fileInputRef.current.value = "";
|
|
||||||
fileInputRef.current?.click();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Удаление изображения
|
|
||||||
const handleRemoveImage = (e: React.MouseEvent) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
updateOwnVariant(variantId, ownVariantData?.variant.answer || "", "", "", "");
|
|
||||||
/*
|
|
||||||
1 - answer - письменный ответ
|
|
||||||
2 - extendedText - строка используется в эмодзи-вопросах для хранения выбранного эмодзи
|
|
||||||
3 - originalImageUrl - полный URL изображения, загруженного на сервер
|
|
||||||
4 - localImageUrl - временный URL для отображения изображения в браузере
|
|
||||||
*/
|
|
||||||
};
|
|
||||||
|
|
||||||
// Определяем, что показывать
|
|
||||||
let imageToDisplay: string | null = null;
|
|
||||||
if (ownVariantData?.variant.localImageUrl) {
|
|
||||||
imageToDisplay = ownVariantData.variant.localImageUrl;
|
|
||||||
} else if (imageUrl) {
|
|
||||||
imageToDisplay = imageUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isUploading) {
|
|
||||||
return (
|
|
||||||
<Skeleton
|
|
||||||
variant="rounded"
|
|
||||||
sx={{ width: "100%", height: "100%", borderRadius: "12px" }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ButtonBase
|
|
||||||
component="div"
|
|
||||||
onClick={handleClick}
|
|
||||||
disabled={isUploading}
|
|
||||||
sx={{
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
borderRadius: "12px",
|
|
||||||
transition: "border-color 0.3s, background-color 0.3s",
|
|
||||||
overflow: "hidden",
|
|
||||||
position: "relative",
|
|
||||||
opacity: isUploading ? 0.7 : 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
ref={fileInputRef}
|
|
||||||
onChange={handleFileChange}
|
|
||||||
accept={ACCEPT_SEND_FILE_TYPES_MAP.picture.join(",")}
|
|
||||||
hidden
|
|
||||||
/>
|
|
||||||
{imageToDisplay ? (
|
|
||||||
<>
|
|
||||||
<Box sx={{ width: "100%", height: "100%", position: "relative" }}>
|
|
||||||
<img
|
|
||||||
src={imageToDisplay}
|
|
||||||
alt="Preview"
|
|
||||||
style={{ width: "100%", height: "100%", objectFit: "cover" }}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<IconButton
|
|
||||||
onClick={handleRemoveImage}
|
|
||||||
sx={{
|
|
||||||
position: "absolute",
|
|
||||||
top: 8,
|
|
||||||
left: 8,
|
|
||||||
zIndex: 1,
|
|
||||||
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
|
||||||
color: "white",
|
|
||||||
height: "25px",
|
|
||||||
width: "25px",
|
|
||||||
display: ownVariantData?.variant.localImageUrl ? "inherit" : "none",
|
|
||||||
"&:hover": {
|
|
||||||
backgroundColor: "rgba(0, 0, 0, 0.7)",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CloseIcon />
|
|
||||||
</IconButton>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
opacity: 0.5,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<UploadIcon />
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
color="text.secondary"
|
|
||||||
sx={{ p: 2, textAlign: "center" }}
|
|
||||||
>
|
|
||||||
{t("Add your image")}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</ButtonBase>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,71 +0,0 @@
|
|||||||
import { useRootContainerSize } from "@contexts/RootContainerWidthContext";
|
|
||||||
import type { QuizQuestionImages } from "@model/questionTypes/images";
|
|
||||||
import { Box, RadioGroup, Typography, useTheme } from "@mui/material";
|
|
||||||
import { createQuizViewStore, useQuizViewStore } from "@stores/quizView";
|
|
||||||
import { ImageVariant } from "./ImageVariant";
|
|
||||||
import moment from "moment";
|
|
||||||
|
|
||||||
type ImagesProps = {
|
|
||||||
currentQuestion: QuizQuestionImages;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Images = ({ currentQuestion }: ImagesProps) => {
|
|
||||||
const answers = useQuizViewStore((state) => state.answers);
|
|
||||||
const theme = useTheme();
|
|
||||||
const answer = answers.find(({ questionId }) => questionId === currentQuestion.id)?.answer;
|
|
||||||
const isTablet = useRootContainerSize() < 1000;
|
|
||||||
const isMobile = useRootContainerSize() < 500;
|
|
||||||
|
|
||||||
if (moment.isMoment(answer)) throw new Error("Answer is Moment in Variant question");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Typography
|
|
||||||
variant="h5"
|
|
||||||
color={theme.palette.text.primary}
|
|
||||||
sx={{ wordBreak: "break-word" }}
|
|
||||||
>
|
|
||||||
{currentQuestion.title}
|
|
||||||
</Typography>
|
|
||||||
<RadioGroup
|
|
||||||
name={currentQuestion.id.toString()}
|
|
||||||
value={currentQuestion.content.variants.findIndex(({ id }) => answer === id)}
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexWrap: "wrap",
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
marginTop: "20px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "grid",
|
|
||||||
gap: "15px",
|
|
||||||
gridTemplateColumns: isTablet ? (isMobile ? "repeat(1, 1fr)" : "repeat(2, 1fr)") : "repeat(3, 1fr)",
|
|
||||||
width: "100%",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{currentQuestion.content.variants
|
|
||||||
.filter((v) => {
|
|
||||||
if (!v.isOwn) return true;
|
|
||||||
return v.isOwn && currentQuestion.content.own;
|
|
||||||
})
|
|
||||||
.map((variant, index) => (
|
|
||||||
<ImageVariant
|
|
||||||
key={variant.id}
|
|
||||||
questionId={currentQuestion.id}
|
|
||||||
variant={variant}
|
|
||||||
index={index}
|
|
||||||
answer={answer}
|
|
||||||
isMulti={Boolean(currentQuestion.content.multi)}
|
|
||||||
own={Boolean(variant.isOwn)}
|
|
||||||
questionLargeCheck={true}
|
|
||||||
ownPlaceholder={currentQuestion.content?.ownPlaceholder || ""}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
</RadioGroup>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,391 +0,0 @@
|
|||||||
import { useQuizStore } from "@/stores/useQuizStore";
|
|
||||||
import type { QuizQuestionNumber } from "@model/questionTypes/number";
|
|
||||||
import { Box, Typography, useTheme } from "@mui/material";
|
|
||||||
import { useQuizViewStore } from "@stores/quizView";
|
|
||||||
import { CustomSlider } from "@ui_kit/CustomSlider";
|
|
||||||
import CustomTextField from "@ui_kit/CustomTextField";
|
|
||||||
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 [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 } = useQuizStore();
|
|
||||||
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 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 sliderValue =
|
|
||||||
answer ||
|
|
||||||
(reversed ? max + min - currentQuestion.content.start + "—" + max : currentQuestion.content.start + "—" + max);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log("reversed:", reversed);
|
|
||||||
}, [reversed]);
|
|
||||||
|
|
||||||
const sendAnswerToBackend = async (value: string, noUpdate = false) => {
|
|
||||||
if (!noUpdate) {
|
|
||||||
updateAnswer(currentQuestion.id, value, 0);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateValueDebounced = useDebouncedCallback(async (value: string) => {
|
|
||||||
if (reversed) {
|
|
||||||
const newValue =
|
|
||||||
window.Number(value) < window.Number(min)
|
|
||||||
? String(min)
|
|
||||||
: window.Number(value) > window.Number(max)
|
|
||||||
? String(max)
|
|
||||||
: value;
|
|
||||||
|
|
||||||
setReversedInputValue(newValue);
|
|
||||||
updateAnswer(currentQuestion.id, String(max + min - window.Number(newValue)), 0);
|
|
||||||
await sendAnswerToBackend(String(window.Number(newValue)), true);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newValue =
|
|
||||||
window.Number(value) < window.Number(minRange)
|
|
||||||
? minRange
|
|
||||||
: window.Number(value) > window.Number(maxRange)
|
|
||||||
? 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
|
|
||||||
? min
|
|
||||||
: max + min - window.Number(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);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
? max
|
|
||||||
: max + min - window.Number(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);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newMaxValue = crowded
|
|
||||||
? minRange
|
|
||||||
: window.Number(value.split("—")[1]) > max
|
|
||||||
? String(max)
|
|
||||||
: value.split("—")[1];
|
|
||||||
|
|
||||||
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])));
|
|
||||||
} else {
|
|
||||||
setMinRange(answer.split("—")[0]);
|
|
||||||
setMaxRange(answer.split("—")[1]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (reversed) {
|
|
||||||
setReversedInputValue(String(max + min - window.Number(answer)));
|
|
||||||
} else {
|
|
||||||
setInputValue(answer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!answer) {
|
|
||||||
setMinRange(String(currentQuestion.content.start));
|
|
||||||
setMaxRange(String(max));
|
|
||||||
|
|
||||||
if (currentQuestion.content.chooseRange) {
|
|
||||||
setReversedMinRange(String(currentQuestion.content.start));
|
|
||||||
setReversedMaxRange(String(min));
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
updateAnswer(currentQuestion.id, range, 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
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]);
|
|
||||||
const newMaxReversedValue = String(max + min - value[1]);
|
|
||||||
|
|
||||||
setMinRange(String(value[0]));
|
|
||||||
setMaxRange(String(value[1]));
|
|
||||||
setReversedMinRange(newMinReversedValue);
|
|
||||||
setReversedMaxRange(newMaxReversedValue);
|
|
||||||
await sendAnswerToBackend(`${newMinReversedValue}—${newMaxReversedValue}`, true);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setMinRange(String(value[0]));
|
|
||||||
setMaxRange(String(value[1]));
|
|
||||||
await sendAnswerToBackend(`${value[0]}—${value[1]}`);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reversed) {
|
|
||||||
setReversedInputValue(String(max + min - window.Number(value)));
|
|
||||||
} else {
|
|
||||||
setInputValue(String(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
await sendAnswerToBackend(String(value));
|
|
||||||
};
|
|
||||||
|
|
||||||
const changeValueLabelFormat = (value: number) => {
|
|
||||||
if (!reversed) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [minSliderBorder, maxSliderBorder] = sliderValue.split("—").map(window.Number);
|
|
||||||
|
|
||||||
if (value === minSliderBorder) {
|
|
||||||
return max + min - minSliderBorder;
|
|
||||||
}
|
|
||||||
|
|
||||||
return max + min - maxSliderBorder;
|
|
||||||
};
|
|
||||||
|
|
||||||
const onInputChange = ({ target }: ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const value = target.value.replace(/\D/g, "");
|
|
||||||
|
|
||||||
if (reversed) {
|
|
||||||
setReversedInputValue(value);
|
|
||||||
} else {
|
|
||||||
setInputValue(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateValueDebounced(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onMinInputChange = ({ target }: ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const newValue = target.value.replace(/\D/g, "");
|
|
||||||
|
|
||||||
if (reversed) {
|
|
||||||
setReversedMinRange(newValue);
|
|
||||||
|
|
||||||
if (window.Number(newValue) <= window.Number(reversedMaxRange)) {
|
|
||||||
const value = max + min - window.Number(reversedMaxRange);
|
|
||||||
updateMinRangeDebounced(`${value}—${value}`, true);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateMinRangeDebounced(`${newValue}—${max + min - window.Number(reversedMaxRange)}`);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setMinRange(newValue);
|
|
||||||
|
|
||||||
if (window.Number(newValue) >= window.Number(maxRange)) {
|
|
||||||
updateMinRangeDebounced(`${maxRange}—${maxRange}`, true);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateMinRangeDebounced(`${newValue}—${maxRange}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onMaxInputChange = ({ target }: ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const newValue = target.value.replace(/\D/g, "");
|
|
||||||
|
|
||||||
if (reversed) {
|
|
||||||
setReversedMaxRange(newValue);
|
|
||||||
|
|
||||||
if (window.Number(newValue) >= window.Number(reversedMinRange)) {
|
|
||||||
const value = max + min - window.Number(reversedMinRange);
|
|
||||||
updateMaxRangeDebounced(`${value}—${value}`, true);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateMaxRangeDebounced(`${max + min - window.Number(reversedMinRange)}—${newValue}`);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setMaxRange(newValue);
|
|
||||||
|
|
||||||
if (window.Number(newValue) <= window.Number(minRange)) {
|
|
||||||
updateMaxRangeDebounced(`${minRange}—${minRange}`, true);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateMaxRangeDebounced(`${minRange}—${newValue}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Typography
|
|
||||||
variant="h5"
|
|
||||||
color={theme.palette.text.primary}
|
|
||||||
sx={{ wordBreak: "break-word" }}
|
|
||||||
>
|
|
||||||
{currentQuestion.title}
|
|
||||||
</Typography>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
width: "100%",
|
|
||||||
marginTop: "20px",
|
|
||||||
gap: "30px",
|
|
||||||
padding: "0 30px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CustomSlider
|
|
||||||
value={
|
|
||||||
currentQuestion.content.chooseRange
|
|
||||||
? sliderValue.split("—").length || 0 > 1
|
|
||||||
? sliderValue.split("—").map((item) => window.Number(item))
|
|
||||||
: [min, min + 1]
|
|
||||||
: window.Number(sliderValue.split("—")[0])
|
|
||||||
}
|
|
||||||
min={min}
|
|
||||||
max={max}
|
|
||||||
step={currentQuestion.content.step || 1}
|
|
||||||
onChange={onSliderChange}
|
|
||||||
onChangeCommitted={onChangeCommitted}
|
|
||||||
valueLabelFormat={changeValueLabelFormat}
|
|
||||||
sx={{
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
"& .MuiSlider-valueLabel": {
|
|
||||||
background: theme.palette.primary.main,
|
|
||||||
borderRadius: "8px",
|
|
||||||
minWidth: "60px",
|
|
||||||
height: "36px",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{!currentQuestion.content.chooseRange && (
|
|
||||||
<CustomTextField
|
|
||||||
placeholder="0"
|
|
||||||
value={reversed ? reversedInputValue : inputValue}
|
|
||||||
onChange={onInputChange}
|
|
||||||
sx={{
|
|
||||||
maxWidth: "80px",
|
|
||||||
borderColor: theme.palette.text.primary,
|
|
||||||
"& .MuiOutlinedInput-root": { background: "transparent" },
|
|
||||||
"& .MuiInputBase-input": { textAlign: "center", zIndex: 1 },
|
|
||||||
"& .MuiOutlinedInput-notchedOutline": {
|
|
||||||
backgroundColor: quizThemes[settings.cfg.theme].isLight ? "white" : theme.palette.background.default,
|
|
||||||
borderColor: "#9A9AAF",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{currentQuestion.content.chooseRange && (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
gap: "15px",
|
|
||||||
alignItems: "center",
|
|
||||||
"& .MuiFormControl-root": { width: "auto" },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CustomTextField
|
|
||||||
placeholder="0"
|
|
||||||
value={reversed ? String(reversedMinRange) : minRange}
|
|
||||||
onChange={onMinInputChange}
|
|
||||||
sx={{
|
|
||||||
maxWidth: "80px",
|
|
||||||
borderColor: theme.palette.text.primary,
|
|
||||||
"& .MuiOutlinedInput-root": { background: "transparent" },
|
|
||||||
"& .MuiInputBase-input": { textAlign: "center", zIndex: 1 },
|
|
||||||
"& .MuiOutlinedInput-notchedOutline": {
|
|
||||||
backgroundColor: quizThemes[settings.cfg.theme].isLight ? "white" : theme.palette.background.default,
|
|
||||||
borderColor: "#9A9AAF",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Typography color={theme.palette.text.primary}>до</Typography>
|
|
||||||
<CustomTextField
|
|
||||||
placeholder="0"
|
|
||||||
value={reversed ? String(reversedMaxRange) : maxRange}
|
|
||||||
onChange={onMaxInputChange}
|
|
||||||
sx={{
|
|
||||||
maxWidth: "80px",
|
|
||||||
"& .MuiOutlinedInput-root": { background: "transparent" },
|
|
||||||
"& .MuiInputBase-input": { textAlign: "center", zIndex: 1 },
|
|
||||||
"& .MuiOutlinedInput-notchedOutline": {
|
|
||||||
backgroundColor: quizThemes[settings.cfg.theme].isLight ? "white" : theme.palette.background.default,
|
|
||||||
borderColor: "#9A9AAF",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,76 +0,0 @@
|
|||||||
import { Box, Typography, useTheme } from "@mui/material";
|
|
||||||
import type { QuizQuestionPage } from "@model/questionTypes/page";
|
|
||||||
import QuizVideo from "@/ui_kit/VideoIframe/VideoIframe";
|
|
||||||
|
|
||||||
type PageProps = {
|
|
||||||
currentQuestion: QuizQuestionPage;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Page = ({ currentQuestion }: PageProps) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Typography
|
|
||||||
variant="h5"
|
|
||||||
sx={{
|
|
||||||
paddingBottom: "25px",
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
wordBreak: "break-word",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{currentQuestion.title}
|
|
||||||
</Typography>
|
|
||||||
<Typography
|
|
||||||
color={theme.palette.text.primary}
|
|
||||||
sx={{ wordBreak: "break-word" }}
|
|
||||||
>
|
|
||||||
{currentQuestion.content.text}
|
|
||||||
</Typography>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
width: "100%",
|
|
||||||
marginTop: "20px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{currentQuestion.content.useImage
|
|
||||||
? currentQuestion.content.back && (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
borderRadius: "12px",
|
|
||||||
border: "1px solid #9A9AAF",
|
|
||||||
overflow: "hidden",
|
|
||||||
}}
|
|
||||||
onClick={(event) => event.preventDefault()}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
key={currentQuestion.id}
|
|
||||||
src={currentQuestion.content.back}
|
|
||||||
alt=""
|
|
||||||
style={{
|
|
||||||
display: "block",
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
objectFit: "contain",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
: currentQuestion.content.video && (
|
|
||||||
<QuizVideo
|
|
||||||
containerSX={{
|
|
||||||
width: "100%",
|
|
||||||
height: "calc(100% - 270px)",
|
|
||||||
maxHeight: "80%",
|
|
||||||
objectFit: "contain",
|
|
||||||
aspectRatio: "16 / 9",
|
|
||||||
}}
|
|
||||||
videoUrl={currentQuestion.content.video}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,144 +0,0 @@
|
|||||||
import { useRootContainerSize } from "@contexts/RootContainerWidthContext";
|
|
||||||
import FlagIcon from "@icons/questionsPage/FlagIcon";
|
|
||||||
import StarIconMini from "@icons/questionsPage/StarIconMini";
|
|
||||||
import HashtagIcon from "@icons/questionsPage/hashtagIcon";
|
|
||||||
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}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "trophie",
|
|
||||||
icon: (color: string, width: number) => (
|
|
||||||
<TropfyIcon
|
|
||||||
width={width}
|
|
||||||
color={color}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "flag",
|
|
||||||
icon: (color: string, width: number) => (
|
|
||||||
<FlagIcon
|
|
||||||
width={width}
|
|
||||||
color={color}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "heart",
|
|
||||||
icon: (color: string, width: number) => (
|
|
||||||
<HeartIcon
|
|
||||||
width={width}
|
|
||||||
color={color}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "like",
|
|
||||||
icon: (color: string, width: number) => (
|
|
||||||
<LikeIcon
|
|
||||||
width={width}
|
|
||||||
color={color}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "bubble",
|
|
||||||
icon: (color: string, width: number) => (
|
|
||||||
<LightbulbIcon
|
|
||||||
width={width}
|
|
||||||
color={color}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "hashtag",
|
|
||||||
icon: (color: string, width: number) => (
|
|
||||||
<HashtagIcon
|
|
||||||
width={width}
|
|
||||||
color={color}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
type RatingProps = {
|
|
||||||
currentQuestion: QuizQuestionRating;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Rating = ({ currentQuestion }: RatingProps) => {
|
|
||||||
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 sendRating = async (value: number | null) => {
|
|
||||||
updateAnswer(currentQuestion.id, String(value), 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Typography
|
|
||||||
variant="h5"
|
|
||||||
color={theme.palette.text.primary}
|
|
||||||
sx={{ wordBreak: "break-word" }}
|
|
||||||
>
|
|
||||||
{currentQuestion.title}
|
|
||||||
</Typography>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "inline-flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: "20px",
|
|
||||||
marginTop: "20px",
|
|
||||||
flexDirection: "column",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box sx={{ display: "inline-block", width: "100%" }}>
|
|
||||||
<RatingComponent
|
|
||||||
value={Number(answer || 0)}
|
|
||||||
onChange={(_, value) => sendRating(value)}
|
|
||||||
sx={{
|
|
||||||
height: "50px",
|
|
||||||
opacity: "1!important",
|
|
||||||
"& .MuiRating-root.Mui-disabled": { opacity: "1!important" },
|
|
||||||
"& .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)}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
gap: 2,
|
|
||||||
width: "100%",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography sx={{ color: "#9A9AAF" }}>{currentQuestion.content.ratingNegativeDescription}</Typography>
|
|
||||||
<Typography sx={{ color: "#9A9AAF" }}>{currentQuestion.content.ratingPositiveDescription}</Typography>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,66 +0,0 @@
|
|||||||
import { Select as SelectComponent } from "@/components/ViewPublicationPage/tools/Select";
|
|
||||||
import { useQuizStore } from "@/stores/useQuizStore";
|
|
||||||
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 { settings } = useQuizStore();
|
|
||||||
const { updateAnswer, deleteAnswer } = useQuizViewStore((state) => state);
|
|
||||||
const answers = useQuizViewStore((state) => state.answers);
|
|
||||||
const theme = useTheme();
|
|
||||||
const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {};
|
|
||||||
|
|
||||||
const sendSelectedAnswer = async (value: number) => {
|
|
||||||
if (value < 0) {
|
|
||||||
deleteAnswer(currentQuestion.id);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateAnswer(currentQuestion.id, String(value), 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Typography
|
|
||||||
variant="h5"
|
|
||||||
color={theme.palette.text.primary}
|
|
||||||
sx={{ wordBreak: "break-word" }}
|
|
||||||
>
|
|
||||||
{currentQuestion.title}
|
|
||||||
</Typography>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
width: "100%",
|
|
||||||
marginTop: "20px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SelectComponent
|
|
||||||
placeholder={currentQuestion.content.default}
|
|
||||||
activeItemIndex={answer ? Number(answer) : -1}
|
|
||||||
items={currentQuestion.content.variants.map(({ answer }) => answer)}
|
|
||||||
colorMain={theme.palette.primary.main}
|
|
||||||
sx={{
|
|
||||||
"& .MuiSelect-select.MuiSelect-outlined": { zIndex: 1 },
|
|
||||||
"& .MuiOutlinedInput-notchedOutline": {
|
|
||||||
background: settings.cfg.design
|
|
||||||
? quizThemes[settings.cfg.theme].isLight
|
|
||||||
? "#F2F3F7"
|
|
||||||
: "rgba(255,255,255, 0.3)"
|
|
||||||
: "transparent",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
onChange={(_, value) => sendSelectedAnswer(value)}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
@ -37,7 +37,6 @@ export const TextNormal = ({ currentQuestion, answer }: TextNormalProps) => {
|
|||||||
return currentQuestion.content.back;
|
return currentQuestion.content.back;
|
||||||
}
|
}
|
||||||
}, [currentQuestion]);
|
}, [currentQuestion]);
|
||||||
let isCrutch23022025 = window.location.pathname === "/bf8cae3a-e150-479d-befa-7f264087b223";
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Typography
|
<Typography
|
||||||
@ -52,7 +51,7 @@ export const TextNormal = ({ currentQuestion, answer }: TextNormalProps) => {
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
marginTop: "20px",
|
marginTop: "20px",
|
||||||
flexDirection: isCrutch23022025 ? "column" : isMobile ? "column-reverse" : undefined,
|
flexDirection: isMobile ? "column-reverse" : undefined,
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -75,9 +74,9 @@ export const TextNormal = ({ currentQuestion, answer }: TextNormalProps) => {
|
|||||||
{choiceImgUrlQuestion && choiceImgUrlQuestion !== " " && choiceImgUrlQuestion !== null && (
|
{choiceImgUrlQuestion && choiceImgUrlQuestion !== " " && choiceImgUrlQuestion !== null && (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
maxWidth: isCrutch23022025 ? undefined : "400px",
|
maxWidth: "400px",
|
||||||
width: isCrutch23022025 ? "auto" : "100%",
|
width: "100%",
|
||||||
height: isCrutch23022025 ? "auto" : "300px",
|
height: "300px",
|
||||||
margin: "15px",
|
margin: "15px",
|
||||||
}}
|
}}
|
||||||
onClick={(event) => event.preventDefault()}
|
onClick={(event) => event.preventDefault()}
|
||||||
|
@ -1,139 +0,0 @@
|
|||||||
import { Box, TextField as MuiTextField, TextFieldProps, Typography, useTheme } from "@mui/material";
|
|
||||||
|
|
||||||
import { Answer, useQuizViewStore } from "@stores/quizView";
|
|
||||||
import { useRootContainerSize } from "@contexts/RootContainerWidthContext";
|
|
||||||
|
|
||||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
|
||||||
|
|
||||||
import type { ChangeEvent, FC } from "react";
|
|
||||||
import type { QuizQuestionText } from "@model/questionTypes/text";
|
|
||||||
import { useQuizStore } from "@/stores/useQuizStore";
|
|
||||||
|
|
||||||
const TextField = MuiTextField as unknown as FC<TextFieldProps>; // temporary fix ts(2590)
|
|
||||||
|
|
||||||
const ORIENTATION = [
|
|
||||||
{ horizontal: true },
|
|
||||||
{ horizontal: false },
|
|
||||||
{ horizontal: true },
|
|
||||||
{ horizontal: true },
|
|
||||||
{ horizontal: false },
|
|
||||||
{ horizontal: true },
|
|
||||||
{ horizontal: true },
|
|
||||||
{ horizontal: true },
|
|
||||||
{ horizontal: true },
|
|
||||||
{ horizontal: true },
|
|
||||||
{ horizontal: true },
|
|
||||||
{ horizontal: false },
|
|
||||||
{ horizontal: true },
|
|
||||||
{ horizontal: false },
|
|
||||||
{ horizontal: true },
|
|
||||||
{ horizontal: true },
|
|
||||||
{ horizontal: true },
|
|
||||||
{ horizontal: true },
|
|
||||||
{ horizontal: false },
|
|
||||||
{ horizontal: false },
|
|
||||||
{ horizontal: true },
|
|
||||||
{ horizontal: true },
|
|
||||||
{ horizontal: true },
|
|
||||||
{ horizontal: true },
|
|
||||||
];
|
|
||||||
|
|
||||||
interface TextSpecialProps {
|
|
||||||
currentQuestion: QuizQuestionText;
|
|
||||||
answer?: Answer;
|
|
||||||
stepNumber?: number | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const TextSpecial = ({ currentQuestion, answer, stepNumber }: TextSpecialProps) => {
|
|
||||||
const { settings } = useQuizStore();
|
|
||||||
const { updateAnswer } = useQuizViewStore((state) => state);
|
|
||||||
const isHorizontal = ORIENTATION[Number(stepNumber) - 1].horizontal;
|
|
||||||
const theme = useTheme();
|
|
||||||
const isMobile = useRootContainerSize() < 650;
|
|
||||||
|
|
||||||
const onInputChange = async ({ target }: ChangeEvent<HTMLInputElement>) => {
|
|
||||||
updateAnswer(currentQuestion.id, target.value, 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: isMobile ? "column" : undefined,
|
|
||||||
alignItems: isMobile ? "center" : undefined,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
width: "100%",
|
|
||||||
marginTop: "20px",
|
|
||||||
flexDirection: "column",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: "20px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
variant="h5"
|
|
||||||
color={theme.palette.text.primary}
|
|
||||||
sx={{ wordBreak: "break-word" }}
|
|
||||||
>
|
|
||||||
{currentQuestion.title}
|
|
||||||
</Typography>
|
|
||||||
{isHorizontal && currentQuestion.content.back && currentQuestion.content.back !== " " && (
|
|
||||||
<Box
|
|
||||||
sx={{ margin: "30px", width: "50vw", maxHeight: "550px" }}
|
|
||||||
onClick={(event) => event.preventDefault()}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
key={currentQuestion.id}
|
|
||||||
src={currentQuestion.content.back}
|
|
||||||
style={{ width: "100%", height: "100%", objectFit: "cover" }}
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
{
|
|
||||||
<TextField
|
|
||||||
autoFocus={true}
|
|
||||||
multiline
|
|
||||||
maxRows={4}
|
|
||||||
placeholder={currentQuestion.content.placeholder}
|
|
||||||
value={answer || ""}
|
|
||||||
onChange={onInputChange}
|
|
||||||
inputProps={{
|
|
||||||
maxLength: 400,
|
|
||||||
background: settings.cfg.design
|
|
||||||
? quizThemes[settings.cfg.theme].isLight
|
|
||||||
? "#F2F3F7"
|
|
||||||
: "rgba(154,154,175, 0.2)"
|
|
||||||
: "transparent",
|
|
||||||
}}
|
|
||||||
sx={{
|
|
||||||
width: "100%",
|
|
||||||
"& .MuiOutlinedInput-root": {
|
|
||||||
backgroundColor: settings.cfg.design ? "rgba(154,154,175, 0.2)" : "#FFFFFF",
|
|
||||||
},
|
|
||||||
"&:focus-visible": {
|
|
||||||
borderColor: theme.palette.primary.main,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</Box>
|
|
||||||
{!isHorizontal && currentQuestion.content.back && currentQuestion.content.back !== " " && (
|
|
||||||
<Box
|
|
||||||
sx={{ margin: "15px", width: "40vw" }}
|
|
||||||
onClick={(event) => event.preventDefault()}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
key={currentQuestion.id}
|
|
||||||
src={currentQuestion.content.back}
|
|
||||||
style={{ width: "100%", height: "100%", objectFit: "contain" }}
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,112 +0,0 @@
|
|||||||
import { Box, TextField as MuiTextField, TextFieldProps, Typography, useTheme } from "@mui/material";
|
|
||||||
|
|
||||||
import { Answer, useQuizViewStore } from "@stores/quizView";
|
|
||||||
import { useRootContainerSize } from "@contexts/RootContainerWidthContext";
|
|
||||||
|
|
||||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
|
||||||
|
|
||||||
import type { ChangeEvent, FC } from "react";
|
|
||||||
import type { QuizQuestionText } from "@model/questionTypes/text";
|
|
||||||
import { useQuizStore } from "@/stores/useQuizStore";
|
|
||||||
|
|
||||||
const TextField = MuiTextField as unknown as FC<TextFieldProps>; // temporary fix ts(2590)
|
|
||||||
|
|
||||||
interface TextSpecialProps {
|
|
||||||
currentQuestion: QuizQuestionText;
|
|
||||||
answer?: Answer;
|
|
||||||
stepNumber?: number | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const TextSpecialHorisontal = ({ currentQuestion, answer, stepNumber }: TextSpecialProps) => {
|
|
||||||
const { settings } = useQuizStore();
|
|
||||||
const { updateAnswer } = useQuizViewStore((state) => state);
|
|
||||||
const isHorizontal = true;
|
|
||||||
const theme = useTheme();
|
|
||||||
const isMobile = useRootContainerSize() < 650;
|
|
||||||
|
|
||||||
const onInputChange = async ({ target }: ChangeEvent<HTMLInputElement>) => {
|
|
||||||
updateAnswer(currentQuestion.id, target.value, 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: isMobile ? "column" : undefined,
|
|
||||||
alignItems: isMobile ? "center" : undefined,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
width: "100%",
|
|
||||||
marginTop: "20px",
|
|
||||||
flexDirection: "column",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: "20px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
variant="h5"
|
|
||||||
color={theme.palette.text.primary}
|
|
||||||
sx={{ wordBreak: "break-word" }}
|
|
||||||
>
|
|
||||||
{currentQuestion.title}
|
|
||||||
</Typography>
|
|
||||||
{isHorizontal && currentQuestion.content.back && currentQuestion.content.back !== " " && (
|
|
||||||
<Box
|
|
||||||
sx={{ margin: "30px", width: "50vw", maxHeight: "550px" }}
|
|
||||||
onClick={(event) => event.preventDefault()}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
key={currentQuestion.id}
|
|
||||||
src={currentQuestion.content.back}
|
|
||||||
style={{ width: "100%", height: "100%", objectFit: "contain" }}
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
{
|
|
||||||
<TextField
|
|
||||||
autoFocus={true}
|
|
||||||
multiline
|
|
||||||
maxRows={4}
|
|
||||||
placeholder={currentQuestion.content.placeholder}
|
|
||||||
value={answer || ""}
|
|
||||||
onChange={onInputChange}
|
|
||||||
inputProps={{
|
|
||||||
maxLength: 400,
|
|
||||||
background: settings.cfg.design
|
|
||||||
? quizThemes[settings.cfg.theme].isLight
|
|
||||||
? "#F2F3F7"
|
|
||||||
: "rgba(154,154,175, 0.2)"
|
|
||||||
: "transparent",
|
|
||||||
}}
|
|
||||||
sx={{
|
|
||||||
width: "100%",
|
|
||||||
"& .MuiOutlinedInput-root": {
|
|
||||||
backgroundColor: settings.cfg.design ? "rgba(154,154,175, 0.2)" : "#FFFFFF",
|
|
||||||
},
|
|
||||||
"&:focus-visible": {
|
|
||||||
borderColor: theme.palette.primary.main,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</Box>
|
|
||||||
{!isHorizontal && currentQuestion.content.back && currentQuestion.content.back !== " " && (
|
|
||||||
<Box
|
|
||||||
sx={{ margin: "15px", width: "40vw" }}
|
|
||||||
onClick={(event) => event.preventDefault()}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
key={currentQuestion.id}
|
|
||||||
src={currentQuestion.content.back}
|
|
||||||
style={{ width: "100%", height: "100%", objectFit: "contain" }}
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,55 +1,21 @@
|
|||||||
import { useQuizViewStore } from "@stores/quizView";
|
import { useQuizViewStore } from "@stores/quizView";
|
||||||
import { TextNormal } from "./TextNormal";
|
import { TextNormal } from "./TextNormal";
|
||||||
import { TextSpecial } from "./TextSpecial";
|
|
||||||
import { TextSpecialHorisontal } from "./TextSpecialHorisontal";
|
|
||||||
|
|
||||||
import type { QuizQuestionText } from "@model/questionTypes/text";
|
import type { QuizQuestionText } from "@model/questionTypes/text";
|
||||||
import { useQuizStore } from "@/stores/useQuizStore";
|
|
||||||
|
|
||||||
type TextProps = {
|
type TextProps = {
|
||||||
currentQuestion: QuizQuestionText;
|
currentQuestion: QuizQuestionText;
|
||||||
stepNumber: number | null;
|
stepNumber: number | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const pathOnly = window.location.pathname;
|
|
||||||
|
|
||||||
export const Text = ({ currentQuestion, stepNumber }: TextProps) => {
|
export const Text = ({ currentQuestion, stepNumber }: TextProps) => {
|
||||||
const { settings } = useQuizStore();
|
|
||||||
const answers = useQuizViewStore((state) => state.answers);
|
const answers = useQuizViewStore((state) => state.answers);
|
||||||
const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {};
|
const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {};
|
||||||
|
|
||||||
if (pathOnly === "/92ed5e3e-8e6a-491e-87d0-d3197682d0e3" || pathOnly === "/cc006b40-ccbd-4600-a1d3-f902f85aa0a0")
|
|
||||||
return (
|
|
||||||
<TextSpecialHorisontal
|
|
||||||
currentQuestion={currentQuestion}
|
|
||||||
answer={answer}
|
|
||||||
stepNumber={stepNumber}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
switch (settings.cfg.spec) {
|
|
||||||
case true:
|
|
||||||
return (
|
|
||||||
<TextSpecial
|
|
||||||
currentQuestion={currentQuestion}
|
|
||||||
answer={answer}
|
|
||||||
stepNumber={stepNumber}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
case undefined:
|
|
||||||
return (
|
return (
|
||||||
<TextNormal
|
<TextNormal
|
||||||
currentQuestion={currentQuestion}
|
currentQuestion={currentQuestion}
|
||||||
answer={answer}
|
answer={answer}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
default:
|
|
||||||
return (
|
|
||||||
<TextNormal
|
|
||||||
currentQuestion={currentQuestion}
|
|
||||||
answer={answer}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
@ -1,216 +0,0 @@
|
|||||||
import { useQuizStore } from "@/stores/useQuizStore";
|
|
||||||
import type { QuestionVariant } from "@model/questionTypes/shared";
|
|
||||||
import {
|
|
||||||
Checkbox,
|
|
||||||
FormControlLabel,
|
|
||||||
Input,
|
|
||||||
TextField as MuiTextField,
|
|
||||||
Radio,
|
|
||||||
TextFieldProps,
|
|
||||||
TextareaAutosize,
|
|
||||||
Typography,
|
|
||||||
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 { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
const TextField = MuiTextField as unknown as FC<TextFieldProps>;
|
|
||||||
|
|
||||||
interface OwnInputProps {
|
|
||||||
questionId: string;
|
|
||||||
variant: QuestionVariant;
|
|
||||||
largeCheck: boolean;
|
|
||||||
ownPlaceholder: string;
|
|
||||||
}
|
|
||||||
const OwnInput = ({ questionId, variant, largeCheck, ownPlaceholder }: OwnInputProps) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
const ownVariants = useQuizViewStore((state) => state.ownVariants);
|
|
||||||
const { updateOwnVariant } = useQuizViewStore((state) => state);
|
|
||||||
|
|
||||||
const ownAnswer = ownVariants[ownVariants.findIndex((v) => v.id === variant.id)]?.variant.answer || "";
|
|
||||||
|
|
||||||
return largeCheck ? (
|
|
||||||
<TextareaAutosize
|
|
||||||
placeholder={ownPlaceholder || "|"}
|
|
||||||
style={{
|
|
||||||
resize: "none",
|
|
||||||
width: "100%",
|
|
||||||
fontSize: "16px",
|
|
||||||
color: ownAnswer.length === 0 ? "ownPlaceholder" : theme.palette.text.primary,
|
|
||||||
letterSpacing: "-0.4px",
|
|
||||||
wordSpacing: "-3px",
|
|
||||||
outline: "0px none",
|
|
||||||
backgroundColor: "inherit",
|
|
||||||
border: "none",
|
|
||||||
//@ts-ignore
|
|
||||||
"&::-webkit-scrollbar": {
|
|
||||||
width: "4px",
|
|
||||||
},
|
|
||||||
"&::-webkit-scrollbar-thumb": {
|
|
||||||
backgroundColor: theme.palette.primary.main,
|
|
||||||
},
|
|
||||||
scrollbarColor: theme.palette.primary.main,
|
|
||||||
}}
|
|
||||||
value={ownAnswer}
|
|
||||||
onClick={(e: React.MouseEvent<HTMLTextAreaElement>) => e.stopPropagation()}
|
|
||||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
||||||
updateOwnVariant(variant.id, e.target.value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Input
|
|
||||||
placeholder={ownPlaceholder || "|"}
|
|
||||||
sx={{
|
|
||||||
backgroundColor: "inherit",
|
|
||||||
width: "100%",
|
|
||||||
fontSize: "18px",
|
|
||||||
color: ownAnswer.length === 0 ? "ownPlaceholder" : theme.palette.text.primary,
|
|
||||||
}}
|
|
||||||
value={ownAnswer}
|
|
||||||
disableUnderline
|
|
||||||
onClick={(e: React.MouseEvent<HTMLElement>) => e.stopPropagation()}
|
|
||||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
||||||
updateOwnVariant(variant.id, e.target.value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const VariantItem = ({
|
|
||||||
questionId,
|
|
||||||
isMulti,
|
|
||||||
variant,
|
|
||||||
answer,
|
|
||||||
index,
|
|
||||||
own = false,
|
|
||||||
questionLargeCheck,
|
|
||||||
ownPlaceholder,
|
|
||||||
}: {
|
|
||||||
isMulti: boolean;
|
|
||||||
questionId: string;
|
|
||||||
variant: QuestionVariant;
|
|
||||||
answer: string | string[] | undefined;
|
|
||||||
index: number;
|
|
||||||
own: boolean;
|
|
||||||
questionLargeCheck: boolean;
|
|
||||||
ownPlaceholder: string;
|
|
||||||
}) => {
|
|
||||||
const { settings } = useQuizStore();
|
|
||||||
const theme = useTheme();
|
|
||||||
const { updateAnswer, deleteAnswer } = useQuizViewStore((state) => state);
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const sendVariant = async (event: MouseEvent<HTMLLabelElement>) => {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
const variantId = variant.id;
|
|
||||||
|
|
||||||
if (isMulti) {
|
|
||||||
const currentAnswer = typeof answer !== "string" ? answer || [] : [];
|
|
||||||
|
|
||||||
return updateAnswer(
|
|
||||||
questionId,
|
|
||||||
currentAnswer.includes(variantId)
|
|
||||||
? currentAnswer?.filter((item) => item !== variantId)
|
|
||||||
: [...currentAnswer, variantId],
|
|
||||||
variant.points || 0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateAnswer(questionId, variantId, answer === variantId ? 0 : variant.points || 0);
|
|
||||||
|
|
||||||
if (answer === variantId) {
|
|
||||||
deleteAnswer(questionId);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormControlLabel
|
|
||||||
key={variant.id}
|
|
||||||
sx={{
|
|
||||||
position: "relative",
|
|
||||||
margin: "0",
|
|
||||||
mt: own ? "10px" : "0",
|
|
||||||
borderRadius: "12px",
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
padding: "15px",
|
|
||||||
border: `1px solid`,
|
|
||||||
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,
|
|
||||||
display: "flex",
|
|
||||||
maxWidth: "685px",
|
|
||||||
maxHeight: "85px",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
width: "100%",
|
|
||||||
"&:hover": { borderColor: theme.palette.primary.main },
|
|
||||||
"&.MuiFormControl-root": { width: "100%" },
|
|
||||||
"& .MuiFormControlLabel-label": {
|
|
||||||
width: "100%",
|
|
||||||
maxHeight: "100%",
|
|
||||||
wordBreak: "break-word",
|
|
||||||
height: variant.answer.length <= 60 ? undefined : "60px",
|
|
||||||
overflow: "auto",
|
|
||||||
lineHeight: "normal",
|
|
||||||
"&::-webkit-scrollbar": { width: "4px" },
|
|
||||||
"&::-webkit-scrollbar-thumb": { backgroundColor: theme.palette.primary.main },
|
|
||||||
scrollbarColor: theme.palette.primary.main,
|
|
||||||
},
|
|
||||||
"& .MuiFormControlLabel-label.Mui-disabled": {
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
value={index}
|
|
||||||
labelPlacement="start"
|
|
||||||
control={
|
|
||||||
isMulti ? (
|
|
||||||
<Radio
|
|
||||||
checked={!!answer?.includes(variant.id)}
|
|
||||||
checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
|
|
||||||
icon={<RadioIcon />}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Radio
|
|
||||||
checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
|
|
||||||
icon={<RadioIcon />}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
label={
|
|
||||||
own ? (
|
|
||||||
<>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
fontSize: "14px",
|
|
||||||
position: "absolute",
|
|
||||||
top: "-23px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("Enter your answer")}
|
|
||||||
</Typography>
|
|
||||||
<OwnInput
|
|
||||||
questionId={questionId}
|
|
||||||
variant={variant}
|
|
||||||
largeCheck={questionLargeCheck}
|
|
||||||
ownPlaceholder={ownPlaceholder || "|"}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
variant.answer
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onClick={sendVariant}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,159 +0,0 @@
|
|||||||
import { Box, FormGroup, RadioGroup, Typography, useTheme } from "@mui/material";
|
|
||||||
import { useEffect, useMemo } from "react";
|
|
||||||
|
|
||||||
import { VariantItem } from "./VariantItem";
|
|
||||||
|
|
||||||
import { useRootContainerSize } from "@contexts/RootContainerWidthContext";
|
|
||||||
import { useQuizViewStore } from "@stores/quizView";
|
|
||||||
|
|
||||||
import type { QuizQuestionVariant } from "@model/questionTypes/variant";
|
|
||||||
import moment from "moment";
|
|
||||||
|
|
||||||
type VariantProps = {
|
|
||||||
currentQuestion: QuizQuestionVariant;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 23.02.2025
|
|
||||||
const crutchlist = {
|
|
||||||
115048: { x: 629, y: 491 },
|
|
||||||
115101: { x: 979, y: 980 },
|
|
||||||
115109: { x: 746, y: 745 },
|
|
||||||
115122: { x: 959, y: 960 },
|
|
||||||
115132: { x: 541, y: 541 },
|
|
||||||
115142: { x: 834, y: 544 },
|
|
||||||
115178: { x: 1127, y: 1127 },
|
|
||||||
115191: { x: 1106, y: 1106 },
|
|
||||||
115207: { x: 905, y: 906 },
|
|
||||||
115254: { x: 637, y: 637 },
|
|
||||||
115270: { x: 702, y: 703 },
|
|
||||||
115287: { x: 714, y: 715 },
|
|
||||||
115329: { x: 915, y: 916 },
|
|
||||||
115348: { x: 700, y: 701 },
|
|
||||||
115368: { x: 400, y: 300 },
|
|
||||||
115389: { x: 839, y: 840 },
|
|
||||||
115411: { x: 612, y: 610 },
|
|
||||||
115434: { x: 474, y: 473 },
|
|
||||||
115462: { x: 385, y: 385 },
|
|
||||||
115487: { x: 676, y: 677 },
|
|
||||||
115515: { x: 341, y: 341 },
|
|
||||||
115547: { x: 402, y: 403 },
|
|
||||||
115575: { x: 502, y: 503 },
|
|
||||||
115612: { x: 400, y: 300 },
|
|
||||||
115642: { x: 603, y: 603 },
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Variant = ({ currentQuestion }: VariantProps) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const isMobile = useRootContainerSize() < 650;
|
|
||||||
const isTablet = useRootContainerSize() < 850;
|
|
||||||
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)?.answer;
|
|
||||||
const ownVariant = ownVariants.find((variant) => variant.id === currentQuestion.id);
|
|
||||||
|
|
||||||
const Group = currentQuestion.content.multi ? FormGroup : RadioGroup;
|
|
||||||
|
|
||||||
//let isCrutch23022025Question = isCrutch23022025 && crutchlist.hasOwnProperty(currentQuestion.id)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!ownVariant) {
|
|
||||||
updateOwnVariant(currentQuestion.id, "");
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const choiceImgUrlQuestion = useMemo(() => {
|
|
||||||
if (
|
|
||||||
currentQuestion.content.editedUrlImagesList !== undefined &&
|
|
||||||
currentQuestion.content.editedUrlImagesList !== null
|
|
||||||
) {
|
|
||||||
return currentQuestion.content.editedUrlImagesList[isMobile ? "mobile" : isTablet ? "tablet" : "desktop"];
|
|
||||||
} else {
|
|
||||||
return currentQuestion.content.back;
|
|
||||||
}
|
|
||||||
}, [currentQuestion]);
|
|
||||||
if (moment.isMoment(answer)) throw new Error("Answer is Moment in Variant question");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Typography
|
|
||||||
variant="h5"
|
|
||||||
color={theme.palette.text.primary}
|
|
||||||
sx={{ wordBreak: "break-word" }}
|
|
||||||
>
|
|
||||||
{currentQuestion.title}
|
|
||||||
</Typography>
|
|
||||||
<Box
|
|
||||||
id="batya"
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
gap: "20px",
|
|
||||||
flexDirection: isMobile ? "column-reverse" : undefined,
|
|
||||||
alignItems: isMobile ? "center" : undefined,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Group
|
|
||||||
name={currentQuestion.id.toString()}
|
|
||||||
value={currentQuestion.content.variants.findIndex(({ id }) => answer === id)}
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexWrap: "wrap",
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
flexBasis: "100%",
|
|
||||||
marginTop: "20px",
|
|
||||||
width: isMobile ? "100%" : undefined,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
flexWrap: "wrap",
|
|
||||||
width: "100%",
|
|
||||||
gap: "20px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{currentQuestion.content.variants
|
|
||||||
.filter((v) => {
|
|
||||||
if (!v.isOwn) return true;
|
|
||||||
return v.isOwn && currentQuestion.content.own;
|
|
||||||
})
|
|
||||||
.map((variant, index) => (
|
|
||||||
<VariantItem
|
|
||||||
key={variant.id}
|
|
||||||
questionId={currentQuestion.id}
|
|
||||||
isMulti={currentQuestion.content.multi}
|
|
||||||
variant={variant}
|
|
||||||
answer={answer}
|
|
||||||
index={index}
|
|
||||||
own={Boolean(variant.isOwn)}
|
|
||||||
questionLargeCheck={currentQuestion.content.largeCheck}
|
|
||||||
ownPlaceholder={currentQuestion.content?.ownPlaceholder || ""}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
</Group>
|
|
||||||
{choiceImgUrlQuestion && choiceImgUrlQuestion !== " " && choiceImgUrlQuestion !== null && (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
maxWidth: "400px",
|
|
||||||
width: "100%",
|
|
||||||
height: "300px",
|
|
||||||
}}
|
|
||||||
onClick={(event) => event.preventDefault()}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
key={currentQuestion.id}
|
|
||||||
src={choiceImgUrlQuestion}
|
|
||||||
style={{ width: "100%", height: "100%", objectFit: "cover" }}
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,83 +0,0 @@
|
|||||||
import React, { forwardRef, useState } from "react";
|
|
||||||
import { useQuizViewStore } from "@stores/quizView";
|
|
||||||
import { useQuizStore } from "@/stores/useQuizStore";
|
|
||||||
import { useSnackbar } from "notistack";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { sendFile } from "@/api/quizRelase";
|
|
||||||
import { ACCEPT_SEND_FILE_TYPES_MAP, MAX_FILE_SIZE } from "../../tools/fileUpload";
|
|
||||||
|
|
||||||
interface OwnVarimgImageProps {
|
|
||||||
questionId: string;
|
|
||||||
variantId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const OwnVarimgImage = forwardRef<HTMLInputElement, OwnVarimgImageProps>(({ questionId, variantId }, ref) => {
|
|
||||||
const { updateAnswer, updateOwnVariant } = useQuizViewStore((state) => state);
|
|
||||||
const { quizId, preview } = useQuizStore();
|
|
||||||
const { enqueueSnackbar } = useSnackbar();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const [isUploading, setIsUploading] = useState(false);
|
|
||||||
|
|
||||||
const uploadImage = async (file: File) => {
|
|
||||||
if (isUploading) return;
|
|
||||||
if (!file) return;
|
|
||||||
|
|
||||||
// Валидация размера файла
|
|
||||||
if (file.size > MAX_FILE_SIZE) {
|
|
||||||
enqueueSnackbar(t("file is too big"), { variant: "warning" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Валидация типа файла
|
|
||||||
const isFileTypeAccepted = ACCEPT_SEND_FILE_TYPES_MAP.picture.some((fileType) =>
|
|
||||||
file.name.toLowerCase().endsWith(fileType)
|
|
||||||
);
|
|
||||||
if (!isFileTypeAccepted) {
|
|
||||||
enqueueSnackbar(t("file type is not supported"), { variant: "warning" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsUploading(true);
|
|
||||||
try {
|
|
||||||
const data = await sendFile({
|
|
||||||
questionId,
|
|
||||||
body: { file, name: file.name, preview },
|
|
||||||
qid: quizId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const fileId = data?.data.fileIDMap[questionId];
|
|
||||||
const localImageUrl = URL.createObjectURL(file);
|
|
||||||
|
|
||||||
updateOwnVariant(variantId, "", "", fileId, localImageUrl);
|
|
||||||
// Убираем автоматический выбор own варианта - загрузка возможна только при выбранном own варианте
|
|
||||||
// updateAnswer(questionId, variantId, 0);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error uploading image:", error);
|
|
||||||
enqueueSnackbar(t("The answer was not counted"));
|
|
||||||
} finally {
|
|
||||||
setIsUploading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const file = event.target.files?.[0];
|
|
||||||
if (file) {
|
|
||||||
uploadImage(file);
|
|
||||||
event.target.value = "";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
ref={ref}
|
|
||||||
style={{ display: "none" }}
|
|
||||||
accept={ACCEPT_SEND_FILE_TYPES_MAP.picture.join(",")}
|
|
||||||
onChange={handleFileChange}
|
|
||||||
disabled={isUploading}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
OwnVarimgImage.displayName = "OwnVarimgImage";
|
|
@ -1,237 +0,0 @@
|
|||||||
import type { QuestionVariant, QuestionVariantWithEditedImages } from "@/model/questionTypes/shared";
|
|
||||||
import { useQuizStore } from "@/stores/useQuizStore";
|
|
||||||
import { FormControlLabel, TextareaAutosize, Radio, useTheme, Box, Input, Typography } 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 MouseEvent } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
type VarimgVariantProps = {
|
|
||||||
questionId: string;
|
|
||||||
variant: QuestionVariantWithEditedImages;
|
|
||||||
index: number;
|
|
||||||
isSending: boolean;
|
|
||||||
setIsSending: (isSending: boolean) => void;
|
|
||||||
questionLargeCheck: boolean;
|
|
||||||
isMulti: boolean;
|
|
||||||
answer: string | string[] | undefined;
|
|
||||||
ownPlaceholder: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface OwnInputProps {
|
|
||||||
questionId: string;
|
|
||||||
variant: QuestionVariant;
|
|
||||||
largeCheck: boolean;
|
|
||||||
ownPlaceholder: string;
|
|
||||||
}
|
|
||||||
const OwnInput = ({ questionId, variant, largeCheck, ownPlaceholder }: OwnInputProps) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const ownVariants = useQuizViewStore((state) => state.ownVariants);
|
|
||||||
const { updateOwnVariant } = useQuizViewStore((state) => state);
|
|
||||||
|
|
||||||
const ownAnswer = ownVariants[ownVariants.findIndex((v) => v.id === variant.id)]?.variant.answer || "";
|
|
||||||
|
|
||||||
return largeCheck ? (
|
|
||||||
<TextareaAutosize
|
|
||||||
placeholder={ownPlaceholder || "|"}
|
|
||||||
style={{
|
|
||||||
resize: "none",
|
|
||||||
width: "100%",
|
|
||||||
fontSize: "16px",
|
|
||||||
color: ownAnswer.length === 0 ? "ownPlaceholder" : theme.palette.text.primary,
|
|
||||||
letterSpacing: "-0.4px",
|
|
||||||
wordSpacing: "-3px",
|
|
||||||
outline: "0px none",
|
|
||||||
backgroundColor: "inherit",
|
|
||||||
border: "none",
|
|
||||||
//@ts-ignore
|
|
||||||
"&::-webkit-scrollbar": {
|
|
||||||
width: "4px",
|
|
||||||
},
|
|
||||||
"&::-webkit-scrollbar-thumb": {
|
|
||||||
backgroundColor: theme.palette.primary.main,
|
|
||||||
},
|
|
||||||
scrollbarColor: theme.palette.primary.main,
|
|
||||||
maxHeight: "44px",
|
|
||||||
overflow: "auto",
|
|
||||||
}}
|
|
||||||
value={ownAnswer}
|
|
||||||
onClick={(e: React.MouseEvent<HTMLTextAreaElement>) => e.stopPropagation()}
|
|
||||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
||||||
updateOwnVariant(variant.id, e.target.value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Input
|
|
||||||
placeholder={ownPlaceholder || "|"}
|
|
||||||
sx={{
|
|
||||||
backgroundColor: "inherit",
|
|
||||||
width: "100%",
|
|
||||||
fontSize: "18px",
|
|
||||||
color: ownAnswer.length === 0 ? "ownPlaceholder" : theme.palette.text.primary,
|
|
||||||
}}
|
|
||||||
value={ownAnswer}
|
|
||||||
disableUnderline
|
|
||||||
onClick={(e: React.MouseEvent<HTMLElement>) => e.stopPropagation()}
|
|
||||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
||||||
updateOwnVariant(variant.id, e.target.value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const VarimgVariant = ({
|
|
||||||
questionId,
|
|
||||||
variant,
|
|
||||||
index,
|
|
||||||
isSending,
|
|
||||||
setIsSending,
|
|
||||||
questionLargeCheck,
|
|
||||||
ownPlaceholder,
|
|
||||||
answer,
|
|
||||||
}: VarimgVariantProps) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
const { settings } = useQuizStore();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { updateAnswer, deleteAnswer } = useQuizViewStore((state) => state);
|
|
||||||
|
|
||||||
const sendVariant = async (event: MouseEvent<HTMLLabelElement>) => {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
updateAnswer(questionId, variant.id, variant.points || 0);
|
|
||||||
|
|
||||||
if (answer === variant.id) {
|
|
||||||
deleteAnswer(questionId);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (variant?.isOwn) {
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
fontSize: "14px",
|
|
||||||
pl: "15px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("Enter your answer")}
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<FormControlLabel
|
|
||||||
key={variant.id}
|
|
||||||
disabled={isSending}
|
|
||||||
sx={{
|
|
||||||
marginBottom: "15px",
|
|
||||||
borderRadius: "12px",
|
|
||||||
padding: "20px",
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
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,
|
|
||||||
border: `1px solid`,
|
|
||||||
borderColor: answer === variant.id ? theme.palette.primary.main : "#9A9AAF",
|
|
||||||
display: "flex",
|
|
||||||
margin: 0,
|
|
||||||
justifyContent: "space-between",
|
|
||||||
"&:hover": { borderColor: theme.palette.primary.main },
|
|
||||||
"& .MuiFormControlLabel-label": {
|
|
||||||
wordBreak: "break-word",
|
|
||||||
height: variant.answer.length <= 60 ? undefined : "60px",
|
|
||||||
overflow: "auto",
|
|
||||||
lineHeight: "normal",
|
|
||||||
width: "100%",
|
|
||||||
"&::-webkit-scrollbar": {
|
|
||||||
width: "4px",
|
|
||||||
},
|
|
||||||
"&::-webkit-scrollbar-thumb": {
|
|
||||||
backgroundColor: theme.palette.primary.main,
|
|
||||||
},
|
|
||||||
scrollbarColor: theme.palette.primary.main,
|
|
||||||
},
|
|
||||||
"& .MuiFormControlLabel-label.Mui-disabled": {
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
labelPlacement="start"
|
|
||||||
value={index}
|
|
||||||
onClick={sendVariant}
|
|
||||||
label={
|
|
||||||
<OwnInput
|
|
||||||
questionId={questionId}
|
|
||||||
variant={variant}
|
|
||||||
largeCheck={questionLargeCheck}
|
|
||||||
ownPlaceholder={ownPlaceholder || "|"}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
control={
|
|
||||||
<Radio
|
|
||||||
checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
|
|
||||||
icon={<RadioIcon />}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<FormControlLabel
|
|
||||||
key={variant.id}
|
|
||||||
disabled={isSending}
|
|
||||||
sx={{
|
|
||||||
marginBottom: "15px",
|
|
||||||
borderRadius: "12px",
|
|
||||||
padding: "20px",
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
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,
|
|
||||||
border: `1px solid`,
|
|
||||||
borderColor: answer === variant.id ? theme.palette.primary.main : "#9A9AAF",
|
|
||||||
display: "flex",
|
|
||||||
margin: 0,
|
|
||||||
justifyContent: "space-between",
|
|
||||||
"&:hover": { borderColor: theme.palette.primary.main },
|
|
||||||
"& .MuiFormControlLabel-label": {
|
|
||||||
wordBreak: "break-word",
|
|
||||||
height: variant.answer.length <= 60 ? undefined : "60px",
|
|
||||||
overflow: "auto",
|
|
||||||
lineHeight: "normal",
|
|
||||||
width: "100%",
|
|
||||||
"&::-webkit-scrollbar": {
|
|
||||||
width: "4px",
|
|
||||||
},
|
|
||||||
"&::-webkit-scrollbar-thumb": {
|
|
||||||
backgroundColor: theme.palette.primary.main,
|
|
||||||
},
|
|
||||||
scrollbarColor: theme.palette.primary.main,
|
|
||||||
},
|
|
||||||
"& .MuiFormControlLabel-label.Mui-disabled": {
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
labelPlacement="start"
|
|
||||||
value={index}
|
|
||||||
onClick={sendVariant}
|
|
||||||
label={variant.answer}
|
|
||||||
control={
|
|
||||||
<Radio
|
|
||||||
checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
|
|
||||||
icon={<RadioIcon />}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,267 +0,0 @@
|
|||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
|
||||||
import { Box, ButtonBase, RadioGroup, Typography, useTheme, IconButton } from "@mui/material";
|
|
||||||
import CloseIcon from "@mui/icons-material/Close";
|
|
||||||
|
|
||||||
import { VarimgVariant } from "./VarimgVariant";
|
|
||||||
import { OwnVarimgImage } from "./OwnVarimgImage";
|
|
||||||
|
|
||||||
import { useQuizViewStore } from "@stores/quizView";
|
|
||||||
import { useRootContainerSize } from "@contexts/RootContainerWidthContext";
|
|
||||||
|
|
||||||
import BlankImage from "@icons/BlankImage";
|
|
||||||
|
|
||||||
import type { QuizQuestionVarImg } from "@model/questionTypes/varimg";
|
|
||||||
import moment from "moment";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
type VarimgProps = {
|
|
||||||
currentQuestion: QuizQuestionVarImg;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Varimg = ({ currentQuestion }: VarimgProps) => {
|
|
||||||
const [isSending, setIsSending] = useState<boolean>(false);
|
|
||||||
const answers = useQuizViewStore((state) => state.answers);
|
|
||||||
const ownVariants = useQuizViewStore((state) => state.ownVariants);
|
|
||||||
const updateOwnVariant = useQuizViewStore((state) => state.updateOwnVariant);
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const theme = useTheme();
|
|
||||||
const isMobile = useRootContainerSize() < 650;
|
|
||||||
const isTablet = useRootContainerSize() < 850;
|
|
||||||
|
|
||||||
const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {};
|
|
||||||
const ownVariant = ownVariants.find((variant) => variant.id === currentQuestion.id);
|
|
||||||
const variant = currentQuestion.content.variants.find(({ id }) => answer === id);
|
|
||||||
const ownVariantInQuestion = useMemo(
|
|
||||||
() => currentQuestion.content.variants.find((v) => v.isOwn),
|
|
||||||
[currentQuestion.content.variants]
|
|
||||||
);
|
|
||||||
const ownVariantData = ownVariants.find((v) => v.id === answer);
|
|
||||||
const ownImageUrl = useMemo(() => {
|
|
||||||
return ownVariantData?.variant.localImageUrl;
|
|
||||||
}, [ownVariantData]);
|
|
||||||
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!ownVariant) {
|
|
||||||
updateOwnVariant(currentQuestion.id, "");
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const choiceImgUrlAnswer = useMemo(() => {
|
|
||||||
if (variant !== undefined) {
|
|
||||||
if (variant.editedUrlImagesList !== undefined && variant.editedUrlImagesList !== null) {
|
|
||||||
return variant.editedUrlImagesList[isMobile ? "mobile" : isTablet ? "tablet" : "desktop"];
|
|
||||||
} else {
|
|
||||||
return variant.extendedText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [variant]);
|
|
||||||
|
|
||||||
const choiceImgUrlQuestion = useMemo(() => {
|
|
||||||
if (
|
|
||||||
currentQuestion.content.editedUrlImagesList !== undefined &&
|
|
||||||
currentQuestion.content.editedUrlImagesList !== null
|
|
||||||
) {
|
|
||||||
return currentQuestion.content.editedUrlImagesList[isMobile ? "mobile" : isTablet ? "tablet" : "desktop"];
|
|
||||||
} else {
|
|
||||||
return currentQuestion.content.back;
|
|
||||||
}
|
|
||||||
}, [variant]);
|
|
||||||
|
|
||||||
const handlePreviewAreaClick = () => {
|
|
||||||
// Загрузка возможна только если own вариант выбран
|
|
||||||
if (ownVariantInQuestion && answer === ownVariantInQuestion.id) {
|
|
||||||
inputRef.current?.click();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRemoveImage = (e: React.MouseEvent) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
if (ownVariantData) {
|
|
||||||
// Сохраняем текущий answer, очищаем только изображения
|
|
||||||
const currentAnswer = ownVariantData.variant.answer || "";
|
|
||||||
updateOwnVariant(ownVariantData.id, currentAnswer, "", "", "");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (moment.isMoment(answer)) throw new Error("Answer is Moment in Variant question");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Typography
|
|
||||||
variant="h5"
|
|
||||||
color={theme.palette.text.primary}
|
|
||||||
sx={{ wordBreak: "break-word" }}
|
|
||||||
>
|
|
||||||
{currentQuestion.title}
|
|
||||||
</Typography>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
marginTop: "20px",
|
|
||||||
flexDirection: isMobile ? "column-reverse" : undefined,
|
|
||||||
gap: "30px",
|
|
||||||
alignItems: isMobile ? "center" : undefined,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<RadioGroup
|
|
||||||
name={currentQuestion.id}
|
|
||||||
value={currentQuestion.content.variants.findIndex(({ id }) => answer === id)}
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexWrap: "wrap",
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
flexBasis: "100%",
|
|
||||||
width: isMobile ? "100%" : undefined,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
width: "100%",
|
|
||||||
gap: "20px",
|
|
||||||
"&:focus": { color: theme.palette.text.primary },
|
|
||||||
"&:active": { color: theme.palette.text.primary },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{currentQuestion.content.variants
|
|
||||||
.filter((v) => {
|
|
||||||
if (!v.isOwn) return true;
|
|
||||||
return v.isOwn && currentQuestion.content.own;
|
|
||||||
})
|
|
||||||
.map((variant, index) => (
|
|
||||||
<VarimgVariant
|
|
||||||
key={variant.id}
|
|
||||||
questionId={currentQuestion.id}
|
|
||||||
variant={variant}
|
|
||||||
isSending={isSending}
|
|
||||||
setIsSending={setIsSending}
|
|
||||||
index={index}
|
|
||||||
questionLargeCheck={currentQuestion.content.largeCheck}
|
|
||||||
ownPlaceholder={currentQuestion.content?.ownPlaceholder || ""}
|
|
||||||
isMulti={Boolean(currentQuestion.content?.multi)}
|
|
||||||
answer={answer}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
{ownVariantInQuestion && (
|
|
||||||
<OwnVarimgImage
|
|
||||||
ref={inputRef}
|
|
||||||
questionId={currentQuestion.id}
|
|
||||||
variantId={ownVariantInQuestion.id}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</RadioGroup>
|
|
||||||
<ButtonBase
|
|
||||||
onClick={handlePreviewAreaClick}
|
|
||||||
disabled={!ownVariantInQuestion || answer !== ownVariantInQuestion.id}
|
|
||||||
sx={{
|
|
||||||
maxWidth: "450px",
|
|
||||||
width: "100%",
|
|
||||||
height: "450px",
|
|
||||||
border: "1px solid #9A9AAF",
|
|
||||||
borderRadius: "12px",
|
|
||||||
overflow: "hidden",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
backgroundColor: "#9A9AAF30",
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
textAlign: "center",
|
|
||||||
position: "relative",
|
|
||||||
"&:hover": {
|
|
||||||
backgroundColor:
|
|
||||||
ownVariantInQuestion && answer === ownVariantInQuestion.id ? "rgba(0,0,0,0.04)" : "transparent",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{(() => {
|
|
||||||
if (answer) {
|
|
||||||
const imageUrl = variant?.isOwn && ownImageUrl ? ownImageUrl : choiceImgUrlAnswer;
|
|
||||||
if (imageUrl) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<img
|
|
||||||
key={imageUrl}
|
|
||||||
src={imageUrl}
|
|
||||||
style={{ width: "100%", height: "100%", objectFit: "cover" }}
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
{variant?.isOwn && ownImageUrl && (
|
|
||||||
<IconButton
|
|
||||||
onClick={handleRemoveImage}
|
|
||||||
sx={{
|
|
||||||
position: "absolute",
|
|
||||||
top: 8,
|
|
||||||
left: 8,
|
|
||||||
zIndex: 1,
|
|
||||||
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
|
||||||
color: "white",
|
|
||||||
height: "25px",
|
|
||||||
width: "25px",
|
|
||||||
"&:hover": {
|
|
||||||
backgroundColor: "rgba(0, 0, 0, 0.7)",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CloseIcon />
|
|
||||||
</IconButton>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
position: "relative",
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<BlankImage />
|
|
||||||
{variant?.isOwn && (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
position: "absolute",
|
|
||||||
top: "50%",
|
|
||||||
left: "50%",
|
|
||||||
transform: "translate(-50%, -50%)",
|
|
||||||
zIndex: 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("Add your image")}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (choiceImgUrlQuestion && choiceImgUrlQuestion.trim().length > 0) {
|
|
||||||
return (
|
|
||||||
<img
|
|
||||||
src={choiceImgUrlQuestion}
|
|
||||||
style={{ width: "100%", height: "100%", objectFit: "cover" }}
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentQuestion.content.replText && currentQuestion.content.replText.trim().length > 0) {
|
|
||||||
return currentQuestion.content.replText;
|
|
||||||
}
|
|
||||||
|
|
||||||
return isMobile ? t("Select an answer option below") : t("Select an answer option on the left");
|
|
||||||
})()}
|
|
||||||
</ButtonBase>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,136 +0,0 @@
|
|||||||
import { useState, useEffect } from "react";
|
|
||||||
import { Select as MuiSelect, MenuItem, FormControl, Typography, useTheme } from "@mui/material";
|
|
||||||
|
|
||||||
import ArrowDown from "@icons/ArrowDownIcon";
|
|
||||||
|
|
||||||
import type { SelectChangeEvent, SxProps } from "@mui/material";
|
|
||||||
|
|
||||||
type SelectProps = {
|
|
||||||
items: string[];
|
|
||||||
activeItemIndex?: number;
|
|
||||||
empty?: boolean;
|
|
||||||
onChange?: (item: string, num: number) => void;
|
|
||||||
sx?: SxProps;
|
|
||||||
colorMain?: string;
|
|
||||||
colorPlaceholder?: string;
|
|
||||||
placeholder?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Select = ({
|
|
||||||
items,
|
|
||||||
activeItemIndex = 0,
|
|
||||||
empty,
|
|
||||||
onChange,
|
|
||||||
sx,
|
|
||||||
placeholder = "",
|
|
||||||
colorMain = "#7E2AEA",
|
|
||||||
colorPlaceholder = "#9A9AAF",
|
|
||||||
}: SelectProps) => {
|
|
||||||
const [activeItem, setActiveItem] = useState<number>(empty ? -1 : activeItemIndex);
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setActiveItem(activeItemIndex);
|
|
||||||
}, [activeItemIndex]);
|
|
||||||
|
|
||||||
const handleChange = (event: SelectChangeEvent) => {
|
|
||||||
const newItemIndex = Number(event.target.value);
|
|
||||||
|
|
||||||
if (newItemIndex === activeItem) {
|
|
||||||
setActiveItem(-1);
|
|
||||||
onChange?.("", -1);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setActiveItem(newItemIndex);
|
|
||||||
onChange?.(items[newItemIndex], newItemIndex);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormControl
|
|
||||||
fullWidth
|
|
||||||
size="small"
|
|
||||||
sx={{ width: "100%", height: "48px", ...sx }}
|
|
||||||
>
|
|
||||||
<MuiSelect
|
|
||||||
displayEmpty
|
|
||||||
renderValue={(value) =>
|
|
||||||
value ? items[Number(value)] : <Typography sx={{ color: colorPlaceholder }}>{placeholder}</Typography>
|
|
||||||
}
|
|
||||||
id="display-select"
|
|
||||||
variant="outlined"
|
|
||||||
value={activeItem === -1 ? "" : String(activeItem)}
|
|
||||||
onChange={handleChange}
|
|
||||||
sx={{
|
|
||||||
width: "100%",
|
|
||||||
height: "48px",
|
|
||||||
borderRadius: "8px",
|
|
||||||
"& .MuiOutlinedInput-notchedOutline": {
|
|
||||||
border: `1px solid ${colorMain} !important`,
|
|
||||||
borderRadius: "10px",
|
|
||||||
},
|
|
||||||
"& .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)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
MenuListProps: {
|
|
||||||
sx: {
|
|
||||||
py: 0,
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
gap: "8px",
|
|
||||||
maxWidth: "1380px",
|
|
||||||
"& .Mui-selected": {
|
|
||||||
backgroundColor: "#F2F3F7",
|
|
||||||
color: colorMain,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
inputProps={{
|
|
||||||
sx: {
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
display: "block",
|
|
||||||
px: "9px",
|
|
||||||
gap: "20px",
|
|
||||||
"& .MuiTypography-root": {
|
|
||||||
overflow: "hidden",
|
|
||||||
textOverflow: "ellipsis",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
IconComponent={(props) => <ArrowDown {...props} />}
|
|
||||||
>
|
|
||||||
{items.map((item, index) => (
|
|
||||||
<MenuItem
|
|
||||||
key={item + index}
|
|
||||||
value={index}
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: "20px",
|
|
||||||
padding: "10px",
|
|
||||||
borderRadius: "5px",
|
|
||||||
color: colorPlaceholder,
|
|
||||||
whiteSpace: "normal",
|
|
||||||
wordBreak: "break-word",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{item}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</MuiSelect>
|
|
||||||
</FormControl>
|
|
||||||
);
|
|
||||||
};
|
|
@ -27,7 +27,6 @@ export interface GetQuizDataResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function parseQuizData(quizDataResponse: GetQuizDataResponse): Omit<QuizSettings, "recentlyCompleted"> {
|
export function parseQuizData(quizDataResponse: GetQuizDataResponse): Omit<QuizSettings, "recentlyCompleted"> {
|
||||||
console.log(quizDataResponse);
|
|
||||||
const readyData = {
|
const readyData = {
|
||||||
cnt: quizDataResponse.cnt,
|
cnt: quizDataResponse.cnt,
|
||||||
show_badge: quizDataResponse.show_badge,
|
show_badge: quizDataResponse.show_badge,
|
||||||
@ -66,7 +65,6 @@ export function parseQuizData(quizDataResponse: GetQuizDataResponse): Omit<QuizS
|
|||||||
readyData.questions = items;
|
readyData.questions = items;
|
||||||
|
|
||||||
if (quizDataResponse?.settings !== undefined) {
|
if (quizDataResponse?.settings !== undefined) {
|
||||||
console.log("попытка парсануть сеттингс", quizDataResponse.settings);
|
|
||||||
readyData.settings = {
|
readyData.settings = {
|
||||||
fp: quizDataResponse.settings.fp,
|
fp: quizDataResponse.settings.fp,
|
||||||
rep: quizDataResponse.settings.rep,
|
rep: quizDataResponse.settings.rep,
|
||||||
|
@ -98,21 +98,9 @@ export interface QuizQuestionBase {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AnyTypedQuizQuestion =
|
export type AnyTypedQuizQuestion = QuizQuestionText | QuizQuestionResult;
|
||||||
| QuizQuestionVariant
|
|
||||||
| QuizQuestionImages
|
|
||||||
| QuizQuestionVarImg
|
|
||||||
| QuizQuestionEmoji
|
|
||||||
| QuizQuestionText
|
|
||||||
| QuizQuestionSelect
|
|
||||||
| QuizQuestionDate
|
|
||||||
| QuizQuestionNumber
|
|
||||||
| QuizQuestionFile
|
|
||||||
| QuizQuestionPage
|
|
||||||
| QuizQuestionRating
|
|
||||||
| QuizQuestionResult;
|
|
||||||
|
|
||||||
export type RealTypedQuizQuestion = Exclude<AnyTypedQuizQuestion, QuizQuestionResult>;
|
export type RealTypedQuizQuestion = Exclude<QuizQuestionText, QuizQuestionResult>;
|
||||||
|
|
||||||
type FilterQuestionsWithVariants<T> = T extends {
|
type FilterQuestionsWithVariants<T> = T extends {
|
||||||
content: { variants: QuestionVariant[] };
|
content: { variants: QuestionVariant[] };
|
||||||
|
@ -25,22 +25,14 @@ export const useQuizStore = create<QuizStore>(() => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
export const setQuizData = (data: QuizSettings) => {
|
export const setQuizData = (data: QuizSettings) => {
|
||||||
console.log("setQuizData called with:");
|
|
||||||
console.log("data:", data);
|
|
||||||
console.log("data.settings:", data.settings);
|
|
||||||
console.log("data.questions:", data.questions);
|
|
||||||
|
|
||||||
const currentState = useQuizStore.getState();
|
const currentState = useQuizStore.getState();
|
||||||
console.log("Current state before update:", currentState);
|
|
||||||
|
|
||||||
useQuizStore.setState((state: QuizStore) => {
|
useQuizStore.setState((state: QuizStore) => {
|
||||||
const newState = { ...state, ...data };
|
const newState = { ...state, ...data };
|
||||||
console.log("New state after update:", newState);
|
|
||||||
return newState;
|
return newState;
|
||||||
});
|
});
|
||||||
|
|
||||||
const updatedState = useQuizStore.getState();
|
const updatedState = useQuizStore.getState();
|
||||||
console.log("State after setState:", updatedState);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addQuestions = (newQuestions: AnyTypedQuizQuestion[]) =>
|
export const addQuestions = (newQuestions: AnyTypedQuizQuestion[]) =>
|
||||||
|
@ -13,6 +13,5 @@ const isProduction = !(
|
|||||||
|
|
||||||
//туризм больше не в исключениях
|
//туризм больше не в исключениях
|
||||||
if (!isProduction) domain = "https://s.hbpn.link";
|
if (!isProduction) domain = "https://s.hbpn.link";
|
||||||
domain = "https://hbpn.link";
|
|
||||||
|
|
||||||
export { domain, isProduction };
|
export { domain, isProduction };
|
||||||
|
@ -14,10 +14,7 @@ export function useAIQuiz() {
|
|||||||
//Получаем инфо о квизе и список вопросов.
|
//Получаем инфо о квизе и список вопросов.
|
||||||
const { settings, questions, quizId, cnt, quizStep } = useQuizStore();
|
const { settings, questions, quizId, cnt, quizStep } = useQuizStore();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {}, [questions]);
|
||||||
console.log("useQuestionFlowControl useEffect");
|
|
||||||
console.log(questions);
|
|
||||||
}, [questions]);
|
|
||||||
|
|
||||||
//Список ответов на вопрос. Мы записываем ответы локально, параллельно отправляя на бек информацию о ответах
|
//Список ответов на вопрос. Мы записываем ответы локально, параллельно отправляя на бек информацию о ответах
|
||||||
const answers = useQuizViewStore((state) => state.answers);
|
const answers = useQuizViewStore((state) => state.answers);
|
||||||
@ -29,9 +26,6 @@ export function useAIQuiz() {
|
|||||||
const yandexMetrics = useYandexMetricsGoals(settings.cfg.yandexMetricsNumber);
|
const yandexMetrics = useYandexMetricsGoals(settings.cfg.yandexMetricsNumber);
|
||||||
|
|
||||||
const currentQuestion = useMemo(() => {
|
const currentQuestion = useMemo(() => {
|
||||||
console.log("выбор currentQuestion");
|
|
||||||
console.log("quizStep ", quizStep);
|
|
||||||
console.log("questions[quizStep] ", questions[quizStep]);
|
|
||||||
const calcQuestion = questions[quizStep];
|
const calcQuestion = questions[quizStep];
|
||||||
if (calcQuestion) {
|
if (calcQuestion) {
|
||||||
vkMetrics.questionPassed(calcQuestion.id);
|
vkMetrics.questionPassed(calcQuestion.id);
|
||||||
@ -44,8 +38,6 @@ export function useAIQuiz() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentQuestion.type === "result") showResult();
|
if (currentQuestion.type === "result") showResult();
|
||||||
if (currentQuestion) changeNextLoading(false);
|
if (currentQuestion) changeNextLoading(false);
|
||||||
console.log("questions");
|
|
||||||
console.log(questions);
|
|
||||||
}, [currentQuestion, questions]);
|
}, [currentQuestion, questions]);
|
||||||
|
|
||||||
//Показать визуалом юзеру результат
|
//Показать визуалом юзеру результат
|
||||||
|
@ -14,12 +14,6 @@ export function useBranchingQuiz() {
|
|||||||
//Получаем инфо о квизе и список вопросов.
|
//Получаем инфо о квизе и список вопросов.
|
||||||
const { settings, questions, quizId, cnt } = useQuizStore();
|
const { settings, questions, quizId, cnt } = useQuizStore();
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log("useQuestionFlowControl useEffect");
|
|
||||||
console.log(questions);
|
|
||||||
}, [questions]);
|
|
||||||
console.log(questions);
|
|
||||||
|
|
||||||
//Когда квиз линейный, не ветвящийся, мы идём по вопросам по их порядковому номеру. Это их page.
|
//Когда квиз линейный, не ветвящийся, мы идём по вопросам по их порядковому номеру. Это их page.
|
||||||
//За корректность page отвечает конструктор квизов. Интересный факт, если в конструкторе удалить из середины вопрос, то случится куча запросов изменения вопросов с изменением этого page
|
//За корректность page отвечает конструктор квизов. Интересный факт, если в конструкторе удалить из середины вопрос, то случится куча запросов изменения вопросов с изменением этого page
|
||||||
const sortedQuestions = useMemo(() => {
|
const sortedQuestions = useMemo(() => {
|
||||||
@ -236,10 +230,6 @@ export function useBranchingQuiz() {
|
|||||||
if ("required" in currentQuestion.content && currentQuestion.content.required) {
|
if ("required" in currentQuestion.content && currentQuestion.content.required) {
|
||||||
return hasAnswer;
|
return hasAnswer;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(linearQuestionIndex);
|
|
||||||
console.log(questions.length);
|
|
||||||
console.log(cnt);
|
|
||||||
if (linearQuestionIndex !== null && questions.length < cnt) return true;
|
if (linearQuestionIndex !== null && questions.length < cnt) return true;
|
||||||
return Boolean(nextQuestion);
|
return Boolean(nextQuestion);
|
||||||
}, [answers, currentQuestion, nextQuestion]);
|
}, [answers, currentQuestion, nextQuestion]);
|
||||||
|
@ -14,12 +14,6 @@ export function useLinearQuiz() {
|
|||||||
//Получаем инфо о квизе и список вопросов.
|
//Получаем инфо о квизе и список вопросов.
|
||||||
const { settings, questions, quizId, cnt } = useQuizStore();
|
const { settings, questions, quizId, cnt } = useQuizStore();
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log("useQuestionFlowControl useEffect");
|
|
||||||
console.log(questions);
|
|
||||||
}, [questions]);
|
|
||||||
console.log(questions);
|
|
||||||
|
|
||||||
//Когда квиз линейный, не ветвящийся, мы идём по вопросам по их порядковому номеру. Это их page.
|
//Когда квиз линейный, не ветвящийся, мы идём по вопросам по их порядковому номеру. Это их page.
|
||||||
//За корректность page отвечает конструктор квизов. Интересный факт, если в конструкторе удалить из середины вопрос, то случится куча запросов изменения вопросов с изменением этого page
|
//За корректность page отвечает конструктор квизов. Интересный факт, если в конструкторе удалить из середины вопрос, то случится куча запросов изменения вопросов с изменением этого page
|
||||||
const sortedQuestions = useMemo(() => {
|
const sortedQuestions = useMemo(() => {
|
||||||
@ -236,10 +230,6 @@ export function useLinearQuiz() {
|
|||||||
if ("required" in currentQuestion.content && currentQuestion.content.required) {
|
if ("required" in currentQuestion.content && currentQuestion.content.required) {
|
||||||
return hasAnswer;
|
return hasAnswer;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(linearQuestionIndex);
|
|
||||||
console.log(questions.length);
|
|
||||||
console.log(cnt);
|
|
||||||
if (linearQuestionIndex !== null && questions.length < cnt) return true;
|
if (linearQuestionIndex !== null && questions.length < cnt) return true;
|
||||||
return Boolean(nextQuestion);
|
return Boolean(nextQuestion);
|
||||||
}, [answers, currentQuestion, nextQuestion]);
|
}, [answers, currentQuestion, nextQuestion]);
|
||||||
|
@ -2,7 +2,6 @@ import { sendAnswer } from "@/api/quizRelase";
|
|||||||
import { RealTypedQuizQuestion } from "@/model/questionTypes/shared";
|
import { RealTypedQuizQuestion } from "@/model/questionTypes/shared";
|
||||||
import { OwnVariant, QuestionAnswer, createQuizViewStore } from "@/stores/quizView";
|
import { OwnVariant, QuestionAnswer, createQuizViewStore } from "@/stores/quizView";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { notReachable } from "./notReachable";
|
|
||||||
|
|
||||||
export async function sendQuestionAnswer(
|
export async function sendQuestionAnswer(
|
||||||
quizId: string,
|
quizId: string,
|
||||||
@ -17,202 +16,8 @@ export async function sendQuestionAnswer(
|
|||||||
qid: quizId,
|
qid: quizId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
switch (question.type) {
|
|
||||||
case "date": {
|
|
||||||
let answer = "";
|
|
||||||
|
|
||||||
if (question.content.isRange) {
|
if (question.type === "text") {
|
||||||
if (!Array.isArray(questionAnswer.answer)) throw new Error("Cannot send answer in range question");
|
|
||||||
|
|
||||||
let from = Number(questionAnswer.answer[0]);
|
|
||||||
let to = Number(questionAnswer.answer[1]);
|
|
||||||
|
|
||||||
if (
|
|
||||||
from !== 0 &&
|
|
||||||
to !== 0 &&
|
|
||||||
from !== Math.min(Number(questionAnswer.answer[0]), Number(questionAnswer.answer[1]))
|
|
||||||
) {
|
|
||||||
from = Math.min(Number(questionAnswer.answer[0]), Number(questionAnswer.answer[1]));
|
|
||||||
to = Math.max(Number(questionAnswer.answer[0]), Number(questionAnswer.answer[1]));
|
|
||||||
}
|
|
||||||
|
|
||||||
answer = `${!from ? "_" : moment(from).format("YYYY.MM.DD")} - ${!to ? "_" : moment(to).format("YYYY.MM.DD")}`;
|
|
||||||
} else {
|
|
||||||
if (!moment.isMoment(questionAnswer.answer)) throw new Error("Cannot send answer in date question");
|
|
||||||
|
|
||||||
answer = moment(questionAnswer.answer).format("YYYY.MM.DD");
|
|
||||||
}
|
|
||||||
return sendAnswer({
|
|
||||||
questionId: question.id,
|
|
||||||
body: answer,
|
|
||||||
qid: quizId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
case "emoji": {
|
|
||||||
if (question.content.multi) {
|
|
||||||
const answer = questionAnswer.answer as string[];
|
|
||||||
let answerString = ``;
|
|
||||||
|
|
||||||
const selectedVariants = question.content.variants.filter((v) => answer.includes(v.id));
|
|
||||||
|
|
||||||
selectedVariants.forEach((variant) => {
|
|
||||||
const ownVariantData = ownVariants.find((v) => v.id === variant.id)?.variant;
|
|
||||||
const customEmoji = ownVariantData?.extendedText || "";
|
|
||||||
const emojiToSend = customEmoji || variant.extendedText;
|
|
||||||
const textToSend = variant.isOwn ? ownVariantData?.answer || "" : variant.answer;
|
|
||||||
answerString += `\`${emojiToSend} ${textToSend}\`,`;
|
|
||||||
});
|
|
||||||
|
|
||||||
answerString = answerString.slice(0, -1);
|
|
||||||
|
|
||||||
return sendAnswer({
|
|
||||||
questionId: question.id,
|
|
||||||
body: answerString,
|
|
||||||
qid: quizId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback for old string format for single choice
|
|
||||||
const answer = questionAnswer.answer as string;
|
|
||||||
const variant = question.content.variants.find((v) => v.id === answer);
|
|
||||||
if (!variant) {
|
|
||||||
// This can happen if the answer is not set, so we don't throw an error, just send empty
|
|
||||||
return sendAnswer({
|
|
||||||
questionId: question.id,
|
|
||||||
body: "",
|
|
||||||
qid: quizId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const ownVariantData = ownVariants.find((v) => v.id === variant.id)?.variant;
|
|
||||||
const customEmoji = ownVariantData?.extendedText || "";
|
|
||||||
const emojiToSend = customEmoji || variant.extendedText;
|
|
||||||
const textToSend = variant.isOwn ? ownVariantData?.answer || "" : variant.answer;
|
|
||||||
const body = `${emojiToSend} ${textToSend}`.trim();
|
|
||||||
|
|
||||||
return sendAnswer({
|
|
||||||
questionId: question.id,
|
|
||||||
body: body,
|
|
||||||
qid: quizId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
case "file": {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case "images": {
|
|
||||||
if (question.content.multi) {
|
|
||||||
const answer = questionAnswer.answer;
|
|
||||||
const ownAnswer = Array.isArray(answer)
|
|
||||||
? ownVariants[ownVariants.findIndex((variant) => answer.some((a: string) => a === variant.id))]?.variant
|
|
||||||
?.answer || ""
|
|
||||||
: ownVariants[ownVariants.findIndex((variant) => variant.id === questionAnswer.answer)]?.variant?.answer ||
|
|
||||||
"";
|
|
||||||
|
|
||||||
if (moment.isMoment(answer)) throw new Error("Answer is Moment in Variant question");
|
|
||||||
|
|
||||||
//Оставляем только выбранные варианты
|
|
||||||
const selectedVariants = question.content.variants.filter((v) => answer.includes(v.id));
|
|
||||||
|
|
||||||
let answerString = ``;
|
|
||||||
selectedVariants.forEach((e) => {
|
|
||||||
if (!e.isOwn || (e.isOwn && question.content.own)) {
|
|
||||||
let imageValue = e.extendedText;
|
|
||||||
if (e.isOwn) {
|
|
||||||
// Берем fileId из ownVariants для own вариантов
|
|
||||||
const ownVariantData = ownVariants.find((v) => v.id === e.id)?.variant;
|
|
||||||
if (ownVariantData?.originalImageUrl) {
|
|
||||||
// Конструируем полный URL для own вариантов
|
|
||||||
const baseUrl =
|
|
||||||
"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/55c25eb9-4533-4d51-9da5-54e63e8aeace/";
|
|
||||||
// Убираем расширение файла из fileId
|
|
||||||
const fileIdWithoutExtension = ownVariantData.originalImageUrl.replace(
|
|
||||||
/\.(jpg|jpeg|png|gif|webp)$/i,
|
|
||||||
""
|
|
||||||
);
|
|
||||||
imageValue = baseUrl + fileIdWithoutExtension;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = {
|
|
||||||
Image: imageValue,
|
|
||||||
Description: e.isOwn ? ownAnswer : e.answer,
|
|
||||||
};
|
|
||||||
answerString += `\`${JSON.stringify(body)}\`,`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
answerString = answerString.slice(0, -1);
|
|
||||||
|
|
||||||
return sendAnswer({
|
|
||||||
questionId: question.id,
|
|
||||||
body: answerString,
|
|
||||||
qid: quizId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const variant = question.content.variants.find((v) => v.id === questionAnswer.answer);
|
|
||||||
|
|
||||||
if (!variant) throw new Error(`Cannot find variant with id ${questionAnswer.answer} in question ${question.id}`);
|
|
||||||
|
|
||||||
let imageValue = variant.extendedText;
|
|
||||||
if (variant.isOwn) {
|
|
||||||
// Берем fileId из ownVariants для own вариантов
|
|
||||||
const ownVariantData = ownVariants.find((v) => v.id === variant.id)?.variant;
|
|
||||||
if (ownVariantData?.originalImageUrl) {
|
|
||||||
// Конструируем полный URL для own вариантов
|
|
||||||
const baseUrl =
|
|
||||||
"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/55c25eb9-4533-4d51-9da5-54e63e8aeace/";
|
|
||||||
// Убираем расширение файла из fileId
|
|
||||||
const fileIdWithoutExtension = ownVariantData.originalImageUrl.replace(/\.(jpg|jpeg|png|gif|webp)$/i, "");
|
|
||||||
imageValue = baseUrl + fileIdWithoutExtension;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = {
|
|
||||||
Image: imageValue,
|
|
||||||
Description: variant.answer,
|
|
||||||
};
|
|
||||||
if (!body) throw new Error(`Body of answer in question ${question.id} is undefined`);
|
|
||||||
|
|
||||||
return sendAnswer({
|
|
||||||
questionId: question.id,
|
|
||||||
body: `\`${JSON.stringify(body)}\``,
|
|
||||||
qid: quizId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
case "number": {
|
|
||||||
if (typeof questionAnswer.answer !== "string") throw new Error("Cannot send answer in select question");
|
|
||||||
|
|
||||||
return sendAnswer({
|
|
||||||
questionId: question.id,
|
|
||||||
body: questionAnswer.answer,
|
|
||||||
qid: quizId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
case "page": {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case "rating": {
|
|
||||||
if (typeof questionAnswer.answer !== "string") throw new Error("Cannot send answer in select question");
|
|
||||||
|
|
||||||
return sendAnswer({
|
|
||||||
questionId: question.id,
|
|
||||||
body: String(questionAnswer.answer) + " из " + question.content.steps,
|
|
||||||
qid: quizId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
case "select": {
|
|
||||||
if (typeof questionAnswer.answer !== "string") throw new Error("Cannot send answer in select question");
|
|
||||||
|
|
||||||
const variant = question.content.variants[Number(questionAnswer.answer)];
|
|
||||||
if (!variant) throw new Error(`Cannot find variant with id ${questionAnswer.answer} in question ${question.id}`);
|
|
||||||
|
|
||||||
return sendAnswer({
|
|
||||||
questionId: question.id,
|
|
||||||
body: variant.answer,
|
|
||||||
qid: quizId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
case "text": {
|
|
||||||
if (moment.isMoment(questionAnswer.answer)) throw new Error("Cannot send Moment in text question");
|
if (moment.isMoment(questionAnswer.answer)) throw new Error("Cannot send Moment in text question");
|
||||||
|
|
||||||
return sendAnswer({
|
return sendAnswer({
|
||||||
@ -220,81 +25,5 @@ export async function sendQuestionAnswer(
|
|||||||
body: questionAnswer.answer,
|
body: questionAnswer.answer,
|
||||||
qid: quizId,
|
qid: quizId,
|
||||||
});
|
});
|
||||||
}
|
} else throw new Error("Inappropriate question type");
|
||||||
case "variant": {
|
|
||||||
if (question.content.multi) {
|
|
||||||
const answer = questionAnswer.answer;
|
|
||||||
if (moment.isMoment(answer)) throw new Error("Answer is Moment in Variant question");
|
|
||||||
|
|
||||||
const ownAnswer = Array.isArray(answer)
|
|
||||||
? ownVariants[ownVariants.findIndex((variant) => answer.some((a: string) => a === variant.id))]?.variant
|
|
||||||
?.answer || ""
|
|
||||||
: ownVariants[ownVariants.findIndex((variant) => variant.id === questionAnswer.answer)]?.variant?.answer ||
|
|
||||||
"";
|
|
||||||
|
|
||||||
//Оставляем только выбранные варианты
|
|
||||||
const selectedVariants = question.content.variants.filter((v) => answer.includes(v.id));
|
|
||||||
|
|
||||||
let answerString = ``;
|
|
||||||
selectedVariants.forEach((e) => {
|
|
||||||
if (!e.isOwn) answerString += `\`${e.answer}\`,`;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (question.content.own && selectedVariants.some((v) => v.isOwn)) {
|
|
||||||
answerString += `\`${ownAnswer}\`,`;
|
|
||||||
}
|
|
||||||
answerString = answerString.slice(0, -1);
|
|
||||||
|
|
||||||
return sendAnswer({
|
|
||||||
questionId: question.id,
|
|
||||||
body: answerString,
|
|
||||||
qid: quizId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const variant = question.content.variants.find((v) => v.id === questionAnswer.answer);
|
|
||||||
if (!variant) throw new Error(`Cannot find variant with id ${questionAnswer.answer} in question ${question.id}`);
|
|
||||||
|
|
||||||
return sendAnswer({
|
|
||||||
questionId: question.id,
|
|
||||||
body: variant.answer,
|
|
||||||
qid: quizId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
case "varimg": {
|
|
||||||
const variant = question.content.variants.find((v) => v.id === questionAnswer.answer);
|
|
||||||
const ownAnswer =
|
|
||||||
ownVariants[ownVariants.findIndex((variant) => variant.id === questionAnswer.answer)]?.variant?.answer || "";
|
|
||||||
|
|
||||||
if (!variant) throw new Error(`Cannot find variant with id ${questionAnswer.answer} in question ${question.id}`);
|
|
||||||
|
|
||||||
let imageValue = variant.extendedText;
|
|
||||||
if (variant.isOwn) {
|
|
||||||
// Берем fileId из ownVariants для own вариантов
|
|
||||||
const ownVariantData = ownVariants.find((v) => v.id === variant.id)?.variant;
|
|
||||||
if (ownVariantData?.originalImageUrl) {
|
|
||||||
// Конструируем полный URL для own вариантов
|
|
||||||
const baseUrl =
|
|
||||||
"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/55c25eb9-4533-4d51-9da5-54e63e8aeace/";
|
|
||||||
// Убираем расширение файла из fileId
|
|
||||||
const fileIdWithoutExtension = ownVariantData.originalImageUrl.replace(/\.(jpg|jpeg|png|gif|webp)$/i, "");
|
|
||||||
imageValue = baseUrl + fileIdWithoutExtension;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = {
|
|
||||||
Image: imageValue,
|
|
||||||
Description: variant.isOwn ? ownAnswer : variant.answer,
|
|
||||||
};
|
|
||||||
if (!body) throw new Error(`Body of answer in question ${question.id} is undefined`);
|
|
||||||
|
|
||||||
return sendAnswer({
|
|
||||||
questionId: question.id,
|
|
||||||
body: `\`${JSON.stringify(body)}\``,
|
|
||||||
qid: quizId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
notReachable(question);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -64,9 +64,7 @@ i18n
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 3. Логирование всех событий
|
// 3. Логирование всех событий
|
||||||
i18n.on("languageChanged", (lng) => {
|
i18n.on("languageChanged", (lng) => {});
|
||||||
console.log("Язык изменён на:", lng);
|
|
||||||
});
|
|
||||||
|
|
||||||
i18n.on("failedLoading", (lng, ns, msg) => {
|
i18n.on("failedLoading", (lng, ns, msg) => {
|
||||||
console.error(`Ошибка загрузки ${lng}.json:`, msg);
|
console.error(`Ошибка загрузки ${lng}.json:`, msg);
|
||||||
|
@ -197,9 +197,7 @@ const r = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 3. Конфигурация i18n без Backend
|
// 3. Конфигурация i18n без Backend
|
||||||
i18n
|
i18n.use(initReactI18next).init({
|
||||||
.use(initReactI18next)
|
|
||||||
.init({
|
|
||||||
resources: r, // Используем встроенные переводы
|
resources: r, // Используем встроенные переводы
|
||||||
lng: getLanguageFromURL(),
|
lng: getLanguageFromURL(),
|
||||||
fallbackLng: "ru",
|
fallbackLng: "ru",
|
||||||
@ -228,18 +226,6 @@ i18n
|
|||||||
stack: new Error().stack,
|
stack: new Error().stack,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
console.log("i18n initialized. Current language:", i18n.language);
|
|
||||||
console.log("Available languages:", i18n.languages);
|
|
||||||
console.log("Available keys for ru:", Object.keys(r.ru));
|
|
||||||
console.log("Available keys for en:", Object.keys(r.en));
|
|
||||||
console.log("Available keys for uz:", Object.keys(r.uz));
|
|
||||||
});
|
|
||||||
|
|
||||||
// 4. Логирование событий
|
|
||||||
i18n.on("languageChanged", (lng) => {
|
|
||||||
console.log("Language changed to:", lng);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default i18n;
|
export default i18n;
|
||||||
|
47250
widget_en.js
47250
widget_en.js
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user