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