feat(UserData fecthed in userProfile): tanstack query

This commit is contained in:
Laurian-Dufrechou
2023-04-03 21:35:03 +02:00
parent 05ae0eedd8
commit 13b8a0481c
13 changed files with 872 additions and 409 deletions
+21
View File
@@ -0,0 +1,21 @@
import { Flex, Spinner } from "@chakra-ui/react";
export default function LoadingPage() {
return (
<Flex
justifyContent="center"
alignItems="center"
height="100vh"
width="100vw"
bg="gray.100"
>
<Spinner
thickness="4px"
speed="0.65s"
emptyColor="gray.200"
color="purple.500"
size="xl"
/>
</Flex>
);
}
@@ -10,21 +10,19 @@ import {
ModalFooter,
ModalHeader,
ModalOverlay,
Text,
useCheckbox,
useCheckboxGroup,
useDisclosure,
useToast,
} from "@chakra-ui/react";
import { useEffect } from "react";
import { useState } from "react";
import { useQueryClient } from "@tanstack/react-query";
import { RiEditBoxLine } from "react-icons/ri";
import CustomCheckbox from "./CustomCheckbox";
export default function ModalChoosePassion(props) {
const { isOpen, onOpen, onClose } = useDisclosure();
const { user, passions } = props;
const toast = useToast();
const toast = useToast({ position: "top", isClosable: true });
const client = useQueryClient();
const { value, getCheckboxProps } = useCheckboxGroup({
defaultValue: user.PassionID,
@@ -46,8 +44,8 @@ export default function ModalChoosePassion(props) {
title: "Centres d'intérêts mis à jour",
status: "success",
duration: 9000,
isClosable: true,
});
client.invalidateQueries("user");
onClose();
})
.catch((err) => {
@@ -98,7 +96,9 @@ export default function ModalChoosePassion(props) {
<Button
colorScheme="purple"
mr={3}
onClick={() => savePassions(value)}
onClick={() => {
savePassions(value);
}}
>
Save
</Button>
@@ -13,8 +13,10 @@ import {
ModalHeader,
ModalOverlay,
useDisclosure,
useQuery,
useToast,
} from "@chakra-ui/react";
import { useQueryClient } from "@tanstack/react-query";
import { useState } from "react";
import { RiEditBoxLine } from "react-icons/ri";
@@ -22,7 +24,9 @@ export default function ModalModifyImages(props) {
const { isOpen, onOpen, onClose } = useDisclosure();
const { images, user, userData, setUserData } = props;
const [listImage, setlistImage] = useState(images);
const toast = useToast();
const toast = useToast({ position: "top", isClosable: true });
const client = useQueryClient();
const uploadImage = async (file) => {
const body = new FormData();
@@ -187,10 +191,7 @@ export default function ModalModifyImages(props) {
colorScheme="purple"
mr={3}
onClick={(e) => {
setUserData({
...userData,
images: [...listImage],
});
client.invalidateQueries("user");
onClose();
}}
>
+13 -5
View File
@@ -1,14 +1,22 @@
import '@/styles/globals.css';
import type {AppProps} from 'next/app';
import {ChakraProvider} from '@chakra-ui/react';
import { SessionProvider } from "next-auth/react"
import "@/styles/globals.css";
import type { AppProps } from "next/app";
import { ChakraProvider } from "@chakra-ui/react";
import { SessionProvider } from "next-auth/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
export default function App({Component, pageProps: { session, ...pageProps }}: AppProps) {
const queryClient = new QueryClient();
export default function App({
Component,
pageProps: { session, ...pageProps },
}: AppProps) {
return (
<QueryClientProvider client={queryClient}>
<ChakraProvider>
<SessionProvider session={session}>
<Component {...pageProps} />
</SessionProvider>
</ChakraProvider>
</QueryClientProvider>
);
}
+84 -80
View File
@@ -1,91 +1,95 @@
import {
Box,
Button,
Flex,
FormControl,
FormErrorMessage,
FormLabel,
Input,
useToast,
} from '@chakra-ui/react';
import { useSession } from 'next-auth/react';
import { useRouter } from 'next/router';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
export default function settings() {
Box,
Button,
Flex,
FormControl,
FormErrorMessage,
FormLabel,
Input,
useToast,
} from "@chakra-ui/react";
import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import { useState } from "react";
import { useForm } from "react-hook-form";
const router = useRouter();
const toast = useToast();
export default function settings() {
const router = useRouter();
const toast = useToast({ position: "top", isClosable: true });
const [isLoading, setIsLoading] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const {
handleSubmit,
register,
formState: { errors, isSubmitting },
} = useForm()
const {
handleSubmit,
register,
formState: { errors, isSubmitting },
} = useForm();
const [userData, setUserData] = useState({});
const [userData, setUserData] = useState({});
const { data: session, status } = useSession();
if (status === "unauthenticated") router.push("/login");
const { data: session, status } = useSession();
if (status === "unauthenticated") router.push("/login");
if (status === "authenticated") {
const { user } = session as unknown as Session;
if (status === "authenticated") {
const { user } = session as unknown as Session;
if (user.role !== "ADMIN") router.push("/login");
if (user.role !== "ADMIN") router.push("/login");
const savePassion = (passion: any) => {
const options = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(passion),
};
fetch(`/api/passions`, options)
.then((res) => {
setIsLoading(false);
toast({
position:'top',
title: `Passion ajoutée`,
status: "success",
isClosable: true,
});
})
.catch((err) => {
setIsLoading(false);
toast({
title: `Erreur lors de l'ajout`,
position :'top',
status: "error",
isClosable: true,
});
});
}
const savePassion = (passion: any) => {
const options = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(passion),
};
return (
<>
<Box as="form" id='form_passion' width={"80%"} onSubmit={handleSubmit(savePassion)}>
<FormControl isInvalid={errors.name}>
<FormLabel htmlFor='passion'>Passion</FormLabel>
<Input
id='passion'
placeholder='passion'
{...register('name', {
required: 'This is required',
})}
/>
<FormErrorMessage>
{errors.name && errors.name.message}
</FormErrorMessage>
</FormControl>
<Button mt={4} colorScheme='purple' isLoading={isSubmitting} type='submit'>
Submit
</Button>
</Box>
</>
);
}
fetch(`/api/passions`, options)
.then((res) => {
setIsLoading(false);
toast({
title: `Passion ajoutée`,
status: "success",
});
})
.catch((err) => {
setIsLoading(false);
toast({
title: `Erreur lors de l'ajout`,
status: "error",
});
});
};
return (
<>
<Box
as="form"
id="form_passion"
width={"80%"}
onSubmit={handleSubmit(savePassion)}
>
<FormControl isInvalid={errors.name}>
<FormLabel htmlFor="passion">Passion</FormLabel>
<Input
id="passion"
placeholder="passion"
{...register("name", {
required: "This is required",
})}
/>
<FormErrorMessage>
{errors.name && errors.name.message}
</FormErrorMessage>
</FormControl>
<Button
mt={4}
colorScheme="purple"
isLoading={isSubmitting}
type="submit"
>
Submit
</Button>
</Box>
</>
);
}
}
+330 -305
View File
@@ -18,7 +18,6 @@ import {
FormErrorMessage,
FormLabel,
HStack,
Input,
Radio,
RadioGroup,
Text,
@@ -29,15 +28,17 @@ import {
import ModalModifyImages from "@/components/layout/user_profile/ModalModifyImages";
import ModalChoosePassion from "@/components/layout/user_profile/ModalChoosePassion";
import ProfileTagList from "@/components/layout/user_profile/ProfileTagList";
import LoadingPage from "@/components/LoadingPage";
import { useEffect, useState } from "react";
import { useForm, Controller } from "react-hook-form";
import { useQuery } from "@tanstack/react-query";
export default function UserProfile() {
const router = useRouter();
const toast = useToast();
const toast = useToast({ position: "top", isClosable: true });
const [isLoading, setIsLoading] = useState(false);
const [currentlyLoading, setCurrentlyLoading] = useState(false);
const [passions, setPassions] = useState(null);
const {
@@ -58,320 +59,347 @@ export default function UserProfile() {
}, []);
// faire un useEffect ou je fetch user
const [userData, setUserData] = useState({});
const { data: session, status } = useSession();
if (status === "unauthenticated") router.push("/login");
if (status === "authenticated") {
const { user } = session as unknown as Session;
// if (status === "unauthenticated") router.push("/login");
const getTextGender = (gender) => {
switch (gender) {
case Gender.MALE:
return "Homme";
case Gender.FEMALE:
return "Femme";
case Gender.OTHER:
return "Autre";
case Gender.UNKNOWN:
return "Non renseigné";
// if (status === "authenticated") {
const {
isLoading,
isError,
data: userData,
error,
} = useQuery({
queryKey: ["user"],
enabled: status === "authenticated",
queryFn: async () => {
const { user } = session as unknown as Session;
return fetch(`/api/users/${user.id}`)
.then((res) => res.json())
.catch((err) => {
return err;
});
},
});
if (isLoading) {
return <LoadingPage />;
}
if (isError) {
return <span>Error: {error.message}</span>;
}
const getTextGender = (gender) => {
switch (gender) {
case Gender.MALE:
return "Homme";
case Gender.FEMALE:
return "Femme";
case Gender.OTHER:
return "Autre";
case Gender.UNKNOWN:
return "Non renseigné";
}
};
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),
};
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 (Object.keys(trueValues).length > 0) {
setIsLoading(true);
fetch(`/api/users/${user.id}`, options)
.then((res) => {
setIsLoading(false);
toast({
position: "top",
title: `Modifications effectuées`,
status: "success",
isClosable: true,
});
// router.reload();
})
.catch((err) => {
setIsLoading(false);
toast({
title: `Erreur lors de l'envoi des modifications`,
position: "top",
status: "error",
isClosable: true,
});
console.log(err);
if (Object.keys(trueValues).length > 0) {
setCurrentlyLoading(true);
fetch(`/api/users/${userData.id}`, options)
.then((res) => {
setCurrentlyLoading(false);
toast({
position: "top",
title: `Modifications effectuées`,
status: "success",
isClosable: true,
});
}
};
// router.reload();
})
.catch((err) => {
setCurrentlyLoading(false);
toast({
title: `Erreur lors de l'envoi des modifications`,
position: "top",
status: "error",
isClosable: true,
});
console.log(err);
});
}
};
const formateDate = (dateString: string) => {
var options = { year: "numeric", month: "long", day: "numeric" };
return new Date(dateString).toLocaleDateString([], options);
};
const formateDate = (dateString: string) => {
var options = { year: "numeric", month: "long", day: "numeric" };
return new Date(dateString).toLocaleDateString([], options);
};
return (
<Box bgColor={"purple.50"}>
<Container justifyContent={"center"} maxW={"70rem"} mt={"1rem"}>
<Flex flexDirection={"column"} alignItems={"center"} gap={"1rem"}>
<Box width={"50%"}>
{userData.images ? (
<Carousel
images={userData.images}
borderRadius={"1rem"}
></Carousel>
) : (
<Carousel images={user.images} borderRadius={"1rem"}></Carousel>
)}
</Box>
{/* {modal} */}
{!userData.images ? (
<ModalModifyImages
setUserData={setUserData}
userData={userData}
user={user}
images={user.images}
/>
) : (
<ModalModifyImages
setUserData={setUserData}
userData={userData}
user={user}
return (
<Box bgColor={"purple.50"}>
<Container justifyContent={"center"} maxW={"70rem"} mt={"1rem"}>
<Flex flexDirection={"column"} alignItems={"center"} gap={"1rem"}>
<Box width={"50%"}>
{userData.images ? (
<Carousel
images={userData.images}
/>
borderRadius={"1rem"}
></Carousel>
) : (
<Carousel
images={userData.images}
borderRadius={"1rem"}
></Carousel>
)}
</Box>
{/* {modal} */}
<Divider colorScheme={"purple"} />
<Text align={"center"} as="i" color={"grey"}>
Modifiez les champs en les selectionnants
</Text>
{!userData.images ? (
<ModalModifyImages
userData={userData}
user={userData}
images={userData.images}
/>
) : (
<ModalModifyImages
userData={userData}
user={userData}
images={userData.images}
/>
)}
<Box as="form" onSubmit={handleSubmit(saveData)} width={"80%"}>
<Flex width={"100%"} justify={"space-between"} mb={"1rem"}>
<Box>
<FormControl id="firstName" isInvalid={errors.firstName}>
<FormLabel as="legend" htmlFor={"firstName"}>
Prénom :
</FormLabel>
<Controller
name={"firstName"}
control={control}
defaultValue={
user.firstName === null ? "" : user.firstName
}
rules={{
required: { value: true, message: "Prénom requis" },
}}
render={({ field }) => {
return (
<Editable
{...field}
id={"firstName"}
as={"b"}
placeholder={"Non renseigné"}
>
<EditablePreview />
<EditableInput />
</Editable>
);
}}
/>
<FormErrorMessage as="b">
{errors.firstName?.message}
</FormErrorMessage>
</FormControl>
</Box>
<Box>
<FormControl id="lastName" isInvalid={errors.lastName}>
<FormLabel as={"legend"} htmlFor={"lastName"}>
Nom :
</FormLabel>
<Controller
name={"lastName"}
control={control}
defaultValue={user.lastName === null ? "" : user.lastName}
rules={{
required: { value: true, message: "Nom requis" },
}}
render={({ field }) => (
<Divider colorScheme={"purple"} />
<Text align={"center"} as="i" color={"grey"}>
Modifiez les champs en les selectionnants
</Text>
<Box as="form" onSubmit={handleSubmit(saveData)} width={"80%"}>
<Flex width={"100%"} justify={"space-between"} mb={"1rem"}>
<Box>
<FormControl id="firstName" isInvalid={errors.firstName}>
<FormLabel as="legend" htmlFor={"firstName"}>
Prénom :
</FormLabel>
<Controller
name={"firstName"}
control={control}
defaultValue={
userData.firstName === null ? "" : userData.firstName
}
rules={{
required: { value: true, message: "Prénom requis" },
}}
render={({ field }) => {
return (
<Editable
{...field}
id={"lastName"}
id={"firstName"}
as={"b"}
placeholder={"Non renseigné"}
>
<EditablePreview />
<EditableInput />
</Editable>
)}
/>
<FormErrorMessage as={"b"}>
{errors.lastName?.message}
</FormErrorMessage>
</FormControl>
</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"}>
<FormControl id="lastName" isInvalid={errors.bio}>
<FormLabel as={"legend"} htmlFor={"bio"}>
À propos :
);
}}
/>
<FormErrorMessage as="b">
{errors.firstName?.message}
</FormErrorMessage>
</FormControl>
</Box>
<Box>
<FormControl id="lastName" isInvalid={errors.lastName}>
<FormLabel as={"legend"} htmlFor={"lastName"}>
Nom :
</FormLabel>
<Controller
name={"bio"}
name={"lastName"}
control={control}
defaultValue={user.bio === null ? "" : user.bio}
defaultValue={
userData.lastName === null ? "" : userData.lastName
}
rules={{
maxLength: {
value: 240,
message: "240 caractères maximum",
},
required: { value: true, message: "Nom requis" },
}}
render={({ field }) => (
<Editable
{...field}
id={"bio"}
as="b"
width={"100%"}
id={"lastName"}
as={"b"}
placeholder={"Non renseigné"}
defaultValue={user.bio === null ? "" : user.bio}
onSubmit={(value) => {
setUserData({ ...userData, bio: value });
}}
>
<EditablePreview />
<EditableTextarea />
<EditableInput />
</Editable>
)}
/>
<FormErrorMessage as="b">
{errors.bio?.message}
<FormErrorMessage as={"b"}>
{errors.lastName?.message}
</FormErrorMessage>
</FormControl>
</Box>
<Divider colorScheme={"purple"} />
<Box my={"1rem"}>
<Box>
<FormLabel as={"legend"} htmlFor={"passion"}>
Centre d'intéret :
</FormLabel>
<VStack gap={"1rem"} align="start">
<ProfileTagList
userPassions={
user.PassionID !== undefined ? user.PassionID : []
}
passions={passions}
/>
<ModalChoosePassion user={user} passions={passions} />
</VStack>
</Box>
<Box>
<FormLabel as={"legend"} htmlFor={"birthdate"}>
Date de naissance :
</FormLabel>
<Editable
id={"birthdate"}
as="b"
color={"grey"}
isDisabled={true}
defaultValue={
userData.birthdate === undefined
? "Non renseigné"
: formateDate(userData.birthdate.toString())
}
>
<EditablePreview />
</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
}
>
<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>
)}
</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={
userData.location === null || userData.location === ""
? "Rendez vous sur la carte"
: userData.location
}
>
<EditablePreview />
</Editable>
</Box>
<Box>
<FormLabel as={"legend"} htmlFor={"email"}>
Adresse mail :
</FormLabel>
<Editable
id={"email"}
as="b"
color={"grey"}
isDisabled={true}
defaultValue={userData.email}
>
<EditablePreview />
</Editable>
</Box>
</Flex>
<Divider colorScheme={"purple"} />
<Box my={"1rem"}>
<FormControl id="lastName" isInvalid={errors.bio}>
<FormLabel as={"legend"} htmlFor={"bio"}>
À propos :
</FormLabel>
<Controller
name={"bio"}
control={control}
defaultValue={userData.bio === null ? "" : userData.bio}
rules={{
maxLength: {
value: 240,
message: "240 caractères maximum",
},
}}
render={({ field }) => (
<Editable
{...field}
id={"bio"}
as="b"
width={"100%"}
placeholder={"Non renseigné"}
defaultValue={userData.bio === null ? "" : userData.bio}
>
<EditablePreview />
<EditableTextarea />
</Editable>
)}
/>
<FormErrorMessage as="b">
{errors?.bio?.message ?? ""}
</FormErrorMessage>
</FormControl>
</Box>
<Divider colorScheme={"purple"} />
<Box my={"1rem"}>
<Box>
<FormLabel as={"legend"} htmlFor={"passion"}>
Centre d'intéret :
</FormLabel>
<VStack gap={"1rem"} align="start">
<ProfileTagList
userPassions={
userData.PassionID !== undefined ? userData.PassionID : []
}
passions={passions}
/>
</Box>
{/* <Box>
<ModalChoosePassion user={userData} passions={passions} />
</VStack>
</Box>
</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={
userData.gender === null
? Gender.UNKNOWN
: userData.gender
}
>
<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>
@@ -379,13 +407,10 @@ export default function UserProfile() {
id={"preference"}
as="b"
value={
user.preference === null
userData.preference === null
? Gender.UNKNOWN
: user.preference
: userData.preference
}
onChange={(value) => {
setUserData({ ...userData, preference: value });
}}
>
<HStack spacing={"0.5rem"}>
<Radio value={Gender.MALE}>
@@ -403,28 +428,28 @@ export default function UserProfile() {
</HStack>
</RadioGroup>
</Flex> */}
</Box>
<Divider colorScheme={"purple"} />
<Center gap={"1rem"} my={"1rem"}>
<Button
colorScheme={"purple"}
isLoading={isLoading}
type="submit"
>
Sauvegarder les modifications
</Button>
<Button
colorScheme={"purple"}
variant="outline"
onClick={() => router.push("/dashboard")}
>
Retour
</Button>
</Center>
</Box>
</Flex>
</Container>
</Box>
);
}
<Divider colorScheme={"purple"} />
<Center gap={"1rem"} my={"1rem"}>
<Button
colorScheme={"purple"}
isLoading={currentlyLoading}
type="submit"
>
Sauvegarder les modifications
</Button>
<Button
colorScheme={"purple"}
variant="outline"
onClick={() => router.push("/dashboard")}
>
Retour
</Button>
</Center>
</Box>
</Flex>
</Container>
</Box>
);
// }
}