feat(Map with bars): added the map with bars provided by opendatasoft

TODO: -modal to invite match to bar

-use overpass ? (openStreetMap API)
This commit is contained in:
Laurian-Dufrechou
2023-04-24 22:38:08 +02:00
parent 4889426643
commit 57f97b8e22
15 changed files with 4209 additions and 3819 deletions
+65
View File
@@ -9,6 +9,7 @@
"version": "0.1.0",
"dependencies": {
"@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",
@@ -24,12 +25,15 @@
"formidable": "^2.1.1",
"framer-motion": "^10.8.5",
"fs": "^0.0.1-security",
"leaflet": "^1.9.3",
"leaflet.markercluster": "^1.5.3",
"next": "13.2.3",
"next-auth": "^4.20.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hook-form": "^7.43.5",
"react-icons": "^4.8.0",
"react-leaflet": "^4.2.1",
"react-query": "^3.39.3",
"socket.io": "^4.6.1",
"socket.io-client": "^4.6.1"
@@ -1316,6 +1320,23 @@
"react": ">=18"
}
},
"node_modules/@christopherpickering/react-leaflet-markercluster": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@christopherpickering/react-leaflet-markercluster/-/react-leaflet-markercluster-1.1.0.tgz",
"integrity": "sha512-69Q3c/Szq7vXNSq6wy+wi6Wj4yHHVxzAuJMiFMgTcoyuZO4EQj8a6qzOc/XdcuQbNX+gfFe2Me/C61bc6sjO4g==",
"dependencies": {
"@react-leaflet/core": "^2.1.0",
"leaflet": "^1.9.3",
"leaflet.markercluster": "^1.5.3",
"postcss-flexbugs-fixes": "^5.0.2",
"react-leaflet": "^4.0.0"
},
"peerDependencies": {
"leaflet": "^1.9.3",
"leaflet.markercluster": "^1.5.3",
"react-leaflet": "^4.0.0"
}
},
"node_modules/@cspotcode/source-map-support": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
@@ -2145,6 +2166,16 @@
"integrity": "sha512-jh9ajkGl3Lw9uTKEwlNBSh79a1v6C1JducqxakgkQyLev6kG1xxkuc2rDGrIDxUdpCOuuPRjuSCHhjnv7qLLgQ==",
"optional": true
},
"node_modules/@react-leaflet/core": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-2.1.0.tgz",
"integrity": "sha512-Qk7Pfu8BSarKGqILj4x7bCSZ1pjuAPZ+qmRwH5S7mDS91VSbVVsJSrW4qA+GPrro8t69gFYVMWb1Zc4yFmPiVg==",
"peerDependencies": {
"leaflet": "^1.9.0",
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
},
"node_modules/@reduxjs/toolkit": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.3.tgz",
@@ -5646,6 +5677,19 @@
"safe-buffer": "~5.1.0"
}
},
"node_modules/leaflet": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.3.tgz",
"integrity": "sha512-iB2cR9vAkDOu5l3HAay2obcUHZ7xwUBBjph8+PGtmW/2lYhbLizWtG7nTeYht36WfOslixQF9D/uSIzhZgGMfQ=="
},
"node_modules/leaflet.markercluster": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/leaflet.markercluster/-/leaflet.markercluster-1.5.3.tgz",
"integrity": "sha512-vPTw/Bndq7eQHjLBVlWpnGeLa3t+3zGiuM7fJwCkiMFq+nmRuG3RI3f7f4N4TDX7T4NpbAXpR2+NTRSEGfCSeA==",
"peerDependencies": {
"leaflet": "^1.3.1"
}
},
"node_modules/levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -6735,6 +6779,14 @@
"node": "^10 || ^12 || >=14"
}
},
"node_modules/postcss-flexbugs-fixes": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-5.0.2.tgz",
"integrity": "sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ==",
"peerDependencies": {
"postcss": "^8.1.4"
}
},
"node_modules/preact": {
"version": "10.13.2",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.13.2.tgz",
@@ -6966,6 +7018,19 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/react-leaflet": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-4.2.1.tgz",
"integrity": "sha512-p9chkvhcKrWn/H/1FFeVSqLdReGwn2qmiobOQGO3BifX+/vV/39qhY8dGqbdcPh1e6jxh/QHriLXr7a4eLFK4Q==",
"dependencies": {
"@react-leaflet/core": "^2.1.0"
},
"peerDependencies": {
"leaflet": "^1.9.0",
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
},
"node_modules/react-query": {
"version": "3.39.3",
"resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.3.tgz",
+4
View File
@@ -10,6 +10,7 @@
},
"dependencies": {
"@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",
@@ -25,12 +26,15 @@
"formidable": "^2.1.1",
"framer-motion": "^10.8.5",
"fs": "^0.0.1-security",
"leaflet": "^1.9.3",
"leaflet.markercluster": "^1.5.3",
"next": "13.2.3",
"next-auth": "^4.20.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hook-form": "^7.43.5",
"react-icons": "^4.8.0",
"react-leaflet": "^4.2.1",
"react-query": "^3.39.3",
"socket.io": "^4.6.1",
"socket.io-client": "^4.6.1"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

+25
View File
@@ -0,0 +1,25 @@
import { MdError } from "react-icons/md";
import { Button, Text, VStack } from "@chakra-ui/react";
import { useRouter } from "next/router";
export default function LoadingPage() {
const router = useRouter();
return (
<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>
</VStack>
);
}
+3 -1
View File
@@ -9,7 +9,6 @@ import {
} from "@chakra-ui/react";
import { useRouter } from "next/router";
import { signOut, useSession } from "next-auth/react";
import Nextlink from "next/link";
type Props = {
variant: "static" | "fixed";
@@ -73,6 +72,9 @@ export default function Navbar({ variant = "fixed" }: Props) {
<Link href={"/userProfile"} color={"purple.500"}>
Profile
</Link>
<Link href={"/map"} color={"purple.500"}>
Carte
</Link>
</>
) : (
<>
@@ -13,6 +13,7 @@ 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 LeftPanelButton from "@/components/layout/dashboard/left_panel/LeftPanelButton";
import { signOut } from "next-auth/react";
@@ -69,6 +70,12 @@ export default function LeftPanel(props) {
spacing={3.5}
alignContent={"bottom"}
>
<LeftPanelButton
leftIcon={<FaMapMarkedAlt />}
onClickHandler={() => router.push("/map")}
>
Carte
</LeftPanelButton>
<LeftPanelButton
leftIcon={<AiFillMessage />}
onClickHandler={() => router.push("/dashboard")}
@@ -0,0 +1,68 @@
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 MarkerBar from "./MarkerBar";
import "leaflet/dist/leaflet.css";
export default function MapComponent(props) {
const { location, listBars } = props;
const toast = useToast({ position: "top" });
const idToastError = "error_location";
const userIcon = new L.Icon({
iconUrl: "logo.svg",
iconSize: [35, 35],
});
return (
<>
{location[0] === null || location[1] === null ? (
<>
{!toast.isActive(idToastError)
? toast({
title: "Erreur",
id: idToastError,
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}
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'
/>
{/* Pour le user */}
<Marker icon={userIcon} position={location}></Marker>
<MarkerClusterGroup chunkedLoading showCoverageOnHover={false}>
{listBars.map((bar, index) => {
return <MarkerBar key={index} bar={bar} />;
})}
</MarkerClusterGroup>
</MapContainer>
</Flex>
)}
</>
);
}
+33
View File
@@ -0,0 +1,33 @@
import { Marker, Popup } from "react-leaflet";
import "leaflet/dist/leaflet.css";
import { Box, Button, Card, CardBody, Text } from "@chakra-ui/react";
export default function MarkerBar(props) {
const { bar } = props;
//changer l'icon
const barIcon = new L.Icon({
iconUrl: "drink_cocktail.png",
iconSize: [35, 35],
popupAnchor: [0, -5],
backgroundColor: "purple",
});
const location = [bar.geo_point_2d.lat, bar.geo_point_2d.lon];
//mettre un tooltip...
return (
<Marker icon={barIcon} position={location}>
<Popup>
<Box alignItems={"center"}>
<Text fontSize={"1rem"} fontWeight={"bold"} align={"center"}>
{bar.name || '"Nom du bar"'}
</Text>
<Button colorScheme={"purple"} variant={"outline"}>
Inviter un match
</Button>
</Box>
</Popup>
</Marker>
);
}
+11 -14
View File
@@ -1,7 +1,7 @@
import { Grid, GridItem, Text, Box, useToast } from "@chakra-ui/react";
import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { useState } from "react";
import type { Session } from "@/models/auth/Session";
import CardUser from "../components/layout/dashboard/card_user/CardUser";
@@ -16,10 +16,8 @@ import LoadingPage from "@/components/LoadingPage";
export default function Dashboard() {
const router = useRouter();
const toast = useToast({ position: "top", isClosable: true });
const [userLikes, setUserLikes] = useState();
const [userDislikes, setUserDislikes] = useState();
const [preferences, setPreferences] = useState(null);
const [userLikes, setUserLikes] = useState([] as string[]);
const [userDislikes, setUserDislikes] = useState([] as string[]);
const { data: session, status } = useSession();
@@ -37,13 +35,6 @@ export default function Dashboard() {
setUserDislikes([...user.UserDislikesID]);
setUserLikes([...user.UserLikesID]);
setPreferences([
user.prefGender,
user.ageMin,
user.ageMax,
user.distance,
]);
return fetch(`/api/users/${user.id}`)
.then((res) => {
return res.json();
@@ -68,7 +59,14 @@ export default function Dashboard() {
enabled: status === "authenticated" && !isLoading,
queryFn: async () => {
return fetch(
`/api/user/userDashboard?preferences=${preferences}&excludedId=${loggedUser.id}&userLikes=${loggedUser.UserLikesID}&userDislikes=${loggedUser.UserDislikesID}`
`/api/user/userDashboard?preferences=${[
loggedUser.prefGender,
loggedUser.ageMin,
loggedUser.ageMax,
loggedUser.distance,
]}&excludedId=${loggedUser.id}&userLikes=${
loggedUser.UserLikesID
}&userDislikes=${loggedUser.UserDislikesID}`
) //exclure les profils déjà like ou dislike
.then((res) => res.json())
.catch((err) => {
@@ -89,7 +87,6 @@ export default function Dashboard() {
position: "top",
});
if (status === "unauthenticated") router.push("/");
return <span>Error: {error.message}</span>;
}
return (
+146
View File
@@ -0,0 +1,146 @@
import { useEffect, useState } from "react";
import dynamic from "next/dynamic";
import { useToast } from "@chakra-ui/react";
import "leaflet/dist/leaflet.css";
import { useSession } from "next-auth/react";
import { useMutation, useQuery } from "@tanstack/react-query";
import LoadingPage from "@/components/LoadingPage";
import ErrorPage from "@/components/ErrorPage";
import Navbar from "@/components/Navbar";
export default function Map() {
const [location, setLocation] = useState([
null as unknown as number,
null as unknown as number,
]);
const toast = useToast({ position: "bottom" });
const idSaveToast = "saved_location";
const { data: session, status } = useSession();
//faire un useQuery pour récupérer les bars et restaurants et en meme temps regarder si l'user a une location
//Si oui, faire une mutation avec navigator.geolocation.getCurrentPosition pour mettre à jour la location de l'user dans la bdd
// cet url : https://data.opendatasoft.com/api/v2/catalog/datasets/osm-fr-bars%40babel/records?limit=100&offset=0&lang=fr&timezone=UTC
// ou : https://data.opendatasoft.com/api/v2/catalog/datasets/osm-fr-bars%40babel/exports/json?limit=-1&offset=0&lang=fr&timezone=UTC MAIS RENVOI UN file
const {
isLoading,
isError,
data: loggedUser,
error,
} = useQuery({
queryKey: ["LoggedUser"],
enabled: status === "authenticated",
queryFn: async () => {
const { user } = session as unknown as Session;
return fetch(`/api/users/${user.id}`)
.then((res) => {
return res.json();
})
.catch((err) => {
return err;
});
},
});
const {
data: listBars,
isError: isErrorListBars,
isLoading: isLoadingListBars,
error: errorListBars,
} = useQuery({
queryKey: ["listBars"],
enabled: !isLoading && location[0] !== null,
queryFn: async () => {
let urlBars = new URL(
"https://data.opendatasoft.com/api/v2/catalog/datasets/osm-fr-bars%40babel/exports/json?"
);
const coordinates = location[1].toString() + " " + location[0].toString();
urlBars.searchParams.append(
"where",
`distance(geo_point_2d,geom'POINT(${coordinates})',75km)`
);
urlBars.searchParams.append("limit", "-1");
urlBars.searchParams.append("offset", "0");
urlBars.searchParams.append("timezone", `UTC`);
return fetch(urlBars.toString())
.then((res) => res.json())
.catch((err) => {
return err;
});
},
});
const userSetLocation = useMutation({
mutationKey: "userSetLocation",
enabled: !isLoading && !loggedUser,
mutationFn: async (position: string) => {
const pos = {
location: position,
};
return fetch(`/api/users/${loggedUser.id}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(pos),
})
.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();
})
.catch((err) => {
return err;
});
},
});
useEffect(() => {
navigator.geolocation.getCurrentPosition((position) => {
setLocation([position.coords.latitude, position.coords.longitude]);
userSetLocation.mutate(
`${position.coords.latitude},${position.coords.longitude}`
);
});
}, []);
const MapWithNoSSR = dynamic(
() => import("../components/layout/map/MapComponent"),
{
ssr: false,
}
);
return (
<>
{isLoading || isLoadingListBars ? (
<LoadingPage />
) : isError || isErrorListBars ? (
<ErrorPage />
) : (
<>
<Navbar />
<MapWithNoSSR
location={location}
loggedUser={loggedUser}
listBars={listBars}
/>
</>
)}
</>
);
}
+5 -6
View File
@@ -53,9 +53,9 @@ export default function UserProfile() {
const [passions, setPassions] = useState(null);
const [showTooltipAge, setShowTooltipAge] = useState(true);
const [sliderAgeValue, setSliderAgeValue] = useState([]);
const [showTooltipDistance, setShowTooltipDistance] = useState(true);
const [sliderDistanceValue, setSliderDistanceValue] = useState([]);
const [sliderAgeValue, setSliderAgeValue] = useState([] as number[]);
// const [showTooltipDistance, setShowTooltipDistance] = useState(true);
// const [sliderDistanceValue, setSliderDistanceValue] = useState([]);
const {
handleSubmit,
@@ -88,7 +88,7 @@ export default function UserProfile() {
const { user } = session as unknown as Session;
setSliderAgeValue([user.ageMin, user.ageMax]);
setSliderDistanceValue(user.distance);
// setSliderDistanceValue(user.distance);
return fetch(`/api/users/${user.id}`)
.then((res) => res.json())
@@ -110,10 +110,9 @@ export default function UserProfile() {
position: "top",
});
if (status === "unauthenticated") router.push("/");
return <span>Error: {error.message}</span>;
}
const getTextGender = (gender) => {
const getTextGender = (gender: string) => {
switch (gender) {
case Gender.MALE:
return "Homme";
+3842 -3798
View File
File diff suppressed because it is too large Load Diff