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"
+9 -10
View File
@@ -18,18 +18,17 @@ 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())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
// Les préférences de l'utilisateur // Les préférences de l'utilisateur
distance Int @default(100) distance Int @default(100)
ageMax Int @default(99) ageMax Int @default(99)
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
@@ -47,15 +46,15 @@ model User {
UserLikes User[] @relation("Likes", fields: [UserLikesID], references: [id]) UserLikes User[] @relation("Likes", fields: [UserLikesID], references: [id])
// Les personnes qui aiment l'utilisateur // Les personnes qui aiment l'utilisateur
OtherUserLikesID String[] @db.ObjectId OtherUserLikesID String[] @db.ObjectId
OtherUserLikes User[] @relation("Likes", fields: [OtherUserLikesID], references: [id]) OtherUserLikes User[] @relation("Likes", fields: [OtherUserLikesID], references: [id])
// Les personnes que l'utilisateur a dislike // Les personnes que l'utilisateur a dislike
UserDislikesID String[] @db.ObjectId UserDislikesID String[] @db.ObjectId
UserDislikes User[] @relation("Dislikes", fields: [UserDislikesID], references: [id]) UserDislikes User[] @relation("Dislikes", fields: [UserDislikesID], references: [id])
// Les personnes qui aiment l'utilisateur // Les personnes qui aiment l'utilisateur
OtherUserDislikesID String[] @db.ObjectId OtherUserDislikesID String[] @db.ObjectId
OtherUserDislikes User[] @relation("Dislikes", fields: [OtherUserDislikesID], references: [id]) OtherUserDislikes User[] @relation("Dislikes", fields: [OtherUserDislikesID], references: [id])
MatchID String[] @db.ObjectId MatchID String[] @db.ObjectId
@@ -135,4 +134,4 @@ enum NotificationType {
enum Role { enum Role {
USER USER
ADMIN ADMIN
} }
+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>
+17 -36
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,41 +18,22 @@ 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]} </IconButton>
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 aria-label="left-arrow" borderRadius="full" onClick={handleClickNext}>
aria-label="left-arrow" <BiRightArrowAlt/>
colorScheme="purple" </IconButton>
borderRadius="full" </Flex>
onClick={handleClickNext}
>
<BiRightArrowAlt />
</IconButton>
</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>
); );
} }
+53 -67
View File
@@ -6,87 +6,73 @@ 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>
</>
);
return (
<Flex gap={5} justify={'center'} flexBasis={'100%'}>
{status === 'authenticated'
? authenticatedLinks
: <></>
}
</Flex>
);
};
const RightSection = () => { const RightSection = () => {
if (status === "authenticated" && session.user)
return ( const authenticatedButtons = (
<Flex justify={"right"} flexBasis={"100%"}> <Button onClick={() => signOut()}>Déconnexion</Button>
);
const unauthenticatedButtons = (
<>
<Button onClick={() => router.push('/register')}>Inscription</Button>
<Button onClick={() => router.push('/login')}>Connexion</Button>
</>
);
return (
<Flex justify={'right'} flexBasis={'100%'}>
<ButtonGroup> <ButtonGroup>
<Button colorScheme={"purple"} onClick={() => signOut()}> {status === 'authenticated'
Déconnexion ? authenticatedButtons
</Button> : unauthenticatedButtons
}
</ButtonGroup> </ButtonGroup>
</Flex> </Flex>
); );
else
return (
<Flex justify={"right"} flexBasis={"100%"}>
<ButtonGroup>
<Button onClick={() => router.push("/register")}>
Inscription
</Button>
<Button
colorScheme={"purple"}
onClick={() => router.push("/login")}
>
Connexion
</Button>
</ButtonGroup>
</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"} </Box>
backdropBlur={"20px"}
px={10}
py={2}
>
<Flex align={"center"}>
<Box flexBasis={"100%"}>
<Image src={"/logo.svg"} h={"3rem"} objectFit={"contain"} />
</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> </Flex>
</Box>
<RightSection />
</Flex>
</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>
+12 -11
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) => {
<Flex direction={'column'} gap={1}> return (
{messages.map((message, index) => <Flex direction={'column'} gap={1}>
<Message key={index} {messages.map((message, index) => <Message
align={user.id === message.UserID ? 'right' : 'left'} align={user.id === message.UserID ? 'right' : 'left'} key={index}
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>
</>
);
}
+73 -81
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,92 +24,84 @@ 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
</Heading>
{/*Email*/}
<FormControl mb={"1rem"} id={"email"} isInvalid={errors.email}>
<FormLabel>Adresse email</FormLabel>
<Input
type="text"
{...register("email", {
required: { value: true, message: "Adresse email requise" },
pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: "Adresse email invalide",
},
})}
placeholder="adresse@email.com"
/>
<FormErrorMessage>{errors.email?.message}</FormErrorMessage>
</FormControl>
{/*Mot de passe*/}
<FormControl
mb={"1rem"}
id="password"
isInvalid={errors.password || wrongPasswordError}
>
<FormLabel>Mot de passe</FormLabel>
<Input
type="password"
placeholder="Mot de passe"
{...register("password", {
required: { value: true, message: "Mot de passe requis" },
})}
/>
<FormErrorMessage>{errors.password?.message}</FormErrorMessage>
<FormErrorMessage>
{wrongPasswordError && errorPassword}
</FormErrorMessage>
</FormControl>
{/*Boutons*/}
<Flex
justify={"center"}
mt={"50px"}
gap={5}
justifyContent={"space-between"}
>
<Button onClick={() => router.push("/")} w={buttonWidth}>
Retour
</Button>
<Button
isLoading={isLoading}
w={buttonWidth}
colorScheme="purple"
type="submit"
>
Connexion Connexion
</Button> </Heading>
</Flex>
</form> {/*Email*/}
</Container> <FormControl mb={'1rem'} id={'email'}
</Box> isInvalid={errors.email as boolean | undefined}>
<FormLabel>Adresse email</FormLabel>
<Input
type="text"
{...register('email', {
required: {value: true, message: 'Adresse email requise'},
pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: 'Adresse email invalide',
},
})}
placeholder="adresse@email.com"
/>
<FormErrorMessage>{errors.email?.message as string}</FormErrorMessage>
</FormControl>
{/*Mot de passe*/}
<FormControl
mb={'1rem'}
id="password"
isInvalid={errors.password as boolean | undefined ||
wrongPasswordError}
>
<FormLabel>Mot de passe</FormLabel>
<Input
type="password"
placeholder="Mot de passe"
{...register('password', {
required: {value: true, message: 'Mot de passe requis'},
})}
/>
<FormErrorMessage>{errors.password?.message as string}</FormErrorMessage>
<FormErrorMessage>{wrongPasswordError &&
errorPassword}</FormErrorMessage>
</FormControl>
{/*Boutons*/}
<Flex justify={'center'} mt={'50px'} gap={5}
justifyContent={'space-between'}>
<Button onClick={() => router.push('/')} w={buttonWidth}>
Retour
</Button>
<Button isLoading={isLoading} w={buttonWidth} type="submit">
Connexion
</Button>
</Flex>
</form>
</Container>
</Box>
); );
} }
+66 -91
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" {...register('firstName', {
placeholder="Prénom" required: {value: true, message: 'Prénom requis'},
{...register('firstName', { })}/>
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" {...register('lastName', {
placeholder="Nom" required: {value: true, message: 'Nom requis'},
{...register('lastName', { })}/>
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: {
required: {value: true, message: 'Adresse email requise'}, value: true,
pattern: { message: 'Adresse email requise',
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i, },
message: 'Adresse email invalide', pattern: {
}, value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
})} 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) &&
/> <FormErrorMessage>{errors.birthdate.message as string}</FormErrorMessage>}
{ (errors.birthdate) ? <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" {...register('password', {
placeholder="Mot de passe" required: {value: true, message: 'Mot de passe requis'},
{...register('password', { })}/>
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" {...register('confirmPassword', {
placeholder="Mot de passe" required: {value: true, message: 'Confirmation requise'},
{...register('confirmPassword', { validate: (value: string) => (
required: {value: true, message: 'Confirmation requise'}, watch('password') === value ||
validate: (value: string) => { 'Les mots de passe ne correspondent pas'
return ( ),
watch('password') === value || })}/>
'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,93 +114,72 @@ 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 /> </Flex>
</IconButton>
<IconButton
aria-label="dislike"
borderRadius={"1rem"}
colorScheme={"purple"}
variant={"outline"}
onClick={() => {
dislikeMutation.mutate(listUsers[0].id);
}}
>
<RxCross1 />
</IconButton>
</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>
)} </CardBody>
</Box> </Card>
</CardBody>
</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"} {listPassions?.find((passion) => passion.id === passionID)?.name}
colorScheme={"purple"} </Tag>
>
{listPassions?.find((passion) => passion.id === passionID)?.name}
</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,93 +1,67 @@
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} <Box>
padding={"0px"} <Image src={user.images[0] ?? '/blank_profile_picture.webp'}
bg={"#faf9ff"} objectFit={'contain'} borderRadius={'1rem'}/>
> <Box mt={'2rem'}>
<Flex direction={"column"} height={"100%"} margin={"10%"}> <Flex align={'center'} justifyContent="space-between" mb={'1rem'}>
<Box> <Text fontSize={'1.5rem'} fontWeight={'bold'}>
{user.images.length === 0 ? ( {user.firstName} {user.lastName}
<Image src={"/blank_profile_picture.webp"} borderRadius={"1rem"} /> </Text>
) : ( <Text fontSize={'1rem'} fontWeight={'bold'}>
<Image {formateDate(user.birthdate)}
src={user.images[0]} </Text>
borderRadius={"1rem"} </Flex>
objectFit={"contain"} <Divider mb={'1rem'}/>
width={"100%"} <Text as="i" fontWeight={'bold'}>&quot;{user.bio}&quot;</Text>
/> </Box>
)}
<Box mt={"2rem"}>
<Flex align={"center"} justifyContent="space-between" mb={"1rem"}>
<Text fontSize={"1.5rem"} fontWeight={"bold"}>
{user.firstName} {user.lastName}
</Text>
<Text fontSize={"1rem"} fontWeight={"bold"}>
{formateDate(user.birthdate)}
</Text>
</Flex>
<Divider colorScheme={"purple"} mb={"1rem"} />
<Text as="i" fontWeight={"bold"}>
&quot;{user.bio}&quot;
</Text>
</Box> </Box>
</Box> <Spacer/>
<Spacer /> <Flex gap={2} direction={'column'} mt={'1vh'}
<Flex spacing={3.5} alignContent={'bottom'}>
gap={2} <LeftPanelButton leftIcon={<FaMapMarkedAlt/>}
direction={"column"} onClickHandler={() => router.push('/map')}>
mt={"1vh"} Carte
spacing={3.5} </LeftPanelButton>
alignContent={"bottom"}
> <LeftPanelButton leftIcon={<AiFillMessage/>}
<LeftPanelButton onClickHandler={() => router.push('/dashboard')}>
leftIcon={<FaMapMarkedAlt />} Messages
onClickHandler={() => router.push("/map")} </LeftPanelButton>
>
Carte <LeftPanelButton leftIcon={<BsFillPersonFill/>}
</LeftPanelButton> onClickHandler={() => router.push('/profile')}>
<LeftPanelButton Profil
leftIcon={<AiFillMessage />} </LeftPanelButton>
onClickHandler={() => router.push("/dashboard")} <LeftPanelButton variant={'outline'} leftIcon={<BiLogOut/>}
> onClickHandler={() => signOut({callbackUrl: '/'})}>
Messages Déconnexion
</LeftPanelButton> </LeftPanelButton>
<LeftPanelButton </Flex>
leftIcon={<BsFillPersonFill />}
onClickHandler={() => router.push("/profile")}
>
Profile
</LeftPanelButton>
<LeftPanelButton
variant={"outline"}
leftIcon={<BiLogOut />}
onClickHandler={() => signOut({ callbackUrl: "/" })}
>
Deconnexion
</LeftPanelButton>
</Flex> </Flex>
</Flex> </Card>
</Card>
); );
} }
@@ -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'}>
+45 -54
View File
@@ -1,69 +1,60 @@
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],
}); });
return ( return (
<> <>
{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 center={location} zoom={13} minZoom={8}
<MapContainer zoomControl={false}
center={location} style={{width: '100vw', height: '100vw'}}>
zoom={13} <TileLayer
minZoom={8} url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
zoomControl={false} attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
style={{ />
width: "100vw", {/* Pour le user */}
height: "100vw", <Marker icon={userIcon} position={location}></Marker>
}}
>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
/>
{/* Pour le user */}
<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>
})} </MapContainer>
</MarkerClusterGroup> </Flex>
</MapContainer> )}
</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>
) : ( </chakra.label>
<Tag
{...getCheckboxProps()}
colorScheme={"purple"}
variant={"outline"}
{...getLabelProps()}
>
<TagLeftIcon as={IoAdd} />
<TagLabel>{text}</TagLabel>
</Tag>
)}
</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} <FormControl id={name} isInvalid={invalid}>
defaultValue={defaultValue} <FormLabel as="legend" htmlFor={name}>{label}</FormLabel>
render={({ field, fieldState: { invalid, error } }) => { <Editable{...field} id={name} fontWeight="bold"
return ( isDisabled={isDisabled}
<FormControl id={name} isInvalid={invalid}> placeholder={'Non renseigné'}
<FormLabel as="legend" htmlFor={name}> color={isDisabled ? 'gray.500' : undefined}>
{label} <EditablePreview/>
</FormLabel> <EditableInput/>
<Editable </Editable>
{...field} <FormHelperText>{helperText}</FormHelperText>
id={name} <FormErrorMessage as="b">{error?.message}</FormErrorMessage>
fontWeight="bold" </FormControl>
isDisabled={isDisabled} )}/>
placeholder={"Non renseigné"}
color={isDisabled ? "gray.500" : undefined}
>
<EditablePreview />
<EditableInput />
</Editable>
<FormHelperText>{helperText}</FormHelperText>
<FormErrorMessage as="b">{error?.message}</FormErrorMessage>
</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} <FormControl id={name} isInvalid={invalid}>
defaultValue={defaultValue} <FormLabel as="legend" htmlFor={name}>{label}</FormLabel>
render={({ field, fieldState: { invalid, error } }) => { <Editable{...field} id={name} fontWeight="bold"
return ( width={'100%'} isDisabled={isDisabled}
<FormControl id={name} isInvalid={invalid}> placeholder={'Non renseigné'}
<FormLabel as="legend" htmlFor={name}> color={isDisabled ? 'gray.500' : undefined}>
{label} <EditablePreview/>
</FormLabel> <EditableTextarea/>
<Editable </Editable>
{...field} <FormErrorMessage as="b">{error?.message}</FormErrorMessage>
id={name} </FormControl>
fontWeight="bold" )}/>
width={"100%"}
isDisabled={isDisabled}
placeholder={"Non renseigné"}
color={isDisabled ? "gray.500" : undefined}
>
<EditablePreview />
<EditableTextarea />
</Editable>
<FormErrorMessage as="b">{error?.message}</FormErrorMessage>
</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,23 +14,16 @@ 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} </Editable>
fontWeight="bold" <FormHelperText>{helperText}</FormHelperText>
isDisabled={true} </FormControl>
placeholder={"Non renseigné"}
color={"gray.500"}
>
<EditablePreview />
</Editable>
<FormHelperText>{helperText}</FormHelperText>
</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} </HStack>
colorScheme={"purple"} </RadioGroup>
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>
</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 {
@@ -34,60 +34,40 @@ export default function CustomRangeSlider(props: CustomRangeSliderProps) {
} = props; } = props;
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} max={99}
name={name} id={name} color={'pink.500'}
render={({ field: { onChange } }) => ( defaultValue={defaultValue}
<RangeSlider onChange={(v: [number, number]) => {
aria-label={["min", "max"]} setSliderAgeValue(v);
colorScheme={"purple"} onChange(v);
min={18} }}
max={99} onMouseEnter={() => setShowTooltipAge(true)}
id={name} onMouseLeave={() => setShowTooltipAge(false)}>
color={"pink.500"} <RangeSliderTrack>
defaultValue={defaultValue} <RangeSliderFilledTrack bgColor={'purple.500'}/>
onChange={(v: [number, number]) => { </RangeSliderTrack>
setSliderAgeValue(v); <Tooltip hasArrow bg="purple.500" color="white"
onChange(v); placement="top" isOpen={showTooltipAge}
}} label={`${sliderAgeValue[0]}`}>
onMouseEnter={() => setShowTooltipAge(true)} <RangeSliderThumb boxSize={6} index={0}>
onMouseLeave={() => setShowTooltipAge(false)} <Box color={'purple.500'} as={FaLessThan}/>
> </RangeSliderThumb>
<RangeSliderTrack> </Tooltip>
<RangeSliderFilledTrack bgColor={"purple.500"} />
</RangeSliderTrack>
<Tooltip
hasArrow
bg="purple.500"
color="white"
placement="top"
isOpen={showTooltipAge}
label={`${sliderAgeValue[0]}`}
>
<RangeSliderThumb boxSize={6} index={0}>
<Box color={"purple.500"} as={FaLessThan} />
</RangeSliderThumb>
</Tooltip>
<Tooltip <Tooltip hasArrow bg="purple.500" color="white"
hasArrow placement="top" isOpen={showTooltipAge}
bg="purple.500" label={`${sliderAgeValue[1]}`}>
color="white" <RangeSliderThumb boxSize={6} index={1}>
placement="top" <Box color={'purple.500'} as={FaGreaterThan}/>
isOpen={showTooltipAge} </RangeSliderThumb>
label={`${sliderAgeValue[1]}`} </Tooltip>
> </RangeSlider>
<RangeSliderThumb boxSize={6} index={1}> )}/>
<Box color={"purple.500"} as={FaGreaterThan} /> </>
</RangeSliderThumb>
</Tooltip>
</RangeSlider>
)}
/>
</>
); );
} }
+30 -46
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 {
@@ -34,47 +34,31 @@ export default function CustomSlider(props: CustomSliderProps) {
} = props; } = props;
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} defaultValue={defaultValue}
render={({ field: { onChange } }) => ( onChange={v => {
<Slider setSliderDistanceValue(v);
aria-label={"distance"} onChange(v);
colorScheme={"purple"} }}
min={20} onMouseEnter={() => setShowTooltipDistance(true)}
max={250} onMouseLeave={() => setShowTooltipDistance(false)}>
id={"distance"} <SliderTrack>
color={"pink.500"} <SliderFilledTrack bgColor={'purple.500'}/>
defaultValue={defaultValue} </SliderTrack>
onChange={(v) => { <Tooltip hasArrow bg="purple.500" color="white"
setSliderDistanceValue(v); placement="top" isOpen={showTooltipDistance}
onChange(v); label={`${sliderDistanceValue} km`}>
}} <SliderThumb boxSize={6}>
onMouseEnter={() => setShowTooltipDistance(true)} <Box color={'purple.500'} as={FaWalking}/>
onMouseLeave={() => setShowTooltipDistance(false)} </SliderThumb>
> </Tooltip>
<SliderTrack> </Slider>
<SliderFilledTrack bgColor={"purple.500"} /> )}/>
</SliderTrack> </>
<Tooltip
hasArrow
bg="purple.500"
color="white"
placement="top"
isOpen={showTooltipDistance}
label={`${sliderDistanceValue} km`}
>
<SliderThumb boxSize={6}>
<Box color={"purple.500"} as={FaWalking} />
</SliderThumb>
</Tooltip>
</Slider>
)}
/>
</>
); );
} }
@@ -13,104 +13,80 @@ 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} </Button>
leftIcon={<RiEditBoxLine />}
>
Choisir les centres d'intérèts
</Button>
<Modal <Modal blockScrollOnMount={false} size={'4xl'} isOpen={isOpen}
blockScrollOnMount={false} onClose={onClose} scrollBehavior={'inside'}>
size={"4xl"} <ModalOverlay/>
isOpen={isOpen} <ModalContent>
onClose={onClose} <ModalHeader>Choix des centres d'intérêts</ModalHeader>
scrollBehavior={"inside"} <ModalCloseButton/>
> <ModalBody>
<ModalOverlay /> <Flex gap={'1rem'} flexWrap="wrap">
<ModalContent> {passions && (
<ModalHeader>Choix des centres d'intérèts</ModalHeader> passions.map((passion: Passion) => (
<ModalCloseButton /> <CustomCheckbox key={passion.id} text={passion.name}
<ModalBody> {...getCheckboxProps({value: passion.id})}/>
<Flex gap={"1rem"} flexWrap="wrap"> )
{passions !== null ? ( )
passions.map((passion, index) => { )}
return ( </Flex>
<CustomCheckbox </ModalBody>
{...getCheckboxProps({ value: passion.id })}
key={passion.id}
text={passion.name}
/>
);
})
) : (
<></>
)}
</Flex>
</ModalBody>
<ModalFooter> <ModalFooter>
<Button <Button mr={3} onClick={() => savePassions(value)}>
colorScheme="purple" Save
mr={3} </Button>
onClick={() => { </ModalFooter>
savePassions(value); </ModalContent>
}} </Modal>
> </>
Save
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</>
); );
} }
@@ -13,66 +13,65 @@ 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}`]);
// router.reload(); // router.reload();
}) })
.catch(() => { .catch(() => {
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 => {
toast({
title: `Erreur lors de l'ajout des images`,
status: 'error',
isClosable: true,
}); });
}) console.log(err);
.catch((err) => {
toast({
title: `Erreur lors de l'ajout des images`,
status: "error",
isClosable: true,
}); });
console.log(err);
});
}; };
const deleteImage = async (fileName) => { const deleteImage = async (fileName) => {
@@ -85,121 +84,97 @@ 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],
}), }),
}; };
fetch(`/api/users/${user.id}`, imagePatchOptions) fetch(`/api/users/${user.id}`, imagePatchOptions)
.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(() => {
toast({
title: `Erreur lors de la suppression des images`,
status: 'error',
isClosable: true,
});
}); });
})
.catch(() => {
toast({
title: `Erreur lors de la suppression des images`,
status: "error",
isClosable: true,
});
});
}); });
}; };
return ( return (
<> <>
<Button <Button onClick={onOpen} leftIcon={<RiEditBoxLine/>}>
colorScheme={"purple"} Modifier les images
onClick={onOpen} </Button>
leftIcon={<RiEditBoxLine />}
>
Modifier les images
</Button>
<Modal <Modal blockScrollOnMount={false} size={'4xl'} isOpen={isOpen}
blockScrollOnMount={false} onClose={onClose} scrollBehavior={'inside'}>
size={"4xl"} <ModalOverlay/>
isOpen={isOpen} <ModalContent>
onClose={onClose} <ModalHeader>Modification des images</ModalHeader>
scrollBehavior={"inside"} <ModalCloseButton/>
> <ModalBody>
<ModalOverlay /> <Grid templateColumns={`repeat(${listImage.length + 1}, 1fr)`}
<ModalContent> gap={5}>
<ModalHeader>Modification des images</ModalHeader> {listImage.map((image, index) => (
<ModalCloseButton /> <GridItem key={index}>
<ModalBody> <Flex direction={'column'} gap={'1rem'}>
<Grid <Image src={image}/>
templateColumns={`repeat(${listImage.length + 1}, 1fr)`} <Button id={index.toString()} colorScheme={'red'}
gap={5} onClick={() =>
> deleteImage(`${listImage[index]}`)}>
{listImage.map((image, index) => ( Supprimer l'image
<GridItem key={index}> </Button>
<Flex direction={"column"} gap={"1rem"}> </Flex>
<Image src={image} /> </GridItem>
<Button ))}
id={"" + index} {listImage.length < 5 && (
colorScheme={"red"} <GridItem width={'100%'}>
onClick={(e) => { <Input type={'file'} height={'100%'}
deleteImage(`${listImage[index]}`); accept={'image/png, image/jpeg, image/webp'}
}} onInput={({target}) => {
> const date = new Date();
Supprimer l'image const time = date.getTime();
</Button>
</Flex>
</GridItem>
))}
{listImage.length < 5 ? (
<GridItem width={"100%"}>
<Input
type={"file"}
height={"100%"}
accept={"image/png, image/jpeg, image/webp"}
onInput={({ target }) => {
const date = new Date();
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>
<></> </ModalBody>
)}
</Grid>
</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> </ModalContent>
</ModalContent> </Modal>
</Modal> </>
</>
); );
} }
@@ -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>
+47 -59
View File
@@ -1,95 +1,83 @@
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 => {
setIsLoading(false);
toast({
title: `Erreur lors de l'ajout`,
status: 'error',
});
}); });
})
.catch((err) => {
setIsLoading(false);
toast({
title: `Erreur lors de l'ajout`,
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%"} <FormLabel htmlFor="passion">Passion</FormLabel>
onSubmit={handleSubmit(savePassion)} <Input id="passion" placeholder="passion"
> {...register('name', {
<FormControl isInvalid={errors.name}> required: 'This is required',
<FormLabel htmlFor="passion">Passion</FormLabel> })}/>
<Input <FormErrorMessage>
id="passion" {errors.name && errors.name.message as string}
placeholder="passion" </FormErrorMessage>
{...register("name", { </FormControl>
required: "This is required", <Button mt={4} isLoading={isSubmitting} type="submit">
})} Submit
/> </Button>
<FormErrorMessage> </Box>
{errors.name && errors.name.message} </>
</FormErrorMessage>
</FormControl>
<Button
mt={4}
colorScheme="purple"
isLoading={isSubmitting}
type="submit"
>
Submit
</Button>
</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>
+12 -9
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==