diff --git a/src/App.tsx b/src/App.tsx index 825ef5c9..6448e864 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -12,10 +12,10 @@ import InstallQuiz from "./pages/InstallQuiz/InstallQuiz"; import Landing from "./pages/Landing/Landing"; import QuestionsPage from "./pages/Questions/QuestionsPage"; 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 Main from "./pages/main"; -import StartPage from "./pages/startPage/StartPage"; +import EditPage from "./pages/startPage/EditPage"; import { clearAuthToken, getMessageFromFetchError, useUserFetcher } from "@frontend/kitui"; import { clearUserData, setUser, useUserStore } from "@root/user"; import { enqueueSnackbar } from "notistack"; @@ -28,7 +28,7 @@ const routeslink = [ { path: "/questions/:quizId", page: , 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: "/settings", page: , header: true, sidebar: true }, { path: "/install", page: , header: true, sidebar: true }, ] as const; @@ -57,7 +57,7 @@ export default function App() { {routeslink.map((e, i) => ( } /> ))} - } /> + } /> } /> } /> } /> diff --git a/src/assets/Rectangle 110.png b/src/assets/Rectangle 110.png index 8fe97c9e..1bf27f89 100644 Binary files a/src/assets/Rectangle 110.png and b/src/assets/Rectangle 110.png differ diff --git a/src/assets/icons/ArrowGear.svg b/src/assets/icons/ArrowGear.svg index 442cb590..9eef58be 100644 --- a/src/assets/icons/ArrowGear.svg +++ b/src/assets/icons/ArrowGear.svg @@ -1,5 +1,5 @@ - + diff --git a/src/assets/icons/ExpandLessIconBG.tsx b/src/assets/icons/ExpandLessIconBG.tsx new file mode 100644 index 00000000..0fd285c0 --- /dev/null +++ b/src/assets/icons/ExpandLessIconBG.tsx @@ -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 ( + + + + + + + ); +} \ No newline at end of file diff --git a/src/assets/icons/checked.svg b/src/assets/icons/checked.svg index b0c0977e..3266d15e 100644 --- a/src/assets/icons/checked.svg +++ b/src/assets/icons/checked.svg @@ -1,4 +1,4 @@ - + diff --git a/src/constants/result.ts b/src/constants/result.ts index 0178a3fe..bc043b3f 100644 --- a/src/constants/result.ts +++ b/src/constants/result.ts @@ -8,19 +8,10 @@ export const QUIZ_QUESTION_RESULT: Omit type: "result", content: { ...QUIZ_QUESTION_BASE.content, - multi: false, - own: false, - innerNameCheck: false, + video: "", innerName: "", - required: false, - variants: [ - { - id: nanoid(), - answer: "", - extendedText: "", - hints: "", - originalImageUrl: "", - }, - ], + text: "", + price: [0], + rangePrice: false }, }; diff --git a/src/model/questionTypes/result.ts b/src/model/questionTypes/result.ts index 6c1f4536..2a325ee5 100644 --- a/src/model/questionTypes/result.ts +++ b/src/model/questionTypes/result.ts @@ -1,29 +1,22 @@ import type { QuizQuestionBase, - QuestionVariant, + QuestionBranchingRule, QuestionHint, - PreviewRule, } from "./shared"; export interface QuizQuestionResult extends QuizQuestionBase { type: "result"; content: { id: string; - /** Чекбокс "Можно несколько" */ - multi: boolean; - /** Чекбокс "Вариант "свой ответ"" */ - own: boolean; - /** Чекбокс "Внутреннее название вопроса" */ - innerNameCheck: boolean; - /** Поле "Внутреннее название вопроса" */ - innerName: string; - /** Чекбокс "Необязательный вопрос" */ - required: boolean; - variants: QuestionVariant[]; - hint: QuestionHint; - rule: PreviewRule; back: string; originalBack: string; + video: string; + innerName: string; + text: string; + price: [number] | [number, number]; + rangePrice: boolean; + rule: QuestionBranchingRule, + hint: QuestionHint; autofill: boolean; }; } diff --git a/src/model/questionTypes/shared.ts b/src/model/questionTypes/shared.ts index 05524770..e0607c83 100644 --- a/src/model/questionTypes/shared.ts +++ b/src/model/questionTypes/shared.ts @@ -10,6 +10,7 @@ import type { QuizQuestionSelect } from "./select"; import type { QuizQuestionText } from "./text"; import type { QuizQuestionVariant } from "./variant"; import type { QuizQuestionVarImg } from "./varimg"; +import type { QuizQuestionResult } from "./result"; import { nanoid } from "nanoid"; export interface QuestionBranchingRuleMain { @@ -92,7 +93,8 @@ export type AnyTypedQuizQuestion = | QuizQuestionNumber | QuizQuestionFile | QuizQuestionPage - | QuizQuestionRating; + | QuizQuestionRating + | QuizQuestionResult; type FilterQuestionsWithVariants = T extends { content: { variants: QuestionVariant[]; }; diff --git a/src/model/quizSettings.ts b/src/model/quizSettings.ts index c2a84ffa..34c20ce7 100644 --- a/src/model/quizSettings.ts +++ b/src/model/quizSettings.ts @@ -33,6 +33,14 @@ export interface QuizConfig { startpageType: QuizStartpageType; results: QuizResultsType; haveRoot: string | null; + resultInfo: { + when: 'before' | 'after' | 'email', + share: true | false, + replay: true | false, + theme: string, + reply: string, + replname: string, + } startpage: { description: string; button: string; @@ -67,6 +75,14 @@ export const defaultQuizConfig: QuizConfig = { startpageType: null, results: null, haveRoot: null, + resultInfo: { + when: 'after', + share: false, + replay: false, + theme: "", + reply: "", + replname: "", + }, startpage: { description: "", button: "", diff --git a/src/pages/Questions/BranchingMap/CsComponent.tsx b/src/pages/Questions/BranchingMap/CsComponent.tsx index cf7d85ef..1934832c 100644 --- a/src/pages/Questions/BranchingMap/CsComponent.tsx +++ b/src/pages/Questions/BranchingMap/CsComponent.tsx @@ -122,7 +122,9 @@ function CsComponent ({ }: Props) { 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 [startRemove, setStartRemove] = useState(""); diff --git a/src/pages/Questions/BranchingMap/FirstNodeField.tsx b/src/pages/Questions/BranchingMap/FirstNodeField.tsx index ca5c78fa..a72ed3c2 100644 --- a/src/pages/Questions/BranchingMap/FirstNodeField.tsx +++ b/src/pages/Questions/BranchingMap/FirstNodeField.tsx @@ -12,7 +12,7 @@ interface Props { } export const FirstNodeField = ({ setOpenedModalQuestions, modalQuestionTargetContentId }: Props) => { const quiz = useCurrentQuiz(); - const { dragQuestionContentId, questions } = useQuestionsStore() + const { dragQuestionContentId } = useQuestionsStore() const Container = useRef(null); const modalOpen = () => setOpenedModalQuestions(true) diff --git a/src/pages/Questions/BranchingQuestionsModal/index.tsx b/src/pages/Questions/BranchingQuestionsModal/index.tsx index 31194d29..007d8395 100644 --- a/src/pages/Questions/BranchingQuestionsModal/index.tsx +++ b/src/pages/Questions/BranchingQuestionsModal/index.tsx @@ -15,7 +15,8 @@ export const BranchingQuestionsModal = ({ setModalQuestionTargetContentId, setModalQuestionParentContentId }: Props) => { - const { questions } = useQuestionsStore(); + const trashQuestions = useQuestionsStore().questions + const questions = trashQuestions.filter((question) => question.type !== "result") const handleClose = () => { setOpenedModalQuestions(false); diff --git a/src/pages/Questions/DraggableList/QuestionPageCard.tsx b/src/pages/Questions/DraggableList/QuestionPageCard.tsx index 34de3139..4b43eff5 100644 --- a/src/pages/Questions/DraggableList/QuestionPageCard.tsx +++ b/src/pages/Questions/DraggableList/QuestionPageCard.tsx @@ -68,7 +68,7 @@ export default function QuestionsPageCard({ question, draggableProps, isDragging return ( <> { - const { questions, isLoading } = useQuestions(); - + const { questions } = useQuestionsStore() + const filteredQuestions = questions.filter((question) => question.type !== "result") + console.log(questions) + console.log(filteredQuestions) const onDragEnd = ({ destination, source }: DropResult) => { if (destination) reorderQuestions(source.index, destination.index); }; - if (isLoading && !questions) return Загрузка вопросов...; - return ( {(provided, snapshot) => ( - {questions.map((question, index) => ( + {filteredQuestions.map((question, index) => ( ({ type AnyQuestion = UntypedQuizQuestion | AnyTypedQuizQuestion export const QuestionsList = () => { - const { questions, desireToOpenABranchingModal } = useQuestionsStore() + const { desireToOpenABranchingModal } = useQuestionsStore() + const trashQuestions = useQuestionsStore().questions + const questions = trashQuestions.filter((question) => question.type !== "result") return ( diff --git a/src/pages/ResultPage/DescriptionForm/PriceButton.tsx b/src/pages/ResultPage/DescriptionForm/PriceButton.tsx index 6da97916..d2612ee4 100644 --- a/src/pages/ResultPage/DescriptionForm/PriceButton.tsx +++ b/src/pages/ResultPage/DescriptionForm/PriceButton.tsx @@ -39,24 +39,6 @@ const priceButtonsArray: { title: string; type: string; sx: SxProps }[] = whiteSpace: "nowrap", }, }, - { - title: "ƒ", - type: "ƒ", - sx: { - width: "38px", - height: "48px", - border: "1px solid #9A9AAF", - }, - }, - { - title: "Скидка", - type: "discount", - sx: { - width: "93px", - height: "48px", - border: "1px solid #9A9AAF", - }, - }, ]; type Props = { @@ -74,9 +56,6 @@ export default function PriceButtons({ Стоимость - - - { 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 ( - + - - - Результаты квиза в зависимости от ответов - - - {text} + + Результаты квиза в зависимости от ответов - - {text2} - + + Вы можете показывать разные результаты квиза (добавьте описание, изображение, стоимость и т.п.) разным пользователям, нужно только их создать и проставить условия. Таким образом ваш квиз получится максимально индивидуальным для каждого клиента. Показывайте картинку/видео вместо результата или переадресовывайте пользователя по нужной ссылке. + + + Этот шаг - необязательный, квиз будет работать и без автоматических результатов. + + + quiz creation - quiz creation - + > + Создать результаты + + ); -} +} \ No newline at end of file diff --git a/src/pages/ResultPage/Result.tsx b/src/pages/ResultPage/Result.tsx index a414e15d..97b520a4 100644 --- a/src/pages/ResultPage/Result.tsx +++ b/src/pages/ResultPage/Result.tsx @@ -3,7 +3,7 @@ import { updateQuiz } from "@root/quizes/actions"; import { useCurrentQuiz } from "@root/quizes/hooks"; import image from "../../assets/Rectangle 110.png"; import Info from "../../assets/icons/Info"; -import CreationFullCard from "./FirstEntry"; +// import CreationFullCard from "./FirstEntry"; export const Result = () => { @@ -13,11 +13,11 @@ export const Result = () => { return ( - + /> */} - - + + + + {quiz.config.resultInfo.when === "email" && } + + @@ -60,52 +77,14 @@ export const Setting = () => { }, }} variant="text" + onClick={() => setResultContract(!resultContract)} > Развернуть все - - - - - - - - - - - - - - - - + + ); }; diff --git a/src/pages/ResultPage/SwichResult.tsx b/src/pages/ResultPage/SwichResult.tsx index 0c3bb5a1..7a80be69 100644 --- a/src/pages/ResultPage/SwichResult.tsx +++ b/src/pages/ResultPage/SwichResult.tsx @@ -5,10 +5,11 @@ import * as React from "react"; interface Props { text: string; icon: string; - onClick?: () => void; + onClick?: (a:any) => void; + value: boolean } -export const SwitchSetting = ({ text, icon, onClick }: Props) => { +export const SwitchSetting = ({ text, icon, onClick, value }: Props) => { return ( { icon } label={text} labelPlacement="start" diff --git a/src/pages/ResultPage/cards/EmailSettingsCard.tsx b/src/pages/ResultPage/cards/EmailSettingsCard.tsx new file mode 100644 index 00000000..c67fe1ca --- /dev/null +++ b/src/pages/ResultPage/cards/EmailSettingsCard.tsx @@ -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 ( + + + + Настройки почты + + + setExpand(!expand)} + > + {expand ? ( + + ) : ( + + )} + + + + {expand && ( + + + Тема письма + + {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", + }, + }} + /> + + E-mail ответа + + {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", + }, + }} + /> + + Имя отправителя + + {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", + }, + }} + /> + + )} + + ) +} \ No newline at end of file diff --git a/src/pages/ResultPage/cards/ResultCard.tsx b/src/pages/ResultPage/cards/ResultCard.tsx new file mode 100644 index 00000000..5a9db1a9 --- /dev/null +++ b/src/pages/ResultPage/cards/ResultCard.tsx @@ -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(""); + const [priceButtonsActive, setPriceButtonsActive] = useState(0); + const [priceButtonsType, setPriceButtonsType] = useState(); + const [forwarding, setForwarding] = useState(false); + + const buttonsActive = (index: number, type: string) => { + setPriceButtonsActive(index); + setPriceButtonsType(type); + }; + + const SSHC = (data: string) => { + setSwitchState(data); + }; + + return( + + + + Заголовок результата + + + setExpand(!expand)} + > + {expand ? ( + + ) : ( + + )} + + + + {expand && ( + + + + + + + + + + + + + + + + + + {priceButtonsType === "smooth" ? ( + + + + Призыв к действию + + + + + + + + + {forwarding ? ( + + + + Переадресация + + + + + + + + + + + ) : ( + <> + )} + + + ) : ( + + )} + + + + + )} + + ) +} \ No newline at end of file diff --git a/src/pages/ResultPage/cards/WhenCard.tsx b/src/pages/ResultPage/cards/WhenCard.tsx new file mode 100644 index 00000000..fdd8fe94 --- /dev/null +++ b/src/pages/ResultPage/cards/WhenCard.tsx @@ -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 ( + + + + Показывать результат + + + setExpand(!expand)} + > + {expand ? ( + + ) : ( + + )} + + + + {expand && ( + <> + + + + {whenValues.map(({ title, value }, index) => ( + + ))} + + + + { + (quiz?.config.resultInfo.when !== "email") && updateQuiz(quiz.id, (quiz) => quiz.config.resultInfo.share = event.target.checked)} + value={quiz?.config.resultInfo.share} + /> + } + { + quiz?.config.resultInfo.when === "before" && updateQuiz(quiz.id, (quiz) => quiz.config.resultInfo.replay = event.target.checked)} + value={quiz?.config.resultInfo.replay} + /> + } + + + + + + )} + + + ) +} \ No newline at end of file diff --git a/src/pages/ViewPublicationPage/Question.tsx b/src/pages/ViewPublicationPage/Question.tsx index a46fc60c..84b3ff98 100644 --- a/src/pages/ViewPublicationPage/Question.tsx +++ b/src/pages/ViewPublicationPage/Question.tsx @@ -55,8 +55,8 @@ export const Question = ({ sx={{ minHeight: "calc(100vh - 75px)", width: "100%", - maxWidth: "1000px", - padding: "20px 10px 0", + maxWidth: "1440px", + padding: "40px 25px 20px", margin: "0 auto", }} > diff --git a/src/pages/ViewPublicationPage/questions/Images.tsx b/src/pages/ViewPublicationPage/questions/Images.tsx index 425eaa87..ed42ffc2 100644 --- a/src/pages/ViewPublicationPage/questions/Images.tsx +++ b/src/pages/ViewPublicationPage/questions/Images.tsx @@ -1,16 +1,15 @@ import { useEffect } from "react"; import { - Box, - Typography, - RadioGroup, - FormControlLabel, - Radio, - useTheme, - useMediaQuery, + Box, + Typography, + RadioGroup, + FormControlLabel, + Radio, + useTheme, + useMediaQuery, FormControl, } from "@mui/material"; import { useQuizViewStore, updateAnswer } from "@root/quizView"; - import RadioCheck from "@ui_kit/RadioCheck"; import RadioIcon from "@ui_kit/RadioIcon"; @@ -92,7 +91,7 @@ export const Images = ({ currentQuestion }: ImagesProps) => { )} - { return ( - {currentQuestion.title} + {currentQuestion.title} + {currentQuestion.content.text} { }} > {currentQuestion.content.picture && ( - + + + + )} {currentQuestion.content.video && (