diff --git a/src/App.tsx b/src/App.tsx index a3b69afd..d717a830 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,7 +5,7 @@ import "dayjs/locale/ru"; import SigninDialog from "./pages/auth/Signin"; import SignupDialog from "./pages/auth/Signup"; import { ViewPage } from "./pages/ViewPublicationPage"; -import { BrowserRouter, Route, Routes, useLocation, useNavigate, Navigate } from "react-router-dom"; +import { Route, Routes, useLocation, useNavigate, Navigate } from "react-router-dom"; import "./index.css"; import ContactFormPage from "./pages/ContactFormPage/ContactFormPage"; import InstallQuiz from "./pages/InstallQuiz/InstallQuiz"; @@ -21,61 +21,65 @@ import { clearUserData, setUser, useUserStore } from "@root/user"; import { enqueueSnackbar } from "notistack"; import PrivateRoute from "@ui_kit/PrivateRoute"; +import { Restore } from "./pages/startPage/Restore"; dayjs.locale("ru"); const routeslink = [ - { path: "/list", page: , header: false, sidebar: false }, - { path: "/questions/:quizId", page: , header: true, sidebar: true, }, - { path: "/contacts", page: , header: true, sidebar: true }, - { path: "/result", page: , header: true, sidebar: true }, - { path: "/settings", page: , header: true, sidebar: true }, - { path: "/install", page: , header: true, sidebar: true }, + { path: "/list", page: , header: false, sidebar: false }, + { path: "/questions/:quizId", page: , header: true, sidebar: true }, + { path: "/contacts", page: , header: true, sidebar: true }, + { path: "/result", page: , header: true, sidebar: true }, + { path: "/settings", page: , header: true, sidebar: true }, + { path: "/install", page: , header: true, sidebar: true }, ] as const; export default function App() { - const userId = useUserStore((state) => state.userId); - const location = useLocation(); - const navigate = useNavigate(); + const userId = useUserStore((state) => state.userId); + const location = useLocation(); + const navigate = useNavigate(); - useUserFetcher({ - url: `https://hub.pena.digital/user/${userId}`, - userId, - onNewUser: setUser, - onError: (error) => { - const errorMessage = getMessageFromFetchError(error); - if (errorMessage) { - enqueueSnackbar(errorMessage); - clearUserData(); - clearAuthToken(); - } - }, - }); - if (location.state?.redirectTo) - return ; + useUserFetcher({ + url: `https://hub.pena.digital/user/${userId}`, + userId, + onNewUser: setUser, + onError: (error) => { + const errorMessage = getMessageFromFetchError(error); + if (errorMessage) { + enqueueSnackbar(errorMessage); + clearUserData(); + clearAuthToken(); + } + }, + }); + if (location.state?.redirectTo) + return ; - return ( - <> - - {location.state?.backgroundLocation && ( - - } /> - } /> - - )} - - } /> - } /> - } /> - }> - {routeslink.map((e, i) => ( - } /> - ))} - } /> - } /> - } /> - - - - ); -} \ No newline at end of file + return ( + <> + + {location.state?.backgroundLocation && ( + + } /> + } /> + } /> + + )} + + } /> + } /> + } /> + } /> + }> + {routeslink.map((e, i) => ( + } /> + ))} + + } /> + } /> + } /> + + + + ); +} diff --git a/src/pages/Questions/BranchingMap/CsComponent.tsx b/src/pages/Questions/BranchingMap/CsComponent.tsx index 3df6a30e..d07c5b35 100644 --- a/src/pages/Questions/BranchingMap/CsComponent.tsx +++ b/src/pages/Questions/BranchingMap/CsComponent.tsx @@ -51,7 +51,6 @@ type Popper = { type NodeSingularWithPopper = NodeSingular & { popper: (config: PopperConfig) => Popper; }; - let counter = 0; const stylesheet: Stylesheet[] = [ { @@ -161,7 +160,6 @@ function CsComponent({ }, [modalQuestionTargetContentId]) const addNode = ({ parentNodeContentId, targetNodeContentId }: { parentNodeContentId: string, targetNodeContentId?: string }) => { -console.log('AN', counter++, parentNodeContentId,targetNodeContentId) //запрещаем работу родителя-ребенка если это один и тот же вопрос if (parentNodeContentId === targetNodeContentId) return @@ -171,7 +169,6 @@ console.log('AN', counter++, parentNodeContentId,targetNodeContentId) const parentNodeChildren = cy?.$('edge[source = "' + parentNodeContentId + '"]')?.length //если есть инфо о выбранном вопросе из модалки - берём родителя из инфо модалки. Иначе из значения дропа const targetQuestion = { ...getQuestionByContentId(targetNodeContentId || dragQuestionContentId) } as AnyTypedQuizQuestion -console.log('AN1', targetQuestion, parentNodeContentId,parentNodeChildren) if (Object.keys(targetQuestion).length !== 0 && parentNodeContentId && parentNodeChildren !== undefined) { clearDataAfterAddNode({ parentNodeContentId, targetQuestion, parentNodeChildren }) cy?.data('changed', true) @@ -199,13 +196,13 @@ console.log('AN1', targetQuestion, parentNodeContentId,parentNodeChildren) const clearDataAfterAddNode = ({ parentNodeContentId, targetQuestion, parentNodeChildren }: { parentNodeContentId: string, targetQuestion: AnyTypedQuizQuestion, parentNodeChildren: number }) => { -console.log('AN2', parentNodeContentId,parentNodeChildren,targetQuestion) const parentQuestion = { ...getQuestionByContentId(parentNodeContentId) } as AnyTypedQuizQuestion //смотрим не добавлен ли родителю result. Если да - убираем его. Веточкам result не нужен trashQuestions.forEach((targetQuestion) => { if (targetQuestion.type === "result" && targetQuestion.content.rule.parentId === parentQuestion.content.id) { + console.log('deleteQ', targetQuestion.id) deleteQuestion(targetQuestion.id); } }) diff --git a/src/pages/Questions/DraggableList/QuestionPageCard.tsx b/src/pages/Questions/DraggableList/QuestionPageCard.tsx index d4ea5342..73c19b13 100644 --- a/src/pages/Questions/DraggableList/QuestionPageCard.tsx +++ b/src/pages/Questions/DraggableList/QuestionPageCard.tsx @@ -18,18 +18,28 @@ import RatingIcon from "@icons/questionsPage/rating"; import Slider from "@icons/questionsPage/slider"; import ExpandLessIcon from "@mui/icons-material/ExpandLess"; import { - Box, - Checkbox, - FormControl, - FormControlLabel, - IconButton, - InputAdornment, - Paper, - TextField, - useMediaQuery, - useTheme, + Box, + Checkbox, + FormControl, + FormControlLabel, + IconButton, + InputAdornment, + Paper, + TextField, + useMediaQuery, + useTheme, } from "@mui/material"; -import { copyQuestion, createUntypedQuestion, deleteQuestion, clearRuleForAll, toggleExpandQuestion, updateQuestion, updateUntypedQuestion, getQuestionByContentId, deleteQuestionWithTimeout } from "@root/questions/actions"; +import { + copyQuestion, + createUntypedQuestion, + deleteQuestion, + clearRuleForAll, + toggleExpandQuestion, + updateQuestion, + updateUntypedQuestion, + getQuestionByContentId, + deleteQuestionWithTimeout, +} from "@root/questions/actions"; import { updateRootContentId } from "@root/quizes/actions"; import { useRef, useState } from "react"; import type { DraggableProvidedDragHandleProps } from "react-beautiful-dnd"; @@ -44,445 +54,387 @@ import { useCurrentQuiz } from "@root/quizes/hooks"; import { useQuestionsStore } from "@root/questions/store"; interface Props { - question: AnyTypedQuizQuestion | UntypedQuizQuestion; - draggableProps: DraggableProvidedDragHandleProps | null | undefined; - isDragging: boolean; - index: number; + question: AnyTypedQuizQuestion | UntypedQuizQuestion; + draggableProps: DraggableProvidedDragHandleProps | null | undefined; + isDragging: boolean; + index: number; } export default function QuestionsPageCard({ question, draggableProps, isDragging, index }: Props) { - const { questions } = useQuestionsStore(); - const [plusVisible, setPlusVisible] = useState(false); - const [open, setOpen] = useState(false); - const theme = useTheme(); - const isTablet = useMediaQuery(theme.breakpoints.down(1000)); - const isMobile = useMediaQuery(theme.breakpoints.down(790)); - const anchorRef = useRef(null); - const quiz = useCurrentQuiz(); + const { questions } = useQuestionsStore(); + const [plusVisible, setPlusVisible] = useState(false); + const [open, setOpen] = useState(false); + const theme = useTheme(); + const isTablet = useMediaQuery(theme.breakpoints.down(1000)); + const isMobile = useMediaQuery(theme.breakpoints.down(790)); + const anchorRef = useRef(null); + const quiz = useCurrentQuiz(); - const setTitle = useDebouncedCallback((title) => { - const updateQuestionFn = question.type === null ? updateUntypedQuestion : updateQuestion; + const setTitle = useDebouncedCallback((title) => { + const updateQuestionFn = question.type === null ? updateUntypedQuestion : updateQuestion; - updateQuestionFn(question.id, question => { - question.title = title; - }); - }, 200); + updateQuestionFn(question.id, (question) => { + question.title = title; + }); + }, 200); - return ( - <> - - - + + + + setTitle(target.value || " ")} + InputProps={{ + startAdornment: ( + + setOpen((isOpened) => !isOpened)} > - setTitle(target.value || " ")} - InputProps={{ - startAdornment: ( - - setOpen((isOpened) => !isOpened)} - > - {IconAndrom(question.expanded, question.type)} - - setOpen(false)} - anchorRef={anchorRef} - question={question} - questionType={question.type} - /> - - ), - }} - sx={{ - margin: isMobile ? "10px 0" : 0, - "& .MuiInputBase-root": { - color: "#000000", - backgroundColor: question.expanded - ? theme.palette.background.default - : "transparent", - height: "48px", - borderRadius: "10px", - ".MuiOutlinedInput-notchedOutline": { - borderWidth: "1px !important", - border: !question.expanded ? "none" : null, - }, - "& .MuiInputBase-input::placeholder": { - color: "#4D4D4D", - opacity: 0.8, - }, - }, - }} - inputProps={{ - sx: { - fontSize: "18px", - lineHeight: "21px", - py: 0, - paddingLeft: question.type === null ? 0 : "18px", - }, - "data-cy": "quiz-question-title", - }} - /> - - - toggleExpandQuestion(question.id)} - > - {question.expanded ? ( - - ) : ( - - )} - - {question.expanded ? ( - <> - ) : ( - - - } - checkedIcon={} - /> - } - label={""} - sx={{ - color: theme.palette.grey2.main, - ml: "-9px", - mr: 0, - userSelect: "none", - }} - /> - copyQuestion(question.id, question.quizId)} - > - - - { - const deleteFn = () => { - if (question.type !== null) { - if (question.content.rule.parentId === "root") { //удалить из стора root и очистить rule всем вопросам - updateRootContentId(quiz.id, ""); - clearRuleForAll(); - deleteQuestion(question.id); - questions.forEach(q => { - if (q.type === "result") { - deleteQuestion(q.id); - } - }); - } else if (question.content.rule.parentId.length > 0) { //удалить из стора вопрос из дерева и очистить его потомков - const clearQuestions = [] as string[]; - - //записываем потомков , а их результаты удаляем - const getChildren = (parentQuestion: AnyTypedQuizQuestion) => { - questions.forEach((targetQuestion) => { - if (targetQuestion.content.rule.parentId === parentQuestion.content.id) {//если у вопроса совпал родитель с родителем => он потомок, в кучу его - if (targetQuestion.type === "result") { - deleteQuestion(targetQuestion.id); - } else { - if (!clearQuestions.includes(targetQuestion.content.id)) clearQuestions.push(targetQuestion.content.id); - getChildren(targetQuestion); //и ищем его потомков - } - } - }); - }; - getChildren(question); - //чистим потомков от инфы ветвления - clearQuestions.forEach((id) => { - updateQuestion(id, question => { - question.content.rule.parentId = ""; - question.content.rule.main = []; - question.content.rule.default = ""; - }); - }); - - //чистим rule родителя - const parentQuestion = getQuestionByContentId(question.content.rule.parentId); - const newRule = {}; - newRule.main = parentQuestion.content.rule.main.filter((data) => data.next !== question.content.id); //удаляем условия перехода от родителя к этому вопросу - newRule.parentId = parentQuestion.content.rule.parentId; - newRule.default = parentQuestion.content.rule.parentId === question.content.id ? "" : parentQuestion.content.rule.parentId; - newRule.children = [...parentQuestion.content.rule.children].splice(parentQuestion.content.rule.children.indexOf(question.content.id), 1); - - updateQuestion(question.content.rule.parentId, (PQ) => { - PQ.content.rule = newRule; - }); - deleteQuestion(question.id); - } - - - deleteQuestion(question.id); - } else { - console.log("удаляю безтипогово"); - deleteQuestion(question.id); - } - }; - - deleteQuestionWithTimeout(question.id, deleteFn); - }} - data-cy="delete-question" - > - - - - )} - {question.type !== null && - - {question.page + 1} - - } - - - - - - {question.expanded && ( - - {question.type === null ? ( - - ) : ( - - )} - - )} - - setPlusVisible(true)} - onMouseLeave={() => setPlusVisible(false)} - sx={{ - maxWidth: "825px", - display: "flex", - alignItems: "center", - height: "40px", - cursor: "pointer", - }} - > - createUntypedQuestion(question.quizId, question.id)} - sx={{ - display: plusVisible && !isDragging ? "flex" : "none", - width: "100%", - alignItems: "center", - columnGap: "10px", - }} - data-cy="create-question" - > - + setOpen(false)} + anchorRef={anchorRef} + question={question} + questionType={question.type} /> - - - - - ); + + ), + }} + sx={{ + margin: isMobile ? "10px 0" : 0, + "& .MuiInputBase-root": { + color: "#000000", + backgroundColor: question.expanded ? theme.palette.background.default : "transparent", + height: "48px", + borderRadius: "10px", + ".MuiOutlinedInput-notchedOutline": { + borderWidth: "1px !important", + border: !question.expanded ? "none" : null, + }, + "& .MuiInputBase-input::placeholder": { + color: "#4D4D4D", + opacity: 0.8, + }, + }, + }} + inputProps={{ + sx: { + fontSize: "18px", + lineHeight: "21px", + py: 0, + paddingLeft: question.type === null ? 0 : "18px", + }, + "data-cy": "quiz-question-title", + }} + /> + + + toggleExpandQuestion(question.id)} + > + {question.expanded ? ( + + ) : ( + + )} + + {question.expanded ? ( + <> + ) : ( + + + } + checkedIcon={} + /> + } + label={""} + sx={{ + color: theme.palette.grey2.main, + ml: "-9px", + mr: 0, + userSelect: "none", + }} + /> + copyQuestion(question.id, question.quizId)}> + + + { + const deleteFn = () => { + if (question.type !== null) { + if (question.content.rule.parentId === "root") { + //удалить из стора root и очистить rule всем вопросам + updateRootContentId(quiz.id, ""); + clearRuleForAll(); + deleteQuestion(question.id); + questions.forEach((q) => { + if (q.type === "result") { + deleteQuestion(q.id); + } + }); + } else if (question.content.rule.parentId.length > 0) { + //удалить из стора вопрос из дерева и очистить его потомков + const clearQuestions = [] as string[]; + + //записываем потомков , а их результаты удаляем + const getChildren = (parentQuestion: AnyTypedQuizQuestion) => { + questions.forEach((targetQuestion) => { + if (targetQuestion.content.rule.parentId === parentQuestion.content.id) { + //если у вопроса совпал родитель с родителем => он потомок, в кучу его + if (targetQuestion.type === "result") { + deleteQuestion(targetQuestion.id); + } else { + if (!clearQuestions.includes(targetQuestion.content.id)) + clearQuestions.push(targetQuestion.content.id); + getChildren(targetQuestion); //и ищем его потомков + } + } + }); + }; + getChildren(question); + //чистим потомков от инфы ветвления + clearQuestions.forEach((id) => { + updateQuestion(id, (question) => { + question.content.rule.parentId = ""; + question.content.rule.main = []; + question.content.rule.default = ""; + }); + }); + + //чистим rule родителя + const parentQuestion = getQuestionByContentId(question.content.rule.parentId); + const newRule = {}; + newRule.main = parentQuestion.content.rule.main.filter( + (data) => data.next !== question.content.id + ); //удаляем условия перехода от родителя к этому вопросу + newRule.parentId = parentQuestion.content.rule.parentId; + newRule.default = + parentQuestion.content.rule.parentId === question.content.id + ? "" + : parentQuestion.content.rule.parentId; + newRule.children = [...parentQuestion.content.rule.children].splice( + parentQuestion.content.rule.children.indexOf(question.content.id), + 1 + ); + + updateQuestion(question.content.rule.parentId, (PQ) => { + PQ.content.rule = newRule; + }); + deleteQuestion(question.id); + } + + deleteQuestion(question.id); + } else { + console.log("удаляю безтипогово"); + deleteQuestion(question.id); + } + }; + + deleteQuestionWithTimeout(question.id, deleteFn); + }} + data-cy="delete-question" + > + + + + )} + {question.type !== null && ( + + {question.page + 1} + + )} + + + + + + {question.expanded && ( + + {question.type === null ? ( + + ) : ( + + )} + + )} + + setPlusVisible(true)} + onMouseLeave={() => setPlusVisible(false)} + sx={{ + maxWidth: "825px", + display: "flex", + alignItems: "center", + height: "40px", + cursor: "pointer", + }} + > + createUntypedQuestion(question.quizId, question.id)} + sx={{ + display: plusVisible && !isDragging ? "flex" : "none", + width: "100%", + alignItems: "center", + columnGap: "10px", + }} + data-cy="create-question" + > + + + + + + ); } const IconAndrom = (isExpanded: boolean, questionType: QuestionType | null) => { - switch (questionType) { - case "variant": - return ( - - ); - case "images": - return ( - - ); - case "varimg": - return ( - - ); - case "emoji": - return ( - - ); - case "text": - return ( - - ); - case "select": - return ( - - ); - case "date": - return ( - - ); - case "number": - return ( - - ); - case "file": - return ( - - ); - case "page": - return ( - - ); - case "rating": - return ( - - ); - default: - return <>; - } + switch (questionType) { + case "variant": + return ; + case "images": + return ; + case "varimg": + return ; + case "emoji": + return ; + case "text": + return ; + case "select": + return ; + case "date": + return ; + case "number": + return ; + case "file": + return ; + case "page": + return ; + case "rating": + return ; + default: + return <>; + } }; diff --git a/src/pages/Questions/Form/FormDraggableList/FormDraggableListItem.tsx b/src/pages/Questions/Form/FormDraggableList/FormDraggableListItem.tsx index 61b62039..d8e89df0 100644 --- a/src/pages/Questions/Form/FormDraggableList/FormDraggableListItem.tsx +++ b/src/pages/Questions/Form/FormDraggableList/FormDraggableListItem.tsx @@ -5,80 +5,77 @@ import { Draggable } from "react-beautiful-dnd"; import { AnyTypedQuizQuestion, UntypedQuizQuestion } from "../../../../model/questionTypes/shared"; import QuestionsPageCard from "./QuestionPageCard"; - type FormDraggableListItemProps = { - question: AnyTypedQuizQuestion | UntypedQuizQuestion; - questionIndex: number; + question: AnyTypedQuizQuestion | UntypedQuizQuestion; + questionIndex: number; }; -export default memo( - ({ question, questionIndex }: FormDraggableListItemProps) => { - const theme = useTheme(); +export default memo(({ question, questionIndex }: FormDraggableListItemProps) => { + const theme = useTheme(); - return ( - - {(provided) => ( - - {question.deleted ? ( - - - Вопрос удалён. - - { - updateQuestion(question.id, question => { - question.deleted = false; - }); - }} - sx={{ - cursor: "pointer", - fontSize: "16px", - textDecoration: "underline", - color: theme.palette.brightPurple.main, - textDecorationColor: theme.palette.brightPurple.main, - }} - > - Восстановить? - - - ) : ( - - - - )} - - )} - - ); - } -); + return ( + + {(provided) => ( + + {question.deleted ? ( + + + Вопрос удалён. + + { + updateQuestion(question.id, (question) => { + question.deleted = false; + }); + }} + sx={{ + cursor: "pointer", + fontSize: "16px", + textDecoration: "underline", + color: theme.palette.brightPurple.main, + textDecorationColor: theme.palette.brightPurple.main, + }} + > + Восстановить? + + + ) : ( + + + + )} + + )} + + ); +}); diff --git a/src/pages/Questions/Form/FormDraggableList/QuestionPageCard.tsx b/src/pages/Questions/Form/FormDraggableList/QuestionPageCard.tsx index 89e8006e..d4f140f5 100644 --- a/src/pages/Questions/Form/FormDraggableList/QuestionPageCard.tsx +++ b/src/pages/Questions/Form/FormDraggableList/QuestionPageCard.tsx @@ -11,8 +11,8 @@ import OptionsPict from "@icons/questionsPage/options_pict"; import Page from "@icons/questionsPage/page"; import RatingIcon from "@icons/questionsPage/rating"; import Slider from "@icons/questionsPage/slider"; -import { Box, InputAdornment, Paper } from "@mui/material"; -import { updateQuestion, updateUntypedQuestion } from "@root/questions/actions"; +import { Box, FormControlLabel, IconButton, InputAdornment, Paper, useMediaQuery, useTheme } from "@mui/material"; +import { toggleExpandQuestion, updateQuestion, updateUntypedQuestion } from "@root/questions/actions"; import CustomTextField from "@ui_kit/CustomTextField"; import { useRef, useState } from "react"; import type { DraggableProvidedDragHandleProps } from "react-beautiful-dnd"; @@ -22,142 +22,206 @@ import SwitchQuestionsPage from "../../SwitchQuestionsPage"; import { ChooseAnswerModal } from "./ChooseAnswerModal"; import FormTypeQuestions from "../FormTypeQuestions"; import { QuestionType } from "@model/question/question"; - +import { CrossedEyeIcon } from "@icons/CrossedEyeIcon"; +import { ArrowDownIcon } from "@icons/questionsPage/ArrowDownIcon"; +import { CopyIcon } from "@icons/questionsPage/CopyIcon"; +import { DeleteIcon } from "@icons/questionsPage/deleteIcon"; +import { HideIcon } from "@icons/questionsPage/hideIcon"; +import ExpandLessIcon from "@mui/icons-material/ExpandLess"; +import { NoLuggageOutlined, SignalCellularNullOutlined } from "@mui/icons-material"; interface Props { - question: AnyTypedQuizQuestion | UntypedQuizQuestion; - questionIndex: number; - draggableProps: DraggableProvidedDragHandleProps | null | undefined; + question: AnyTypedQuizQuestion | UntypedQuizQuestion; + questionIndex: number; + draggableProps: DraggableProvidedDragHandleProps | null | undefined; } -export default function QuestionsPageCard({ - question, - questionIndex, - draggableProps, -}: Props) { - const [open, setOpen] = useState(false); - const anchorRef = useRef(null); +export default function QuestionsPageCard({ question, questionIndex, draggableProps }: Props) { + const [open, setOpen] = useState(false); + const anchorRef = useRef(null); + const theme = useTheme(); + const isTablet = useMediaQuery(theme.breakpoints.down(1000)); + const isMobile = useMediaQuery(theme.breakpoints.down(790)); - const setTitle = useDebouncedCallback((title) => { - const updateQuestionFn = question.type === null ? updateUntypedQuestion : updateQuestion; + const setTitle = useDebouncedCallback((title) => { + const updateQuestionFn = question.type === null ? updateUntypedQuestion : updateQuestion; - updateQuestionFn(question.id, question => { - question.title = title; - }); - }, 200); + updateQuestionFn(question.id, (question) => { + question.title = title; + }); + }, 200); - return ( - <> - - - setTitle(target.value)} - sx={{ margin: "20px", width: "auto" }} - InputProps={{ - startAdornment: ( - - setOpen((isOpened) => !isOpened)} - > - {IconAndrom(question.type)} - - setOpen(false)} - anchorRef={anchorRef} - question={question} - questionType={question.type} - /> - - ), - endAdornment: ( - - {questionIndex !== 0 && ( - - - - )} - - ), - }} + return ( + <> + + + + setTitle(target.value)} + sx={{ width: "100%" }} + InputProps={{ + startAdornment: ( + + setOpen((isOpened) => !isOpened)} + > + {IconAndrom(question.type)} + + setOpen(false)} + anchorRef={anchorRef} + question={question} + questionType={question.type} /> - {question.type === null ? ( - - ) : ( - - )} + + ), + }} + /> + + + toggleExpandQuestion(question.id)} + > + {question.expanded ? ( + + ) : ( + + )} + + + + {questionIndex + 1} - - - ); + + + + + + + + + {question.type === null ? ( + + ) : ( + + )} + + + + ); } const IconAndrom = (questionType: QuestionType | null) => { - switch (questionType) { - case "variant": - return ; - case "images": - return ( - - ); - case "varimg": - return ( - - ); - case "emoji": - return ; - case "text": - return ; - case "select": - return ( - - ); - case "date": - return ; - case "number": - return ; - case "file": - return ( - - ); - case "page": - return ; - case "rating": - return ( - - ); - default: - return ( - - ); - } + switch (questionType) { + case "variant": + return ; + case "images": + return ; + case "varimg": + return ; + case "emoji": + return ; + case "text": + return ; + case "select": + return ; + case "date": + return ; + case "number": + return ; + case "file": + return ; + case "page": + return ; + case "rating": + return ; + default: + return ; + } }; diff --git a/src/pages/Questions/Form/FormDraggableList/index.tsx b/src/pages/Questions/Form/FormDraggableList/index.tsx index 9eda5284..77c01d28 100644 --- a/src/pages/Questions/Form/FormDraggableList/index.tsx +++ b/src/pages/Questions/Form/FormDraggableList/index.tsx @@ -5,31 +5,25 @@ import { DragDropContext, Droppable } from "react-beautiful-dnd"; import FormDraggableListItem from "./FormDraggableListItem"; import { useQuestions } from "@root/questions/hooks"; - export const FormDraggableList = () => { + const { questions } = useQuestions(); - const { questions } = useQuestions() - - const onDragEnd = ({ destination, source }: DropResult) => { - if (destination) reorderQuestions(source.index, destination.index); - }; + const onDragEnd = ({ destination, source }: DropResult) => { + if (destination) reorderQuestions(source.index, destination.index); + }; - return ( - - - {(provided) => ( - - {questions?.map((question, index) => ( - - ))} - {provided.placeholder} - - )} - - - ); + return ( + + + {(provided) => ( + + {questions?.map((question, index) => ( + + ))} + {provided.placeholder} + + )} + + + ); }; diff --git a/src/pages/Questions/Form/FormQuestionsPage.tsx b/src/pages/Questions/Form/FormQuestionsPage.tsx index d4e138fe..baca3a95 100644 --- a/src/pages/Questions/Form/FormQuestionsPage.tsx +++ b/src/pages/Questions/Form/FormQuestionsPage.tsx @@ -8,106 +8,103 @@ import { FormDraggableList } from "./FormDraggableList"; import { collapseAllQuestions, createUntypedQuestion } from "@root/questions/actions"; import { useCurrentQuiz } from "@root/quizes/hooks"; - export default function FormQuestionsPage() { - const theme = useTheme(); - const quiz = useCurrentQuiz(); + const theme = useTheme(); + const quiz = useCurrentQuiz(); - if (!quiz) return null; + if (!quiz) return null; - return ( - <> - - Заголовок анкеты - - - - - { - createUntypedQuestion(quiz.backendId); - }} - data-cy="create-question" - > - - - Добавить еще один вопрос - - - - - - - - {createPortal(, document.body)} - - ); + return ( + <> + + Заголовок анкеты + + + + + { + createUntypedQuestion(quiz.backendId); + }} + data-cy="create-question" + > + + Добавить еще один вопрос + + + + + + + {createPortal(, document.body)} + + ); } diff --git a/src/pages/Questions/Form/FormTypeQuestions.tsx b/src/pages/Questions/Form/FormTypeQuestions.tsx index a1bc3907..b0cd4fe7 100644 --- a/src/pages/Questions/Form/FormTypeQuestions.tsx +++ b/src/pages/Questions/Form/FormTypeQuestions.tsx @@ -12,80 +12,75 @@ import Slider from "../../../assets/icons/questionsPage/slider"; import { QuestionType } from "@model/question/question"; import { createTypedQuestion } from "@root/questions/actions"; -import type { - UntypedQuizQuestion -} from "../../../model/questionTypes/shared"; - +import type { UntypedQuizQuestion } from "../../../model/questionTypes/shared"; type ButtonTypeQuestion = { - icon: JSX.Element; - title: string; - value: QuestionType; + icon: JSX.Element; + title: string; + value: QuestionType; }; const BUTTON_TYPE_SHORT_QUESTIONS: ButtonTypeQuestion[] = [ - { - icon: , - title: "Варианты ответов", - value: "variant", - }, - { - icon: , - title: "Своё поле для ввода", - value: "text", - }, - { - icon: , - title: "Выпадающий список", - value: "select", - }, - { - icon: , - title: "Дата", - value: "date", - }, - { - icon: , - title: "Ползунок", - value: "number", - }, - { - icon: , - title: "Загрузка файла", - value: "file", - }, + { + icon: , + title: "Варианты ответов", + value: "variant", + }, + { + icon: , + title: "Своё поле для ввода", + value: "text", + }, + { + icon: , + title: "Выпадающий список", + value: "select", + }, + { + icon: , + title: "Дата", + value: "date", + }, + { + icon: , + title: "Ползунок", + value: "number", + }, + { + icon: , + title: "Загрузка файла", + value: "file", + }, ]; interface Props { - question: UntypedQuizQuestion; + question: UntypedQuizQuestion; } export default function FormTypeQuestions({ question }: Props) { - - return ( - - - {(("page" in question) && question.page === 0 - ? BUTTON_TYPE_QUESTIONS - : BUTTON_TYPE_SHORT_QUESTIONS - ).map(({ icon, title, value: questionType }) => ( - { - createTypedQuestion(question.id, questionType); - }} - icon={icon} - text={title} - /> - ))} - - - ); + return ( + + + {("page" in question && question.page === 0 ? BUTTON_TYPE_QUESTIONS : BUTTON_TYPE_SHORT_QUESTIONS).map( + ({ icon, title, value: questionType }) => ( + { + createTypedQuestion(question.id, questionType); + }} + icon={icon} + text={title} + /> + ) + )} + + + ); } diff --git a/src/pages/Questions/QuestionsPage.tsx b/src/pages/Questions/QuestionsPage.tsx index 20b72b5c..e1593a9f 100755 --- a/src/pages/Questions/QuestionsPage.tsx +++ b/src/pages/Questions/QuestionsPage.tsx @@ -1,12 +1,5 @@ -import { useState, useEffect, useLayoutEffect, useRef } from "react" -import { - Box, - Button, - IconButton, - Typography, - useMediaQuery, - useTheme, -} from "@mui/material"; +import { useState, useEffect, useLayoutEffect, useRef } from "react"; +import { Box, Button, IconButton, Typography, useMediaQuery, useTheme } from "@mui/material"; import { collapseAllQuestions, createUntypedQuestion } from "@root/questions/actions"; import { decrementCurrentStep, incrementCurrentStep } from "@root/quizes/actions"; import { useCurrentQuiz } from "@root/quizes/hooks"; @@ -14,105 +7,102 @@ import QuizPreview from "@ui_kit/QuizPreview/QuizPreview"; import { createPortal } from "react-dom"; import AddPlus from "../../assets/icons/questionsPage/addPlus"; import ArrowLeft from "../../assets/icons/questionsPage/arrowLeft"; -import BranchingQuestions from "./BranchingModal/BranchingQuestionsModal" +import BranchingQuestions from "./BranchingModal/BranchingQuestionsModal"; import { QuestionSwitchWindowTool } from "./QuestionSwitchWindowTool"; import { useQuestionsStore } from "@root/questions/store"; import { updateOpenBranchingPanel, updateEditSomeQuestion } from "@root/uiTools/actions"; import { useUiTools } from "@root/uiTools/store"; export default function QuestionsPage() { - const theme = useTheme(); - const { openedModalSettingsId, openBranchingPanel } = useUiTools(); - const isMobile = useMediaQuery(theme.breakpoints.down(660)); - const quiz = useCurrentQuiz(); - useLayoutEffect(() => { - updateOpenBranchingPanel(false) - updateEditSomeQuestion() - },[]) + const theme = useTheme(); + const { openedModalSettingsId, openBranchingPanel } = useUiTools(); + const isMobile = useMediaQuery(theme.breakpoints.down(660)); + const quiz = useCurrentQuiz(); + useLayoutEffect(() => { + updateOpenBranchingPanel(false); + updateEditSomeQuestion(); + }, []); - const ref = useRef() - if (!quiz) return null; + const ref = useRef(); + if (!quiz) return null; + return ( + <> + + {quiz.name ? quiz.name : "Заголовок квиза"} + + + + + { + createUntypedQuestion(quiz.backendId); + }} + sx={{ + position: "fixed", + left: isMobile ? "20px" : "250px", + bottom: isMobile ? "140px" : "20px", + }} + data-cy="create-question" + > + + - return ( - <> - - { - quiz.name ? quiz.name : "Заголовок квиза" } - - - - - { - createUntypedQuestion(quiz.backendId); - }} - sx={{ - position: "fixed", - left: isMobile ? "20px" : "250px", - bottom: isMobile ? "140px" : "20px", - }} - data-cy="create-question" - > - - - - - - - - - - {createPortal(, document.body)} - {openedModalSettingsId !== null && } - - ); + + + + + + {createPortal(, document.body)} + {openedModalSettingsId !== null && } + + ); } diff --git a/src/pages/Questions/SwitchQuestionsPage.tsx b/src/pages/Questions/SwitchQuestionsPage.tsx index 31e29c15..44911e35 100644 --- a/src/pages/Questions/SwitchQuestionsPage.tsx +++ b/src/pages/Questions/SwitchQuestionsPage.tsx @@ -12,48 +12,46 @@ import UploadFile from "./UploadFile/UploadFile"; import AnswerOptions from "./answerOptions/AnswerOptions"; import { notReachable } from "../../utils/notReachable"; - interface Props { - question: AnyTypedQuizQuestion; + question: AnyTypedQuizQuestion; } export default function SwitchQuestionsPage({ question }: Props) { + switch (question.type) { + case "variant": + return ; - switch (question.type) { - case "variant": - return ; + case "images": + return ; - case "images": - return ; + case "varimg": + return ; - case "varimg": - return ; + case "emoji": + return ; - case "emoji": - return ; + case "text": + return ; - case "text": - return ; + case "select": + return ; - case "select": - return ; + case "date": + return ; - case "date": - return ; + case "number": + return ; - case "number": - return ; + case "file": + return ; - case "file": - return ; + case "page": + return ; - case "page": - return ; + case "rating": + return ; - case "rating": - return ; - - default: - notReachable(question) - } + default: + notReachable(question); + } } diff --git a/src/pages/Questions/UploadImage/UploadImageModal.tsx b/src/pages/Questions/UploadImage/UploadImageModal.tsx index 39b63879..d722e81f 100644 --- a/src/pages/Questions/UploadImage/UploadImageModal.tsx +++ b/src/pages/Questions/UploadImage/UploadImageModal.tsx @@ -4,14 +4,23 @@ import SearchIcon from "../../../assets/icons/SearchIcon"; import UnsplashIcon from "../../../assets/icons/Unsplash.svg"; import { useRef, useState, type DragEvent } from "react"; +type ImageFormat = "jpg" | "jpeg" | "png" | "gif"; + interface ModalkaProps { isOpen: boolean; onClose: () => void; handleImageChange: (file: File) => void; description?: string; + accept?: ImageFormat[]; } -export const UploadImageModal: React.FC = ({ handleImageChange, isOpen, onClose, description }) => { +export const UploadImageModal: React.FC = ({ + handleImageChange, + isOpen, + onClose, + accept, + description, +}) => { const theme = useTheme(); const dropZone = useRef(null); const [ready, setReady] = useState(false); @@ -31,6 +40,10 @@ export const UploadImageModal: React.FC = ({ handleImageChange, is handleImageChange(file); }; + const acceptedFormats = accept ? accept.map((format) => "." + format).join(", ") : ""; + + console.log(acceptedFormats); + return ( = ({ handleImageChange, is event.target.files?.[0] && handleImageChange(event.target.files[0])} hidden - accept=".jpg, .jpeg, .png" + accept={acceptedFormats || ".jpg, .jpeg, .png , .gif"} multiple type="file" data-cy="upload-image-input" diff --git a/src/pages/auth/Signin.tsx b/src/pages/auth/Signin.tsx index 41da4624..3fc929d5 100644 --- a/src/pages/auth/Signin.tsx +++ b/src/pages/auth/Signin.tsx @@ -1,18 +1,9 @@ import { login } from "@api/auth"; import CloseIcon from "@mui/icons-material/Close"; -import { - Box, - Button, - Dialog, - IconButton, - Link, - Typography, - useMediaQuery, - useTheme, -} from "@mui/material"; +import { Box, Button, Dialog, IconButton, Link, Typography, useMediaQuery, useTheme } from "@mui/material"; import { setUserId, useUserStore } from "@root/user"; import InputTextfield from "@ui_kit/InputTextfield"; -import PenaLogo2 from "@ui_kit/PenaLogo2"; +import Logotip from "../../pages/Landing/images/icons/QuizLogo"; import PasswordInput from "@ui_kit/passwordInput"; import { useFormik } from "formik"; import { enqueueSnackbar } from "notistack"; @@ -31,9 +22,7 @@ const initialValues: Values = { }; const validationSchema = object({ - email: string() - .required("Поле обязательно") - .email("Введите корректный email"), + email: string().required("Поле обязательно").email("Введите корректный email"), password: string().required("Поле обязательно").min(8, "Минимум 8 символов"), }); @@ -49,10 +38,7 @@ export default function SigninDialog() { initialValues, validationSchema, onSubmit: async (values, formikHelpers) => { - const [loginResponse, loginError] = await login( - values.email.trim(), - values.password.trim() - ); + const [loginResponse, loginError] = await login(values.email.trim(), values.password.trim()); formikHelpers.setSubmitting(false); @@ -111,8 +97,7 @@ export default function SigninDialog() { gap: "15px", borderRadius: "12px", boxShadow: "0px 15px 80px rgb(210 208 225 / 70%)", - "& .MuiFormHelperText-root.Mui-error, & .MuiFormHelperText-root.Mui-error.MuiFormHelperText-filled": - { + "& .MuiFormHelperText-root.Mui-error, & .MuiFormHelperText-root.Mui-error.MuiFormHelperText-filled": { position: "absolute", top: "46px", margin: "0", @@ -130,7 +115,7 @@ export default function SigninDialog() { - + - - Вы еще не присоединились? - - + Вы еще не присоединились? + Регистрация diff --git a/src/pages/auth/Signup.tsx b/src/pages/auth/Signup.tsx index 636806e7..0359a97d 100644 --- a/src/pages/auth/Signup.tsx +++ b/src/pages/auth/Signup.tsx @@ -1,23 +1,14 @@ import { register } from "@api/auth"; import CloseIcon from "@mui/icons-material/Close"; -import { - Box, - Button, - Dialog, - IconButton, - Link, - Typography, - useMediaQuery, - useTheme, -} from "@mui/material"; +import { Box, Button, Dialog, IconButton, Link, Typography, useMediaQuery, useTheme } from "@mui/material"; import { setUserId, useUserStore } from "@root/user"; import InputTextfield from "@ui_kit/InputTextfield"; -import PenaLogo2 from "@ui_kit/PenaLogo2"; +import Logotip from "../../pages/Landing/images/icons/QuizLogo"; import PasswordInput from "@ui_kit/passwordInput"; import { useFormik } from "formik"; import { enqueueSnackbar } from "notistack"; import { useEffect, useState } from "react"; -import {Link as RouterLink, useLocation, useNavigate} from "react-router-dom"; +import { Link as RouterLink, useLocation, useNavigate } from "react-router-dom"; import { object, ref, string } from "yup"; interface Values { @@ -33,9 +24,7 @@ const initialValues: Values = { }; const validationSchema = object({ - email: string() - .required("Поле обязательно") - .email("Введите корректный email"), + email: string().required("Поле обязательно").email("Введите корректный email"), password: string() .min(8, "Минимум 8 символов") .matches(/^[.,:;-_+\d\w]+$/, "Некорректные символы") @@ -50,18 +39,14 @@ export default function SignupDialog() { const user = useUserStore((state) => state.user); const theme = useTheme(); const upMd = useMediaQuery(theme.breakpoints.up("md")); - const location = useLocation() + const location = useLocation(); const navigate = useNavigate(); const formik = useFormik({ initialValues, validationSchema, onSubmit: async (values, formikHelpers) => { - const [registerResponse, registerError] = await register( - values.email.trim(), - values.password.trim(), - "+7" - ); + const [registerResponse, registerError] = await register(values.email.trim(), values.password.trim(), "+7"); formikHelpers.setSubmitting(false); @@ -120,12 +105,11 @@ export default function SignupDialog() { gap: "15px", borderRadius: "12px", boxShadow: "0px 15px 80px rgb(210 208 225 / 70%)", - "& .MuiFormHelperText-root.Mui-error, & .MuiFormHelperText-root.Mui-error.MuiFormHelperText-filled": - { - position: "absolute", - top: "46px", - margin: "0", - }, + "& .MuiFormHelperText-root.Mui-error, & .MuiFormHelperText-root.Mui-error.MuiFormHelperText-filled": { + position: "absolute", + top: "46px", + margin: "0", + }, }} > - + !imageUrl && setIsDropReady(true)} diff --git a/src/pages/startPage/Restore.tsx b/src/pages/startPage/Restore.tsx new file mode 100644 index 00000000..01f00a8f --- /dev/null +++ b/src/pages/startPage/Restore.tsx @@ -0,0 +1,189 @@ +import { FC, useState } from "react"; +import CloseIcon from "@mui/icons-material/Close"; +import { Box, Button, Dialog, IconButton, Typography, useMediaQuery, useTheme } from "@mui/material"; +import InputTextfield from "@ui_kit/InputTextfield"; +import PasswordInput from "@ui_kit/passwordInput"; +import { useFormik } from "formik"; +import { object, ref, string } from "yup"; +import Logotip from "../Landing/images/icons/QuizLogo"; +import { useNavigate } from "react-router-dom"; + +interface Values { + email: string; + password: string; + repeatPassword: string; +} + +const initialValues: Values = { + email: "", + password: "", + repeatPassword: "", +}; + +const validationSchema = object({ + email: string().required("Поле обязательно").email("Введите корректный email"), + password: string() + .min(8, "Минимум 8 символов") + .matches(/^[.,:;-_+\d\w]+$/, "Некорректные символы") + .required("Поле обязательно"), + repeatPassword: string() + .oneOf([ref("password"), undefined], "Пароли не совпадают") + .required("Повторите пароль"), +}); + +export const Restore: FC = () => { + const [isDialogOpen, setIsDialogOpen] = useState(true); + const navigate = useNavigate(); + const theme = useTheme(); + const upMd = useMediaQuery(theme.breakpoints.up("md")); + + const formik = useFormik({ + initialValues, + validationSchema, + onSubmit: (values) => { + console.log(values); + }, + }); + + function handleClose() { + setIsDialogOpen(false); + setTimeout(() => navigate("/"), theme.transitions.duration.leavingScreen); + } + + return ( + + + + + + + + + + + + Восстановление пароля + + + + + + + + + ); +};