diff --git a/cypress/e2e/quizPublish.cy.ts b/cypress/e2e/quizPublish.cy.ts index 5f114527..06203589 100644 --- a/cypress/e2e/quizPublish.cy.ts +++ b/cypress/e2e/quizPublish.cy.ts @@ -282,5 +282,15 @@ describe("Форма Входа", () => { cy.wait(5000); cy.get('[data-cy="create-question"]').click(); + + // Удаления Квиза + + cy.visit("http://localhost:3000/list"); + cy.wait(500); + cy.get('[data-cy="delete-quiz"]').each(($button) => { + cy.wrap($button).click(); + cy.wait(500); + cy.contains("button", "Удалить").click(); + }); }); }); diff --git a/src/assets/icons/CropIcon.tsx b/src/assets/icons/CropIcon.tsx index 520e90d9..16b098a7 100644 --- a/src/assets/icons/CropIcon.tsx +++ b/src/assets/icons/CropIcon.tsx @@ -1,40 +1,10 @@ import { FC, SVGProps } from "react"; -export const CropIcon: FC = () => ( - - - - - +export const CropIcon: FC> = (props) => ( + + + + + ); diff --git a/src/pages/Landing/HeaderLanding.tsx b/src/pages/Landing/HeaderLanding.tsx index 18a598f6..6eb1381c 100644 --- a/src/pages/Landing/HeaderLanding.tsx +++ b/src/pages/Landing/HeaderLanding.tsx @@ -18,7 +18,7 @@ export default function Component() { const [select, setSelect] = React.useState(0); const userId = useUserStore((state) => state.userId); const navigate = useNavigate(); - const location = useLocation() + const location = useLocation(); const onClick = () => (userId ? navigate("/list") : navigate("/signin")); @@ -41,7 +41,9 @@ export default function Component() { padding: 0, }} > - + + + {/**/} */} - {/* */} - {/* ))}*/} - {/**/} - {isMobile ? ( - - ) : ( - - - - )} - - - - - - ); + {isMobile && } + + + + + + + {/**/} + {/* {buttonMenu.map(({ path, title }) => (*/} + {/* */} + {/* */} + {/* {title}*/} + {/* */} + {/* */} + {/* ))}*/} + {/**/} + {isMobile ? ( + + ) : ( + + + + )} + + + + + + ); } diff --git a/src/pages/ResultPage/FirstEntry.tsx b/src/pages/ResultPage/FirstEntry.tsx index ab4ec58b..5e470119 100644 --- a/src/pages/ResultPage/FirstEntry.tsx +++ b/src/pages/ResultPage/FirstEntry.tsx @@ -1,5 +1,4 @@ - -import { ResultSettings } from "./ResultSettings" +import { ResultSettings } from "./ResultSettings"; import { createFrontResult } from "@root/questions/actions"; import { useQuestionsStore } from "@root/questions/store"; import { useCurrentQuiz } from "@root/quizes/hooks"; @@ -7,6 +6,8 @@ import { Box, Typography, useTheme, useMediaQuery, Button } from "@mui/material" import image from "../../assets/Rectangle 110.png"; import { enqueueSnackbar } from "notistack"; import { AnyTypedQuizQuestion } from "@model/questionTypes/shared"; +import ArrowLeft from "../../assets/icons/questionsPage/arrowLeft"; +import { decrementCurrentStep } from "@root/quizes/actions"; export const FirstEntry = () => { const theme = useTheme(); @@ -17,16 +18,20 @@ export const FirstEntry = () => { const create = () => { if (quiz?.config.haveRoot) { questions - .filter((question:AnyTypedQuizQuestion) => { - return question.type !== null && question.content.rule.parentId.length !== 0 && question.content.rule.children.length === 0 - }) - .forEach(question => { - createFrontResult(quiz.id, question.content.id) - }) + .filter((question: AnyTypedQuizQuestion) => { + return ( + question.type !== null && + question.content.rule.parentId.length !== 0 && + question.content.rule.children.length === 0 + ); + }) + .forEach((question) => { + createFrontResult(quiz.id, question.content.id); + }); } else { - createFrontResult(quiz.id, "line") + createFrontResult(quiz.id, "line"); } - } + }; return ( <> @@ -53,7 +58,7 @@ export const FirstEntry = () => { mr: !isSmallMonitor ? "104px" : 0, marginBottom: isSmallMonitor ? "20px" : 0, position: "relative", - height: "100%" + height: "100%", }} > @@ -69,7 +74,10 @@ export const FirstEntry = () => { }} > - Вы можете показывать разные результаты квиза (добавьте описание, изображение, стоимость и т.п.) разным пользователям, нужно только их создать и проставить условия. Таким образом ваш квиз получится максимально индивидуальным для каждого клиента. Показывайте картинку/видео вместо результата или переадресовывайте пользователя по нужной ссылке. + Вы можете показывать разные результаты квиза (добавьте описание, изображение, стоимость и т.п.) разным + пользователям, нужно только их создать и проставить условия. Таким образом ваш квиз получится максимально + индивидуальным для каждого клиента. Показывайте картинку/видео вместо результата или переадресовывайте + пользователя по нужной ссылке. { }} /> - + + + + + ); -} \ No newline at end of file +}; diff --git a/src/pages/createQuize/MyQuizzesFull.tsx b/src/pages/createQuize/MyQuizzesFull.tsx index dfc9ee16..2254f08e 100644 --- a/src/pages/createQuize/MyQuizzesFull.tsx +++ b/src/pages/createQuize/MyQuizzesFull.tsx @@ -1,12 +1,4 @@ -import { - Box, - Button, - SxProps, - Theme, - Typography, - useMediaQuery, - useTheme, -} from "@mui/material"; +import { Box, Button, SxProps, Theme, Typography, useMediaQuery, useTheme } from "@mui/material"; import { createQuiz } from "@root/quizes/actions"; import { useQuizes } from "@root/quizes/hooks"; import SectionWrapper from "@ui_kit/SectionWrapper"; @@ -16,72 +8,62 @@ import ComplexNavText from "./ComplexNavText"; import FirstQuiz from "./FirstQuiz"; import QuizCard from "./QuizCard"; - interface Props { - outerContainerSx?: SxProps; - children?: React.ReactNode; + outerContainerSx?: SxProps; + children?: React.ReactNode; } -export default function MyQuizzesFull({ - outerContainerSx: sx, - children, -}: Props) { - const { quizes } = useQuizes(); - const navigate = useNavigate(); - const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down(500)); +export default function MyQuizzesFull({ outerContainerSx: sx, children }: Props) { + const { quizes } = useQuizes(); + const navigate = useNavigate(); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down(500)); - return ( - <> - {quizes.length === 0 ? ( - - ) : ( - - - - Мои квизы - - - - {quizes.map(quiz => ( - - ))} - - {children} - - )} - - ); + return ( + <> + {quizes.length === 0 ? ( + + ) : ( + + + + Мои квизы + + + + {quizes.map((quiz) => ( + + ))} + + {children} + + )} + + ); } diff --git a/src/ui_kit/Header/Header.tsx b/src/ui_kit/Header/Header.tsx index aaec0d34..8a983181 100755 --- a/src/ui_kit/Header/Header.tsx +++ b/src/ui_kit/Header/Header.tsx @@ -5,6 +5,7 @@ import { decrementCurrentStep } from "@root/quizes/actions"; import PenaLogo from "../PenaLogo"; import CustomAvatar from "./Avatar"; import NavMenuItem from "./NavMenuItem"; +import { Link } from "react-router-dom"; export default function Header() { const theme = useTheme(); @@ -25,7 +26,9 @@ export default function Header() { zIndex: theme.zIndex.drawer + 1, }} > - + + + )} - + + + {!isTablet && ( Мой баланс - + 00.00 руб. @@ -111,13 +103,13 @@ export default function HeaderFull() { height: "36px", width: "36px", }} - /> - + /> + )} diff --git a/src/ui_kit/Modal/CropModal.tsx b/src/ui_kit/Modal/CropModal.tsx index 218b3b10..9edc2cf8 100644 --- a/src/ui_kit/Modal/CropModal.tsx +++ b/src/ui_kit/Modal/CropModal.tsx @@ -1,330 +1,344 @@ import { CropIcon } from "@icons/CropIcon"; import { ResetIcon } from "@icons/ResetIcon"; import { - Box, - Button, - IconButton, - Modal, - Slider, - SxProps, - Theme, - Typography, - useMediaQuery, - useTheme, + Box, + Button, + IconButton, + Modal, + Slider, + SxProps, + Theme, + Typography, + useMediaQuery, + useTheme, } from "@mui/material"; import { FC, useMemo, useRef, useState } from "react"; import ReactCrop, { Crop, PixelCrop } from "react-image-crop"; import "react-image-crop/dist/ReactCrop.css"; import { canvasPreview } from "./utils/canvasPreview"; - const styleSlider: SxProps = { - color: "#7E2AEA", - height: "12px", - "& .MuiSlider-track": { - border: "none", - }, - "& .MuiSlider-rail": { - backgroundColor: "#F2F3F7", - border: `1px solid #9A9AAF`, - }, - "& .MuiSlider-thumb": { - height: 26, - width: 26, - border: `6px solid #7E2AEA`, - backgroundColor: "white", - boxShadow: `0px 0px 0px 3px white, + color: "#7E2AEA", + height: "12px", + "& .MuiSlider-track": { + border: "none", + }, + "& .MuiSlider-rail": { + backgroundColor: "#F2F3F7", + border: `1px solid #9A9AAF`, + }, + "& .MuiSlider-thumb": { + height: 26, + width: 26, + border: `6px solid #7E2AEA`, + backgroundColor: "white", + boxShadow: `0px 0px 0px 3px white, 0px 4px 4px 3px #C3C8DD`, - "&:focus, &:hover, &.Mui-active, &.Mui-focusVisible": { - boxShadow: `0px 0px 0px 3px white, + "&:focus, &:hover, &.Mui-active, &.Mui-focusVisible": { + boxShadow: `0px 0px 0px 3px white, 0px 4px 4px 3px #C3C8DD`, - }, }, + }, }; interface Props { - isOpen: boolean; - imageBlob: Blob | null; - originalImageUrl: string | null; - setCropModalImageBlob: (imageBlob: Blob) => void; - onClose: () => void; - onSaveImageClick: (imageBlob: Blob) => void; + isOpen: boolean; + imageBlob: Blob | null; + originalImageUrl: string | null; + setCropModalImageBlob: (imageBlob: Blob) => void; + onClose: () => void; + onSaveImageClick: (imageBlob: Blob) => void; } -export const CropModal: FC = ({ isOpen, imageBlob, originalImageUrl, setCropModalImageBlob, onSaveImageClick, onClose }) => { - const theme = useTheme(); - const [crop, setCrop] = useState(); - const [completedCrop, setCompletedCrop] = useState(); - const [darken, setDarken] = useState(0); - const [rotate, setRotate] = useState(0); - const [width, setWidth] = useState(240); - const cropImageElementRef = useRef(null); - const isMobile = useMediaQuery(theme.breakpoints.down(786)); +export const CropModal: FC = ({ + isOpen, + imageBlob, + originalImageUrl, + setCropModalImageBlob, + onSaveImageClick, + onClose, +}) => { + const theme = useTheme(); + const [crop, setCrop] = useState(); + const [completedCrop, setCompletedCrop] = useState(); + const [darken, setDarken] = useState(0); + const [rotate, setRotate] = useState(0); + const [width, setWidth] = useState(240); + const cropImageElementRef = useRef(null); + const isMobile = useMediaQuery(theme.breakpoints.down(786)); - const imageUrl = useMemo(() => imageBlob && URL.createObjectURL(imageBlob), [imageBlob]); + const imageUrl = useMemo(() => imageBlob && URL.createObjectURL(imageBlob), [imageBlob]); - const handleCropClick = async () => { - if (!completedCrop) throw new Error("No completed crop"); - if (!cropImageElementRef.current) throw new Error("No image"); + const handleCropClick = async () => { + if (!completedCrop) throw new Error("No completed crop"); + if (!cropImageElementRef.current) throw new Error("No image"); - const canvasCopy = document.createElement("canvas"); - const ctx = canvasCopy.getContext("2d"); - if (!ctx) throw new Error("No 2d context"); + const canvasCopy = document.createElement("canvas"); + const ctx = canvasCopy.getContext("2d"); + if (!ctx) throw new Error("No 2d context"); - canvasCopy.width = completedCrop.width; - canvasCopy.height = completedCrop.height; - ctx.filter = `brightness(${100 - darken}%)`; + canvasCopy.width = completedCrop.width; + canvasCopy.height = completedCrop.height; + ctx.filter = `brightness(${100 - darken}%)`; - await canvasPreview(cropImageElementRef.current, canvasCopy, completedCrop, rotate); + await canvasPreview(cropImageElementRef.current, canvasCopy, completedCrop, rotate); - canvasCopy.toBlob((blob) => { - if (!blob) throw new Error("Failed to create blob"); + canvasCopy.toBlob((blob) => { + if (!blob) throw new Error("Failed to create blob"); - setCropModalImageBlob(blob); - setCrop(undefined); - setCompletedCrop(undefined); - }); - }; + setCropModalImageBlob(blob); + setCrop(undefined); + setCompletedCrop(undefined); + }); + }; - function handleSaveClick() { - if (imageBlob) onSaveImageClick?.(imageBlob); - setCrop(undefined); - setCompletedCrop(undefined); - onClose(); + function handleSaveClick() { + if (imageBlob) onSaveImageClick?.(imageBlob); + setCrop(undefined); + setCompletedCrop(undefined); + onClose(); + } + + async function handleLoadOriginalImage() { + if (!originalImageUrl) return; + + const response = await fetch(originalImageUrl); + const blob = await response.blob(); + + setCropModalImageBlob(blob); + setCrop(undefined); + setCompletedCrop(undefined); + } + + const getImageSize = () => { + if (cropImageElementRef.current) { + const imageWidth = cropImageElementRef.current.naturalWidth; + const imageHeight = cropImageElementRef.current.naturalHeight; + + const aspect = imageWidth / imageHeight; + + if (aspect <= 1.333) { + setWidth(240); + } + if (aspect >= 1.5) { + setWidth(580); + } + if (aspect >= 1.778) { + setWidth(580); + } } + }; - async function handleLoadOriginalImage() { - if (!originalImageUrl) return; - - const response = await fetch(originalImageUrl); - const blob = await response.blob(); - - setCropModalImageBlob(blob); - setCrop(undefined); - setCompletedCrop(undefined); - } - - const getImageSize = () => { - if (cropImageElementRef.current) { - const imageWidth = cropImageElementRef.current.naturalWidth; - const imageHeight = cropImageElementRef.current.naturalHeight; - - const aspect = imageWidth / imageHeight; - - if (aspect <= 1.333) { - setWidth(240); - } - if (aspect >= 1.5) { - setWidth(580); - } - if (aspect >= 1.778) { - setWidth(580); - } - } - }; - - return ( - + + - - - {imageUrl && ( - setCrop(percentCrop)} - onComplete={(c) => setCompletedCrop(c)} - maxWidth={500} - minWidth={50} - maxHeight={320} - minHeight={50} - > - Crop me - - )} - - - - {crop?.width ? Math.round(crop.width) + "px" : ""} - - - {crop?.height ? Math.round(crop.height) + "px" : ""} - - + {imageUrl && ( + setCrop(percentCrop)} + onComplete={(c) => setCompletedCrop(c)} + maxWidth={500} + minWidth={50} + maxHeight={320} + minHeight={50} + > + Crop me + + )} + + + + {crop?.width ? Math.round(crop.width) + "px" : ""} + + + {crop?.height ? Math.round(crop.height) + "px" : ""} + + - - setRotate(r => (r + 90) % 360)}> - - - - - Размер - - { - setWidth(newValue as number); - }} - /> - - - - Затемнение - - setDarken(newValue as number)} - /> - - - - - - - - - - ); + + setRotate((r) => (r + 90) % 360)}> + + + + Размер + { + setWidth(newValue as number); + }} + /> + + + Затемнение + setDarken(newValue as number)} + /> + + + + + + + + + + ); }; export function useCropModalState(initialOpenState = false) { - const [isCropModalOpen, setOpened] = useState(initialOpenState); - const [imageBlob, setCropModalImageBlob] = useState(null); - const [originalImageUrl, setOriginalImageUrl] = useState(null); + const [isCropModalOpen, setOpened] = useState(initialOpenState); + const [imageBlob, setCropModalImageBlob] = useState(null); + const [originalImageUrl, setOriginalImageUrl] = useState(null); - const closeCropModal = () => { - setOpened(false); - setCropModalImageBlob(null); - setOriginalImageUrl(null); - }; + const closeCropModal = () => { + setOpened(false); + setCropModalImageBlob(null); + setOriginalImageUrl(null); + }; - async function openCropModal(image: Blob | string, originalImageUrl: string | null | undefined = null) { - if (typeof image === "string") { - const response = await fetch(image); - image = await response.blob(); - } - - setCropModalImageBlob(image); - setOriginalImageUrl(originalImageUrl); - setOpened(true); + async function openCropModal(image: Blob | string, originalImageUrl: string | null | undefined = null) { + if (typeof image === "string") { + const response = await fetch(image); + image = await response.blob(); } - return { - isCropModalOpen, - openCropModal, - closeCropModal, - imageBlob, - setCropModalImageBlob, - originalImageUrl, - } as const; + setCropModalImageBlob(image); + setOriginalImageUrl(originalImageUrl); + setOpened(true); + } + + return { + isCropModalOpen, + openCropModal, + closeCropModal, + imageBlob, + setCropModalImageBlob, + originalImageUrl, + } as const; }