feat(invite Match to bar / create chat on match): Modal to invite match to bar/ create chat on match

This commit is contained in:
Laurian-Dufrechou
2023-05-18 17:03:45 +02:00
parent 5b2e9ebafc
commit 8e021a833d
15 changed files with 598 additions and 231 deletions
+16
View File
@@ -44,6 +44,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/bcrypt": "^5.0.0", "@types/bcrypt": "^5.0.0",
"@types/leaflet": "^1.9.3",
"@types/node": "^18.15.1", "@types/node": "^18.15.1",
"next-transpile-modules": "^10.0.0", "next-transpile-modules": "^10.0.0",
"prisma": "^4.11.0", "prisma": "^4.11.0",
@@ -2341,6 +2342,12 @@
"@types/ms": "*" "@types/ms": "*"
} }
}, },
"node_modules/@types/geojson": {
"version": "7946.0.10",
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz",
"integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==",
"dev": true
},
"node_modules/@types/hoist-non-react-statics": { "node_modules/@types/hoist-non-react-statics": {
"version": "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", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
@@ -2355,6 +2362,15 @@
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="
}, },
"node_modules/@types/leaflet": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.3.tgz",
"integrity": "sha512-Caa1lYOgKVqDkDZVWkto2Z5JtVo09spEaUt2S69LiugbBpoqQu92HYFMGUbYezZbnBkyOxMNPXHSgRrRY5UyIA==",
"dev": true,
"dependencies": {
"@types/geojson": "*"
}
},
"node_modules/@types/lodash": { "node_modules/@types/lodash": {
"version": "4.14.192", "version": "4.14.192",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.192.tgz", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.192.tgz",
+1
View File
@@ -45,6 +45,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/bcrypt": "^5.0.0", "@types/bcrypt": "^5.0.0",
"@types/leaflet": "^1.9.3",
"@types/node": "^18.15.1", "@types/node": "^18.15.1",
"next-transpile-modules": "^10.0.0", "next-transpile-modules": "^10.0.0",
"prisma": "^4.11.0", "prisma": "^4.11.0",
+8 -3
View File
@@ -2,12 +2,17 @@ import { MdError } from "react-icons/md";
import { Button, Text, VStack } from "@chakra-ui/react"; import { Button, Text, VStack } from "@chakra-ui/react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
export default function LoadingPage() { export default function ErrorGeolocation() {
const router = useRouter(); const router = useRouter();
return ( return (
<VStack justifyContent="center" alignItems="center" height="100vh" <VStack
width="100vw" bg="gray.100"> justifyContent="center"
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
@@ -49,7 +49,7 @@ export default function LeftPanel(props) {
</Flex> </Flex>
<Divider mb={"1rem"} /> <Divider mb={"1rem"} />
<Text as="i" fontWeight={"bold"}> <Text as="i" fontWeight={"bold"}>
&quot;{user.bio}&quot; {user.bio && `"${user.bio}"`}
</Text> </Text>
</Box> </Box>
</Box> </Box>
@@ -14,7 +14,6 @@ import {
Text, Text,
useDisclosure, useDisclosure,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import { SetStateAction } from "react";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { User } from "@prisma/client"; import { User } from "@prisma/client";
import Carousel from "@/components/Carousel"; import Carousel from "@/components/Carousel";
@@ -111,7 +110,7 @@ export default function ModalMatch({ notif, loggedUser }: modalMatchProps) {
</Text> </Text>
</Box> </Box>
<Box> <Box>
<Heading size={"sm"} fontWeight={"bold"}> <Heading size={"sm"} mt={4} fontWeight={"bold"}>
Passions : Passions :
</Heading> </Heading>
{passionLoading ? ( {passionLoading ? (
@@ -0,0 +1,83 @@
import {
Box,
Card,
CardBody,
chakra,
Flex,
GridItem,
Image,
Spacer,
Text,
useRadio,
} from "@chakra-ui/react";
import { Notification } from "@prisma/client";
import { useQuery } from "@tanstack/react-query";
type CardMatchedUserProps = {
notif: Notification;
};
export default function CardMatchedUser({
notif,
...radioProps
}: CardMatchedUserProps) {
const {
isLoading,
isError,
data: matchedUser,
error,
} = useQuery({
refetchOnWindowFocus: false,
queryKey: ["matchUser", notif.MatchedUserID],
queryFn: async () => {
return fetch(`/api/users/${notif.MatchedUserID}?include=Chat`)
.then((res) => {
return res.json();
})
.catch((err) => {
return err;
});
},
});
const { state, getInputProps, getRadioProps, htmlProps, getLabelProps } =
useRadio(radioProps);
return (
<>
{!isLoading ? (
<chakra.label {...htmlProps} cursor="pointer">
<input {...getInputProps()} hidden />
<GridItem height={"100%"}>
<Card
{...getRadioProps()}
height={"100%"}
outline={state.isChecked ? "3px solid" : "none"}
outlineColor={state.isChecked ? "purple.500" : "none"}
bg={state.isChecked ? "purple.50" : "white"}
>
<CardBody>
<Flex
height={"100%"}
direction={"column"}
gap={"1rem"}
justifyContent={"space-between"}
{...getLabelProps()}
>
<Image src={matchedUser.images[0]}></Image>
<Text
align={"center"}
fontSize={"1.5rem"}
fontWeight={"bold"}
>
{matchedUser.firstName} {matchedUser.lastName}
</Text>
</Flex>
</CardBody>
</Card>
</GridItem>
</chakra.label>
) : null}
</>
);
}
+24 -19
View File
@@ -1,22 +1,23 @@
import {MapContainer, TileLayer, Marker, Popup} from 'react-leaflet'; import { MapContainer, TileLayer, Marker, useMapEvents } from "react-leaflet";
import {Flex, Text, useToast} from '@chakra-ui/react'; import { Flex, Text, useToast } from "@chakra-ui/react";
import MarkerClusterGroup import MarkerClusterGroup from "@christopherpickering/react-leaflet-markercluster";
from '@christopherpickering/react-leaflet-markercluster'; import L from "leaflet";
import { memo, useState } from "react";
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],
}); });
@@ -26,10 +27,10 @@ export default function MapComponent(props: any) {
<> <>
{!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,
})} })}
@@ -38,10 +39,13 @@ export default function MapComponent(props: any) {
</Text> </Text>
</> </>
) : ( ) : (
<Flex> <MapContainer
<MapContainer center={location} zoom={13} minZoom={8} center={location}
zoom={13}
minZoom={8}
zoomControl={false} zoomControl={false}
style={{width: '100vw', height: '100vw'}}> style={{ width: "100vw", height: "100vw" }}
>
<TileLayer <TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
@@ -50,10 +54,11 @@ export default function MapComponent(props: any) {
<Marker icon={userIcon} position={location}></Marker> <Marker icon={userIcon} position={location}></Marker>
<MarkerClusterGroup chunkedLoading showCoverageOnHover={false}> <MarkerClusterGroup chunkedLoading showCoverageOnHover={false}>
{listBars.map((bar: any, index: number) => <MarkerBar key={index} bar={bar}/>)} {listBars.map((bar: any, index: number) => (
<MarkerBar key={index} bar={bar} />
))}
</MarkerClusterGroup> </MarkerClusterGroup>
</MapContainer> </MapContainer>
</Flex>
)} )}
</> </>
); );
+13 -8
View File
@@ -1,11 +1,14 @@
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, Text } from "@chakra-ui/react"; import { Box, Button, Text, useDisclosure } from "@chakra-ui/react";
import ModalInviteBar from "./ModalInviteBar";
import L, { LatLngTuple } from "leaflet";
export default function MarkerBar(props) { export default function MarkerBar(props: any) {
const { bar } = props; const { bar } = props;
//changer l'icon const { isOpen, onClose, onOpen } = useDisclosure();
const barIcon = new L.Icon({ const barIcon = new L.Icon({
iconUrl: "drink_cocktail.png", iconUrl: "drink_cocktail.png",
iconSize: [35, 35], iconSize: [35, 35],
@@ -13,17 +16,19 @@ export default function MarkerBar(props) {
backgroundColor: "purple", backgroundColor: "purple",
}); });
const location = [bar.geo_point_2d.lat, bar.geo_point_2d.lon]; const location = [bar.geo_point_2d.lat, bar.geo_point_2d.lon] as LatLngTuple;
//mettre un tooltip...
return ( return (
<Marker icon={barIcon} position={location}> <Marker icon={barIcon} position={location}>
<Popup> <Popup>
<Box alignItems={"center"}> <Box align={"center"}>
<Text fontSize={"1rem"} fontWeight={"bold"} align={"center"}> <Text fontSize={"1rem"} fontWeight={"bold"} align={"center"}>
{bar.name || '"Nom du bar"'} {bar.name}
</Text> </Text>
<Button variant={"outline"}>Inviter un match</Button> <Button variant={"outline"} onClick={onOpen}>
Inviter un match
</Button>
<ModalInviteBar bar={bar} isOpen={isOpen} onClose={onClose} />
</Box> </Box>
</Popup> </Popup>
</Marker> </Marker>
@@ -0,0 +1,161 @@
import {
Box,
Button,
Card,
CardBody,
Flex,
Grid,
GridItem,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
Text,
useRadio,
useRadioGroup,
useToast,
} from "@chakra-ui/react";
import { Notification, NotificationType, User } from "@prisma/client";
import { useMutation, useQuery } from "@tanstack/react-query";
import { useSession } from "next-auth/react";
import type { Session } from "@/models/auth/Session";
import CardMatchedUser from "./CardMatchedUser";
import { Router } from "next/router";
import { log } from "console";
type ModalInviteBarProps = {
isOpen: boolean;
onClose: () => void;
bar: any;
};
export default function ModalInviteBar({
isOpen,
onClose,
bar,
}: ModalInviteBarProps) {
const { data: session, status } = useSession();
const toast = useToast();
const { value, getRadioProps, getRootProps } = useRadioGroup({
defaultValue: "-1",
});
const inviteToBar = useMutation({
mutationFn: async (id) => {
const inviteOptions = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
idUser: id,
idUserMatched: value,
bar: bar,
}),
};
try {
const res = await fetch(`/api/user/inviteToBar`, inviteOptions);
const response = await res.json();
return response;
} catch (err) {
return err;
}
},
onSuccess: (data) => {
if (data.error) {
toast({
title: "Erreur",
description: "Une erreur est survenue",
status: "error",
duration: 2000,
});
return;
}
console.log(data);
},
});
const {
isLoading,
isError,
data: notificationMatch,
error,
} = useQuery({
queryKey: ["notificationMatch"],
enabled: status === "authenticated",
queryFn: async () => {
const { user } = session as unknown as Session;
return fetch(
`/api/notifications?where={"$and":{"UserID":"${user.id}", "type":"${NotificationType.NEW_MATCH}"}}`
)
.then((res) => {
return res.json();
})
.catch((err) => {
return err;
});
},
});
console.log(bar);
return (
<>
<Modal
blockScrollOnMount={false}
size={"2xl"}
isOpen={isOpen}
onClose={onClose}
scrollBehavior={"inside"}
>
<ModalContent>
<ModalHeader display={"flex"} alignItems={"center"} gap={2}>
Inviter au bar : {bar.name}
<Box as="span" fontSize={"3xl"}>
🍻
</Box>
</ModalHeader>
<ModalBody>
<Grid
{...getRootProps()}
templateColumns={`repeat(${notificationMatch?.length}, 1fr)`}
gap={6}
>
{!isError && !error
? notificationMatch?.map((notif: Notification) => {
return (
<CardMatchedUser
key={notif.MatchedUserID}
notif={notif}
{...getRadioProps({ value: notif.MatchedUserID })}
/>
);
})
: null}
</Grid>
</ModalBody>
<ModalFooter>
<Button
mr={3}
isDisabled={value === "-1"}
onClick={() => {
const { user } = session as unknown as Session;
inviteToBar.mutate(user.id);
onClose();
// Router.push("/chat");
}}
>
Inviter
</Button>
<Button variant="ghost" onClick={onClose}>
Annuler
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</>
);
}
@@ -14,60 +14,58 @@ import {
ModalOverlay, ModalOverlay,
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 {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();
}) })
.catch(() => { .catch(() => {
setIsLoading(false);
toast({ toast({
title: `Erreur lors de l'ajout des images`, title: `Erreur lors de l'ajout des images`,
status: 'error', status: "error",
isClosable: true, isClosable: true,
}); });
}); });
}) })
.catch(err => { .catch((err) => {
toast({ toast({
title: `Erreur lors de l'ajout des images`, title: `Erreur lors de l'ajout des images`,
status: 'error', status: "error",
isClosable: true, isClosable: true,
}); });
console.log(err); console.log(err);
@@ -84,14 +82,14 @@ export default function ModalModifyImages(props) {
} }
const imageDeleteOptions = { const imageDeleteOptions = {
method: 'DELETE', method: "DELETE",
body: JSON.stringify({fileName: fileName.split('/').pop()}), body: JSON.stringify({ fileName: fileName.split("/").pop() }),
}; };
fetch(`/api/file/deleteFile`, imageDeleteOptions).then((res) => { fetch(`/api/file/deleteFile`, imageDeleteOptions).then((res) => {
const imagePatchOptions = { const imagePatchOptions = {
method: 'PUT', method: "PUT",
headers: {'Content-Type': 'application/json'}, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ body: JSON.stringify({
images: [...listImage], images: [...listImage],
}), }),
@@ -100,14 +98,14 @@ export default function ModalModifyImages(props) {
.then((res) => { .then((res) => {
toast({ toast({
title: `Suppression d'image effectué`, title: `Suppression d'image effectué`,
status: 'success', status: "success",
isClosable: true, isClosable: true,
}); });
}) })
.catch(() => { .catch(() => {
toast({ toast({
title: `Erreur lors de la suppression des images`, title: `Erreur lors de la suppression des images`,
status: 'error', status: "error",
isClosable: true, isClosable: true,
}); });
}); });
@@ -116,60 +114,75 @@ export default function ModalModifyImages(props) {
return ( return (
<> <>
<Button onClick={onOpen} leftIcon={<RiEditBoxLine/>}> <Button onClick={onOpen} leftIcon={<RiEditBoxLine />}>
Modifier les images Modifier les images
</Button> </Button>
<Modal blockScrollOnMount={false} size={'4xl'} isOpen={isOpen} <Modal
onClose={onClose} scrollBehavior={'inside'}> blockScrollOnMount={false}
<ModalOverlay/> size={"4xl"}
isOpen={isOpen}
onClose={onClose}
scrollBehavior={"inside"}
>
<ModalOverlay />
<ModalContent> <ModalContent>
<ModalHeader>Modification des images</ModalHeader> <ModalHeader>Modification des images</ModalHeader>
<ModalCloseButton/> <ModalCloseButton />
<ModalBody> <ModalBody>
<Grid templateColumns={`repeat(${listImage.length + 1}, 1fr)`} <Grid
gap={5}> templateColumns={`repeat(${listImage.length + 1}, 1fr)`}
gap={5}
>
{listImage.map((image, index) => ( {listImage.map((image, index) => (
<GridItem key={index}> <GridItem key={index}>
<Flex direction={'column'} gap={'1rem'}> <Flex direction={"column"} gap={"1rem"}>
<Image src={image}/> <Image src={image} />
<Button id={index.toString()} colorScheme={'red'} <Button
onClick={() => id={index.toString()}
deleteImage(`${listImage[index]}`)}> colorScheme={"red"}
onClick={() => deleteImage(`${listImage[index]}`)}
>
Supprimer l'image Supprimer l'image
</Button> </Button>
</Flex> </Flex>
</GridItem> </GridItem>
))} ))}
{listImage.length < 5 && ( {listImage.length < 5 && (
<GridItem width={'100%'}> <GridItem width={"100%"}>
<Input type={'file'} height={'100%'} <Input
accept={'image/png, image/jpeg, image/webp'} type={"file"}
onInput={({target}) => { height={"100%"}
accept={"image/png, image/jpeg, image/webp"}
onInput={({ target }) => {
const date = new Date(); const date = new Date();
const time = date.getTime(); const time = date.getTime();
const file = target.files[0]; const file = target.files[0];
const extension = file.name.split('.').pop(); const extension = file.name.split(".").pop();
const newFile = new File( const newFile = new File(
[file], [file],
`${user.id}_${time}.${extension}`, `${user.id}_${time}.${extension}`,
{ {
type: file.type, type: file.type,
}, }
); );
uploadImage(newFile); uploadImage(newFile);
}}/> }}
/>
</GridItem> </GridItem>
)} )}
</Grid> </Grid>
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<Button mr={3} onClick={() => { <Button
client.invalidateQueries('user'); mr={3}
onClick={() => {
client.invalidateQueries("user");
onClose(); onClose();
}}> }}
>
Save Save
</Button> </Button>
</ModalFooter> </ModalFooter>
+53
View File
@@ -0,0 +1,53 @@
import { log } from "console";
const { PrismaClient } = require("@prisma/client");
const post = async (req: any, res: any) => {
const prisma = new PrismaClient();
const { idUser, idUserMatched, bar } = req.body;
try {
const chat = await prisma.chat.findFirst({
where: {
AND: [
{ User: { some: { id: idUser } } },
{ User: { some: { id: idUserMatched } } },
],
},
include: {
User: true,
},
});
const message = await prisma.message.create({
data: {
text: `<!lon=${bar.geo_point_2d.lon},lat=${bar.geo_point_2d.lat},name=${bar.name}>`,
Chat: {
connect: {
id: chat.id,
},
},
User: {
connect: {
id: idUser,
},
},
},
});
res
.status(200)
.json({ message: "invitation envoyée", message_sent: message });
} catch (error) {
res.status(400).json({ message: "Something went wrong", error: error });
} finally {
await prisma.$disconnect();
}
};
export default (req: any, res: any) => {
req.method === "POST"
? post(req, res)
: res.status(404).send({ message: "Wrong method, please use POST" });
};
+8 -2
View File
@@ -50,9 +50,15 @@ const post = async (req, res) => {
}, },
}, },
}); });
}
//faire une notification mais faut qu'on regarde un meilleur moyen dans la BD await prisma.chat.create({
data: {
User: {
connect: [{ id: idUserLiked }, { id: idUser }],
},
},
});
}
await prisma.user.update({ await prisma.user.update({
where: { where: {
+27 -29
View File
@@ -1,14 +1,20 @@
import { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import { useToast } from "@chakra-ui/react";
import "leaflet/dist/leaflet.css";
import { useSession } from "next-auth/react"; import { useSession } from "next-auth/react";
import { useMutation, useQuery } from "@tanstack/react-query"; import { useMutation, useQuery } from "@tanstack/react-query";
import type { Session } from "@/models/auth/Session";
import LoadingPage from "@/components/LoadingPage"; import LoadingPage from "@/components/LoadingPage";
import ErrorPage from "@/components/ErrorPage"; import ErrorPage from "@/components/ErrorPage";
import ErrorGeolocation from "@/components/ErrorGeolocation";
import Navbar from "@/components/Navbar"; import Navbar from "@/components/Navbar";
import ErrorGeolocation from "@/components/ErrorGeoLocation";
const MapWithNoSSR = dynamic(
() => import("../components/layout/map/MapComponent"),
{
ssr: false,
}
);
export default function Map() { export default function Map() {
const [location, setLocation] = useState([ const [location, setLocation] = useState([
@@ -17,8 +23,6 @@ export default function Map() {
]); ]);
const [geolocationError, setGeolocationError] = useState(false); const [geolocationError, setGeolocationError] = useState(false);
const toast = useToast({ position: "bottom" });
const idSaveToast = "saved_location";
const { data: session, status } = useSession(); const { data: session, status } = useSession();
const [listBars, setListBars] = useState({} as unknown as any); const [listBars, setListBars] = useState({} as unknown as any);
@@ -29,6 +33,7 @@ export default function Map() {
error, error,
} = useQuery({ } = useQuery({
queryKey: ["LoggedUser"], queryKey: ["LoggedUser"],
refetchOnWindowFocus: false,
enabled: status === "authenticated", enabled: status === "authenticated",
queryFn: async () => { queryFn: async () => {
const { user } = session as unknown as Session; const { user } = session as unknown as Session;
@@ -50,8 +55,11 @@ export default function Map() {
error: errorListBars, error: errorListBars,
} = useQuery({ } = useQuery({
queryKey: ["listBars"], queryKey: ["listBars"],
refetchOnWindowFocus: false,
enabled: !isLoading && location[0] !== null, enabled: !isLoading && location[0] !== null,
queryFn: async () => { queryFn: async () => {
///Utiliser api de noratim
let urlBars = new URL( let urlBars = new URL(
"https://data.opendatasoft.com/api/v2/catalog/datasets/osm-fr-bars%40babel/exports/json?" "https://data.opendatasoft.com/api/v2/catalog/datasets/osm-fr-bars%40babel/exports/json?"
); );
@@ -80,7 +88,6 @@ export default function Map() {
}); });
const userSetLocation = useMutation({ const userSetLocation = useMutation({
mutationKey: "userSetLocation",
mutationFn: async (position: string) => { mutationFn: async (position: string) => {
const pos = { const pos = {
location: position, location: position,
@@ -94,14 +101,6 @@ export default function Map() {
body: JSON.stringify(pos), body: JSON.stringify(pos),
}) })
.then((res) => { .then((res) => {
// if (!toast.isActive(idSaveToast)) {
// toast({
// id: idSaveToast,
// title: "Position enregistrée",
// description: "Votre position a bien été enregistrée",
// status: "success",
// });
// }
res.json(); res.json();
}) })
.catch((err) => { .catch((err) => {
@@ -125,6 +124,13 @@ export default function Map() {
}, [loggedUser]); }, [loggedUser]);
function successPosition(position: GeolocationPosition) { function successPosition(position: GeolocationPosition) {
if (
position.coords.latitude === location[0] &&
position.coords.longitude === location[1]
) {
return;
}
setLocation([position.coords.latitude, position.coords.longitude]); setLocation([position.coords.latitude, position.coords.longitude]);
setGeolocationError(false); setGeolocationError(false);
@@ -138,29 +144,21 @@ export default function Map() {
setGeolocationError(true); setGeolocationError(true);
} }
const MapWithNoSSR = dynamic(
() => import("../components/layout/map/MapComponent"),
{
ssr: false,
}
);
return ( return (
<> <>
{geolocationError ? ( {geolocationError ? (
<ErrorGeolocation /> <ErrorGeolocation />
) : isError || isErrorListBars ? ( ) : isError || isErrorListBars ? (
<ErrorPage /> <ErrorPage />
) : isLoading || isLoadingListBars ? ( ) : isLoading ||
isLoadingListBars ||
location[0] === null ||
location[1] === null ? (
<LoadingPage /> <LoadingPage />
) : ( ) : (
<> <>
<Navbar /> <Navbar variant={"fixed"} />
<MapWithNoSSR <MapWithNoSSR location={location} listBars={listBars} />
location={location}
loggedUser={loggedUser}
listBars={listBars}
/>
</> </>
)} )}
</> </>
+37 -27
View File
@@ -9,15 +9,14 @@ import {
Center, Center,
Container, Container,
Divider, Divider,
Editable,
Flex, Flex,
FormHelperText,
FormLabel, FormLabel,
Text, Text,
useToast, useToast,
VStack, VStack,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import { formateDate } from "@/lib/formateDate";
import ModalModifyImages from "@/components/layout/profile/ModalModifyImages"; import ModalModifyImages from "@/components/layout/profile/ModalModifyImages";
import ModalChoosePassion from "@/components/layout/profile/ModalChoosePassion"; import ModalChoosePassion from "@/components/layout/profile/ModalChoosePassion";
import ProfileTagList from "@/components/layout/profile/ProfileTagList"; import ProfileTagList from "@/components/layout/profile/ProfileTagList";
@@ -39,6 +38,7 @@ export default function UserProfile() {
const [currentlyLoading, setCurrentlyLoading] = useState(false); const [currentlyLoading, setCurrentlyLoading] = useState(false);
const [passions, setPassions] = useState([] as any[]); const [passions, setPassions] = useState([] as any[]);
const [address, setAddress] = useState({} as any);
const [showTooltipAge, setShowTooltipAge] = useState(false); const [showTooltipAge, setShowTooltipAge] = useState(false);
const [sliderAgeValue, setSliderAgeValue] = useState([[] as number[]]); const [sliderAgeValue, setSliderAgeValue] = useState([[] as number[]]);
@@ -86,6 +86,22 @@ export default function UserProfile() {
}, },
}); });
const { data: addressData, isSuccess: addressIsSuccess } = useQuery({
queryKey: ["address"],
enabled: Boolean(userData?.location),
queryFn: async () => {
const [lat, long] = userData.location.split(",");
return fetch(
`https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=${lat}&lon=${long}`
)
.then((res) => res.json())
.catch((err) => {
return err;
});
},
});
if (isLoading) { if (isLoading) {
if (status === "unauthenticated") router.push("/login"); if (status === "unauthenticated") router.push("/login");
return <LoadingPage />; return <LoadingPage />;
@@ -131,7 +147,6 @@ export default function UserProfile() {
status: "success", status: "success",
isClosable: true, isClosable: true,
}); });
// router.reload();
}) })
.catch((err) => { .catch((err) => {
setCurrentlyLoading(false); setCurrentlyLoading(false);
@@ -146,27 +161,19 @@ export default function UserProfile() {
} }
}; };
const formateDate = (dateString: string) => {
var options = { year: "numeric", month: "long", day: "numeric" };
return new Date(dateString).toLocaleDateString([], options);
};
return ( return (
<Box bgColor={"purple.50"}> <Box bgColor={"purple.50"}>
<Container justifyContent={"center"} maxW={"70rem"} mt={"1rem"}> <Container justifyContent={"center"} maxW={"70rem"} mt={"1rem"}>
<Flex flexDirection={"column"} alignItems={"center"} gap={"1rem"}> <Flex flexDirection={"column"} alignItems={"center"} gap={"1rem"}>
<Box width={"50%"}> <Box width={"50%"}>
{userData.images ? (
<Carousel <Carousel
images={userData.images} images={
userData.images
? userData.images
: ["blank_profile_picture.webp"]
}
borderRadius={"1rem"} borderRadius={"1rem"}
></Carousel> ></Carousel>
) : (
<Carousel
images={userData.images}
borderRadius={"1rem"}
></Carousel>
)}
</Box> </Box>
<ModalModifyImages <ModalModifyImages
userData={userData} userData={userData}
@@ -179,7 +186,12 @@ export default function UserProfile() {
Modifiez les champs en les selectionnants Modifiez les champs en les selectionnants
</Text> </Text>
<Box as="form" onSubmit={handleSubmit(saveData)} width={"80%"}> <Box
position={"relative"}
as="form"
onSubmit={handleSubmit(saveData)}
width={"80%"}
>
<Flex width={"100%"} justify={"space-between"} mb={"1rem"}> <Flex width={"100%"} justify={"space-between"} mb={"1rem"}>
<Box> <Box>
<CustomEditable <CustomEditable
@@ -210,7 +222,7 @@ export default function UserProfile() {
<Box> <Box>
<CustomFalseEditable <CustomFalseEditable
id="birthdate" id="birthdate"
defaultValue={ value={
userData.birthdate === undefined || userData.birthdate === undefined ||
userData.birthdate === null userData.birthdate === null
? "Non renseigné" ? "Non renseigné"
@@ -225,13 +237,12 @@ export default function UserProfile() {
<Box> <Box>
<CustomFalseEditable <CustomFalseEditable
id="location" id="location"
isDisabled={true} value={
defaultValue={ !addressIsSuccess || addressData === undefined
userData.location === null || userData.location === "" ? "Rendez vous sur la carte !"
? "Rendez vous sur la carte" : `${addressData.address.town}, ${addressData.address.county}, ${addressData.address.state}`
: userData.location
} }
label={"Ville :"} label={"Adresse :"}
helperText={ helperText={
"Ce champ est modifié automatiquement depuis la carte" "Ce champ est modifié automatiquement depuis la carte"
} }
@@ -240,8 +251,7 @@ export default function UserProfile() {
<Box> <Box>
<CustomFalseEditable <CustomFalseEditable
id="email" id="email"
isDisabled={true} value={userData.email}
defaultValue={userData.email}
label={"Adresse mail :"} label={"Adresse mail :"}
/> />
</Box> </Box>
@@ -334,7 +344,7 @@ export default function UserProfile() {
/> */} /> */}
</Box> </Box>
<Divider colorScheme={"purple"} /> <Divider colorScheme={"purple"} />
<Center gap={"1rem"} my={"1rem"}> <Center position={"sticky"} bottom={0} gap={"1rem"} my={"1rem"}>
<Button <Button
colorScheme={"purple"} colorScheme={"purple"}
isLoading={currentlyLoading} isLoading={currentlyLoading}
+12
View File
@@ -1416,6 +1416,11 @@
dependencies: dependencies:
"@types/ms" "*" "@types/ms" "*"
"@types/geojson@*":
"integrity" "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA=="
"resolved" "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz"
"version" "7946.0.10"
"@types/hoist-non-react-statics@^3.3.1": "@types/hoist-non-react-statics@^3.3.1":
"integrity" "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==" "integrity" "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA=="
"resolved" "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz" "resolved" "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz"
@@ -1429,6 +1434,13 @@
"resolved" "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" "resolved" "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz"
"version" "0.0.29" "version" "0.0.29"
"@types/leaflet@^1.9.3":
"integrity" "sha512-Caa1lYOgKVqDkDZVWkto2Z5JtVo09spEaUt2S69LiugbBpoqQu92HYFMGUbYezZbnBkyOxMNPXHSgRrRY5UyIA=="
"resolved" "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.3.tgz"
"version" "1.9.3"
dependencies:
"@types/geojson" "*"
"@types/lodash.mergewith@4.6.7": "@types/lodash.mergewith@4.6.7":
"integrity" "sha512-3m+lkO5CLRRYU0fhGRp7zbsGi6+BZj0uTVSwvcKU+nSlhjA9/QRNfuSGnD2mX6hQA7ZbmcCkzk5h4ZYGOtk14A==" "integrity" "sha512-3m+lkO5CLRRYU0fhGRp7zbsGi6+BZj0uTVSwvcKU+nSlhjA9/QRNfuSGnD2mX6hQA7ZbmcCkzk5h4ZYGOtk14A=="
"resolved" "https://registry.npmjs.org/@types/lodash.mergewith/-/lodash.mergewith-4.6.7.tgz" "resolved" "https://registry.npmjs.org/@types/lodash.mergewith/-/lodash.mergewith-4.6.7.tgz"