feat(Image upload): image upload to ImageUsers folder

Image upload added.

Todo :

- delete from folder when users delete

-add
This commit is contained in:
Laurian-Dufrechou
2023-03-30 00:41:07 +02:00
parent a039d44de4
commit 448b7a100f
6 changed files with 458 additions and 136 deletions
+57
View File
@@ -18,6 +18,7 @@
"bcrypt": "^5.1.0", "bcrypt": "^5.1.0",
"eslint": "8.35.0", "eslint": "8.35.0",
"eslint-config-next": "13.2.3", "eslint-config-next": "13.2.3",
"form-data": "^4.0.0",
"formidable": "^2.1.1", "formidable": "^2.1.1",
"framer-motion": "^10.8.5", "framer-motion": "^10.8.5",
"fs": "^0.0.1-security", "fs": "^0.0.1-security",
@@ -2722,6 +2723,11 @@
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==",
"optional": true "optional": true
}, },
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/available-typed-arrays": { "node_modules/available-typed-arrays": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
@@ -3074,6 +3080,17 @@
"resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.2.tgz", "resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.2.tgz",
"integrity": "sha512-kJhwH5nAwb34tmyuqq/lgjEKzlFXn1U99NlnB6Ws4qVaERcRUYeYP1cBw6BJ4vxaWStAUEef4WMr7WjOCnBt8w==" "integrity": "sha512-kJhwH5nAwb34tmyuqq/lgjEKzlFXn1U99NlnB6Ws4qVaERcRUYeYP1cBw6BJ4vxaWStAUEef4WMr7WjOCnBt8w=="
}, },
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/commondir": { "node_modules/commondir": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
@@ -3328,6 +3345,14 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/delegates": { "node_modules/delegates": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
@@ -4165,6 +4190,19 @@
"is-callable": "^1.1.3" "is-callable": "^1.1.3"
} }
}, },
"node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/formidable": { "node_modules/formidable": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.1.tgz", "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.1.tgz",
@@ -5547,6 +5585,25 @@
"node": ">=8.6" "node": ">=8.6"
} }
}, },
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mimic-fn": { "node_modules/mimic-fn": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+1
View File
@@ -19,6 +19,7 @@
"bcrypt": "^5.1.0", "bcrypt": "^5.1.0",
"eslint": "8.35.0", "eslint": "8.35.0",
"eslint-config-next": "13.2.3", "eslint-config-next": "13.2.3",
"form-data": "^4.0.0",
"formidable": "^2.1.1", "formidable": "^2.1.1",
"framer-motion": "^10.8.5", "framer-motion": "^10.8.5",
"fs": "^0.0.1-security", "fs": "^0.0.1-security",
+69 -5
View File
@@ -12,16 +12,70 @@ import {
ModalFooter, ModalFooter,
ModalHeader, ModalHeader,
ModalOverlay, ModalOverlay,
Text,
useDisclosure, useDisclosure,
useToast,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import { useState } from "react"; import { useState } from "react";
import { RiEditBoxLine } from "react-icons/ri"; import { RiEditBoxLine } from "react-icons/ri";
export default function ModalModifyImages(props) { export default function ModalModifyImages(props) {
const { isOpen, onOpen, onClose } = useDisclosure(); const { isOpen, onOpen, onClose } = useDisclosure();
const { images, userData, setUserData, files, setFiles } = props; const { images, user, userData, setUserData, files, setFiles } = props;
const [listImage, setlistImage] = useState(images); const [listImage, setlistImage] = useState(images);
const toast = useToast();
const uploadImage = async (file) => {
console.log(file);
const body = new FormData();
body.append("file", file);
const imagePostOptions = {
method: "POST",
body,
};
fetch(`/api/file/file`, imagePostOptions)
.then((res) => {
console.log(res);
toast({
title: `Ajout d'image effectué`,
status: "success",
isClosable: true,
});
})
.catch((err) => {
toast({
title: `Erreur lors de l'ajout des images`,
status: "error",
isClosable: true,
});
console.log(err);
});
};
const deleteImage = async (fileName) => {
const body = new FormData();
body.append("fileName", fileName);
const imageDeleteOptions = {
method: "DELETE",
};
fetch(`/api/file/file/${fileName}`, imageDeleteOptions)
.then((res) => {
console.log(res);
Toast({
title: `Suppression d'image effectué`,
status: "success",
isClosable: true,
});
})
.catch(() => {
toast({
title: `Erreur lors de la suppression des images`,
status: "error",
isClosable: true,
});
});
};
return ( return (
<> <>
@@ -51,7 +105,6 @@ export default function ModalModifyImages(props) {
> >
{listImage.map((image, index) => ( {listImage.map((image, index) => (
<GridItem key={index}> <GridItem key={index}>
<Text>{image}</Text>
<Flex direction={"column"} gap={"1rem"}> <Flex direction={"column"} gap={"1rem"}>
<Image src={image} /> <Image src={image} />
<Button <Button
@@ -75,8 +128,19 @@ export default function ModalModifyImages(props) {
accept={"image/png, image/jpeg, image/webp"} accept={"image/png, image/jpeg, image/webp"}
onChange={({ target }) => { onChange={({ target }) => {
const file = target.files[0]; const file = target.files[0];
setlistImage([...listImage, URL.createObjectURL(file)]); const extension = file.name.split(".").pop();
setFiles([...files, file]); const newFile = new File(
[file],
`${user.id}_${listImage.length}.${extension}`,
{
type: file.type,
}
);
uploadImage(newFile);
setlistImage([
...listImage,
URL.createObjectURL(newFile),
]);
}} }}
></Input> ></Input>
</GridItem> </GridItem>
+15 -5
View File
@@ -1,5 +1,6 @@
import fs from "fs"; import fs from "fs";
import formidable from "formidable"; import formidable from "formidable";
export const config = { export const config = {
api: { api: {
bodyParser: false, bodyParser: false,
@@ -7,11 +8,14 @@ export const config = {
}; };
const post = async (req, res) => { const post = async (req, res) => {
const form = new formidable.IncomingForm(); const form = new formidable.IncomingForm({
console.log(form); maxFileSize: 5 * 1024 * 1024,
uploadDir: "./public/imageUsers",
keepExtensions: true,
});
form.parse(req, async function (err, fields, files) { form.parse(req, async function (err, fields, files) {
await saveFile(files.file); saveFile(files.file);
return res.status(201).send(""); return res.status(201).send("image saved");
}); });
}; };
@@ -22,13 +26,19 @@ const saveFile = async (file) => {
return; return;
}; };
const deleteImage = async (req, res) => {
const body = req.body;
await fs.unlinkSync(`./public/imageUsers/${body.name}`);
res.status(200).send("image deleted");
};
export default (req, res) => { export default (req, res) => {
req.method === "POST" req.method === "POST"
? post(req, res) ? post(req, res)
: req.method === "PUT" : req.method === "PUT"
? console.log("PUT") ? console.log("PUT")
: req.method === "DELETE" : req.method === "DELETE"
? console.log("DELETE") ? deleteImage(req, res)
: req.method === "GET" : req.method === "GET"
? console.log("GET") ? console.log("GET")
: res.status(404).send(""); : res.status(404).send("");
+278 -126
View File
@@ -2,6 +2,7 @@ import { useSession } from "next-auth/react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import type { Session } from "@/models/auth/Session"; import type { Session } from "@/models/auth/Session";
import Carousel from "@/components/Carousel"; import Carousel from "@/components/Carousel";
import { Gender } from "@prisma/client";
import { import {
Box, Box,
Button, Button,
@@ -13,23 +14,29 @@ import {
EditablePreview, EditablePreview,
EditableTextarea, EditableTextarea,
Flex, Flex,
Spacer, FormControl,
FormErrorMessage,
FormLabel,
HStack,
Input,
Radio,
RadioGroup,
Text, Text,
useDisclosure,
useToast, useToast,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import { RiEditBoxLine } from "react-icons/ri";
import BottomBar from "@/components/BottomBar";
import ModalModifyImages from "@/components/ModalModifyImages"; import ModalModifyImages from "@/components/ModalModifyImages";
import { useState } from "react"; import { useState } from "react";
import { useForm, Controller } from "react-hook-form";
export default function UserProfile() { export default function UserProfile() {
const router = useRouter(); const router = useRouter();
const toast = useToast(); const toast = useToast();
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const { handleSubmit, control } = useForm();
const [userData, setUserData] = useState({}); const [userData, setUserData] = useState({});
const [files, setFiles] = useState([]); const [files, setFiles] = useState([]);
@@ -39,22 +46,41 @@ export default function UserProfile() {
if (status === "authenticated") { if (status === "authenticated") {
const { user } = session as unknown as Session; const { user } = session as unknown as Session;
const saveData = () => { const getTextGender = (gender) => {
const options = { switch (gender) {
method: "PUT", case Gender.MALE:
headers: { "Content-Type": "application/json" }, return "Homme";
body: JSON.stringify(userData), case Gender.FEMALE:
}; return "Femme";
case Gender.OTHER:
return "Autre";
case Gender.UNKNOWN:
return "Non renseigné";
}
};
setIsLoading(true); const saveData = (values: any) => {
const trueValues = Object.keys(values).reduce((acc, key) => {
if (values[key] !== "" && values[key] !== undefined) {
acc[key] = values[key];
}
return acc;
}, {});
const options = {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(trueValues),
};
// if (files.length > 0) { // if (files.length > 0) {
// files.forEach((file) => { // files.forEach((file) => {
// console.log(file);
// const body = new FormData(); // const body = new FormData();
// body.append("file", file); // body.append("file", file);
// const imagePostOptions = { // const imagePostOptions = {
// method: "POST", // method: "POST",
// headers: { "Content-Type": "application/json" }, // headers: { "Content-Type": "multipart/form-data" },
// body, // body,
// }; // };
@@ -79,24 +105,28 @@ export default function UserProfile() {
// }); // });
// } // }
fetch(`/api/users/${user.id}`, options) if (Object.keys(trueValues).length > 0) {
.then(() => { setIsLoading(true);
setIsLoading(false); fetch(`/api/users/${user.id}`, options)
toast({ .then((res) => {
title: `Modifications effectuées`, console.log(res);
status: "success", setIsLoading(false);
isClosable: true, toast({
title: `Modifications effectuées`,
status: "success",
isClosable: true,
});
// router.reload();
})
.catch(() => {
setIsLoading(false);
toast({
title: `Erreur lors de l'envoi des modifications`,
status: "error",
isClosable: true,
});
}); });
// router.reload(); }
})
.catch(() => {
setIsLoading(false);
toast({
title: `Erreur lors de l'envoi des modifications`,
status: "error",
isClosable: true,
});
});
}; };
const formateDate = (dateString: string) => { const formateDate = (dateString: string) => {
@@ -104,24 +134,9 @@ export default function UserProfile() {
return new Date(dateString).toLocaleDateString([], options); return new Date(dateString).toLocaleDateString([], options);
}; };
// const refinedUser = {
// // ...user,
// firstName: "Jean",
// lastName: "Dujardin",
// birthdate: formateDate(new Date().toString()),
// aPropos: "Je suis la personne fictive la plus fictive",
// images: ["135538.webp"],
// passions: ["Sport", "Voiture", "Cuisine"],
// };
return ( return (
<Box bgColor={"purple.50"}> <Box bgColor={"purple.50"}>
<Container <Container justifyContent={"center"} maxWidth={"70rem"} mt={"1rem"}>
justifyContent={"center"}
maxWidth={"70rem"}
mt={"1rem"}
bgColor={"purple.50"}
>
<Flex flexDirection={"column"} alignItems={"center"} gap={"1rem"}> <Flex flexDirection={"column"} alignItems={"center"} gap={"1rem"}>
<Box width={"50%"}> <Box width={"50%"}>
{userData.images ? ( {userData.images ? (
@@ -134,10 +149,12 @@ export default function UserProfile() {
)} )}
</Box> </Box>
{/* {modal} */} {/* {modal} */}
{!userData.images ? ( {!userData.images ? (
<ModalModifyImages <ModalModifyImages
setUserData={setUserData} setUserData={setUserData}
userData={userData} userData={userData}
user={user}
images={user.images} images={user.images}
files={files} files={files}
setFiles={setFiles} setFiles={setFiles}
@@ -146,100 +163,235 @@ export default function UserProfile() {
<ModalModifyImages <ModalModifyImages
setUserData={setUserData} setUserData={setUserData}
userData={userData} userData={userData}
user={user}
images={userData.images} images={userData.images}
files={files} files={files}
setFiles={setFiles} setFiles={setFiles}
/> />
)} )}
<Divider />
<Divider colorScheme={"purple"} />
<Text align={"center"} as="i" color={"grey"}> <Text align={"center"} as="i" color={"grey"}>
Modifiez les champs en les selectionnants Modifiez les champs en les selectionnants
</Text> </Text>
<Box width={"100%"}>
<Flex justify={"space-between"} mb={"1rem"}> <form onSubmit={handleSubmit(saveData)}>
<Flex gap={"0.5rem"}> <FormControl width={"100%"}>
<Text margin={"auto"}>Prénom : </Text> <Flex justify={"space-between"} mb={"1rem"}>
<Editable <Box>
id={"lastName"} <FormLabel as="legend" htmlFor={"firstName"}>
Prénom :
</FormLabel>
<Controller
name={"firstName"}
control={control}
render={({ field }) => (
<Editable
{...field}
id={"firstName"}
placeholder={"Non renseigné"}
as={"b"}
defaultValue={
user.firstName === null ? "" : user.firstName
}
>
<EditablePreview />
<EditableInput />
</Editable>
)}
/>
</Box>
<Box>
<FormLabel as={"legend"} htmlFor={"lastName"}>
Nom :
</FormLabel>
<Controller
name={"lastName"}
control={control}
render={({ field }) => (
<Editable
{...field}
id={"lastName"}
as={"b"}
placeholder={"Non renseigné"}
defaultValue={
user.lastName === null ? "" : user.lastName
}
// onSubmit={(value) => {
// setUserData({ ...userData, lastName: value });
// }}
>
<EditablePreview />
<EditableInput />
</Editable>
)}
/>
</Box>
<Box>
<FormLabel as={"legend"} htmlFor={"birthdate"}>
Date de naissance :
</FormLabel>
<Editable
id={"birthdate"}
as="b"
color={"grey"}
isDisabled={true}
defaultValue={
user.birthdate === null
? "Non renseigné"
: formateDate(user.birthdate.toString())
}
>
<EditablePreview />
</Editable>
</Box>
</Flex>
<Divider colorScheme={"purple"} />
<Flex justify={"space-between"} my={"1rem"}>
<Box>
<FormLabel as={"legend"} htmlFor={"location"}>
Ville :
</FormLabel>
<Editable
id={"location"}
as="b"
color={"grey"}
isDisabled={true}
defaultValue={
user.location === null || user.location === ""
? "Rendez vous sur la carte"
: user.location
}
>
<EditablePreview />
</Editable>
</Box>
<Box>
<FormLabel as={"legend"} htmlFor={"email"}>
Adresse mail :
</FormLabel>
<Editable
id={"email"}
as="b"
color={"grey"}
isDisabled={true}
defaultValue={user.email}
>
<EditablePreview />
</Editable>
</Box>
</Flex>
<Divider colorScheme={"purple"} />
<Box my={"1rem"}>
<FormLabel as={"legend"} htmlFor={"bio"}>
À propos :
</FormLabel>
<Controller
name={"bio"}
control={control}
render={({ field }) => (
<Editable
{...field}
id={"bio"}
as="b"
width={"100%"}
placeholder={"Non renseigné"}
defaultValue={user.bio === null ? "" : user.bio}
onSubmit={(value) => {
setUserData({ ...userData, bio: value });
}}
>
<EditablePreview />
<EditableTextarea />
</Editable>
)}
/>
</Box>
<Divider colorScheme={"purple"} />
<Box my={"1rem"}>
<Box>
<FormLabel as={"legend"} htmlFor={"gender"}>
Genre :
</FormLabel>
<Controller
name={"gender"}
control={control}
render={({ field }) => (
<RadioGroup
{...field}
colorScheme={"purple"}
id={"gender"}
as="b"
defaultValue={
user.gender === null ? Gender.UNKNOWN : user.gender
}
// onChange={(value) => {
// setUserData({ ...userData, gender: value });
// }}
>
<HStack spacing={"0.5rem"}>
<Radio value={Gender.MALE}>
{getTextGender(Gender.MALE)}
</Radio>
<Radio value={Gender.FEMALE}>
{getTextGender(Gender.FEMALE)}
</Radio>
<Radio value={Gender.OTHER}>
{getTextGender(Gender.OTHER)}
</Radio>
<Radio value={Gender.UNKNOWN}>
{getTextGender(Gender.UNKNOWN)}
</Radio>
</HStack>
</RadioGroup>
)}
/>
</Box>
{/* <Box>
<FormLabel as={"legend"} htmlFor={"preference"}>
Préference :
</FormLabel>
<RadioGroup
id={"preference"}
as="b" as="b"
defaultValue={ value={
user.lastName === null || user.lastName === "" user.preference === null
? "Non renseigné" ? Gender.UNKNOWN
: user.lastName : user.preference
} }
onSubmit={(value) => { onChange={(value) => {
setUserData({ ...userData, lastName: value }); setUserData({ ...userData, preference: value });
}} }}
> >
<EditablePreview /> <HStack spacing={"0.5rem"}>
<EditableInput /> <Radio value={Gender.MALE}>
</Editable> {getTextGender(Gender.MALE)}
</Flex> </Radio>
<Flex gap={"0.5rem"}> <Radio value={Gender.FEMALE}>
<Text margin={"auto"}>Nom : </Text> {getTextGender(Gender.FEMALE)}
<Editable </Radio>
id={"firstName"} <Radio value={Gender.OTHER}>
as="b" {getTextGender(Gender.OTHER)}
defaultValue={ </Radio>
user.firstName === null || user.firstName === "" <Radio value={Gender.UNKNOWN}>
? "Non renseigné" {getTextGender(Gender.UNKNOWN)}
: user.firstName </Radio>
} </HStack>
onSubmit={(value) => { </RadioGroup>
setUserData({ ...userData, firstName: value }); </Flex> */}
}} </Box>
<Divider colorScheme={"purple"} />
<Center my={"1rem"}>
<Button
colorScheme={"purple"}
isLoading={isLoading}
type="submit"
> >
<EditablePreview /> Sauvegarder les modifications
<EditableInput /> </Button>
</Editable> </Center>
</Flex> </FormControl>
<Flex gap={"0.5rem"}> </form>
<Text margin={"auto"}>Date de naissance : </Text>
<Text margin={"auto"} id={"birthdate"} as="b" color={"grey"}>
{user.birthdate === null
? "Non renseigné"
: formateDate(user.birthdate.toString())}
</Text>
</Flex>
<Flex gap={"0.5rem"}>
<Text>Ville : </Text>
<Text id={"location"} as="b" color={"grey"}>
{user.location === null || user.location === ""
? "Non renseigné"
: user.location}
</Text>
</Flex>
<Flex>
<Text>Adresse mail : </Text>
<Text id={"email"} as="b" color={"grey"}>
{user.email}
</Text>
</Flex>
</Flex>
<Flex gap={"0.5rem"}>
<Text width={"100%"} align={"right"} margin={"auto"}>
À propos :
</Text>
<Editable
id={"bio"}
as="b"
width={"100%"}
defaultValue={
user.bio === null || user.bio === ""
? "Non renseigné"
: user.bio
}
onSubmit={(value) => {
setUserData({ ...userData, bio: value });
}}
>
<EditablePreview />
<EditableTextarea />
</Editable>
</Flex>
{/* préférences / sexe / type de relation recherchés */}
</Box>
<BottomBar variant={"fixed"} saveData={saveData} />
</Flex> </Flex>
</Container> </Container>
</Box> </Box>
+38
View File
@@ -1684,6 +1684,11 @@
"resolved" "https://registry.npmjs.org/async/-/async-3.2.4.tgz" "resolved" "https://registry.npmjs.org/async/-/async-3.2.4.tgz"
"version" "3.2.4" "version" "3.2.4"
"asynckit@^0.4.0":
"integrity" "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
"resolved" "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz"
"version" "0.4.0"
"available-typed-arrays@^1.0.5": "available-typed-arrays@^1.0.5":
"integrity" "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" "integrity" "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw=="
"resolved" "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz" "resolved" "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz"
@@ -1906,6 +1911,13 @@
"resolved" "https://registry.npmjs.org/color2k/-/color2k-2.0.2.tgz" "resolved" "https://registry.npmjs.org/color2k/-/color2k-2.0.2.tgz"
"version" "2.0.2" "version" "2.0.2"
"combined-stream@^1.0.8":
"integrity" "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="
"resolved" "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz"
"version" "1.0.8"
dependencies:
"delayed-stream" "~1.0.0"
"commondir@^1.0.1": "commondir@^1.0.1":
"integrity" "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" "integrity" "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="
"resolved" "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz" "resolved" "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz"
@@ -2094,6 +2106,11 @@
"rimraf" "^3.0.2" "rimraf" "^3.0.2"
"slash" "^3.0.0" "slash" "^3.0.0"
"delayed-stream@~1.0.0":
"integrity" "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
"resolved" "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
"version" "1.0.0"
"delegates@^1.0.0": "delegates@^1.0.0":
"integrity" "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" "integrity" "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="
"resolved" "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz" "resolved" "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz"
@@ -2623,6 +2640,15 @@
dependencies: dependencies:
"is-callable" "^1.1.3" "is-callable" "^1.1.3"
"form-data@^4.0.0":
"integrity" "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww=="
"resolved" "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz"
"version" "4.0.0"
dependencies:
"asynckit" "^0.4.0"
"combined-stream" "^1.0.8"
"mime-types" "^2.1.12"
"formidable@^2.1.1": "formidable@^2.1.1":
"integrity" "sha512-0EcS9wCFEzLvfiks7omJ+SiYJAiD+TzK4Pcw1UlUoGnhUxDcMKjt0P7x8wEb0u6OHu8Nb98WG3nxtlF5C7bvUQ==" "integrity" "sha512-0EcS9wCFEzLvfiks7omJ+SiYJAiD+TzK4Pcw1UlUoGnhUxDcMKjt0P7x8wEb0u6OHu8Nb98WG3nxtlF5C7bvUQ=="
"resolved" "https://registry.npmjs.org/formidable/-/formidable-2.1.1.tgz" "resolved" "https://registry.npmjs.org/formidable/-/formidable-2.1.1.tgz"
@@ -3465,6 +3491,18 @@
"braces" "^3.0.2" "braces" "^3.0.2"
"picomatch" "^2.3.1" "picomatch" "^2.3.1"
"mime-db@1.52.0":
"integrity" "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
"resolved" "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz"
"version" "1.52.0"
"mime-types@^2.1.12":
"integrity" "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="
"resolved" "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz"
"version" "2.1.35"
dependencies:
"mime-db" "1.52.0"
"mimic-fn@^2.1.0": "mimic-fn@^2.1.0":
"integrity" "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" "integrity" "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
"resolved" "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz" "resolved" "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz"