Merge branch 'dev' into backend-integration

This commit is contained in:
Nastya 2023-12-09 14:31:16 +03:00
commit 2a9ad65093
67 changed files with 1898 additions and 592 deletions

@ -1,4 +1,4 @@
FROM node:19.1-alpine as build
FROM node:20.10-alpine3.18 as build
RUN apk update && rm -rf /var/cache/apk/*
WORKDIR /usr/app

@ -40,6 +40,7 @@
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18.2.0",
"react-easy-crop": "^5.0.0",
"react-error-boundary": "^4.0.11",
"react-image-crop": "^10.1.5",
"react-image-file-resizer": "^0.4.8",
"react-rnd": "^10.4.1",

@ -11,11 +11,11 @@ import ContactFormPage from "./pages/ContactFormPage/ContactFormPage";
import InstallQuiz from "./pages/InstallQuiz/InstallQuiz";
import Landing from "./pages/Landing/Landing";
import QuestionsPage from "./pages/Questions/QuestionsPage";
import { Result } from "./pages/Result/Result";
import { Setting } from "./pages/Result/Setting";
import { Result } from "./pages/ResultPage/Result";
import { ResultSettings } from "./pages/ResultPage/ResultSettings";
import MyQuizzesFull from "./pages/createQuize/MyQuizzesFull";
import Main from "./pages/main";
import StartPage from "./pages/startPage/StartPage";
import EditPage from "./pages/startPage/EditPage";
import { clearAuthToken, getMessageFromFetchError, useUserFetcher } from "@frontend/kitui";
import { clearUserData, setUser, useUserStore } from "@root/user";
import { enqueueSnackbar } from "notistack";
@ -28,7 +28,7 @@ const routeslink = [
{ path: "/questions/:quizId", page: <QuestionsPage />, header: true, sidebar: true, },
{ path: "/contacts", page: <ContactFormPage />, header: true, sidebar: true },
{ path: "/result", page: <Result />, header: true, sidebar: true },
{ path: "/settings", page: <Setting />, header: true, sidebar: true },
{ path: "/settings", page: <ResultSettings />, header: true, sidebar: true },
{ path: "/install", page: <InstallQuiz />, header: true, sidebar: true },
] as const;
@ -57,7 +57,7 @@ export default function App() {
{routeslink.map((e, i) => (
<Route key={i} path={e.path} element={<Main page={e.page} header={e.header} sidebar={e.sidebar} />} />
))}
<Route path="edit" element={<StartPage />} />
<Route path="edit" element={<EditPage />} />
<Route path="crop" element={<ImageCrop />} />
<Route path="/" element={<Landing />} />
<Route path="/signin" element={<SigninDialog />} />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 823 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

@ -0,0 +1,18 @@
import { useTheme, SxProps, Box } from "@mui/material";
interface Props {
sx?: SxProps;
}
export default function ExpandIcon({ sx }: Props) {
const theme = useTheme();
return (
<Box sx={{ ...sx }}>
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="30" height="30" rx="6" fill="#EEE4FC" />
<path d="M22.5 11.25L15 18.75L7.5 11.25" stroke="#7E2AEA" stroke-width="1.5" stroke-linecap="round" strokeLinejoin="round" />
</svg>
</Box>
);
}

@ -1,4 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 -2.62268e-07C21.3137 -1.17422e-07 24 2.68629 24 6L24 18C24 21.3137 21.3137 24 18 24L6 24C2.68629 24 -9.31652e-07 21.3137 -7.86805e-07 18L-5.24537e-07 12L-2.62268e-07 6C-1.17422e-07 2.68629 2.68629 -9.31652e-07 6 -7.86805e-07L18 -2.62268e-07Z" fill="#9A9AAF" fill-opacity="0.7"/>
<path d="M7 11.5L11.2857 15.5L17 8" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 11.5L11.2857 15.5L17 8" stroke="white" stroke-linecap="round" strokeLinejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 495 B

After

Width:  |  Height:  |  Size: 494 B

@ -11,6 +11,7 @@ import { QUIZ_QUESTION_SELECT } from "./select";
import { QUIZ_QUESTION_TEXT } from "./text";
import { QUIZ_QUESTION_VARIANT } from "./variant";
import { QUIZ_QUESTION_VARIMG } from "./varimg";
import { QUIZ_QUESTION_RESULT } from "./result";
export const defaultQuestionByType: Record<QuestionType, Omit<AnyTypedQuizQuestion, "id" | "backendId">> = {
@ -25,4 +26,5 @@ export const defaultQuestionByType: Record<QuestionType, Omit<AnyTypedQuizQuesti
"text": QUIZ_QUESTION_TEXT,
"variant": QUIZ_QUESTION_VARIANT,
"varimg": QUIZ_QUESTION_VARIMG,
"result": QUIZ_QUESTION_RESULT,
} as const;

17
src/constants/result.ts Normal file

@ -0,0 +1,17 @@
import { QUIZ_QUESTION_BASE } from "./base";
import type { QuizQuestionResult } from "../model/questionTypes/result";
import { nanoid } from "nanoid";
export const QUIZ_QUESTION_RESULT: Omit<QuizQuestionResult, "id" | "backendId"> = {
...QUIZ_QUESTION_BASE,
type: "result",
content: {
...QUIZ_QUESTION_BASE.content,
video: "",
innerName: "",
text: "",
price: [0],
rangePrice: false
},
};

@ -14,7 +14,8 @@ export type QuestionType =
| "number"
| "file"
| "page"
| "rating";
| "rating"
| "result";
/** Type that comes from server */
export interface RawQuestion {

@ -0,0 +1,22 @@
import type {
QuizQuestionBase,
QuestionBranchingRule,
QuestionHint,
} from "./shared";
export interface QuizQuestionResult extends QuizQuestionBase {
type: "result";
content: {
id: string;
back: string;
originalBack: string;
video: string;
innerName: string;
text: string;
price: [number] | [number, number];
rangePrice: boolean;
rule: QuestionBranchingRule,
hint: QuestionHint;
autofill: boolean;
};
}

@ -10,6 +10,7 @@ import type { QuizQuestionSelect } from "./select";
import type { QuizQuestionText } from "./text";
import type { QuizQuestionVariant } from "./variant";
import type { QuizQuestionVarImg } from "./varimg";
import type { QuizQuestionResult } from "./result";
import { nanoid } from "nanoid";
export interface QuestionBranchingRuleMain {
@ -92,7 +93,8 @@ export type AnyTypedQuizQuestion =
| QuizQuestionNumber
| QuizQuestionFile
| QuizQuestionPage
| QuizQuestionRating;
| QuizQuestionRating
| QuizQuestionResult;
type FilterQuestionsWithVariants<T> = T extends {
content: { variants: QuestionVariant[]; };

@ -33,6 +33,14 @@ export interface QuizConfig {
startpageType: QuizStartpageType;
results: QuizResultsType;
haveRoot: string | null;
resultInfo: {
when: 'before' | 'after' | 'email',
share: true | false,
replay: true | false,
theme: string,
reply: string,
replname: string,
}
startpage: {
description: string;
button: string;
@ -66,6 +74,14 @@ export const defaultQuizConfig: QuizConfig = {
startpageType: null,
results: null,
haveRoot: null,
resultInfo: {
when: 'after',
share: false,
replay: false,
theme: "",
reply: "",
replname: "",
},
startpage: {
description: "",
button: "",

@ -6,7 +6,8 @@ import { useCurrentQuiz } from "@root/quizes/hooks";
import { updateRootContentId } from "@root/quizes/actions"
import { AnyTypedQuizQuestion } from "@model/questionTypes/shared"
import { useQuestionsStore } from "@root/questions/store";
import { cleardragQuestionContentId, updateQuestion, updateOpenedModalSettingsId, getQuestionByContentId } from "@root/questions/actions";
import { cleardragQuestionContentId, updateQuestion, updateOpenedModalSettingsId, getQuestionByContentId, clearRuleForAll } from "@root/questions/actions";
import { withErrorBoundary } from "react-error-boundary";
import { storeToNodes } from "./helper";
@ -19,8 +20,9 @@ import type {
AbstractEventObject,
ElementDefinition,
} from "cytoscape";
import { QuestionsList } from "../BranchingPanel/QuestionsList";
import { QuestionsList } from "../SwitchBranchingPanel/QuestionsList";
import { enqueueSnackbar } from "notistack";
import { Typography } from "@mui/material";
type PopperItem = {
id: () => string;
@ -65,6 +67,14 @@ const stylesheet: Stylesheet[] = [
"text-max-width": "80",
},
},
{
selector: "[?eroticeyeblink]",
style: {
"border-width": "4px",
"border-style": "solid",
"border-color": "#7e2aea",
},
},
{
selector: ".multiline-auto",
style: {
@ -103,16 +113,18 @@ interface Props {
}
export const CsComponent = ({
function CsComponent ({
modalQuestionParentContentId,
modalQuestionTargetContentId,
setOpenedModalQuestions,
setModalQuestionParentContentId,
setModalQuestionTargetContentId
}: Props) => {
}: Props) {
const quiz = useCurrentQuiz();
const { dragQuestionContentId, questions } = useQuestionsStore()
const { dragQuestionContentId, desireToOpenABranchingModal } = useQuestionsStore()
const trashQuestions = useQuestionsStore().questions
const questions = trashQuestions.filter((question) => question.type !== "result")
const [startCreate, setStartCreate] = useState("");
const [startRemove, setStartRemove] = useState("");
@ -122,16 +134,27 @@ export const CsComponent = ({
const crossesContainer = useRef<HTMLDivElement | null>(null);
const gearsContainer = useRef<HTMLDivElement | null>(null);
useLayoutEffect(() => {
updateOpenedModalSettingsId()
}, [])
useLayoutEffect(() => {
const cy = cyRef?.current
if (desireToOpenABranchingModal) {
setTimeout(() => {
cy?.getElementById(desireToOpenABranchingModal)?.data("eroticeyeblink", true)
}, 250)
} else {
cy?.elements().data("eroticeyeblink", false)
}
}, [desireToOpenABranchingModal])
useLayoutEffect(() => {
updateOpenedModalSettingsId()
// updateRootContentId(quiz.id, "")
// clearRuleForAll()
}, [])
useEffect(() => {
if (modalQuestionTargetContentId.length !== 0 && modalQuestionParentContentId.length !== 0) {
addNode({ parentNodeContentId: modalQuestionParentContentId, targetNodeContentId: modalQuestionTargetContentId })
}
setModalQuestionParentContentId("")
setModalQuestionTargetContentId("")
}, [modalQuestionTargetContentId])
const addNode = ({ parentNodeContentId, targetNodeContentId }: { parentNodeContentId: string, targetNodeContentId?: string }) => {
@ -211,6 +234,7 @@ export const CsComponent = ({
question.content.rule.main = []
question.content.rule.default = ""
})
clearRuleForAll()
} else {
const parentQuestionContentId = cy?.$('edge[target = "' + targetNodeContentId + '"]')?.toArray()?.[0]?.data()?.source
if (targetNodeContentId && parentQuestionContentId) {
@ -248,11 +272,21 @@ export const CsComponent = ({
question.content.rule.default = ""
})
updateQuestion(parentQuestionContentId, question => {
//Заменяем id удаляемого вопроса либо на id одного из оставшихся, либо на пустую строку
if (question.content.rule.default === targetQuestionContentId) question.content.rule.default = ""
})
//чистим rule родителя
const parentQuestion = getQuestionByContentId(parentQuestionContentId)
const newRule = {}
newRule.main = parentQuestion.content.rule.main.filter((data) => data.next !== targetQuestionContentId) //удаляем условия перехода от родителя к этому вопросу
newRule.parentId = parentQuestion.content.rule.parentId
newRule.default = questions.filter((q) => {
return q.content.rule.parentId === parentQuestionContentId && q.content.id !== targetQuestionContentId
})[0]?.content.id || ""
//Если этот вопрос был дефолтным у родителя - чистим дефолт
//Смотрим можем ли мы заменить id на один из main
console.log(newRule)
updateQuestion(parentQuestionContentId, (PQ) => {
PQ.content.rule = newRule
})
}
@ -390,6 +424,7 @@ export const CsComponent = ({
const cy = cyRef.current;
const eles = cy?.add(storeToNodes(questions))
cy.data('changed', true)
// cy.data('changed', true)
const elecs = eles.layout(lyopts).run()
cy?.on('add', () => cy.data('changed', true))
cy?.fit()
@ -665,8 +700,9 @@ export const CsComponent = ({
cy={(cy) => {
cyRef.current = cy;
}}
// autolock
/>
{/* <button onClick={() => {
<button onClick={() => {
console.log("NODES____________________________")
cyRef.current?.elements().forEach((ele: any) => {
console.log(ele.data())
@ -675,7 +711,23 @@ export const CsComponent = ({
<button onClick={() => {
console.log("ELEMENTS____________________________")
console.log(questions)
}}>elements</button> */}
}}>elements</button>
</>
);
};
function Clear () {
const quiz = useCurrentQuiz();
updateRootContentId(quiz.id, "")
clearRuleForAll()
return <></>
}
export default withErrorBoundary(CsComponent, {
fallback: <Clear/>,
onError: (error, info) => {
enqueueSnackbar("Дерево порвалось")
console.log(info)
console.log(error)
},
});

@ -12,7 +12,7 @@ interface Props {
}
export const FirstNodeField = ({ setOpenedModalQuestions, modalQuestionTargetContentId }: Props) => {
const quiz = useCurrentQuiz();
const { dragQuestionContentId, questions } = useQuestionsStore()
const { dragQuestionContentId } = useQuestionsStore()
const Container = useRef<HTMLDivElement | null>(null);
const modalOpen = () => setOpenedModalQuestions(true)

@ -1,6 +1,6 @@
import { Box } from "@mui/material";
import { FirstNodeField } from "./FirstNodeField";
import { CsComponent } from "./CsComponent";
import CsComponent from "./CsComponent";
import { useQuestionsStore } from "@root/questions/store"
import { useCurrentQuiz } from "@root/quizes/hooks";
import { useState } from "react";

@ -290,7 +290,12 @@ const DateInputsType = ({ parentQuestion, targetQuestion, ruleIndex, setParentQu
}
<DatePicker
value={parentQuestion.content.rule.main[ruleIndex].rules[0].answers[0]}
onChange={(e) => console.log(e)}
onChange={(dateString) => {
const date = dateString?.$d?.toLocaleDateString("ru-RU", { year: "numeric", month: "2-digit", day: "2-digit" });
let newParentQuestion = JSON.parse(JSON.stringify(parentQuestion))
newParentQuestion.content.rule.main[ruleIndex].rules[0].answers = [date]
// setParentQuestion(newParentQuestion)
}}
slots={{
openPickerIcon: () => <CalendarIcon />,
}}

@ -1,13 +1,16 @@
import { Box, Typography, Switch, useTheme } from "@mui/material";
import {Box, Typography, Switch, useTheme, Button, useMediaQuery, SxProps, Theme} from "@mui/material";
import { QuestionsList } from "./QuestionsList";
import { updateOpenBranchingPanel } from "@root/questions/actions";
import {useQuestionsStore} from "@root/questions/store";
import {useRef} from "react";
export const BranchingPanel = () => {
export const BranchingPanel = (sx?: SxProps<Theme>) => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(660));
const {openBranchingPanel} = useQuestionsStore.getState()
const ref = useRef()
return (
<Box sx={{ userSelect: "none", maxWidth: "350px", width: "100%" }}>
<Box
@ -19,12 +22,13 @@ export const BranchingPanel = () => {
background: "#fff",
borderRadius: "12px",
boxShadow: "0px 10px 30px #e7e7e7",
...sx
}}
>
<Switch
value={openBranchingPanel}
onChange={(_, value) => {
updateOpenBranchingPanel(!value)
updateOpenBranchingPanel(value)
}}
sx={{
width: 50,
@ -71,7 +75,7 @@ export const BranchingPanel = () => {
}}
/>
<Box>
<Typography sx={{ fontWeight: "bold", color: "#4D4D4D" }}>
<Typography ref={ref} sx={{ fontWeight: "bold", color: "#4D4D4D" }}>
Логика ветвления
</Typography>
<Typography sx={{ color: "#4D4D4D", fontSize: "12px" }}>
@ -79,7 +83,7 @@ export const BranchingPanel = () => {
</Typography>
</Box>
</Box>
{ openBranchingPanel === true && <QuestionsList /> }
{ openBranchingPanel && <QuestionsList /> }
</Box>
);
};

@ -15,7 +15,8 @@ export const BranchingQuestionsModal = ({
setModalQuestionTargetContentId,
setModalQuestionParentContentId
}: Props) => {
const { questions } = useQuestionsStore();
const trashQuestions = useQuestionsStore().questions
const questions = trashQuestions.filter((question) => question.type !== "result")
const handleClose = () => {
setOpenedModalQuestions(false);

@ -11,7 +11,7 @@ import {
useMediaQuery,
useTheme,
} from "@mui/material";
import {copyQuestion, deleteQuestion, updateOpenBranchingPanel, updateQuestion} from "@root/questions/actions";
import { copyQuestion, deleteQuestion, updateOpenBranchingPanel, updateDesireToOpenABranchingModal } from "@root/questions/actions";
import MiniButtonSetting from "@ui_kit/MiniButtonSetting";
import { CopyIcon } from "../../assets/icons/questionsPage/CopyIcon";
import Branching from "../../assets/icons/questionsPage/branching";
@ -19,10 +19,9 @@ import Clue from "../../assets/icons/questionsPage/clue";
import { HideIcon } from "../../assets/icons/questionsPage/hideIcon";
import SettingIcon from "../../assets/icons/questionsPage/settingIcon";
import type { AnyTypedQuizQuestion } from "../../model/questionTypes/shared";
import { updateOpenedModalSettingsId } from "@root/questions/actions";
import { useCurrentQuiz } from "@root/quizes/hooks";
import {enqueueSnackbar} from "notistack";
import {useQuestionsStore} from "@root/questions/store";
import { enqueueSnackbar } from "notistack";
import { useQuestionsStore } from "@root/questions/store";
interface Props {
@ -42,25 +41,7 @@ export default function ButtonsOptions({
const isWrappMiniButtonSetting = useMediaQuery(theme.breakpoints.down(920));
const quiz = useCurrentQuiz();
const {openBranchingPanel} = useQuestionsStore.getState()
const openedModal = () => {
updateOpenedModalSettingsId(question.id)
};
const handleClickBranching = (_, value) => {
const parentId = question.content.rule.parentId
if (parentId.length === 0 ){
return enqueueSnackbar("Вопрос не учавствует в ветвлении")
}
if (parentId === "root") {
return enqueueSnackbar("У корня нет условий ветвления")
}
if (parentId.length !== 0) {
updateOpenBranchingPanel(!value)
openedModal()
}
}
const { openBranchingPanel } = useQuestionsStore.getState()
const buttonSetting: {
icon: JSX.Element;
@ -98,7 +79,10 @@ const {openBranchingPanel} = useQuestionsStore.getState()
),
title: "Ветвление",
value: "branching",
myFunc: () => handleClickBranching(question.id, openBranchingPanel),
myFunc: (question) => {
updateOpenBranchingPanel(true)
updateDesireToOpenABranchingModal(question.content.id)
}
},
];
@ -174,7 +158,7 @@ const {openBranchingPanel} = useQuestionsStore.getState()
key={title}
onClick={() => {
SSHC(value);
myFunc();
myFunc(question);
}}
sx={{
backgroundColor:

@ -23,8 +23,8 @@ import ImgIcon from "../../assets/icons/questionsPage/imgIcon";
import SettingIcon from "../../assets/icons/questionsPage/settingIcon";
import { QuizQuestionVariant } from "@model/questionTypes/variant";
import { updateOpenedModalSettingsId } from "@root/questions/actions";
import { updateOpenBranchingPanel } from "@root/questions/actions";
import {useQuestionsStore} from "@root/questions/store";
import { updateOpenBranchingPanel, updateDesireToOpenABranchingModal } from "@root/questions/actions";
import { useQuestionsStore } from "@root/questions/store";
import { enqueueSnackbar } from "notistack";
import { useCurrentQuiz } from "@root/quizes/hooks";
@ -46,7 +46,7 @@ export default function ButtonsOptionsAndPict({
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(790));
const isIconMobile = useMediaQuery(theme.breakpoints.down(1050));
const {openBranchingPanel} = useQuestionsStore.getState()
const { openBranchingPanel } = useQuestionsStore.getState()
const quiz = useCurrentQuiz();
useEffect(() => {
@ -55,22 +55,6 @@ export default function ButtonsOptionsAndPict({
}
}, [question]);
const handleClickBranching = (_, value) => {
const parentId = question.content.rule.parentId
if (parentId.length === 0 ) {
return enqueueSnackbar("Вопрос не учавствует в ветвлении")
}
if (parentId === "root") {
return enqueueSnackbar("У корня нет условий ветвления")
}
if (parentId.length !== 0) {
updateOpenBranchingPanel(!value)
updateOpenedModalSettingsId(question.id)
}
}
return (
<Box
sx={{
@ -206,7 +190,8 @@ export default function ButtonsOptionsAndPict({
onMouseEnter={() => setButtonHover("branching")}
onMouseLeave={() => setButtonHover("")}
onClick={() => {
handleClickBranching(question.id, openBranchingPanel)
updateOpenBranchingPanel(true)
updateDesireToOpenABranchingModal(question.content.id)
}}
sx={{
height: "30px",

@ -1,8 +1,10 @@
import { AnyTypedQuizQuestion, UntypedQuizQuestion } from "@model/questionTypes/shared";
import { Box, ListItem, Typography, useTheme } from "@mui/material";
import { memo } from "react";
import { memo, useEffect } from "react";
import { Draggable } from "react-beautiful-dnd";
import QuestionsPageCard from "./QuestionPageCard";
import { updateEditSomeQuestion } from "@root/questions/actions"
import { useQuestionsStore } from "@root/questions/store"
type Props = {
@ -13,6 +15,23 @@ type Props = {
function DraggableListItem({ question, isDragging, index }: Props) {
const theme = useTheme();
const { editSomeQuestion } = useQuestionsStore()
useEffect(() => {
if (editSomeQuestion !== null) {
const setI = setInterval(() => {
let comp = document.getElementById(editSomeQuestion)
console.log(comp)
if(comp !== null) {
clearInterval(setI)
comp.scrollIntoView({behavior: 'instant'})
updateEditSomeQuestion()
}
}, 200)
}
console.log(editSomeQuestion)
}, [editSomeQuestion])
return (
<Draggable draggableId={question.id.toString()} index={index}>

@ -68,6 +68,7 @@ export default function QuestionsPageCard({ question, draggableProps, isDragging
return (
<>
<Paper
id={question.id}
data-cy="quiz-question-card"
sx={{
maxWidth: "796px",

@ -1,26 +1,26 @@
import { Box } from "@mui/material";
import { reorderQuestions } from "@root/questions/actions";
import { useQuestions } from "@root/questions/hooks";
import type { DropResult } from "react-beautiful-dnd";
import { DragDropContext, Droppable } from "react-beautiful-dnd";
import DraggableListItem from "./DraggableListItem";
import { useQuestionsStore } from "@root/questions/store";
export const DraggableList = () => {
const { questions, isLoading } = useQuestions();
const { questions } = useQuestionsStore()
const filteredQuestions = questions.filter((question) => question.type !== "result")
console.log(questions)
console.log(filteredQuestions)
const onDragEnd = ({ destination, source }: DropResult) => {
if (destination) reorderQuestions(source.index, destination.index);
};
if (isLoading && !questions) return <Box>Загрузка вопросов...</Box>;
return (
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="droppable-list">
{(provided, snapshot) => (
<Box ref={provided.innerRef} {...provided.droppableProps}>
{questions.map((question, index) => (
{filteredQuestions.map((question, index) => (
<DraggableListItem
key={question.id}
question={question}

@ -3,21 +3,20 @@ import {
Box,
} from "@mui/material";
import { DraggableList } from "./DraggableList";
import { BranchingPanel } from "./BranchingPanel";
import { SwitchBranchingPanel } from "./SwitchBranchingPanel";
import { BranchingMap } from "./BranchingMap";
import { updateOpenBranchingPanel } from "@root/questions/actions";
import {useQuestionsStore} from "@root/questions/store";
export const QuestionSwitchWindowTool = () => {
const {openBranchingPanel} = useQuestionsStore.getState()
console.log(openBranchingPanel)
return (
<Box sx={{ display: "flex", gap: "20px", flexWrap: "wrap" }}>
<Box sx={{ flexBasis: "796px" }}>
{openBranchingPanel? <BranchingMap /> : <DraggableList />}
</Box>
<BranchingPanel
<SwitchBranchingPanel
/>
</Box>
)

@ -1,4 +1,4 @@
import { useState, useEffect, useLayoutEffect } from "react"
import { useState, useEffect, useLayoutEffect, useRef } from "react"
import {
Box,
Button,
@ -17,25 +17,29 @@ import ArrowLeft from "../../assets/icons/questionsPage/arrowLeft";
import BranchingQuestions from "./BranchingModal/BranchingQuestionsModal"
import { QuestionSwitchWindowTool } from "./QuestionSwitchWindowTool";
import { useQuestionsStore } from "@root/questions/store";
import { updateOpenBranchingPanel } from "@root/questions/actions";
import { updateOpenBranchingPanel, updateEditSomeQuestion } from "@root/questions/actions";
export default function QuestionsPage() {
const theme = useTheme();
const { openedModalSettingsId } = useQuestionsStore();
const { openedModalSettingsId, openBranchingPanel } = useQuestionsStore();
const isMobile = useMediaQuery(theme.breakpoints.down(660));
const quiz = useCurrentQuiz();
const {openBranchingPanel} = useQuestionsStore.getState()
console.log(quiz)
useLayoutEffect(() => {
updateOpenBranchingPanel(true)
updateOpenBranchingPanel(false)
updateEditSomeQuestion()
},[])
const ref = useRef()
if (!quiz) return null;
return (
<>
<Box
ref={ref}
id="QuestionsPage"
sx={{
maxWidth: "796px",
width: "100%",
@ -44,7 +48,8 @@ export default function QuestionsPage() {
margin: "60px 0 40px 0",
}}
>
<Typography variant={"h5"}>Заголовок квиза</Typography>
<Typography variant={"h5"}>{
quiz.name ? quiz.name : "Заголовок квиза" }</Typography>
<Button
sx={{
display: openBranchingPanel ? "none" : "flex",
@ -81,6 +86,8 @@ export default function QuestionsPage() {
>
<AddPlus />
</IconButton>
<Box sx={{ display: "flex", gap: "8px", marginLeft: "auto" }}>
<Button
variant="outlined"
@ -109,4 +116,4 @@ export default function QuestionsPage() {
{openedModalSettingsId !== null && <BranchingQuestions/>}
</>
);
}
}

@ -1,13 +1,15 @@
import { useParams } from "react-router-dom";
import { Box, Button, Typography } from "@mui/material";
import { Box, Button, IconButton, Typography } from "@mui/material";
import { ReactComponent as CheckedIcon } from "@icons/checked.svg";
import { useQuestionsStore } from "@root/questions/store";
import { updateDragQuestionContentId } from "@root/questions/actions";
import { useEffect } from "react";
import { AnyTypedQuizQuestion, UntypedQuizQuestion } from "@model/questionTypes/shared";
import { Pencil } from "../../startPage/Sidebar/icons/Pencil";
import {updateOpenBranchingPanel, updateEditSomeQuestion} from "@root/questions/actions"
const getItemStyle = (isDragging:any, draggableStyle:any) => ({
const getItemStyle = (isDragging: any, draggableStyle: any) => ({
// some basic styles to make the items look a bit nicer
userSelect: "none",
padding: 5 * 2,
@ -22,7 +24,9 @@ const getItemStyle = (isDragging:any, draggableStyle:any) => ({
type AnyQuestion = UntypedQuizQuestion | AnyTypedQuizQuestion
export const QuestionsList = () => {
const { questions } = useQuestionsStore()
const { desireToOpenABranchingModal } = useQuestionsStore()
const trashQuestions = useQuestionsStore().questions
const questions = trashQuestions.filter((question) => question.type !== "result")
return (
<Box sx={{ padding: "15px" }}>
@ -51,11 +55,11 @@ export const QuestionsList = () => {
}}
>
{/* тут нужно будет фильтровать с проверкой, что вопрос имеет тип*/}
{questions.filter((q:AnyQuestion) => q.type).map(({ title, id, content }, index) => (
<Button
onMouseDown={() => {//Разрешаем добавить этот вопрос если у него нет родителя (не добавляли ещё в дерево)
if (!content.rule.parentId) updateDragQuestionContentId(content.id)
}}
{questions.filter((q: AnyQuestion) => q.type).map(({ title, content }, index) => (
<Button
onMouseDown={() => {//Разрешаем добавить этот вопрос если у него нет родителя (не добавляли ещё в дерево)
if (!content.rule.parentId) updateDragQuestionContentId(content.id)
}}
key={index}
sx={{
width: "100%",
@ -66,6 +70,7 @@ export const QuestionsList = () => {
padding: "12px",
background: "#FFFFFF",
borderRadius: "8px",
border: desireToOpenABranchingModal === content.id ? "4px solid #7e2aea" : "none",
marginBottom: "20px",
boxShadow: "0px 10px 30px #e7e7e7",
backgroundImage: `url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='8' ry='8' stroke='rgb(154, 154, 175)' stroke-width='2' stroke-dasharray='8 8' stroke-dashoffset='0' stroke-linecap='square'/%3e%3c/svg%3e");
@ -73,13 +78,21 @@ export const QuestionsList = () => {
"&:last-child": { marginBottom: 0 },
}}
>
<Typography sx={{ width: "100%", color: content.rule.parentId ? "#9A9AAF" : "#000" }}>
<Typography sx={{ width: "100%", color: content.rule.parentId ? "#9A9AAF" : "#000" }}>
{title || "нет заголовка"}
</Typography>
<IconButton
onClick={() => {
updateOpenBranchingPanel(false)
updateEditSomeQuestion(content.id)
}}
>
<Pencil style={{color: "#7e2aea"}}/>
</IconButton>
{content.rule.parentId && <CheckedIcon />}
</Button>
))}
</Box>
</Box>

@ -0,0 +1,91 @@
import {Box, Typography, Switch, useTheme, Button, useMediaQuery} from "@mui/material";
import { QuestionsList } from "./QuestionsList";
import { updateOpenBranchingPanel } from "@root/questions/actions";
import {useQuestionsStore} from "@root/questions/store";
import {useRef} from "react";
export const SwitchBranchingPanel = () => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(660));
const {openBranchingPanel} = useQuestionsStore.getState()
console.log(openBranchingPanel)
const ref = useRef()
return (
<Box sx={{ userSelect: "none", maxWidth: "350px", width: "100%" }}>
<Box
sx={{
display: "flex",
alignItems: "center",
gap: "15px",
padding: "18px",
background: "#fff",
borderRadius: "12px",
boxShadow: "0px 10px 30px #e7e7e7",
}}
>
<Switch
value={openBranchingPanel}
onChange={(_, value) => {
console.log("меняю на " + value)
updateOpenBranchingPanel(value)
}}
sx={{
width: 50,
height: 30,
padding: 0,
"& .MuiSwitch-switchBase": {
padding: 0,
margin: "2px",
transitionDuration: "300ms",
"&.Mui-checked": {
transform: "translateX(20px)",
color: theme.palette.brightPurple.main,
"& + .MuiSwitch-track": {
backgroundColor: "#E8DCF9",
opacity: 1,
border: 0,
},
"&.Mui-disabled + .MuiSwitch-track": { opacity: 0.5 },
},
"&.Mui-disabled .MuiSwitch-thumb": {
color:
theme.palette.mode === "light"
? theme.palette.grey[100]
: theme.palette.grey[600],
},
"&.Mui-disabled + .MuiSwitch-track": {
opacity: theme.palette.mode === "light" ? 0.7 : 0.3,
},
},
"& .MuiSwitch-thumb": {
boxSizing: "border-box",
width: 25,
height: 25,
},
"& .MuiSwitch-track": {
borderRadius: 13,
backgroundColor:
theme.palette.mode === "light" ? "#E9E9EA" : "#39393D",
opacity: 1,
transition: theme.transitions.create(["background-color"], {
duration: 500,
}),
},
}}
/>
<Box>
<Typography ref={ref} sx={{ fontWeight: "bold", color: "#4D4D4D" }}>
Логика ветвления
</Typography>
<Typography sx={{ color: "#4D4D4D", fontSize: "12px" }}>
Настройте связи между вопросами
</Typography>
</Box>
</Box>
{ openBranchingPanel && <QuestionsList /> }
</Box>
);
};

@ -1,76 +0,0 @@
import { Box, Typography, useTheme, useMediaQuery } from "@mui/material";
type Props = {
text: string;
text2: string;
image: string;
};
export default function CreationFullCard({ text, text2, image }: Props) {
const theme = useTheme();
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1500));
return (
<Box
sx={{
flexGrow: 1,
backgroundColor: "white",
p: "20px",
marginTop: "50px",
borderRadius: "12px",
display: isSmallMonitor ? "block" : "flex",
flexDirection: isSmallMonitor ? "column" : "row",
gap: "20px",
boxShadow: `0px 100px 309px rgba(210, 208, 225, 0.24),
0px 41.7776px 129.093px rgba(210, 208, 225, 0.172525),
0px 22.3363px 69.0192px rgba(210, 208, 225, 0.143066),
0px 12.5216px 38.6916px rgba(210, 208, 225, 0.12),
0px 6.6501px 20.5488px rgba(210, 208, 225, 0.0969343),
0px 2.76726px 8.55082px rgba(210, 208, 225, 0.0674749)`,
}}
>
<Box
sx={{
mr: !isSmallMonitor ? "104px" : 0,
marginBottom: "20px",
position: "relative",
}}
>
<Typography variant="h5" sx={{ marginBottom: "20px" }}>
Результаты квиза в зависимости от ответов
</Typography>
<Box
sx={{
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
height: "100%",
maxHeight: isSmallMonitor ? "none" : "220px",
gap: "25px",
}}
>
<Typography sx={{ color: "#4D4D4D", width: "95%" }}>
{text}
</Typography>
<Typography
sx={{
color: "#9A9AAF",
width: "100%",
}}
>
{text2}
</Typography>
</Box>
</Box>
<img
src={image}
alt="quiz creation"
style={{
display: "block",
width: isSmallMonitor ? "100%" : "auto",
maxHeight: isSmallMonitor ? "none" : "270px",
}}
/>
</Box>
);
}

@ -39,24 +39,6 @@ const priceButtonsArray: { title: string; type: string; sx: SxProps<Theme> }[] =
whiteSpace: "nowrap",
},
},
{
title: "ƒ",
type: "ƒ",
sx: {
width: "38px",
height: "48px",
border: "1px solid #9A9AAF",
},
},
{
title: "Скидка",
type: "discount",
sx: {
width: "93px",
height: "48px",
border: "1px solid #9A9AAF",
},
},
];
type Props = {
@ -74,9 +56,6 @@ export default function PriceButtons({
<Typography component={"h6"} sx={{ weight: "500", fontSize: "18px" }}>
Стоимость
</Typography>
<IconButton sx={{ borderRadius: "6px", padding: "2px" }}>
<DeleteIcon style={{ color: "#4D4D4D" }} />
</IconButton>
</Box>
<Box
component="div"

@ -0,0 +1,112 @@
import { ResultSettings } from "./ResultSettings"
import { createFrontResult } from "@root/questions/actions";
import { useQuestionsStore } from "@root/questions/store";
import { useCurrentQuiz } from "@root/quizes/hooks";
import { Box, Typography, useTheme, useMediaQuery, Button } from "@mui/material";
import image from "../../assets/Rectangle 110.png";
import { enqueueSnackbar } from "notistack";
export const FirstEntry = () => {
const theme = useTheme();
const quiz = useCurrentQuiz();
const { questions } = useQuestionsStore();
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1250));
const create = () => {
if (quiz?.config.haveRoot) {
if (questions.length === 0) {
enqueueSnackbar("У вас не добавлено ни одного вопроса")
return
}
questions
.filter((question) => question.content.rule.parentId.length !== 0 && question.content.rule.default.length === 0)
.forEach(question => {
createFrontResult(quiz.id, question.content.id)
})
} else {
createFrontResult(quiz.id)
}
}
return (
<>
<Box
sx={{
flexGrow: 1,
backgroundColor: "white",
p: "20px",
marginTop: "50px",
borderRadius: "12px",
display: isSmallMonitor ? "block" : "flex",
flexDirection: isSmallMonitor ? "column" : "row",
gap: "20px",
boxShadow: `0px 100px 309px rgba(210, 208, 225, 0.24),
0px 41.7776px 129.093px rgba(210, 208, 225, 0.172525),
0px 22.3363px 69.0192px rgba(210, 208, 225, 0.143066),
0px 12.5216px 38.6916px rgba(210, 208, 225, 0.12),
0px 6.6501px 20.5488px rgba(210, 208, 225, 0.0969343),
0px 2.76726px 8.55082px rgba(210, 208, 225, 0.0674749)`,
}}
>
<Box
sx={{
mr: !isSmallMonitor ? "104px" : 0,
marginBottom: isSmallMonitor ? "20px" : 0,
position: "relative",
height: "100%"
}}
>
<Typography variant="h5" sx={{ marginBottom: "20px" }}>
Результаты квиза в зависимости от ответов
</Typography>
<Box
sx={{
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
height: "100%",
gap: "25px",
}}
>
<Typography sx={{ color: "#4D4D4D", width: "95%" }}>
Вы можете показывать разные результаты квиза (добавьте описание, изображение, стоимость и т.п.) разным пользователям, нужно только их создать и проставить условия. Таким образом ваш квиз получится максимально индивидуальным для каждого клиента. Показывайте картинку/видео вместо результата или переадресовывайте пользователя по нужной ссылке.
</Typography>
<Typography
sx={{
color: "#9A9AAF",
width: "100%",
}}
>
Этот шаг - необязательный, квиз будет работать и без автоматических результатов.
</Typography>
</Box>
</Box>
<img
src={image}
alt="quiz creation"
style={{
display: "block",
width: isSmallMonitor ? "100%" : "auto",
maxHeight: isSmallMonitor ? "none" : "270px",
}}
/>
</Box>
<Button
onClick={create}
variant="contained"
sx={{
backgroundColor: "#7E2AEA",
fontSize: "18px",
lineHeight: "18px",
width: "216px",
height: "44px",
mt: "30px",
p: "10px 20px"
}}
>
Создать результаты
</Button>
</>
);
}

@ -3,7 +3,7 @@ import { updateQuiz } from "@root/quizes/actions";
import { useCurrentQuiz } from "@root/quizes/hooks";
import image from "../../assets/Rectangle 110.png";
import Info from "../../assets/icons/Info";
import CreationFullCard from "./CreationFullCard";
// import CreationFullCard from "./FirstEntry";
export const Result = () => {
@ -13,11 +13,11 @@ export const Result = () => {
return (
<Box component="section">
<CreationFullCard
{/* <CreationFullCard
text="Вы можете показывать разные результаты квиза (добавьте описание, изображение, стоимость и т.п.) разным пользователям, нужно только их создать и проставить условия. Таким образом ваш квиз получится максимально индивидуальным для каждого клиента. Показывайте картинку/видео вместо результата или переадресовывайте пользователя по нужной ссылке."
text2="Этот шаг - необязательный, квиз будет работать и без автоматических результатов."
image={image}
/>
/> */}
<Box sx={{ display: "flex", mt: "30px", alignItems: "center" }}>
<Button
variant="contained"

@ -0,0 +1,15 @@
import { useQuestionsStore } from "@root/questions/store";
import { FirstEntry } from "./FirstEntry"
import { ResultSettings } from "./ResultSettings"
export const ResultPage = () => {
const { questions } = useQuestionsStore();
//ищём хотя бы один result
const haveResult = questions.some((question) => question.type === "result")
return (
haveResult ?
<ResultSettings />
:
<FirstEntry />
);
}

@ -2,19 +2,31 @@ import IconPlus from "@icons/IconPlus";
import Info from "@icons/Info";
import Plus from "@icons/Plus";
import ArrowLeft from "@icons/questionsPage/arrowLeft";
import { Box, Button, Typography } from "@mui/material";
import { Box, Button, Typography, Paper, FormControl, TextField } from "@mui/material";
import { incrementCurrentStep } from "@root/quizes/actions";
import CustomWrapper from "@ui_kit/CustomWrapper";
import { DescriptionForm } from "./DescriptionForm/DescriptionForm";
import { ResultListForm } from "./ResultListForm";
import { SettingForm } from "./SettingForm";
import { useState } from "react";
import { WhenCard } from "./cards/WhenCard";
import { ResultCard } from "./cards/ResultCard";
import { EmailSettingsCard } from "./cards/EmailSettingsCard";
import { useCurrentQuiz } from "@root/quizes/hooks"
export const ResultSettings = () => {
const quiz = useCurrentQuiz()
const [quizExpand, setQuizExpand] = useState(true)
const [resultContract, setResultContract] = useState(true)
export const Setting = () => {
return (
<Box sx={{ maxWidth: "796px" }}>
<Box sx={{ display: "flex", alignItems: "center", mb: "40px" }}>
<Typography sx={{ pr: "10px" }} variant="h5">
<Box sx={{
display: "flex",
alignItems: "center",
margin: "60px 0 40px 0",
}}>
<Typography variant="h5">
Настройки результатов
</Typography>
<Info />
@ -33,12 +45,17 @@ export const Setting = () => {
},
}}
variant="text"
onClick={() => setQuizExpand(!quizExpand)}
>
Свернуть
</Button>
</Box>
<CustomWrapper sx={{ mt: "30px" }} text="Показывать результат" />
<CustomWrapper sx={{ mt: "30px" }} text="Настройки почты" />
<WhenCard quizExpand={quizExpand} />
{quiz.config.resultInfo.when === "email" && <EmailSettingsCard quizExpand={quizExpand} />}
<Box
sx={{ display: "flex", alignItems: "center", mb: "15px", mt: "15px" }}
>
@ -60,52 +77,14 @@ export const Setting = () => {
},
}}
variant="text"
onClick={() => setResultContract(!resultContract)}
>
Развернуть все
</Button>
</Box>
<CustomWrapper result={true} text="Показывать результат" />
<Box
sx={{
display: "flex",
width: "100%",
alignItems: "center",
columnGap: "10px",
}}
>
<Box
sx={{
boxSizing: "border-box",
width: "100%",
height: "1px",
backgroundPosition: "bottom",
backgroundRepeat: "repeat-x",
backgroundSize: "20px 1px",
backgroundImage:
"radial-gradient(circle, #7E2AEA 6px, #F2F3F7 1px)",
}}
/>
<IconPlus />
</Box>
<CustomWrapper result={true} text="Настройки почты" />
<Box sx={{ pt: "30px", display: "flex", alignItems: "center" }}>
<Plus />
<Typography component="div" sx={{ ml: "auto" }}>
<Button
variant="outlined"
sx={{ padding: "10px 20px", borderRadius: "8px" }}
>
<ArrowLeft />
</Button>
<Button variant="contained" sx={{ ml: "10px" }} onClick={incrementCurrentStep}>
Настроить форму
</Button>
</Typography>
</Box>
<SettingForm />
<ResultListForm />
<DescriptionForm />
<ResultCard resultContract={resultContract} />
</Box>
);
};

@ -5,10 +5,11 @@ import * as React from "react";
interface Props {
text: string;
icon: string;
onClick?: () => void;
onClick?: (a:any) => void;
value: boolean
}
export const SwitchSetting = ({ text, icon, onClick }: Props) => {
export const SwitchSetting = ({ text, icon, onClick, value }: Props) => {
return (
<Box
sx={{
@ -24,7 +25,7 @@ export const SwitchSetting = ({ text, icon, onClick }: Props) => {
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "left", maxWidth: "756px", width: "100%" }}>
<img src={icon} alt="icon" />
<FormControlLabel
value="start"
checked={value}
control={<CustomizedSwitch />}
label={text}
labelPlacement="start"

@ -0,0 +1,251 @@
import { useEffect, useState } from "react";
import { useDebouncedCallback } from "use-debounce";
import { useCurrentQuiz } from "@root/quizes/hooks"
import {
Box,
TextField,
IconButton,
Paper,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import ExpandLessIconBG from "@icons/ExpandLessIconBG";
import ExpandLessIcon from "@mui/icons-material/ExpandLess";
import { updateQuiz } from "@root/quizes/actions";
interface Props {
quizExpand: boolean
}
export const EmailSettingsCard = ({ quizExpand }: Props) => {
const quiz = useCurrentQuiz()
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(790));
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1100));
const [expand, setExpand] = useState(true)
useEffect(() => {
setExpand(false)
}, [quizExpand])
const debouncedCallback = useDebouncedCallback((callback) => {
callback();
}, 200);
return (
<Paper
data-cy="quiz-question-card"
sx={{
maxWidth: "796px",
width: "100%",
borderRadius: "12px",
backgroundColor: expand ? "white" : "#EEE4FC",
border: expand ? "none" : "1px solid #9A9AAF",
boxShadow: "0px 10px 30px #e7e7e7",
m: "20px 0"
}}
>
<Box
sx={{
display: "flex",
alignItems: "center",
padding: expand ? isMobile ? "10px 10px 0 10px" : "20px 20px 0 20px" : isMobile ? "10px" : "20px",
flexDirection: isMobile ? "column" : null,
justifyContent: "space-between",
minHeight: "40px",
}}
>
<Typography
sx={{
margin: isMobile ? "10px 0" : 0,
color: expand ? "#9A9AAF" : "#000000",
}}
>
Настройки почты
</Typography>
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
width: isMobile ? "100%" : "auto",
position: "relative",
}}
>
<IconButton
sx={{ padding: "0", margin: "5px" }}
disableRipple
data-cy="expand-question"
onClick={() => setExpand(!expand)}
>
{expand ? (
<ExpandLessIconBG />
) : (
<ExpandLessIcon
sx={{
boxSizing: "border-box",
fill: theme.palette.brightPurple.main,
background: "#FFF",
borderRadius: "6px",
height: "30px",
width: "30px",
}}
/>
)}
</IconButton>
</Box>
</Box>
{expand && (
<Box
sx={{
p: "0 20px 20px 20px",
}}
>
<Typography
sx={{
color: "#4D4D4D",
m: "20px 0 14px 0",
fontSize: "18px",
fontWeight: 500
}}
>
Тема письма
</Typography>
<TextField
value={quiz.config.resultInfo.theme}
placeholder={"Заголовок вопроса"}
onChange={({ target }: { target: HTMLInputElement }) => {debouncedCallback(updateQuiz(quiz.id, (quiz) => {
quiz.config.resultInfo.theme = target.value
})) }}
sx={{
margin: isMobile ? "10px 0" : 0,
width:"100%",
"& .MuiInputBase-root": {
color: "#000000",
backgroundColor: expand
? theme.palette.background.default
: "transparent",
height: "48px",
borderRadius: "10px",
".MuiOutlinedInput-notchedOutline": {
borderWidth: "1px !important",
border: expand ? "none" : null,
},
"& .MuiInputBase-input::placeholder": {
color: "#4D4D4D",
opacity: 0.8,
},
},
}}
inputProps={{
sx: {
fontSize: "18px",
lineHeight: "21px",
py: 0,
paddingLeft: "18px",
},
}}
/>
<Typography
sx={{
color: "#4D4D4D",
m: "20px 0 14px 0",
fontSize: "18px",
fontWeight: 500
}}
>
E-mail ответа
</Typography>
<TextField
value={quiz.config.resultInfo.reply}
placeholder={"noreplay@example.ru"}
onChange={({ target }: { target: HTMLInputElement }) => {debouncedCallback(updateQuiz(quiz.id, (quiz) => {
quiz.config.resultInfo.reply = target.value
})) }}
sx={{
margin: isMobile ? "10px 0" : 0,
width:"100%",
"& .MuiInputBase-root": {
color: "#000000",
backgroundColor: expand
? theme.palette.background.default
: "transparent",
height: "48px",
borderRadius: "10px",
".MuiOutlinedInput-notchedOutline": {
borderWidth: "1px !important",
border: expand ? "none" : null,
},
"& .MuiInputBase-input::placeholder": {
color: "#4D4D4D",
opacity: 0.8,
},
},
}}
inputProps={{
sx: {
fontSize: "18px",
lineHeight: "21px",
py: 0,
paddingLeft: "18px",
},
}}
/>
<Typography
sx={{
color: "#4D4D4D",
m: "20px 0 14px 0",
fontSize: "18px",
fontWeight: 500
}}
>
Имя отправителя
</Typography>
<TextField
value={quiz.config.resultInfo.replname}
placeholder={"Название компании"}
onChange={({ target }: { target: HTMLInputElement }) => {debouncedCallback(updateQuiz(quiz.id, (quiz) => {
quiz.config.resultInfo.replname = target.value
})) }}
sx={{
margin: isMobile ? "10px 0" : 0,
width:"100%",
"& .MuiInputBase-root": {
color: "#000000",
backgroundColor: expand
? theme.palette.background.default
: "transparent",
height: "48px",
borderRadius: "10px",
".MuiOutlinedInput-notchedOutline": {
borderWidth: "1px !important",
border: expand ? "none" : null,
},
"& .MuiInputBase-input::placeholder": {
color: "#4D4D4D",
opacity: 0.8,
},
},
}}
inputProps={{
sx: {
fontSize: "18px",
lineHeight: "21px",
py: 0,
paddingLeft: "18px",
},
}}
/>
</Box>
)}
</Paper>
)
}

@ -0,0 +1,291 @@
import { useEffect, useState } from "react";
import { updateQuiz } from "@root/quizes/actions"
import { useCurrentQuiz } from "@root/quizes/hooks"
import { SwitchSetting } from "../SwichResult";
import {
Box,
IconButton,
Paper,
Button,
Typography,
TextField,
useMediaQuery,
useTheme,
} from "@mui/material";
import ExpandLessIconBG from "@icons/ExpandLessIconBG";
import ExpandLessIcon from "@mui/icons-material/ExpandLess";
import ShareNetwork from "@icons/ShareNetwork.svg";
import ArrowCounterClockWise from "@icons/ArrowCounterClockWise.svg";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import SwitchResult from "../DescriptionForm/SwitchResult";
import ButtonsOptionsForm from "../DescriptionForm/ButtinsOptionsForm";
import PriceButtons from "../DescriptionForm/PriceButton";
import DiscountButtons from "../DescriptionForm/DiscountButtons";
import CustomTextField from "@ui_kit/CustomTextField";
import { OneIcon } from "@icons/questionsPage/OneIcon";
import { DeleteIcon } from "@icons/questionsPage/deleteIcon";
import { PointsIcon } from "@icons/questionsPage/PointsIcon";
import Info from "@icons/Info";
import ImageAndVideoButtons from "../DescriptionForm/ImageAndVideoButtons";
interface Props {
resultContract: boolean;
}
export const ResultCard = ({ resultContract }:Props) => {
const quiz = useCurrentQuiz()
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(790));
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1100));
const [expand, setExpand] = useState(true)
useEffect(() => {
setExpand(true)
}, [resultContract])
const [switchState, setSwitchState] = useState<string>("");
const [priceButtonsActive, setPriceButtonsActive] = useState<number>(0);
const [priceButtonsType, setPriceButtonsType] = useState<string>();
const [forwarding, setForwarding] = useState<boolean>(false);
const buttonsActive = (index: number, type: string) => {
setPriceButtonsActive(index);
setPriceButtonsType(type);
};
const SSHC = (data: string) => {
setSwitchState(data);
};
return(
<Paper
data-cy="quiz-question-card"
sx={{
maxWidth: "796px",
width: "100%",
borderRadius: "12px",
backgroundColor: expand ? "white" : "#EEE4FC",
border: expand ? "none" : "1px solid #9A9AAF",
boxShadow: "0px 10px 30px #e7e7e7",
m: "20px 0"
}}
>
<Box
sx={{
display: "flex",
alignItems: "center",
padding: isMobile ? "10px" : "20px",
flexDirection: isMobile ? "column" : null,
justifyContent: "space-between",
minHeight: "40px",
}}
>
<Typography
sx={{
margin: isMobile ? "10px 0" : 0,
color: expand ? "#9A9AAF" : "#000000",
}}
>
Заголовок результата
</Typography>
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
width: isMobile ? "100%" : "auto",
position: "relative",
}}
>
<IconButton
sx={{ padding: "0", margin: "5px" }}
disableRipple
data-cy="expand-question"
onClick={() => setExpand(!expand)}
>
{expand ? (
<ExpandLessIconBG />
) : (
<ExpandLessIcon
sx={{
boxSizing: "border-box",
fill: theme.palette.brightPurple.main,
background: "#FFF",
borderRadius: "6px",
height: "30px",
width: "30px",
}}
/>
)}
</IconButton>
</Box>
</Box>
{expand && (
<Box
sx={{
overflow: "hidden",
maxWidth: "796px",
height: "100%",
bgcolor: "#FFFFFF",
borderRadius: "12px",
boxShadow: "0px 10px 30px #e7e7e7",
}}
>
<Box sx={{ p: "0 20px", pt: "30px" }}>
<Box
sx={{
width: "100%",
maxWidth: "760px",
display: "flex",
alignItems: "center",
gap: "10px",
mb: "19px",
}}
>
<CustomTextField placeholder="Заголовок вопроса" text={""} />
<IconButton>
<ExpandMoreIcon />
</IconButton>
<OneIcon />
<PointsIcon style={{ color: "#9A9AAF" }} />
</Box>
<Box sx={{ display: "flex" }}>
<PriceButtons
ButtonsActive={buttonsActive}
priceButtonsActive={priceButtonsActive}
/>
</Box>
<TextField
fullWidth
placeholder="Описание"
sx={{
"& .MuiInputBase-root": {
backgroundColor: "#F2F3F7",
width: "100%",
height: "110px",
borderRadius: "10px",
},
}}
inputProps={{
sx: {
borderRadius: "10px",
fontSize: "18px",
lineHeight: "21px",
py: 0,
},
}}
/>
<ImageAndVideoButtons />
{priceButtonsType === "smooth" ? (
<Box sx={{ mb: "20px" }}>
<Box sx={{ display: "flex", alignItems: "center", mb: "14xp" }}>
<Typography
component={"h6"}
sx={{ weight: "500", fontSize: "18px" }}
>
Призыв к действию
</Typography>
<IconButton sx={{ borderRadius: "6px", padding: "2px" }}>
<DeleteIcon style={{ color: "#4D4D4D" }} />
</IconButton>
</Box>
<Box sx={{ display: "flex" }}>
<TextField
placeholder="Узнать подробнее"
fullWidth
sx={{
width: "410px",
"& .MuiInputBase-root": {
backgroundColor: "#F2F3F7",
width: "410px",
height: "48px",
borderRadius: "10px",
},
}}
inputProps={{
sx: {
borderRadius: "10px",
fontSize: "18px",
lineHeight: "21px",
py: 0,
},
}}
/>
<Button
onClick={() => setForwarding(true)}
variant="outlined"
sx={{
display: forwarding ? "none" : "",
ml: "20px",
mb: "20px",
}}
>
Переадресация +
</Button>
{forwarding ? (
<Box sx={{ ml: "20px", mt: "-36px" }}>
<Box
sx={{ display: "flex", alignItems: "center", mb: "14xp" }}
>
<Typography
component={"h6"}
sx={{ weight: "500", fontSize: "18px" }}
>
Переадресация
</Typography>
<IconButton sx={{ borderRadius: "6px", padding: "2px" }}>
<DeleteIcon style={{ color: "#4D4D4D" }} />
</IconButton>
<Info />
</Box>
<Box>
<TextField
placeholder="https://exemple.ru"
fullWidth
sx={{
"& .MuiInputBase-root": {
backgroundColor: "#F2F3F7",
width: "326px",
height: "48px",
borderRadius: "10px",
},
}}
inputProps={{
sx: {
borderRadius: "10px",
fontSize: "18px",
lineHeight: "21px",
py: 0,
},
}}
/>
</Box>
</Box>
) : (
<></>
)}
</Box>
</Box>
) : (
<Button variant="outlined" sx={{ mb: "20px" }}>
Кнопка +
</Button>
)}
</Box>
<ButtonsOptionsForm switchState={switchState} SSHC={SSHC} />
<SwitchResult switchState={switchState} totalIndex={0} />
</Box>
)}
</Paper>
)
}

@ -0,0 +1,187 @@
import { useEffect, useState } from "react";
import { updateQuiz } from "@root/quizes/actions"
import { useCurrentQuiz } from "@root/quizes/hooks"
import { SwitchSetting } from "../SwichResult";
import {
Box,
IconButton,
Paper,
Button,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import ExpandLessIconBG from "@icons/ExpandLessIconBG";
import ExpandLessIcon from "@mui/icons-material/ExpandLess";
import ShareNetwork from "@icons/ShareNetwork.svg";
import ArrowCounterClockWise from "@icons/ArrowCounterClockWise.svg";
const whenValues = [
{
title: "До формы контактов",
value: "before",
},
{
title: "После формы контактов",
value: "after",
},
{
title: "Отправить на E-mail",
value: "email",
},
];
interface Props {
quizExpand: boolean
}
export const WhenCard = ({ quizExpand }: Props) => {
const quiz = useCurrentQuiz()
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(790));
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1100));
const [expand, setExpand] = useState(true)
useEffect(() => {
setExpand(false)
}, [quizExpand])
return (
<Paper
data-cy="quiz-question-card"
sx={{
maxWidth: "796px",
width: "100%",
borderRadius: "12px",
backgroundColor: expand ? "white" : "#EEE4FC",
border: expand ? "none" : "1px solid #9A9AAF",
boxShadow: "0px 10px 30px #e7e7e7",
m: "20px 0"
}}
>
<Box
sx={{
display: "flex",
alignItems: "center",
padding: isMobile ? "10px" : "20px",
flexDirection: isMobile ? "column" : null,
justifyContent: "space-between",
minHeight: "40px",
}}
>
<Typography
sx={{
margin: isMobile ? "10px 0" : 0,
color: expand ? "#9A9AAF" : "#000000",
}}
>
Показывать результат
</Typography>
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
width: isMobile ? "100%" : "auto",
position: "relative",
}}
>
<IconButton
sx={{ padding: "0", margin: "5px" }}
disableRipple
data-cy="expand-question"
onClick={() => setExpand(!expand)}
>
{expand ? (
<ExpandLessIconBG />
) : (
<ExpandLessIcon
sx={{
boxSizing: "border-box",
fill: theme.palette.brightPurple.main,
background: "#FFF",
borderRadius: "6px",
height: "30px",
width: "30px",
}}
/>
)}
</IconButton>
</Box>
</Box>
{expand && (
<>
<Box
sx={{
p: "33px 20px",
}}
>
<Box
sx={{
display: "flex",
flexDirection: isSmallMonitor ? "column" : "row",
justifyContent: "space-between",
gap: "20px",
mb: "20px",
}}
>
{whenValues.map(({ title, value }, index) => (
<Button
onClick={() => updateQuiz(quiz.id, (quiz) => quiz.config.resultInfo.when = value)}
key={title}
sx={{
bgcolor: quiz?.config.resultInfo.when === value ? " #7E2AEA" : "#F2F3F7",
color: quiz?.config.resultInfo.when === value ? " white" : "#9A9AAF",
minWidth: isSmallMonitor ? "300px" : "auto",
borderRadius: "8px",
width: "237px",
height: "44px",
border: quiz?.config.resultInfo.when === value ? "none" : "1px solid #9A9AAF",
"&:hover": {
backgroundColor: quiz?.config.resultInfo.when === value ? "#581CA7" : "#7E2AEA",
color: "white"
}
}}
>
{title}
</Button>
))}
</Box>
{
(quiz?.config.resultInfo.when !== "email") && <SwitchSetting
icon={ShareNetwork}
text="Поделиться результатами"
onClick={(event) => updateQuiz(quiz.id, (quiz) => quiz.config.resultInfo.share = event.target.checked)}
value={quiz?.config.resultInfo.share}
/>
}
{
quiz?.config.resultInfo.when === "before" && <SwitchSetting
icon={ArrowCounterClockWise}
text="Кнопка `Пройти тест заново`"
onClick={(event) => updateQuiz(quiz.id, (quiz) => quiz.config.resultInfo.replay = event.target.checked)}
value={quiz?.config.resultInfo.replay}
/>
}
</Box>
</>
)}
</Paper>
)
}

@ -19,13 +19,69 @@ export const Footer = ({ setCurrentQuestion, question }: FooterProps) => {
const [disabledQuestionsId, setDisabledQuestionsId] = useState<Set<string>>(
new Set()
);
const [disablePreviousButton, setDisablePreviousButton] =
useState<boolean>(false);
const [disableNextButton, setDisableNextButton] = useState<boolean>(false);
const { answers } = useQuizViewStore();
const theme = useTheme();
useEffect(() => {
// Логика для аргумента disabled у кнопки "Назад"
if (question?.content.rule.parentId === "root") {
setDisablePreviousButton(true);
} else {
setDisablePreviousButton(false);
}
// Логика для аргумента disabled у кнопки "Далее"
const nextQuestionId = getNextQuestionId();
if (nextQuestionId) {
setDisableNextButton(false);
} else {
const nextQuestion = getQuestionByContentId(
question.content.rule.default
);
if (nextQuestion?.type) {
setDisableNextButton(false);
} else {
setDisableNextButton(true);
}
}
}, [question]);
const getNextQuestionId = () => {
if (answers.length) {
const answer = answers.find(
({ questionId }) => questionId === question.content.id
);
let readyBeNextQuestion = "";
question.content.rule.main.forEach(({ next, rules }) => {
let longerArray = Math.max(
rules[0].answers.length,
[answer?.answer].length
);
for (
var i = 0;
i < longerArray;
i++ // Цикл по всем эле­мен­там бОльшего массива
) {
if (rules[0].answers[i] === answer?.answer) {
readyBeNextQuestion = next; // Ес­ли хоть один эле­мент от­ли­ча­ет­ся, мас­си­вы не рав­ны
}
}
});
return readyBeNextQuestion;
}
};
const followPreviousStep = () => {
if (question?.content.rule.parentId !== "root") {
const parent = getQuestionByContentId(question?.content.rule.parentId);
if (parent) {
if (parent?.type) {
setCurrentQuestion(parent);
} else {
enqueueSnackbar("не могу получить предыдущий вопрос");
@ -36,45 +92,25 @@ export const Footer = ({ setCurrentQuestion, question }: FooterProps) => {
};
const followNextStep = () => {
if (answers.length) {
let readyBeNextQuestion = "";
const nextQuestionId = getNextQuestionId();
question.content.rule.main.forEach(({ next, rules }) => {
if (nextQuestionId) {
const nextQuestion = getQuestionByContentId(nextQuestionId);
let longerArray = Math.max(
rules[0].answers.length,
[answers.at(-1)?.answer].length
);
for (
var i = 0;
i < longerArray;
i++ // Цикл по всем эле­мен­там бОльшего массива
) {
if (rules[0].answers[i] === answers.at(-1)?.answer) {
readyBeNextQuestion = next; // Ес­ли хоть один эле­мент от­ли­ча­ет­ся, мас­си­вы не рав­ны
}
}
});
if (readyBeNextQuestion) {
const nextQuestion = getQuestionByContentId(readyBeNextQuestion);
if (nextQuestion) {
setCurrentQuestion(nextQuestion);
return;
} else {
enqueueSnackbar("не могу получить последующий вопрос");
}
if (nextQuestion?.type) {
setCurrentQuestion(nextQuestion);
return;
} else {
const nextQuestion = getQuestionByContentId(
question.content.rule.default
);
if (nextQuestion) {
setCurrentQuestion(nextQuestion);
} else {
enqueueSnackbar("не могу получить последующий вопрос (дефолтный)");
}
enqueueSnackbar("не могу получить последующий вопрос");
}
} else {
const nextQuestion = getQuestionByContentId(
question.content.rule.default
);
if (nextQuestion?.type) {
setCurrentQuestion(nextQuestion);
} else {
enqueueSnackbar("не могу получить последующий вопрос (дефолтный)");
}
}
};
@ -128,6 +164,7 @@ export const Footer = ({ setCurrentQuestion, question }: FooterProps) => {
</Typography> */}
</Box>
<Button
disabled={disablePreviousButton}
variant="contained"
sx={{ fontSize: "16px", padding: "10px 15px" }}
onClick={followPreviousStep}
@ -135,6 +172,7 @@ export const Footer = ({ setCurrentQuestion, question }: FooterProps) => {
Назад
</Button>
<Button
disabled={disableNextButton}
variant="contained"
sx={{ fontSize: "16px", padding: "10px 15px" }}
onClick={followNextStep}

@ -55,8 +55,8 @@ export const Question = ({
sx={{
minHeight: "calc(100vh - 75px)",
width: "100%",
maxWidth: "1000px",
padding: "20px 10px 0",
maxWidth: "1440px",
padding: "40px 25px 20px",
margin: "0 auto",
}}
>

@ -13,7 +13,10 @@ type DateProps = {
export const Date = ({ currentQuestion }: DateProps) => {
const { answers } = useQuizViewStore();
const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.content.id) ?? {};
const { answer } =
answers.find(
({ questionId }) => questionId === currentQuestion.content.id
) ?? {};
return (
<Box>
@ -28,7 +31,18 @@ export const Date = ({ currentQuestion }: DateProps) => {
>
<DatePicker
selected={answer ? new window.Date(answer) : new window.Date()}
onChange={(date) => updateAnswer(currentQuestion.content.id, String(date))}
onChange={(date) =>
updateAnswer(
currentQuestion.content.id,
String(
date?.toLocaleDateString("ru-RU", {
year: "numeric",
month: "2-digit",
day: "2-digit",
})
)
)
}
/>
</Box>
</Box>

@ -5,7 +5,7 @@ import {
RadioGroup,
FormControlLabel,
Radio,
useTheme,
useTheme, FormControl,
} from "@mui/material";
import { useQuizViewStore, updateAnswer } from "@root/quizView";
@ -50,31 +50,49 @@ export const Emoji = ({ currentQuestion }: EmojiProps) => {
marginTop: "20px",
}}
>
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
<Box sx={{ display: "flex", width: "100%", gap: "42px" }}>
{currentQuestion.content.variants.map(
({ id, answer, extendedText }, index) => (
<FormControlLabel
key={id}
sx={{
marginBottom: "15px",
borderRadius: "5px",
padding: "15px",
color: theme.palette.grey2.main,
border: `1px solid ${theme.palette.grey2.main}`,
display: "flex",
gap: "10px",
}}
value={index}
control={
<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />
}
label={
<Box sx={{ display: "flex", gap: "10px" }}>
<Typography>{extendedText}</Typography>
<Typography>{answer}</Typography>
<FormControl
key={id}
sx={{
borderRadius: "12px",
border: `1px solid ${theme.palette.grey2.main}`,
overflow: "hidden",
maxWidth: "317px",
width: "100%",
height: "255px"
}}
>
<Box
sx={{ display: "flex", alignItems: "center", height: "193px", background: "#ffffff" }}
>
<Box sx={{ width: "100%", display: "flex", justifyContent: "center" }}>
{extendedText && (
<Typography fontSize={"100px"}>{extendedText}</Typography>
)}
</Box>
</Box>
}
/>
<FormControlLabel
key={id}
sx={{
margin: 0,
padding: "15px",
color: "#4D4D4D",
display: "flex",
gap: "10px",
}}
value={index}
control={
<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />
}
label={
<Box sx={{ display: "flex", gap: "10px" }}>
<Typography>{answer}</Typography>
</Box>
}
/>
</FormControl>
)
)}
</Box>

@ -1,16 +1,15 @@
import { useEffect } from "react";
import {
Box,
Typography,
RadioGroup,
FormControlLabel,
Radio,
useTheme,
useMediaQuery,
Box,
Typography,
RadioGroup,
FormControlLabel,
Radio,
useTheme,
useMediaQuery, FormControl,
} from "@mui/material";
import { useQuizViewStore, updateAnswer } from "@root/quizView";
import RadioCheck from "@ui_kit/RadioCheck";
import RadioIcon from "@ui_kit/RadioIcon";

@ -14,7 +14,8 @@ export const Page = ({ currentQuestion }: PageProps) => {
return (
<Box>
<Typography variant="h5">{currentQuestion.title}</Typography>
<Typography variant="h5" sx={{ paddingBottom: "25px" }}>{currentQuestion.title}</Typography>
<Typography>{currentQuestion.content.text}</Typography>
<Box
sx={{
display: "flex",
@ -24,17 +25,20 @@ export const Page = ({ currentQuestion }: PageProps) => {
}}
>
{currentQuestion.content.picture && (
<img
src={currentQuestion.content.picture}
alt=""
style={{
display: "block",
width: "100%",
height: "100%",
maxHeight: "80vh",
objectFit: "contain",
}}
/>
<Box sx={{borderRadius: "12px",
border: "1px solid #9A9AAF", overflow: "hidden" }}>
<img
src={currentQuestion.content.picture}
alt=""
style={{
display: "block",
width: "100%",
height: "100%",
objectFit: "contain",
}}
/>
</Box>
)}
{currentQuestion.content.video && (
<video

@ -53,20 +53,22 @@ export const Variant = ({ currentQuestion }: VariantProps) => {
marginTop: "20px",
}}
>
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
<Box sx={{ display: "flex", flexDirection: "row", flexWrap: "wrap", width: "100%", gap: "20px", }}>
{currentQuestion.content.variants.map(({ id, answer }, index) => (
<FormControlLabel
key={id}
sx={{
marginBottom: "15px",
borderRadius: "5px",
margin: "0",
borderRadius: "12px",
padding: "15px",
color: theme.palette.grey2.main,
border: `1px solid ${theme.palette.grey2.main}`,
display: "flex",
gap: "10px",
maxWidth: "685px",
justifyContent: "space-between",
width: "100%"
}}
value={index}
labelPlacement="start"
control={
<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />
}

@ -34,7 +34,7 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
return (
<Box>
<Typography variant="h5">{currentQuestion.title}</Typography>
<Box sx={{ display: "flex" }}>
<Box sx={{ display: "flex", marginTop: "20px", }}>
<RadioGroup
name={currentQuestion.id}
value={currentQuestion.content.variants.findIndex(({ id }) => answer === id)}
@ -50,7 +50,6 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
flexDirection: "row",
justifyContent: "space-between",
flexBasis: "100%",
marginTop: "20px",
}}
>
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
@ -61,7 +60,7 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
marginBottom: "15px",
borderRadius: "5px",
padding: "15px",
color: theme.palette.grey2.main,
color: "#4D4D4D",
border: `1px solid ${theme.palette.grey2.main}`,
display: "flex",
}}
@ -75,7 +74,7 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
</Box>
</RadioGroup>
{(variant?.extendedText || currentQuestion.content.back) && (
<Box sx={{ maxWidth: "400px", width: "100%", height: "300px" }}>
<Box sx={{ maxWidth: "450px", width: "100%", height: "450px", border: "1px solid #E3E3E3", borderRadius: "12px", overflow: "hidden", }}>
<img
src={answer ? variant?.extendedText : currentQuestion.content.back}
style={{ width: "100%", height: "100%", objectFit: "cover" }}

@ -9,8 +9,8 @@ import {
Button,
Container,
FormControl,
IconButton,
TextField,
IconButton, Switch,
TextField, Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
@ -29,10 +29,13 @@ import { useEffect, useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import useSWR from "swr";
import { SidebarMobile } from "./Sidebar/SidebarMobile";
import { cleanQuestions } from "@root/questions/actions";
import {cleanQuestions, updateOpenBranchingPanel} from "@root/questions/actions";
import {BranchingPanel} from "../Questions/BranchingPanel";
import {useQuestionsStore} from "@root/questions/store";
import { useQuestions } from "@root/questions/hooks";
export default function StartPage() {
export default function EditPage() {
useSWR("quizes", () => quizApi.getList(), {
onSuccess: setQuizes,
onError: error => {
@ -42,6 +45,9 @@ export default function StartPage() {
enqueueSnackbar(`Не удалось получить квизы. ${message}`);
},
});
// if (isLoading && !questions) return <Box>Загрузка вопросов...</Box>;
const theme = useTheme();
const navigate = useNavigate();
const editQuizId = useQuizStore(state => state.editQuizId);
@ -50,8 +56,9 @@ export default function StartPage() {
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
const isMobile = useMediaQuery(theme.breakpoints.down(660));
const [mobileSidebar, setMobileSidebar] = useState<boolean>(false);
const {openBranchingPanel} = useQuestionsStore.getState()
const quizConfig = quiz?.config;
const { questions, isLoading } = useQuestions();
useEffect(() => {
if (editQuizId === null) navigate("/list");
@ -212,6 +219,7 @@ export default function StartPage() {
boxSizing: "border-box",
}}
>
{/* Выбор текущей страницы редактирования чего-либо находится здесь */}
{quizConfig &&
<>
<Stepper activeStep={currentStep} />
@ -228,9 +236,9 @@ export default function StartPage() {
<Box
sx={{
position: "absolute",
left: isMobile ? 0 : "230px",
left: 0,
bottom: 0,
width: isMobile ? "100%" : "calc(100% - 230px)",
width: "100%",
padding: "20px 40px",
display: "flex",
justifyContent: "flex-end",
@ -238,6 +246,75 @@ export default function StartPage() {
background: "#FFF",
}}
>
<Box
sx={{
display: openBranchingPanel ? "none" : "display",
alignItems: "center",
gap: "15px",
padding: "18px",
background: "#fff",
borderRadius: "12px",
boxShadow: "0px 10px 30px #e7e7e7",
}}
>
<Switch
value={openBranchingPanel}
onChange={(_, value) => {
updateOpenBranchingPanel(value)
}}
sx={{
width: 50,
height: 30,
padding: 0,
"& .MuiSwitch-switchBase": {
padding: 0,
margin: "2px",
transitionDuration: "300ms",
"&.Mui-checked": {
transform: "translateX(20px)",
color: theme.palette.brightPurple.main,
"& + .MuiSwitch-track": {
backgroundColor: "#E8DCF9",
opacity: 1,
border: 0,
},
"&.Mui-disabled + .MuiSwitch-track": { opacity: 0.5 },
},
"&.Mui-disabled .MuiSwitch-thumb": {
color:
theme.palette.mode === "light"
? theme.palette.grey[100]
: theme.palette.grey[600],
},
"&.Mui-disabled + .MuiSwitch-track": {
opacity: theme.palette.mode === "light" ? 0.7 : 0.3,
},
},
"& .MuiSwitch-thumb": {
boxSizing: "border-box",
width: 25,
height: 25,
},
"& .MuiSwitch-track": {
borderRadius: 13,
backgroundColor:
theme.palette.mode === "light" ? "#E9E9EA" : "#39393D",
opacity: 1,
transition: theme.transitions.create(["background-color"], {
duration: 500,
}),
},
}}
/>
<Box>
<Typography sx={{ fontWeight: "bold", color: "#4D4D4D" }}>
Логика ветвления
</Typography>
<Typography sx={{ color: "#4D4D4D", fontSize: "12px" }}>
Настройте связи между вопросами
</Typography>
</Box>
</Box>
<Button
variant="contained"
sx={{

@ -4,21 +4,23 @@ import { devlog } from "@frontend/kitui";
import { questionToEditQuestionRequest } from "@model/question/edit";
import { QuestionType, RawQuestion, rawQuestionToQuestion } from "@model/question/question";
import { AnyTypedQuizQuestion, QuestionVariant, UntypedQuizQuestion, createQuestionVariant } from "@model/questionTypes/shared";
import { updateRootContentId } from "@root/quizes/actions";
import { defaultQuestionByType } from "../../constants/default";
import { produce } from "immer";
import { nanoid } from "nanoid";
import { enqueueSnackbar } from "notistack";
import { defaultQuestionByType } from "../../constants/default";
import { isAxiosCanceledError } from "../../utils/isAxiosCanceledError";
import { RequestQueue } from "../../utils/requestQueue";
import { updateRootContentId } from "@root/quizes/actions"
import { useCurrentQuiz } from "@root/quizes/hooks"
import { QuestionsStore, useQuestionsStore } from "./store";
import { withErrorBoundary } from "react-error-boundary";
export const setQuestions = (questions: RawQuestion[] | null) => setProducedState(state => {
const untypedQuestions = state.questions.filter(q => q.type === null);
const untypedResultQuestions = state.questions.filter(q => q.type === null || q.type === "result");
state.questions = questions?.map(rawQuestionToQuestion) ?? [];
state.questions.push(...untypedQuestions);
state.questions.push(...untypedResultQuestions);
}, {
type: "setQuestions",
questions,
@ -86,7 +88,7 @@ const setQuestionBackendId = (questionId: string, backendId: number) => setProdu
const updateQuestionOrders = () => {
const questions = useQuestionsStore.getState().questions.filter(
(question): question is AnyTypedQuizQuestion => question.type !== null
(question): question is AnyTypedQuizQuestion => question.type !== null && question.type !== "result"
);
questions.forEach((question, index) => {
@ -345,10 +347,45 @@ export const deleteQuestion = async (questionId: string, quizId: string) => requ
try {
await questionApi.delete(question.backendId);
if (question.content.rule.parentId === "root") { //удалить из стора root и очистить rule всем вопросам
updateRootContentId(quizId, "");
clearRoleForAll();
}
updateRootContentId(quizId, "")
clearRuleForAll()
} 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 (!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 = questions.filter((q) => {
return q.content.rule.parentId === question.content.rule.parentId && q.content.id !== question.content.id
})[0]?.content.id || ""
//Если этот вопрос был дефолтным у родителя - чистим дефолт
//Смотрим можем ли мы заменить id на один из main
console.log(newRule)
updateQuestion(question.content.rule.parentId, (PQ) => {
PQ.content.rule = newRule
})
}
removeQuestion(questionId);
} catch (error) {
devlog("Error deleting question", error);
@ -363,7 +400,8 @@ export const copyQuestion = async (questionId: string, quizId: number) => reques
const frontId = nanoid();
if (question.type === null) {
const copiedQuestion = structuredClone(question);
copiedQuestion.id = frontId;
copiedQuestion.id = frontId
copiedQuestion.content.id = frontId
setProducedState(state => {
state.questions.push(copiedQuestion);
@ -430,17 +468,92 @@ export const updateDragQuestionContentId = (contentId?: string) => {
useQuestionsStore.setState({ dragQuestionContentId: contentId ? contentId : null });
};
export const clearRoleForAll = () => {
const { questions } = useQuestionsStore.getState();
export const clearRuleForAll = () => {
const { questions } = useQuestionsStore.getState()
questions.forEach(question => {
if (question.type !== null && (question.content.rule.main.length > 0 || question.content.rule.default.length > 0 || question.content.rule.parentId.length > 0)) {
updateQuestion(question.content.id, question => {
question.content.rule.parentId = "";
question.content.rule.main = [];
question.content.rule.default = "";
});
question.content.rule.parentId = ""
question.content.rule.main = []
question.content.rule.default = ""
})
}
});
};
}
export const updateOpenBranchingPanel = (value: boolean) => useQuestionsStore.setState({ openBranchingPanel: !value });
export const updateOpenBranchingPanel = (value: boolean) => useQuestionsStore.setState({ openBranchingPanel: value });
let UDTOABM: ReturnType<typeof setTimeout>;
export const updateDesireToOpenABranchingModal = (contentId: string) => {
useQuestionsStore.setState({ desireToOpenABranchingModal: contentId })
clearTimeout(UDTOABM)
UDTOABM = setTimeout(() => {
useQuestionsStore.setState({ desireToOpenABranchingModal: null })
}, 7000)
}
export const clearDesireToOpenABranchingModal = () => {
useQuestionsStore.setState({ desireToOpenABranchingModal: null })
}
export const updateEditSomeQuestion = (contentId?: string) => {
useQuestionsStore.setState({ editSomeQuestion: contentId === undefined ? null : contentId })
}
export const createFrontResult = (quizId: number, parentContentId?: string) => setProducedState(state => {
const frontId = nanoid()
const content = JSON.parse(JSON.stringify(defaultQuestionByType["result"].content))
content.id = frontId
if (parentContentId) content.rule.parentId = parentContentId
state.questions.push({
id: frontId,
quizId,
type: "result",
title: "",
description: "",
deleted: false,
expanded: true,
page: 101,
required: true,
content
});
}, {
type: "createFrontResult",
quizId,
});
export const createBackResult = async (
questionId: string,
type: QuestionType,
) => requestQueue.enqueue(async () => {
const question = useQuestionsStore.getState().questions.find(q => q.id === questionId);
if (!question) return;
if (question.type !== "result") throw new Error("Cannot upgrade already typed question");
try {
const createdQuestion = await questionApi.create({
quiz_id: question.quizId,
type,
title: question.title,
description: question.description,
page: 0,
required: true,
content: JSON.stringify(defaultQuestionByType[type].content),
});
setProducedState(state => {
const questionIndex = state.questions.findIndex(q => q.id === questionId);
if (questionIndex !== -1) state.questions.splice(
questionIndex,
1,
rawQuestionToQuestion(createdQuestion)
);
}, {
type: "createTypedQuestion",
question,
});
} catch (error) {
devlog("Error creating question", error);
enqueueSnackbar("Не удалось создать вопрос");
}
});

@ -9,6 +9,7 @@ import { useCurrentQuiz } from "@root/quizes/hooks";
export function useQuestions() {
console.log("вызываю вопросы")
const quiz = useCurrentQuiz();
const { isLoading, error, isValidating } = useSWR(["questions", quiz?.backendId], ([, id]) => questionApi.getList({ quiz_id: id }), {
onSuccess: setQuestions,

@ -8,6 +8,8 @@ export type QuestionsStore = {
openedModalSettingsId: string | null;
dragQuestionContentId: string | null;
openBranchingPanel: boolean;
desireToOpenABranchingModal: string | null;
editSomeQuestion: string | null;
};
const initialState: QuestionsStore = {
@ -15,6 +17,9 @@ const initialState: QuestionsStore = {
openedModalSettingsId: null as null,
dragQuestionContentId: null,
openBranchingPanel: false,
desireToOpenABranchingModal: null as null,
editSomeQuestion: null as null,
};
export const useQuestionsStore = create<QuestionsStore>()(

@ -65,7 +65,7 @@ export default function QuizPreviewLayout() {
>
<Box
sx={{
p: "16px",
p: "40px 20px 20px",
whiteSpace: "break-spaces",
overflowY: "auto",
flexGrow: 1,

@ -7,12 +7,14 @@ import {
Radio,
RadioGroup,
Tooltip,
Typography,
Typography, useTheme,
} from "@mui/material";
import InfoIcon from "@icons/InfoIcon";
import type { QuizQuestionEmoji } from "model/questionTypes/emoji";
import RadioCheck from "@ui_kit/RadioCheck";
import RadioIcon from "@ui_kit/RadioIcon";
interface Props {
question: QuizQuestionEmoji;
@ -20,45 +22,78 @@ interface Props {
export default function Emoji({ question }: Props) {
const [value, setValue] = useState<string | null>(null);
const theme = useTheme();
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
setValue((event.target as HTMLInputElement).value);
};
return (
<FormControl fullWidth>
<FormLabel id="quiz-question-radio-group" data-cy="question-title">{question.title}</FormLabel>
<FormLabel id="quiz-question-radio-group" data-cy="question-title" sx={{fontSize: "24px", fontWeight: 500, marginBottom: "25px"}}>{question.title}</FormLabel>
<RadioGroup
aria-labelledby="quiz-question-radio-group"
value={value}
onChange={handleChange}
sx={{display: "flex",
flexDirection: "row", gap: "42px"}}
>
{question.content.variants
.filter(({ answer }) => answer)
.map((variant, index) => (
<FormControlLabel
key={index}
value={variant.answer}
control={
<Radio
inputProps={{
"data-cy": "variant-radio",
<Box
key={index}
sx={{
borderRadius: "12px",
border: `1px solid ${theme.palette.grey2.main}`,
overflow: "hidden",
maxWidth: "317px",
width: "100%",
height: "255px"
}}
/>
}
label={
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }} data-cy="variant-answer">
<Typography>{`${variant.extendedText} ${variant.answer}`}</Typography>
{variant.hints && (
<Tooltip title="Подсказка" placement="right">
<Box>
<InfoIcon />
</Box>
</Tooltip>
)}
>
<Box
sx={{ display: "flex", alignItems: "center", height: "193px", background: "#ffffff" }}
>
<Box sx={{ width: "100%", display: "flex", justifyContent: "center" }}>
{variant.extendedText && (
<Typography fontSize={"100px"}>{`${variant.extendedText}`}</Typography>
)}
</Box>
</Box>
}
/>
<FormControlLabel
key={index}
value={variant.answer}
sx={{
margin: 0,
padding: "15px",
color: "#4D4D4D",
display: "flex",
gap: "10px",
background: "#f2f3f7"
}}
control={
<Radio
inputProps={{
"data-cy": "variant-radio",
}}
checkedIcon={<RadioCheck />} icon={<RadioIcon />}
/>
}
label={
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }} data-cy="variant-answer">
<Typography>{`${variant.answer}`}</Typography>
{variant.hints && (
<Tooltip title="Подсказка" placement="right">
<Box>
<InfoIcon />
</Box>
</Tooltip>
)}
</Box>
}
/>
</Box>
))}
</RadioGroup>
</FormControl>

@ -16,19 +16,22 @@ export default function Page({ question }: Props) {
gap: 1,
}}
>
<Typography variant="h6" data-cy="question-title">{question.title}</Typography>
<Typography data-cy="question-text">{question.content.text}</Typography>
<Typography variant="h6" data-cy="question-title" sx={{ paddingBottom: "25px" }}>{question.title}</Typography>
<Typography data-cy="question-text" sx={{ paddingBottom: "20px" }}>{question.content.text}</Typography>
{question.content.picture && (
<img
src={question.content.picture}
alt=""
style={{
width: "100%",
display: "block",
objectFit: "scale-down",
flexGrow: 1,
}}
/>
<Box sx={{borderRadius: "12px",
border: "1px solid #9A9AAF", width: "100%", overflow: "hidden"}}>
<img
src={question.content.picture}
alt=""
style={{
display: "block",
width: "100%",
height: "100%",
objectFit: "contain",
}}
/>
</Box>
)}
</Box>
);

@ -1,82 +1,89 @@
import { ChangeEvent, useState } from "react";
import {
Box,
FormControl,
FormControlLabel,
FormLabel,
Radio,
RadioGroup,
Tooltip,
Typography,
Box,
FormControl,
FormControlLabel,
FormLabel,
Radio,
RadioGroup,
useRadioGroup,
Tooltip,
Typography,
} from "@mui/material";
import InfoIcon from "@icons/InfoIcon";
import type { QuizQuestionVariant } from "model/questionTypes/variant";
import CustomRadio from "@ui_kit/CustomRadio";
import RadioCheck from "@ui_kit/RadioCheck";
import RadioIcon from "@ui_kit/RadioIcon";
interface Props {
question: QuizQuestionVariant;
question: QuizQuestionVariant;
}
export default function Variant({ question }: Props) {
const [value, setValue] = useState<string | null>(null);
const [value, setValue] = useState<string | null>(null);
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
setValue((event.target as HTMLInputElement).value);
};
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
setValue((event.target as HTMLInputElement).value);
};
return (
<Box sx={{
display: "flex",
flexDirection: "column",
gap: 1,
}}>
<Typography>{question.title}</Typography>
<RadioGroup
aria-labelledby="quiz-question-radio-group"
value={value}
onChange={handleChange}
>
{question.content.variants
.filter(({ answer }) => answer)
.map((variant, index) => (
<FormControlLabel
key={index}
value={variant.answer}
data-cy="variant-answer"
control={
<Radio
inputProps={{
"data-cy": "variant-radio",
}}
/>
}
label={
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
<Typography>
{variant.answer}
</Typography>
{variant.hints && (
<Tooltip title={variant.hints} placement="right">
<Box>
<InfoIcon />
</Box>
</Tooltip>
)}
</Box>
}
/>
))}
</RadioGroup>
<img
src={question.content.back}
style={{
display: "block",
objectFit: "scale-down",
alignSelf: "start",
maxWidth: "100%",
}}
return (
<FormControl fullWidth>
<FormLabel id="quiz-question-radio-group" data-cy="question-title" sx={{color: "#000000", marginBottom: "20px", fontSize: "24px", fontWeight: 500}}>
{question.title}
</FormLabel>
<RadioGroup
aria-labelledby="quiz-question-radio-group"
value={value}
onChange={handleChange}
sx={{
flexDirection: "row",
gap: "20px"
}}
>
{question.content.variants
.filter(({ answer }) => answer)
.map((variant, index) => (
<FormControlLabel
key={index}
value={variant.answer}
data-cy="variant-answer"
labelPlacement="start"
sx={{borderRadius: "12px",
border: value === value ? "1px solid #7E2AEA" : "1px solid #9A9AAF",
padding: "20px",
justifyContent: "space-between",
maxWidth: "685px",
width: "100%",
margin: 0
}}
control={
<Radio
inputProps={{
"data-cy": "variant-radio",
}}
checkedIcon={<RadioCheck />} icon={<RadioIcon />}
/>
}
label={
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
<Typography>
{variant.answer}
</Typography>
{variant.hints && (
<Tooltip title={variant.hints} placement="right">
<Box>
<InfoIcon />
</Box>
</Tooltip>
)}
</Box>
}
/>
</Box>
);
))}
</RadioGroup>
</FormControl>
);
}

@ -1,4 +1,4 @@
import { useState, ChangeEvent, useEffect } from "react";
import { useState, ChangeEvent } from "react";
import {
Box,
FormControl,
@ -7,122 +7,129 @@ import {
Radio,
RadioGroup,
Tooltip,
Typography,
Typography, useTheme,
} from "@mui/material";
import InfoIcon from "@icons/InfoIcon";
import type { QuestionVariant } from "model/questionTypes/shared";
import type { QuizQuestionVarImg } from "model/questionTypes/varimg";
import RadioCheck from "@ui_kit/RadioCheck";
import RadioIcon from "@ui_kit/RadioIcon";
interface Props {
question: QuizQuestionVarImg;
question: QuizQuestionVarImg;
}
export default function Varimg({ question }: Props) {
const [selectedVariantIndex, setSelectedVariantIndex] = useState<number>(-1);
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
setSelectedVariantIndex(
question.content.variants.findIndex(
(variant) => variant.answer === event.target.value
)
);
};
const currentVariant: QuestionVariant | undefined =
question.content.variants[selectedVariantIndex];
return (
<Box
sx={{
display: "flex",
flexWrap: "wrap",
gap: 2,
}}
>
<FormControl>
<FormLabel
id="quiz-question-radio-group"
data-cy="question-title"
>
{question.title}
</FormLabel>
<RadioGroup
aria-labelledby="quiz-question-radio-group"
value={currentVariant?.answer ?? ""}
onChange={handleChange}
>
{question.content.variants
.filter(({ answer }) => answer)
.map((variant, index) => (
<FormControlLabel
key={index}
value={variant.answer}
data-cy="variant-answer"
control={
<Radio
inputProps={{
"data-cy": "variant-radio",
}}
/>
}
label={
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
<Typography>{variant.answer}</Typography>
{variant.hints && (
<Tooltip title={variant.hints} placement="right">
<Box>
<InfoIcon />
</Box>
</Tooltip>
)}
</Box>
}
/>
))}
</RadioGroup>
</FormControl>
<Box
sx={{
border: "1px solid #E3E3E3",
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
{currentVariant ?
currentVariant.extendedText ? (
<img
src={currentVariant.extendedText}
data-cy="variant-image"
alt="question variant"
style={{
width: "100%",
display: "block",
objectFit: "scale-down",
flexGrow: 1,
}}
/>
) : (
<Typography p={2}>Картинка отсутствует</Typography>
) : question.content.back ? (
<img
src={question.content.back}
data-cy="variant-image"
alt="question variant"
style={{
width: "100%",
display: "block",
objectFit: "scale-down",
flexGrow: 1,
}}
/>
) : (
<Typography p={2}>Выберите вариант</Typography>
)
}
</Box>
</Box>
const [selectedVariantIndex, setSelectedVariantIndex] = useState<number>(-1);
const theme = useTheme();
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
setSelectedVariantIndex(
question.content.variants.findIndex(
(variant) => variant.answer === event.target.value
)
);
};
const currentVariant: QuestionVariant | undefined =
question.content.variants[selectedVariantIndex];
return (
<Box
sx={{
display: "flex",
flexWrap: "wrap",
gap: "40px",
}}
>
<FormControl sx={{maxWidth: "900px",
width: "100%", gap: "25px"}}>
<FormLabel
id="quiz-question-radio-group"
data-cy="question-title"
sx={{fontSize: "24px", fontWeight: 500}}
>
{question.title}
</FormLabel>
<RadioGroup
aria-labelledby="quiz-question-radio-group"
value={currentVariant?.answer ?? ""}
onChange={handleChange}
sx={{gap: "20px"}}
>
{question.content.variants
.filter(({ answer }) => answer)
.map((variant, index) => (
<FormControlLabel
key={index}
value={variant.answer}
data-cy="variant-answer"
sx={{
margin: 0,
borderRadius: "5px",
padding: "15px",
color: "#4D4D4D",
border: `1px solid ${theme.palette.grey2.main}`,
display: "flex",
}}
control={
<Radio
inputProps={{
"data-cy": "variant-radio",
}}
checkedIcon={<RadioCheck />} icon={<RadioIcon />}
/>
}
label={
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
<Typography>{variant.answer}</Typography>
{variant.hints && (
<Tooltip title={variant.hints} placement="right">
<Box>
<InfoIcon />
</Box>
</Tooltip>
)}
</Box>
}
/>
))}
</RadioGroup>
</FormControl>
<Box
sx={{
border: "1px solid #E3E3E3",
maxWidth: "400px",
height: "400px",
display: "flex",
justifyContent: "center",
alignItems: "center",
borderRadius: "12px",
marginTop: "50px",
overflow: "hidden",
}}
>
{currentVariant?.extendedText ? (
<img
src={currentVariant.extendedText}
data-cy="variant-image"
alt="question variant"
style={{
width: "100%",
display: "block",
objectFit: "scale-down",
flexGrow: 1,
}}
/>
) : (
<Typography p={2}>
{selectedVariantIndex === -1
? "Выберите вариант"
: "Картинка отсутствует"}
</Typography>
)}
</Box>
</Box>
);
}

@ -46,6 +46,7 @@ export default function Sidebar() {
overflow: "hidden",
whiteSpace: "nowrap",
boxSizing: "border-box",
zIndex: 1
}}
>
<Box

@ -4,8 +4,8 @@ import InstallQuiz from "../pages/InstallQuiz/InstallQuiz";
import FormQuestionsPage from "../pages/Questions/Form/FormQuestionsPage";
import QuestionsPage from "../pages/Questions/QuestionsPage";
import { QuestionsMap } from "../pages/QuestionsMap";
import { Result } from "../pages/Result/Result";
import { Setting } from "../pages/Result/Setting";
import { ResultPage } from "../pages/ResultPage/ResultPage";
import { ResultSettings } from "../pages/ResultPage/ResultSettings";
import StartPageSettings from "../pages/startPage/StartPageSettings";
import StepOne from "../pages/startPage/stepOne";
import Steptwo from "../pages/startPage/steptwo";
@ -31,10 +31,7 @@ export default function SwitchStepPages({
return <StartPageSettings />;
}
case 1: return quizType === "form" ? <FormQuestionsPage /> : <QuestionsPage />;
case 2: {
if (!quizResults) return <Result />;
return <Setting />;
}
case 2: return <ResultPage />;
case 3: return <QuestionsMap />;
case 4: return <ContactFormPage />;
case 5: return <InstallQuiz />;

@ -8628,6 +8628,13 @@ react-easy-crop@^5.0.0:
normalize-wheel "^1.0.1"
tslib "2.0.1"
react-error-boundary@^4.0.11:
version "4.0.11"
resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-4.0.11.tgz#36bf44de7746714725a814630282fee83a7c9a1c"
integrity sha512-U13ul67aP5DOSPNSCWQ/eO0AQEYzEFkVljULQIjMV0KlffTAhxuDoBKdO0pb/JZ8mDhMKFZ9NZi0BmLGUiNphw==
dependencies:
"@babel/runtime" "^7.12.5"
react-error-overlay@^6.0.11:
version "6.0.11"
resolved "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz"