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) => ( + {`Ad + ))} + + + ))} + + + + + {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 ( - - - + -
- - Login - - - - - {showPassword ? ( - setShowPassword(false)} /> - ) : ( - setShowPassword(true)} /> - )} - - ), - }} - /> - - - - Dont have an account?{" "} - - - Register - - + + Login - - -
-
- - 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" }} > - - Profile - - - My account - - - - - - My Posts - - navigate("/messages")}> - - - - Messages - - navigate("/postAd")}> - - - - Post Ad - - - - - - - Profile - - - - - - Settings - - - - - - Logout - - + navigate("/profile")}> + My Account + + + + + + My Posts + + navigate("/messages")}> + + + + Messages + + navigate("/postAd")}> + + + + Post Ad + + + + + + + Logout + + )} ); 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" + } +}