diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 06598441..98e5bdf2 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -25,9 +25,11 @@ "formik": "^2.4.4", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-leaflet": "^4.2.1", "react-pro-sidebar": "^0.7.1", "react-router-dom": "^6.16.0", "react-scripts": "5.0.1", + "react-simple-maps": "^3.0.0", "recharts": "^2.8.0", "web-vitals": "^2.1.4", "yup": "^1.2.0" @@ -4173,6 +4175,16 @@ "url": "https://opencollective.com/popperjs" } }, + "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/@react-spring/animated": { "version": "9.7.3", "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.7.3.tgz", @@ -7446,6 +7458,20 @@ "delaunator": "4" } }, + "node_modules/d3-dispatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-2.0.0.tgz", + "integrity": "sha512-S/m2VsXI7gAti2pBoLClFFTMOO1HTtT0j99AuXLoGFKO6deHDdnv6ZGTxSTTUTgO1zVcv82fCOtDjYK4EECmWA==" + }, + "node_modules/d3-drag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-2.0.0.tgz", + "integrity": "sha512-g9y9WbMnF5uqB9qKqwIIa/921RYWzlUDv9Jl1/yONQwxbOfszAWTCm8u7HOTgJgRDXiRZN56cHT9pd24dmXs8w==", + "dependencies": { + "d3-dispatch": "1 - 2", + "d3-selection": "2" + } + }, "node_modules/d3-ease": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", @@ -7527,6 +7553,11 @@ "d3-array": "2" } }, + "node_modules/d3-selection": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-2.0.0.tgz", + "integrity": "sha512-XoGGqhLUN/W14NmaqcO/bb1nqjDAw5WtSYb2X8wiuQWvSZUsUVYsOSkOybUrNvcBjaywBdYPy03eXHMXjk9nZA==" + }, "node_modules/d3-shape": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", @@ -7556,6 +7587,69 @@ "node": ">=12" } }, + "node_modules/d3-transition": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-2.0.0.tgz", + "integrity": "sha512-42ltAGgJesfQE3u9LuuBHNbGrI/AJjNL2OAUdclE70UE6Vy239GCBEYD38uBPoLeNsOhFStGpPI0BAOV+HMxog==", + "dependencies": { + "d3-color": "1 - 2", + "d3-dispatch": "1 - 2", + "d3-ease": "1 - 2", + "d3-interpolate": "1 - 2", + "d3-timer": "1 - 2" + }, + "peerDependencies": { + "d3-selection": "2" + } + }, + "node_modules/d3-transition/node_modules/d3-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-2.0.0.tgz", + "integrity": "sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==" + }, + "node_modules/d3-transition/node_modules/d3-ease": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-2.0.0.tgz", + "integrity": "sha512-68/n9JWarxXkOWMshcT5IcjbB+agblQUaIsbnXmrzejn2O82n3p2A9R2zEB9HIEFWKFwPAEDDN8gR0VdSAyyAQ==" + }, + "node_modules/d3-transition/node_modules/d3-interpolate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-2.0.1.tgz", + "integrity": "sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ==", + "dependencies": { + "d3-color": "1 - 2" + } + }, + "node_modules/d3-transition/node_modules/d3-timer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-2.0.0.tgz", + "integrity": "sha512-TO4VLh0/420Y/9dO3+f9abDEFYeCUr2WZRlxJvbp4HPTQcSylXNiL6yZa9FIUvV1yRiFufl1bszTCLDqv9PWNA==" + }, + "node_modules/d3-zoom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-2.0.0.tgz", + "integrity": "sha512-fFg7aoaEm9/jf+qfstak0IYpnesZLiMX6GZvXtUSdv8RH2o4E2qeelgdU09eKS6wGuiGMfcnMI0nTIqWzRHGpw==", + "dependencies": { + "d3-dispatch": "1 - 2", + "d3-drag": "2", + "d3-interpolate": "1 - 2", + "d3-selection": "2", + "d3-transition": "2" + } + }, + "node_modules/d3-zoom/node_modules/d3-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-2.0.0.tgz", + "integrity": "sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==" + }, + "node_modules/d3-zoom/node_modules/d3-interpolate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-2.0.1.tgz", + "integrity": "sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ==", + "dependencies": { + "d3-color": "1 - 2" + } + }, "node_modules/daisyui": { "version": "3.7.7", "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-3.7.7.tgz", @@ -9330,19 +9424,6 @@ "node": ">=6" } }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/formik": { "version": "2.4.5", "resolved": "https://registry.npmjs.org/formik/-/formik-2.4.5.tgz", @@ -12785,6 +12866,12 @@ "shell-quote": "^1.7.3" } }, + "node_modules/leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", + "peer": true + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -15383,6 +15470,19 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, + "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-lifecycles-compat": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", @@ -15537,6 +15637,43 @@ } } }, + "node_modules/react-simple-maps": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/react-simple-maps/-/react-simple-maps-3.0.0.tgz", + "integrity": "sha512-vKNFrvpPG8Vyfdjnz5Ne1N56rZlDfHXv5THNXOVZMqbX1rWZA48zQuYT03mx6PAKanqarJu/PDLgshIZAfHHqw==", + "dependencies": { + "d3-geo": "^2.0.2", + "d3-selection": "^2.0.0", + "d3-zoom": "^2.0.0", + "topojson-client": "^3.1.0" + }, + "peerDependencies": { + "prop-types": "^15.7.2", + "react": "^16.8.0 || 17.x || 18.x", + "react-dom": "^16.8.0 || 17.x || 18.x" + } + }, + "node_modules/react-simple-maps/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/react-simple-maps/node_modules/d3-geo": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-2.0.2.tgz", + "integrity": "sha512-8pM1WGMLGFuhq9S+FpPURxic+gKzjluCD/CHTuUF3mXMeiCo0i6R0tO1s4+GArRFde96SLcW/kOFRjoAosPsFA==", + "dependencies": { + "d3-array": "^2.5.0" + } + }, + "node_modules/react-simple-maps/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" + }, "node_modules/react-smooth": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-2.0.4.tgz", @@ -17385,6 +17522,24 @@ "node": ">=0.6" } }, + "node_modules/topojson-client": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz", + "integrity": "sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==", + "dependencies": { + "commander": "2" + }, + "bin": { + "topo2geo": "bin/topo2geo", + "topomerge": "bin/topomerge", + "topoquantize": "bin/topoquantize" + } + }, + "node_modules/topojson-client/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, "node_modules/toposort": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index bc76ce96..57af0822 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -20,9 +20,11 @@ "formik": "^2.4.4", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-leaflet": "^4.2.1", "react-pro-sidebar": "^0.7.1", "react-router-dom": "^6.16.0", "react-scripts": "5.0.1", + "react-simple-maps": "^3.0.0", "recharts": "^2.8.0", "web-vitals": "^2.1.4", "yup": "^1.2.0" diff --git a/frontend/src/scenes/geolocate/index.jsx b/frontend/src/scenes/geolocate/index.jsx index 5835a9e9..70674c4a 100644 --- a/frontend/src/scenes/geolocate/index.jsx +++ b/frontend/src/scenes/geolocate/index.jsx @@ -1,14 +1,118 @@ -import { Box } from "@mui/material"; +import { Box, Alert } from "@mui/material"; import Header from "../../components/Header"; +import { React, useState } from "react"; +import { ComposableMap, Geographies, Geography } from "react-simple-maps" +import { Marker } from "react-simple-maps" +import Paper from '@mui/material/Paper'; +import InputBase from '@mui/material/InputBase'; +import IconButton from '@mui/material/IconButton'; +import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; +import SearchIcon from '@mui/icons-material/Search'; +import PinDropIcon from '@mui/icons-material/PinDrop'; -// Return a geolocate component. Right now it just displays the title and subtitle. -const Geolocate = () => { +//set the maps' width and height cooordinates +const width = 800 +const height = 500 +const geoUrl = + "https://raw.githubusercontent.com/deldersveld/topojson/master/world-countries.json" + + +// call the backend API to fetch the location data using the /geolocate endpoint +// if we are in development we need to call the API using the full URL +function fetchLocationData(inputValue) { + if (process.env.REACT_APP_ENVIRONMENT === 'dev') { + return fetch(process.env.REACT_APP_API_URL + '/geolocate/' + inputValue); + } + else { + return fetch('/geolocate' + inputValue); + } +} + +function Geolocate() { + const [inputValue, setInputValue] = useState(''); + const [location, setLocation] = useState(null); + + //execute the fetchLocationData function when the user clicks on the search button or presses enter + function handleGeolocateSearch(e) { + e.preventDefault(); + //call the api to fetch the location data + fetchLocationData(inputValue).then(response => { + if (!response.ok) { + throw new Error('Invalid IP address'); + } + return response.json(); + }) + .then(data => { + // Handle the JSON data from the response + setLocation(data); + }) + .catch(error => { + setLocation({detail: "Something went wrong: " + error}); + console.error('There was a problem with the fetch operation:', error); + }); + }; + + // execute the handleGeolocateSearch function when the user presses enter + function HandleKeyDown(e) { + if (e.key === 'Enter') { + handleGeolocateSearch(e); + } + } + return ( - -
- - -)}; - + {/* Display the title */} + +
+ + {/* Display the input bo and search button */} + + + + + {/* */} + + setInputValue(e.target.value)} + onKeyDown={e => HandleKeyDown(e)} + /> + + + + + {/* If we have data, then display the city, country and location coordinates. If not, then display the error alert */} + {location && location.detail ? ( + + {location.detail} + ) : ( + location &&

Located in {location.city}, {location.country} with coordinates [{location.latitude}, {location.longitude}]
)} + {/* Display the map */} + + + {({ geographies }) => + geographies.map((geo) => ( + + )) + } + + {/* Display the location coordinates of our location with a red circle. */} + {location && location.latitude && location.longitude && ( + + + + )} + +
+ + ); + } + export default Geolocate; \ No newline at end of file