Merge branch 'input-limit' into support-sse-logic

This commit is contained in:
Nastya 2024-02-15 12:40:27 +03:00
commit d5ad04cf3a
21 changed files with 429 additions and 222 deletions

@ -430,23 +430,23 @@ const PseudoButton = () => {
if (!quiz) return null; if (!quiz) return null;
return ( return (
<TextField <CustomTextField
onChange={({ target }) => { onChange={({ target }) => {
updateQuiz(quiz.id, (quiz) => { updateQuiz(quiz.id, (quiz) => {
quiz.config.formContact.button = target.value; quiz.config.formContact.button = target.value;
}); });
}} }}
maxLength={23}
value={quiz.config.formContact.button} value={quiz.config.formContact.button}
sx={{ sx={{
heigth: "44px", heigth: "44px",
width: "190px", width: "190px",
"& .MuiInputBase-root": { backgroundColor: "#7E2AEA",
backgroundColor: "#7E2AEA", borderRadius: "8px",
borderRadius: "8px", color: "white",
color: "white", padding: 0,
},
"& .MuiInputBase-input": { "& .MuiInputBase-input": {
padding: "10px 20px", padding: "10px 10px",
textAlign: "center", textAlign: "center",
}, },
"& .MuiInputBase-input::placeholder": { "& .MuiInputBase-input::placeholder": {
@ -454,9 +454,7 @@ const PseudoButton = () => {
opacity: "1", opacity: "1",
}, },
}} }}
placeholder="Название кнопки" placeholder={quiz.config.formContact.button || "Название кнопки"}
> />
{quiz.config.formContact.button || "Название кнопки"}
</TextField>
); );
}; };

@ -60,6 +60,7 @@ export default function NewFieldParent({
value={quiz.config.formContact.fields[defaultValue].text} value={quiz.config.formContact.fields[defaultValue].text}
placeholder={placeholderHelp} placeholder={placeholderHelp}
text={""} text={""}
maxLength={20}
/> />
</Box> </Box>
<Box sx={{ display: "flex", flexDirection: "column", gap: "15px" }}> <Box sx={{ display: "flex", flexDirection: "column", gap: "15px" }}>
@ -74,6 +75,7 @@ export default function NewFieldParent({
value={quiz.config.formContact.fields[defaultValue].innerText} value={quiz.config.formContact.fields[defaultValue].innerText}
placeholder={placeholderField} placeholder={placeholderField}
text={""} text={""}
maxLength={20}
/> />
</Box> </Box>
</> </>

@ -28,11 +28,18 @@ const ButtonsThemeDark = [
["Розовый", "PinkDarkTheme", "#D34085", "#FFFFFF"], ["Розовый", "PinkDarkTheme", "#D34085", "#FFFFFF"],
["Бирюзовый", "BlueDarkTheme", "#07A0C3", "#FFFFFF"], ["Бирюзовый", "BlueDarkTheme", "#07A0C3", "#FFFFFF"],
]; ];
export const DesignFilling = (mobileSidebar: boolean) => {
interface Props {
mobileSidebar: boolean;
heightSidebar: number;
}
export const DesignFilling = ({ mobileSidebar, heightSidebar }: Props) => {
const quiz = useCurrentQuiz(); const quiz = useCurrentQuiz();
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(830)); const isMobile = useMediaQuery(theme.breakpoints.down(830));
const heightBar = heightSidebar + 51 + 88 + 36;
console.log(mobileSidebar, "111");
return ( return (
<Box <Box
sx={{ sx={{
@ -40,7 +47,7 @@ export const DesignFilling = (mobileSidebar: boolean) => {
padding: "25px", padding: "25px",
height: isMobile height: isMobile
? mobileSidebar ? mobileSidebar
? "calc(100vh - 271px)" ? `calc(100vh - ${heightBar}px)`
: "calc(100vh - 127px)" : "calc(100vh - 127px)"
: "calc(100vh - 80px)", : "calc(100vh - 80px)",
}} }}

@ -22,7 +22,12 @@ import { DesignFilling } from "./DesignFilling";
import { createPortal } from "react-dom"; import { createPortal } from "react-dom";
import QuizPreview from "@ui_kit/QuizPreview/QuizPreview"; import QuizPreview from "@ui_kit/QuizPreview/QuizPreview";
export const DesignPage = () => { interface Props {
heightSidebar: number;
mobileSidebar: boolean;
}
export const DesignPage = ({ heightSidebar, mobileSidebar }: Props) => {
const quiz = useCurrentQuiz(); const quiz = useCurrentQuiz();
const { editQuizId } = useQuizStore(); const { editQuizId } = useQuizStore();
const { showConfirmLeaveModal } = useUiTools(); const { showConfirmLeaveModal } = useUiTools();
@ -30,7 +35,6 @@ export const DesignPage = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const currentStep = useQuizStore((state) => state.currentStep); const currentStep = useQuizStore((state) => state.currentStep);
const isMobile = useMediaQuery(theme.breakpoints.down(660)); const isMobile = useMediaQuery(theme.breakpoints.down(660));
const [mobileSidebar, setMobileSidebar] = useState<boolean>(false);
const [nextStep, setNextStep] = useState<number>(0); const [nextStep, setNextStep] = useState<number>(0);
useEffect( useEffect(
@ -69,7 +73,10 @@ export const DesignPage = () => {
return ( return (
<> <>
<Box sx={{ display: "flex", flexDirection: isMobile ? "column" : "row" }}> <Box sx={{ display: "flex", flexDirection: isMobile ? "column" : "row" }}>
<DesignFilling mobileSidebar={mobileSidebar} /> <DesignFilling
mobileSidebar={mobileSidebar}
heightSidebar={heightSidebar}
/>
{createPortal(<QuizPreview />, document.body)} {createPortal(<QuizPreview />, document.body)}
</Box> </Box>
<ConfirmLeaveModal <ConfirmLeaveModal

@ -47,7 +47,7 @@ export const AnswerItem = ({
const isTablet = useMediaQuery(theme.breakpoints.down(790)); const isTablet = useMediaQuery(theme.breakpoints.down(790));
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null); const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
const [inputValue, setInputValue] = useState("");
const setQuestionVariantAnswer = useDebouncedCallback((value) => { const setQuestionVariantAnswer = useDebouncedCallback((value) => {
setQuestionVariantField(questionId, variant.id, "answer", value); setQuestionVariantField(questionId, variant.id, "answer", value);
}, 200); }, 200);
@ -77,13 +77,17 @@ export const AnswerItem = ({
}} }}
> >
<TextField <TextField
defaultValue={variant.answer} value={variant.answer}
fullWidth fullWidth
focused={false} focused={false}
placeholder={"Добавьте ответ"} placeholder={"Добавьте ответ"}
multiline={largeCheck} multiline={largeCheck}
onChange={({ target }: ChangeEvent<HTMLInputElement>) => { onChange={({ target }: ChangeEvent<HTMLInputElement>) => {
setQuestionVariantAnswer(target.value || " "); if (target.value.length <= 1000) {
const inputValue = target.value;
setInputValue(inputValue);
}
setQuestionVariantAnswer(inputValue || " ");
}} }}
onKeyDown={(event: KeyboardEvent<HTMLInputElement>) => { onKeyDown={(event: KeyboardEvent<HTMLInputElement>) => {
if (disableKeyDown) { if (disableKeyDown) {

@ -205,8 +205,7 @@ export default function ButtonsOptionsAndPict({
</Box> </Box>
} }
> */} > */}
{question.type !== "text" {question.type !== "text" && (
&&
<MiniButtonSetting <MiniButtonSetting
onMouseEnter={() => setButtonHover("branching")} onMouseEnter={() => setButtonHover("branching")}
onMouseLeave={() => setButtonHover("")} onMouseLeave={() => setButtonHover("")}
@ -229,7 +228,9 @@ export default function ButtonsOptionsAndPict({
: theme.palette.grey3.main, : theme.palette.grey3.main,
"&:hover": { "&:hover": {
color: color:
switchState === "branching" ? theme.palette.grey3.main : null, switchState === "branching"
? theme.palette.grey3.main
: null,
}, },
}} }}
> >
@ -244,7 +245,7 @@ export default function ButtonsOptionsAndPict({
/> />
{isIconMobile ? null : "Ветвление"} {isIconMobile ? null : "Ветвление"}
</MiniButtonSetting> </MiniButtonSetting>
} )}
{/* </Tooltip> */} {/* </Tooltip> */}
<MiniButtonSetting <MiniButtonSetting
onMouseEnter={() => setButtonHover("image")} onMouseEnter={() => setButtonHover("image")}

@ -99,6 +99,7 @@ export default function SettingDropDown({ question }: SettingDropDownProps) {
<CustomTextField <CustomTextField
placeholder={"Выберите вариант"} placeholder={"Выберите вариант"}
text={question.content.default} text={question.content.default}
maxLength={60}
onChange={({ target }) => debounceAnswer(target.value)} onChange={({ target }) => debounceAnswer(target.value)}
/> />
</Box> </Box>

@ -51,7 +51,7 @@ export default function Emoji({
<AnswerDraggableList <AnswerDraggableList
question={question} question={question}
additionalContent={(variant) => ( additionalContent={(variant) => (
<>Оооооо, тестовая публикация получа <>
{!isTablet && ( {!isTablet && (
<Box sx={{ cursor: "pointer" }}> <Box sx={{ cursor: "pointer" }}>
<Box <Box

@ -41,6 +41,7 @@ export default function OptionsAndPicture({
setOpenBranchingPage, setOpenBranchingPage,
}: Props) { }: Props) {
const [switchState, setSwitchState] = useState("setting"); const [switchState, setSwitchState] = useState("setting");
const [pictureUploding, setPictureUploading] = useState<boolean>(false);
const [selectedVariantId, setSelectedVariantId] = useState<string | null>( const [selectedVariantId, setSelectedVariantId] = useState<string | null>(
null, null,
); );
@ -66,6 +67,8 @@ export default function OptionsAndPicture({
const handleImageUpload = async (file: File) => { const handleImageUpload = async (file: File) => {
if (!selectedVariantId) return; if (!selectedVariantId) return;
setPictureUploading(true);
const url = await uploadQuestionImage( const url = await uploadQuestionImage(
question.id, question.id,
quizQid, quizQid,
@ -84,6 +87,8 @@ export default function OptionsAndPicture({
); );
closeImageUploadModal(); closeImageUploadModal();
openCropModal(file, url); openCropModal(file, url);
setPictureUploading(false);
}; };
function handleCropModalSaveClick(imageBlob: Blob) { function handleCropModalSaveClick(imageBlob: Blob) {
@ -111,6 +116,7 @@ export default function OptionsAndPicture({
{!isMobile && ( {!isMobile && (
<AddOrEditImageButton <AddOrEditImageButton
imageSrc={variant.extendedText} imageSrc={variant.extendedText}
uploading={pictureUploding}
onImageClick={() => { onImageClick={() => {
setSelectedVariantId(variant.id); setSelectedVariantId(variant.id);
if (variant.extendedText) { if (variant.extendedText) {
@ -136,6 +142,7 @@ export default function OptionsAndPicture({
{isMobile && ( {isMobile && (
<AddOrEditImageButton <AddOrEditImageButton
imageSrc={variant.extendedText} imageSrc={variant.extendedText}
uploading={pictureUploding}
onImageClick={() => { onImageClick={() => {
setSelectedVariantId(variant.id); setSelectedVariantId(variant.id);
if (variant.extendedText) { if (variant.extendedText) {

@ -24,7 +24,7 @@ export default function SettingOptionsAndPict({
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990)); const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
const isMobile = useMediaQuery(theme.breakpoints.down(680)); const isMobile = useMediaQuery(theme.breakpoints.down(680));
console.log("question.content.replText ", question.content.replText) console.log("question.content.replText ", question.content.replText);
const setReplText = useDebouncedCallback((replText) => { const setReplText = useDebouncedCallback((replText) => {
updateQuestion(question.id, (question) => { updateQuestion(question.id, (question) => {
if (question.type !== "varimg") return; if (question.type !== "varimg") return;

@ -1,4 +1,11 @@
import { Box, Link, Typography, useMediaQuery, useTheme } from "@mui/material"; import {
Box,
Link,
Typography,
Skeleton,
useMediaQuery,
useTheme,
} from "@mui/material";
import { import {
addQuestionVariant, addQuestionVariant,
uploadQuestionImage, uploadQuestionImage,
@ -30,6 +37,7 @@ export default function OptionsPicture({
const theme = useTheme(); const theme = useTheme();
const onClickAddAnAnswer = useAddAnswer(); const onClickAddAnAnswer = useAddAnswer();
const quizQid = useCurrentQuiz()?.qid; const quizQid = useCurrentQuiz()?.qid;
const [pictureUploding, setPictureUploading] = useState<boolean>(false);
const [selectedVariantId, setSelectedVariantId] = useState<string | null>( const [selectedVariantId, setSelectedVariantId] = useState<string | null>(
null, null,
); );
@ -53,6 +61,8 @@ export default function OptionsPicture({
const handleImageUpload = async (file: File) => { const handleImageUpload = async (file: File) => {
if (!selectedVariantId) return; if (!selectedVariantId) return;
setPictureUploading(true);
const url = await uploadQuestionImage( const url = await uploadQuestionImage(
question.id, question.id,
quizQid, quizQid,
@ -69,8 +79,11 @@ export default function OptionsPicture({
variant.originalImageUrl = url; variant.originalImageUrl = url;
}, },
); );
closeImageUploadModal(); closeImageUploadModal();
openCropModal(file, url); openCropModal(file, url);
setPictureUploading(false);
}; };
function handleCropModalSaveClick(imageBlob: Blob) { function handleCropModalSaveClick(imageBlob: Blob) {
@ -98,6 +111,7 @@ export default function OptionsPicture({
{!isMobile && ( {!isMobile && (
<AddOrEditImageButton <AddOrEditImageButton
imageSrc={variant.extendedText} imageSrc={variant.extendedText}
uploading={pictureUploding}
onImageClick={() => { onImageClick={() => {
setSelectedVariantId(variant.id); setSelectedVariantId(variant.id);
if (variant.extendedText) { if (variant.extendedText) {
@ -123,6 +137,7 @@ export default function OptionsPicture({
{isMobile && ( {isMobile && (
<AddOrEditImageButton <AddOrEditImageButton
imageSrc={variant.extendedText} imageSrc={variant.extendedText}
uploading={pictureUploding}
onImageClick={() => { onImageClick={() => {
setSelectedVariantId(variant.id); setSelectedVariantId(variant.id);
if (variant.extendedText) { if (variant.extendedText) {

@ -66,7 +66,7 @@ export default function QuestionsPage({
margin: "60px 0 40px 0", margin: "60px 0 40px 0",
}} }}
> >
<Typography variant={"h5"}> <Typography variant={"h5"} sx={{ wordBreak: "break-word" }}>
{quiz.name ? quiz.name : "Заголовок quiz"} {quiz.name ? quiz.name : "Заголовок quiz"}
</Typography> </Typography>
{!openBranchingPage && ( {!openBranchingPage && (

@ -1,5 +1,6 @@
import { useState } from "react";
import { AnyTypedQuizQuestion } from "@model/questionTypes/shared"; import { AnyTypedQuizQuestion } from "@model/questionTypes/shared";
import { Box, ButtonBase, Typography, useTheme } from "@mui/material"; import { Box, Skeleton, Typography, useTheme } from "@mui/material";
import { updateQuestion, uploadQuestionImage } from "@root/questions/actions"; import { updateQuestion, uploadQuestionImage } from "@root/questions/actions";
import { useCurrentQuiz } from "@root/quizes/hooks"; import { useCurrentQuiz } from "@root/quizes/hooks";
import { CropModal, useCropModalState } from "@ui_kit/Modal/CropModal"; import { CropModal, useCropModalState } from "@ui_kit/Modal/CropModal";
@ -12,6 +13,7 @@ type UploadImageProps = {
}; };
export default function UploadImage({ question }: UploadImageProps) { export default function UploadImage({ question }: UploadImageProps) {
const [pictureUploding, setPictureUploading] = useState<boolean>(false);
const theme = useTheme(); const theme = useTheme();
const quiz = useCurrentQuiz(); const quiz = useCurrentQuiz();
@ -29,29 +31,51 @@ export default function UploadImage({ question }: UploadImageProps) {
> >
Загрузить изображение Загрузить изображение
</Typography> </Typography>
<DropZone {pictureUploding ? (
text={"5 MB максимум"} <Skeleton variant="rounded" sx={{ height: "120px", width: "300px" }} />
) : (
<DropZone
text={"5 MB максимум"}
heightImg={"110px"} heightImg={"110px"}
sx={{ maxWidth: "300px", width: "100%" }} sx={{ maxWidth: "300px", width: "100%" }}
imageUrl={question.content.back} imageUrl={question.content.back}
originalImageUrl={question.content.originalBack} originalImageUrl={question.content.originalBack}
onImageUploadClick={(file) => { onImageUploadClick={async (file) => {
uploadQuestionImage(question.id, quiz.qid, file, (question, url) => { setPictureUploading(true);
question.content.back = url;
question.content.originalBack = url; await uploadQuestionImage(
}); question.id,
}} quiz.qid,
onDeleteClick={() => { file,
updateQuestion(question.id, (question) => { (question, url) => {
question.content.back = null; question.content.back = url;
}); question.content.originalBack = url;
}} },
onImageSaveClick={(file) => { );
uploadQuestionImage(question.id, quiz.qid, file, (question, url) => {
question.content.back = url; setPictureUploading(false);
}); }}
}} onDeleteClick={() => {
/> updateQuestion(question.id, (question) => {
question.content.back = null;
});
}}
onImageSaveClick={async (file) => {
setPictureUploading(true);
await uploadQuestionImage(
question.id,
quiz.qid,
file,
(question, url) => {
question.content.back = url;
},
);
setPictureUploading(false);
}}
/>
)}
</Box> </Box>
); );
} }

@ -131,6 +131,7 @@ export const ResultCard = ({ resultContract, resultData }: Props) => {
const [expand, setExpand] = useState(true); const [expand, setExpand] = useState(true);
const [resultCardSettings, setResultCardSettings] = useState(false); const [resultCardSettings, setResultCardSettings] = useState(false);
const [buttonPlus, setButtonPlus] = useState(true); const [buttonPlus, setButtonPlus] = useState(true);
const [inputValue, setInputValue] = useState("");
const question = getQuestionByContentId(resultData.content.rule.parentId); const question = getQuestionByContentId(resultData.content.rule.parentId);
const quiz = useCurrentQuiz(); const quiz = useCurrentQuiz();
@ -328,12 +329,16 @@ export const ResultCard = ({ resultContract, resultData }: Props) => {
<TextField <TextField
id="heading-description" id="heading-description"
value={resultData.content.text} value={resultData.content.text}
onChange={({ target }: { target: HTMLInputElement }) => onChange={({ target }: { target: HTMLInputElement }) => {
if (target.value.length <= 3000) {
const inputValue = target.value;
setInputValue(inputValue);
}
updateQuestion( updateQuestion(
resultData.id, resultData.id,
(question) => (question.content.text = target.value), (question) => (question.content.text = inputValue),
) );
} }}
fullWidth fullWidth
placeholder="Описание" placeholder="Описание"
multiline multiline

@ -258,7 +258,7 @@ function TariffPage() {
component="h2" component="h2"
mb="20px" mb="20px"
> >
Вы подтверждаете платёж в сумму {openModal.price} Вы подтверждаете платёж в сумму {openModal.price}
</Typography> </Typography>
<Button variant="contained" onClick={() => tryBuy(openModal)}> <Button variant="contained" onClick={() => tryBuy(openModal)}>
купить купить

@ -95,8 +95,9 @@ export default function Main({ sidebar, header, footer, Page }: Props) {
}; };
const [widthMain, setWidthMain] = useState(null); const [widthMain, setWidthMain] = useState(null);
const [heightSidebar, setHeightSidebar] = useState(null);
const mainBlock = useRef(0); const mainBlock = useRef(0);
const heightHeader = heightSidebar + 51 + 36;
const observer = useRef( const observer = useRef(
new ResizeObserver((entries) => { new ResizeObserver((entries) => {
const { width } = entries[0].contentRect; const { width } = entries[0].contentRect;
@ -121,7 +122,11 @@ export default function Main({ sidebar, header, footer, Page }: Props) {
{sidebar ? ( {sidebar ? (
<> <>
{isMobile ? ( {isMobile ? (
<SidebarMobile open={mobileSidebar} changePage={changePage} /> <SidebarMobile
open={mobileSidebar}
changePage={changePage}
setHeightSitebar={setHeightSidebar}
/>
) : ( ) : (
<Sidebar changePage={changePage} /> <Sidebar changePage={changePage} />
)} )}
@ -134,7 +139,7 @@ export default function Main({ sidebar, header, footer, Page }: Props) {
width: "100%", width: "100%",
height: isMobile height: isMobile
? mobileSidebar ? mobileSidebar
? "calc(100vh - 195px)" ? `calc(100vh - ${heightHeader}px)`
: "calc(100vh - 51px)" : "calc(100vh - 51px)"
: "calc(100vh - 80px)", : "calc(100vh - 80px)",
display: "flex", display: "flex",
@ -157,6 +162,7 @@ export default function Main({ sidebar, header, footer, Page }: Props) {
setOpenBranchingPage={openBranchingPageHC} setOpenBranchingPage={openBranchingPageHC}
widthMain={widthMain} widthMain={widthMain}
mobileSidebar={mobileSidebar} mobileSidebar={mobileSidebar}
heightSidebar={heightSidebar}
/> />
</Box> </Box>

@ -47,12 +47,14 @@ interface Props {
setOpenBranchingPage: (a: boolean) => void; setOpenBranchingPage: (a: boolean) => void;
widthMain: number; widthMain: number;
mobileSidebar: boolean; mobileSidebar: boolean;
heightSidebar: number;
} }
export default function EditPage({ export default function EditPage({
openBranchingPage, openBranchingPage,
setOpenBranchingPage, setOpenBranchingPage,
widthMain, widthMain,
mobileSidebar, mobileSidebar,
heightSidebar,
}: Props) { }: Props) {
const quiz = useCurrentQuiz(); const quiz = useCurrentQuiz();
const { editQuizId } = useQuizStore(); const { editQuizId } = useQuizStore();
@ -67,6 +69,7 @@ export default function EditPage({
const quizConfig = quiz?.config; const quizConfig = quiz?.config;
// const [openBranchingPage, setOpenBranchingPage] = useState<boolean>(false); // const [openBranchingPage, setOpenBranchingPage] = useState<boolean>(false);
const heightBar = heightSidebar + 51 + 88 + 23;
useEffect(() => { useEffect(() => {
if (editQuizId === null) navigate("/list"); if (editQuizId === null) navigate("/list");
}, [navigate, editQuizId]); }, [navigate, editQuizId]);
@ -133,7 +136,7 @@ export default function EditPage({
overflow: "auto", overflow: "auto",
height: isMobile height: isMobile
? mobileSidebar ? mobileSidebar
? `calc(100vh - 269px)` ? `calc(100vh - ${heightBar}px)`
: `calc(100vh - 125px)` : `calc(100vh - 125px)`
: isConditionMet : isConditionMet
? isBranchingLogic ? isBranchingLogic

@ -22,6 +22,7 @@ import {
Select, Select,
Tooltip, Tooltip,
Typography, Typography,
Skeleton,
useMediaQuery, useMediaQuery,
useTheme, useTheme,
} from "@mui/material"; } from "@mui/material";
@ -72,6 +73,9 @@ export default function StartPageSettings() {
const quiz = useCurrentQuiz(); const quiz = useCurrentQuiz();
const [formState, setFormState] = useState<"design" | "content">("design"); const [formState, setFormState] = useState<"design" | "content">("design");
const [mobileVersion, setMobileVersion] = useState(false); const [mobileVersion, setMobileVersion] = useState(false);
const [faviconUploding, setFaviconUploading] = useState<boolean>(false);
const [backgroundUploding, setBackgroundUploading] = useState<boolean>(false);
const [logoUploding, setLogoUploading] = useState<boolean>(false);
if (!quiz) return null; if (!quiz) return null;
@ -81,14 +85,20 @@ export default function StartPageSettings() {
const designType = quiz?.config?.startpageType; const designType = quiz?.config?.startpageType;
const favIconDropZoneElement = ( const favIconDropZoneElement = faviconUploding ? (
<Skeleton sx={{ width: "48px", height: "48px", transform: "none" }} />
) : (
<FaviconDropZone <FaviconDropZone
imageUrl={quiz.config.startpage.favIcon} imageUrl={quiz.config.startpage.favIcon}
onImageUploadClick={async (file) => { onImageUploadClick={async (file) => {
setFaviconUploading(true);
const resizedImage = await resizeFavIcon(file); const resizedImage = await resizeFavIcon(file);
uploadQuizImage(quiz.id, resizedImage, (quiz, url) => {
await uploadQuizImage(quiz.id, resizedImage, (quiz, url) => {
quiz.config.startpage.favIcon = url; quiz.config.startpage.favIcon = url;
}); });
setFaviconUploading(false);
}} }}
onDeleteClick={() => { onDeleteClick={() => {
updateQuiz(quiz.id, (quiz) => { updateQuiz(quiz.id, (quiz) => {
@ -327,30 +337,47 @@ export default function StartPageSettings() {
> >
Изображение Изображение
</Typography> </Typography>
<DropZone {backgroundUploding ? (
value={"5 MB максимум"} <Skeleton
sx={{ maxWidth: "300px" }} sx={{
imageUrl={quiz.config.startpage.background.desktop} width: "300px",
originalImageUrl={ height: "120px",
quiz.config.startpage.background.originalDesktop transform: "none",
} }}
onImageUploadClick={(file) => { />
uploadQuizImage(quiz.id, file, (quiz, url) => { ) : (
quiz.config.startpage.background.desktop = url; <DropZone
quiz.config.startpage.background.originalDesktop = url; text={"5 MB максимум"}
}); sx={{ maxWidth: "300px" }}
}} imageUrl={quiz.config.startpage.background.desktop}
onImageSaveClick={(file) => { originalImageUrl={
uploadQuizImage(quiz.id, file, (quiz, url) => { quiz.config.startpage.background.originalDesktop
quiz.config.startpage.background.desktop = url; }
}); onImageUploadClick={async (file) => {
}} setBackgroundUploading(true);
onDeleteClick={() => { await uploadQuizImage(quiz.id, file, (quiz, url) => {
updateQuiz(quiz.id, (quiz) => { quiz.config.startpage.background.desktop = url;
quiz.config.startpage.background.desktop = null; quiz.config.startpage.background.originalDesktop =
}); url;
}} });
/>
setBackgroundUploading(false);
}}
onImageSaveClick={async (file) => {
setBackgroundUploading(true);
await uploadQuizImage(quiz.id, file, (quiz, url) => {
quiz.config.startpage.background.desktop = url;
});
setBackgroundUploading(false);
}}
onDeleteClick={() => {
updateQuiz(quiz.id, (quiz) => {
quiz.config.startpage.background.desktop = null;
});
}}
/>
)}
</Box> </Box>
<ModalSizeImage /> <ModalSizeImage />
@ -383,47 +410,68 @@ export default function StartPageSettings() {
</Tooltip> </Tooltip>
)} )}
</Box> </Box>
<ButtonBase {backgroundUploding ? (
component="label" <Skeleton
sx={{
justifyContent: "center",
height: "48px",
width: "48px",
display: "flex",
alignItems: "center",
my: "20px",
}}
>
<input
onChange={(event) => {
const file = event.target.files?.[0];
if (file) {
uploadQuizImage(quiz.id, file, (quiz, url) => {
quiz.config.startpage.background.video = url;
});
// setVideo(URL.createObjectURL(file));
}
}}
hidden
accept=".mp4"
multiple
type="file"
/>
<UploadBox
icon={<UploadIcon />}
sx={{ sx={{
height: "48px",
width: "48px", width: "48px",
height: "48px",
transform: "none",
margin: "20px 0",
}} }}
/> />
</ButtonBase> ) : (
{quiz.config.startpage.background.video ? ( <>
<video <ButtonBase
src={quiz.config.startpage.background.video} component="label"
width="400" sx={{
controls justifyContent: "center",
/> height: "48px",
) : null} width: "48px",
display: "flex",
alignItems: "center",
my: "20px",
}}
>
<input
onChange={async (event) => {
setBackgroundUploading(true);
const file = event.target.files?.[0];
if (file) {
await uploadQuizImage(
quiz.id,
file,
(quiz, url) => {
quiz.config.startpage.background.video = url;
},
);
// setVideo(URL.createObjectURL(file));
}
setBackgroundUploading(false);
}}
hidden
accept=".mp4"
multiple
type="file"
/>
<UploadBox
icon={<UploadIcon />}
sx={{
height: "48px",
width: "48px",
}}
/>
</ButtonBase>
{quiz.config.startpage.background.video && (
<video
src={quiz.config.startpage.background.video}
width="400"
controls
/>
)}
</>
)}
</> </>
)} )}
{designType !== "centered" && ( {designType !== "centered" && (
@ -497,28 +545,44 @@ export default function StartPageSettings() {
> >
Логотип Логотип
</Typography> </Typography>
<DropZone {logoUploding ? (
value={"5 MB максимум"} <Skeleton
sx={{ maxWidth: "300px" }} sx={{
imageUrl={quiz.config.startpage.logo} width: "300px",
originalImageUrl={quiz.config.startpage.originalLogo} height: "120px",
onImageUploadClick={(file) => { transform: "none",
uploadQuizImage(quiz.id, file, (quiz, url) => { }}
quiz.config.startpage.logo = url; />
quiz.config.startpage.originalLogo = url; ) : (
}); <DropZone
}} text={"5 MB максимум"}
onImageSaveClick={(file) => { sx={{ maxWidth: "300px" }}
uploadQuizImage(quiz.id, file, (quiz, url) => { imageUrl={quiz.config.startpage.logo}
quiz.config.startpage.logo = url; originalImageUrl={quiz.config.startpage.originalLogo}
}); onImageUploadClick={async (file) => {
}} setLogoUploading(true);
onDeleteClick={() => { await uploadQuizImage(quiz.id, file, (quiz, url) => {
updateQuiz(quiz.id, (quiz) => { quiz.config.startpage.logo = url;
quiz.config.startpage.logo = null; quiz.config.startpage.originalLogo = url;
}); });
}}
/> setLogoUploading(false);
}}
onImageSaveClick={async (file) => {
setLogoUploading(true);
await uploadQuizImage(quiz.id, file, (quiz, url) => {
quiz.config.startpage.logo = url;
});
setLogoUploading(false);
}}
onDeleteClick={() => {
updateQuiz(quiz.id, (quiz) => {
quiz.config.startpage.logo = null;
});
}}
/>
)}
</Box> </Box>
<Typography <Typography
@ -564,28 +628,44 @@ export default function StartPageSettings() {
> >
Логотип Логотип
</Typography> </Typography>
<DropZone {logoUploding ? (
value={"5 MB максимум"} <Skeleton
sx={{ maxWidth: "300px" }} sx={{
imageUrl={quiz.config.startpage.logo} width: "300px",
originalImageUrl={quiz.config.startpage.originalLogo} height: "120px",
onImageUploadClick={(file) => { transform: "none",
uploadQuizImage(quiz.id, file, (quiz, url) => { }}
quiz.config.startpage.logo = url; />
quiz.config.startpage.originalLogo = url; ) : (
}); <DropZone
}} text={"5 MB максимум"}
onImageSaveClick={(file) => { sx={{ maxWidth: "300px" }}
uploadQuizImage(quiz.id, file, (quiz, url) => { imageUrl={quiz.config.startpage.logo}
quiz.config.startpage.logo = url; originalImageUrl={quiz.config.startpage.originalLogo}
}); onImageUploadClick={async (file) => {
}} setLogoUploading(true);
onDeleteClick={() => { await uploadQuizImage(quiz.id, file, (quiz, url) => {
updateQuiz(quiz.id, (quiz) => { quiz.config.startpage.logo = url;
quiz.config.startpage.logo = null; quiz.config.startpage.originalLogo = url;
}); });
}}
/> setLogoUploading(false);
}}
onImageSaveClick={async (file) => {
setLogoUploading(true);
await uploadQuizImage(quiz.id, file, (quiz, url) => {
quiz.config.startpage.logo = url;
});
setLogoUploading(false);
}}
onDeleteClick={() => {
updateQuiz(quiz.id, (quiz) => {
quiz.config.startpage.logo = null;
});
}}
/>
)}
</Box> </Box>
<Typography <Typography
@ -621,7 +701,7 @@ export default function StartPageSettings() {
quiz.name = e.target.value; quiz.name = e.target.value;
}) })
} }
maxLength={40} maxLength={200}
/> />
<Typography <Typography
sx={{ sx={{

@ -1,12 +1,15 @@
import { Box, Button, Skeleton, useTheme, useMediaQuery } from "@mui/material";
import Plus from "@icons/questionsPage/plus"; import Plus from "@icons/questionsPage/plus";
import { Box, Button, SxProps, Theme } from "@mui/material";
import Image from "../assets/icons/questionsPage/image"; import Image from "../assets/icons/questionsPage/image";
import type { SxProps, Theme } from "@mui/material";
interface Props { interface Props {
sx?: SxProps<Theme>; sx?: SxProps<Theme>;
imageSrc?: string; imageSrc?: string;
onImageClick?: () => void; onImageClick?: () => void;
onPlusClick?: () => void; onPlusClick?: () => void;
uploading: boolean;
} }
export default function AddOrEditImageButton({ export default function AddOrEditImageButton({
@ -14,59 +17,76 @@ export default function AddOrEditImageButton({
onPlusClick, onPlusClick,
sx, sx,
imageSrc, imageSrc,
uploading = false,
}: Props) { }: Props) {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(790));
return ( return (
<Box <>
sx={{ {uploading ? (
display: "flex", <Skeleton
height: "40px", variant="rounded"
minWidth: "60px", sx={{
width: "60px", height: "40px",
borderRadius: "3px", width: isMobile ? "auto" : "80px",
overflow: "hidden", margin: isMobile ? "8px" : "0 10px 0 8px",
...sx, }}
}} />
> ) : (
<Button <Box
onClick={onImageClick} sx={{
sx={{ display: "flex",
p: 0, height: "40px",
minWidth: "40px", minWidth: "60px",
flexGrow: 1, width: "60px",
backgroundColor: "#EEE4FC", borderRadius: "3px",
}} overflow: "hidden",
> ...sx,
{imageSrc ? ( }}
<img >
src={imageSrc} <Button
alt="" onClick={onImageClick}
style={{
width: "100%",
height: "100%",
objectFit: "scale-down",
display: "block",
}}
/>
) : (
<Image
sx={{ sx={{
height: "100%", p: 0,
width: "100%", minWidth: "40px",
flexGrow: 1,
backgroundColor: "#EEE4FC",
}} }}
/> >
)} {imageSrc ? (
</Button> <img
<Button src={imageSrc}
onClick={onPlusClick} alt=""
data-cy="add-image-button" style={{
sx={{ width: "100%",
p: 0, height: "100%",
minWidth: "20px", objectFit: "scale-down",
width: "20px", display: "block",
}} }}
> />
<Plus /> ) : (
</Button> <Image
</Box> sx={{
height: "100%",
width: "100%",
}}
/>
)}
</Button>
<Button
onClick={onPlusClick}
data-cy="add-image-button"
sx={{
p: 0,
minWidth: "20px",
width: "20px",
}}
>
<Plus />
</Button>
</Box>
)}
</>
); );
} }

@ -1,4 +1,4 @@
import { FC } from "react"; import { useState, FC } from "react";
import { import {
Box, Box,
Button, Button,
@ -25,6 +25,7 @@ interface Iprops {
} }
export const MediaSelectionAndDisplay: FC<Iprops> = ({ resultData }) => { export const MediaSelectionAndDisplay: FC<Iprops> = ({ resultData }) => {
const [pictureUploding, setPictureUploading] = useState<boolean>(false);
const quizQid = useCurrentQuiz()?.qid; const quizQid = useCurrentQuiz()?.qid;
const theme = useTheme(); const theme = useTheme();
const { const {
@ -38,6 +39,8 @@ export const MediaSelectionAndDisplay: FC<Iprops> = ({ resultData }) => {
const [isImageUploadOpen, openImageUploadModal, closeImageUploadModal] = const [isImageUploadOpen, openImageUploadModal, closeImageUploadModal] =
useDisclosure(); useDisclosure();
async function handleImageUpload(file: File) { async function handleImageUpload(file: File) {
setPictureUploading(true);
const url = await uploadQuestionImage( const url = await uploadQuestionImage(
resultData.id, resultData.id,
quizQid, quizQid,
@ -49,6 +52,8 @@ export const MediaSelectionAndDisplay: FC<Iprops> = ({ resultData }) => {
); );
closeImageUploadModal(); closeImageUploadModal();
openCropModal(file, url); openCropModal(file, url);
setPictureUploading(false);
} }
function handleCropModalSaveClick(imageBlob: Blob) { function handleCropModalSaveClick(imageBlob: Blob) {
@ -142,6 +147,7 @@ export const MediaSelectionAndDisplay: FC<Iprops> = ({ resultData }) => {
> >
<AddOrEditImageButton <AddOrEditImageButton
imageSrc={resultData.content.back} imageSrc={resultData.content.back}
uploading={pictureUploding}
onImageClick={() => { onImageClick={() => {
if (resultData.content.back) { if (resultData.content.back) {
return openCropModal( return openCropModal(

@ -32,6 +32,7 @@ import { clearUserData } from "@root/user";
interface Iprops { interface Iprops {
open: boolean; open: boolean;
changePage: (step: number) => void; changePage: (step: number) => void;
setHeightSitebar: any;
} }
const quizSetupSteps = [ const quizSetupSteps = [
@ -46,12 +47,29 @@ const quizSetupSteps = [
{ sidebarIcon: <Question style={{ color: "#974BFA", fontSize: "24px" }} /> }, { sidebarIcon: <Question style={{ color: "#974BFA", fontSize: "24px" }} /> },
] as const; ] as const;
export const SidebarMobile: FC<Iprops> = ({ open, changePage }) => { export const SidebarMobile: FC<Iprops> = ({
open,
changePage,
setHeightSitebar,
}) => {
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null); const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const [inputOpen, setInputOpen] = useState<boolean>(false); const [inputOpen, setInputOpen] = useState<boolean>(false);
const quiz = useCurrentQuiz(); const quiz = useCurrentQuiz();
const ref = useRef(null); const ref = useRef(null);
const heightSidebar = useRef(null);
const navigate = useNavigate(); const navigate = useNavigate();
const observer = useRef(
new ResizeObserver((entries) => {
const { height } = entries[0].contentRect;
setHeightSitebar(height);
}),
);
useEffect(() => {
observer.current.observe(heightSidebar.current);
}, [heightSidebar, observer]);
const handleClick = (event) => { const handleClick = (event) => {
setAnchorEl(anchorEl ? null : event.currentTarget); setAnchorEl(anchorEl ? null : event.currentTarget);
}; };
@ -80,6 +98,7 @@ export const SidebarMobile: FC<Iprops> = ({ open, changePage }) => {
const id = openPopper ? "simple-popper" : undefined; const id = openPopper ? "simple-popper" : undefined;
return ( return (
<Box <Box
ref={heightSidebar}
sx={{ sx={{
display: open ? "block" : "none", display: open ? "block" : "none",
minHeight: "134px", minHeight: "134px",
@ -148,7 +167,9 @@ export const SidebarMobile: FC<Iprops> = ({ open, changePage }) => {
/> />
</FormControl> </FormControl>
) : ( ) : (
<Typography color={"white"}>{quiz.name}</Typography> <Typography color={"white"} sx={{ wordBreak: "break-word" }}>
{quiz.name}
</Typography>
)} )}
</Box> </Box>
<IconButton onClick={() => setInputOpen(true)}> <IconButton onClick={() => setInputOpen(true)}>