Merge branch 'skeletons' into input-limit

This commit is contained in:
Nastya 2024-02-15 11:02:56 +03:00
commit c0e7396455
7 changed files with 336 additions and 184 deletions

@ -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) {

@ -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) {

@ -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>
); );
} }

@ -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)}>
купить купить

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

@ -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(