страница результатов создаёт фротнтовые карточки результатов, имеет настройки квиза

This commit is contained in:
Nastya 2023-12-08 01:56:31 +03:00
commit 7aa846f52e
35 changed files with 1075 additions and 310 deletions

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

Before

Width:  |  Height:  |  Size: 495 B

After

Width:  |  Height:  |  Size: 494 B

@ -8,19 +8,10 @@ export const QUIZ_QUESTION_RESULT: Omit<QuizQuestionResult, "id" | "backendId">
type: "result", type: "result",
content: { content: {
...QUIZ_QUESTION_BASE.content, ...QUIZ_QUESTION_BASE.content,
multi: false, video: "",
own: false,
innerNameCheck: false,
innerName: "", innerName: "",
required: false, text: "",
variants: [ price: [0],
{ rangePrice: false
id: nanoid(),
answer: "",
extendedText: "",
hints: "",
originalImageUrl: "",
},
],
}, },
}; };

@ -1,29 +1,22 @@
import type { import type {
QuizQuestionBase, QuizQuestionBase,
QuestionVariant, QuestionBranchingRule,
QuestionHint, QuestionHint,
PreviewRule,
} from "./shared"; } from "./shared";
export interface QuizQuestionResult extends QuizQuestionBase { export interface QuizQuestionResult extends QuizQuestionBase {
type: "result"; type: "result";
content: { content: {
id: string; id: string;
/** Чекбокс "Можно несколько" */
multi: boolean;
/** Чекбокс "Вариант "свой ответ"" */
own: boolean;
/** Чекбокс "Внутреннее название вопроса" */
innerNameCheck: boolean;
/** Поле "Внутреннее название вопроса" */
innerName: string;
/** Чекбокс "Необязательный вопрос" */
required: boolean;
variants: QuestionVariant[];
hint: QuestionHint;
rule: PreviewRule;
back: string; back: string;
originalBack: string; originalBack: string;
video: string;
innerName: string;
text: string;
price: [number] | [number, number];
rangePrice: boolean;
rule: QuestionBranchingRule,
hint: QuestionHint;
autofill: boolean; autofill: boolean;
}; };
} }

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

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

@ -122,7 +122,9 @@ function CsComponent ({
}: Props) { }: Props) {
const quiz = useCurrentQuiz(); const quiz = useCurrentQuiz();
const { dragQuestionContentId, questions, desireToOpenABranchingModal } = useQuestionsStore() const { dragQuestionContentId, desireToOpenABranchingModal } = useQuestionsStore()
const trashQuestions = useQuestionsStore().questions
const questions = trashQuestions.filter((question) => question.type !== "result")
const [startCreate, setStartCreate] = useState(""); const [startCreate, setStartCreate] = useState("");
const [startRemove, setStartRemove] = useState(""); const [startRemove, setStartRemove] = useState("");

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

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

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

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

@ -24,7 +24,9 @@ const getItemStyle = (isDragging: any, draggableStyle: any) => ({
type AnyQuestion = UntypedQuizQuestion | AnyTypedQuizQuestion type AnyQuestion = UntypedQuizQuestion | AnyTypedQuizQuestion
export const QuestionsList = () => { export const QuestionsList = () => {
const { questions, desireToOpenABranchingModal } = useQuestionsStore() const { desireToOpenABranchingModal } = useQuestionsStore()
const trashQuestions = useQuestionsStore().questions
const questions = trashQuestions.filter((question) => question.type !== "result")
return ( return (
<Box sx={{ padding: "15px" }}> <Box sx={{ padding: "15px" }}>

@ -39,24 +39,6 @@ const priceButtonsArray: { title: string; type: string; sx: SxProps<Theme> }[] =
whiteSpace: "nowrap", 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 = { type Props = {
@ -74,9 +56,6 @@ export default function PriceButtons({
<Typography component={"h6"} sx={{ weight: "500", fontSize: "18px" }}> <Typography component={"h6"} sx={{ weight: "500", fontSize: "18px" }}>
Стоимость Стоимость
</Typography> </Typography>
<IconButton sx={{ borderRadius: "6px", padding: "2px" }}>
<DeleteIcon style={{ color: "#4D4D4D" }} />
</IconButton>
</Box> </Box>
<Box <Box
component="div" component="div"

@ -1,16 +1,36 @@
import { Box, Typography, useTheme, useMediaQuery } from "@mui/material";
type Props = { import { ResultSettings } from "./ResultSettings"
text: string; import { createFrontResult } from "@root/questions/actions";
text2: string; import { useQuestionsStore } from "@root/questions/store";
image: string; 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 default function CreationFullCard({ text, text2, image }: Props) { export const FirstEntry = () => {
const theme = useTheme(); const theme = useTheme();
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1500)); 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 ( return (
<>
<Box <Box
sx={{ sx={{
flexGrow: 1, flexGrow: 1,
@ -32,8 +52,9 @@ export default function CreationFullCard({ text, text2, image }: Props) {
<Box <Box
sx={{ sx={{
mr: !isSmallMonitor ? "104px" : 0, mr: !isSmallMonitor ? "104px" : 0,
marginBottom: "20px", marginBottom: isSmallMonitor ? "20px" : 0,
position: "relative", position: "relative",
height: "100%"
}} }}
> >
<Typography variant="h5" sx={{ marginBottom: "20px" }}> <Typography variant="h5" sx={{ marginBottom: "20px" }}>
@ -45,12 +66,11 @@ export default function CreationFullCard({ text, text2, image }: Props) {
flexDirection: "column", flexDirection: "column",
justifyContent: "space-between", justifyContent: "space-between",
height: "100%", height: "100%",
maxHeight: isSmallMonitor ? "none" : "220px",
gap: "25px", gap: "25px",
}} }}
> >
<Typography sx={{ color: "#4D4D4D", width: "95%" }}> <Typography sx={{ color: "#4D4D4D", width: "95%" }}>
{text} Вы можете показывать разные результаты квиза (добавьте описание, изображение, стоимость и т.п.) разным пользователям, нужно только их создать и проставить условия. Таким образом ваш квиз получится максимально индивидуальным для каждого клиента. Показывайте картинку/видео вместо результата или переадресовывайте пользователя по нужной ссылке.
</Typography> </Typography>
<Typography <Typography
sx={{ sx={{
@ -58,7 +78,7 @@ export default function CreationFullCard({ text, text2, image }: Props) {
width: "100%", width: "100%",
}} }}
> >
{text2} Этот шаг - необязательный, квиз будет работать и без автоматических результатов.
</Typography> </Typography>
</Box> </Box>
</Box> </Box>
@ -72,5 +92,21 @@ export default function CreationFullCard({ text, text2, image }: Props) {
}} }}
/> />
</Box> </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 { useCurrentQuiz } from "@root/quizes/hooks";
import image from "../../assets/Rectangle 110.png"; import image from "../../assets/Rectangle 110.png";
import Info from "../../assets/icons/Info"; import Info from "../../assets/icons/Info";
import CreationFullCard from "./FirstEntry"; // import CreationFullCard from "./FirstEntry";
export const Result = () => { export const Result = () => {
@ -13,11 +13,11 @@ export const Result = () => {
return ( return (
<Box component="section"> <Box component="section">
<CreationFullCard {/* <CreationFullCard
text="Вы можете показывать разные результаты квиза (добавьте описание, изображение, стоимость и т.п.) разным пользователям, нужно только их создать и проставить условия. Таким образом ваш квиз получится максимально индивидуальным для каждого клиента. Показывайте картинку/видео вместо результата или переадресовывайте пользователя по нужной ссылке." text="Вы можете показывать разные результаты квиза (добавьте описание, изображение, стоимость и т.п.) разным пользователям, нужно только их создать и проставить условия. Таким образом ваш квиз получится максимально индивидуальным для каждого клиента. Показывайте картинку/видео вместо результата или переадресовывайте пользователя по нужной ссылке."
text2="Этот шаг - необязательный, квиз будет работать и без автоматических результатов." text2="Этот шаг - необязательный, квиз будет работать и без автоматических результатов."
image={image} image={image}
/> /> */}
<Box sx={{ display: "flex", mt: "30px", alignItems: "center" }}> <Box sx={{ display: "flex", mt: "30px", alignItems: "center" }}>
<Button <Button
variant="contained" variant="contained"

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

@ -2,19 +2,31 @@ import IconPlus from "@icons/IconPlus";
import Info from "@icons/Info"; import Info from "@icons/Info";
import Plus from "@icons/Plus"; import Plus from "@icons/Plus";
import ArrowLeft from "@icons/questionsPage/arrowLeft"; 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 { incrementCurrentStep } from "@root/quizes/actions";
import CustomWrapper from "@ui_kit/CustomWrapper"; import CustomWrapper from "@ui_kit/CustomWrapper";
import { DescriptionForm } from "./DescriptionForm/DescriptionForm"; import { DescriptionForm } from "./DescriptionForm/DescriptionForm";
import { ResultListForm } from "./ResultListForm"; import { ResultListForm } from "./ResultListForm";
import { SettingForm } from "./SettingForm"; 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 Setting = () => { export const ResultSettings = () => {
const quiz = useCurrentQuiz()
const [quizExpand, setQuizExpand] = useState(true)
const [resultContract, setResultContract] = useState(true)
return ( return (
<Box sx={{ maxWidth: "796px" }}> <Box sx={{ maxWidth: "796px" }}>
<Box sx={{ display: "flex", alignItems: "center", mb: "40px" }}> <Box sx={{
<Typography sx={{ pr: "10px" }} variant="h5"> display: "flex",
alignItems: "center",
margin: "60px 0 40px 0",
}}>
<Typography variant="h5">
Настройки результатов Настройки результатов
</Typography> </Typography>
<Info /> <Info />
@ -33,12 +45,17 @@ export const Setting = () => {
}, },
}} }}
variant="text" variant="text"
onClick={() => setQuizExpand(!quizExpand)}
> >
Свернуть Свернуть
</Button> </Button>
</Box> </Box>
<CustomWrapper sx={{ mt: "30px" }} text="Показывать результат" />
<CustomWrapper sx={{ mt: "30px" }} text="Настройки почты" />
<WhenCard quizExpand={quizExpand} />
{quiz.config.resultInfo.when === "email" && <EmailSettingsCard quizExpand={quizExpand} />}
<Box <Box
sx={{ display: "flex", alignItems: "center", mb: "15px", mt: "15px" }} sx={{ display: "flex", alignItems: "center", mb: "15px", mt: "15px" }}
> >
@ -60,52 +77,14 @@ export const Setting = () => {
}, },
}} }}
variant="text" variant="text"
onClick={() => setResultContract(!resultContract)}
> >
Развернуть все Развернуть все
</Button> </Button>
</Box> </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 /> <ResultCard resultContract={resultContract} />
<DescriptionForm />
</Box> </Box>
); );
}; };

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

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

@ -6,11 +6,10 @@ import {
FormControlLabel, FormControlLabel,
Radio, Radio,
useTheme, useTheme,
useMediaQuery, useMediaQuery, FormControl,
} from "@mui/material"; } from "@mui/material";
import { useQuizViewStore, updateAnswer } from "@root/quizView"; import { useQuizViewStore, updateAnswer } from "@root/quizView";
import RadioCheck from "@ui_kit/RadioCheck"; import RadioCheck from "@ui_kit/RadioCheck";
import RadioIcon from "@ui_kit/RadioIcon"; import RadioIcon from "@ui_kit/RadioIcon";
@ -92,7 +91,7 @@ export const Images = ({ currentQuestion }: ImagesProps) => {
)} )}
</Box> </Box>
</Box> </Box>
<FormControlLabel <FormControl
key={id} key={id}
sx={{ sx={{
display: "block", display: "block",

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

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

@ -32,9 +32,10 @@ import { SidebarMobile } from "./Sidebar/SidebarMobile";
import {cleanQuestions, updateOpenBranchingPanel} from "@root/questions/actions"; import {cleanQuestions, updateOpenBranchingPanel} from "@root/questions/actions";
import {BranchingPanel} from "../Questions/BranchingPanel"; import {BranchingPanel} from "../Questions/BranchingPanel";
import {useQuestionsStore} from "@root/questions/store"; import {useQuestionsStore} from "@root/questions/store";
import { useQuestions } from "@root/questions/hooks";
export default function StartPage() { export default function EditPage() {
useSWR("quizes", () => quizApi.getList(), { useSWR("quizes", () => quizApi.getList(), {
onSuccess: setQuizes, onSuccess: setQuizes,
onError: error => { onError: error => {
@ -44,6 +45,9 @@ export default function StartPage() {
enqueueSnackbar(`Не удалось получить квизы. ${message}`); enqueueSnackbar(`Не удалось получить квизы. ${message}`);
}, },
}); });
// if (isLoading && !questions) return <Box>Загрузка вопросов...</Box>;
const theme = useTheme(); const theme = useTheme();
const navigate = useNavigate(); const navigate = useNavigate();
const editQuizId = useQuizStore(state => state.editQuizId); const editQuizId = useQuizStore(state => state.editQuizId);
@ -54,6 +58,7 @@ export default function StartPage() {
const [mobileSidebar, setMobileSidebar] = useState<boolean>(false); const [mobileSidebar, setMobileSidebar] = useState<boolean>(false);
const {openBranchingPanel} = useQuestionsStore.getState() const {openBranchingPanel} = useQuestionsStore.getState()
const quizConfig = quiz?.config; const quizConfig = quiz?.config;
const { questions, isLoading } = useQuestions();
useEffect(() => { useEffect(() => {
if (editQuizId === null) navigate("/list"); if (editQuizId === null) navigate("/list");
@ -214,6 +219,7 @@ export default function StartPage() {
boxSizing: "border-box", boxSizing: "border-box",
}} }}
> >
{/* Выбор текущей страницы редактирования чего-либо находится здесь */}
{quizConfig && {quizConfig &&
<> <>
<Stepper activeStep={currentStep} /> <Stepper activeStep={currentStep} />

@ -18,10 +18,10 @@ import { withErrorBoundary } from "react-error-boundary";
export const setQuestions = (questions: RawQuestion[] | null) => setProducedState(state => { 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 = questions?.map(rawQuestionToQuestion) ?? [];
state.questions.push(...untypedQuestions); state.questions.push(...untypedResultQuestions);
}, { }, {
type: "setQuestions", type: "setQuestions",
questions, questions,
@ -472,15 +472,22 @@ export const updateEditSomeQuestion = (contentId?: string) => {
useQuestionsStore.setState({ editSomeQuestion: contentId === undefined ? null : contentId }) useQuestionsStore.setState({ editSomeQuestion: contentId === undefined ? null : contentId })
} }
export const createFrontResult = (quizId: number) => setProducedState(state => { 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({ state.questions.push({
id: nanoid(), id: frontId,
quizId, quizId,
type: "result", type: "result",
title: "", title: "",
description: "", description: "",
deleted: false, deleted: false,
expanded: true, expanded: true,
page: 101,
required: true,
content
}); });
}, { }, {
type: "createFrontResult", type: "createFrontResult",
@ -494,7 +501,7 @@ export const createBackResult = async (
) => requestQueue.enqueue(async () => { ) => requestQueue.enqueue(async () => {
const question = useQuestionsStore.getState().questions.find(q => q.id === questionId); const question = useQuestionsStore.getState().questions.find(q => q.id === questionId);
if (!question) return; if (!question) return;
if (question.type !== null) throw new Error("Cannot upgrade already typed question"); if (question.type !== "result") throw new Error("Cannot upgrade already typed question");
try { try {
const createdQuestion = await questionApi.create({ const createdQuestion = await questionApi.create({
@ -523,9 +530,3 @@ export const createBackResult = async (
enqueueSnackbar("Не удалось создать вопрос"); enqueueSnackbar("Не удалось создать вопрос");
} }
}); });
export const updateResult = () => {
}

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

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

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

@ -6,6 +6,7 @@ import {
FormLabel, FormLabel,
Radio, Radio,
RadioGroup, RadioGroup,
useRadioGroup,
Tooltip, Tooltip,
Typography, Typography,
} from "@mui/material"; } from "@mui/material";
@ -13,6 +14,9 @@ import {
import InfoIcon from "@icons/InfoIcon"; import InfoIcon from "@icons/InfoIcon";
import type { QuizQuestionVariant } from "model/questionTypes/variant"; 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 { interface Props {
question: QuizQuestionVariant; question: QuizQuestionVariant;
@ -26,16 +30,18 @@ export default function Variant({ question }: Props) {
}; };
return ( return (
<Box sx={{ <FormControl fullWidth>
display: "flex", <FormLabel id="quiz-question-radio-group" data-cy="question-title" sx={{color: "#000000", marginBottom: "20px", fontSize: "24px", fontWeight: 500}}>
flexDirection: "column", {question.title}
gap: 1, </FormLabel>
}}>
<Typography>{question.title}</Typography>
<RadioGroup <RadioGroup
aria-labelledby="quiz-question-radio-group" aria-labelledby="quiz-question-radio-group"
value={value} value={value}
onChange={handleChange} onChange={handleChange}
sx={{
flexDirection: "row",
gap: "20px"
}}
> >
{question.content.variants {question.content.variants
.filter(({ answer }) => answer) .filter(({ answer }) => answer)
@ -44,11 +50,21 @@ export default function Variant({ question }: Props) {
key={index} key={index}
value={variant.answer} value={variant.answer}
data-cy="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={ control={
<Radio <Radio
inputProps={{ inputProps={{
"data-cy": "variant-radio", "data-cy": "variant-radio",
}} }}
checkedIcon={<RadioCheck />} icon={<RadioIcon />}
/> />
} }
label={ label={
@ -68,15 +84,6 @@ export default function Variant({ question }: Props) {
/> />
))} ))}
</RadioGroup> </RadioGroup>
<img </FormControl>
src={question.content.back}
style={{
display: "block",
objectFit: "scale-down",
alignSelf: "start",
maxWidth: "100%",
}}
/>
</Box>
); );
} }

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