diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..07e6e47
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/node_modules
diff --git a/backend/middleware/index.js b/backend/middleware/index.js
index 6fbb0f5..ea59b6d 100644
--- a/backend/middleware/index.js
+++ b/backend/middleware/index.js
@@ -36,4 +36,4 @@ class Middleware {
}
}
-module.exports = new Middleware();
+module.exports = new Middleware();
\ No newline at end of file
diff --git a/backend/models/UserModel.js b/backend/models/UserModel.js
index f0cc785..b62fef7 100644
--- a/backend/models/UserModel.js
+++ b/backend/models/UserModel.js
@@ -18,6 +18,12 @@ const userSchema = new mongoose.Schema({
fullName: {
type: String,
required: true,
+ },
+ firebaseUID: {
+ type: String,
+ required: true,
+ unique: true,
+ index: true,
}
}, { versionKey: false });
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 5433469..2c82d8b 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -8,7 +8,7 @@
"name": "frontend",
"version": "0.1.0",
"dependencies": {
- "@emotion/react": "^11.11.3",
+ "@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.0",
"@fontsource/inter": "^5.0.16",
"@mui/icons-material": "^5.15.10",
diff --git a/frontend/package.json b/frontend/package.json
index 1e06232..f23a381 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
- "@emotion/react": "^11.11.3",
+ "@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.0",
"@fontsource/inter": "^5.0.16",
"@mui/icons-material": "^5.15.10",
diff --git a/frontend/src/App.js b/frontend/src/App.js
index 44b5af2..da4f2a0 100644
--- a/frontend/src/App.js
+++ b/frontend/src/App.js
@@ -16,6 +16,7 @@ import { auth } from "./firebase-config";
import { UserContext } from "./contexts/UserContext";
import { useContext } from "react";
+import UserProfile from './components/UserProfile'
function App() {
const { user, setUser } = useContext(UserContext);
@@ -58,11 +59,10 @@ function App() {
path="/postAd"
element={user.isLoggedIn ? : }
/>
- } />
- } />
+ : } />
} />
- } />
- } />
+ : } />
+ : } />
{user.isAdmin && } />}
{user.isAdmin && } />}
} />
diff --git a/frontend/src/components/EditAdModal.jsx b/frontend/src/components/EditAdModal.jsx
new file mode 100644
index 0000000..1437c6b
--- /dev/null
+++ b/frontend/src/components/EditAdModal.jsx
@@ -0,0 +1,65 @@
+// EditAdModal.jsx
+import React, { useState } from 'react';
+import { Modal, Box, Typography, TextField, Button } from "@mui/material";
+
+
+function EditAdModal({ open, onClose, adInfo, onSave }) {
+ const [editedAd, setEditedAd] = useState(adInfo);
+
+ const handleSubmit = (e) => {
+ e.preventDefault();
+ console.log(editedAd._id)
+ onSave(editedAd); // Pass the edited ad back to UserProfile
+ onClose(); // Close the modal
+ };
+
+ const handleChange = (e) => {
+ const { name, value } = e.target;
+ setEditedAd({ ...editedAd, [name]: value });
+ };
+
+ // Define modal styles here
+ const style = {
+ position: 'absolute',
+ top: '50%',
+ left: '50%',
+ transform: 'translate(-50%, -50%)',
+ width: 400,
+ bgcolor: 'background.paper',
+ border: '2px solid #000',
+ boxShadow: 24,
+ p: 4,
+ };
+
+ return (
+
+
+ Edit Ad
+
+
+
+
+
+ );
+}
+
+export default EditAdModal;
+
diff --git a/frontend/src/components/EditProfileModal.jsx b/frontend/src/components/EditProfileModal.jsx
new file mode 100644
index 0000000..1cde49c
--- /dev/null
+++ b/frontend/src/components/EditProfileModal.jsx
@@ -0,0 +1,59 @@
+import React, { useState } from 'react';
+import { Modal, Box, Typography, TextField, Button } from "@mui/material";
+
+function EditProfileModal({ open, onClose, userInfo, onSave }) {
+ const [editedUserInfo, setEditedUserInfo] = useState(userInfo);
+
+ const handleChange = (e) => {
+ const { name, value } = e.target;
+ setEditedUserInfo(prev => ({ ...prev, [name]: value }));
+ };
+
+ const handleSubmit = (e) => {
+ e.preventDefault();
+ onSave(editedUserInfo);
+ onClose();
+ };
+
+ const style = {
+ position: 'absolute',
+ top: '50%',
+ left: '50%',
+ transform: 'translate(-50%, -50%)',
+ width: 400,
+ bgcolor: 'background.paper',
+ border: '2px solid #000',
+ boxShadow: 24,
+ p: 4,
+ };
+
+ return (
+
+
+ Edit Profile
+
+
+ {/* Add other fields as necessary */}
+
+
+
+ );
+}
+
+export default EditProfileModal;
\ No newline at end of file
diff --git a/frontend/src/components/UserProfile.jsx b/frontend/src/components/UserProfile.jsx
new file mode 100644
index 0000000..25c6538
--- /dev/null
+++ b/frontend/src/components/UserProfile.jsx
@@ -0,0 +1,182 @@
+import React, { useState, useEffect, useContext } from 'react';
+import { Box, Card, CardContent, Typography, TextField, Button } from "@mui/material";
+import EditAdModal from './EditAdModal';
+import EditProfileModal from './EditProfileModal';
+
+import { UserContext } from "../contexts/UserContext";
+
+
+const UserProfile = () => {
+ const [userInfo, setUserInfo] = useState({ name: '', email: '' });
+ const [ads, setAds] = useState([]);
+ const [editModalOpen, setEditModalOpen] = useState(false);
+ const [selectedAd, setSelectedAd] = useState(null)
+
+ const { user, setUser } = useContext(UserContext);
+
+ // NOTE: user object has all properties you need to display/edit user info
+
+ const fetchUserAds = async () => {
+ const token = user.authToken;
+ let items = [];
+ await fetch(`http://localhost:5001/api/ads/get/itemsWanted/author/${user.username}`, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'authorization': `Bearer ${token}`
+ }
+ })
+ .then(res => {
+ return res.json();
+ })
+ .then(data => {
+ let temp = data;
+ temp.forEach(ad => { ad.category = 'Items Wanted'; });
+ items.push(...temp);
+ return fetch(`http://localhost:5001/api/ads/get/itemsForSale/author/${user.username}`, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'authorization': `Bearer ${token}`
+ }
+ });
+ })
+ .then(res => {
+ return res.json();
+ })
+ .then(data => {
+ let temp = data;
+ temp.forEach(ad => { ad.category = 'Items For Sale'; });
+ items.push(...temp);
+ return fetch(`http://localhost:5001/api/ads/get/academicServices/author/${user.username}`, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'authorization': `Bearer ${token}`
+ }
+ });
+ })
+ .then(res => {
+ return res.json();
+ })
+ .then(data => {
+ let temp = data;
+ temp.forEach(ad => { ad.category = 'Academic Services'; });
+ items.push(...temp);
+ setAds(items);
+ console.log('ads by user: ', ads);
+ });
+ }
+
+ useEffect(() => {
+ fetchUserAds();
+ }, []);
+
+
+ const handleUpdateUserInfo = (e) => {
+ e.preventDefault();
+ console.log('Updated Info:', userInfo);
+ };
+
+ const handleEditAd = (ad) => {
+ console.log(ad._id);
+ setSelectedAd(ad);
+ setEditModalOpen(true);
+ };
+
+ const handleSaveAdChanges = async (editedAd) => {
+ const token = user.authToken;
+ const { title, description } = editedAd; // Destructure the needed properties
+
+ try {
+ const response = await fetch(`http://localhost:5001/api/ads/update/${editedAd.category}/id/${editedAd._id}`, {
+ method: "PATCH",
+ headers: {
+ "Accept": "application/json",
+ "Content-Type": "application/json",
+ "Authorization": `Bearer ${token}`,
+ },
+ body: JSON.stringify({ editedAd }),
+ });
+
+ if (!response.ok) {
+ throw new Error(`Failed to update ad. Status: ${response.status}`);
+ }
+
+ const data = await response.json();
+ console.log('Update successful:', data);
+
+ // Update local state or re-fetch ads to reflect changes
+ fetchUserAds();
+ } catch (error) {
+ console.error('Error updating ad:', error);
+ }
+ };
+
+ // const handleSaveProfile = async (updatedUserInfo) => {
+ // const token = user.authToken;
+ // try {
+ // const response = await fetch('/api/users/update/me', {
+ // method: 'PATCH',
+ // headers: {
+ // 'Authorization': `Bearer ${token}`,
+ // 'Content-Type': 'application/json',
+ // },
+ // body: JSON.stringify(updatedUserInfo),
+ // });
+
+ // if (!response.ok) {
+ // throw new Error('Failed to update user info');
+ // }
+
+ // const data = await response.json();
+ // setEditModalOpen(false);
+ // } catch (error) {
+ // console.error('Error updating user info:', error)
+ // }
+ // };
+
+
+ return (
+ <>
+
+
+ User Profile
+
+ Personal Information
+ Username: {user.username}
+ Email: {user.email}
+
+
+ Your Ads
+ {ads.map((ad) => (
+
+ {ad.title}
+ {ad.description}
+ {/* Render photos */}
+ {ad.photos.map((photoUrl, index) => (
+
+ ))}
+
+
+ ))}
+
+
+
+
+ {selectedAd && (
+ setEditModalOpen(false)}
+ adInfo={selectedAd}
+ onSave={handleSaveAdChanges}
+ />
+ )}
+ >
+ );
+};
+
+export default UserProfile;
\ No newline at end of file
diff --git a/frontend/src/firebase.js b/frontend/src/firebase.js
index 6eca276..96f37aa 100644
--- a/frontend/src/firebase.js
+++ b/frontend/src/firebase.js
@@ -2,6 +2,8 @@
// https://firebase.google.com/docs/web/setup#available-libraries
import { initializeApp } from "firebase/app";
import { getStorage } from "firebase/storage";
+import { getAuth } from "firebase/auth";
+
// Your web app's Firebase configuration
const firebaseConfig = {
@@ -15,4 +17,5 @@ const firebaseConfig = {
// Initialize Firebase
const app = initializeApp(firebaseConfig);
-export const firebaseStorage = getStorage(app);
\ No newline at end of file
+export const firebaseStorage = getStorage(app);
+export const auth = getAuth(app);
\ No newline at end of file
diff --git a/frontend/src/login/Login.js b/frontend/src/login/Login.js
index de617ad..77e1bfb 100644
--- a/frontend/src/login/Login.js
+++ b/frontend/src/login/Login.js
@@ -1,243 +1,243 @@
import {
- Box,
- Button,
- Card,
- CardContent,
- FormControlLabel,
- FormLabel,
- TextField,
- Typography,
- Checkbox,
- IconButton,
-} from "@mui/material";
-import React, { useState, useContext, useEffect } from "react";
-import { Visibility, VisibilityOff } from "@mui/icons-material";
-import { useFormik } from "formik";
-import * as yup from "yup";
-import { auth } from "../firebase-config";
-import { signInWithEmailAndPassword } from "firebase/auth";
-import { useNavigate } from "react-router-dom";
-import Snackbar from "@mui/material/Snackbar";
-import Alert from "@mui/material/Alert";
-import { Link } from "react-router-dom";
-
-import { UserContext } from "../contexts/UserContext";
-
-const validationSchema = yup.object({
- email: yup
- .string()
- .email("Enter a valid email")
- .required("Email is required"),
- password: yup
- .string()
- .min(8, "Password should be of minimum 8 characters length")
- .required("Password is required"),
-});
-
-const Login = () => {
- const { user, setUser } = useContext(UserContext);
-
- const navigate = useNavigate();
- /*
- const loggedIn = sessionStorage.getItem("isLoggedIn") === "true";
- if (loggedIn)
- navigate('/');
- */
-
- const [openSnackbar, setOpenSnackbar] = useState(false);
- const [snackbarMessage, setSnackbarMessage] = useState("");
- const [snackbarSeverity, setSnackbarSeverity] = useState("error"); // 'success' or 'error'
- const formik = useFormik({
- initialValues: {
- email: "",
- password: "",
- },
- validationSchema: validationSchema,
- onSubmit: (values) => {
- signInWithEmailAndPassword(auth, values.email, values.password)
- .then(async (userCredential) => {
- console.log(userCredential.user, "User Logged In Successfully"); // Sign-in successful
-
- const token = userCredential.user.accessToken;
-
- // Store auth status and token in sessionStorage
- sessionStorage.setItem("isLoggedIn", "true");
- sessionStorage.setItem("authToken", token);
-
- const userInfo = await fetch(
- `http://localhost:5001/api/users/get/email/${values.email}`,
- {
- method: "GET",
- headers: {
- Accept: "application/json",
- "Content-Type": "application/json",
- authorization: `Bearer ${token}`,
- },
- }
- )
- .then((res) => {
- return res.json();
- })
- .then((data) => {
- return data;
+ Box,
+ Button,
+ Card,
+ CardContent,
+ FormControlLabel,
+ FormLabel,
+ TextField,
+ Typography,
+ Checkbox,
+ IconButton,
+ } from "@mui/material";
+ import React, { useState, useContext, useEffect } from "react";
+ import { Visibility, VisibilityOff } from "@mui/icons-material";
+ import { useFormik } from "formik";
+ import * as yup from "yup";
+ import { auth } from "../firebase-config";
+ import { signInWithEmailAndPassword } from "firebase/auth";
+ import { useNavigate } from "react-router-dom";
+ import Snackbar from "@mui/material/Snackbar";
+ import Alert from "@mui/material/Alert";
+ import { Link } from "react-router-dom";
+
+ import { UserContext } from "../contexts/UserContext";
+
+ const validationSchema = yup.object({
+ email: yup
+ .string()
+ .email("Enter a valid email")
+ .required("Email is required"),
+ password: yup
+ .string()
+ .min(8, "Password should be of minimum 8 characters length")
+ .required("Password is required"),
+ });
+
+ const Login = () => {
+ const { user, setUser } = useContext(UserContext);
+
+ const navigate = useNavigate();
+ /*
+ const loggedIn = sessionStorage.getItem("isLoggedIn") === "true";
+ if (loggedIn)
+ navigate('/');
+ */
+
+ const [openSnackbar, setOpenSnackbar] = useState(false);
+ const [snackbarMessage, setSnackbarMessage] = useState("");
+ const [snackbarSeverity, setSnackbarSeverity] = useState("error"); // 'success' or 'error'
+ const formik = useFormik({
+ initialValues: {
+ email: "",
+ password: "",
+ },
+ validationSchema: validationSchema,
+ onSubmit: (values) => {
+ signInWithEmailAndPassword(auth, values.email, values.password)
+ .then(async (userCredential) => {
+ console.log(userCredential.user, "User Logged In Successfully"); // Sign-in successful
+
+ const token = userCredential.user.accessToken;
+
+ // Store auth status and token in sessionStorage
+ sessionStorage.setItem("isLoggedIn", "true");
+ sessionStorage.setItem("authToken", token);
+
+ const userInfo = await fetch(
+ `http://localhost:5001/api/users/get/email/${values.email}`,
+ {
+ method: "GET",
+ headers: {
+ Accept: "application/json",
+ "Content-Type": "application/json",
+ authorization: `Bearer ${token}`,
+ },
+ }
+ )
+ .then((res) => {
+ return res.json();
+ })
+ .then((data) => {
+ return data;
+ });
+
+ sessionStorage.setItem("email", values.email);
+ sessionStorage.setItem("fullName", userInfo[0].fullName);
+ sessionStorage.setItem("isAdmin", userInfo[0].isAdmin.toString());
+ sessionStorage.setItem("_id", userInfo[0]._id);
+ sessionStorage.setItem("username", userInfo[0].username);
+
+ setUser({
+ isLoggedIn: true,
+ username: userInfo[0].username,
+ email: userInfo[0].email,
+ fullName: userInfo[0].fullName,
+ isAdmin: userInfo[0].isAdmin,
+ _id: userInfo[0]._id,
+ authToken: token,
});
-
- sessionStorage.setItem("email", values.email);
- sessionStorage.setItem("fullName", userInfo[0].fullName);
- sessionStorage.setItem("isAdmin", userInfo[0].isAdmin.toString());
- sessionStorage.setItem("_id", userInfo[0]._id);
- sessionStorage.setItem("username", userInfo[0].username);
-
- setUser({
- isLoggedIn: true,
- username: userInfo[0].username,
- email: userInfo[0].email,
- fullName: userInfo[0].fullName,
- isAdmin: userInfo[0].isAdmin,
- _id: userInfo[0]._id,
- authToken: token,
+
+ setSnackbarMessage("Login successful!");
+ setSnackbarSeverity("success");
+ setOpenSnackbar(true);
+ navigate("/"); // Redirect after showing success message
+ })
+ .catch((error) => {
+ // Sign-in failure
+ console.error("Error signing in: ", error.message);
+ alert(`Error signing in: ${error.message}`);
+ setSnackbarMessage(`Error signing in: ${error.message}`);
+ setSnackbarSeverity("error");
+ setOpenSnackbar(true);
});
-
- setSnackbarMessage("Login successful!");
- setSnackbarSeverity("success");
- setOpenSnackbar(true);
- navigate("/"); // Redirect after showing success message
- })
- .catch((error) => {
- // Sign-in failure
- console.error("Error signing in: ", error.message);
- alert(`Error signing in: ${error.message}`);
- setSnackbarMessage(`Error signing in: ${error.message}`);
- setSnackbarSeverity("error");
- setOpenSnackbar(true);
- });
- console.log(values, "Submiited Values");
- },
- });
-
- const [showPassword, setShowPassword] = useState(false);
-
- return (
-
-
-
+
-
-
-
-
- setOpenSnackbar(false)}
- >
-
+
+ {showPassword ? (
+ setShowPassword(false)} />
+ ) : (
+ setShowPassword(true)} />
+ )}
+
+ ),
+ }}
+ />
+
+
+
+ Dont have an account?{" "}
+
+
+ Register
+
+
+
+
+
+
+
+
+ setOpenSnackbar(false)}
- severity={snackbarSeverity}
- sx={{ width: "100%" }}
>
- {snackbarMessage}
-
-
-
- );
-};
-
-export default Login;
+ setOpenSnackbar(false)}
+ severity={snackbarSeverity}
+ sx={{ width: "100%" }}
+ >
+ {snackbarMessage}
+
+
+
+ );
+ };
+
+ export default Login;
\ No newline at end of file
diff --git a/frontend/src/nav.js b/frontend/src/nav.js
index d07c8d8..a57004b 100644
--- a/frontend/src/nav.js
+++ b/frontend/src/nav.js
@@ -228,54 +228,51 @@ export default function Navigation({ admin }) {
zIndex: 0,
},
},
+ "&::before": {
+ content: '""',
+ display: "block",
+ position: "absolute",
+ top: 0,
+ right: 14,
+ width: 10,
+ height: 10,
+ bgcolor: "background.paper",
+ transform: "translateY(-50%) rotate(45deg)",
+ zIndex: 0,
+ },
}}
transformOrigin={{ horizontal: "right", vertical: "top" }}
anchorOrigin={{ horizontal: "right", vertical: "bottom" }}
>
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
)}
);
diff --git a/frontend/src/register/Register.js b/frontend/src/register/Register.js
index f42cb8f..6665995 100644
--- a/frontend/src/register/Register.js
+++ b/frontend/src/register/Register.js
@@ -68,6 +68,7 @@ const Register = () => {
.then(async (userCredential) => {
console.log(userCredential.user, "User Created Successfully");
+ const firebaseUID = userCredential.user.uid;
const token = userCredential.user.accessToken;
// Set 'isLoggedIn' to true and store 'authToken'
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..9895b48
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,278 @@
+{
+ "name": "CPS630",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {
+ "": {
+ "dependencies": {
+ "@react-google-maps/api": "^2.19.3",
+ "autosuggest-highlight": "^3.3.4"
+ }
+ },
+ "node_modules/@googlemaps/js-api-loader": {
+ "version": "1.16.2",
+ "resolved": "https://registry.npmjs.org/@googlemaps/js-api-loader/-/js-api-loader-1.16.2.tgz",
+ "integrity": "sha512-psGw5u0QM6humao48Hn4lrChOM2/rA43ZCm3tKK9qQsEj1/VzqkCqnvGfEOshDbBQflydfaRovbKwZMF4AyqbA==",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3"
+ }
+ },
+ "node_modules/@googlemaps/markerclusterer": {
+ "version": "2.5.3",
+ "resolved": "https://registry.npmjs.org/@googlemaps/markerclusterer/-/markerclusterer-2.5.3.tgz",
+ "integrity": "sha512-x7lX0R5yYOoiNectr10wLgCBasNcXFHiADIBdmn7jQllF2B5ENQw5XtZK+hIw4xnV0Df0xhN4LN98XqA5jaiOw==",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "supercluster": "^8.0.1"
+ }
+ },
+ "node_modules/@react-google-maps/api": {
+ "version": "2.19.3",
+ "resolved": "https://registry.npmjs.org/@react-google-maps/api/-/api-2.19.3.tgz",
+ "integrity": "sha512-jiLqvuOt5lOowkLeq7d077AByTyJp+s6hZVlLhlq7SBacBD37aUNpXBz2OsazfeR6Aw4a+9RRhAEjEFvrR1f5A==",
+ "dependencies": {
+ "@googlemaps/js-api-loader": "1.16.2",
+ "@googlemaps/markerclusterer": "2.5.3",
+ "@react-google-maps/infobox": "2.19.2",
+ "@react-google-maps/marker-clusterer": "2.19.2",
+ "@types/google.maps": "3.55.2",
+ "invariant": "2.2.4"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17 || ^18",
+ "react-dom": "^16.8 || ^17 || ^18"
+ }
+ },
+ "node_modules/@react-google-maps/infobox": {
+ "version": "2.19.2",
+ "resolved": "https://registry.npmjs.org/@react-google-maps/infobox/-/infobox-2.19.2.tgz",
+ "integrity": "sha512-6wvBqeJsQ/eFSvoxg+9VoncQvNoVCdmxzxRpLvmjPD+nNC6mHM0vJH1xSqaKijkMrfLJT0nfkTGpovrF896jwg=="
+ },
+ "node_modules/@react-google-maps/marker-clusterer": {
+ "version": "2.19.2",
+ "resolved": "https://registry.npmjs.org/@react-google-maps/marker-clusterer/-/marker-clusterer-2.19.2.tgz",
+ "integrity": "sha512-x9ibmsP0ZVqzyCo1Pitbw+4b6iEXRw/r1TCy3vOUR3eKrzWLnHYZMR325BkZW2r8fnuWE/V3Fp4QZOP9qYORCw=="
+ },
+ "node_modules/@types/google.maps": {
+ "version": "3.55.2",
+ "resolved": "https://registry.npmjs.org/@types/google.maps/-/google.maps-3.55.2.tgz",
+ "integrity": "sha512-JcTwzkxskR8DN/nnX96Pie3gGN3WHiPpuxzuQ9z3516o1bB243d8w8DHUJ8BohuzoT1o3HUFta2ns/mkZC8KRw=="
+ },
+ "node_modules/autosuggest-highlight": {
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/autosuggest-highlight/-/autosuggest-highlight-3.3.4.tgz",
+ "integrity": "sha512-j6RETBD2xYnrVcoV1S5R4t3WxOlWZKyDQjkwnggDPSjF5L4jV98ZltBpvPvbkM1HtoSe5o+bNrTHyjPbieGeYA==",
+ "dependencies": {
+ "remove-accents": "^0.4.2"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+ },
+ "node_modules/invariant": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+ "dependencies": {
+ "loose-envify": "^1.0.0"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+ },
+ "node_modules/kdbush": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz",
+ "integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA=="
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/react": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
+ "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
+ "peer": true,
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
+ "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
+ "peer": true,
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.0"
+ },
+ "peerDependencies": {
+ "react": "^18.2.0"
+ }
+ },
+ "node_modules/remove-accents": {
+ "version": "0.4.4",
+ "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.4.tgz",
+ "integrity": "sha512-EpFcOa/ISetVHEXqu+VwI96KZBmq+a8LJnGkaeFw45epGlxIZz5dhEEnNZMsQXgORu3qaMoLX4qJCzOik6ytAg=="
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
+ "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
+ "peer": true,
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/supercluster": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz",
+ "integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==",
+ "dependencies": {
+ "kdbush": "^4.0.2"
+ }
+ }
+ },
+ "dependencies": {
+ "@googlemaps/js-api-loader": {
+ "version": "1.16.2",
+ "resolved": "https://registry.npmjs.org/@googlemaps/js-api-loader/-/js-api-loader-1.16.2.tgz",
+ "integrity": "sha512-psGw5u0QM6humao48Hn4lrChOM2/rA43ZCm3tKK9qQsEj1/VzqkCqnvGfEOshDbBQflydfaRovbKwZMF4AyqbA==",
+ "requires": {
+ "fast-deep-equal": "^3.1.3"
+ }
+ },
+ "@googlemaps/markerclusterer": {
+ "version": "2.5.3",
+ "resolved": "https://registry.npmjs.org/@googlemaps/markerclusterer/-/markerclusterer-2.5.3.tgz",
+ "integrity": "sha512-x7lX0R5yYOoiNectr10wLgCBasNcXFHiADIBdmn7jQllF2B5ENQw5XtZK+hIw4xnV0Df0xhN4LN98XqA5jaiOw==",
+ "requires": {
+ "fast-deep-equal": "^3.1.3",
+ "supercluster": "^8.0.1"
+ }
+ },
+ "@react-google-maps/api": {
+ "version": "2.19.3",
+ "resolved": "https://registry.npmjs.org/@react-google-maps/api/-/api-2.19.3.tgz",
+ "integrity": "sha512-jiLqvuOt5lOowkLeq7d077AByTyJp+s6hZVlLhlq7SBacBD37aUNpXBz2OsazfeR6Aw4a+9RRhAEjEFvrR1f5A==",
+ "requires": {
+ "@googlemaps/js-api-loader": "1.16.2",
+ "@googlemaps/markerclusterer": "2.5.3",
+ "@react-google-maps/infobox": "2.19.2",
+ "@react-google-maps/marker-clusterer": "2.19.2",
+ "@types/google.maps": "3.55.2",
+ "invariant": "2.2.4"
+ }
+ },
+ "@react-google-maps/infobox": {
+ "version": "2.19.2",
+ "resolved": "https://registry.npmjs.org/@react-google-maps/infobox/-/infobox-2.19.2.tgz",
+ "integrity": "sha512-6wvBqeJsQ/eFSvoxg+9VoncQvNoVCdmxzxRpLvmjPD+nNC6mHM0vJH1xSqaKijkMrfLJT0nfkTGpovrF896jwg=="
+ },
+ "@react-google-maps/marker-clusterer": {
+ "version": "2.19.2",
+ "resolved": "https://registry.npmjs.org/@react-google-maps/marker-clusterer/-/marker-clusterer-2.19.2.tgz",
+ "integrity": "sha512-x9ibmsP0ZVqzyCo1Pitbw+4b6iEXRw/r1TCy3vOUR3eKrzWLnHYZMR325BkZW2r8fnuWE/V3Fp4QZOP9qYORCw=="
+ },
+ "@types/google.maps": {
+ "version": "3.55.2",
+ "resolved": "https://registry.npmjs.org/@types/google.maps/-/google.maps-3.55.2.tgz",
+ "integrity": "sha512-JcTwzkxskR8DN/nnX96Pie3gGN3WHiPpuxzuQ9z3516o1bB243d8w8DHUJ8BohuzoT1o3HUFta2ns/mkZC8KRw=="
+ },
+ "autosuggest-highlight": {
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/autosuggest-highlight/-/autosuggest-highlight-3.3.4.tgz",
+ "integrity": "sha512-j6RETBD2xYnrVcoV1S5R4t3WxOlWZKyDQjkwnggDPSjF5L4jV98ZltBpvPvbkM1HtoSe5o+bNrTHyjPbieGeYA==",
+ "requires": {
+ "remove-accents": "^0.4.2"
+ }
+ },
+ "fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+ },
+ "invariant": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+ "requires": {
+ "loose-envify": "^1.0.0"
+ }
+ },
+ "js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+ },
+ "kdbush": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz",
+ "integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA=="
+ },
+ "loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "requires": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ }
+ },
+ "react": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
+ "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
+ "peer": true,
+ "requires": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "react-dom": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
+ "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
+ "peer": true,
+ "requires": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.0"
+ }
+ },
+ "remove-accents": {
+ "version": "0.4.4",
+ "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.4.tgz",
+ "integrity": "sha512-EpFcOa/ISetVHEXqu+VwI96KZBmq+a8LJnGkaeFw45epGlxIZz5dhEEnNZMsQXgORu3qaMoLX4qJCzOik6ytAg=="
+ },
+ "scheduler": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
+ "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
+ "peer": true,
+ "requires": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "supercluster": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz",
+ "integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==",
+ "requires": {
+ "kdbush": "^4.0.2"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..79510c0
--- /dev/null
+++ b/package.json
@@ -0,0 +1,6 @@
+{
+ "dependencies": {
+ "@react-google-maps/api": "^2.19.3",
+ "autosuggest-highlight": "^3.3.4"
+ }
+}