Merge branch 'dev' of github.com:LucasVbr/projet-web4 into dev

This commit is contained in:
Laurian-Dufrechou
2023-05-08 17:49:26 +02:00
50 changed files with 1325 additions and 1342 deletions
+1 -1
View File
@@ -43,4 +43,4 @@ next-env.d.ts
.env .env
# vscode files # vscode files
.vscode/ .vscode/**/*
-3
View File
@@ -1,3 +0,0 @@
{
"typescript.tsdk": "node_modules\\typescript\\lib"
}
+90 -10
View File
@@ -8,13 +8,14 @@
"name": "projet-web4", "name": "projet-web4",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@chakra-ui/icons": "^2.0.19",
"@chakra-ui/react": "^2.5.1", "@chakra-ui/react": "^2.5.1",
"@christopherpickering/react-leaflet-markercluster": "^1.1.0", "@christopherpickering/react-leaflet-markercluster": "^1.1.0",
"@emotion/react": "^11.10.6", "@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6", "@emotion/styled": "^11.10.6",
"@premieroctet/next-crud": "^2.2.0", "@premieroctet/next-crud": "^2.2.0",
"@prisma/client": "^4.11.0", "@prisma/client": "^4.11.0",
"@reduxjs/toolkit": "^1.9.3", "@reduxjs/toolkit": "^1.9.5",
"@tanstack/react-query": "^4.28.0", "@tanstack/react-query": "^4.28.0",
"@types/react": "18.0.28", "@types/react": "18.0.28",
"@types/react-dom": "18.0.11", "@types/react-dom": "18.0.11",
@@ -27,6 +28,7 @@
"fs": "^0.0.1-security", "fs": "^0.0.1-security",
"leaflet": "^1.9.3", "leaflet": "^1.9.3",
"leaflet.markercluster": "^1.5.3", "leaflet.markercluster": "^1.5.3",
"moment": "^2.29.4",
"next": "13.2.3", "next": "13.2.3",
"next-auth": "^4.20.1", "next-auth": "^4.20.1",
"react": "18.2.0", "react": "18.2.0",
@@ -35,6 +37,7 @@
"react-icons": "^4.8.0", "react-icons": "^4.8.0",
"react-leaflet": "^4.2.1", "react-leaflet": "^4.2.1",
"react-query": "^3.39.3", "react-query": "^3.39.3",
"react-redux": "^8.0.5",
"socket.io": "^4.6.1", "socket.io": "^4.6.1",
"socket.io-client": "^4.6.1", "socket.io-client": "^4.6.1",
"yup": "^1.1.1" "yup": "^1.1.1"
@@ -474,6 +477,18 @@
"react": ">=18" "react": ">=18"
} }
}, },
"node_modules/@chakra-ui/icons": {
"version": "2.0.19",
"resolved": "https://registry.npmjs.org/@chakra-ui/icons/-/icons-2.0.19.tgz",
"integrity": "sha512-0A6U1ZBZhLIxh3QgdjuvIEhAZi3B9v8g6Qvlfa3mu6vSnXQn2CHBZXmJwxpXxO40NK/2gj/gKXrLeUaFR6H/Qw==",
"dependencies": {
"@chakra-ui/icon": "3.0.16"
},
"peerDependencies": {
"@chakra-ui/system": ">=2.0.0",
"react": ">=18"
}
},
"node_modules/@chakra-ui/image": { "node_modules/@chakra-ui/image": {
"version": "2.0.15", "version": "2.0.15",
"resolved": "https://registry.npmjs.org/@chakra-ui/image/-/image-2.0.15.tgz", "resolved": "https://registry.npmjs.org/@chakra-ui/image/-/image-2.0.15.tgz",
@@ -2178,14 +2193,14 @@
} }
}, },
"node_modules/@reduxjs/toolkit": { "node_modules/@reduxjs/toolkit": {
"version": "1.9.3", "version": "1.9.5",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.3.tgz", "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.5.tgz",
"integrity": "sha512-GU2TNBQVofL09VGmuSioNPQIu6Ml0YLf4EJhgj0AvBadRlCGzUWet8372LjvO4fqKZF2vH1xU0htAa7BrK9pZg==", "integrity": "sha512-Rt97jHmfTeaxL4swLRNPD/zV4OxTes4la07Xc4hetpUW/vc75t5m1ANyxG6ymnEQ2FsLQsoMlYB2vV1sO3m8tQ==",
"dependencies": { "dependencies": {
"immer": "^9.0.16", "immer": "^9.0.21",
"redux": "^4.2.0", "redux": "^4.2.1",
"redux-thunk": "^2.4.2", "redux-thunk": "^2.4.2",
"reselect": "^4.1.7" "reselect": "^4.1.8"
}, },
"peerDependencies": { "peerDependencies": {
"react": "^16.9.0 || ^17.0.0 || ^18", "react": "^16.9.0 || ^17.0.0 || ^18",
@@ -2326,6 +2341,15 @@
"@types/ms": "*" "@types/ms": "*"
} }
}, },
"node_modules/@types/hoist-non-react-statics": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
"dependencies": {
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0"
}
},
"node_modules/@types/json5": { "node_modules/@types/json5": {
"version": "0.0.29", "version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
@@ -2400,6 +2424,11 @@
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
"integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ=="
}, },
"node_modules/@types/use-sync-external-store": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
"integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
},
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "5.57.1", "version": "5.57.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.57.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.57.1.tgz",
@@ -5976,6 +6005,14 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/moment": {
"version": "2.29.4",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
"engines": {
"node": "*"
}
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -7062,6 +7099,49 @@
} }
} }
}, },
"node_modules/react-redux": {
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz",
"integrity": "sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==",
"dependencies": {
"@babel/runtime": "^7.12.1",
"@types/hoist-non-react-statics": "^3.3.1",
"@types/use-sync-external-store": "^0.0.3",
"hoist-non-react-statics": "^3.3.2",
"react-is": "^18.0.0",
"use-sync-external-store": "^1.0.0"
},
"peerDependencies": {
"@types/react": "^16.8 || ^17.0 || ^18.0",
"@types/react-dom": "^16.8 || ^17.0 || ^18.0",
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0",
"react-native": ">=0.59",
"redux": "^4"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
},
"react-dom": {
"optional": true
},
"react-native": {
"optional": true
},
"redux": {
"optional": true
}
}
},
"node_modules/react-redux/node_modules/react-is": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
},
"node_modules/react-remove-scroll": { "node_modules/react-remove-scroll": {
"version": "2.5.5", "version": "2.5.5",
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz",
@@ -7340,9 +7420,9 @@
} }
}, },
"node_modules/reselect": { "node_modules/reselect": {
"version": "4.1.7", "version": "4.1.8",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz", "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz",
"integrity": "sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A==" "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="
}, },
"node_modules/resolve": { "node_modules/resolve": {
"version": "1.22.2", "version": "1.22.2",
+4 -1
View File
@@ -9,13 +9,14 @@
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {
"@chakra-ui/icons": "^2.0.19",
"@chakra-ui/react": "^2.5.1", "@chakra-ui/react": "^2.5.1",
"@christopherpickering/react-leaflet-markercluster": "^1.1.0", "@christopherpickering/react-leaflet-markercluster": "^1.1.0",
"@emotion/react": "^11.10.6", "@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6", "@emotion/styled": "^11.10.6",
"@premieroctet/next-crud": "^2.2.0", "@premieroctet/next-crud": "^2.2.0",
"@prisma/client": "^4.11.0", "@prisma/client": "^4.11.0",
"@reduxjs/toolkit": "^1.9.3", "@reduxjs/toolkit": "^1.9.5",
"@tanstack/react-query": "^4.28.0", "@tanstack/react-query": "^4.28.0",
"@types/react": "18.0.28", "@types/react": "18.0.28",
"@types/react-dom": "18.0.11", "@types/react-dom": "18.0.11",
@@ -28,6 +29,7 @@
"fs": "^0.0.1-security", "fs": "^0.0.1-security",
"leaflet": "^1.9.3", "leaflet": "^1.9.3",
"leaflet.markercluster": "^1.5.3", "leaflet.markercluster": "^1.5.3",
"moment": "^2.29.4",
"next": "13.2.3", "next": "13.2.3",
"next-auth": "^4.20.1", "next-auth": "^4.20.1",
"react": "18.2.0", "react": "18.2.0",
@@ -36,6 +38,7 @@
"react-icons": "^4.8.0", "react-icons": "^4.8.0",
"react-leaflet": "^4.2.1", "react-leaflet": "^4.2.1",
"react-query": "^3.39.3", "react-query": "^3.39.3",
"react-redux": "^8.0.5",
"socket.io": "^4.6.1", "socket.io": "^4.6.1",
"socket.io-client": "^4.6.1", "socket.io-client": "^4.6.1",
"yup": "^1.1.1" "yup": "^1.1.1"
+1 -2
View File
@@ -18,7 +18,7 @@ model User {
bio String? bio String?
location String? location String?
images String[] images String[]
birthdate DateTime? birthdate DateTime? @db.Date
gender Gender @default(UNKNOWN) gender Gender @default(UNKNOWN)
role Role @default(USER) role Role @default(USER)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
@@ -30,7 +30,6 @@ model User {
ageMin Int @default(18) ageMin Int @default(18)
prefGender Gender @default(UNKNOWN) prefGender Gender @default(UNKNOWN)
// Liste des chats de l'utilisateur // Liste des chats de l'utilisateur
ChatID String[] @db.ObjectId ChatID String[] @db.ObjectId
Chat Chat[] @relation(fields: [ChatID], references: [id]) Chat Chat[] @relation(fields: [ChatID], references: [id])
+3 -15
View File
@@ -1,27 +1,15 @@
import { Box, Flex, Text, Button, Image } from "@chakra-ui/react"; import { Box, Flex, Text, Button, Image } from "@chakra-ui/react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
export default function BottomBar(props) { export default function BottomBar(props: { variant: any; saveData: any; }) {
const router = useRouter(); const router = useRouter();
const { variant, saveData } = props; const { variant, saveData } = props;
return ( return (
<Box <Box position={variant} zIndex={0} bottom={0} backdropFilter={"auto"} px={10} py={2}>
position={variant}
zIndex={0}
bottom={0}
backdropFilter={"auto"}
px={10}
py={2}
>
<Flex align={"center"}> <Flex align={"center"}>
<Button <Button onClick={() => saveData()}>
colorScheme={"purple"}
onClick={() => {
saveData();
}}
>
Sauvegarder les modifications Sauvegarder les modifications
</Button> </Button>
</Flex> </Flex>
+14 -33
View File
@@ -1,13 +1,13 @@
import { useState } from "react"; import {useState} from 'react';
import { Flex, IconButton } from "@chakra-ui/react"; import {Flex, IconButton} from '@chakra-ui/react';
import { BiLeftArrowAlt, BiRightArrowAlt } from "react-icons/bi"; import {BiLeftArrowAlt, BiRightArrowAlt} from 'react-icons/bi';
interface Props { interface Props {
images: string[]; images: string[];
borderRadius?: string | number; borderRadius?: string | number;
} }
const Carousel = ({ images, borderRadius: bRadius }: Props) => { const Carousel = ({images, borderRadius: bRadius}: Props) => {
const [currentIndex, setCurrentIndex] = useState(0); const [currentIndex, setCurrentIndex] = useState(0);
const handleClickPrevious = () => { const handleClickPrevious = () => {
@@ -18,39 +18,20 @@ const Carousel = ({ images, borderRadius: bRadius }: Props) => {
setCurrentIndex(currentIndex === images.length - 1 ? 0 : currentIndex + 1); setCurrentIndex(currentIndex === images.length - 1 ? 0 : currentIndex + 1);
}; };
if (images.length === 0) images = ["blank_profile_picture.webp"]; if (images.length === 0) images = ['blank_profile_picture.webp'];
return ( return (
<Flex <Flex px={2} align="center" borderRadius={bRadius} overflow={'hidden'}
px={2} justify={'space-between'} bgColor={'purple.50'} height={500}
align="center" bgImage={images[currentIndex]} bgSize={'contain'}
borderRadius={bRadius} bgRepeat={'no-repeat'} bgPosition={'center'} width={'100%'}>
overflow={"hidden"} <IconButton aria-label="left-arrow" borderRadius="full"
justify={"space-between"} onClick={handleClickPrevious}>
bgColor={"purple.50"} <BiLeftArrowAlt/>
bgImage={images[currentIndex]}
bgSize={"contain"}
bgRepeat={"no-repeat"}
bgPosition={"center"}
width={"100%"}
height={500}
>
<IconButton
aria-label="left-arrow"
colorScheme="purple"
borderRadius="full"
onClick={handleClickPrevious}
>
<BiLeftArrowAlt />
</IconButton> </IconButton>
<IconButton <IconButton aria-label="left-arrow" borderRadius="full" onClick={handleClickNext}>
aria-label="left-arrow" <BiRightArrowAlt/>
colorScheme="purple"
borderRadius="full"
onClick={handleClickNext}
>
<BiRightArrowAlt />
</IconButton> </IconButton>
</Flex> </Flex>
); );
+3 -10
View File
@@ -6,20 +6,13 @@ export default function LoadingPage() {
const router = useRouter(); const router = useRouter();
return ( return (
<VStack <VStack justifyContent="center" alignItems="center" height="100vh"
justifyContent="center" width="100vw" bg="gray.100">
alignItems="center"
height="100vh"
width="100vw"
bg="gray.100"
>
<MdError color="purple.500" size="50%" /> <MdError color="purple.500" size="50%" />
<Text color="purple.500" fontSize="2xl" fontWeight="bold"> <Text color="purple.500" fontSize="2xl" fontWeight="bold">
Veillez à autoriser la géolocalisation Veillez à autoriser la géolocalisation
</Text> </Text>
<Button colorScheme={"purple"} onClick={() => router.push("/")}> <Button onClick={() => router.push("/")}>Page d'accueil</Button>
Page d'accueuil
</Button>
</VStack> </VStack>
); );
} }
+3 -10
View File
@@ -6,20 +6,13 @@ export default function LoadingPage() {
const router = useRouter(); const router = useRouter();
return ( return (
<VStack <VStack justifyContent="center" alignItems="center" height="100vh"
justifyContent="center" width="100vw" bg="gray.100">
alignItems="center"
height="100vh"
width="100vw"
bg="gray.100"
>
<MdError color="purple.500" size="50%" /> <MdError color="purple.500" size="50%" />
<Text color="purple.500" fontSize="2xl" fontWeight="bold"> <Text color="purple.500" fontSize="2xl" fontWeight="bold">
Une erreur est survenue Une erreur est survenue
</Text> </Text>
<Button colorScheme={"purple"} onClick={() => router.push("/")}> <Button onClick={() => router.push("/")}>Page d'accueil</Button>
Page d'accueuil
</Button>
</VStack> </VStack>
); );
} }
+3 -14
View File
@@ -2,20 +2,9 @@ import { Flex, Spinner } from "@chakra-ui/react";
export default function LoadingPage() { export default function LoadingPage() {
return ( return (
<Flex <Flex justifyContent="center" alignItems="center" height="100vh"
justifyContent="center" width="100vw" bg="gray.100">
alignItems="center" <Spinner thickness="4px" speed="0.65s" emptyColor="gray.200" size="xl"/>
height="100vh"
width="100vw"
bg="gray.100"
>
<Spinner
thickness="4px"
speed="0.65s"
emptyColor="gray.200"
color="purple.500"
size="xl"
/>
</Flex> </Flex>
); );
} }
+47 -61
View File
@@ -6,86 +6,72 @@ import {
Button, Button,
Image, Image,
Link, Link,
} from "@chakra-ui/react"; } from '@chakra-ui/react';
import { useRouter } from "next/router"; import {useRouter} from 'next/router';
import { signOut, useSession } from "next-auth/react"; import {signOut, useSession} from 'next-auth/react';
type Props = { type Props = {
variant: "static" | "fixed"; variant: 'static' | 'fixed';
}; };
export default function Navbar({ variant = "fixed" }: Props) { export default function Navbar({variant = 'fixed'}: Props) {
const router = useRouter(); const router = useRouter();
const { data: session, status } = useSession(); const {data: session, status} = useSession();
const MiddleSection = () => {
const authenticatedLinks = (
<>
<Link href={'/dashboard'}>Tableau de bord</Link>
<Link href={'/profile'}>Profile</Link>
<Link href={'/map'}>Carte</Link>
</>
);
const RightSection = () => {
if (status === "authenticated" && session.user)
return ( return (
<Flex justify={"right"} flexBasis={"100%"}> <Flex gap={5} justify={'center'} flexBasis={'100%'}>
<ButtonGroup> {status === 'authenticated'
<Button colorScheme={"purple"} onClick={() => signOut()}> ? authenticatedLinks
Déconnexion : <></>
</Button> }
</ButtonGroup>
</Flex> </Flex>
); );
else };
const RightSection = () => {
const authenticatedButtons = (
<Button onClick={() => signOut()}>Déconnexion</Button>
);
const unauthenticatedButtons = (
<>
<Button onClick={() => router.push('/register')}>Inscription</Button>
<Button onClick={() => router.push('/login')}>Connexion</Button>
</>
);
return ( return (
<Flex justify={"right"} flexBasis={"100%"}> <Flex justify={'right'} flexBasis={'100%'}>
<ButtonGroup> <ButtonGroup>
<Button onClick={() => router.push("/register")}> {status === 'authenticated'
Inscription ? authenticatedButtons
</Button> : unauthenticatedButtons
<Button }
colorScheme={"purple"}
onClick={() => router.push("/login")}
>
Connexion
</Button>
</ButtonGroup> </ButtonGroup>
</Flex> </Flex>
); );
}; };
return ( return (
<Box <Box position={variant} zIndex={9999} top={0} width={'100vw'}
position={variant} backdropFilter={'auto'} backdropBlur={'20px'} px={10} py={2}>
zIndex={9999} <Flex align={'center'}>
top={0} <Box flexBasis={'100%'}>
width={"100vw"} <Image src={'/logo.svg'} h={'3rem'} objectFit={'contain'}/>
backdropFilter={"auto"}
backdropBlur={"20px"}
px={10}
py={2}
>
<Flex align={"center"}>
<Box flexBasis={"100%"}>
<Image src={"/logo.svg"} h={"3rem"} objectFit={"contain"} />
</Box> </Box>
<Flex gap={5} justify={"center"} flexBasis={"100%"}> <MiddleSection/>
{status === "authenticated" ? ( <RightSection/>
<>
<Link href={"/dashboard"} color={"purple.500"}>
Tableau de bord
</Link>
<Link href={"/profile"} color={"purple.500"}>
Profile
</Link>
<Link href={"/map"} color={"purple.500"}>
Carte
</Link>
</>
) : (
<>
<Text>A propos</Text>
<Text>Contact</Text>
<Text>Aide</Text>
</>
)}
</Flex>
<RightSection />
</Flex> </Flex>
</Box> </Box>
); );
+1 -1
View File
@@ -8,7 +8,7 @@ type Props = {
const Message = ({message, align}: Props) => ( const Message = ({message, align}: Props) => (
<Flex justifyContent={align}> <Flex justifyContent={align}>
<Text w={'fit-content'} bg={'purple.500'} borderRadius={'full'} <Text maxW={"70%"} w={'fit-content'} bg={'purple.500'} borderRadius={10}
color={'white'} px={'10px'} py={'5px'}> color={'white'} px={'10px'} py={'5px'}>
{message.text} {message.text}
</Text> </Text>
+8 -7
View File
@@ -1,20 +1,21 @@
import type {Message as MessageType, User} from '@prisma/client'; import type {Message as MessageType, User} from '@prisma/client';
import Message from '@/components/chat/Message'; import Message from '@/components/chat/Message';
import {Box, Flex} from '@chakra-ui/react'; import {Flex} from '@chakra-ui/react';
type Props = { type Props = {
user: User user: User,
messages: MessageType[] messages: MessageType[]
} }
const MessageList = ({messages, user}: Props) => ( const MessageList = ({messages, user}: Props) => {
return (
<Flex direction={'column'} gap={1}> <Flex direction={'column'} gap={1}>
{messages.map((message, index) => {messages.map((message, index) => <Message
<Message key={index} align={user.id === message.UserID ? 'right' : 'left'} key={index}
align={user.id === message.UserID ? 'right' : 'left'}
message={message}/> message={message}/>
)} )}
</Flex> </Flex>
); );
};
export default MessageList; export default MessageList;
+2 -2
View File
@@ -32,8 +32,8 @@ export default function FormMessage(props: Props) {
return ( return (
<Flex gap={5} mt={5}> <Flex gap={5} mt={5}>
<Input type={text} colorScheme={'purple'} onChange={(evt) => setText(evt.target.value) } value={text} /> <Input type={text} onChange={evt => setText(evt.target.value) } value={text}/>
<Button colorScheme={'purple'} onClick={handleSubmit} >Envoyer</Button> <Button onClick={handleSubmit}>Envoyer</Button>
</Flex> </Flex>
) )
} }
-119
View File
@@ -1,119 +0,0 @@
import {useEffect, useState} from 'react';
import {
Gender,
Message as MessageType, Passion,
User as UserType,
} from '@prisma/client';
import MessageList from '@/components/chat/MessageList';
import {Button, ButtonGroup, Input, Slider} from '@chakra-ui/react';
type Props = {
user: UserType
}
type FormInputs = {
userGender: Gender
genderResearch: Gender,
ages: [number, number],
birth: Date,
passions: Passion[]
}
export default function GettingStartForm({user}: Props) {
const steps = [
{
question: 'Salut, je suis Cupibot. Il me manque quelques détails avant de terminer ton inscription. C\'est quoi ton sexe ?',
component: ( // TODO Optimiser ça
<ButtonGroup>
<Button onClick={() => {
setFormInputs({...formInputs, userGender: Gender.MALE});
nextStep();
}}>Homme</Button>
<Button onClick={() => {
setFormInputs({...formInputs, userGender: Gender.FEMALE});
nextStep();
}}>Femme</Button>
<Button onClick={() => {
setFormInputs({...formInputs, userGender: Gender.OTHER});
nextStep();
}}>Autre</Button>
</ButtonGroup>
),
},
{
question: 'Ok et tu es attiré par quel genre ?',
component: ( // TODO Optimiser ça
<ButtonGroup>
<Button onClick={() => {
setFormInputs({...formInputs, genderResearch: Gender.MALE});
nextStep();
}}>Homme</Button>
<Button onClick={() => {
setFormInputs({...formInputs, genderResearch: Gender.FEMALE});
nextStep();
}}>Femme</Button>
<Button onClick={() => {
setFormInputs({...formInputs, genderResearch: Gender.OTHER});
nextStep();
}}>Autre</Button>
</ButtonGroup>),
},
{
question: 'Ça marche, c\'est quoi la tranche d\'âge que tu recherche ?',
component: (
<>
<Slider/>
<Button onClick={() => nextStep()}>Suite</Button>
</>
), // TODO
},
{
question: 'D\'ailleurs je ne t\'ai pas demandé, quand est-ce que tu est né ?',
component: (
<>
<Input type={'date'}/>
<Button onClick={() => nextStep()}>Suite</Button>
</>
), // TODO
},
{
question: 'T\'as des passions dans la vie ? (Choisis-en 3 minimum)',
component: (
<>
Passion selector
<Button onClick={() => nextStep()}>Suite</Button>
</>), // TODO
},
{
question: 'Ok j\'ai toutes les infos d\'on j\'ai besoin, je vais tout faire pour te faire trouver la personne qui te correspond!',
component: (<Button onClick={() => {/*handleSubmit()*/}}>Suite</Button>), // TODO
},
];
const [progress, setProgress] = useState(0);
const [messages, setMessages] = useState<MessageType[]>([
{text: steps[0].question} as MessageType,
]);
const [formInputs, setFormInputs] = useState<FormInputs>({
userGender: Gender.UNKNOWN,
genderResearch: Gender.UNKNOWN,
ages: [18, 25],
birth: new Date(),
passions: [],
});
const nextStep = () => {
const newMessage = {text: steps[progress + 1].question} as MessageType;
setMessages([...messages, newMessage]);
setProgress(progress + 1);
};
return (
<>
<MessageList user={user} messages={messages}/>
{steps[progress].component}
</>
);
}
@@ -0,0 +1,38 @@
import {
Button,
RangeSlider,
RangeSliderFilledTrack, RangeSliderThumb,
RangeSliderTrack, Tooltip,
} from '@chakra-ui/react';
import {useDispatch} from 'react-redux';
import {useState} from 'react';
const AgeInput = () => {
const dispatch = useDispatch();
const [age, setAge] = useState([18, 25]);
return (
<>
<RangeSlider mb={5} min={18} max={99} step={2} value={age}
onChange={(newAge) => setAge(newAge)}>
<RangeSliderTrack><RangeSliderFilledTrack/></RangeSliderTrack>
{age.map((value, index) => (
<Tooltip key={index} isOpen hasArrow placement={'top'}
label={`${value}`}>
<RangeSliderThumb index={index}/>
</Tooltip>
))}
</RangeSlider>
<Button onClick={() => dispatch({
type: 'gettingStartForm/setInput',
payload: {
inputs: {'ages': age},
message: `Entre ${age[0]} et ${age[1]} ans`,
},
})}>Envoyer</Button>
</>
);
};
export default AgeInput;
@@ -0,0 +1,30 @@
import {Button, Flex, FormControl, Input} from '@chakra-ui/react';
import {useDispatch} from 'react-redux';
import {useState} from 'react';
import moment from 'moment';
const BirthInput = () => {
const dispatch = useDispatch();
const [birth, setBirth] = useState('');
return (
<Flex>
<FormControl>
<Input type={'date'} onChange={(evt) => setBirth(evt.target.value)}
value={birth}/>
</FormControl>
<FormControl>
<Button onClick={() => dispatch({
type: 'gettingStartForm/setInput',
payload: {
inputs: {'birth': moment(birth).toISOString()},
message: `Le ${moment(birth).format("ll")}.`, // TODO formate date
},
})}>Envoyer</Button>
</FormControl>
</Flex>
);
};
export default BirthInput;
@@ -0,0 +1,33 @@
import {Button, ButtonGroup} from '@chakra-ui/react';
import {Gender} from '@prisma/client';
import {useDispatch} from 'react-redux';
type Props = {
field: 'ownGender' | 'researchGender'
}
const GenderInput = ({field}: Props) => {
const inputs = [
{label: 'Homme', gender: Gender.MALE},
{label: 'Femme', gender: Gender.FEMALE},
{label: 'Autre', gender: Gender.OTHER},
];
const dispatch = useDispatch();
return (
<ButtonGroup>
{inputs.map(({label, gender}, index) =>
<Button key={index} onClick={() =>
dispatch({
type: 'gettingStartForm/setInput',
payload: {inputs: {[field]: gender}, message: label},
})
}>{label}</Button>,
)}
</ButtonGroup>
);
};
export default GenderInput;
@@ -0,0 +1,60 @@
import {Gender, Passion, User as UserType} from '@prisma/client';
import {Box, Container, Flex, Spacer} from '@chakra-ui/react';
import {useSelector} from 'react-redux';
import {RootState} from '@/redux/redux';
import Message from '@/components/chat/Message';
import {Message as MessageType} from '@prisma/client';
import GenderInput from '@/components/form/GettingStartForm/GenderInput';
import AgeInput from '@/components/form/GettingStartForm/AgeInput';
import BirthInput from '@/components/form/GettingStartForm/BirthInput';
import PassionsInput from '@/components/form/GettingStartForm/PassionsInput';
import SubmitGettingStartForm
from '@/components/form/GettingStartForm/SubmitGettingStartForm';
type Props = {
user: UserType
}
type FormInputs = {
ownGender?: Gender
researchGender?: Gender,
ages?: [number, number],
birth?: Date,
passions?: Passion[]
}
export default function GettingStartForm({user}: Props) {
const gettingStartForm = useSelector(
(state: RootState) => state.gettingStartForm);
const stepComponents: JSX.Element[] = [
<GenderInput key={0} field={'ownGender'}/>,
<GenderInput key={1} field={'researchGender'}/>,
<AgeInput key={2}/>,
<BirthInput key={3}/>,
<PassionsInput key={4}/>,
<SubmitGettingStartForm user={user} key={5}/>,
];
return (
<Box bgColor={'gray.50'}>
<Container bgColor={'white'} h={'100vh'}>
<Flex alignItems={'center'} flexDirection={'column'} h={'100%'}
pt={5} pb={20}>
<Flex direction={'column'} gap={1}>
{gettingStartForm.messages.map((
{text, user}: { text: string, user: string },
index: number) => (
<Message align={user === 'me' ? 'right' : 'left'} key={index}
message={{text} as MessageType}/>
))}
</Flex>
<Spacer/>
{stepComponents[gettingStartForm.currentStep]}
</Flex>
</Container>
</Box>
);
}
@@ -0,0 +1,56 @@
import {
Box,
Button,
Tag,
TagLabel,
TagLeftIcon,
TagRightIcon,
} from '@chakra-ui/react';
import {useEffect, useState} from 'react';
import {useDispatch} from 'react-redux';
import {Passion} from '@prisma/client';
import {AddIcon, SmallCloseIcon} from '@chakra-ui/icons';
export default function PassionsInput() {
const dispatch = useDispatch();
const [passionsList, setPassionsList] = useState<Passion[]>([]);
const [passions, setPassions] = useState<Passion[]>([]);
useEffect(() => {
fetch('/api/passions')
.then(res => res.json())
.then(data => setPassionsList(data));
}, []);
// TODO Use modal
return (
<>
<Box mb={5}>
{passionsList.map((p, index) => (
<Tag cursor={'pointer'} m={0.5} key={index}
variant={(passions.includes(p)) ? 'solid' : 'outline'}
onClick={() =>
setPassions((passions.includes(p))
? passions.filter(elt => elt !== p)
: [...passions, p])
}>
<TagLabel>{p.name}</TagLabel>
<TagRightIcon boxSize="12px" as={passions.includes(p) ? SmallCloseIcon : AddIcon}/>
</Tag>
))}
</Box>
<Button isDisabled={passions.length < 3}
onClick={() => dispatch({
type: 'gettingStartForm/setInput',
payload: {
inputs: {passions},
message: `Liste de passions`,
},
})}>
Envoyer
</Button>
</>
);
};
@@ -0,0 +1,49 @@
import {Button} from '@chakra-ui/react';
import {useRouter} from 'next/router';
import {Passion, User} from '@prisma/client';
import {useSelector} from 'react-redux';
import {RootState} from '@/redux/redux';
export default function SubmitGettingStartForm({user}: { user: User }) {
const router = useRouter();
const gettingStartForm = useSelector(
(state: RootState) => state.gettingStartForm);
const handleSubmit = () => {
const {
ownGender,
researchGender,
ages,
birth,
passions,
} = gettingStartForm.inputs;
const options = {
method: 'PUT',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
gender: ownGender,
prefGender: researchGender,
ageMin: ages[0],
ageMax: ages[1],
birthdate: new Date(birth),
Passion: {
connect: passions.map((p: Passion) => {
return {id: p.id};
}),
},
}),
};
fetch(`/api/users/${user.id}`, options)
.then(() => router.push('/dashboard'))
.catch(err => console.error(err));
};
return (
<>
<Button onClick={handleSubmit}>Ça marche!</Button>
</>
);
}
+33 -41
View File
@@ -8,13 +8,13 @@ import {
Input, Input,
Container, Container,
FormErrorMessage, FormErrorMessage,
} from "@chakra-ui/react"; } from '@chakra-ui/react';
import { useState } from "react"; import {useState} from 'react';
import { useRouter } from "next/router"; import {useRouter} from 'next/router';
import { signIn, SignInResponse } from "next-auth/react"; import {signIn, SignInResponse} from 'next-auth/react';
import { LoginData } from "@/models/form/LoginData"; import {LoginData} from '@/models/form/LoginData';
import { useForm } from "react-hook-form"; import {useForm} from 'react-hook-form';
export default function LoginForm() { export default function LoginForm() {
const router = useRouter(); const router = useRouter();
@@ -24,87 +24,79 @@ export default function LoginForm() {
const { const {
handleSubmit, handleSubmit,
register, register,
formState: { errors, isSubmitting }, formState: {errors, isSubmitting},
} = useForm(); } = useForm();
const buttonWidth = { base: "100%", md: "unset" }; const buttonWidth = {base: '100%', md: 'unset'};
const errorPassword = "Mot de passe incorrect"; const errorPassword = 'Mot de passe incorrect';
const loginUser = async (value: LoginData) => { const loginUser = async (value: LoginData) => {
setIsLoading(true); setIsLoading(true);
signIn("credentials", { ...value, redirect: false }).then( signIn('credentials', {...value, redirect: false}).then(
(res: unknown) => { (res: unknown) => {
const { ok: connexionSuccess } = res as SignInResponse; const {ok: connexionSuccess} = res as SignInResponse;
setIsLoading(false); setIsLoading(false);
if (connexionSuccess) router.push("/dashboard"); if (connexionSuccess) router.push('/dashboard');
else setWrongPasswordError(true); else setWrongPasswordError(true);
} },
); );
}; };
return ( return (
<Box flexBasis={"100%"}> <Box flexBasis={'100%'}>
<Container> <Container>
<form id="login_form" onSubmit={handleSubmit(loginUser)}> <form id="login_form" onSubmit={handleSubmit(loginUser)}>
<Heading textAlign={"center"} size={"2xl"}> <Heading textAlign={'center'} size={'2xl'}>
Connexion Connexion
</Heading> </Heading>
{/*Email*/} {/*Email*/}
<FormControl mb={"1rem"} id={"email"} isInvalid={errors.email}> <FormControl mb={'1rem'} id={'email'}
isInvalid={errors.email as boolean | undefined}>
<FormLabel>Adresse email</FormLabel> <FormLabel>Adresse email</FormLabel>
<Input <Input
type="text" type="text"
{...register("email", { {...register('email', {
required: { value: true, message: "Adresse email requise" }, required: {value: true, message: 'Adresse email requise'},
pattern: { pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i, value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: "Adresse email invalide", message: 'Adresse email invalide',
}, },
})} })}
placeholder="adresse@email.com" placeholder="adresse@email.com"
/> />
<FormErrorMessage>{errors.email?.message}</FormErrorMessage> <FormErrorMessage>{errors.email?.message as string}</FormErrorMessage>
</FormControl> </FormControl>
{/*Mot de passe*/} {/*Mot de passe*/}
<FormControl <FormControl
mb={"1rem"} mb={'1rem'}
id="password" id="password"
isInvalid={errors.password || wrongPasswordError} isInvalid={errors.password as boolean | undefined ||
wrongPasswordError}
> >
<FormLabel>Mot de passe</FormLabel> <FormLabel>Mot de passe</FormLabel>
<Input <Input
type="password" type="password"
placeholder="Mot de passe" placeholder="Mot de passe"
{...register("password", { {...register('password', {
required: { value: true, message: "Mot de passe requis" }, required: {value: true, message: 'Mot de passe requis'},
})} })}
/> />
<FormErrorMessage>{errors.password?.message}</FormErrorMessage> <FormErrorMessage>{errors.password?.message as string}</FormErrorMessage>
<FormErrorMessage> <FormErrorMessage>{wrongPasswordError &&
{wrongPasswordError && errorPassword} errorPassword}</FormErrorMessage>
</FormErrorMessage>
</FormControl> </FormControl>
{/*Boutons*/} {/*Boutons*/}
<Flex <Flex justify={'center'} mt={'50px'} gap={5}
justify={"center"} justifyContent={'space-between'}>
mt={"50px"} <Button onClick={() => router.push('/')} w={buttonWidth}>
gap={5}
justifyContent={"space-between"}
>
<Button onClick={() => router.push("/")} w={buttonWidth}>
Retour Retour
</Button> </Button>
<Button <Button isLoading={isLoading} w={buttonWidth} type="submit">
isLoading={isLoading}
w={buttonWidth}
colorScheme="purple"
type="submit"
>
Connexion Connexion
</Button> </Button>
</Flex> </Flex>
+43 -68
View File
@@ -10,7 +10,6 @@ import {
FormErrorMessage, FormErrorMessage,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import {useRouter} from 'next/router'; import {useRouter} from 'next/router';
import {useState} from 'react';
import {signIn, SignInResponse} from 'next-auth/react'; import {signIn, SignInResponse} from 'next-auth/react';
import {RegisterData} from '@/models/form/RegisterData'; import {RegisterData} from '@/models/form/RegisterData';
import {SubmitHandler, useForm} from 'react-hook-form'; import {SubmitHandler, useForm} from 'react-hook-form';
@@ -48,137 +47,113 @@ export default function RegisterForm() {
}), }),
}; };
fetch('/api/users', options).then(() => { fetch('/api/users', options)
signIn('credentials', {email, password, redirect: false}).then( .then(() => signIn('credentials', {email, password, redirect: false}))
(res: unknown) => { .then((res: unknown) => {
const {ok: connexionSuccess} = res as SignInResponse; const {ok: connexionSuccess} = res as SignInResponse;
router.push(connexionSuccess ? '/getting-start' : '/');
// TODO If success -> goto interactive form else login
router.push(connexionSuccess ? '/dashboard' : '/');
}, },
); )
}).catch((err) => console.error(err)); .catch(err => console.error(err));
}; };
return ( return (
<Box flexBasis={'100%'}> <Box flexBasis={'100%'}>
<Container> <Container>
<form id="register_form" onSubmit={handleSubmit(submitHandler)}> <form id="register_form" onSubmit={handleSubmit(submitHandler)}>
<Heading textAlign={'center'} size={'2xl'}> <Heading textAlign={'center'} size={'2xl'}>Inscription</Heading>
Inscription
</Heading>
<Flex mt={'100px'} mb={'1rem'} gap={5}> <Flex mt={'100px'} mb={'1rem'} gap={5}>
{/*Prénom*/} {/*Prénom*/}
<FormControl id="firstName" isInvalid={errors.firstName as boolean|undefined}> <FormControl id="firstName"
isInvalid={errors.firstName as boolean | undefined}>
<FormLabel>Prénom</FormLabel> <FormLabel>Prénom</FormLabel>
<Input <Input type="text" placeholder="Prénom"
type="text"
placeholder="Prénom"
{...register('firstName', { {...register('firstName', {
required: {value: true, message: 'Prénom requis'}, required: {value: true, message: 'Prénom requis'},
})} })}/>
/>
<FormErrorMessage>{errors.firstName?.message as string}</FormErrorMessage> <FormErrorMessage>{errors.firstName?.message as string}</FormErrorMessage>
</FormControl> </FormControl>
{/*Nom*/} {/*Nom*/}
<FormControl id="lastName" isInvalid={errors.lastName as boolean|undefined}> <FormControl id="lastName"
isInvalid={errors.lastName as boolean | undefined}>
<FormLabel>Nom</FormLabel> <FormLabel>Nom</FormLabel>
<Input <Input type="text" placeholder="Nom"
type="text"
placeholder="Nom"
{...register('lastName', { {...register('lastName', {
required: {value: true, message: 'Nom requis'}, required: {value: true, message: 'Nom requis'},
})} })}/>
/>
<FormErrorMessage>{errors.lastName?.message as string}</FormErrorMessage> <FormErrorMessage>{errors.lastName?.message as string}</FormErrorMessage>
</FormControl> </FormControl>
</Flex> </Flex>
{/*Email*/} {/*Email*/}
<FormControl mb={'1rem'} id={'email'} isInvalid={errors.email as boolean|undefined}> <FormControl mb={'1rem'} id={'email'}
isInvalid={errors.email as boolean | undefined}>
<FormLabel>Adresse email</FormLabel> <FormLabel>Adresse email</FormLabel>
<Input <Input type="text" placeholder="adresse@email.com"
type="text"
{...register('email', { {...register('email', {
required: {value: true, message: 'Adresse email requise'}, required: {
value: true,
message: 'Adresse email requise',
},
pattern: { pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i, value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: 'Adresse email invalide', message: 'Adresse email invalide',
}, },
})} })}/>
placeholder="adresse@email.com"
/>
<FormErrorMessage>{errors.email?.message as string}</FormErrorMessage> <FormErrorMessage>{errors.email?.message as string}</FormErrorMessage>
</FormControl> </FormControl>
{/*Date de naissance*/} {/*Date de naissance*/}
<FormControl <FormControl mb={'1rem'} id={'birthdate'}
mb={'1rem'} isInvalid={errors.birthdate as boolean | undefined}>
id={'birthdate'}
isInvalid={errors.birthdate as boolean|undefined}
>
<FormLabel>Date de naissance</FormLabel> <FormLabel>Date de naissance</FormLabel>
<Input <Input type="date"
type="date"
{...register('birthdate', { {...register('birthdate', {
required: { required: {
value: true, value: true,
message: 'Date de naissance requise', message: 'Date de naissance requise',
}, },
validate: validateDate, validate: validateDate,
})} })}/>
/> {(errors.birthdate) &&
{ (errors.birthdate) ? <FormErrorMessage>{errors.birthdate.message as string}</FormErrorMessage> : "" } <FormErrorMessage>{errors.birthdate.message as string}</FormErrorMessage>}
</FormControl> </FormControl>
{/*Mot de passe*/} {/*Mot de passe*/}
<FormControl mb={'1rem'} id="password" isInvalid={errors.password as boolean|undefined}> <FormControl mb={'1rem'} id="password"
isInvalid={errors.password as boolean | undefined}>
<FormLabel>Mot de passe</FormLabel> <FormLabel>Mot de passe</FormLabel>
<Input <Input type="password" placeholder="Mot de passe"
type="password"
placeholder="Mot de passe"
{...register('password', { {...register('password', {
required: {value: true, message: 'Mot de passe requis'}, required: {value: true, message: 'Mot de passe requis'},
})} })}/>
/>
<FormErrorMessage>{errors.password?.message as string}</FormErrorMessage> <FormErrorMessage>{errors.password?.message as string}</FormErrorMessage>
</FormControl> </FormControl>
{/*Mot de passe (2)*/} {/*Mot de passe (2)*/}
<FormControl <FormControl mb={'1rem'} id="confirmPassword"
mb={'1rem'} isInvalid={errors.confirmPassword as boolean | undefined}>
id="confirmPassword"
isInvalid={errors.confirmPassword as boolean|undefined}
>
<FormLabel>Confirmation du mot de passe</FormLabel> <FormLabel>Confirmation du mot de passe</FormLabel>
<Input <Input type="password" placeholder="Mot de passe"
type="password"
placeholder="Mot de passe"
{...register('confirmPassword', { {...register('confirmPassword', {
required: {value: true, message: 'Confirmation requise'}, required: {value: true, message: 'Confirmation requise'},
validate: (value: string) => { validate: (value: string) => (
return (
watch('password') === value || watch('password') === value ||
'Les mots de passe ne correspondent pas' 'Les mots de passe ne correspondent pas'
); ),
}, })}/>
})}
/>
<FormErrorMessage>{errors.confirmPassword?.message as string}</FormErrorMessage> <FormErrorMessage>{errors.confirmPassword?.message as string}</FormErrorMessage>
</FormControl> </FormControl>
<Flex mt={'50px'} w={'100%'} justify={'space-between'} gap={5}> <Flex mt={'50px'} w={'100%'} justify={'space-between'} gap={5}>
<Button onClick={() => router.push('/')} w={{base: '100%', md: 'unset'}}> <Button onClick={() => router.push('/')}
w={{base: '100%', md: 'unset'}}>
Retour Retour
</Button> </Button>
<Button <Button isLoading={isSubmitting} w={{base: '100%', md: 'unset'}}
isLoading={isSubmitting} type="submit">
w={{base: '100%', md: 'unset'}}
colorScheme="purple"
type="submit"
>
Je m&apos;inscris Je m&apos;inscris
</Button> </Button>
</Flex> </Flex>
@@ -8,19 +8,20 @@ import {
Box, Box,
CardHeader, CardHeader,
useToast, useToast,
} from "@chakra-ui/react"; } from '@chakra-ui/react';
import Carousel from "../../../Carousel"; import Carousel from '../../../Carousel';
import { BiHeart } from "react-icons/bi"; import {BiHeart} from 'react-icons/bi';
import { RxCross1 } from "react-icons/rx"; import {RxCross1} from 'react-icons/rx';
import { useMutation, useQuery } from "@tanstack/react-query"; import {useMutation, useQuery} from '@tanstack/react-query';
import PassionTagList from "@/components/layout/dashboard/card_user/PassionTagList"; import PassionTagList
import { useState } from "react"; from '@/components/layout/dashboard/card_user/PassionTagList';
import SearchFailCard from "./SearchFailCard"; import {useState} from 'react';
import LoadingPage from "@/components/LoadingPage"; import SearchFailCard from './SearchFailCard';
import LoadingPage from '@/components/LoadingPage';
export default function CardUser({ users, loggedUser, setMatch }) { export default function CardUser({users, loggedUser, setMatch}) {
const toast = useToast({ const toast = useToast({
position: "top", position: 'top',
duration: 2000, duration: 2000,
isClosable: true, isClosable: true,
}); });
@@ -29,8 +30,8 @@ export default function CardUser({ users, loggedUser, setMatch }) {
const likeMutation = useMutation({ const likeMutation = useMutation({
mutationFn: async (id) => { mutationFn: async (id) => {
const likePostOptions = { const likePostOptions = {
method: "POST", method: 'POST',
headers: { "Content-Type": "application/json" }, headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ body: JSON.stringify({
idUser: loggedUser.id, idUser: loggedUser.id,
idUserLiked: id, idUserLiked: id,
@@ -48,9 +49,9 @@ export default function CardUser({ users, loggedUser, setMatch }) {
onSuccess: (data) => { onSuccess: (data) => {
if (data.error) { if (data.error) {
toast({ toast({
title: "Erreur", title: 'Erreur',
description: "Une erreur est survenue", description: 'Une erreur est survenue',
status: "error", status: 'error',
}); });
return; return;
} }
@@ -59,9 +60,9 @@ export default function CardUser({ users, loggedUser, setMatch }) {
} }
setListUsers(listUsers.slice(1)); setListUsers(listUsers.slice(1));
toast({ toast({
title: "J'aime", title: 'J\'aime',
description: "Votre action a bien été prise en compte", description: 'Votre action a bien été prise en compte',
status: "success", status: 'success',
}); });
//tester si match et afficher un truc //tester si match et afficher un truc
@@ -71,8 +72,8 @@ export default function CardUser({ users, loggedUser, setMatch }) {
const dislikeMutation = useMutation({ const dislikeMutation = useMutation({
mutationFn: async (id) => { mutationFn: async (id) => {
const dislikePostOptions = { const dislikePostOptions = {
method: "POST", method: 'POST',
headers: { "Content-Type": "application/json" }, headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ body: JSON.stringify({
idUser: loggedUser.id, idUser: loggedUser.id,
idUserDisliked: id, idUserDisliked: id,
@@ -90,18 +91,18 @@ export default function CardUser({ users, loggedUser, setMatch }) {
onSuccess: (data) => { onSuccess: (data) => {
if (data.error) { if (data.error) {
toast({ toast({
title: "Erreur", title: 'Erreur',
description: "Une erreur est survenue", description: 'Une erreur est survenue',
status: "error", status: 'error',
duration: 2000, duration: 2000,
}); });
return; return;
} }
setListUsers(listUsers.slice(1)); setListUsers(listUsers.slice(1));
toast({ toast({
title: "J'aime pas", title: 'J\'aime pas',
description: "Votre action a bien été prise en compte", description: 'Votre action a bien été prise en compte',
status: "success", status: 'success',
duration: 2000, duration: 2000,
}); });
}, },
@@ -113,91 +114,70 @@ export default function CardUser({ users, loggedUser, setMatch }) {
data: listPassions, data: listPassions,
error: passionError, error: passionError,
} = useQuery({ } = useQuery({
queryKey: ["passions"], queryKey: ['passions'],
queryFn: async () => { queryFn: async () => {
return fetch(`/api/passions/`) return fetch(`/api/passions/`)
.then((res) => res.json()) .then(res => res.json())
.catch((err) => { .catch(err => err);
return err;
});
}, },
}); });
const formateDateToAge = (birthdate: Date) => { const formateDateToAge = (birthdate: Date) => {
const date = new Date(birthdate); const date = new Date(birthdate);
var ageDifMs = Date.now() - date.getTime(); const ageDifMs = Date.now() - date.getTime();
var ageDate = new Date(ageDifMs); // miliseconds from epoch const ageDate = new Date(ageDifMs); // miliseconds from epoch
return Math.abs(ageDate.getUTCFullYear() - 1970); return Math.abs(ageDate.getUTCFullYear() - 1970);
}; };
if (listUsers.length === 0) { if (listUsers.length === 0) return <SearchFailCard/>;
return <SearchFailCard />; if (likeMutation.isLoading ||
} dislikeMutation.isLoading) return <LoadingPage/>;
if (likeMutation.isLoading || dislikeMutation.isLoading) {
return <LoadingPage />;
}
return ( return (
<Card w={"100%"} h={"100%"} borderRadius={"1rem"} overflow={"hidden"}> <Card w={'100%'} h={'100%'} borderRadius={'1rem'} overflow={'hidden'}>
<CardHeader> <CardHeader>
<Carousel borderRadius={"1rem"} images={listUsers?.[0].images} /> <Carousel borderRadius={'1rem'} images={listUsers?.[0].images}/>
</CardHeader> </CardHeader>
<CardBody> <CardBody>
<Flex justify={"space-between"} mb={"20px"}> <Flex justify={'space-between'} mb={'20px'}>
<Heading fontSize={"1.5rem"} fontWeight={"bold"} flexBasis={"70%"}> <Heading fontSize={'1.5rem'} fontWeight={'bold'} flexBasis={'70%'}>
{listUsers[0].firstName} {listUsers[0].lastName},{" "} {listUsers[0].firstName} {listUsers[0].lastName},{' '}
{formateDateToAge(listUsers[0].birthdate)} ans {formateDateToAge(listUsers[0].birthdate)} ans
</Heading> </Heading>
<Flex gap={1}> <Flex gap={1}>
<IconButton <IconButton icon={<BiHeart/>} aria-label="like"
aria-label="like" borderRadius={'1rem'}
borderRadius={"1rem"} onClick={() => likeMutation.mutate(listUsers[0].id)
colorScheme={"purple"} }/>
onClick={() => { <IconButton icon={<RxCross1/>} aria-label="dislike"
likeMutation.mutate(listUsers[0].id); borderRadius={'1rem'} variant={'outline'}
}} onClick={() => dislikeMutation.mutate(
> listUsers[0].id)}/>
<BiHeart />
</IconButton>
<IconButton
aria-label="dislike"
borderRadius={"1rem"}
colorScheme={"purple"}
variant={"outline"}
onClick={() => {
dislikeMutation.mutate(listUsers[0].id);
}}
>
<RxCross1 />
</IconButton>
</Flex> </Flex>
</Flex> </Flex>
<Box mb={"20px"}> <Box mb={'20px'}>
<Heading size={"sm"} fontWeight={"bold"} mb="0.5rem"> <Heading size={'sm'} fontWeight={'bold'} mb="0.5rem">
A propos : A propos :
</Heading> </Heading>
<Text as="i">&quot;{listUsers[0].bio}&quot;</Text> <Text as="i">&quot;{listUsers[0].bio}&quot;</Text>
</Box> </Box>
<Box> <Box>
<Heading size={"sm"} fontWeight={"bold"}> <Heading size={'sm'} fontWeight={'bold'}>
Passions : Passions :
</Heading> </Heading>
{passionLoading ? ( {passionLoading
<Text>Chargement des passions...</Text> ? <Text>Chargement des passions...</Text>
) : passionIsError ? ( : passionIsError
<Text>Erreur lors du chargement des passions</Text> ? <Text>Erreur lors du chargement des passions</Text>
) : ( :
<PassionTagList <PassionTagList passions={listUsers[0].PassionID}
passions={listUsers[0].PassionID}
userPassions={loggedUser.PassionID} userPassions={loggedUser.PassionID}
listPassions={listPassions} listPassions={listPassions}/>
/> }
)}
</Box> </Box>
</CardBody> </CardBody>
</Card> </Card>
@@ -1,4 +1,4 @@
import { Badge, Flex, Tag } from "@chakra-ui/react"; import {Flex, Tag} from '@chakra-ui/react';
type Props = { type Props = {
passions: string[]; passions: string[];
@@ -6,22 +6,15 @@ type Props = {
listPassions?: Object[]; listPassions?: Object[];
}; };
export default function PassionTagList({ const PassionTagList = ({passions, userPassions = [], listPassions}: Props) => (
passions, <Flex gap={'0.5rem'} mt={'1vh'} flexWrap="wrap">
userPassions = [],
listPassions,
}: Props) {
return (
<Flex gap={"0.5rem"} mt={"1vh"} flexWrap="wrap">
{passions.map((passionID, index) => ( {passions.map((passionID, index) => (
<Tag <Tag variant={userPassions.includes(passionID) ? 'subtle' : 'outline'}
key={index} key={index}>
variant={userPassions.includes(passionID) ? "subtle" : "outline"}
colorScheme={"purple"}
>
{listPassions?.find((passion) => passion.id === passionID)?.name} {listPassions?.find((passion) => passion.id === passionID)?.name}
</Tag> </Tag>
))} ))}
</Flex> </Flex>
); );
}
export default PassionTagList;
@@ -1,8 +1,7 @@
import { Card, Text, CardBody, CardHeader } from "@chakra-ui/react"; import {Card, Text, CardBody, CardHeader} from '@chakra-ui/react';
export default function SearchFailCard(props) { const SearchFailCard = () => (
return ( <Card w={'100%'} h={'100%'} borderRadius={'1rem'} overflow={'hidden'}>
<Card w={"100%"} h={"100%"} borderRadius={"1rem"} overflow={"hidden"}>
<CardHeader> Aucun profil correspondant à vos critères </CardHeader> <CardHeader> Aucun profil correspondant à vos critères </CardHeader>
<CardBody> <CardBody>
<Text as="b"> <Text as="b">
@@ -10,5 +9,6 @@ export default function SearchFailCard(props) {
</Text> </Text>
</CardBody> </CardBody>
</Card> </Card>
); );
}
export default SearchFailCard;
@@ -1,90 +1,64 @@
import {Card, Flex, Box, Image, Text, Spacer, Divider} from "@chakra-ui/react"; import {Card, Flex, Box, Image, Text, Spacer, Divider} from '@chakra-ui/react';
import { useRouter } from "next/router"; import {useRouter} from 'next/router';
import { AiFillMessage } from "react-icons/ai"; import {AiFillMessage} from 'react-icons/ai';
import { BsFillPersonFill } from "react-icons/bs"; import {BsFillPersonFill} from 'react-icons/bs';
import { BiLogOut } from "react-icons/bi"; import {BiLogOut} from 'react-icons/bi';
import { FaMapMarkedAlt } from "react-icons/fa"; import {FaMapMarkedAlt} from 'react-icons/fa';
import LeftPanelButton from "@/components/layout/dashboard/left_panel/LeftPanelButton"; import LeftPanelButton
import { signOut } from "next-auth/react"; from '@/components/layout/dashboard/left_panel/LeftPanelButton';
import {signOut} from 'next-auth/react';
export default function LeftPanel(props) { export default function LeftPanel(props) {
const router = useRouter(); const router = useRouter();
const { user } = props; const {user} = props;
const formateDate = (dateString) => { const formateDate = (dateString) => {
var options = { year: "numeric", month: "long", day: "numeric" }; const options = {year: 'numeric', month: 'long', day: 'numeric'};
return new Date(dateString).toLocaleDateString([], options); return new Date(dateString).toLocaleDateString([], options);
}; };
return ( return (
<Card <Card width={'20vw'} height={'100%'} borderRadius={0} padding={'0px'}
width={"20vw"} bg={'#faf9ff'}>
height={"100%"} <Flex direction={'column'} height={'100%'} margin={'10%'}>
borderRadius={0}
padding={"0px"}
bg={"#faf9ff"}
>
<Flex direction={"column"} height={"100%"} margin={"10%"}>
<Box> <Box>
{user.images.length === 0 ? ( <Image src={user.images[0] ?? '/blank_profile_picture.webp'}
<Image src={"/blank_profile_picture.webp"} borderRadius={"1rem"} /> objectFit={'contain'} borderRadius={'1rem'}/>
) : ( <Box mt={'2rem'}>
<Image <Flex align={'center'} justifyContent="space-between" mb={'1rem'}>
src={user.images[0]} <Text fontSize={'1.5rem'} fontWeight={'bold'}>
borderRadius={"1rem"}
objectFit={"contain"}
width={"100%"}
/>
)}
<Box mt={"2rem"}>
<Flex align={"center"} justifyContent="space-between" mb={"1rem"}>
<Text fontSize={"1.5rem"} fontWeight={"bold"}>
{user.firstName} {user.lastName} {user.firstName} {user.lastName}
</Text> </Text>
<Text fontSize={"1rem"} fontWeight={"bold"}> <Text fontSize={'1rem'} fontWeight={'bold'}>
{formateDate(user.birthdate)} {formateDate(user.birthdate)}
</Text> </Text>
</Flex> </Flex>
<Divider colorScheme={"purple"} mb={"1rem"} /> <Divider mb={'1rem'}/>
<Text as="i" fontWeight={"bold"}> <Text as="i" fontWeight={'bold'}>&quot;{user.bio}&quot;</Text>
&quot;{user.bio}&quot;
</Text>
</Box> </Box>
</Box> </Box>
<Spacer /> <Spacer/>
<Flex <Flex gap={2} direction={'column'} mt={'1vh'}
gap={2} spacing={3.5} alignContent={'bottom'}>
direction={"column"} <LeftPanelButton leftIcon={<FaMapMarkedAlt/>}
mt={"1vh"} onClickHandler={() => router.push('/map')}>
spacing={3.5}
alignContent={"bottom"}
>
<LeftPanelButton
leftIcon={<FaMapMarkedAlt />}
onClickHandler={() => router.push("/map")}
>
Carte Carte
</LeftPanelButton> </LeftPanelButton>
<LeftPanelButton
leftIcon={<AiFillMessage />} <LeftPanelButton leftIcon={<AiFillMessage/>}
onClickHandler={() => router.push("/dashboard")} onClickHandler={() => router.push('/dashboard')}>
>
Messages Messages
</LeftPanelButton> </LeftPanelButton>
<LeftPanelButton
leftIcon={<BsFillPersonFill />} <LeftPanelButton leftIcon={<BsFillPersonFill/>}
onClickHandler={() => router.push("/profile")} onClickHandler={() => router.push('/profile')}>
> Profil
Profile
</LeftPanelButton> </LeftPanelButton>
<LeftPanelButton <LeftPanelButton variant={'outline'} leftIcon={<BiLogOut/>}
variant={"outline"} onClickHandler={() => signOut({callbackUrl: '/'})}>
leftIcon={<BiLogOut />} Déconnexion
onClickHandler={() => signOut({ callbackUrl: "/" })}
>
Deconnexion
</LeftPanelButton> </LeftPanelButton>
</Flex> </Flex>
</Flex> </Flex>
@@ -2,25 +2,20 @@ import {Button, ResponsiveValue} from '@chakra-ui/react';
import {ReactJSXElement} from '@emotion/react/types/jsx-namespace'; import {ReactJSXElement} from '@emotion/react/types/jsx-namespace';
type Props = { type Props = {
children?: ReactJSXElement children?: ReactJSXElement | string
onClickHandler: () => void onClickHandler: () => void
leftIcon?: ReactJSXElement, leftIcon?: ReactJSXElement,
variant?: ResponsiveValue<'link' | 'outline' | string | 'ghost' | 'solid' | 'unstyled'> variant?: ResponsiveValue<'link' | 'outline' | string | 'ghost' | 'solid' | 'unstyled'>
} }
export default function LeftPanelButton(props: Props) { export default function LeftPanelButton(props: Props) {
const {children, onClickHandler, leftIcon: icon, variant = "ghost"} = props; const {children, onClickHandler, leftIcon: icon, variant = 'ghost'} = props;
return ( return (
<Button <Button variant={variant} width={'100%'} justifyContent={'left'}
colorScheme={'purple'} onClick={onClickHandler} leftIcon={icon} fontWeight={'bold'}
variant={variant} fontSize={'1.2rem'}>
width={'100%'} {children}
justifyContent={'left'} </Button>
onClick={onClickHandler}
leftIcon={icon}
fontWeight={'bold'}
fontSize={'1.2rem'}
>{children}</Button>
); );
} }
+9 -17
View File
@@ -13,37 +13,29 @@ export default function HeroBanner() {
const router = useRouter(); const router = useRouter();
return ( return (
<Flex <Flex bg={'#FAF9FF'} p={150} minH={{base: 'unset', md: '100vh'}}
bg={'#FAF9FF'} align={'center'} justify={'center'}>
p={150}
minH={{base: 'unset', md: '100vh'}}
align={'center'}
justify={'center'}
>
<Flex gap={10}> <Flex gap={10}>
<Flex justify={'center'} direction={'column'} flexBasis={'100%'}> <Flex justify={'center'} direction={'column'} flexBasis={'100%'}>
<Heading mb={'2.5rem'}>Prêt(e) à trouver votre âme sœur ?</Heading> <Heading mb={'2.5rem'}>Prêt(e) à trouver votre âme sœur ?</Heading>
<Text mb={'2rem'} maxW={{md: "500px"}}> <Text mb={'2rem'} maxW={{md: '500px'}}>
Notre site de rencontre vous offre la possibilité de rencontrer Notre site de rencontre vous offre la possibilité de rencontrer
des personnes intéressantes et de trouver l&apos;amour. Inscrivez-vous des personnes intéressantes et de trouver l&apos;amour.
Inscrivez-vous
dès maintenant pour découvrir toutes nos fonctionnalités ! dès maintenant pour découvrir toutes nos fonctionnalités !
</Text> </Text>
<ButtonGroup> <ButtonGroup>
<Button colorScheme={'purple'} <Button onClick={() => router.push('/register')}>
onClick={() => router.push('/register')}>
S&apos;inscrire S&apos;inscrire
</Button> </Button>
</ButtonGroup> </ButtonGroup>
</Flex> </Flex>
<Box flexBasis={'100%'} display={{base: "none", md: "block"}}> <Box flexBasis={'100%'} display={{base: 'none', md: 'block'}}>
<Image borderRadius={20} <Image borderRadius={20} boxShadow={'lg'} src={'/couple_img.jpg'}
boxShadow={'lg'} alt="happy couple"/>
src={'/couple_img.jpg'}
alt="happy couple"
/>
</Box> </Box>
</Flex> </Flex>
</Flex> </Flex>
@@ -4,8 +4,7 @@ export default function PresentationSection() {
return ( return (
<Flex justify={{base: 'center', md: 'right'}} alignItems={'center'} <Flex justify={{base: 'center', md: 'right'}} alignItems={'center'}
bg={{base: '#805AD5'}} bgImage={'/couple_funny.png'} bg={{base: '#805AD5'}} bgImage={'/couple_funny.png'}
bgSize={'cover'} bgSize={'cover'} bgPos={'center'}
bgPos={'center'}
bgAttachment={'fixed'} minH={{base: 'initial', md: '80vh'}}> bgAttachment={'fixed'} minH={{base: 'initial', md: '80vh'}}>
<Box p={50} width={{base: '100%', md: '50%'}} maxW={{md: "500px"}} bg={'#805AD5'}> <Box p={50} width={{base: '100%', md: '50%'}} maxW={{md: "500px"}} bg={'#805AD5'}>
+20 -29
View File
@@ -1,21 +1,22 @@
import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet"; import {MapContainer, TileLayer, Marker, Popup} from 'react-leaflet';
import { Flex, Text, useToast } from "@chakra-ui/react"; import {Flex, Text, useToast} from '@chakra-ui/react';
import MarkerClusterGroup from "@christopherpickering/react-leaflet-markercluster"; import MarkerClusterGroup
from '@christopherpickering/react-leaflet-markercluster';
import "@christopherpickering/react-leaflet-markercluster/dist/styles.min.css"; import '@christopherpickering/react-leaflet-markercluster/dist/styles.min.css';
import MarkerBar from "./MarkerBar"; import MarkerBar from './MarkerBar';
import "leaflet/dist/leaflet.css"; import 'leaflet/dist/leaflet.css';
export default function MapComponent(props: any) { export default function MapComponent(props: any) {
const { location, listBars } = props; const {location, listBars} = props;
const toast = useToast({ position: "bottom" }); const toast = useToast({position: 'bottom'});
const idToastError = "error_location"; const idToastError = 'error_location';
const userIcon = new L.Icon({ const userIcon = new L.Icon({
iconUrl: "logo.svg", iconUrl: 'logo.svg',
iconSize: [35, 35], iconSize: [35, 35],
}); });
@@ -23,32 +24,24 @@ export default function MapComponent(props: any) {
<> <>
{location[0] === null || location[1] === null ? ( {location[0] === null || location[1] === null ? (
<> <>
{!toast.isActive(idToastError) {!toast.isActive(idToastError) &&
? toast({ toast({
title: "Erreur", title: 'Erreur',
id: idToastError, id: idToastError,
description: "Veuillez autoriser la localisation", description: 'Veuillez autoriser la localisation',
status: "error", status: 'error',
isClosable: false, isClosable: false,
duration: 100000, duration: 100000,
}) })}
: null}
<Text color="purple.500" fontSize="2xl" fontWeight="bold"> <Text color="purple.500" fontSize="2xl" fontWeight="bold">
Veuillez autoriser la localisation Veuillez autoriser la localisation
</Text> </Text>
</> </>
) : ( ) : (
<Flex> <Flex>
<MapContainer <MapContainer center={location} zoom={13} minZoom={8}
center={location}
zoom={13}
minZoom={8}
zoomControl={false} zoomControl={false}
style={{ style={{width: '100vw', height: '100vw'}}>
width: "100vw",
height: "100vw",
}}
>
<TileLayer <TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
@@ -57,9 +50,7 @@ export default function MapComponent(props: any) {
<Marker icon={userIcon} position={location}></Marker> <Marker icon={userIcon} position={location}></Marker>
<MarkerClusterGroup chunkedLoading showCoverageOnHover={false}> <MarkerClusterGroup chunkedLoading showCoverageOnHover={false}>
{listBars.map((bar, index) => { {listBars.map((bar: any, index: number) => <MarkerBar key={index} bar={bar}/>)}
return <MarkerBar key={index} bar={bar} />;
})}
</MarkerClusterGroup> </MarkerClusterGroup>
</MapContainer> </MapContainer>
</Flex> </Flex>
+2 -4
View File
@@ -1,6 +1,6 @@
import { Marker, Popup } from "react-leaflet"; import { Marker, Popup } from "react-leaflet";
import "leaflet/dist/leaflet.css"; import "leaflet/dist/leaflet.css";
import { Box, Button, Card, CardBody, Text } from "@chakra-ui/react"; import { Box, Button, Text } from "@chakra-ui/react";
export default function MarkerBar(props) { export default function MarkerBar(props) {
const { bar } = props; const { bar } = props;
@@ -23,9 +23,7 @@ export default function MarkerBar(props) {
<Text fontSize={"1rem"} fontWeight={"bold"} align={"center"}> <Text fontSize={"1rem"} fontWeight={"bold"} align={"center"}>
{bar.name || '"Nom du bar"'} {bar.name || '"Nom du bar"'}
</Text> </Text>
<Button colorScheme={"purple"} variant={"outline"}> <Button variant={"outline"}>Inviter un match</Button>
Inviter un match
</Button>
</Box> </Box>
</Popup> </Popup>
</Marker> </Marker>
@@ -4,43 +4,27 @@ import {
Tag, Tag,
TagLeftIcon, TagLeftIcon,
TagLabel, TagLabel,
} from "@chakra-ui/react"; } from '@chakra-ui/react';
import { IoAdd, IoRemove } from "react-icons/io5"; import {IoAdd, IoRemove} from 'react-icons/io5';
export default function CustomCheckbox(props) { export default function CustomCheckbox(props) {
const { text } = props; const {text} = props;
const { state, getInputProps, getCheckboxProps, getLabelProps, htmlProps } = const {state, getInputProps, getCheckboxProps, getLabelProps, htmlProps} =
useCheckbox(props); useCheckbox(props);
return ( return (
<chakra.label <chakra.label variant={state.isChecked ? 'solid' : 'outline'}
// colorScheme={"purple"} cursor="pointer" {...htmlProps}>
variant={state.isChecked ? "solid" : "outline"} <input {...getInputProps()} hidden/>
cursor="pointer"
{...htmlProps} <Tag {...getCheckboxProps()} variant={state.isChecked ? "solid" : "outline"} {...getLabelProps()}>
> {state.isChecked
<input {...getInputProps()} hidden /> ? <TagLeftIcon as={IoRemove}/>
{state.isChecked ? ( : <TagLeftIcon as={IoAdd}/>
<Tag }
{...getCheckboxProps()}
colorScheme={"purple"}
{...getLabelProps()}
>
<TagLeftIcon as={IoRemove} />
<TagLabel>{text}</TagLabel> <TagLabel>{text}</TagLabel>
</Tag> </Tag>
) : (
<Tag
{...getCheckboxProps()}
colorScheme={"purple"}
variant={"outline"}
{...getLabelProps()}
>
<TagLeftIcon as={IoAdd} />
<TagLabel>{text}</TagLabel>
</Tag>
)}
</chakra.label> </chakra.label>
); );
} }
@@ -6,52 +6,40 @@ import {
FormErrorMessage, FormErrorMessage,
FormHelperText, FormHelperText,
FormLabel, FormLabel,
} from "@chakra-ui/react"; } from '@chakra-ui/react';
import { Controller, ControllerProps } from "react-hook-form"; import {Controller, ControllerProps} from 'react-hook-form';
type CustomEditableProps = { type CustomEditableProps = {
label: string; label: string;
isDisabled?: boolean; isDisabled?: boolean;
helperText?: string; helperText?: string;
} & Omit<ControllerProps, "render">; } & Omit<ControllerProps, 'render'>;
export default function CustomEditable(props: CustomEditableProps) { export default function CustomEditable(props: CustomEditableProps) {
const { const {
defaultValue, defaultValue,
label, label,
name, name,
helperText = "", helperText = '',
isDisabled = false, isDisabled = false,
...controllerProps ...controllerProps
} = props; } = props;
return ( return (
<Controller <Controller{...controllerProps} name={name} defaultValue={defaultValue}
{...controllerProps} render={({field, fieldState: {invalid, error}}) => (
name={name}
defaultValue={defaultValue}
render={({ field, fieldState: { invalid, error } }) => {
return (
<FormControl id={name} isInvalid={invalid}> <FormControl id={name} isInvalid={invalid}>
<FormLabel as="legend" htmlFor={name}> <FormLabel as="legend" htmlFor={name}>{label}</FormLabel>
{label} <Editable{...field} id={name} fontWeight="bold"
</FormLabel>
<Editable
{...field}
id={name}
fontWeight="bold"
isDisabled={isDisabled} isDisabled={isDisabled}
placeholder={"Non renseigné"} placeholder={'Non renseigné'}
color={isDisabled ? "gray.500" : undefined} color={isDisabled ? 'gray.500' : undefined}>
> <EditablePreview/>
<EditablePreview /> <EditableInput/>
<EditableInput />
</Editable> </Editable>
<FormHelperText>{helperText}</FormHelperText> <FormHelperText>{helperText}</FormHelperText>
<FormErrorMessage as="b">{error?.message}</FormErrorMessage> <FormErrorMessage as="b">{error?.message}</FormErrorMessage>
</FormControl> </FormControl>
); )}/>
}}
/>
); );
} }
@@ -5,13 +5,13 @@ import {
FormControl, FormControl,
FormErrorMessage, FormErrorMessage,
FormLabel, FormLabel,
} from "@chakra-ui/react"; } from '@chakra-ui/react';
import { Controller, ControllerProps } from "react-hook-form"; import {Controller, ControllerProps} from 'react-hook-form';
type CustomEditableProps = { type CustomEditableProps = {
label: string; label: string;
isDisabled?: boolean; isDisabled?: boolean;
} & Omit<ControllerProps, "render">; } & Omit<ControllerProps, 'render'>;
export default function CustomEditableArea(props: CustomEditableProps) { export default function CustomEditableArea(props: CustomEditableProps) {
const { const {
@@ -23,32 +23,19 @@ export default function CustomEditableArea(props: CustomEditableProps) {
} = props; } = props;
return ( return (
<Controller <Controller{...controllerProps} name={name} defaultValue={defaultValue}
{...controllerProps} render={({field, fieldState: {invalid, error}}) => (
name={name}
defaultValue={defaultValue}
render={({ field, fieldState: { invalid, error } }) => {
return (
<FormControl id={name} isInvalid={invalid}> <FormControl id={name} isInvalid={invalid}>
<FormLabel as="legend" htmlFor={name}> <FormLabel as="legend" htmlFor={name}>{label}</FormLabel>
{label} <Editable{...field} id={name} fontWeight="bold"
</FormLabel> width={'100%'} isDisabled={isDisabled}
<Editable placeholder={'Non renseigné'}
{...field} color={isDisabled ? 'gray.500' : undefined}>
id={name} <EditablePreview/>
fontWeight="bold" <EditableTextarea/>
width={"100%"}
isDisabled={isDisabled}
placeholder={"Non renseigné"}
color={isDisabled ? "gray.500" : undefined}
>
<EditablePreview />
<EditableTextarea />
</Editable> </Editable>
<FormErrorMessage as="b">{error?.message}</FormErrorMessage> <FormErrorMessage as="b">{error?.message}</FormErrorMessage>
</FormControl> </FormControl>
); )}/>
}}
/>
); );
} }
@@ -5,7 +5,7 @@ import {
FormHelperText, FormHelperText,
FormLabel, FormLabel,
UseEditableProps, UseEditableProps,
} from "@chakra-ui/react"; } from '@chakra-ui/react';
type CustomEditableProps = { type CustomEditableProps = {
id: string; id: string;
@@ -14,21 +14,14 @@ type CustomEditableProps = {
} & UseEditableProps; } & UseEditableProps;
export default function CustomEditable(props: CustomEditableProps) { export default function CustomEditable(props: CustomEditableProps) {
const { label, id, helperText = "", ...UseEditableProps } = props; const {label, id, helperText = '', ...UseEditableProps} = props;
return ( return (
<FormControl> <FormControl>
<FormLabel as="legend" htmlFor={id}> <FormLabel as="legend" htmlFor={id}>{label}</FormLabel>
{label} <Editable{...UseEditableProps} fontWeight="bold" isDisabled={true}
</FormLabel> placeholder={'Non renseigné'} color={'gray.500'}>
<Editable <EditablePreview/>
{...UseEditableProps}
fontWeight="bold"
isDisabled={true}
placeholder={"Non renseigné"}
color={"gray.500"}
>
<EditablePreview />
</Editable> </Editable>
<FormHelperText>{helperText}</FormHelperText> <FormHelperText>{helperText}</FormHelperText>
</FormControl> </FormControl>
@@ -1,60 +1,35 @@
import { FormLabel, HStack, Radio, RadioGroup } from "@chakra-ui/react"; import {FormLabel, HStack, Radio, RadioGroup} from '@chakra-ui/react';
import { Gender } from "@prisma/client"; import {Gender} from '@prisma/client';
import { Controller, ControllerProps } from "react-hook-form"; import {Controller, ControllerProps} from 'react-hook-form';
type CustomRadioGenderProps = { type CustomRadioGenderProps = {
label: string; label: string;
} & Omit<ControllerProps, "render">; } & Omit<ControllerProps, 'render'>;
export default function CustomRadioGender(props: CustomRadioGenderProps) { export default function CustomRadioGender(props: CustomRadioGenderProps) {
const { defaultValue, label, name, ...controllerProps } = props; const {defaultValue, label, name, ...controllerProps} = props;
const getTextGender = (gender: string) => { const genders = [
switch (gender) { {label: 'Homme', gender: Gender.MALE},
case Gender.MALE: {label: 'Femme', gender: Gender.FEMALE},
return "Homme"; {label: 'Autre', gender: Gender.OTHER},
case Gender.FEMALE: {label: 'Non renseigné', gender: Gender.UNKNOWN},
return "Femme"; ];
case Gender.OTHER:
return "Autre";
case Gender.UNKNOWN:
return "Non renseigné";
}
};
return ( return (
<> <>
<FormLabel as={"legend"} htmlFor={name}> <FormLabel as={'legend'} htmlFor={name}>{label}</FormLabel>
{label} <Controller{...controllerProps} name={name}
</FormLabel> render={({field}) => (
<Controller <RadioGroup{...field} id={name} as="b"
{...controllerProps} defaultValue={defaultValue}>
name={name} <HStack spacing={'0.5rem'}>
render={({ field }) => { {genders.map(({label, gender}, index) => (
return ( <Radio key={index} value={gender}>{label}</Radio>
<RadioGroup ))}
{...field}
colorScheme={"purple"}
id={name}
as="b"
defaultValue={defaultValue}
>
<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> </HStack>
</RadioGroup> </RadioGroup>
); )}/>
}}
/>
</> </>
); );
} }
@@ -6,10 +6,10 @@ import {
RangeSliderThumb, RangeSliderThumb,
RangeSliderTrack, RangeSliderTrack,
Tooltip, Tooltip,
} from "@chakra-ui/react"; } from '@chakra-ui/react';
import { Controller, ControllerProps } from "react-hook-form"; import {Controller, ControllerProps} from 'react-hook-form';
import { FaGreaterThan, FaLessThan } from "react-icons/fa"; import {FaGreaterThan, FaLessThan} from 'react-icons/fa';
type CustomRangeSliderProps = { type CustomRangeSliderProps = {
label: string; label: string;
@@ -19,7 +19,7 @@ type CustomRangeSliderProps = {
showTooltipAge: boolean; showTooltipAge: boolean;
setShowTooltipAge: React.Dispatch<React.SetStateAction<boolean>>; setShowTooltipAge: React.Dispatch<React.SetStateAction<boolean>>;
name: string; name: string;
} & Omit<ControllerProps, "render">; } & Omit<ControllerProps, 'render'>;
export default function CustomRangeSlider(props: CustomRangeSliderProps) { export default function CustomRangeSlider(props: CustomRangeSliderProps) {
const { const {
@@ -35,59 +35,39 @@ export default function CustomRangeSlider(props: CustomRangeSliderProps) {
return ( return (
<> <>
<FormLabel as={"legend"} htmlFor={name}> <FormLabel as={'legend'} htmlFor={name}>{label}</FormLabel>
{label} <Controller{...controllerProps} name={name}
</FormLabel> render={({field: {onChange}}) => (
<Controller <RangeSlider aria-label={['min', 'max']} min={18}
{...controllerProps}
name={name}
render={({ field: { onChange } }) => (
<RangeSlider
aria-label={["min", "max"]}
colorScheme={"purple"}
min={18}
max={99} max={99}
id={name} id={name} color={'pink.500'}
color={"pink.500"}
defaultValue={defaultValue} defaultValue={defaultValue}
onChange={(v: [number, number]) => { onChange={(v: [number, number]) => {
setSliderAgeValue(v); setSliderAgeValue(v);
onChange(v); onChange(v);
}} }}
onMouseEnter={() => setShowTooltipAge(true)} onMouseEnter={() => setShowTooltipAge(true)}
onMouseLeave={() => setShowTooltipAge(false)} onMouseLeave={() => setShowTooltipAge(false)}>
>
<RangeSliderTrack> <RangeSliderTrack>
<RangeSliderFilledTrack bgColor={"purple.500"} /> <RangeSliderFilledTrack bgColor={'purple.500'}/>
</RangeSliderTrack> </RangeSliderTrack>
<Tooltip <Tooltip hasArrow bg="purple.500" color="white"
hasArrow placement="top" isOpen={showTooltipAge}
bg="purple.500" label={`${sliderAgeValue[0]}`}>
color="white"
placement="top"
isOpen={showTooltipAge}
label={`${sliderAgeValue[0]}`}
>
<RangeSliderThumb boxSize={6} index={0}> <RangeSliderThumb boxSize={6} index={0}>
<Box color={"purple.500"} as={FaLessThan} /> <Box color={'purple.500'} as={FaLessThan}/>
</RangeSliderThumb> </RangeSliderThumb>
</Tooltip> </Tooltip>
<Tooltip <Tooltip hasArrow bg="purple.500" color="white"
hasArrow placement="top" isOpen={showTooltipAge}
bg="purple.500" label={`${sliderAgeValue[1]}`}>
color="white"
placement="top"
isOpen={showTooltipAge}
label={`${sliderAgeValue[1]}`}
>
<RangeSliderThumb boxSize={6} index={1}> <RangeSliderThumb boxSize={6} index={1}>
<Box color={"purple.500"} as={FaGreaterThan} /> <Box color={'purple.500'} as={FaGreaterThan}/>
</RangeSliderThumb> </RangeSliderThumb>
</Tooltip> </Tooltip>
</RangeSlider> </RangeSlider>
)} )}/>
/>
</> </>
); );
} }
+17 -33
View File
@@ -6,10 +6,10 @@ import {
SliderThumb, SliderThumb,
SliderTrack, SliderTrack,
Tooltip, Tooltip,
} from "@chakra-ui/react"; } from '@chakra-ui/react';
import { Controller, ControllerProps } from "react-hook-form"; import {Controller, ControllerProps} from 'react-hook-form';
import { FaWalking } from "react-icons/fa"; import {FaWalking} from 'react-icons/fa';
type CustomSliderProps = { type CustomSliderProps = {
label: string; label: string;
@@ -19,7 +19,7 @@ type CustomSliderProps = {
showTooltipDistance: boolean; showTooltipDistance: boolean;
setShowTooltipDistance: React.Dispatch<React.SetStateAction<boolean>>; setShowTooltipDistance: React.Dispatch<React.SetStateAction<boolean>>;
name: string; name: string;
} & Omit<ControllerProps, "render">; } & Omit<ControllerProps, 'render'>;
export default function CustomSlider(props: CustomSliderProps) { export default function CustomSlider(props: CustomSliderProps) {
const { const {
@@ -35,46 +35,30 @@ export default function CustomSlider(props: CustomSliderProps) {
return ( return (
<> <>
<FormLabel as={"legend"} htmlFor={name}> <FormLabel as={'legend'} htmlFor={name}>{label}</FormLabel>
{label} <Controller{...controllerProps} name={name}
</FormLabel> render={({field: {onChange}}) => (
<Controller <Slider aria-label={'distance'} min={20} max={250}
{...controllerProps} id={'distance'} color={'pink.500'}
name={name}
render={({ field: { onChange } }) => (
<Slider
aria-label={"distance"}
colorScheme={"purple"}
min={20}
max={250}
id={"distance"}
color={"pink.500"}
defaultValue={defaultValue} defaultValue={defaultValue}
onChange={(v) => { onChange={v => {
setSliderDistanceValue(v); setSliderDistanceValue(v);
onChange(v); onChange(v);
}} }}
onMouseEnter={() => setShowTooltipDistance(true)} onMouseEnter={() => setShowTooltipDistance(true)}
onMouseLeave={() => setShowTooltipDistance(false)} onMouseLeave={() => setShowTooltipDistance(false)}>
>
<SliderTrack> <SliderTrack>
<SliderFilledTrack bgColor={"purple.500"} /> <SliderFilledTrack bgColor={'purple.500'}/>
</SliderTrack> </SliderTrack>
<Tooltip <Tooltip hasArrow bg="purple.500" color="white"
hasArrow placement="top" isOpen={showTooltipDistance}
bg="purple.500" label={`${sliderDistanceValue} km`}>
color="white"
placement="top"
isOpen={showTooltipDistance}
label={`${sliderDistanceValue} km`}
>
<SliderThumb boxSize={6}> <SliderThumb boxSize={6}>
<Box color={"purple.500"} as={FaWalking} /> <Box color={'purple.500'} as={FaWalking}/>
</SliderThumb> </SliderThumb>
</Tooltip> </Tooltip>
</Slider> </Slider>
)} )}/>
/>
</> </>
); );
} }
@@ -13,99 +13,75 @@ import {
useCheckboxGroup, useCheckboxGroup,
useDisclosure, useDisclosure,
useToast, useToast,
} from "@chakra-ui/react"; } from '@chakra-ui/react';
import { useQueryClient } from "@tanstack/react-query"; import {useQueryClient} from '@tanstack/react-query';
import { RiEditBoxLine } from "react-icons/ri"; import {RiEditBoxLine} from 'react-icons/ri';
import CustomCheckbox from "./CustomCheckbox"; import CustomCheckbox from './CustomCheckbox';
import {Passion} from '@prisma/client';
export default function ModalChoosePassion(props) { export default function ModalChoosePassion(props) {
const { isOpen, onOpen, onClose } = useDisclosure(); const {isOpen, onOpen, onClose} = useDisclosure();
const { user, passions } = props; const {user, passions} = props;
const toast = useToast({ position: "top", isClosable: true }); const toast = useToast({position: 'top', isClosable: true});
const client = useQueryClient(); const client = useQueryClient();
const { value, getCheckboxProps } = useCheckboxGroup({ const {value, getCheckboxProps} = useCheckboxGroup({
defaultValue: user.PassionID, defaultValue: user.PassionID,
}); });
const savePassions = (idPassionList) => { const savePassions = (idPassionList) => {
let jsonPassions = { Passion: { connect: [] } }; let jsonPassions = {Passion: {connect: []}};
idPassionList.forEach((id) => { idPassionList.forEach((id) => {
jsonPassions.Passion.connect.push({ id: id }); jsonPassions.Passion.connect.push({id: id});
}); });
const options = { const options = {
method: "PATCH", method: 'PATCH',
headers: { headers: {'Content-Type': 'application/json'},
"Content-Type": "application/json",
},
body: JSON.stringify(jsonPassions), body: JSON.stringify(jsonPassions),
}; };
fetch(`/api/users/${user.id}`, options) fetch(`/api/users/${user.id}`, options)
.then((res) => res.json()) .then(res => res.json())
.then((data) => { .then(() => {
toast({ toast({
title: "Centres d'intérêts mis à jour", title: 'Centres d\'intérêts mis à jour',
status: "success", status: 'success',
duration: 9000, duration: 9000,
}); });
client.invalidateQueries("user"); client.invalidateQueries('user');
onClose(); onClose();
}) })
.catch((err) => { .catch(err => console.log(err));
console.log(err);
});
}; };
return ( return (
<> <>
<Button <Button onClick={onOpen} leftIcon={<RiEditBoxLine/>}>
colorScheme={"purple"} Choisir les centres d'intérêts
onClick={onOpen}
leftIcon={<RiEditBoxLine />}
>
Choisir les centres d'intérèts
</Button> </Button>
<Modal <Modal blockScrollOnMount={false} size={'4xl'} isOpen={isOpen}
blockScrollOnMount={false} onClose={onClose} scrollBehavior={'inside'}>
size={"4xl"} <ModalOverlay/>
isOpen={isOpen}
onClose={onClose}
scrollBehavior={"inside"}
>
<ModalOverlay />
<ModalContent> <ModalContent>
<ModalHeader>Choix des centres d'intérèts</ModalHeader> <ModalHeader>Choix des centres d'intérêts</ModalHeader>
<ModalCloseButton /> <ModalCloseButton/>
<ModalBody> <ModalBody>
<Flex gap={"1rem"} flexWrap="wrap"> <Flex gap={'1rem'} flexWrap="wrap">
{passions !== null ? ( {passions && (
passions.map((passion, index) => { passions.map((passion: Passion) => (
return ( <CustomCheckbox key={passion.id} text={passion.name}
<CustomCheckbox {...getCheckboxProps({value: passion.id})}/>
{...getCheckboxProps({ value: passion.id })} )
key={passion.id} )
text={passion.name}
/>
);
})
) : (
<></>
)} )}
</Flex> </Flex>
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<Button <Button mr={3} onClick={() => savePassions(value)}>
colorScheme="purple"
mr={3}
onClick={() => {
savePassions(value);
}}
>
Save Save
</Button> </Button>
</ModalFooter> </ModalFooter>
@@ -13,44 +13,43 @@ import {
ModalHeader, ModalHeader,
ModalOverlay, ModalOverlay,
useDisclosure, useDisclosure,
useQuery,
useToast, useToast,
} from "@chakra-ui/react"; } from '@chakra-ui/react';
import { useQueryClient } from "@tanstack/react-query"; import {useQueryClient} from '@tanstack/react-query';
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, user, userData, setUserData } = props; const {images, user, userData, setUserData} = props;
const [listImage, setlistImage] = useState(images); const [listImage, setlistImage] = useState(images);
const toast = useToast({ position: "top", isClosable: true }); const toast = useToast({position: 'top', isClosable: true});
const client = useQueryClient(); const client = useQueryClient();
const uploadImage = async (file) => { const uploadImage = async (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',
body, body,
}; };
const imagePatchOptions = { const imagePatchOptions = {
method: "PUT", method: 'PUT',
headers: { "Content-Type": "application/json" }, headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ body: JSON.stringify({
images: [...listImage, `imageUsers/${file.name}`], images: [...listImage, `imageUsers/${file.name}`],
}), }),
}; };
fetch(`/api/file/uploadFile`, imagePostOptions) fetch(`/api/file/uploadFile`, imagePostOptions)
.then((res) => { .then(res => {
fetch(`/api/users/${user.id}`, imagePatchOptions) fetch(`/api/users/${user.id}`, imagePatchOptions)
.then((res) => { .then(res => {
toast({ toast({
title: `Ajout d'image effectué`, title: `Ajout d'image effectué`,
status: "success", status: 'success',
isClosable: true, isClosable: true,
}); });
setlistImage([...listImage, `imageUsers/${file.name}`]); setlistImage([...listImage, `imageUsers/${file.name}`]);
@@ -60,15 +59,15 @@ export default function ModalModifyImages(props) {
setIsLoading(false); setIsLoading(false);
toast({ toast({
title: `Erreur lors de l'ajout des images`, title: `Erreur lors de l'ajout des images`,
status: "error", status: 'error',
isClosable: true, isClosable: true,
}); });
}); });
}) })
.catch((err) => { .catch(err => {
toast({ toast({
title: `Erreur lors de l'ajout des images`, title: `Erreur lors de l'ajout des images`,
status: "error", status: 'error',
isClosable: true, isClosable: true,
}); });
console.log(err); console.log(err);
@@ -85,14 +84,14 @@ export default function ModalModifyImages(props) {
} }
const imageDeleteOptions = { const imageDeleteOptions = {
method: "DELETE", method: 'DELETE',
body: JSON.stringify({ fileName: fileName.split("/").pop() }), body: JSON.stringify({fileName: fileName.split('/').pop()}),
}; };
fetch(`/api/file/deleteFile`, imageDeleteOptions).then((res) => { fetch(`/api/file/deleteFile`, imageDeleteOptions).then((res) => {
const imagePatchOptions = { const imagePatchOptions = {
method: "PUT", method: 'PUT',
headers: { "Content-Type": "application/json" }, headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ body: JSON.stringify({
images: [...listImage], images: [...listImage],
}), }),
@@ -101,14 +100,14 @@ export default function ModalModifyImages(props) {
.then((res) => { .then((res) => {
toast({ toast({
title: `Suppression d'image effectué`, title: `Suppression d'image effectué`,
status: "success", status: 'success',
isClosable: true, isClosable: true,
}); });
}) })
.catch(() => { .catch(() => {
toast({ toast({
title: `Erreur lors de la suppression des images`, title: `Erreur lors de la suppression des images`,
status: "error", status: 'error',
isClosable: true, isClosable: true,
}); });
}); });
@@ -117,84 +116,60 @@ export default function ModalModifyImages(props) {
return ( return (
<> <>
<Button <Button onClick={onOpen} leftIcon={<RiEditBoxLine/>}>
colorScheme={"purple"}
onClick={onOpen}
leftIcon={<RiEditBoxLine />}
>
Modifier les images Modifier les images
</Button> </Button>
<Modal <Modal blockScrollOnMount={false} size={'4xl'} isOpen={isOpen}
blockScrollOnMount={false} onClose={onClose} scrollBehavior={'inside'}>
size={"4xl"} <ModalOverlay/>
isOpen={isOpen}
onClose={onClose}
scrollBehavior={"inside"}
>
<ModalOverlay />
<ModalContent> <ModalContent>
<ModalHeader>Modification des images</ModalHeader> <ModalHeader>Modification des images</ModalHeader>
<ModalCloseButton /> <ModalCloseButton/>
<ModalBody> <ModalBody>
<Grid <Grid templateColumns={`repeat(${listImage.length + 1}, 1fr)`}
templateColumns={`repeat(${listImage.length + 1}, 1fr)`} gap={5}>
gap={5}
>
{listImage.map((image, index) => ( {listImage.map((image, index) => (
<GridItem key={index}> <GridItem key={index}>
<Flex direction={"column"} gap={"1rem"}> <Flex direction={'column'} gap={'1rem'}>
<Image src={image} /> <Image src={image}/>
<Button <Button id={index.toString()} colorScheme={'red'}
id={"" + index} onClick={() =>
colorScheme={"red"} deleteImage(`${listImage[index]}`)}>
onClick={(e) => {
deleteImage(`${listImage[index]}`);
}}
>
Supprimer l'image Supprimer l'image
</Button> </Button>
</Flex> </Flex>
</GridItem> </GridItem>
))} ))}
{listImage.length < 5 ? ( {listImage.length < 5 && (
<GridItem width={"100%"}> <GridItem width={'100%'}>
<Input <Input type={'file'} height={'100%'}
type={"file"} accept={'image/png, image/jpeg, image/webp'}
height={"100%"} onInput={({target}) => {
accept={"image/png, image/jpeg, image/webp"}
onInput={({ target }) => {
const date = new Date(); const date = new Date();
const time = date.getTime(); const time = date.getTime();
const file = target.files[0]; const file = target.files[0];
const extension = file.name.split(".").pop(); const extension = file.name.split('.').pop();
const newFile = new File( const newFile = new File(
[file], [file],
`${user.id}_${time}.${extension}`, `${user.id}_${time}.${extension}`,
{ {
type: file.type, type: file.type,
} },
); );
uploadImage(newFile); uploadImage(newFile);
}} }}/>
></Input>
</GridItem> </GridItem>
) : (
<></>
)} )}
</Grid> </Grid>
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<Button <Button mr={3} onClick={() => {
colorScheme="purple" client.invalidateQueries('user');
mr={3}
onClick={(e) => {
client.invalidateQueries("user");
onClose(); onClose();
}} }}>
>
Save Save
</Button> </Button>
</ModalFooter> </ModalFooter>
@@ -5,7 +5,7 @@ export default function ProfileBadgeList(props) {
return ( return (
<Flex gap={"0.5rem"} mt={"1vh"} flexWrap="wrap"> <Flex gap={"0.5rem"} mt={"1vh"} flexWrap="wrap">
{userPassions.map((idPassion, index) => ( {userPassions.map((idPassion, index) => (
<Tag key={index} colorScheme={"purple"}> <Tag key={index}>
{passions !== null && passions !== undefined && passions.length > 0 {passions !== null && passions !== undefined && passions.length > 0
? passions.find((element) => element.id === idPassion).name ? passions.find((element) => element.id === idPassion).name
: ""} : ""}
+2 -1
View File
@@ -3,6 +3,7 @@ import type { AppProps } from "next/app";
import { ChakraProvider } from "@chakra-ui/react"; import { ChakraProvider } from "@chakra-ui/react";
import { SessionProvider } from "next-auth/react"; import { SessionProvider } from "next-auth/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import theme from '@/styles/theme';
const queryClient = new QueryClient(); const queryClient = new QueryClient();
@@ -12,7 +13,7 @@ export default function App({
}: AppProps) { }: AppProps) {
return ( return (
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<ChakraProvider> <ChakraProvider theme={theme}>
<SessionProvider session={session}> <SessionProvider session={session}>
<Component {...pageProps} /> <Component {...pageProps} />
</SessionProvider> </SessionProvider>
+29 -41
View File
@@ -1,91 +1,79 @@
import { import {
Box, Box,
Button, Button,
Flex,
FormControl, FormControl,
FormErrorMessage, FormErrorMessage,
FormLabel, FormLabel,
Input, Input,
useToast, useToast,
} from "@chakra-ui/react"; } from '@chakra-ui/react';
import { useSession } from "next-auth/react"; import {useSession} from 'next-auth/react';
import { useRouter } from "next/router"; import {useRouter} from 'next/router';
import { useState } from "react"; import {useState} from 'react';
import { useForm } from "react-hook-form"; import {useForm} from 'react-hook-form';
import LoadingPage from '@/components/LoadingPage';
import {Session} from '@/models/auth/Session';
export default function settings() { export default function settings() {
const router = useRouter(); const router = useRouter();
const toast = useToast({ position: "top", isClosable: true }); const toast = useToast({position: 'top', isClosable: true});
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const { const {
handleSubmit, handleSubmit,
register, register,
formState: { errors, isSubmitting }, formState: {errors, isSubmitting},
} = useForm(); } = useForm();
const [userData, setUserData] = useState({}); const [userData, setUserData] = useState({});
const {data: session, status} = useSession({required: true});
const { data: session, status } = useSession(); if (status === "loading") return <LoadingPage/>
if (status === "unauthenticated") router.push("/login"); if (status === 'authenticated') {
const {user} = session as unknown as Session;
if (status === "authenticated") { if (user.role !== 'ADMIN') router.push('/login');
const { user } = session as unknown as Session;
if (user.role !== "ADMIN") router.push("/login");
const savePassion = (passion: any) => { const savePassion = (passion: any) => {
const options = { const options = {
method: "POST", method: 'POST',
headers: { "Content-Type": "application/json" }, headers: {'Content-Type': 'application/json'},
body: JSON.stringify(passion), body: JSON.stringify(passion),
}; };
fetch(`/api/passions`, options) fetch(`/api/passions`, options)
.then((res) => { .then(res => {
setIsLoading(false); setIsLoading(false);
toast({ toast({
title: `Passion ajoutée`, title: `Passion ajoutée`,
status: "success", status: 'success',
}); });
}) })
.catch((err) => { .catch(err => {
setIsLoading(false); setIsLoading(false);
toast({ toast({
title: `Erreur lors de l'ajout`, title: `Erreur lors de l'ajout`,
status: "error", status: 'error',
}); });
}); });
}; };
return ( return (
<> <>
<Box <Box as="form" id="form_passion" width={'80%'}
as="form" onSubmit={handleSubmit(savePassion)}>
id="form_passion" <FormControl isInvalid={errors.name as boolean | undefined}>
width={"80%"}
onSubmit={handleSubmit(savePassion)}
>
<FormControl isInvalid={errors.name}>
<FormLabel htmlFor="passion">Passion</FormLabel> <FormLabel htmlFor="passion">Passion</FormLabel>
<Input <Input id="passion" placeholder="passion"
id="passion" {...register('name', {
placeholder="passion" required: 'This is required',
{...register("name", { })}/>
required: "This is required",
})}
/>
<FormErrorMessage> <FormErrorMessage>
{errors.name && errors.name.message} {errors.name && errors.name.message as string}
</FormErrorMessage> </FormErrorMessage>
</FormControl> </FormControl>
<Button <Button mt={4} isLoading={isSubmitting} type="submit">
mt={4}
colorScheme="purple"
isLoading={isSubmitting}
type="submit"
>
Submit Submit
</Button> </Button>
</Box> </Box>
+1 -1
View File
@@ -55,7 +55,7 @@ export default function ChatId() {
<MessageList user={session.user as User} messages={messages}/> <MessageList user={session.user as User} messages={messages}/>
<Flex gap={5} mt={5}> <Flex gap={5} mt={5}>
<Input type={"text"} colorScheme={'purple'} onChange={(evt) => setText(evt.target.value) } value={text} /> <Input type={"text"} colorScheme={'purple'} onChange={evt => setText(evt.target.value)} value={text} />
<Button colorScheme={'purple'} onClick={handleSubmit}>Envoyer</Button> <Button colorScheme={'purple'} onClick={handleSubmit}>Envoyer</Button>
</Flex> </Flex>
</Container> </Container>
+9 -6
View File
@@ -1,17 +1,20 @@
import {Button, Text} from '@chakra-ui/react'; import {Button} from '@chakra-ui/react';
import {useSession} from 'next-auth/react'; import {useSession} from 'next-auth/react';
import {useRouter} from 'next/router'; import {useRouter} from 'next/router';
export default function Chat() { export default function Chat() {
const router = useRouter(); const router = useRouter();
const {data: session, status} = useSession() const {data: session, status} = useSession();
if (session) if (session)
return ( return (
<> <>
{ {session.user.ChatID.map(
session.user.ChatID.map((id: string, index: number) => <Button key={index} onClick={() => router.push(`/chat/${id}`)} >{id}</Button>) (id: string, index: number) => (
} <Button key={index} onClick={() =>
router.push(`/chat/${id}`)}>{id}</Button>
),
)}
</> </>
) );
} }
+10 -5
View File
@@ -2,16 +2,21 @@ import Head from 'next/head';
import {websiteName} from '@/lib/constants'; import {websiteName} from '@/lib/constants';
import {useSession} from 'next-auth/react'; import {useSession} from 'next-auth/react';
import LoadingPage from '@/components/LoadingPage'; import LoadingPage from '@/components/LoadingPage';
import GettingStartForm from '@/components/form/GettingStartForm'; import GettingStartForm from '@/components/form/GettingStartForm/GettingStartForm';
import {store} from '@/redux/redux';
import {Provider} from 'react-redux';
import {User} from '@prisma/client';
export default function GettingStart() { export default function GettingStart() {
const {data: session, status} = useSession({required: true}) const {data: session, status} = useSession({required: true});
if (status === "loading") return <LoadingPage/> if (status === 'loading') return <LoadingPage/>;
return ( return (
<> <>
<Head><title>{websiteName}</title></Head> <Head><title>{websiteName}</title></Head>
<GettingStartForm user={session.user}/> <Provider store={store}>
<GettingStartForm user={session.user as User}/>
</Provider>
</> </>
) );
} }
+47
View File
@@ -0,0 +1,47 @@
import {configureStore, createSlice} from '@reduxjs/toolkit';
import {Gender} from '@prisma/client';
import {rootReducer} from '@reduxjs/toolkit/src/tests/injectableCombineReducers.example';
const questions: string[] = [
'Salut, je suis Cupibot. Il me manque quelques détails avant de terminer ton inscription. C\'est quoi ton sexe ?',
'Ok et tu es attiré par quel genre ?',
'Ça marche, c\'est quoi la tranche d\'âge que tu recherche ?',
'D\'ailleurs je ne t\'ai pas demandé, quand est-ce que tu est né ?',
'T\'as des passions dans la vie ? (Choisis-en 3 minimum)',
'Ok j\'ai toutes les infos d\'on j\'ai besoin, je vais tout faire pour te faire trouver la personne qui te correspond!',
];
const gettingStartFormSlice = createSlice({
name: 'gettingStartForm',
initialState: {
currentStep: 0,
inputs: {
ownGender: Gender.UNKNOWN,
researchGender: Gender.UNKNOWN,
ages: [18, 25],
birth: "",
passions: [],
},
messages: [
{text: questions[0], user: 'bot'},
],
},
reducers: {
setInput: (state, action) => {
// {type: "SET_INPUT", payload: {inputs: {ownGender: Gender.Male}, message: "Homme"} }
state.currentStep++;
state.inputs = {...state.inputs, ...action.payload.inputs};
state.messages.push({text: action.payload.message, user: 'me'},
{text: questions[state.currentStep], user: 'bot'});
},
},
});
export const store = configureStore({
reducer: {
gettingStartForm: gettingStartFormSlice.reducer,
},
});
export type RootState = ReturnType<typeof rootReducer>
+9
View File
@@ -0,0 +1,9 @@
import {extendTheme, withDefaultColorScheme} from '@chakra-ui/react';
const theme = extendTheme(
{},
withDefaultColorScheme({ colorScheme: 'purple' })
);
export default theme;
+62 -20
View File
@@ -35,7 +35,7 @@
chalk "^2.0.0" chalk "^2.0.0"
js-tokens "^4.0.0" js-tokens "^4.0.0"
"@babel/runtime@^7.0.0", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.7", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.9.2": "@babel/runtime@^7.0.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.7", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.9.2":
version "7.21.0" version "7.21.0"
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz"
integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw== integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==
@@ -249,6 +249,13 @@
dependencies: dependencies:
"@chakra-ui/shared-utils" "2.0.5" "@chakra-ui/shared-utils" "2.0.5"
"@chakra-ui/icons@^2.0.19":
version "2.0.19"
resolved "https://registry.npmjs.org/@chakra-ui/icons/-/icons-2.0.19.tgz"
integrity sha512-0A6U1ZBZhLIxh3QgdjuvIEhAZi3B9v8g6Qvlfa3mu6vSnXQn2CHBZXmJwxpXxO40NK/2gj/gKXrLeUaFR6H/Qw==
dependencies:
"@chakra-ui/icon" "3.0.16"
"@chakra-ui/image@2.0.15": "@chakra-ui/image@2.0.15":
version "2.0.15" version "2.0.15"
resolved "https://registry.npmjs.org/@chakra-ui/image/-/image-2.0.15.tgz" resolved "https://registry.npmjs.org/@chakra-ui/image/-/image-2.0.15.tgz"
@@ -1311,15 +1318,15 @@
resolved "https://registry.npmjs.org/@react-leaflet/core/-/core-2.1.0.tgz" resolved "https://registry.npmjs.org/@react-leaflet/core/-/core-2.1.0.tgz"
integrity sha512-Qk7Pfu8BSarKGqILj4x7bCSZ1pjuAPZ+qmRwH5S7mDS91VSbVVsJSrW4qA+GPrro8t69gFYVMWb1Zc4yFmPiVg== integrity sha512-Qk7Pfu8BSarKGqILj4x7bCSZ1pjuAPZ+qmRwH5S7mDS91VSbVVsJSrW4qA+GPrro8t69gFYVMWb1Zc4yFmPiVg==
"@reduxjs/toolkit@^1.9.3": "@reduxjs/toolkit@^1.9.5":
version "1.9.3" version "1.9.5"
resolved "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.3.tgz" resolved "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.5.tgz"
integrity sha512-GU2TNBQVofL09VGmuSioNPQIu6Ml0YLf4EJhgj0AvBadRlCGzUWet8372LjvO4fqKZF2vH1xU0htAa7BrK9pZg== integrity sha512-Rt97jHmfTeaxL4swLRNPD/zV4OxTes4la07Xc4hetpUW/vc75t5m1ANyxG6ymnEQ2FsLQsoMlYB2vV1sO3m8tQ==
dependencies: dependencies:
immer "^9.0.16" immer "^9.0.21"
redux "^4.2.0" redux "^4.2.1"
redux-thunk "^2.4.2" redux-thunk "^2.4.2"
reselect "^4.1.7" reselect "^4.1.8"
"@rushstack/eslint-patch@^1.1.3": "@rushstack/eslint-patch@^1.1.3":
version "1.2.0" version "1.2.0"
@@ -1409,6 +1416,14 @@
dependencies: dependencies:
"@types/ms" "*" "@types/ms" "*"
"@types/hoist-non-react-statics@^3.3.1":
version "3.3.1"
resolved "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz"
integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
dependencies:
"@types/react" "*"
hoist-non-react-statics "^3.3.0"
"@types/json5@^0.0.29": "@types/json5@^0.0.29":
version "0.0.29" version "0.0.29"
resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz"
@@ -1451,14 +1466,14 @@
resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz" resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz"
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
"@types/react-dom@18.0.11": "@types/react-dom@^16.8 || ^17.0 || ^18.0", "@types/react-dom@18.0.11":
version "18.0.11" version "18.0.11"
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.11.tgz" resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.11.tgz"
integrity sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw== integrity sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==
dependencies: dependencies:
"@types/react" "*" "@types/react" "*"
"@types/react@*", "@types/react@^16.8.0 || ^17.0.0 || ^18.0.0", "@types/react@^16.9.0 || ^17.0.0 || ^18.0.0", "@types/react@18.0.28": "@types/react@*", "@types/react@^16.8 || ^17.0 || ^18.0", "@types/react@^16.8.0 || ^17.0.0 || ^18.0.0", "@types/react@^16.9.0 || ^17.0.0 || ^18.0.0", "@types/react@18.0.28":
version "18.0.28" version "18.0.28"
resolved "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz" resolved "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz"
integrity sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew== integrity sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==
@@ -1477,6 +1492,11 @@
resolved "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz" resolved "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz"
integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ== integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==
"@types/use-sync-external-store@^0.0.3":
version "0.0.3"
resolved "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz"
integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==
"@typescript-eslint/parser@^5.42.0": "@typescript-eslint/parser@^5.42.0":
version "5.57.1" version "5.57.1"
resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.57.1.tgz" resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.57.1.tgz"
@@ -3110,7 +3130,7 @@ hexoid@^1.0.0:
resolved "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz" resolved "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz"
integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g== integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==
hoist-non-react-statics@^3.3.1: hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2:
version "3.3.2" version "3.3.2"
resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz" resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
@@ -3161,7 +3181,7 @@ ignore@^5.2.0:
resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz" resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz"
integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==
immer@^9.0.16: immer@^9.0.21:
version "9.0.21" version "9.0.21"
resolved "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz" resolved "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz"
integrity sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA== integrity sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==
@@ -3745,6 +3765,11 @@ mkdirp@^1.0.3:
resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz" resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
moment@^2.29.4:
version "2.29.4"
resolved "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz"
integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==
ms@^2.1.1, ms@2.1.2: ms@^2.1.1, ms@2.1.2:
version "2.1.2" version "2.1.2"
resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"
@@ -4296,7 +4321,7 @@ react-clientside-effect@^1.2.6:
dependencies: dependencies:
"@babel/runtime" "^7.12.13" "@babel/runtime" "^7.12.13"
"react-dom@^16.8.0 || ^17.0.0 || ^18.0.0", "react-dom@^17.0.2 || ^18", react-dom@^18.0.0, react-dom@^18.2.0, react-dom@>=18, react-dom@18.2.0: "react-dom@^16.8 || ^17.0 || ^18.0", "react-dom@^16.8.0 || ^17.0.0 || ^18.0.0", "react-dom@^17.0.2 || ^18", react-dom@^18.0.0, react-dom@^18.2.0, react-dom@>=18, react-dom@18.2.0:
version "18.2.0" version "18.2.0"
resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz" resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz"
integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
@@ -4336,6 +4361,11 @@ react-is@^16.13.1, react-is@^16.7.0:
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-is@^18.0.0:
version "18.2.0"
resolved "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz"
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
react-leaflet@^4.0.0, react-leaflet@^4.2.1: react-leaflet@^4.0.0, react-leaflet@^4.2.1:
version "4.2.1" version "4.2.1"
resolved "https://registry.npmjs.org/react-leaflet/-/react-leaflet-4.2.1.tgz" resolved "https://registry.npmjs.org/react-leaflet/-/react-leaflet-4.2.1.tgz"
@@ -4352,6 +4382,18 @@ react-query@^3.39.3:
broadcast-channel "^3.4.1" broadcast-channel "^3.4.1"
match-sorter "^6.0.2" match-sorter "^6.0.2"
"react-redux@^7.2.1 || ^8.0.2", react-redux@^8.0.5:
version "8.0.5"
resolved "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz"
integrity sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==
dependencies:
"@babel/runtime" "^7.12.1"
"@types/hoist-non-react-statics" "^3.3.1"
"@types/use-sync-external-store" "^0.0.3"
hoist-non-react-statics "^3.3.2"
react-is "^18.0.0"
use-sync-external-store "^1.0.0"
react-remove-scroll-bar@^2.3.3: react-remove-scroll-bar@^2.3.3:
version "2.3.4" version "2.3.4"
resolved "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz" resolved "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz"
@@ -4380,7 +4422,7 @@ react-style-singleton@^2.2.1:
invariant "^2.2.4" invariant "^2.2.4"
tslib "^2.0.0" tslib "^2.0.0"
react@*, "react@^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17 || ^18", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.9.0 || ^17.0.0 || ^18", "react@^17.0.2 || ^18", react@^18.0.0, react@^18.2.0, "react@>= 16.8.0 || 17.x.x || ^18.0.0-0", react@>=16.8.0, react@>=18, react@18.2.0: react@*, "react@^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", "react@^16.8 || ^17.0 || ^18.0", "react@^16.8.0 || ^17 || ^18", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.9.0 || ^17.0.0 || ^18", "react@^17.0.2 || ^18", react@^18.0.0, react@^18.2.0, "react@>= 16.8.0 || 17.x.x || ^18.0.0-0", react@>=16.8.0, react@>=18, react@18.2.0:
version "18.2.0" version "18.2.0"
resolved "https://registry.npmjs.org/react/-/react-18.2.0.tgz" resolved "https://registry.npmjs.org/react/-/react-18.2.0.tgz"
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
@@ -4453,7 +4495,7 @@ redux-thunk@^2.4.2:
resolved "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz" resolved "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz"
integrity sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q== integrity sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==
redux@^4, redux@^4.2.0: redux@^4, redux@^4.2.1:
version "4.2.1" version "4.2.1"
resolved "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz" resolved "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz"
integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w== integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==
@@ -4489,10 +4531,10 @@ replace-string@3.1.0:
resolved "https://registry.npmjs.org/replace-string/-/replace-string-3.1.0.tgz" resolved "https://registry.npmjs.org/replace-string/-/replace-string-3.1.0.tgz"
integrity sha512-yPpxc4ZR2makceA9hy/jHNqc7QVkd4Je/N0WRHm6bs3PtivPuPynxE5ejU/mp5EhnCv8+uZL7vhz8rkluSlx+Q== integrity sha512-yPpxc4ZR2makceA9hy/jHNqc7QVkd4Je/N0WRHm6bs3PtivPuPynxE5ejU/mp5EhnCv8+uZL7vhz8rkluSlx+Q==
reselect@^4.1.7: reselect@^4.1.8:
version "4.1.7" version "4.1.8"
resolved "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz" resolved "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz"
integrity sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A== integrity sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==
resolve-from@^4.0.0: resolve-from@^4.0.0:
version "4.0.0" version "4.0.0"
@@ -5187,7 +5229,7 @@ use-sidecar@^1.1.2:
detect-node-es "^1.1.0" detect-node-es "^1.1.0"
tslib "^2.0.0" tslib "^2.0.0"
use-sync-external-store@^1.2.0: use-sync-external-store@^1.0.0, use-sync-external-store@^1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz" resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==