Merge branch 'dev' into style-previw

This commit is contained in:
Nastya 2023-12-12 23:38:46 +03:00
commit a6bd21d228
29 changed files with 1128 additions and 1093 deletions

@ -1,72 +1,86 @@
import React from 'react'; import React from "react";
import Box from '@mui/material/Box'; import Box from "@mui/material/Box";
import Button from '@mui/material/Button'; import Button from "@mui/material/Button";
import SectionStyled from './SectionStyled'; import SectionStyled from "./SectionStyled";
import NavMenuItem from "@ui_kit/Header/NavMenuItem"; import NavMenuItem from "@ui_kit/Header/NavMenuItem";
import QuizLogo from "./images/icons/QuizLogo"; import QuizLogo from "./images/icons/QuizLogo";
import { useMediaQuery, useTheme } from "@mui/material"; import { useMediaQuery, useTheme } from "@mui/material";
import { setIsContactFormOpen } from "../../stores/contactForm"; import { setIsContactFormOpen } from "../../stores/contactForm";
import { useUserStore } from "@root/user";
import { useNavigate } from "react-router-dom";
const buttonMenu = ['Меню 1', 'Меню 2', 'Меню 3', 'Меню 4', 'Меню 5', 'Меню 1', 'Меню 2'] const buttonMenu = ["Меню 1", "Меню 2", "Меню 3", "Меню 4", "Меню 5", "Меню 1", "Меню 2"];
export default function Component() { export default function Component() {
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600)); const isMobile = useMediaQuery(theme.breakpoints.down(600));
const isTablet = useMediaQuery(theme.breakpoints.down(1000)) const isTablet = useMediaQuery(theme.breakpoints.down(1000));
const [select, setSelect] = React.useState(0) const [select, setSelect] = React.useState(0);
return ( const userId = useUserStore((state) => state.userId);
<SectionStyled tag={'header'} bg={'#F2F3F7'} mwidth={'1160px'} const navigate = useNavigate();
sxsect={{
minHeight: '80px',
borderBottom: '1px solid #E3E3E3',
position: "relative",
padding: isMobile ? "0 16px" : isTablet ? "0 40px" : 0,
zIndex: 3
}}
sxcont={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: 0
}}>
<QuizLogo width={isMobile ? 100 : 150} />
{/*<Box*/}
{/* sx={{*/}
{/* maxWidth: '595px',*/}
{/* width: '100%',*/}
{/* display: 'flex',*/}
{/* justifyContent: 'space-between',*/}
{/* flexWrap: 'wrap',*/}
{/* marginRight: "138px",*/}
{/* }}*/} const onClick = () => (userId ? navigate("/list") : setIsContactFormOpen(true));
{/*>*/}
{/* {buttonMenu.map( (element, index) => (*/} return (
{/* <NavMenuItem*/} <SectionStyled
{/* text={element}*/} tag={"header"}
{/* // component={Link}*/} bg={"#F2F3F7"}
{/* // to={url}*/} mwidth={"1160px"}
{/* key={index}*/} sxsect={{
{/* onClick={() => {*/} minHeight: "80px",
{/* setSelect(index);*/} borderBottom: "1px solid #E3E3E3",
{/* }}*/} position: "relative",
{/* isActive={select === index}*/} padding: isMobile ? "0 16px" : isTablet ? "0 40px" : 0,
{/* />*/} zIndex: 3,
{/* ))}*/} }}
{/*</Box>*/} sxcont={{
<Button variant="outlined" display: "flex",
onClick={() => setIsContactFormOpen(true)} justifyContent: "space-between",
sx={{ alignItems: "center",
color: 'black', padding: 0,
border: '1px solid black', }}
textTransform: 'none', >
fontWeight: '400', <QuizLogo width={isMobile ? 100 : 150} />
fontSize: '18px', {/*<Box*/}
lineHeight: '24px', {/* sx={{*/}
borderRadius: '8px', {/* maxWidth: '595px',*/}
padding: '8px 17px', {/* width: '100%',*/}
}} {/* display: 'flex',*/}
>Предрегистрация</Button> {/* justifyContent: 'space-between',*/}
</SectionStyled> {/* flexWrap: 'wrap',*/}
) {/* marginRight: "138px",*/}
}
{/* }}*/}
{/*>*/}
{/* {buttonMenu.map( (element, index) => (*/}
{/* <NavMenuItem*/}
{/* text={element}*/}
{/* // component={Link}*/}
{/* // to={url}*/}
{/* key={index}*/}
{/* onClick={() => {*/}
{/* setSelect(index);*/}
{/* }}*/}
{/* isActive={select === index}*/}
{/* />*/}
{/* ))}*/}
{/*</Box>*/}
<Button
variant="outlined"
onClick={onClick}
sx={{
color: "black",
border: "1px solid black",
textTransform: "none",
fontWeight: "400",
fontSize: "18px",
lineHeight: "24px",
borderRadius: "8px",
padding: "8px 17px",
}}
>
{userId ? "Войти" : " Регистрация / Войти"}
</Button>
</SectionStyled>
);
}

@ -1,75 +1,82 @@
import React from 'react'; import React from "react";
import Box from '@mui/material/Box'; import Box from "@mui/material/Box";
import Button from '@mui/material/Button'; import Button from "@mui/material/Button";
import {Typography, useMediaQuery, useTheme} from "@mui/material"; import { Typography, useMediaQuery, useTheme } from "@mui/material";
import abstraction from '../../assets/Quiz-main.png' import abstraction from "../../assets/Quiz-main.png";
import SectionStyled from './SectionStyled'; import SectionStyled from "./SectionStyled";
import { Link, redirect } from 'react-router-dom'; import { Link, redirect, useNavigate } from "react-router-dom";
import {setIsContactFormOpen} from "@root/contactForm"; import { setIsContactFormOpen } from "@root/contactForm";
import { useUserStore } from "@root/user";
export default function Component() { export default function Component() {
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600)); const isMobile = useMediaQuery(theme.breakpoints.down(600));
const isTablet = useMediaQuery(theme.breakpoints.down(1000)) const isTablet = useMediaQuery(theme.breakpoints.down(1000));
return( const userId = useUserStore((state) => state.userId);
<SectionStyled tag={'section'} bg={'#f2f3f7'} mwidth={'1160px'} const navigate = useNavigate();
sxsect={{
height: isMobile ? '702px' : (isTablet ? '986px' : '660px'),
}}
sxcont={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: isMobile ? "300px 16px 0 16px" : (isTablet ? "494px 40px 0 40px" : 0),
marginBottom: isMobile ? '55px' : (isTablet ? 0 : "55px"),
}}>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
gap: '30px',
height: '100%',
justifyContent: 'space-between',
alignItems: "flex-start",
position: 'relative',
}}
>
<Typography variant='h2'>
Pena Quiz
</Typography>
<Box
sx={{
maxWidth: isTablet ? '715px' : '420px',
minHeight: '64px',
}}>
<Typography variant='body1'>
Помогаем посетителю оставить заявку. <br style={{display: isTablet ? 'flex' : "none"}}/> Готовые шаблоны квизов с легкой установкой на любой сайт и социальные сети
</Typography>
</Box>
{/*<Link */}
{/* to="/list"*/}
{/* style={{textDecoration: "none"}}>*/}
<Button variant="contained"
onClick={() => setIsContactFormOpen(true)}
>
Попробуйте бесплатно
</Button>
{/*</Link>*/}
<Box const tryItForFreeonClick = () => (userId ? navigate("/list") : setIsContactFormOpen(true));
component={"img"}
src={abstraction} return (
sx={{ <SectionStyled
position: "absolute", tag={"section"}
bottom: isMobile ? undefined : (isTablet? "138px" : "-291px"), bg={"#f2f3f7"}
maxWidth: isMobile ? "403px" : "810px", mwidth={"1160px"}
width: isMobile ? "100%" : undefined, sxsect={{
left: isMobile ? "-20px" : (isTablet? "54px" : "401px"), height: isMobile ? "702px" : isTablet ? "986px" : "660px",
top: isMobile ? "-345px" : undefined }}
}} sxcont={{
/> display: "flex",
</Box> justifyContent: "space-between",
</SectionStyled> alignItems: "center",
) padding: isMobile ? "300px 16px 0 16px" : isTablet ? "494px 40px 0 40px" : 0,
} marginBottom: isMobile ? "55px" : isTablet ? 0 : "55px",
}}
>
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: "30px",
height: "100%",
justifyContent: "space-between",
alignItems: "flex-start",
position: "relative",
}}
>
<Typography variant="h2">Pena Quiz</Typography>
<Box
sx={{
maxWidth: isTablet ? "715px" : "420px",
minHeight: "64px",
}}
>
<Typography variant="body1">
Помогаем посетителю оставить заявку. <br style={{ display: isTablet ? "flex" : "none" }} /> Готовые шаблоны
квизов с легкой установкой на любой сайт и социальные сети
</Typography>
</Box>
{/*<Link */}
{/* to="/list"*/}
{/* style={{textDecoration: "none"}}>*/}
<Button variant="contained" onClick={tryItForFreeonClick}>
Попробуйте бесплатно
</Button>
{/*</Link>*/}
<Box
component={"img"}
src={abstraction}
sx={{
position: "absolute",
bottom: isMobile ? undefined : isTablet ? "138px" : "-291px",
maxWidth: isMobile ? "403px" : "810px",
width: isMobile ? "100%" : undefined,
left: isMobile ? "-20px" : isTablet ? "54px" : "401px",
top: isMobile ? "-345px" : undefined,
}}
/>
</Box>
</SectionStyled>
);
}

@ -216,6 +216,7 @@ function CsComponent({
} }
} }
const removeNode = ({ targetNodeContentId }: { targetNodeContentId: string }) => { const removeNode = ({ targetNodeContentId }: { targetNodeContentId: string }) => {
const deleteNodes = [] as string[] const deleteNodes = [] as string[]
const deleteEdges: any = [] const deleteEdges: any = []
@ -331,6 +332,7 @@ function CsComponent({
} }
useEffect(() => { useEffect(() => {
if (startCreate) { if (startCreate) {
addNode({ parentNodeContentId: startCreate }); addNode({ parentNodeContentId: startCreate });
@ -348,7 +350,6 @@ function CsComponent({
const readyLO = (e) => { const readyLO = (e) => {
console.log('REAdy')
if (e.cy.data('firstNode') === 'nonroot') { if (e.cy.data('firstNode') === 'nonroot') {
e.cy.data('firstNode', 'root') e.cy.data('firstNode', 'root')
e.cy.nodes().sort((a, b) => (a.data('root') ? 1 : -1)).layout(lyopts).run() e.cy.nodes().sort((a, b) => (a.data('root') ? 1 : -1)).layout(lyopts).run()
@ -433,7 +434,6 @@ function CsComponent({
task.task.forEach(n => { task.task.forEach(n => {
const width = n.data('subtreeWidth') const width = n.data('subtreeWidth')
console.log('ORORORORO', n.data(), yoffset, width, oldPos, task.parent.data('subtreeWidth'))
n.data('oldPos', { x: 250 * n.data('layer'), y: yoffset + width / 2 }) n.data('oldPos', { x: 250 * n.data('layer'), y: yoffset + width / 2 })
yoffset += width yoffset += width
queue.push({ task: n.cy().edges(`[source="${n.id()}"]`).targets(), parent: n }) queue.push({ task: n.cy().edges(`[source="${n.id()}"]`).targets(), parent: n })
@ -462,7 +462,6 @@ function CsComponent({
} }
useEffect(() => { useEffect(() => {
console.log('KEKEKE')
document.querySelector("#root")?.addEventListener("mouseup", cleardragQuestionContentId); document.querySelector("#root")?.addEventListener("mouseup", cleardragQuestionContentId);
const cy = cyRef.current; const cy = cyRef.current;
const eles = cy?.add(storeToNodes(questions.filter((question: AnyTypedQuizQuestion) => (question.type !== "result" && question.type !== null)))) const eles = cy?.add(storeToNodes(questions.filter((question: AnyTypedQuizQuestion) => (question.type !== "result" && question.type !== null))))
@ -510,7 +509,6 @@ function CsComponent({
if (!container) { if (!container) {
return; return;
} }
console.log('POP')
container.style.overflow = "hidden"; container.style.overflow = "hidden";
@ -540,7 +538,6 @@ function CsComponent({
const bb = n.boundingBox() const bb = n.boundingBox()
return bb.x2 > ext.x1 && bb.x1 < ext.x2 && bb.y2 > ext.y1 && bb.y1 < ext.y2 return bb.x2 > ext.x1 && bb.x1 < ext.x2 && bb.y2 > ext.y1 && bb.y1 < ext.y2
}) })
console.log('POPE')
nodesInView nodesInView
.toArray() .toArray()
@ -553,7 +550,6 @@ function CsComponent({
modifiers: [{ name: "flip", options: { boundary: node } }], modifiers: [{ name: "flip", options: { boundary: node } }],
}, },
content: ([item]) => { content: ([item]) => {
console.log('POPPER', item.id())
const itemId = item.id(); const itemId = item.id();
const itemElement = layoutsContainer.current?.querySelector( const itemElement = layoutsContainer.current?.querySelector(
`.popper-layout[data-id='${itemId}']` `.popper-layout[data-id='${itemId}']`
@ -583,7 +579,6 @@ function CsComponent({
modifiers: [{ name: "flip", options: { boundary: node } }], modifiers: [{ name: "flip", options: { boundary: node } }],
}, },
content: ([item]) => { content: ([item]) => {
console.log('POPPER+', item.id())
const itemId = item.id(); const itemId = item.id();
const itemElement = plusesContainer.current?.querySelector( const itemElement = plusesContainer.current?.querySelector(
`.popper-plus[data-id='${itemId}']` `.popper-plus[data-id='${itemId}']`
@ -612,7 +607,6 @@ function CsComponent({
modifiers: [{ name: "flip", options: { boundary: node } }], modifiers: [{ name: "flip", options: { boundary: node } }],
}, },
content: ([item]) => { content: ([item]) => {
console.log('POPPERx', item.id())
const itemId = item.id(); const itemId = item.id();
const itemElement = crossesContainer.current?.querySelector( const itemElement = crossesContainer.current?.querySelector(
`.popper-cross[data-id='${itemId}']` `.popper-cross[data-id='${itemId}']`
@ -636,18 +630,17 @@ function CsComponent({
return crossElement; return crossElement;
}, },
}); });
let gearsPopper = null
const gearsPopper = node.popper({ console.log('POPE', node.data())
if (node.data().root !== true) {
gearsPopper = node.popper({
popper: { popper: {
placement: "left", placement: "left",
modifiers: [{ name: "flip", options: { boundary: node } }], modifiers: [{ name: "flip", options: { boundary: node } }],
}, },
content: ([item]) => { content: ([item]) => {
console.log('POPPERg', item.id()) console.log('PEPO', item.id())
const itemId = item.id(); const itemId = item.id();
if (item.cy().edges(`[target="${itemId}"]`).sources().length === 0) {
return;
}
const itemElement = gearsContainer.current?.querySelector( const itemElement = gearsContainer.current?.querySelector(
`.popper-gear[data-id='${itemId}']` `.popper-gear[data-id='${itemId}']`
@ -668,12 +661,11 @@ function CsComponent({
return gearElement; return gearElement;
}, },
}); });
}
const update = async () => { const update = async () => {
console.log('POPPERi=u', item.id())
await plusesPopper.update(); await plusesPopper.update();
await crossesPopper.update(); await crossesPopper.update();
await gearsPopper.update(); await gearsPopper?.update();
await layoutsPopper.update(); await layoutsPopper.update();
}; };
@ -696,6 +688,18 @@ function CsComponent({
{ name: "offset", options: { offset: [0, -130 * zoom] } }, { name: "offset", options: { offset: [0, -130 * zoom] } },
], ],
}); });
plusesPopper.setOptions({
modifiers: [
{ name: "flip", options: { boundary: node } },
{ name: "offset", options: { offset: [0, 0 * zoom] } },
],
});
gearsPopper?.setOptions({
modifiers: [
{ name: "flip", options: { boundary: node } },
{ name: "offset", options: { offset: [0, 0] } },
],
});
layoutsContainer.current layoutsContainer.current
?.querySelectorAll("#popper-layouts > .popper-layout") ?.querySelectorAll("#popper-layouts > .popper-layout")
@ -725,7 +729,7 @@ function CsComponent({
element.style.borderRadius = `${6 * zoom}px`; element.style.borderRadius = `${6 * zoom}px`;
}); });
gearsContainer.current gearsContainer?.current
?.querySelectorAll("#popper-gears > .popper-gear") ?.querySelectorAll("#popper-gears > .popper-gear")
.forEach((item) => { .forEach((item) => {
const element = item as HTMLDivElement; const element = item as HTMLDivElement;
@ -735,13 +739,13 @@ function CsComponent({
}; };
//node?.on("position", update); //node?.on("position", update);
let pressed = false let pressed = false
let hide = false let hide = false
cy?.on('mousedown',() => {pressed = true}) cy?.on('mousedown', () => { pressed = true })
cy?.on('mouseup',() => { cy?.on('mouseup', () => {
pressed = false pressed = false
hide = false hide = false
const gc = gearsContainer.current const gc = gearsContainer.current
if (gc) gc.style.display = 'block' if (gc) gc.style.display = 'block'
@ -751,23 +755,23 @@ let pressed = false
if (pc) pc.style.display = 'block' if (pc) pc.style.display = 'block'
if (xc) xc.style.display = 'block' if (xc) xc.style.display = 'block'
if (lc) lc.style.display = 'block' if (lc) lc.style.display = 'block'
update() update()
}) })
cy?.on('mousemove',() => { cy?.on('mousemove', () => {
if (pressed && !hide) { if (pressed && !hide) {
hide = true hide = true
const gc = gearsContainer.current const gc = gearsContainer.current
if (gc) gc.style.display = 'none' if (gc) gc.style.display = 'none'
const pc = plusesContainer.current const pc = plusesContainer.current
const xc = crossesContainer.current const xc = crossesContainer.current
const lc = layoutsContainer.current const lc = layoutsContainer.current
if (pc) pc.style.display = 'none' if (pc) pc.style.display = 'none'
if (xc) xc.style.display = 'none' if (xc) xc.style.display = 'none'
if (lc) lc.style.display = 'block' if (lc) lc.style.display = 'block'
} }
}); });
cy?.on("zoom render", onZoom); cy?.on("zoom render", onZoom);
}); });
}; };
@ -785,7 +789,7 @@ let pressed = false
variant="text" variant="text"
onClick={() => { onClick={() => {
//код сюда cyRef.current?.fit()
}} }}
> >

@ -1,6 +1,6 @@
import { Box } from "@mui/material" import { Box } from "@mui/material"
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { updateDragQuestionContentId, updateQuestion } from "@root/questions/actions" import { deleteQuestion, updateDragQuestionContentId, updateQuestion } from "@root/questions/actions"
import { updateRootContentId } from "@root/quizes/actions" import { updateRootContentId } from "@root/quizes/actions"
import { useCurrentQuiz } from "@root/quizes/hooks" import { useCurrentQuiz } from "@root/quizes/hooks"
import { useQuestionsStore } from "@root/questions/store" import { useQuestionsStore } from "@root/questions/store"
@ -11,8 +11,9 @@ interface Props {
modalQuestionTargetContentId: string; modalQuestionTargetContentId: string;
} }
export const FirstNodeField = ({ setOpenedModalQuestions, modalQuestionTargetContentId }: Props) => { export const FirstNodeField = ({ setOpenedModalQuestions, modalQuestionTargetContentId }: Props) => {
const quiz = useCurrentQuiz(); const quiz = useCurrentQuiz();
const { dragQuestionContentId } = useQuestionsStore() const { dragQuestionContentId, questions } = useQuestionsStore()
const Container = useRef<HTMLDivElement | null>(null); const Container = useRef<HTMLDivElement | null>(null);
const modalOpen = () => setOpenedModalQuestions(true) const modalOpen = () => setOpenedModalQuestions(true)
@ -22,6 +23,11 @@ export const FirstNodeField = ({ setOpenedModalQuestions, modalQuestionTargetCon
if (dragQuestionContentId) { if (dragQuestionContentId) {
updateRootContentId(quiz?.id, dragQuestionContentId) updateRootContentId(quiz?.id, dragQuestionContentId)
updateQuestion(dragQuestionContentId, (question) => question.content.rule.parentId = "root") updateQuestion(dragQuestionContentId, (question) => question.content.rule.parentId = "root")
//если были результаты - удалить
questions.forEach((q) => {
if (q.type === 'result') deleteQuestion(q.id)
})
} }
} else { } else {
enqueueSnackbar("Нет информации о взятом опроснике") enqueueSnackbar("Нет информации о взятом опроснике")
@ -44,6 +50,10 @@ export const FirstNodeField = ({ setOpenedModalQuestions, modalQuestionTargetCon
if (modalQuestionTargetContentId) { if (modalQuestionTargetContentId) {
updateRootContentId(quiz?.id, modalQuestionTargetContentId) updateRootContentId(quiz?.id, modalQuestionTargetContentId)
updateQuestion(modalQuestionTargetContentId, (question) => question.content.rule.parentId = "root") updateQuestion(modalQuestionTargetContentId, (question) => question.content.rule.parentId = "root")
//если были результаты - удалить
questions.forEach((q) => {
if (q.type === 'result') deleteQuestion(q.id)
})
} }
} else { } else {
enqueueSnackbar("Нет информации о взятом опроснике") enqueueSnackbar("Нет информации о взятом опроснике")

@ -11,7 +11,7 @@ import {
useMediaQuery, useMediaQuery,
useTheme, useTheme,
} from "@mui/material"; } from "@mui/material";
import { copyQuestion, deleteQuestion, updateOpenBranchingPanel, updateDesireToOpenABranchingModal } from "@root/questions/actions"; import { copyQuestion, deleteQuestion, updateOpenBranchingPanel, updateDesireToOpenABranchingModal, deleteQuestionWithTimeout } from "@root/questions/actions";
import MiniButtonSetting from "@ui_kit/MiniButtonSetting"; import MiniButtonSetting from "@ui_kit/MiniButtonSetting";
import { CopyIcon } from "../../assets/icons/questionsPage/CopyIcon"; import { CopyIcon } from "../../assets/icons/questionsPage/CopyIcon";
import Branching from "../../assets/icons/questionsPage/branching"; import Branching from "../../assets/icons/questionsPage/branching";
@ -40,10 +40,10 @@ export default function ButtonsOptions({
const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isMobile = useMediaQuery(theme.breakpoints.down(790));
const isWrappMiniButtonSetting = useMediaQuery(theme.breakpoints.down(920)); const isWrappMiniButtonSetting = useMediaQuery(theme.breakpoints.down(920));
const quiz = useCurrentQuiz(); const quiz = useCurrentQuiz();
const { openBranchingPanel } = useQuestionsStore.getState() const { openBranchingPanel, questions } = useQuestionsStore.getState();
const openedModal = () => { const openedModal = () => {
updateOpenedModalSettingsId(question.id) updateOpenedModalSettingsId(question.id);
}; };
@ -84,8 +84,8 @@ export default function ButtonsOptions({
title: "Ветвление", title: "Ветвление",
value: "branching", value: "branching",
myFunc: (question) => { myFunc: (question) => {
updateOpenBranchingPanel(true) updateOpenBranchingPanel(true);
updateDesireToOpenABranchingModal(question.content.id) updateDesireToOpenABranchingModal(question.content.id);
} }
}, },
]; ];
@ -161,7 +161,7 @@ export default function ButtonsOptions({
<MiniButtonSetting <MiniButtonSetting
key={title} key={title}
onClick={() => { onClick={() => {
openedModal() openedModal();
// SSHC(value); // SSHC(value);
// myFunc(question); // myFunc(question);
}} }}
@ -269,76 +269,67 @@ export default function ButtonsOptions({
</IconButton> </IconButton>
<IconButton <IconButton
sx={{ borderRadius: "6px", padding: "2px" }} sx={{ borderRadius: "6px", padding: "2px" }}
onClick={() => { // TODO onClick={() => {
// const removedId = question.id; const deleteFn = () => {
// if (question.deleteTimeoutId) { if (question.type !== null) {
// clearTimeout(question.deleteTimeoutId); if (question.content.rule.parentId === "root") { //удалить из стора root и очистить rule всем вопросам
// } updateRootContentId(quiz.id, "");
clearRuleForAll();
// removeQuestion(quizId, totalIndex); questions.forEach(q => {
if (q.type === "result") {
// const newTimeoutId = window.setTimeout(() => { deleteQuestion(q.id);
// removeQuestionForce(quizId, removedId);
// }, 5000);
// updateQuestionsList<QuizQuestionBase>(quizId, totalIndex, {
// ...question,
// deleteTimeoutId: newTimeoutId,
// });
if (question.type !== null) {
if (question.content.rule.parentId === "root") { //удалить из стора root и очистить rule всем вопросам
updateRootContentId(quiz.id, "")
clearRuleForAll()
questions.forEach(q => {
if (q.type === "result") {
deleteQuestion(q.id);
}
});
deleteQuestion(question.id);
} else if (question.content.rule.parentId.length > 0) { //удалить из стора вопрос из дерева и очистить его потомков
const clearQuestions = [] as string[]
//записываем потомков , а их результаты удаляем
const getChildren = (parentQuestion: AnyTypedQuizQuestion) => {
questions.forEach((targetQuestion) => {
if (targetQuestion.content.rule.parentId === parentQuestion.content.id) {//если у вопроса совпал родитель с родителем => он потомок, в кучу его
if (targetQuestion.type === "result") {
deleteQuestion(targetQuestion.id);
} else {
if (!clearQuestions.includes(targetQuestion.content.id)) clearQuestions.push(targetQuestion.content.id)
getChildren(targetQuestion) //и ищем его потомков
}
} }
}) });
deleteQuestion(question.id);
} else if (question.content.rule.parentId.length > 0) { //удалить из стора вопрос из дерева и очистить его потомков
const clearQuestions = [] as string[];
//записываем потомков , а их результаты удаляем
const getChildren = (parentQuestion: AnyTypedQuizQuestion) => {
questions.forEach((targetQuestion) => {
if (targetQuestion.content.rule.parentId === parentQuestion.content.id) {//если у вопроса совпал родитель с родителем => он потомок, в кучу его
if (targetQuestion.type === "result") {
deleteQuestion(targetQuestion.id);
} else {
if (!clearQuestions.includes(targetQuestion.content.id)) clearQuestions.push(targetQuestion.content.id);
getChildren(targetQuestion); //и ищем его потомков
}
}
});
};
getChildren(question);
//чистим потомков от инфы ветвления
clearQuestions.forEach((id) => {
updateQuestion(id, question => {
question.content.rule.parentId = "";
question.content.rule.main = [];
question.content.rule.default = "";
});
});
//чистим rule родителя
const parentQuestion = getQuestionByContentId(question.content.rule.parentId);
const newRule = {};
newRule.main = parentQuestion.content.rule.main.filter((data) => data.next !== question.content.id); //удаляем условия перехода от родителя к этому вопросу
newRule.parentId = parentQuestion.content.rule.parentId;
newRule.default = parentQuestion.content.rule.parentId === question.content.id ? "" : parentQuestion.content.rule.parentId;
newRule.children = [...parentQuestion.content.rule.children].splice(parentQuestion.content.rule.children.indexOf(question.content.id), 1);
updateQuestion(question.content.rule.parentId, (PQ) => {
PQ.content.rule = newRule;
});
deleteQuestion(question.id);
} }
getChildren(question)
//чистим потомков от инфы ветвления
clearQuestions.forEach((id) => {
updateQuestion(id, question => {
question.content.rule.parentId = ""
question.content.rule.main = []
question.content.rule.default = ""
})
})
//чистим rule родителя deleteQuestion(question.id);
const parentQuestion = getQuestionByContentId(question.content.rule.parentId)
const newRule = {}
newRule.main = parentQuestion.content.rule.main.filter((data) => data.next !== question.content.id) //удаляем условия перехода от родителя к этому вопросу
newRule.parentId = parentQuestion.content.rule.parentId
newRule.default = parentQuestion.content.rule.parentId === question.content.id ? "" : parentQuestion.content.rule.parentId
newRule.children = [...parentQuestion.content.rule.children].splice(parentQuestion.content.rule.children.indexOf(question.content.id), 1);
updateQuestion(question.content.rule.parentId, (PQ) => { } else {
PQ.content.rule = newRule console.log("удаляю безтипогово");
}) deleteQuestion(question.id);
deleteQuestion(question.id)
} }
};
deleteQuestion(question.id) deleteQuestionWithTimeout(question.id, deleteFn);
}
}} }}
data-cy="delete-question" data-cy="delete-question"
> >

@ -10,7 +10,7 @@ import {
useMediaQuery, useMediaQuery,
useTheme, useTheme,
} from "@mui/material"; } from "@mui/material";
import { copyQuestion, deleteQuestion, updateQuestion, clearRuleForAll, getQuestionByContentId } from "@root/questions/actions"; import { copyQuestion, deleteQuestion, updateQuestion, clearRuleForAll, getQuestionByContentId, deleteQuestionWithTimeout } from "@root/questions/actions";
import MiniButtonSetting from "@ui_kit/MiniButtonSetting"; import MiniButtonSetting from "@ui_kit/MiniButtonSetting";
import { ReallyChangingModal } from "@ui_kit/Modal/ReallyChangingModal/ReallyChangingModal"; import { ReallyChangingModal } from "@ui_kit/Modal/ReallyChangingModal/ReallyChangingModal";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
@ -47,7 +47,7 @@ export default function ButtonsOptionsAndPict({
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isMobile = useMediaQuery(theme.breakpoints.down(790));
const isIconMobile = useMediaQuery(theme.breakpoints.down(1050)); const isIconMobile = useMediaQuery(theme.breakpoints.down(1050));
const { questions } = useQuestionsStore.getState() const { questions } = useQuestionsStore.getState();
const quiz = useCurrentQuiz(); const quiz = useCurrentQuiz();
useEffect(() => { useEffect(() => {
@ -191,8 +191,8 @@ export default function ButtonsOptionsAndPict({
onMouseEnter={() => setButtonHover("branching")} onMouseEnter={() => setButtonHover("branching")}
onMouseLeave={() => setButtonHover("")} onMouseLeave={() => setButtonHover("")}
onClick={() => { onClick={() => {
updateOpenBranchingPanel(true) updateOpenBranchingPanel(true);
updateDesireToOpenABranchingModal(question.content.id) updateDesireToOpenABranchingModal(question.content.id);
}} }}
sx={{ sx={{
height: "30px", height: "30px",
@ -307,76 +307,67 @@ export default function ButtonsOptionsAndPict({
</IconButton> </IconButton>
<IconButton <IconButton
sx={{ borderRadius: "6px", padding: "2px" }} sx={{ borderRadius: "6px", padding: "2px" }}
onClick={() => { // TODO onClick={() => {
// const removedId = question.id; const deleteFn = () => {
// if (question.deleteTimeoutId) { if (question.type !== null) {
// clearTimeout(question.deleteTimeoutId); if (question.content.rule.parentId === "root") { //удалить из стора root и очистить rule всем вопросам
// } updateRootContentId(quiz.id, "");
clearRuleForAll();
// removeQuestion(quizId, totalIndex); questions.forEach(q => {
if (q.type === "result") {
// const newTimeoutId = window.setTimeout(() => { deleteQuestion(q.id);
// removeQuestionForce(quizId, removedId);
// }, 5000);
// updateQuestionsList<QuizQuestionBase>(quizId, totalIndex, {
// ...question,
// deleteTimeoutId: newTimeoutId,
// });
if (question.type !== null) {
if (question.content.rule.parentId === "root") { //удалить из стора root и очистить rule всем вопросам
updateRootContentId(quiz.id, "")
clearRuleForAll()
questions.forEach(q => {
if (q.type === "result") {
deleteQuestion(q.id);
}
});
deleteQuestion(question.id);
} else if (question.content.rule.parentId.length > 0) { //удалить из стора вопрос из дерева и очистить его потомков
const clearQuestions = [] as string[]
//записываем потомков , а их результаты удаляем
const getChildren = (parentQuestion: AnyTypedQuizQuestion) => {
questions.forEach((targetQuestion) => {
if (targetQuestion.content.rule.parentId === parentQuestion.content.id) {//если у вопроса совпал родитель с родителем => он потомок, в кучу его
if (targetQuestion.type === "result") {
deleteQuestion(targetQuestion.id);
} else {
if (!clearQuestions.includes(targetQuestion.content.id)) clearQuestions.push(targetQuestion.content.id)
getChildren(targetQuestion) //и ищем его потомков
}
} }
}) });
deleteQuestion(question.id);
} else if (question.content.rule.parentId.length > 0) { //удалить из стора вопрос из дерева и очистить его потомков
const clearQuestions = [] as string[];
//записываем потомков , а их результаты удаляем
const getChildren = (parentQuestion: AnyTypedQuizQuestion) => {
questions.forEach((targetQuestion) => {
if (targetQuestion.content.rule.parentId === parentQuestion.content.id) {//если у вопроса совпал родитель с родителем => он потомок, в кучу его
if (targetQuestion.type === "result") {
deleteQuestion(targetQuestion.id);
} else {
if (!clearQuestions.includes(targetQuestion.content.id)) clearQuestions.push(targetQuestion.content.id);
getChildren(targetQuestion); //и ищем его потомков
}
}
});
};
getChildren(question);
//чистим потомков от инфы ветвления
clearQuestions.forEach((id) => {
updateQuestion(id, question => {
question.content.rule.parentId = "";
question.content.rule.main = [];
question.content.rule.default = "";
});
});
//чистим rule родителя
const parentQuestion = getQuestionByContentId(question.content.rule.parentId);
const newRule = {};
newRule.main = parentQuestion.content.rule.main.filter((data) => data.next !== question.content.id); //удаляем условия перехода от родителя к этому вопросу
newRule.parentId = parentQuestion.content.rule.parentId;
newRule.default = parentQuestion.content.rule.parentId === question.content.id ? "" : parentQuestion.content.rule.parentId;
newRule.children = [...parentQuestion.content.rule.children].splice(parentQuestion.content.rule.children.indexOf(question.content.id), 1);
updateQuestion(question.content.rule.parentId, (PQ) => {
PQ.content.rule = newRule;
});
deleteQuestion(question.id);
} }
getChildren(question)
//чистим потомков от инфы ветвления
clearQuestions.forEach((id) => {
updateQuestion(id, question => {
question.content.rule.parentId = ""
question.content.rule.main = []
question.content.rule.default = ""
})
})
//чистим rule родителя deleteQuestion(question.id);
const parentQuestion = getQuestionByContentId(question.content.rule.parentId)
const newRule = {}
newRule.main = parentQuestion.content.rule.main.filter((data) => data.next !== question.content.id) //удаляем условия перехода от родителя к этому вопросу
newRule.parentId = parentQuestion.content.rule.parentId
newRule.default = parentQuestion.content.rule.parentId === question.content.id ? "" : parentQuestion.content.rule.parentId
newRule.children = [...parentQuestion.content.rule.children].splice(parentQuestion.content.rule.children.indexOf(question.content.id), 1);
updateQuestion(question.content.rule.parentId, (PQ) => { } else {
PQ.content.rule = newRule console.log("удаляю безтипогово");
}) deleteQuestion(question.id);
deleteQuestion(question.id)
} }
};
deleteQuestion(question.id) deleteQuestionWithTimeout(question.id, deleteFn);
}
}} }}
data-cy="delete-question" data-cy="delete-question"
> >

@ -3,8 +3,8 @@ import { Box, ListItem, Typography, useTheme } from "@mui/material";
import { memo, useEffect } from "react"; import { memo, useEffect } from "react";
import { Draggable } from "react-beautiful-dnd"; import { Draggable } from "react-beautiful-dnd";
import QuestionsPageCard from "./QuestionPageCard"; import QuestionsPageCard from "./QuestionPageCard";
import { updateEditSomeQuestion } from "@root/questions/actions" import { cancelQuestionDeletion, updateEditSomeQuestion } from "@root/questions/actions";
import { useQuestionsStore } from "@root/questions/store" import { useQuestionsStore } from "@root/questions/store";
type Props = { type Props = {
@ -15,21 +15,21 @@ type Props = {
function DraggableListItem({ question, isDragging, index }: Props) { function DraggableListItem({ question, isDragging, index }: Props) {
const theme = useTheme(); const theme = useTheme();
const { editSomeQuestion } = useQuestionsStore() const { editSomeQuestion } = useQuestionsStore();
useEffect(() => { useEffect(() => {
if (editSomeQuestion !== null) { if (editSomeQuestion !== null) {
const setI = setInterval(() => { const setI = setInterval(() => {
let comp = document.getElementById(editSomeQuestion) let comp = document.getElementById(editSomeQuestion);
if(comp !== null) { if (comp !== null) {
clearInterval(setI) clearInterval(setI);
comp.scrollIntoView({behavior: 'instant'}) comp.scrollIntoView({ behavior: 'instant' });
updateEditSomeQuestion() updateEditSomeQuestion();
} }
}, 200) }, 200);
} }
}, [editSomeQuestion]) }, [editSomeQuestion]);
return ( return (
<Draggable draggableId={question.id.toString()} index={index}> <Draggable draggableId={question.id.toString()} index={index}>
@ -60,11 +60,8 @@ function DraggableListItem({ question, isDragging, index }: Props) {
Вопрос удалён. Вопрос удалён.
</Typography> </Typography>
<Typography <Typography
onClick={() => { // TODO onClick={() => {
// updateQuestionsList<QuizQuestionBase>(quizId, index, { cancelQuestionDeletion(question.id);
// ...questionData,
// deleted: false,
// });
}} }}
sx={{ sx={{
cursor: "pointer", cursor: "pointer",

@ -29,7 +29,7 @@ import {
useMediaQuery, useMediaQuery,
useTheme, useTheme,
} from "@mui/material"; } from "@mui/material";
import { copyQuestion, createUntypedQuestion, deleteQuestion, clearRuleForAll, toggleExpandQuestion, updateQuestion, updateUntypedQuestion, getQuestionByContentId } from "@root/questions/actions"; import { copyQuestion, createUntypedQuestion, deleteQuestion, clearRuleForAll, toggleExpandQuestion, updateQuestion, updateUntypedQuestion, getQuestionByContentId, deleteQuestionWithTimeout } from "@root/questions/actions";
import { updateRootContentId } from "@root/quizes/actions"; import { updateRootContentId } from "@root/quizes/actions";
import { useRef, useState } from "react"; import { useRef, useState } from "react";
import type { DraggableProvidedDragHandleProps } from "react-beautiful-dnd"; import type { DraggableProvidedDragHandleProps } from "react-beautiful-dnd";
@ -51,7 +51,7 @@ interface Props {
} }
export default function QuestionsPageCard({ question, draggableProps, isDragging, index }: Props) { export default function QuestionsPageCard({ question, draggableProps, isDragging, index }: Props) {
const { questions } = useQuestionsStore() const { questions } = useQuestionsStore();
const [plusVisible, setPlusVisible] = useState<boolean>(false); const [plusVisible, setPlusVisible] = useState<boolean>(false);
const [open, setOpen] = useState<boolean>(false); const [open, setOpen] = useState<boolean>(false);
const theme = useTheme(); const theme = useTheme();
@ -241,79 +241,67 @@ export default function QuestionsPageCard({ question, draggableProps, isDragging
padding: "0", padding: "0",
margin: "0 5px 0 10px", margin: "0 5px 0 10px",
}} }}
onClick={() => { // TODO onClick={() => {
const removedId = question.id; const deleteFn = () => {
// if (question.deleteTimeoutId) { if (question.type !== null) {
// clearTimeout(question.deleteTimeoutId); if (question.content.rule.parentId === "root") { //удалить из стора root и очистить rule всем вопросам
// } updateRootContentId(quiz.id, "");
clearRuleForAll();
// removeQuestion(quizId, totalIndex); deleteQuestion(question.id);
questions.forEach(q => {
// const newTimeoutId = window.setTimeout(() => { if (q.type === "result") {
// removeQuestionForce(quizId, removedId); deleteQuestion(q.id);
// }, 5000);
// updateQuestionsList<AnyTypedQuizQuestion>(quizId, totalIndex, {
// ...question,
// deleteTimeoutId: newTimeoutId,
// });
console.log(question.type)
if (question.type !== null) {
if (question.content.rule.parentId === "root") { //удалить из стора root и очистить rule всем вопросам
updateRootContentId(quiz.id, "")
clearRuleForAll()
deleteQuestion(question.id);
questions.forEach(q => {
if (q.type === "result") {
deleteQuestion(q.id);
}
});
} else if (question.content.rule.parentId.length > 0) { //удалить из стора вопрос из дерева и очистить его потомков
const clearQuestions = [] as string[]
//записываем потомков , а их результаты удаляем
const getChildren = (parentQuestion: AnyTypedQuizQuestion) => {
questions.forEach((targetQuestion) => {
if (targetQuestion.content.rule.parentId === parentQuestion.content.id) {//если у вопроса совпал родитель с родителем => он потомок, в кучу его
if (targetQuestion.type === "result") {
deleteQuestion(targetQuestion.id);
} else {
if (!clearQuestions.includes(targetQuestion.content.id)) clearQuestions.push(targetQuestion.content.id)
getChildren(targetQuestion) //и ищем его потомков
}
} }
}) });
} else if (question.content.rule.parentId.length > 0) { //удалить из стора вопрос из дерева и очистить его потомков
const clearQuestions = [] as string[];
//записываем потомков , а их результаты удаляем
const getChildren = (parentQuestion: AnyTypedQuizQuestion) => {
questions.forEach((targetQuestion) => {
if (targetQuestion.content.rule.parentId === parentQuestion.content.id) {//если у вопроса совпал родитель с родителем => он потомок, в кучу его
if (targetQuestion.type === "result") {
deleteQuestion(targetQuestion.id);
} else {
if (!clearQuestions.includes(targetQuestion.content.id)) clearQuestions.push(targetQuestion.content.id);
getChildren(targetQuestion); //и ищем его потомков
}
}
});
};
getChildren(question);
//чистим потомков от инфы ветвления
clearQuestions.forEach((id) => {
updateQuestion(id, question => {
question.content.rule.parentId = "";
question.content.rule.main = [];
question.content.rule.default = "";
});
});
//чистим rule родителя
const parentQuestion = getQuestionByContentId(question.content.rule.parentId);
const newRule = {};
newRule.main = parentQuestion.content.rule.main.filter((data) => data.next !== question.content.id); //удаляем условия перехода от родителя к этому вопросу
newRule.parentId = parentQuestion.content.rule.parentId;
newRule.default = parentQuestion.content.rule.parentId === question.content.id ? "" : parentQuestion.content.rule.parentId;
newRule.children = [...parentQuestion.content.rule.children].splice(parentQuestion.content.rule.children.indexOf(question.content.id), 1);
updateQuestion(question.content.rule.parentId, (PQ) => {
PQ.content.rule = newRule;
});
deleteQuestion(question.id);
} }
getChildren(question)
//чистим потомков от инфы ветвления
clearQuestions.forEach((id) => {
updateQuestion(id, question => {
question.content.rule.parentId = ""
question.content.rule.main = []
question.content.rule.default = ""
})
})
//чистим rule родителя
const parentQuestion = getQuestionByContentId(question.content.rule.parentId)
const newRule = {}
newRule.main = parentQuestion.content.rule.main.filter((data) => data.next !== question.content.id) //удаляем условия перехода от родителя к этому вопросу
newRule.parentId = parentQuestion.content.rule.parentId
newRule.default = parentQuestion.content.rule.parentId === question.content.id ? "" : parentQuestion.content.rule.parentId
newRule.children = [...parentQuestion.content.rule.children].splice(parentQuestion.content.rule.children.indexOf(question.content.id), 1);
updateQuestion(question.content.rule.parentId, (PQ) => { deleteQuestion(question.id);
PQ.content.rule = newRule } else {
}) console.log("удаляю безтипогово");
deleteQuestion(question.id) deleteQuestion(question.id);
} }
};
deleteQuestionWithTimeout(question.id, deleteFn);
deleteQuestion(question.id)
} else {
console.log("удаляю безтипогово")
deleteQuestion(question.id)
}
}} }}
data-cy="delete-question" data-cy="delete-question"
> >
@ -387,7 +375,7 @@ console.log(question.type)
}} }}
> >
<Box <Box
onClick={() => createUntypedQuestion(question.quizId)} onClick={() => createUntypedQuestion(question.quizId, question.id)}
sx={{ sx={{
display: plusVisible && !isDragging ? "flex" : "none", display: plusVisible && !isDragging ? "flex" : "none",
width: "100%", width: "100%",

@ -18,74 +18,74 @@ import type {
type ButtonTypeQuestion = { type ButtonTypeQuestion = {
icon: JSX.Element; icon: JSX.Element;
title: string; title: string;
value: QuestionType; value: QuestionType;
}; };
const BUTTON_TYPE_SHORT_QUESTIONS: ButtonTypeQuestion[] = [ const BUTTON_TYPE_SHORT_QUESTIONS: ButtonTypeQuestion[] = [
{ {
icon: <Answer color="#9A9AAF" />, icon: <Answer color="#9A9AAF" />,
title: "Варианты ответов", title: "Варианты ответов",
value: "variant", value: "variant",
}, },
{ {
icon: <Input color="#9A9AAF" />, icon: <Input color="#9A9AAF" />,
title: "Своё поле для ввода", title: "Своё поле для ввода",
value: "text", value: "text",
}, },
{ {
icon: <DropDown color="#9A9AAF" />, icon: <DropDown color="#9A9AAF" />,
title: "Выпадающий список", title: "Выпадающий список",
value: "select", value: "select",
}, },
{ {
icon: <Date color="#9A9AAF" />, icon: <Date color="#9A9AAF" />,
title: "Дата", title: "Дата",
value: "date", value: "date",
}, },
{ {
icon: <Slider color="#9A9AAF" />, icon: <Slider color="#9A9AAF" />,
title: "Ползунок", title: "Ползунок",
value: "number", value: "number",
}, },
{ {
icon: <Download color="#9A9AAF" />, icon: <Download color="#9A9AAF" />,
title: "Загрузка файла", title: "Загрузка файла",
value: "file", value: "file",
}, },
]; ];
interface Props { interface Props {
question: UntypedQuizQuestion; question: UntypedQuizQuestion;
} }
export default function FormTypeQuestions({ question }: Props) { export default function FormTypeQuestions({ question }: Props) {
return ( return (
<Box> <Box>
<Box <Box
sx={{ sx={{
display: "flex", display: "flex",
flexWrap: "wrap", flexWrap: "wrap",
gap: "20px", gap: "20px",
margin: "20px", margin: "20px",
}} }}
> >
{(true /* TODO только первый вопрос */ {(("page" in question) && question.page === 0
? BUTTON_TYPE_QUESTIONS ? BUTTON_TYPE_QUESTIONS
: BUTTON_TYPE_SHORT_QUESTIONS : BUTTON_TYPE_SHORT_QUESTIONS
).map(({ icon, title, value: questionType }) => ( ).map(({ icon, title, value: questionType }) => (
<QuestionsMiniButton <QuestionsMiniButton
key={title} key={title}
onClick={() => { onClick={() => {
createTypedQuestion(question.id, questionType); createTypedQuestion(question.id, questionType);
}} }}
icon={icon} icon={icon}
text={title} text={title}
/> />
))} ))}
</Box> </Box>
</Box> </Box>
); );
} }

@ -9,7 +9,7 @@ export default function ImageAndVideoButtons() {
return ( return (
<Box sx={{ display: "flex", alignItems: "center", gap: "12px", mt: "20px", mb: "20px" }}> <Box sx={{ display: "flex", alignItems: "center", gap: "12px", mt: "20px", mb: "20px" }}>
<AddImage onClick={undefined/* TODO () => openCropModal("", "") */} /> <AddImage onClick={undefined} />
<Typography <Typography
sx={{ sx={{
fontWeight: 400, fontWeight: 400,

@ -33,7 +33,6 @@ export const ResultSettings = () => {
isReadyToLeave = false; isReadyToLeave = false;
} }
}); });
console.log(`setting isReadyToLeaveRef to ${isReadyToLeave}`);
isReadyToLeaveRef.current = isReadyToLeave; isReadyToLeaveRef.current = isReadyToLeave;
}, [results]) }, [results])

@ -1,3 +1,4 @@
import dayjs from "dayjs";
import { DatePicker } from "@mui/x-date-pickers"; import { DatePicker } from "@mui/x-date-pickers";
import { Box, Typography } from "@mui/material"; import { Box, Typography } from "@mui/material";
@ -14,10 +15,9 @@ type DateProps = {
export const Date = ({ currentQuestion }: DateProps) => { export const Date = ({ currentQuestion }: DateProps) => {
const { answers } = useQuizViewStore(); const { answers } = useQuizViewStore();
const { answer } = const answer = answers.find(
answers.find( ({ questionId }) => questionId === currentQuestion.content.id
({ questionId }) => questionId === currentQuestion.content.id )?.answer as string;
) ?? {};
const [day, month, year] = answer?.split(".") || []; const [day, month, year] = answer?.split(".") || [];
return ( return (
@ -32,50 +32,54 @@ export const Date = ({ currentQuestion }: DateProps) => {
}} }}
> >
<DatePicker <DatePicker
slots={{ slots={{
openPickerIcon: () => <CalendarIcon />, openPickerIcon: () => <CalendarIcon />,
}} }}
selected={ value={dayjs(
answer answer
? new window.Date(`${month}.${day}.${year}`) ? new window.Date(`${month}.${day}.${year}`)
: new window.Date() : new window.Date()
} )}
onChange={(date) => onChange={(date) => {
if (!date) {
return;
}
updateAnswer( updateAnswer(
currentQuestion.content.id, currentQuestion.content.id,
String( String(
date?.toLocaleDateString("ru-RU", { new window.Date(date.toDate()).toLocaleDateString("ru-RU", {
year: "numeric", year: "numeric",
month: "2-digit", month: "2-digit",
day: "2-digit", day: "2-digit",
}) })
) )
) );
} }}
slotProps={{ slotProps={{
openPickerButton: { openPickerButton: {
sx: { sx: {
p: 0, p: 0,
}, },
"data-cy": "open-datepicker", "data-cy": "open-datepicker",
}, },
}} }}
sx={{ sx={{
"& .MuiInputBase-root": { "& .MuiInputBase-root": {
backgroundColor: "#F2F3F7", backgroundColor: "#F2F3F7",
borderRadius: "10px", borderRadius: "10px",
maxWidth: "250px", maxWidth: "250px",
pr: "22px", pr: "22px",
"& input": { "& input": {
py: "11px", py: "11px",
pl: "20px", pl: "20px",
lineHeight: "19px", lineHeight: "19px",
}, },
"& fieldset": { "& fieldset": {
borderColor: "#9A9AAF", borderColor: "#9A9AAF",
}, },
}, },
}} }}
/> />
</Box> </Box>
</Box> </Box>

@ -14,13 +14,18 @@ type FileProps = {
export const File = ({ currentQuestion }: FileProps) => { export const File = ({ currentQuestion }: FileProps) => {
const { answers } = useQuizViewStore(); const { answers } = useQuizViewStore();
const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.content.id) ?? {}; const answer = answers.find(
({ questionId }) => questionId === currentQuestion.content.id
)?.answer as string;
const theme = useTheme(); const theme = useTheme();
const uploadFile = ({ target }: ChangeEvent<HTMLInputElement>) => { const uploadFile = ({ target }: ChangeEvent<HTMLInputElement>) => {
const file = target.files?.[0]; const file = target.files?.[0];
if (file) { if (file) {
updateAnswer(currentQuestion.content.id, `${file.name}|${URL.createObjectURL(file)}`); updateAnswer(
currentQuestion.content.id,
`${file.name}|${URL.createObjectURL(file)}`
);
} }
}; };
@ -36,75 +41,43 @@ export const File = ({ currentQuestion }: FileProps) => {
maxWidth: answer?.split("|")[0] ? "640px" : "550px" maxWidth: answer?.split("|")[0] ? "640px" : "550px"
}} }}
> >
{answer?.split("|")[0] && ( <ButtonBase component="label" sx={{ justifyContent: "flex-start" }}>
<Box sx={{display: "flex", alignItems: "center", gap: "15px"}}> <input
<Typography>Вы загрузили:</Typography> onChange={uploadFile}
<Box sx={{padding: "5px 5px 5px 16px", backgroundColor: theme.palette.brightPurple.main, hidden
borderRadius: "8px", accept={UPLOAD_FILE_TYPES_MAP[currentQuestion.content.type]}
color: "#FFFFFF", multiple
display: "flex", type="file"
alignItems: "center", />
gap: "15px" <UploadBox icon={<UploadIcon />} text="5 MB максимум" />
}}> </ButtonBase>
<Typography> {answer && currentQuestion.content.type === "picture" && (
{answer?.split("|")[0]} <img
</Typography> src={answer.split("|")[1]}
<IconButton alt=""
sx={{p: 0}} style={{
onClick={() => {updateAnswer(currentQuestion.content.id, "");}} marginTop: "15px",
> maxWidth: "300px",
<X/> maxHeight: "300px",
</IconButton> }}
</Box> />
)}
</Box> {answer && currentQuestion.content.type === "video" && (
<video
)} src={answer.split("|")[1]}
{!answer?.split("|")[0] && ( style={{
<ButtonBase component="label" sx={{ justifyContent: "flex-start" }}> marginTop: "15px",
<input maxWidth: "300px",
onChange={uploadFile} maxHeight: "300px",
hidden objectFit: "cover",
accept={UPLOAD_FILE_TYPES_MAP[currentQuestion.content.type]} }}
multiple />
type="file" )}
/> {answer?.split("|")[0] && (
<Box <Typography sx={{ marginTop: "15px" }}>
onDragOver={(event: DragEvent<HTMLDivElement>) => event.preventDefault()} {answer?.split("|")[0]}
sx={{ </Typography>
width: "100%", )}
height: "120px",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
padding: "33px 44px 33px 55px",
backgroundColor: theme.palette.background.default,
border: `1px solid ${theme.palette.grey2.main}`,
borderRadius: "8px",
}}
>
<UploadIcon />
<Box>
<Typography
sx={{
color: theme.palette.grey2.main,
fontWeight: 500
}}
>Добавить видео</Typography>
<Typography
sx={{
color: theme.palette.grey2.main,
fontSize: "16px",
lineHeight: "19px",
}}
>
Принимает .mp4 и .mov формат максимум 100мб
</Typography>
</Box>
</Box>
</ButtonBase>
)}
</Box> </Box>
</Box> </Box>
); );

@ -60,9 +60,22 @@ export const Images = ({ currentQuestion }: ImagesProps) => {
<Box <Box
key={index} key={index}
sx={{ sx={{
cursor: "pointer",
borderRadius: "5px", borderRadius: "5px",
border: `1px solid ${theme.palette.grey2.main}`, border: `1px solid ${theme.palette.grey2.main}`,
}} }}
onClick={(event) => {
event.preventDefault();
updateAnswer(
currentQuestion.content.id,
currentQuestion.content.variants[index].id
);
if (answer === currentQuestion.content.variants[index].id) {
deleteAnswer(currentQuestion.content.id);
}
}}
> >
<Box sx={{ display: "flex", alignItems: "center", gap: "10px" }}> <Box sx={{ display: "flex", alignItems: "center", gap: "10px" }}>
<Box sx={{ width: "100%", height: "300px" }}> <Box sx={{ width: "100%", height: "300px" }}>
@ -88,18 +101,6 @@ export const Images = ({ currentQuestion }: ImagesProps) => {
color: theme.palette.grey2.main, color: theme.palette.grey2.main,
marginTop: "10px", marginTop: "10px",
}} }}
onClick={(event) => {
event.preventDefault();
updateAnswer(
currentQuestion.content.id,
currentQuestion.content.variants[index].id
);
if (answer === currentQuestion.content.variants[index].id) {
deleteAnswer(currentQuestion.content.id);
}
}}
value={index} value={index}
control={ control={
<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} /> <Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />

@ -7,7 +7,6 @@ import CustomTextField from "@ui_kit/CustomTextField";
import { useQuizViewStore, updateAnswer } from "@root/quizView"; import { useQuizViewStore, updateAnswer } from "@root/quizView";
import type { QuizQuestionNumber } from "../../../model/questionTypes/number"; import type { QuizQuestionNumber } from "../../../model/questionTypes/number";
import {CustomSlider} from "@ui_kit/CustomSlider";
type NumberProps = { type NumberProps = {
currentQuestion: QuizQuestionNumber; currentQuestion: QuizQuestionNumber;
@ -124,7 +123,7 @@ export const Number = ({ currentQuestion }: NumberProps) => {
}} }}
/> />
)} )}
{currentQuestion.content.chooseRange && ( {currentQuestion.content.chooseRange && (
<Box <Box
sx={{ sx={{
@ -177,7 +176,6 @@ export const Number = ({ currentQuestion }: NumberProps) => {
/> />
</Box> </Box>
)} )}
</Box> </Box>
</Box> </Box>
); );

@ -44,12 +44,14 @@ export const Rating = ({ currentQuestion }: RatingProps) => {
<StarIconMini <StarIconMini
color={theme.palette.brightPurple.main} color={theme.palette.brightPurple.main}
width={50} width={50}
sx={{ transform: "scale(1.4)" }}
/> />
} }
emptyIcon={ emptyIcon={
<StarIconMini <StarIconMini
color={theme.palette.grey2.main} color={theme.palette.grey2.main}
width={50} width={50}
sx={{ transform: "scale(1.4)" }}
/> />
} }
/> />

@ -2,8 +2,10 @@ import {
Box, Box,
Typography, Typography,
RadioGroup, RadioGroup,
FormGroup,
FormControlLabel, FormControlLabel,
Radio, Radio,
Checkbox,
useTheme, useTheme,
} from "@mui/material"; } from "@mui/material";
@ -27,11 +29,13 @@ export const Variant = ({ currentQuestion }: VariantProps) => {
({ questionId }) => questionId === currentQuestion.content.id ({ questionId }) => questionId === currentQuestion.content.id
) ?? {}; ) ?? {};
const Group = currentQuestion.content.multi ? FormGroup : RadioGroup;
return ( return (
<Box> <Box>
<Typography variant="h5">{currentQuestion.title}</Typography> <Typography variant="h5">{currentQuestion.title}</Typography>
<Box sx={{ display: "flex" }}> <Box sx={{ display: "flex" }}>
<RadioGroup <Group
name={currentQuestion.id} name={currentQuestion.id}
value={currentQuestion.content.variants.findIndex( value={currentQuestion.content.variants.findIndex(
({ id }) => answer === id ({ id }) => answer === id
@ -70,25 +74,45 @@ export const Variant = ({ currentQuestion }: VariantProps) => {
value={index} value={index}
labelPlacement="start" labelPlacement="start"
control={ control={
<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} /> currentQuestion.content.multi ? (
<Checkbox
checked={!!answer?.includes(variant.id)}
checkedIcon={<RadioCheck />}
icon={<RadioIcon />}
/>
) : (
<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />
)
} }
label={variant.answer} label={variant.answer}
onClick={(event) => { onClick={(event) => {
event.preventDefault(); event.preventDefault();
const variantId = currentQuestion.content.variants[index].id;
updateAnswer( if (currentQuestion.content.multi) {
currentQuestion.content.id, const currentAnswer =
currentQuestion.content.variants[index].id typeof answer !== "string" ? answer || [] : [];
);
if (answer === currentQuestion.content.variants[index].id) { updateAnswer(
currentQuestion.content.id,
currentAnswer?.includes(variantId)
? currentAnswer?.filter((item) => item !== variantId)
: [...currentAnswer, variantId]
);
return;
}
updateAnswer(currentQuestion.content.id, variantId);
if (answer === variantId) {
deleteAnswer(currentQuestion.content.id); deleteAnswer(currentQuestion.content.id);
} }
}} }}
/> />
))} ))}
</Box> </Box>
</RadioGroup> </Group>
{currentQuestion.content.back && ( {currentQuestion.content.back && (
<Box sx={{ maxWidth: "400px", width: "100%", height: "300px" }}> <Box sx={{ maxWidth: "400px", width: "100%", height: "300px" }}>
<img <img

@ -21,7 +21,7 @@ export default function FaviconDropZone({ imageUrl, onImageUploadClick, onDelete
const [isDropReady, setIsDropReady] = useState<boolean>(false); const [isDropReady, setIsDropReady] = useState<boolean>(false);
const [isImageUploadOpen, openImageUploadModal, closeImageUploadModal] = useDisclosure(); const [isImageUploadOpen, openImageUploadModal, closeImageUploadModal] = useDisclosure();
if (!quiz) return null; // TODO throw and catch with error boundary if (!quiz) return null;
async function handleImageUpload(file: File) { async function handleImageUpload(file: File) {
if (file.size > 5 * 2 ** 20) return enqueueSnackbar("Размер картинки слишком велик"); if (file.size > 5 * 2 ** 20) return enqueueSnackbar("Размер картинки слишком велик");

@ -63,12 +63,12 @@ export default function StartPageSettings() {
const [formState, setFormState] = useState<"design" | "content">("design"); const [formState, setFormState] = useState<"design" | "content">("design");
const [mobileVersion, setMobileVersion] = useState(false); const [mobileVersion, setMobileVersion] = useState(false);
if (!quiz) return null; // TODO throw and catch with error boundary if (!quiz) return null;
const MobileVersionHC = (bool: boolean) => { const MobileVersionHC = (bool: boolean) => {
setMobileVersion(bool); setMobileVersion(bool);
}; };
const designType = quiz?.config?.startpageType; const designType = quiz?.config?.startpageType;
const favIconDropZoneElement = ( const favIconDropZoneElement = (
@ -475,36 +475,37 @@ export default function StartPageSettings() {
> >
Расположение элементов Расположение элементов
</Typography> </Typography>
<Box {designType !== "centered" &&
sx={{ <Box
display: "flex", sx={{
gap: "10px", display: "flex",
}} gap: "10px",
> }}
<SelectableIconButton >
onClick={() => updateQuiz(quiz.id, quiz => { <SelectableIconButton
quiz.config.startpage.position = "left"; onClick={() => updateQuiz(quiz.id, quiz => {
})} quiz.config.startpage.position = "left";
isActive={quiz.config.startpage.position === "left"} })}
Icon={AlignLeftIcon} isActive={quiz.config.startpage.position === "left"}
/> Icon={AlignLeftIcon}
<SelectableIconButton />
onClick={() => updateQuiz(quiz.id, quiz => { <SelectableIconButton
quiz.config.startpage.position = "center"; onClick={() => updateQuiz(quiz.id, quiz => {
})} quiz.config.startpage.position = "center";
isActive={quiz.config.startpage.position === "center"} })}
Icon={AlignCenterIcon} isActive={quiz.config.startpage.position === "center"}
sx={{ display: designType === "centered" ? "flex" : "none" }} Icon={AlignCenterIcon}
/> sx={{ display: designType === "standard" ? "none" : "flex" }}
<SelectableIconButton />
onClick={() => updateQuiz(quiz.id, quiz => { <SelectableIconButton
quiz.config.startpage.position = "right"; onClick={() => updateQuiz(quiz.id, quiz => {
})} quiz.config.startpage.position = "right";
isActive={quiz.config.startpage.position === "right"} })}
Icon={AlignRightIcon} isActive={quiz.config.startpage.position === "right"}
/> Icon={AlignRightIcon}
</Box> />
</Box>
}
{(isTablet || !isSmallMonitor) && ( {(isTablet || !isSmallMonitor) && (
<> <>
<Box <Box

@ -44,7 +44,7 @@ export const DropZone = ({ text, sx, deleteIconSx, imageUrl, originalImageUrl, o
setCropModalImageBlob, setCropModalImageBlob,
} = useCropModalState(); } = useCropModalState();
if (!quiz) return null; // TODO throw and catch with error boundary if (!quiz) return null;
async function handleImageUpload(file: File) { async function handleImageUpload(file: File) {
if (file.size > 5 * 2 ** 20) return enqueueSnackbar("Размер картинки слишком велик"); if (file.size > 5 * 2 ** 20) return enqueueSnackbar("Размер картинки слишком велик");

@ -10,7 +10,7 @@ export default function StepOne() {
const quiz = useCurrentQuiz(); const quiz = useCurrentQuiz();
const config = quiz?.config; const config = quiz?.config;
if (!config) return null; // TODO throw and catch with error boundary if (!config) return null;
return ( return (
<Box <Box

@ -20,7 +20,7 @@ export default function Steptwo() {
const config = quiz?.config; const config = quiz?.config;
if (!config) return null; // TODO throw and catch with error boundary if (!config) return null;
return ( return (
<Box sx={{ mt: "60px" }}> <Box sx={{ mt: "60px" }}>

@ -26,8 +26,8 @@ export const setQuestions = (questions: RawQuestion[] | null) => setProducedStat
questions, questions,
}); });
export const createUntypedQuestion = (quizId: number) => setProducedState(state => { export const createUntypedQuestion = (quizId: number, insertAfterQuestionId?: string) => setProducedState(state => {
state.questions.push({ const newUntypedQuestion = {
id: nanoid(), id: nanoid(),
quizId, quizId,
type: null, type: null,
@ -35,7 +35,16 @@ export const createUntypedQuestion = (quizId: number) => setProducedState(state
description: "", description: "",
deleted: false, deleted: false,
expanded: true, expanded: true,
}); };
if (insertAfterQuestionId) {
const index = state.questions.findIndex(q => q.id === insertAfterQuestionId);
if (index === -1) return;
state.questions.splice(index + 1, 0, newUntypedQuestion);
return;
}
state.questions.push(newUntypedQuestion);
}, { }, {
type: "createUntypedQuestion", type: "createUntypedQuestion",
quizId, quizId,
@ -131,6 +140,37 @@ export const collapseAllQuestions = () => setProducedState(state => {
state.questions.forEach(question => question.expanded = false); state.questions.forEach(question => question.expanded = false);
}, "collapseAllQuestions"); }, "collapseAllQuestions");
const DELETE_TIMEOUT = 5000;
export const deleteQuestionWithTimeout = (questionId: string, deleteFn: (questionId: string) => void) => setProducedState(state => {
const question = state.questions.find(q => q.id === questionId);
if (!question) return;
if (question.type === null || question.type === "result") {
deleteFn(questionId);
return;
}
question.deleted = true;
clearTimeout(question.deleteTimeoutId);
question.deleteTimeoutId = window.setTimeout(() => {
deleteFn(questionId);
}, DELETE_TIMEOUT);
}, {
type: "deleteQuestionWithTimeout",
questionId,
});
export const cancelQuestionDeletion = (questionId: string) => setProducedState(state => {
const question = state.questions.find(q => q.id === questionId);
if (!question || question.type === null || question.type === "result") return;
question.deleted = false;
clearTimeout(question.deleteTimeoutId);
}, {
type: "cancelQuestionDeletion",
questionId,
});
const REQUEST_DEBOUNCE = 200; const REQUEST_DEBOUNCE = 200;
const requestQueue = new RequestQueue(); const requestQueue = new RequestQueue();
@ -335,6 +375,8 @@ export const createTypedQuestion = async (
type: "createTypedQuestion", type: "createTypedQuestion",
question, question,
}); });
updateQuestionOrders();
} catch (error) { } catch (error) {
devlog("Error creating question", error); devlog("Error creating question", error);
enqueueSnackbar("Не удалось создать вопрос"); enqueueSnackbar("Не удалось создать вопрос");
@ -355,7 +397,7 @@ export const deleteQuestion = async (questionId: string) => requestQueue.enqueue
try { try {
await questionApi.delete(question.backendId); await questionApi.delete(question.backendId);
removeQuestion(questionId); removeQuestion(questionId);
updateQuestionOrders(); updateQuestionOrders();
@ -521,7 +563,7 @@ export const createBackResult = async (
rawQuestionToQuestion(createdQuestion) rawQuestionToQuestion(createdQuestion)
); );
}, { }, {
type: "createTypedQuestion", type: "createBackResult",
question, question,
}); });
} catch (error) { } catch (error) {

@ -3,7 +3,7 @@ import { devtools } from "zustand/middleware";
type Answer = { type Answer = {
questionId: string; questionId: string;
answer: string; answer: string | string[];
// Поле отвечающее за первое изменение ответа, нужно для галочки "Необязательный вопрос" // Поле отвечающее за первое изменение ответа, нужно для галочки "Необязательный вопрос"
changed: boolean; changed: boolean;
}; };
@ -25,7 +25,7 @@ export const useQuizViewStore = create<QuizViewStore>()(
export const updateAnswer = ( export const updateAnswer = (
questionId: string, questionId: string,
answer: string, answer: string | string[],
changed = true changed = true
) => { ) => {
const answers = [...useQuizViewStore.getState().answers]; const answers = [...useQuizViewStore.getState().answers];

@ -1,6 +1,8 @@
import { import {
Box, Box,
Button, Button,
ButtonBase,
Link,
Paper, Paper,
Typography, Typography,
useMediaQuery, useMediaQuery,
@ -8,51 +10,53 @@ import {
} from "@mui/material"; } from "@mui/material";
import { useCurrentQuiz } from "@root/quizes/hooks"; import { useCurrentQuiz } from "@root/quizes/hooks";
import YoutubeEmbedIframe from "./YoutubeEmbedIframe"; import YoutubeEmbedIframe from "./YoutubeEmbedIframe";
import { QuizStartpageAlignType, QuizStartpageType } from "@model/quizSettings";
import { notReachable } from "../../utils/notReachable";
import { useUADevice } from "../../utils/hooks/useUADevice";
export default function QuizPreviewLayout() { export default function QuizPreviewLayout() {
const theme = useTheme(); const theme = useTheme();
const quiz = useCurrentQuiz(); const quiz = useCurrentQuiz();
const isTablet = useMediaQuery(theme.breakpoints.down(630)); const { isMobileDevice } = useUADevice();
if (!quiz) return null; if (!quiz) return null;
const isMediaFileExist = const handleCopyNumber = () => {
(quiz.config.startpage.background.type === "image" && navigator.clipboard.writeText(quiz.config.info.phonenumber);
quiz.config.startpage.background.desktop) || };
(quiz.config.startpage.background.type === "video" &&
quiz.config.startpage.background.video); const background = quiz.config.startpage.background.type === "image"
? quiz.config.startpage.background.desktop
? (
<img
src={quiz.config.startpage.background.desktop}
alt=""
style={{
width: "100%",
height: "100%",
objectFit: "cover",
}}
/>
)
: null
: quiz.config.startpage.background.type === "video"
? quiz.config.startpage.background.video
? (
<YoutubeEmbedIframe videoUrl={quiz.config.startpage.background.video} />
)
: null
: null;
return ( return (
<Paper className="quiz-preview-draghandle" sx={{ height: "100%" }}> <Paper className="quiz-preview-draghandle" sx={{ height: "100%" }}>
<Box <QuizPreviewLayoutByType
sx={{ quizHeaderBlock={<>
display: "flex", <Box sx={{
flexDirection:
quiz.config.startpage.position === "left"
? "row"
: "row-reverse",
flexGrow: 1,
height: "100%",
"&::-webkit-scrollbar": { width: 0 },
}}
>
<Box
sx={{
width: isMediaFileExist && !isTablet ? "40%" : "100%",
padding: "16px",
display: "flex", display: "flex",
flexDirection: "column", alignItems: "center",
alignItems: isMediaFileExist && !isTablet ? "flex-start" : "center", gap: "20px",
}} }}>
>
<Box
sx={{
display: "flex",
alignItems: "center",
gap: "20px",
}}
>
{quiz.config.startpage.logo && ( {quiz.config.startpage.logo && (
<img <img
src={quiz.config.startpage.logo} src={quiz.config.startpage.logo}
@ -68,15 +72,17 @@ export default function QuizPreviewLayout() {
{quiz.config.info.orgname} {quiz.config.info.orgname}
</Typography> </Typography>
</Box> </Box>
<Box </>}
sx={{ quizMainBlock={<>
flexGrow: 1, <Box sx={{
display: "flex", display: "flex",
gap: "10px", gap: "10px",
flexDirection: "column", flexDirection: "column",
justifyContent: "center", justifyContent: "center",
}} alignItems: (quiz.config.startpageType === "expanded" && quiz.config.startpage.position === "center")
> ? "center"
: "start",
}}>
<Typography sx={{ fontWeight: "bold" }}>{quiz.name}</Typography> <Typography sx={{ fontWeight: "bold" }}>{quiz.name}</Typography>
<Typography sx={{ fontSize: "12px" }}> <Typography sx={{ fontSize: "12px" }}>
{quiz.config.startpage.description} {quiz.config.startpage.description}
@ -89,42 +95,146 @@ export default function QuizPreviewLayout() {
padding: "10px 15px", padding: "10px 15px",
}} }}
> >
{quiz.config.startpage.button ? quiz.config.startpage.button : "Пройти тест"} {quiz.config.startpage.button.trim() ? quiz.config.startpage.button : "Пройти тест"}
</Button> </Button>
</Box> </Box>
</Box> </Box>
<Box> <Box>
<Typography {quiz.config.info.clickable ? (
sx={{ fontSize: "16px", color: theme.palette.brightPurple.main }} isMobileDevice ? (
> <Link href={`tel:${quiz.config.info.phonenumber}`}>
{quiz.config.info.phonenumber} <Typography sx={{ fontSize: "16px", color: theme.palette.brightPurple.main }}>
</Typography> {quiz.config.info.phonenumber}
</Typography>
</Link>
) : (
<ButtonBase onClick={handleCopyNumber}>
<Typography sx={{ fontSize: "16px", color: theme.palette.brightPurple.main }}>
{quiz.config.info.phonenumber}
</Typography>
</ButtonBase>
)
) : (
<Typography sx={{ fontSize: "16px", color: theme.palette.brightPurple.main }}>
{quiz.config.info.phonenumber}
</Typography>
)}
<Typography sx={{ fontSize: "12px" }}> <Typography sx={{ fontSize: "12px" }}>
{quiz.config.info.law} {quiz.config.info.law}
</Typography> </Typography>
</Box> </Box>
</Box> </>}
{!isTablet && isMediaFileExist && ( backgroundBlock={background}
<Box sx={{ width: "60%" }}> startpageType={quiz.config.startpageType}
{quiz.config.startpage.background.type === "image" && alignType={quiz.config.startpage.position}
quiz.config.startpage.background.desktop && ( />
<img
src={quiz.config.startpage.background.desktop}
alt=""
style={{
width: "100%",
height: "100%",
objectFit: "cover",
}}
/>
)}
{quiz.config.startpage.background.type === "video" &&
quiz.config.startpage.background.video && (
<YoutubeEmbedIframe videoUrl={quiz.config.startpage.background.video} />
)}
</Box>
)}
</Box>
</Paper> </Paper>
); );
} }
function QuizPreviewLayoutByType({ quizHeaderBlock, quizMainBlock, backgroundBlock, startpageType, alignType }: {
quizHeaderBlock: JSX.Element;
quizMainBlock: JSX.Element;
backgroundBlock: JSX.Element | null;
startpageType: QuizStartpageType;
alignType: QuizStartpageAlignType;
}) {
const theme = useTheme();
const isTablet = useMediaQuery(theme.breakpoints.down(630));
switch (startpageType) {
case null:
case "standard": {
return (
<Box sx={{
display: "flex",
flexDirection: alignType === "left" ? "row" : "row-reverse",
flexGrow: 1,
height: "100%",
"&::-webkit-scrollbar": { width: 0 },
}}>
<Box sx={{
width: !isTablet ? "40%" : "100%",
padding: "16px",
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
alignItems: !isTablet ? "flex-start" : "center",
}}>
{quizHeaderBlock}
{quizMainBlock}
</Box>
<Box sx={{
width: "60%",
}}>
{backgroundBlock}
</Box>
</Box>
);
}
case "expanded": {
return (
<Box sx={{
position: "relative",
display: "flex",
justifyContent: startpageAlignTypeToJustifyContent[alignType],
flexGrow: 1,
height: "100%",
"&::-webkit-scrollbar": { width: 0 },
}}>
<Box sx={{
width: "40%",
position: "relative",
padding: "16px",
zIndex: 2,
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
alignItems: alignType === "center" ? "center" : "start",
}}>
{quizHeaderBlock}
{quizMainBlock}
</Box>
<Box sx={{
position: "absolute",
left: 0,
top: 0,
height: "100%",
width: "100%",
zIndex: 1,
}}>
{backgroundBlock}
</Box>
</Box>
);
}
case "centered": {
return (
<Box sx={{
padding: "16px",
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
alignItems: "center",
height: "100%",
"&::-webkit-scrollbar": { width: 0 },
}}>
{quizHeaderBlock}
{backgroundBlock &&
<Box>
{backgroundBlock}
</Box>
}
{quizMainBlock}
</Box>
);
}
default: notReachable(startpageType);
}
}
const startpageAlignTypeToJustifyContent: Record<QuizStartpageAlignType, "start" | "center" | "end"> = {
left: "start",
center: "center",
right: "end",
};

@ -17,6 +17,7 @@ export default function YoutubeEmbedIframe({ videoUrl }: Props) {
<Box sx={{ <Box sx={{
width: "100%", width: "100%",
height: "100%", height: "100%",
pointerEvents: "none",
"& iframe": { "& iframe": {
width: "100%", width: "100%",
height: "100%", height: "100%",

@ -107,6 +107,7 @@ export const StartPagePreview = () => {
topLeft: { topLeft: {
top: "-1px", top: "-1px",
left: "-1px", left: "-1px",
zIndex: 100,
}, },
}} }}
style={{ style={{

@ -0,0 +1,13 @@
import { useEffect, useState } from "react";
export function useUADevice(): { isMobileDevice: boolean; } {
const [isMobileDevice, setIsMobileDevice] = useState<boolean>(false);
useEffect(() => {
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
setIsMobileDevice(isMobile);
}, [navigator.userAgent]);
return { isMobileDevice };
}

646
yarn.lock

File diff suppressed because it is too large Load Diff