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

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,76 +1,112 @@
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 <>
sx={{ <Box
flexGrow: 1, sx={{
backgroundColor: "white", flexGrow: 1,
p: "20px", backgroundColor: "white",
marginTop: "50px", p: "20px",
borderRadius: "12px", marginTop: "50px",
display: isSmallMonitor ? "block" : "flex", borderRadius: "12px",
flexDirection: isSmallMonitor ? "column" : "row", display: isSmallMonitor ? "block" : "flex",
gap: "20px", flexDirection: isSmallMonitor ? "column" : "row",
boxShadow: `0px 100px 309px rgba(210, 208, 225, 0.24), gap: "20px",
boxShadow: `0px 100px 309px rgba(210, 208, 225, 0.24),
0px 41.7776px 129.093px rgba(210, 208, 225, 0.172525), 0px 41.7776px 129.093px rgba(210, 208, 225, 0.172525),
0px 22.3363px 69.0192px rgba(210, 208, 225, 0.143066), 0px 22.3363px 69.0192px rgba(210, 208, 225, 0.143066),
0px 12.5216px 38.6916px rgba(210, 208, 225, 0.12), 0px 12.5216px 38.6916px rgba(210, 208, 225, 0.12),
0px 6.6501px 20.5488px rgba(210, 208, 225, 0.0969343), 0px 6.6501px 20.5488px rgba(210, 208, 225, 0.0969343),
0px 2.76726px 8.55082px rgba(210, 208, 225, 0.0674749)`, 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 <Box
sx={{ sx={{
display: "flex", mr: !isSmallMonitor ? "104px" : 0,
flexDirection: "column", marginBottom: isSmallMonitor ? "20px" : 0,
justifyContent: "space-between", position: "relative",
height: "100%", height: "100%"
maxHeight: isSmallMonitor ? "none" : "220px",
gap: "25px",
}} }}
> >
<Typography sx={{ color: "#4D4D4D", width: "95%" }}> <Typography variant="h5" sx={{ marginBottom: "20px" }}>
{text} Результаты квиза в зависимости от ответов
</Typography> </Typography>
<Typography <Box
sx={{ sx={{
color: "#9A9AAF", display: "flex",
width: "100%", flexDirection: "column",
justifyContent: "space-between",
height: "100%",
gap: "25px",
}} }}
> >
{text2} <Typography sx={{ color: "#4D4D4D", width: "95%" }}>
</Typography> Вы можете показывать разные результаты квиза (добавьте описание, изображение, стоимость и т.п.) разным пользователям, нужно только их создать и проставить условия. Таким образом ваш квиз получится максимально индивидуальным для каждого клиента. Показывайте картинку/видео вместо результата или переадресовывайте пользователя по нужной ссылке.
</Typography>
<Typography
sx={{
color: "#9A9AAF",
width: "100%",
}}
>
Этот шаг - необязательный, квиз будет работать и без автоматических результатов.
</Typography>
</Box>
</Box> </Box>
<img
src={image}
alt="quiz creation"
style={{
display: "block",
width: isSmallMonitor ? "100%" : "auto",
maxHeight: isSmallMonitor ? "none" : "270px",
}}
/>
</Box> </Box>
<img <Button
src={image} onClick={create}
alt="quiz creation" variant="contained"
style={{ sx={{
display: "block", backgroundColor: "#7E2AEA",
width: isSmallMonitor ? "100%" : "auto", fontSize: "18px",
maxHeight: isSmallMonitor ? "none" : "270px", lineHeight: "18px",
width: "216px",
height: "44px",
mt: "30px",
p: "10px 20px"
}} }}
/> >
</Box> Создать результаты
</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 ResultSettings = () => {
const quiz = useCurrentQuiz()
const [quizExpand, setQuizExpand] = useState(true)
const [resultContract, setResultContract] = useState(true)
export const Setting = () => {
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",
}} }}
> >

@ -1,16 +1,15 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { import {
Box, Box,
Typography, Typography,
RadioGroup, RadioGroup,
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,17 +25,20 @@ export const Page = ({ currentQuestion }: PageProps) => {
}} }}
> >
{currentQuestion.content.picture && ( {currentQuestion.content.picture && (
<img <Box sx={{borderRadius: "12px",
src={currentQuestion.content.picture} border: "1px solid #9A9AAF", overflow: "hidden" }}>
alt="" <img
style={{ src={currentQuestion.content.picture}
display: "block", alt=""
width: "100%", style={{
height: "100%", display: "block",
maxHeight: "80vh", width: "100%",
objectFit: "contain", height: "100%",
}} 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 && (
<img <Box sx={{borderRadius: "12px",
src={question.content.picture} border: "1px solid #9A9AAF", width: "100%", overflow: "hidden"}}>
alt="" <img
style={{ src={question.content.picture}
width: "100%", alt=""
display: "block", style={{
objectFit: "scale-down", display: "block",
flexGrow: 1, width: "100%",
}} height: "100%",
/> objectFit: "contain",
}}
/>
</Box>
)} )}
</Box> </Box>
); );

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

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