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
# 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",
"version": "0.1.0",
"dependencies": {
"@chakra-ui/icons": "^2.0.19",
"@chakra-ui/react": "^2.5.1",
"@christopherpickering/react-leaflet-markercluster": "^1.1.0",
"@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6",
"@premieroctet/next-crud": "^2.2.0",
"@prisma/client": "^4.11.0",
"@reduxjs/toolkit": "^1.9.3",
"@reduxjs/toolkit": "^1.9.5",
"@tanstack/react-query": "^4.28.0",
"@types/react": "18.0.28",
"@types/react-dom": "18.0.11",
@@ -27,6 +28,7 @@
"fs": "^0.0.1-security",
"leaflet": "^1.9.3",
"leaflet.markercluster": "^1.5.3",
"moment": "^2.29.4",
"next": "13.2.3",
"next-auth": "^4.20.1",
"react": "18.2.0",
@@ -35,6 +37,7 @@
"react-icons": "^4.8.0",
"react-leaflet": "^4.2.1",
"react-query": "^3.39.3",
"react-redux": "^8.0.5",
"socket.io": "^4.6.1",
"socket.io-client": "^4.6.1",
"yup": "^1.1.1"
@@ -474,6 +477,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": {
"version": "2.0.15",
"resolved": "https://registry.npmjs.org/@chakra-ui/image/-/image-2.0.15.tgz",
@@ -2178,14 +2193,14 @@
}
},
"node_modules/@reduxjs/toolkit": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.3.tgz",
"integrity": "sha512-GU2TNBQVofL09VGmuSioNPQIu6Ml0YLf4EJhgj0AvBadRlCGzUWet8372LjvO4fqKZF2vH1xU0htAa7BrK9pZg==",
"version": "1.9.5",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.5.tgz",
"integrity": "sha512-Rt97jHmfTeaxL4swLRNPD/zV4OxTes4la07Xc4hetpUW/vc75t5m1ANyxG6ymnEQ2FsLQsoMlYB2vV1sO3m8tQ==",
"dependencies": {
"immer": "^9.0.16",
"redux": "^4.2.0",
"immer": "^9.0.21",
"redux": "^4.2.1",
"redux-thunk": "^2.4.2",
"reselect": "^4.1.7"
"reselect": "^4.1.8"
},
"peerDependencies": {
"react": "^16.9.0 || ^17.0.0 || ^18",
@@ -2326,6 +2341,15 @@
"@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": {
"version": "0.0.29",
"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",
"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": {
"version": "5.57.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.57.1.tgz",
@@ -5976,6 +6005,14 @@
"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": {
"version": "2.1.2",
"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": {
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz",
@@ -7340,9 +7420,9 @@
}
},
"node_modules/reselect": {
"version": "4.1.7",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz",
"integrity": "sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A=="
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz",
"integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="
},
"node_modules/resolve": {
"version": "1.22.2",
+4 -1
View File
@@ -9,13 +9,14 @@
"lint": "next lint"
},
"dependencies": {
"@chakra-ui/icons": "^2.0.19",
"@chakra-ui/react": "^2.5.1",
"@christopherpickering/react-leaflet-markercluster": "^1.1.0",
"@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6",
"@premieroctet/next-crud": "^2.2.0",
"@prisma/client": "^4.11.0",
"@reduxjs/toolkit": "^1.9.3",
"@reduxjs/toolkit": "^1.9.5",
"@tanstack/react-query": "^4.28.0",
"@types/react": "18.0.28",
"@types/react-dom": "18.0.11",
@@ -28,6 +29,7 @@
"fs": "^0.0.1-security",
"leaflet": "^1.9.3",
"leaflet.markercluster": "^1.5.3",
"moment": "^2.29.4",
"next": "13.2.3",
"next-auth": "^4.20.1",
"react": "18.2.0",
@@ -36,6 +38,7 @@
"react-icons": "^4.8.0",
"react-leaflet": "^4.2.1",
"react-query": "^3.39.3",
"react-redux": "^8.0.5",
"socket.io": "^4.6.1",
"socket.io-client": "^4.6.1",
"yup": "^1.1.1"
+1 -2
View File
@@ -18,7 +18,7 @@ model User {
bio String?
location String?
images String[]
birthdate DateTime?
birthdate DateTime? @db.Date
gender Gender @default(UNKNOWN)
role Role @default(USER)
createdAt DateTime @default(now())
@@ -30,7 +30,6 @@ model User {
ageMin Int @default(18)
prefGender Gender @default(UNKNOWN)
// Liste des chats de l'utilisateur
ChatID String[] @db.ObjectId
Chat Chat[] @relation(fields: [ChatID], references: [id])
+3 -15
View File
@@ -1,27 +1,15 @@
import { Box, Flex, Text, Button, Image } from "@chakra-ui/react";
import { useRouter } from "next/router";
export default function BottomBar(props) {
export default function BottomBar(props: { variant: any; saveData: any; }) {
const router = useRouter();
const { variant, saveData } = props;
return (
<Box
position={variant}
zIndex={0}
bottom={0}
backdropFilter={"auto"}
px={10}
py={2}
>
<Box position={variant} zIndex={0} bottom={0} backdropFilter={"auto"} px={10} py={2}>
<Flex align={"center"}>
<Button
colorScheme={"purple"}
onClick={() => {
saveData();
}}
>
<Button onClick={() => saveData()}>
Sauvegarder les modifications
</Button>
</Flex>
+11 -30
View File
@@ -1,6 +1,6 @@
import { useState } from "react";
import { Flex, IconButton } from "@chakra-ui/react";
import { BiLeftArrowAlt, BiRightArrowAlt } from "react-icons/bi";
import {useState} from 'react';
import {Flex, IconButton} from '@chakra-ui/react';
import {BiLeftArrowAlt, BiRightArrowAlt} from 'react-icons/bi';
interface Props {
images: string[];
@@ -18,38 +18,19 @@ const Carousel = ({ images, borderRadius: bRadius }: Props) => {
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 (
<Flex
px={2}
align="center"
borderRadius={bRadius}
overflow={"hidden"}
justify={"space-between"}
bgColor={"purple.50"}
bgImage={images[currentIndex]}
bgSize={"contain"}
bgRepeat={"no-repeat"}
bgPosition={"center"}
width={"100%"}
height={500}
>
<IconButton
aria-label="left-arrow"
colorScheme="purple"
borderRadius="full"
onClick={handleClickPrevious}
>
<Flex px={2} align="center" borderRadius={bRadius} overflow={'hidden'}
justify={'space-between'} bgColor={'purple.50'} height={500}
bgImage={images[currentIndex]} bgSize={'contain'}
bgRepeat={'no-repeat'} bgPosition={'center'} width={'100%'}>
<IconButton aria-label="left-arrow" borderRadius="full"
onClick={handleClickPrevious}>
<BiLeftArrowAlt/>
</IconButton>
<IconButton
aria-label="left-arrow"
colorScheme="purple"
borderRadius="full"
onClick={handleClickNext}
>
<IconButton aria-label="left-arrow" borderRadius="full" onClick={handleClickNext}>
<BiRightArrowAlt/>
</IconButton>
</Flex>
+3 -10
View File
@@ -6,20 +6,13 @@ export default function LoadingPage() {
const router = useRouter();
return (
<VStack
justifyContent="center"
alignItems="center"
height="100vh"
width="100vw"
bg="gray.100"
>
<VStack justifyContent="center" alignItems="center" height="100vh"
width="100vw" bg="gray.100">
<MdError color="purple.500" size="50%" />
<Text color="purple.500" fontSize="2xl" fontWeight="bold">
Veillez à autoriser la géolocalisation
</Text>
<Button colorScheme={"purple"} onClick={() => router.push("/")}>
Page d'accueuil
</Button>
<Button onClick={() => router.push("/")}>Page d'accueil</Button>
</VStack>
);
}
+3 -10
View File
@@ -6,20 +6,13 @@ export default function LoadingPage() {
const router = useRouter();
return (
<VStack
justifyContent="center"
alignItems="center"
height="100vh"
width="100vw"
bg="gray.100"
>
<VStack justifyContent="center" alignItems="center" height="100vh"
width="100vw" bg="gray.100">
<MdError color="purple.500" size="50%" />
<Text color="purple.500" fontSize="2xl" fontWeight="bold">
Une erreur est survenue
</Text>
<Button colorScheme={"purple"} onClick={() => router.push("/")}>
Page d'accueuil
</Button>
<Button onClick={() => router.push("/")}>Page d'accueil</Button>
</VStack>
);
}
+3 -14
View File
@@ -2,20 +2,9 @@ import { Flex, Spinner } from "@chakra-ui/react";
export default function LoadingPage() {
return (
<Flex
justifyContent="center"
alignItems="center"
height="100vh"
width="100vw"
bg="gray.100"
>
<Spinner
thickness="4px"
speed="0.65s"
emptyColor="gray.200"
color="purple.500"
size="xl"
/>
<Flex justifyContent="center" alignItems="center" height="100vh"
width="100vw" bg="gray.100">
<Spinner thickness="4px" speed="0.65s" emptyColor="gray.200" size="xl"/>
</Flex>
);
}
+45 -59
View File
@@ -6,85 +6,71 @@ import {
Button,
Image,
Link,
} from "@chakra-ui/react";
import { useRouter } from "next/router";
import { signOut, useSession } from "next-auth/react";
} from '@chakra-ui/react';
import {useRouter} from 'next/router';
import {signOut, useSession} from 'next-auth/react';
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 {data: session, status} = useSession();
const RightSection = () => {
if (status === "authenticated" && session.user)
const MiddleSection = () => {
const authenticatedLinks = (
<>
<Link href={'/dashboard'}>Tableau de bord</Link>
<Link href={'/profile'}>Profile</Link>
<Link href={'/map'}>Carte</Link>
</>
);
return (
<Flex justify={"right"} flexBasis={"100%"}>
<ButtonGroup>
<Button colorScheme={"purple"} onClick={() => signOut()}>
Déconnexion
</Button>
</ButtonGroup>
<Flex gap={5} justify={'center'} flexBasis={'100%'}>
{status === 'authenticated'
? authenticatedLinks
: <></>
}
</Flex>
);
else
};
const RightSection = () => {
const authenticatedButtons = (
<Button onClick={() => signOut()}>Déconnexion</Button>
);
const unauthenticatedButtons = (
<>
<Button onClick={() => router.push('/register')}>Inscription</Button>
<Button onClick={() => router.push('/login')}>Connexion</Button>
</>
);
return (
<Flex justify={"right"} flexBasis={"100%"}>
<Flex justify={'right'} flexBasis={'100%'}>
<ButtonGroup>
<Button onClick={() => router.push("/register")}>
Inscription
</Button>
<Button
colorScheme={"purple"}
onClick={() => router.push("/login")}
>
Connexion
</Button>
{status === 'authenticated'
? authenticatedButtons
: unauthenticatedButtons
}
</ButtonGroup>
</Flex>
);
};
return (
<Box
position={variant}
zIndex={9999}
top={0}
width={"100vw"}
backdropFilter={"auto"}
backdropBlur={"20px"}
px={10}
py={2}
>
<Flex align={"center"}>
<Box flexBasis={"100%"}>
<Image src={"/logo.svg"} h={"3rem"} objectFit={"contain"} />
<Box position={variant} zIndex={9999} top={0} width={'100vw'}
backdropFilter={'auto'} 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%"}>
{status === "authenticated" ? (
<>
<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>
<MiddleSection/>
<RightSection/>
</Flex>
</Box>
+1 -1
View File
@@ -8,7 +8,7 @@ type Props = {
const Message = ({message, align}: Props) => (
<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'}>
{message.text}
</Text>
+7 -6
View File
@@ -1,20 +1,21 @@
import type {Message as MessageType, User} from '@prisma/client';
import Message from '@/components/chat/Message';
import {Box, Flex} from '@chakra-ui/react';
import {Flex} from '@chakra-ui/react';
type Props = {
user: User
user: User,
messages: MessageType[]
}
const MessageList = ({messages, user}: Props) => (
const MessageList = ({messages, user}: Props) => {
return (
<Flex direction={'column'} gap={1}>
{messages.map((message, index) =>
<Message key={index}
align={user.id === message.UserID ? 'right' : 'left'}
{messages.map((message, index) => <Message
align={user.id === message.UserID ? 'right' : 'left'} key={index}
message={message}/>
)}
</Flex>
);
};
export default MessageList;
+2 -2
View File
@@ -32,8 +32,8 @@ export default function FormMessage(props: Props) {
return (
<Flex gap={5} mt={5}>
<Input type={text} colorScheme={'purple'} onChange={(evt) => setText(evt.target.value) } value={text} />
<Button colorScheme={'purple'} onClick={handleSubmit} >Envoyer</Button>
<Input type={text} onChange={evt => setText(evt.target.value) } value={text}/>
<Button onClick={handleSubmit}>Envoyer</Button>
</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>
</>
);
}
+31 -39
View File
@@ -8,13 +8,13 @@ import {
Input,
Container,
FormErrorMessage,
} from "@chakra-ui/react";
import { useState } from "react";
import { useRouter } from "next/router";
import { signIn, SignInResponse } from "next-auth/react";
} from '@chakra-ui/react';
import {useState} from 'react';
import {useRouter} from 'next/router';
import {signIn, SignInResponse} from 'next-auth/react';
import { LoginData } from "@/models/form/LoginData";
import { useForm } from "react-hook-form";
import {LoginData} from '@/models/form/LoginData';
import {useForm} from 'react-hook-form';
export default function LoginForm() {
const router = useRouter();
@@ -27,84 +27,76 @@ export default function LoginForm() {
formState: {errors, isSubmitting},
} = 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) => {
setIsLoading(true);
signIn("credentials", { ...value, redirect: false }).then(
signIn('credentials', {...value, redirect: false}).then(
(res: unknown) => {
const {ok: connexionSuccess} = res as SignInResponse;
setIsLoading(false);
if (connexionSuccess) router.push("/dashboard");
if (connexionSuccess) router.push('/dashboard');
else setWrongPasswordError(true);
}
},
);
};
return (
<Box flexBasis={"100%"}>
<Box flexBasis={'100%'}>
<Container>
<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}>
<FormControl mb={'1rem'} id={'email'}
isInvalid={errors.email as boolean | undefined}>
<FormLabel>Adresse email</FormLabel>
<Input
type="text"
{...register("email", {
required: { value: true, message: "Adresse email requise" },
{...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",
message: 'Adresse email invalide',
},
})}
placeholder="adresse@email.com"
/>
<FormErrorMessage>{errors.email?.message}</FormErrorMessage>
<FormErrorMessage>{errors.email?.message as string}</FormErrorMessage>
</FormControl>
{/*Mot de passe*/}
<FormControl
mb={"1rem"}
mb={'1rem'}
id="password"
isInvalid={errors.password || wrongPasswordError}
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" },
{...register('password', {
required: {value: true, message: 'Mot de passe requis'},
})}
/>
<FormErrorMessage>{errors.password?.message}</FormErrorMessage>
<FormErrorMessage>
{wrongPasswordError && errorPassword}
</FormErrorMessage>
<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}>
<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"
>
<Button isLoading={isLoading} w={buttonWidth} type="submit">
Connexion
</Button>
</Flex>
+43 -68
View File
@@ -10,7 +10,6 @@ import {
FormErrorMessage,
} from '@chakra-ui/react';
import {useRouter} from 'next/router';
import {useState} from 'react';
import {signIn, SignInResponse} from 'next-auth/react';
import {RegisterData} from '@/models/form/RegisterData';
import {SubmitHandler, useForm} from 'react-hook-form';
@@ -48,137 +47,113 @@ export default function RegisterForm() {
}),
};
fetch('/api/users', options).then(() => {
signIn('credentials', {email, password, redirect: false}).then(
(res: unknown) => {
fetch('/api/users', options)
.then(() => signIn('credentials', {email, password, redirect: false}))
.then((res: unknown) => {
const {ok: connexionSuccess} = res as SignInResponse;
// TODO If success -> goto interactive form else login
router.push(connexionSuccess ? '/dashboard' : '/');
router.push(connexionSuccess ? '/getting-start' : '/');
},
);
}).catch((err) => console.error(err));
)
.catch(err => console.error(err));
};
return (
<Box flexBasis={'100%'}>
<Container>
<form id="register_form" onSubmit={handleSubmit(submitHandler)}>
<Heading textAlign={'center'} size={'2xl'}>
Inscription
</Heading>
<Heading textAlign={'center'} size={'2xl'}>Inscription</Heading>
<Flex mt={'100px'} mb={'1rem'} gap={5}>
{/*Prénom*/}
<FormControl id="firstName" isInvalid={errors.firstName as boolean|undefined}>
<FormControl id="firstName"
isInvalid={errors.firstName as boolean | undefined}>
<FormLabel>Prénom</FormLabel>
<Input
type="text"
placeholder="Prénom"
<Input type="text" placeholder="Prénom"
{...register('firstName', {
required: {value: true, message: 'Prénom requis'},
})}
/>
})}/>
<FormErrorMessage>{errors.firstName?.message as string}</FormErrorMessage>
</FormControl>
{/*Nom*/}
<FormControl id="lastName" isInvalid={errors.lastName as boolean|undefined}>
<FormControl id="lastName"
isInvalid={errors.lastName as boolean | undefined}>
<FormLabel>Nom</FormLabel>
<Input
type="text"
placeholder="Nom"
<Input type="text" placeholder="Nom"
{...register('lastName', {
required: {value: true, message: 'Nom requis'},
})}
/>
})}/>
<FormErrorMessage>{errors.lastName?.message as string}</FormErrorMessage>
</FormControl>
</Flex>
{/*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>
<Input
type="text"
<Input type="text" placeholder="adresse@email.com"
{...register('email', {
required: {value: true, message: 'Adresse email requise'},
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>
{/*Date de naissance*/}
<FormControl
mb={'1rem'}
id={'birthdate'}
isInvalid={errors.birthdate as boolean|undefined}
>
<FormControl mb={'1rem'} id={'birthdate'}
isInvalid={errors.birthdate as boolean | undefined}>
<FormLabel>Date de naissance</FormLabel>
<Input
type="date"
<Input type="date"
{...register('birthdate', {
required: {
value: true,
message: 'Date de naissance requise',
},
validate: validateDate,
})}
/>
{ (errors.birthdate) ? <FormErrorMessage>{errors.birthdate.message as string}</FormErrorMessage> : "" }
})}/>
{(errors.birthdate) &&
<FormErrorMessage>{errors.birthdate.message as string}</FormErrorMessage>}
</FormControl>
{/*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>
<Input
type="password"
placeholder="Mot de passe"
<Input type="password" placeholder="Mot de passe"
{...register('password', {
required: {value: true, message: 'Mot de passe requis'},
})}
/>
})}/>
<FormErrorMessage>{errors.password?.message as string}</FormErrorMessage>
</FormControl>
{/*Mot de passe (2)*/}
<FormControl
mb={'1rem'}
id="confirmPassword"
isInvalid={errors.confirmPassword as boolean|undefined}
>
<FormControl mb={'1rem'} id="confirmPassword"
isInvalid={errors.confirmPassword as boolean | undefined}>
<FormLabel>Confirmation du mot de passe</FormLabel>
<Input
type="password"
placeholder="Mot de passe"
<Input type="password" placeholder="Mot de passe"
{...register('confirmPassword', {
required: {value: true, message: 'Confirmation requise'},
validate: (value: string) => {
return (
validate: (value: string) => (
watch('password') === value ||
'Les mots de passe ne correspondent pas'
);
},
})}
/>
),
})}/>
<FormErrorMessage>{errors.confirmPassword?.message as string}</FormErrorMessage>
</FormControl>
<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
</Button>
<Button
isLoading={isSubmitting}
w={{base: '100%', md: 'unset'}}
colorScheme="purple"
type="submit"
>
<Button isLoading={isSubmitting} w={{base: '100%', md: 'unset'}}
type="submit">
Je m&apos;inscris
</Button>
</Flex>
@@ -8,19 +8,20 @@ import {
Box,
CardHeader,
useToast,
} from "@chakra-ui/react";
import Carousel from "../../../Carousel";
import { BiHeart } from "react-icons/bi";
import { RxCross1 } from "react-icons/rx";
import { useMutation, useQuery } from "@tanstack/react-query";
import PassionTagList from "@/components/layout/dashboard/card_user/PassionTagList";
import { useState } from "react";
import SearchFailCard from "./SearchFailCard";
import LoadingPage from "@/components/LoadingPage";
} from '@chakra-ui/react';
import Carousel from '../../../Carousel';
import {BiHeart} from 'react-icons/bi';
import {RxCross1} from 'react-icons/rx';
import {useMutation, useQuery} from '@tanstack/react-query';
import PassionTagList
from '@/components/layout/dashboard/card_user/PassionTagList';
import {useState} from 'react';
import SearchFailCard from './SearchFailCard';
import LoadingPage from '@/components/LoadingPage';
export default function CardUser({users, loggedUser, setMatch}) {
const toast = useToast({
position: "top",
position: 'top',
duration: 2000,
isClosable: true,
});
@@ -29,8 +30,8 @@ export default function CardUser({ users, loggedUser, setMatch }) {
const likeMutation = useMutation({
mutationFn: async (id) => {
const likePostOptions = {
method: "POST",
headers: { "Content-Type": "application/json" },
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
idUser: loggedUser.id,
idUserLiked: id,
@@ -48,9 +49,9 @@ export default function CardUser({ users, loggedUser, setMatch }) {
onSuccess: (data) => {
if (data.error) {
toast({
title: "Erreur",
description: "Une erreur est survenue",
status: "error",
title: 'Erreur',
description: 'Une erreur est survenue',
status: 'error',
});
return;
}
@@ -59,9 +60,9 @@ export default function CardUser({ users, loggedUser, setMatch }) {
}
setListUsers(listUsers.slice(1));
toast({
title: "J'aime",
description: "Votre action a bien été prise en compte",
status: "success",
title: 'J\'aime',
description: 'Votre action a bien été prise en compte',
status: 'success',
});
//tester si match et afficher un truc
@@ -71,8 +72,8 @@ export default function CardUser({ users, loggedUser, setMatch }) {
const dislikeMutation = useMutation({
mutationFn: async (id) => {
const dislikePostOptions = {
method: "POST",
headers: { "Content-Type": "application/json" },
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
idUser: loggedUser.id,
idUserDisliked: id,
@@ -90,18 +91,18 @@ export default function CardUser({ users, loggedUser, setMatch }) {
onSuccess: (data) => {
if (data.error) {
toast({
title: "Erreur",
description: "Une erreur est survenue",
status: "error",
title: 'Erreur',
description: 'Une erreur est survenue',
status: 'error',
duration: 2000,
});
return;
}
setListUsers(listUsers.slice(1));
toast({
title: "J'aime pas",
description: "Votre action a bien été prise en compte",
status: "success",
title: 'J\'aime pas',
description: 'Votre action a bien été prise en compte',
status: 'success',
duration: 2000,
});
},
@@ -113,91 +114,70 @@ export default function CardUser({ users, loggedUser, setMatch }) {
data: listPassions,
error: passionError,
} = useQuery({
queryKey: ["passions"],
queryKey: ['passions'],
queryFn: async () => {
return fetch(`/api/passions/`)
.then((res) => res.json())
.catch((err) => {
return err;
});
.then(res => res.json())
.catch(err => err);
},
});
const formateDateToAge = (birthdate: Date) => {
const date = new Date(birthdate);
var ageDifMs = Date.now() - date.getTime();
var ageDate = new Date(ageDifMs); // miliseconds from epoch
const ageDifMs = Date.now() - date.getTime();
const ageDate = new Date(ageDifMs); // miliseconds from epoch
return Math.abs(ageDate.getUTCFullYear() - 1970);
};
if (listUsers.length === 0) {
return <SearchFailCard />;
}
if (likeMutation.isLoading || dislikeMutation.isLoading) {
return <LoadingPage />;
}
if (listUsers.length === 0) return <SearchFailCard/>;
if (likeMutation.isLoading ||
dislikeMutation.isLoading) return <LoadingPage/>;
return (
<Card w={"100%"} h={"100%"} borderRadius={"1rem"} overflow={"hidden"}>
<Card w={'100%'} h={'100%'} borderRadius={'1rem'} overflow={'hidden'}>
<CardHeader>
<Carousel borderRadius={"1rem"} images={listUsers?.[0].images} />
<Carousel borderRadius={'1rem'} images={listUsers?.[0].images}/>
</CardHeader>
<CardBody>
<Flex justify={"space-between"} mb={"20px"}>
<Heading fontSize={"1.5rem"} fontWeight={"bold"} flexBasis={"70%"}>
{listUsers[0].firstName} {listUsers[0].lastName},{" "}
<Flex justify={'space-between'} mb={'20px'}>
<Heading fontSize={'1.5rem'} fontWeight={'bold'} flexBasis={'70%'}>
{listUsers[0].firstName} {listUsers[0].lastName},{' '}
{formateDateToAge(listUsers[0].birthdate)} ans
</Heading>
<Flex gap={1}>
<IconButton
aria-label="like"
borderRadius={"1rem"}
colorScheme={"purple"}
onClick={() => {
likeMutation.mutate(listUsers[0].id);
}}
>
<BiHeart />
</IconButton>
<IconButton
aria-label="dislike"
borderRadius={"1rem"}
colorScheme={"purple"}
variant={"outline"}
onClick={() => {
dislikeMutation.mutate(listUsers[0].id);
}}
>
<RxCross1 />
</IconButton>
<IconButton icon={<BiHeart/>} aria-label="like"
borderRadius={'1rem'}
onClick={() => likeMutation.mutate(listUsers[0].id)
}/>
<IconButton icon={<RxCross1/>} aria-label="dislike"
borderRadius={'1rem'} variant={'outline'}
onClick={() => dislikeMutation.mutate(
listUsers[0].id)}/>
</Flex>
</Flex>
<Box mb={"20px"}>
<Heading size={"sm"} fontWeight={"bold"} mb="0.5rem">
<Box mb={'20px'}>
<Heading size={'sm'} fontWeight={'bold'} mb="0.5rem">
A propos :
</Heading>
<Text as="i">&quot;{listUsers[0].bio}&quot;</Text>
</Box>
<Box>
<Heading size={"sm"} fontWeight={"bold"}>
<Heading size={'sm'} fontWeight={'bold'}>
Passions :
</Heading>
{passionLoading ? (
<Text>Chargement des passions...</Text>
) : passionIsError ? (
<Text>Erreur lors du chargement des passions</Text>
) : (
<PassionTagList
passions={listUsers[0].PassionID}
{passionLoading
? <Text>Chargement des passions...</Text>
: passionIsError
? <Text>Erreur lors du chargement des passions</Text>
:
<PassionTagList passions={listUsers[0].PassionID}
userPassions={loggedUser.PassionID}
listPassions={listPassions}
/>
)}
listPassions={listPassions}/>
}
</Box>
</CardBody>
</Card>
@@ -1,4 +1,4 @@
import { Badge, Flex, Tag } from "@chakra-ui/react";
import {Flex, Tag} from '@chakra-ui/react';
type Props = {
passions: string[];
@@ -6,22 +6,15 @@ type Props = {
listPassions?: Object[];
};
export default function PassionTagList({
passions,
userPassions = [],
listPassions,
}: Props) {
return (
<Flex gap={"0.5rem"} mt={"1vh"} flexWrap="wrap">
const PassionTagList = ({passions, userPassions = [], listPassions}: Props) => (
<Flex gap={'0.5rem'} mt={'1vh'} flexWrap="wrap">
{passions.map((passionID, index) => (
<Tag
key={index}
variant={userPassions.includes(passionID) ? "subtle" : "outline"}
colorScheme={"purple"}
>
<Tag variant={userPassions.includes(passionID) ? 'subtle' : 'outline'}
key={index}>
{listPassions?.find((passion) => passion.id === passionID)?.name}
</Tag>
))}
</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) {
return (
<Card w={"100%"} h={"100%"} borderRadius={"1rem"} overflow={"hidden"}>
const SearchFailCard = () => (
<Card w={'100%'} h={'100%'} borderRadius={'1rem'} overflow={'hidden'}>
<CardHeader> Aucun profil correspondant à vos critères </CardHeader>
<CardBody>
<Text as="b">
@@ -11,4 +10,5 @@ export default function SearchFailCard(props) {
</CardBody>
</Card>
);
}
export default SearchFailCard;
@@ -1,90 +1,64 @@
import {Card, Flex, Box, Image, Text, Spacer, Divider} from "@chakra-ui/react";
import { useRouter } from "next/router";
import {Card, Flex, Box, Image, Text, Spacer, Divider} from '@chakra-ui/react';
import {useRouter} from 'next/router';
import { AiFillMessage } from "react-icons/ai";
import { BsFillPersonFill } from "react-icons/bs";
import { BiLogOut } from "react-icons/bi";
import { FaMapMarkedAlt } from "react-icons/fa";
import {AiFillMessage} from 'react-icons/ai';
import {BsFillPersonFill} from 'react-icons/bs';
import {BiLogOut} from 'react-icons/bi';
import {FaMapMarkedAlt} from 'react-icons/fa';
import LeftPanelButton from "@/components/layout/dashboard/left_panel/LeftPanelButton";
import { signOut } from "next-auth/react";
import LeftPanelButton
from '@/components/layout/dashboard/left_panel/LeftPanelButton';
import {signOut} from 'next-auth/react';
export default function LeftPanel(props) {
const router = useRouter();
const {user} = props;
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 (
<Card
width={"20vw"}
height={"100%"}
borderRadius={0}
padding={"0px"}
bg={"#faf9ff"}
>
<Flex direction={"column"} height={"100%"} margin={"10%"}>
<Card width={'20vw'} height={'100%'} borderRadius={0} padding={'0px'}
bg={'#faf9ff'}>
<Flex direction={'column'} height={'100%'} margin={'10%'}>
<Box>
{user.images.length === 0 ? (
<Image src={"/blank_profile_picture.webp"} borderRadius={"1rem"} />
) : (
<Image
src={user.images[0]}
borderRadius={"1rem"}
objectFit={"contain"}
width={"100%"}
/>
)}
<Box mt={"2rem"}>
<Flex align={"center"} justifyContent="space-between" mb={"1rem"}>
<Text fontSize={"1.5rem"} fontWeight={"bold"}>
<Image src={user.images[0] ?? '/blank_profile_picture.webp'}
objectFit={'contain'} borderRadius={'1rem'}/>
<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"}>
<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>
<Divider mb={'1rem'}/>
<Text as="i" fontWeight={'bold'}>&quot;{user.bio}&quot;</Text>
</Box>
</Box>
<Spacer/>
<Flex
gap={2}
direction={"column"}
mt={"1vh"}
spacing={3.5}
alignContent={"bottom"}
>
<LeftPanelButton
leftIcon={<FaMapMarkedAlt />}
onClickHandler={() => router.push("/map")}
>
<Flex gap={2} direction={'column'} mt={'1vh'}
spacing={3.5} alignContent={'bottom'}>
<LeftPanelButton leftIcon={<FaMapMarkedAlt/>}
onClickHandler={() => router.push('/map')}>
Carte
</LeftPanelButton>
<LeftPanelButton
leftIcon={<AiFillMessage />}
onClickHandler={() => router.push("/dashboard")}
>
<LeftPanelButton leftIcon={<AiFillMessage/>}
onClickHandler={() => router.push('/dashboard')}>
Messages
</LeftPanelButton>
<LeftPanelButton
leftIcon={<BsFillPersonFill />}
onClickHandler={() => router.push("/profile")}
>
Profile
<LeftPanelButton leftIcon={<BsFillPersonFill/>}
onClickHandler={() => router.push('/profile')}>
Profil
</LeftPanelButton>
<LeftPanelButton
variant={"outline"}
leftIcon={<BiLogOut />}
onClickHandler={() => signOut({ callbackUrl: "/" })}
>
Deconnexion
<LeftPanelButton variant={'outline'} leftIcon={<BiLogOut/>}
onClickHandler={() => signOut({callbackUrl: '/'})}>
Déconnexion
</LeftPanelButton>
</Flex>
</Flex>
@@ -2,25 +2,20 @@ import {Button, ResponsiveValue} from '@chakra-ui/react';
import {ReactJSXElement} from '@emotion/react/types/jsx-namespace';
type Props = {
children?: ReactJSXElement
children?: ReactJSXElement | string
onClickHandler: () => void
leftIcon?: ReactJSXElement,
variant?: ResponsiveValue<'link' | 'outline' | string | 'ghost' | 'solid' | 'unstyled'>
}
export default function LeftPanelButton(props: Props) {
const {children, onClickHandler, leftIcon: icon, variant = "ghost"} = props;
const {children, onClickHandler, leftIcon: icon, variant = 'ghost'} = props;
return (
<Button
colorScheme={'purple'}
variant={variant}
width={'100%'}
justifyContent={'left'}
onClick={onClickHandler}
leftIcon={icon}
fontWeight={'bold'}
fontSize={'1.2rem'}
>{children}</Button>
<Button variant={variant} width={'100%'} justifyContent={'left'}
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();
return (
<Flex
bg={'#FAF9FF'}
p={150}
minH={{base: 'unset', md: '100vh'}}
align={'center'}
justify={'center'}
>
<Flex bg={'#FAF9FF'} p={150} minH={{base: 'unset', md: '100vh'}}
align={'center'} justify={'center'}>
<Flex gap={10}>
<Flex justify={'center'} direction={'column'} flexBasis={'100%'}>
<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
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 !
</Text>
<ButtonGroup>
<Button colorScheme={'purple'}
onClick={() => router.push('/register')}>
<Button onClick={() => router.push('/register')}>
S&apos;inscrire
</Button>
</ButtonGroup>
</Flex>
<Box flexBasis={'100%'} display={{base: "none", md: "block"}}>
<Image borderRadius={20}
boxShadow={'lg'}
src={'/couple_img.jpg'}
alt="happy couple"
/>
<Box flexBasis={'100%'} display={{base: 'none', md: 'block'}}>
<Image borderRadius={20} boxShadow={'lg'} src={'/couple_img.jpg'}
alt="happy couple"/>
</Box>
</Flex>
</Flex>
@@ -4,8 +4,7 @@ export default function PresentationSection() {
return (
<Flex justify={{base: 'center', md: 'right'}} alignItems={'center'}
bg={{base: '#805AD5'}} bgImage={'/couple_funny.png'}
bgSize={'cover'}
bgPos={'center'}
bgSize={'cover'} bgPos={'center'}
bgAttachment={'fixed'} minH={{base: 'initial', md: '80vh'}}>
<Box p={50} width={{base: '100%', md: '50%'}} maxW={{md: "500px"}} bg={'#805AD5'}>
+19 -28
View File
@@ -1,21 +1,22 @@
import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet";
import { Flex, Text, useToast } from "@chakra-ui/react";
import MarkerClusterGroup from "@christopherpickering/react-leaflet-markercluster";
import {MapContainer, TileLayer, Marker, Popup} from 'react-leaflet';
import {Flex, Text, useToast} from '@chakra-ui/react';
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 "leaflet/dist/leaflet.css";
import MarkerBar from './MarkerBar';
import 'leaflet/dist/leaflet.css';
export default function MapComponent(props: any) {
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({
iconUrl: "logo.svg",
iconUrl: 'logo.svg',
iconSize: [35, 35],
});
@@ -23,32 +24,24 @@ export default function MapComponent(props: any) {
<>
{location[0] === null || location[1] === null ? (
<>
{!toast.isActive(idToastError)
? toast({
title: "Erreur",
{!toast.isActive(idToastError) &&
toast({
title: 'Erreur',
id: idToastError,
description: "Veuillez autoriser la localisation",
status: "error",
description: 'Veuillez autoriser la localisation',
status: 'error',
isClosable: false,
duration: 100000,
})
: null}
})}
<Text color="purple.500" fontSize="2xl" fontWeight="bold">
Veuillez autoriser la localisation
</Text>
</>
) : (
<Flex>
<MapContainer
center={location}
zoom={13}
minZoom={8}
<MapContainer center={location} zoom={13} minZoom={8}
zoomControl={false}
style={{
width: "100vw",
height: "100vw",
}}
>
style={{width: '100vw', height: '100vw'}}>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
@@ -57,9 +50,7 @@ export default function MapComponent(props: any) {
<Marker icon={userIcon} position={location}></Marker>
<MarkerClusterGroup chunkedLoading showCoverageOnHover={false}>
{listBars.map((bar, index) => {
return <MarkerBar key={index} bar={bar} />;
})}
{listBars.map((bar: any, index: number) => <MarkerBar key={index} bar={bar}/>)}
</MarkerClusterGroup>
</MapContainer>
</Flex>
+2 -4
View File
@@ -1,6 +1,6 @@
import { Marker, Popup } from "react-leaflet";
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) {
const { bar } = props;
@@ -23,9 +23,7 @@ export default function MarkerBar(props) {
<Text fontSize={"1rem"} fontWeight={"bold"} align={"center"}>
{bar.name || '"Nom du bar"'}
</Text>
<Button colorScheme={"purple"} variant={"outline"}>
Inviter un match
</Button>
<Button variant={"outline"}>Inviter un match</Button>
</Box>
</Popup>
</Marker>
@@ -4,9 +4,9 @@ import {
Tag,
TagLeftIcon,
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) {
const {text} = props;
@@ -14,33 +14,17 @@ export default function CustomCheckbox(props) {
useCheckbox(props);
return (
<chakra.label
// colorScheme={"purple"}
variant={state.isChecked ? "solid" : "outline"}
cursor="pointer"
{...htmlProps}
>
<chakra.label variant={state.isChecked ? 'solid' : 'outline'}
cursor="pointer" {...htmlProps}>
<input {...getInputProps()} hidden/>
{state.isChecked ? (
<Tag
{...getCheckboxProps()}
colorScheme={"purple"}
{...getLabelProps()}
>
<TagLeftIcon as={IoRemove} />
<Tag {...getCheckboxProps()} variant={state.isChecked ? "solid" : "outline"} {...getLabelProps()}>
{state.isChecked
? <TagLeftIcon as={IoRemove}/>
: <TagLeftIcon as={IoAdd}/>
}
<TagLabel>{text}</TagLabel>
</Tag>
) : (
<Tag
{...getCheckboxProps()}
colorScheme={"purple"}
variant={"outline"}
{...getLabelProps()}
>
<TagLeftIcon as={IoAdd} />
<TagLabel>{text}</TagLabel>
</Tag>
)}
</chakra.label>
);
}
@@ -6,52 +6,40 @@ import {
FormErrorMessage,
FormHelperText,
FormLabel,
} from "@chakra-ui/react";
import { Controller, ControllerProps } from "react-hook-form";
} from '@chakra-ui/react';
import {Controller, ControllerProps} from 'react-hook-form';
type CustomEditableProps = {
label: string;
isDisabled?: boolean;
helperText?: string;
} & Omit<ControllerProps, "render">;
} & Omit<ControllerProps, 'render'>;
export default function CustomEditable(props: CustomEditableProps) {
const {
defaultValue,
label,
name,
helperText = "",
helperText = '',
isDisabled = false,
...controllerProps
} = props;
return (
<Controller
{...controllerProps}
name={name}
defaultValue={defaultValue}
render={({ field, fieldState: { invalid, error } }) => {
return (
<Controller{...controllerProps} name={name} defaultValue={defaultValue}
render={({field, fieldState: {invalid, error}}) => (
<FormControl id={name} isInvalid={invalid}>
<FormLabel as="legend" htmlFor={name}>
{label}
</FormLabel>
<Editable
{...field}
id={name}
fontWeight="bold"
<FormLabel as="legend" htmlFor={name}>{label}</FormLabel>
<Editable{...field} id={name} fontWeight="bold"
isDisabled={isDisabled}
placeholder={"Non renseigné"}
color={isDisabled ? "gray.500" : undefined}
>
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,
FormErrorMessage,
FormLabel,
} from "@chakra-ui/react";
import { Controller, ControllerProps } from "react-hook-form";
} from '@chakra-ui/react';
import {Controller, ControllerProps} from 'react-hook-form';
type CustomEditableProps = {
label: string;
isDisabled?: boolean;
} & Omit<ControllerProps, "render">;
} & Omit<ControllerProps, 'render'>;
export default function CustomEditableArea(props: CustomEditableProps) {
const {
@@ -23,32 +23,19 @@ export default function CustomEditableArea(props: CustomEditableProps) {
} = props;
return (
<Controller
{...controllerProps}
name={name}
defaultValue={defaultValue}
render={({ field, fieldState: { invalid, error } }) => {
return (
<Controller{...controllerProps} name={name} defaultValue={defaultValue}
render={({field, fieldState: {invalid, error}}) => (
<FormControl id={name} isInvalid={invalid}>
<FormLabel as="legend" htmlFor={name}>
{label}
</FormLabel>
<Editable
{...field}
id={name}
fontWeight="bold"
width={"100%"}
isDisabled={isDisabled}
placeholder={"Non renseigné"}
color={isDisabled ? "gray.500" : undefined}
>
<FormLabel as="legend" htmlFor={name}>{label}</FormLabel>
<Editable{...field} id={name} 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,
FormLabel,
UseEditableProps,
} from "@chakra-ui/react";
} from '@chakra-ui/react';
type CustomEditableProps = {
id: string;
@@ -14,20 +14,13 @@ type CustomEditableProps = {
} & UseEditableProps;
export default function CustomEditable(props: CustomEditableProps) {
const { label, id, helperText = "", ...UseEditableProps } = props;
const {label, id, helperText = '', ...UseEditableProps} = props;
return (
<FormControl>
<FormLabel as="legend" htmlFor={id}>
{label}
</FormLabel>
<Editable
{...UseEditableProps}
fontWeight="bold"
isDisabled={true}
placeholder={"Non renseigné"}
color={"gray.500"}
>
<FormLabel as="legend" htmlFor={id}>{label}</FormLabel>
<Editable{...UseEditableProps} fontWeight="bold" isDisabled={true}
placeholder={'Non renseigné'} color={'gray.500'}>
<EditablePreview/>
</Editable>
<FormHelperText>{helperText}</FormHelperText>
@@ -1,60 +1,35 @@
import { FormLabel, HStack, Radio, RadioGroup } from "@chakra-ui/react";
import { Gender } from "@prisma/client";
import { Controller, ControllerProps } from "react-hook-form";
import {FormLabel, HStack, Radio, RadioGroup} from '@chakra-ui/react';
import {Gender} from '@prisma/client';
import {Controller, ControllerProps} from 'react-hook-form';
type CustomRadioGenderProps = {
label: string;
} & Omit<ControllerProps, "render">;
} & Omit<ControllerProps, 'render'>;
export default function CustomRadioGender(props: CustomRadioGenderProps) {
const {defaultValue, label, name, ...controllerProps} = props;
const getTextGender = (gender: string) => {
switch (gender) {
case Gender.MALE:
return "Homme";
case Gender.FEMALE:
return "Femme";
case Gender.OTHER:
return "Autre";
case Gender.UNKNOWN:
return "Non renseigné";
}
};
const genders = [
{label: 'Homme', gender: Gender.MALE},
{label: 'Femme', gender: Gender.FEMALE},
{label: 'Autre', gender: Gender.OTHER},
{label: 'Non renseigné', gender: Gender.UNKNOWN},
];
return (
<>
<FormLabel as={"legend"} htmlFor={name}>
{label}
</FormLabel>
<Controller
{...controllerProps}
name={name}
render={({ field }) => {
return (
<RadioGroup
{...field}
colorScheme={"purple"}
id={name}
as="b"
defaultValue={defaultValue}
>
<HStack spacing={"0.5rem"}>
<Radio value={Gender.MALE}>{getTextGender(Gender.MALE)}</Radio>
<Radio value={Gender.FEMALE}>
{getTextGender(Gender.FEMALE)}
</Radio>
<Radio value={Gender.OTHER}>
{getTextGender(Gender.OTHER)}
</Radio>
<Radio value={Gender.UNKNOWN}>
{getTextGender(Gender.UNKNOWN)}
</Radio>
<FormLabel as={'legend'} htmlFor={name}>{label}</FormLabel>
<Controller{...controllerProps} name={name}
render={({field}) => (
<RadioGroup{...field} id={name} as="b"
defaultValue={defaultValue}>
<HStack spacing={'0.5rem'}>
{genders.map(({label, gender}, index) => (
<Radio key={index} value={gender}>{label}</Radio>
))}
</HStack>
</RadioGroup>
);
}}
/>
)}/>
</>
);
}
@@ -6,10 +6,10 @@ import {
RangeSliderThumb,
RangeSliderTrack,
Tooltip,
} from "@chakra-ui/react";
import { Controller, ControllerProps } from "react-hook-form";
} from '@chakra-ui/react';
import {Controller, ControllerProps} from 'react-hook-form';
import { FaGreaterThan, FaLessThan } from "react-icons/fa";
import {FaGreaterThan, FaLessThan} from 'react-icons/fa';
type CustomRangeSliderProps = {
label: string;
@@ -19,7 +19,7 @@ type CustomRangeSliderProps = {
showTooltipAge: boolean;
setShowTooltipAge: React.Dispatch<React.SetStateAction<boolean>>;
name: string;
} & Omit<ControllerProps, "render">;
} & Omit<ControllerProps, 'render'>;
export default function CustomRangeSlider(props: CustomRangeSliderProps) {
const {
@@ -35,59 +35,39 @@ export default function CustomRangeSlider(props: CustomRangeSliderProps) {
return (
<>
<FormLabel as={"legend"} htmlFor={name}>
{label}
</FormLabel>
<Controller
{...controllerProps}
name={name}
<FormLabel as={'legend'} htmlFor={name}>{label}</FormLabel>
<Controller{...controllerProps} name={name}
render={({field: {onChange}}) => (
<RangeSlider
aria-label={["min", "max"]}
colorScheme={"purple"}
min={18}
<RangeSlider aria-label={['min', 'max']} min={18}
max={99}
id={name}
color={"pink.500"}
id={name} color={'pink.500'}
defaultValue={defaultValue}
onChange={(v: [number, number]) => {
setSliderAgeValue(v);
onChange(v);
}}
onMouseEnter={() => setShowTooltipAge(true)}
onMouseLeave={() => setShowTooltipAge(false)}
>
onMouseLeave={() => setShowTooltipAge(false)}>
<RangeSliderTrack>
<RangeSliderFilledTrack bgColor={"purple.500"} />
<RangeSliderFilledTrack bgColor={'purple.500'}/>
</RangeSliderTrack>
<Tooltip
hasArrow
bg="purple.500"
color="white"
placement="top"
isOpen={showTooltipAge}
label={`${sliderAgeValue[0]}`}
>
<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} />
<Box color={'purple.500'} as={FaLessThan}/>
</RangeSliderThumb>
</Tooltip>
<Tooltip
hasArrow
bg="purple.500"
color="white"
placement="top"
isOpen={showTooltipAge}
label={`${sliderAgeValue[1]}`}
>
<Tooltip hasArrow bg="purple.500" color="white"
placement="top" isOpen={showTooltipAge}
label={`${sliderAgeValue[1]}`}>
<RangeSliderThumb boxSize={6} index={1}>
<Box color={"purple.500"} as={FaGreaterThan} />
<Box color={'purple.500'} as={FaGreaterThan}/>
</RangeSliderThumb>
</Tooltip>
</RangeSlider>
)}
/>
)}/>
</>
);
}
+16 -32
View File
@@ -6,10 +6,10 @@ import {
SliderThumb,
SliderTrack,
Tooltip,
} from "@chakra-ui/react";
import { Controller, ControllerProps } from "react-hook-form";
} from '@chakra-ui/react';
import {Controller, ControllerProps} from 'react-hook-form';
import { FaWalking } from "react-icons/fa";
import {FaWalking} from 'react-icons/fa';
type CustomSliderProps = {
label: string;
@@ -19,7 +19,7 @@ type CustomSliderProps = {
showTooltipDistance: boolean;
setShowTooltipDistance: React.Dispatch<React.SetStateAction<boolean>>;
name: string;
} & Omit<ControllerProps, "render">;
} & Omit<ControllerProps, 'render'>;
export default function CustomSlider(props: CustomSliderProps) {
const {
@@ -35,46 +35,30 @@ export default function CustomSlider(props: CustomSliderProps) {
return (
<>
<FormLabel as={"legend"} htmlFor={name}>
{label}
</FormLabel>
<Controller
{...controllerProps}
name={name}
<FormLabel as={'legend'} htmlFor={name}>{label}</FormLabel>
<Controller{...controllerProps} name={name}
render={({field: {onChange}}) => (
<Slider
aria-label={"distance"}
colorScheme={"purple"}
min={20}
max={250}
id={"distance"}
color={"pink.500"}
<Slider aria-label={'distance'} min={20} max={250}
id={'distance'} color={'pink.500'}
defaultValue={defaultValue}
onChange={(v) => {
onChange={v => {
setSliderDistanceValue(v);
onChange(v);
}}
onMouseEnter={() => setShowTooltipDistance(true)}
onMouseLeave={() => setShowTooltipDistance(false)}
>
onMouseLeave={() => setShowTooltipDistance(false)}>
<SliderTrack>
<SliderFilledTrack bgColor={"purple.500"} />
<SliderFilledTrack bgColor={'purple.500'}/>
</SliderTrack>
<Tooltip
hasArrow
bg="purple.500"
color="white"
placement="top"
isOpen={showTooltipDistance}
label={`${sliderDistanceValue} km`}
>
<Tooltip hasArrow bg="purple.500" color="white"
placement="top" isOpen={showTooltipDistance}
label={`${sliderDistanceValue} km`}>
<SliderThumb boxSize={6}>
<Box color={"purple.500"} as={FaWalking} />
<Box color={'purple.500'} as={FaWalking}/>
</SliderThumb>
</Tooltip>
</Slider>
)}
/>
)}/>
</>
);
}
@@ -13,15 +13,16 @@ import {
useCheckboxGroup,
useDisclosure,
useToast,
} from "@chakra-ui/react";
import { useQueryClient } from "@tanstack/react-query";
import { RiEditBoxLine } from "react-icons/ri";
import CustomCheckbox from "./CustomCheckbox";
} from '@chakra-ui/react';
import {useQueryClient} from '@tanstack/react-query';
import {RiEditBoxLine} from 'react-icons/ri';
import CustomCheckbox from './CustomCheckbox';
import {Passion} from '@prisma/client';
export default function ModalChoosePassion(props) {
const {isOpen, onOpen, onClose} = useDisclosure();
const {user, passions} = props;
const toast = useToast({ position: "top", isClosable: true });
const toast = useToast({position: 'top', isClosable: true});
const client = useQueryClient();
const {value, getCheckboxProps} = useCheckboxGroup({
@@ -36,76 +37,51 @@ export default function ModalChoosePassion(props) {
});
const options = {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
method: 'PATCH',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(jsonPassions),
};
fetch(`/api/users/${user.id}`, options)
.then((res) => res.json())
.then((data) => {
.then(res => res.json())
.then(() => {
toast({
title: "Centres d'intérêts mis à jour",
status: "success",
title: 'Centres d\'intérêts mis à jour',
status: 'success',
duration: 9000,
});
client.invalidateQueries("user");
client.invalidateQueries('user');
onClose();
})
.catch((err) => {
console.log(err);
});
.catch(err => console.log(err));
};
return (
<>
<Button
colorScheme={"purple"}
onClick={onOpen}
leftIcon={<RiEditBoxLine />}
>
Choisir les centres d'intérèts
<Button onClick={onOpen} leftIcon={<RiEditBoxLine/>}>
Choisir les centres d'intérêts
</Button>
<Modal
blockScrollOnMount={false}
size={"4xl"}
isOpen={isOpen}
onClose={onClose}
scrollBehavior={"inside"}
>
<Modal blockScrollOnMount={false} size={'4xl'} isOpen={isOpen}
onClose={onClose} scrollBehavior={'inside'}>
<ModalOverlay/>
<ModalContent>
<ModalHeader>Choix des centres d'intérèts</ModalHeader>
<ModalHeader>Choix des centres d'intérêts</ModalHeader>
<ModalCloseButton/>
<ModalBody>
<Flex gap={"1rem"} flexWrap="wrap">
{passions !== null ? (
passions.map((passion, index) => {
return (
<CustomCheckbox
{...getCheckboxProps({ value: passion.id })}
key={passion.id}
text={passion.name}
/>
);
})
) : (
<></>
<Flex gap={'1rem'} flexWrap="wrap">
{passions && (
passions.map((passion: Passion) => (
<CustomCheckbox key={passion.id} text={passion.name}
{...getCheckboxProps({value: passion.id})}/>
)
)
)}
</Flex>
</ModalBody>
<ModalFooter>
<Button
colorScheme="purple"
mr={3}
onClick={() => {
savePassions(value);
}}
>
<Button mr={3} onClick={() => savePassions(value)}>
Save
</Button>
</ModalFooter>
@@ -13,44 +13,43 @@ import {
ModalHeader,
ModalOverlay,
useDisclosure,
useQuery,
useToast,
} from "@chakra-ui/react";
import { useQueryClient } from "@tanstack/react-query";
import { useState } from "react";
import { RiEditBoxLine } from "react-icons/ri";
} from '@chakra-ui/react';
import {useQueryClient} from '@tanstack/react-query';
import {useState} from 'react';
import {RiEditBoxLine} from 'react-icons/ri';
export default function ModalModifyImages(props) {
const {isOpen, onOpen, onClose} = useDisclosure();
const {images, user, userData, setUserData} = props;
const [listImage, setlistImage] = useState(images);
const toast = useToast({ position: "top", isClosable: true });
const toast = useToast({position: 'top', isClosable: true});
const client = useQueryClient();
const uploadImage = async (file) => {
const body = new FormData();
body.append("file", file);
body.append('file', file);
const imagePostOptions = {
method: "POST",
method: 'POST',
body,
};
const imagePatchOptions = {
method: "PUT",
headers: { "Content-Type": "application/json" },
method: 'PUT',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
images: [...listImage, `imageUsers/${file.name}`],
}),
};
fetch(`/api/file/uploadFile`, imagePostOptions)
.then((res) => {
.then(res => {
fetch(`/api/users/${user.id}`, imagePatchOptions)
.then((res) => {
.then(res => {
toast({
title: `Ajout d'image effectué`,
status: "success",
status: 'success',
isClosable: true,
});
setlistImage([...listImage, `imageUsers/${file.name}`]);
@@ -60,15 +59,15 @@ export default function ModalModifyImages(props) {
setIsLoading(false);
toast({
title: `Erreur lors de l'ajout des images`,
status: "error",
status: 'error',
isClosable: true,
});
});
})
.catch((err) => {
.catch(err => {
toast({
title: `Erreur lors de l'ajout des images`,
status: "error",
status: 'error',
isClosable: true,
});
console.log(err);
@@ -85,14 +84,14 @@ export default function ModalModifyImages(props) {
}
const imageDeleteOptions = {
method: "DELETE",
body: JSON.stringify({ fileName: fileName.split("/").pop() }),
method: 'DELETE',
body: JSON.stringify({fileName: fileName.split('/').pop()}),
};
fetch(`/api/file/deleteFile`, imageDeleteOptions).then((res) => {
const imagePatchOptions = {
method: "PUT",
headers: { "Content-Type": "application/json" },
method: 'PUT',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
images: [...listImage],
}),
@@ -101,14 +100,14 @@ export default function ModalModifyImages(props) {
.then((res) => {
toast({
title: `Suppression d'image effectué`,
status: "success",
status: 'success',
isClosable: true,
});
})
.catch(() => {
toast({
title: `Erreur lors de la suppression des images`,
status: "error",
status: 'error',
isClosable: true,
});
});
@@ -117,84 +116,60 @@ export default function ModalModifyImages(props) {
return (
<>
<Button
colorScheme={"purple"}
onClick={onOpen}
leftIcon={<RiEditBoxLine />}
>
<Button onClick={onOpen} leftIcon={<RiEditBoxLine/>}>
Modifier les images
</Button>
<Modal
blockScrollOnMount={false}
size={"4xl"}
isOpen={isOpen}
onClose={onClose}
scrollBehavior={"inside"}
>
<Modal blockScrollOnMount={false} size={'4xl'} isOpen={isOpen}
onClose={onClose} scrollBehavior={'inside'}>
<ModalOverlay/>
<ModalContent>
<ModalHeader>Modification des images</ModalHeader>
<ModalCloseButton/>
<ModalBody>
<Grid
templateColumns={`repeat(${listImage.length + 1}, 1fr)`}
gap={5}
>
<Grid templateColumns={`repeat(${listImage.length + 1}, 1fr)`}
gap={5}>
{listImage.map((image, index) => (
<GridItem key={index}>
<Flex direction={"column"} gap={"1rem"}>
<Flex direction={'column'} gap={'1rem'}>
<Image src={image}/>
<Button
id={"" + index}
colorScheme={"red"}
onClick={(e) => {
deleteImage(`${listImage[index]}`);
}}
>
<Button id={index.toString()} colorScheme={'red'}
onClick={() =>
deleteImage(`${listImage[index]}`)}>
Supprimer l'image
</Button>
</Flex>
</GridItem>
))}
{listImage.length < 5 ? (
<GridItem width={"100%"}>
<Input
type={"file"}
height={"100%"}
accept={"image/png, image/jpeg, image/webp"}
{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 extension = file.name.split(".").pop();
const extension = file.name.split('.').pop();
const newFile = new File(
[file],
`${user.id}_${time}.${extension}`,
{
type: file.type,
}
},
);
uploadImage(newFile);
}}
></Input>
}}/>
</GridItem>
) : (
<></>
)}
</Grid>
</ModalBody>
<ModalFooter>
<Button
colorScheme="purple"
mr={3}
onClick={(e) => {
client.invalidateQueries("user");
<Button mr={3} onClick={() => {
client.invalidateQueries('user');
onClose();
}}
>
}}>
Save
</Button>
</ModalFooter>
@@ -5,7 +5,7 @@ export default function ProfileBadgeList(props) {
return (
<Flex gap={"0.5rem"} mt={"1vh"} flexWrap="wrap">
{userPassions.map((idPassion, index) => (
<Tag key={index} colorScheme={"purple"}>
<Tag key={index}>
{passions !== null && passions !== undefined && passions.length > 0
? 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 { SessionProvider } from "next-auth/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import theme from '@/styles/theme';
const queryClient = new QueryClient();
@@ -12,7 +13,7 @@ export default function App({
}: AppProps) {
return (
<QueryClientProvider client={queryClient}>
<ChakraProvider>
<ChakraProvider theme={theme}>
<SessionProvider session={session}>
<Component {...pageProps} />
</SessionProvider>
+27 -39
View File
@@ -1,21 +1,22 @@
import {
Box,
Button,
Flex,
FormControl,
FormErrorMessage,
FormLabel,
Input,
useToast,
} from "@chakra-ui/react";
import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import { useState } from "react";
import { useForm } from "react-hook-form";
} from '@chakra-ui/react';
import {useSession} from 'next-auth/react';
import {useRouter} from 'next/router';
import {useState} from 'react';
import {useForm} from 'react-hook-form';
import LoadingPage from '@/components/LoadingPage';
import {Session} from '@/models/auth/Session';
export default function settings() {
const router = useRouter();
const toast = useToast({ position: "top", isClosable: true });
const toast = useToast({position: 'top', isClosable: true});
const [isLoading, setIsLoading] = useState(false);
@@ -26,66 +27,53 @@ export default function settings() {
} = useForm();
const [userData, setUserData] = useState({});
const {data: session, status} = useSession({required: true});
const { data: session, status } = useSession();
if (status === "unauthenticated") router.push("/login");
if (status === "authenticated") {
if (status === "loading") return <LoadingPage/>
if (status === 'authenticated') {
const {user} = session as unknown as Session;
if (user.role !== "ADMIN") router.push("/login");
if (user.role !== 'ADMIN') router.push('/login');
const savePassion = (passion: any) => {
const options = {
method: "POST",
headers: { "Content-Type": "application/json" },
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(passion),
};
fetch(`/api/passions`, options)
.then((res) => {
.then(res => {
setIsLoading(false);
toast({
title: `Passion ajoutée`,
status: "success",
status: 'success',
});
})
.catch((err) => {
.catch(err => {
setIsLoading(false);
toast({
title: `Erreur lors de l'ajout`,
status: "error",
status: 'error',
});
});
};
return (
<>
<Box
as="form"
id="form_passion"
width={"80%"}
onSubmit={handleSubmit(savePassion)}
>
<FormControl isInvalid={errors.name}>
<Box as="form" id="form_passion" width={'80%'}
onSubmit={handleSubmit(savePassion)}>
<FormControl isInvalid={errors.name as boolean | undefined}>
<FormLabel htmlFor="passion">Passion</FormLabel>
<Input
id="passion"
placeholder="passion"
{...register("name", {
required: "This is required",
})}
/>
<Input id="passion" placeholder="passion"
{...register('name', {
required: 'This is required',
})}/>
<FormErrorMessage>
{errors.name && errors.name.message}
{errors.name && errors.name.message as string}
</FormErrorMessage>
</FormControl>
<Button
mt={4}
colorScheme="purple"
isLoading={isSubmitting}
type="submit"
>
<Button mt={4} 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}/>
<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>
</Flex>
</Container>
+9 -6
View File
@@ -1,17 +1,20 @@
import {Button, Text} from '@chakra-ui/react';
import {Button} from '@chakra-ui/react';
import {useSession} from 'next-auth/react';
import {useRouter} from 'next/router';
export default function Chat() {
const router = useRouter();
const {data: session, status} = useSession()
const {data: session, status} = useSession();
if (session)
return (
<>
{
session.user.ChatID.map((id: string, index: number) => <Button key={index} onClick={() => router.push(`/chat/${id}`)} >{id}</Button>)
}
{session.user.ChatID.map(
(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 {useSession} from 'next-auth/react';
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() {
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 (
<>
<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"
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"
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz"
integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==
@@ -249,6 +249,13 @@
dependencies:
"@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":
version "2.0.15"
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"
integrity sha512-Qk7Pfu8BSarKGqILj4x7bCSZ1pjuAPZ+qmRwH5S7mDS91VSbVVsJSrW4qA+GPrro8t69gFYVMWb1Zc4yFmPiVg==
"@reduxjs/toolkit@^1.9.3":
version "1.9.3"
resolved "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.3.tgz"
integrity sha512-GU2TNBQVofL09VGmuSioNPQIu6Ml0YLf4EJhgj0AvBadRlCGzUWet8372LjvO4fqKZF2vH1xU0htAa7BrK9pZg==
"@reduxjs/toolkit@^1.9.5":
version "1.9.5"
resolved "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.5.tgz"
integrity sha512-Rt97jHmfTeaxL4swLRNPD/zV4OxTes4la07Xc4hetpUW/vc75t5m1ANyxG6ymnEQ2FsLQsoMlYB2vV1sO3m8tQ==
dependencies:
immer "^9.0.16"
redux "^4.2.0"
immer "^9.0.21"
redux "^4.2.1"
redux-thunk "^2.4.2"
reselect "^4.1.7"
reselect "^4.1.8"
"@rushstack/eslint-patch@^1.1.3":
version "1.2.0"
@@ -1409,6 +1416,14 @@
dependencies:
"@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":
version "0.0.29"
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"
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"
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.11.tgz"
integrity sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==
dependencies:
"@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"
resolved "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz"
integrity sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==
@@ -1477,6 +1492,11 @@
resolved "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz"
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":
version "5.57.1"
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"
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"
resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
@@ -3161,7 +3181,7 @@ ignore@^5.2.0:
resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz"
integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==
immer@^9.0.16:
immer@^9.0.21:
version "9.0.21"
resolved "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz"
integrity sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==
@@ -3745,6 +3765,11 @@ mkdirp@^1.0.3:
resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz"
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:
version "2.1.2"
resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"
@@ -4296,7 +4321,7 @@ react-clientside-effect@^1.2.6:
dependencies:
"@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"
resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz"
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"
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:
version "4.2.1"
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"
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:
version "2.3.4"
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"
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"
resolved "https://registry.npmjs.org/react/-/react-18.2.0.tgz"
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"
integrity sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==
redux@^4, redux@^4.2.0:
redux@^4, redux@^4.2.1:
version "4.2.1"
resolved "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz"
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"
integrity sha512-yPpxc4ZR2makceA9hy/jHNqc7QVkd4Je/N0WRHm6bs3PtivPuPynxE5ejU/mp5EhnCv8+uZL7vhz8rkluSlx+Q==
reselect@^4.1.7:
version "4.1.7"
resolved "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz"
integrity sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A==
reselect@^4.1.8:
version "4.1.8"
resolved "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz"
integrity sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==
resolve-from@^4.0.0:
version "4.0.0"
@@ -5187,7 +5229,7 @@ use-sidecar@^1.1.2:
detect-node-es "^1.1.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"
resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==