From 5b8b6a209dbab0011692ec2d1dfd688078838cbf Mon Sep 17 00:00:00 2001 From: angelp03 Date: Fri, 8 Nov 2024 10:24:55 -0600 Subject: [PATCH 1/7] refactor existing frontend code --- src/App.jsx | 200 ++--------------------------------- src/components/Banner.css | 13 ++- src/components/Banner.jsx | 7 +- src/pages/LandingPage.jsx | 25 +++++ src/pages/ReviewPostPage.jsx | 157 +++++++++++++++++++++++++++ src/utilities/firebase.js | 46 ++++---- 6 files changed, 234 insertions(+), 214 deletions(-) create mode 100644 src/pages/LandingPage.jsx create mode 100644 src/pages/ReviewPostPage.jsx diff --git a/src/App.jsx b/src/App.jsx index f300761..1dc6065 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,194 +1,16 @@ -import React, { useState, useEffect } from 'react'; -import './App.css'; -import { useAuthState, signInWithGoogle, firebaseSignOut } from './utilities/firebase'; -import { findCafes } from './utilities/findCafes'; -import MapComponent from "./MapComponent"; - -function App() { - const [reviews, setReviews] = useState([]); - const [availability, setAvailability] = useState({}); - const [newReview, setNewReview] = useState(''); - const [selectedReview, setSelectedReview] = useState(null); - const [replyText, setReplyText] = useState(''); - const [cafeStatus, setCafeStatus] = useState({ seating: '', outlets: '' }); - const [address, setAddress] = useState(''); - const [dist, setDist] = useState(''); - const [cafes, setCafes] = useState([]); - - const [user] = useAuthState(); - - const handleSignIn = async () => { - try { - await signInWithGoogle(); - } catch (error) { - console.error('Error signing in:', error); - } - }; - - const handleLogout = () => firebaseSignOut(); - - useEffect(() => {}, []); - - const findCafesWrapper = () => { - findCafes(dist, address, setCafes); - }; - - const handleReviewSubmit = () => { - if (newReview.trim()) { - const review = { id: Date.now(), text: newReview, replies: [] }; - setReviews((prevReviews) => [...prevReviews, review]); - setNewReview(''); - } - }; - - const handleReplySubmit = (reviewId) => { - if (replyText.trim()) { - setReviews((prevReviews) => - prevReviews.map((review) => - review.id === reviewId - ? { ...review, replies: [...review.replies, replyText] } - : review - ) - ); - setReplyText(''); - setSelectedReview(null); - } - }; - - const handleAvailabilityUpdate = (cafeId) => { - const timestamp = new Date().toLocaleString(); - const newUpdate = { timestamp, seating: cafeStatus.seating, outlets: cafeStatus.outlets }; - - setAvailability((prevAvailability) => ({ - ...prevAvailability, - [cafeId]: { - history: [...(prevAvailability[cafeId]?.history || []), newUpdate], - }, - })); - setCafeStatus({ seating: '', outlets: '' }); - }; - - const cafesMap = [ - { name: "Cafe Blue", lat: 42.046, lng: -87.688 }, - { name: "Green Bean Cafe", lat: 42.048, lng: -87.684 }, - { name: "Java Lounge", lat: 42.044, lng: -87.690 }, - ]; +import { BrowserRouter as Router, Route, Routes } from "react-router-dom"; +import LandingPage from './pages/LandingPage'; +import ReviewPostPage from "./pages/ReviewPostPage"; +const App = () => { return ( -
-
-

CafeWay

- {user ? ( - <> - -

Welcome, {user.email}

- - ) : ( - - )} -
- -
-
-

Share a Review

- setNewReview(e.target.value)} - /> - - -
    - {reviews.map((review) => ( -
  • -

    setSelectedReview(review)}>{review.text}

    -
      - {review.replies.map((reply, index) => ( -
    • - Reply: {reply} -
    • - ))} -
    - {selectedReview && selectedReview.id === review.id && ( -
    - setReplyText(e.target.value)} - /> - -
    - )} -
  • - ))} -
- -

Update Cafe Availability

- setCafeStatus({ ...cafeStatus, seating: e.target.value })} - /> - setCafeStatus({ ...cafeStatus, outlets: e.target.value })} - /> - - -

Availability History

- {availability['cafe-123']?.history?.map((update, index) => ( -
-

- {update.timestamp}: Seating: {update.seating}, Outlets: {update.outlets} -

-
- ))} -
- -
-

Cafes Near Me

- setAddress(e.target.value)} - /> - setDist(e.target.value)} - /> - -

Cafes Near Me

-
    - {cafes.map((cafe, index) => ( -
  • {cafe}
  • - ))} -
- - -
    - {cafes.map((cafesMap, index) => ( -
  • - {cafe.name} (Lat: {cafe.lat}, Lng: {cafe.lng}) -
  • - ))} -
- - {/* Integrate MapComponent with hardcoded data */} -
- -
-
-
-
- ); + + + }/> + }/> + + + ) } export default App; diff --git a/src/components/Banner.css b/src/components/Banner.css index 89c1df9..f3016a4 100644 --- a/src/components/Banner.css +++ b/src/components/Banner.css @@ -7,7 +7,7 @@ font-family: 'Hind Vadodara', sans-serif; font-weight: bolder; color: #8A3323; - border-bottom: 1px solid #8A3323; + border-bottom: 2px solid #8A3323; } .logo-search-div { @@ -35,7 +35,9 @@ .text-input { width: 22rem; - height: 1.5rem; + height: 2rem; + border-top: none; + border-left: none; border-bottom: 1px solid black; border-radius: 0; padding-left: 0.5rem; @@ -46,7 +48,7 @@ } .search-btn { - height: 2.6rem; + height: 2.3rem; width: 3rem; background-color: #8A3323; border: none; @@ -54,10 +56,13 @@ display: flex; justify-content: center; align-items: center; - margin-bottom: 0.65rem; + border-top: none; + border-left:none; + border-right:none; border-top-left-radius: 0; border-bottom-left-radius: 0; border-bottom: 1px solid black; + border-top-right-radius: 0.25; } .banner-buttons { diff --git a/src/components/Banner.jsx b/src/components/Banner.jsx index 2642603..1633ca0 100644 --- a/src/components/Banner.jsx +++ b/src/components/Banner.jsx @@ -1,8 +1,9 @@ import './Banner.css'; import Icon from '../Icon.svg'; import Search from '../Search.svg'; +import { handleSignIn } from '../utilities/firebase'; -const Banner = () => { +export const Banner = () => { // const navigate = useNavigate(); // const navigateToReviews = () => { // navigate('/reviews') @@ -27,7 +28,7 @@ const Banner = () => { - @@ -35,4 +36,4 @@ const Banner = () => { ) } -export default Banner \ No newline at end of file +export default Banner; \ No newline at end of file diff --git a/src/pages/LandingPage.jsx b/src/pages/LandingPage.jsx new file mode 100644 index 0000000..a198607 --- /dev/null +++ b/src/pages/LandingPage.jsx @@ -0,0 +1,25 @@ +import React from 'react'; +import '../App.css'; +import MapComponent from "../MapComponent"; +import Banner from '../components/Banner'; + +const LandingPage = () => { + + const cafesMap = [ + { name: "Cafe Blue", lat: 42.046, lng: -87.688 }, + { name: "Green Bean Cafe", lat: 42.048, lng: -87.684 }, + { name: "Java Lounge", lat: 42.044, lng: -87.690 }, + ]; + + return ( +
+ + {/* Integrate MapComponent with hardcoded data */} +
+ +
+
+ ); +} + +export default LandingPage; \ No newline at end of file diff --git a/src/pages/ReviewPostPage.jsx b/src/pages/ReviewPostPage.jsx new file mode 100644 index 0000000..861853d --- /dev/null +++ b/src/pages/ReviewPostPage.jsx @@ -0,0 +1,157 @@ +import React, { useState, useEffect } from 'react'; +import '../App.css'; +import { useAuthState } from '../utilities/firebase'; +import { findCafes } from '../utilities/findCafes'; + +const ReviewPostPage = () => { + const [reviews, setReviews] = useState([]); + const [availability, setAvailability] = useState({}); + const [newReview, setNewReview] = useState(''); + const [selectedReview, setSelectedReview] = useState(null); + const [replyText, setReplyText] = useState(''); + const [cafeStatus, setCafeStatus] = useState({ seating: '', outlets: '' }); + const [address, setAddress] = useState(''); + const [dist, setDist] = useState(''); + const [cafes, setCafes] = useState([]); + useEffect(() => { }, []); + + const findCafesWrapper = () => { + findCafes(dist, address, setCafes); + }; + + const handleReviewSubmit = () => { + if (newReview.trim()) { + const review = { id: Date.now(), text: newReview, replies: [] }; + setReviews((prevReviews) => [...prevReviews, review]); + setNewReview(''); + } + }; + + const handleReplySubmit = (reviewId) => { + if (replyText.trim()) { + setReviews((prevReviews) => + prevReviews.map((review) => + review.id === reviewId + ? { ...review, replies: [...review.replies, replyText] } + : review + ) + ); + setReplyText(''); + setSelectedReview(null); + } + }; + + const handleAvailabilityUpdate = (cafeId) => { + const timestamp = new Date().toLocaleString(); + const newUpdate = { timestamp, seating: cafeStatus.seating, outlets: cafeStatus.outlets }; + + setAvailability((prevAvailability) => ({ + ...prevAvailability, + [cafeId]: { + history: [...(prevAvailability[cafeId]?.history || []), newUpdate], + }, + })); + setCafeStatus({ seating: '', outlets: '' }); + }; + return ( +
+
+
+

Share a Review

+ setNewReview(e.target.value)} + /> + + +
    + {reviews.map((review) => ( +
  • +

    setSelectedReview(review)}>{review.text}

    +
      + {review.replies.map((reply, index) => ( +
    • + Reply: {reply} +
    • + ))} +
    + {selectedReview && selectedReview.id === review.id && ( +
    + setReplyText(e.target.value)} + /> + +
    + )} +
  • + ))} +
+ +

Update Cafe Availability

+ setCafeStatus({ ...cafeStatus, seating: e.target.value })} + /> + setCafeStatus({ ...cafeStatus, outlets: e.target.value })} + /> + + +

Availability History

+ {availability['cafe-123']?.history?.map((update, index) => ( +
+

+ {update.timestamp}: Seating: {update.seating}, Outlets: {update.outlets} +

+
+ ))} +
+ +
+

Cafes Near Me

+ setAddress(e.target.value)} + /> + setDist(e.target.value)} + /> + +

Cafes Near Me

+
    + {cafes.map((cafe, index) => ( +
  • {cafe}
  • + ))} +
+ + +
    + {cafes.map((cafesMap, index) => ( +
  • + {cafe.name} (Lat: {cafe.lat}, Lng: {cafe.lng}) +
  • + ))} +
+ +
+
+
+ ) +} + +export default ReviewPostPage; \ No newline at end of file diff --git a/src/utilities/firebase.js b/src/utilities/firebase.js index 4390349..ea1d27d 100644 --- a/src/utilities/firebase.js +++ b/src/utilities/firebase.js @@ -27,16 +27,16 @@ const database = getDatabase(app); export const signInWithGoogle = () => { signInWithPopup(auth, provider) - .then(async (result) => { - console.log('User signed in:', result.user); - var zipcode = await findZipcode(result.user.email); - console.log("Zipcode: ", zipcode); - }) - .catch((error) => { - console.error('Error signing in with Google:', error); - }); - }; - + .then(async (result) => { + console.log('User signed in:', result.user); + var zipcode = await findZipcode(result.user.email); + console.log("Zipcode: ", zipcode); + }) + .catch((error) => { + console.error('Error signing in with Google:', error); + }); +}; + export const firebaseSignOut = () => signOut(auth); export const useAuthState = () => { @@ -53,13 +53,13 @@ export const useAuthState = () => { export const useDbData = (path) => { const [data, setData] = useState(); const [error, setError] = useState(null); - + useEffect(() => { const dbRef = ref(database, path); const unsubscribe = onValue( - dbRef, - (snapshot) => setData(snapshot.val()), - (error) => setError(error) + dbRef, + (snapshot) => setData(snapshot.val()), + (error) => setError(error) ); return () => unsubscribe(); }, [path]); @@ -72,9 +72,9 @@ export const useDbUpdate = (path) => { const updateData = useCallback( (value) => { - update(ref(database, path), value) - .then(() => setResult({ message: "Update successful", timestamp: Date.now() })) - .catch((error) => setResult({ error, message: error.message })); + update(ref(database, path), value) + .then(() => setResult({ message: "Update successful", timestamp: Date.now() })) + .catch((error) => setResult({ error, message: error.message })); }, [path] ); @@ -82,4 +82,14 @@ export const useDbUpdate = (path) => { return [updateData, result]; }; -export {database}; \ No newline at end of file +export const handleSignIn = async () => { + try { + await signInWithGoogle(); + } catch (error) { + console.error('Error signing in:', error); + } +}; + +export const handleLogout = () => firebaseSignOut(); + +export { database }; \ No newline at end of file From 06eb086a235c427e7e0eb1010009eed17df65f91 Mon Sep 17 00:00:00 2001 From: angelp03 Date: Fri, 8 Nov 2024 12:40:47 -0600 Subject: [PATCH 2/7] populate cafes with cafes near authenticated email --- src/App.jsx | 2 ++ src/components/Banner.jsx | 11 ++++---- src/components/CafeList.css | 5 +++- src/components/CafeList.jsx | 52 ++++--------------------------------- src/pages/LandingPage.jsx | 4 ++- src/pages/ReviewsPage.jsx | 42 ++++++++++++++++++++++++++---- src/utilities/findCafes.jsx | 6 ++++- 7 files changed, 62 insertions(+), 60 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 1dc6065..05dee09 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,6 +1,7 @@ import { BrowserRouter as Router, Route, Routes } from "react-router-dom"; import LandingPage from './pages/LandingPage'; import ReviewPostPage from "./pages/ReviewPostPage"; +import ReviewsPage from "./pages/ReviewsPage"; const App = () => { return ( @@ -8,6 +9,7 @@ const App = () => { }/> }/> + }/> ) diff --git a/src/components/Banner.jsx b/src/components/Banner.jsx index 1633ca0..308290c 100644 --- a/src/components/Banner.jsx +++ b/src/components/Banner.jsx @@ -2,12 +2,13 @@ import './Banner.css'; import Icon from '../Icon.svg'; import Search from '../Search.svg'; import { handleSignIn } from '../utilities/firebase'; +import { useNavigate } from 'react-router-dom'; export const Banner = () => { - // const navigate = useNavigate(); - // const navigateToReviews = () => { - // navigate('/reviews') - // } + const navigate = useNavigate(); + const navigateToReviews = () => { + navigate('/cafes') + } return (
@@ -19,7 +20,7 @@ export const Banner = () => {
-
diff --git a/src/components/CafeList.css b/src/components/CafeList.css index 18ba163..661a09d 100644 --- a/src/components/CafeList.css +++ b/src/components/CafeList.css @@ -1,6 +1,8 @@ .cafe-list-container { display: flex; gap: 2rem; + width: 100vw; + height: 100vh; } .filter { @@ -9,6 +11,7 @@ background-color: #f5f5f5; border-radius: 5px; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); + height: 100%; } .cafe-list { @@ -17,7 +20,7 @@ } .cafe-card { - width: 100%; + width: 95%; margin-bottom: 1rem; padding: 1rem; border: 1px solid #ccc; diff --git a/src/components/CafeList.jsx b/src/components/CafeList.jsx index e29411c..8a7d1bc 100644 --- a/src/components/CafeList.jsx +++ b/src/components/CafeList.jsx @@ -1,43 +1,10 @@ import './CafeList.css'; -const reviews = [ - { - id: 1, - author: 'John Doe', - rating: 4, - content: 'Currently busy.', - location: 'Starbucks', - }, - { - id: 2, - author: 'Jane Smith', - rating: 5, - content: 'Somewhat busy, some seats remain.', - location: 'Colectivo', - }, - { - id: 3, - author: 'Alice Johnson', - rating: 3, - content: 'Very empty, plenty of seats.', - location: "Phil's", - }, - { - id: 4, - author: 'Bob Brown', - rating: 5, - content: 'Very quiet and great coffee!', - location: 'Coffee Lab', - }, -]; - -const CafeList = () => { +const CafeList = ({ cafes }) => { return (
- {/* Filter section */}

Filter Options

- {/* Example filter options */} - {/* Add more filters here as needed */}
- - {/* Review cards */}
- {reviews.map((review) => ( -
-

{review.author}

-

{review.location}

-
- {Array.from({ length: review.rating }).map((_, index) => ( - - ))} -
-

{review.content}

+ {cafes.map((cafe, index) => ( +
+

{cafe.name}

+

{cafe.vicinity}

))}
diff --git a/src/pages/LandingPage.jsx b/src/pages/LandingPage.jsx index a198607..b38d666 100644 --- a/src/pages/LandingPage.jsx +++ b/src/pages/LandingPage.jsx @@ -2,9 +2,11 @@ import React from 'react'; import '../App.css'; import MapComponent from "../MapComponent"; import Banner from '../components/Banner'; +import { useAuthState } from '../utilities/firebase'; const LandingPage = () => { - + const [user] = useAuthState(); + console.log(user) const cafesMap = [ { name: "Cafe Blue", lat: 42.046, lng: -87.688 }, { name: "Green Bean Cafe", lat: 42.048, lng: -87.684 }, diff --git a/src/pages/ReviewsPage.jsx b/src/pages/ReviewsPage.jsx index c79993c..6fbc6fa 100644 --- a/src/pages/ReviewsPage.jsx +++ b/src/pages/ReviewsPage.jsx @@ -1,13 +1,45 @@ +import { useAuthState } from "../utilities/firebase"; +import { useState, useEffect } from "react"; +import { findZipcode } from "../utilities/findZipcode"; +import { findCafes } from "../utilities/findCafes"; import Banner from "../components/Banner"; import CafeList from "../components/CafeList"; const ReviewsPage = () => { + const [user] = useAuthState(); + const [zipcode, setZipcode] = useState(null); + const [cafes, setCafes] = useState([]); + + useEffect(() => { + const fetchData = async () => { + if (user && user.email) { + try { + const userZipcode = await findZipcode(user.email); + setZipcode(userZipcode); + + if (userZipcode) { + await findCafes(2, userZipcode, setCafes); + } + } catch (error) { + console.error("Error fetching zipcode or cafes:", error); + setZipcode(null); + setCafes([]); + } + } + }; + fetchData(); + }, [user]); + + useEffect(() => { + console.log(cafes); + }, [cafes]); + return (
- - + +
- ) -} + ); +}; -export default ReviewsPage; \ No newline at end of file +export default ReviewsPage; diff --git a/src/utilities/findCafes.jsx b/src/utilities/findCafes.jsx index 98dea2a..7160390 100644 --- a/src/utilities/findCafes.jsx +++ b/src/utilities/findCafes.jsx @@ -15,7 +15,11 @@ export const findCafes = (dist, address, setCafes) => { }, (results, status) => { if (status === window.google.maps.places.PlacesServiceStatus.OK) { - setCafes(results.map((place) => place.name)); + setCafes(results.map((place) => ({ + name: place.name, + placeId: place.place_id, + vicinity: place.vicinity + }))); } else { alert("No cafes found within the specified radius."); setCafes([]); From 244bed195cd2e3351eeb3a718feece3b0d8c44bd Mon Sep 17 00:00:00 2001 From: angelp03 Date: Fri, 8 Nov 2024 13:08:22 -0600 Subject: [PATCH 3/7] navigation to cafe pages based on place id --- src/App.jsx | 2 + src/components/Banner.css | 39 +++++++++++++++--- src/components/Banner.jsx | 82 +++++++++++++++++++++++++++++++------ src/components/CafeList.jsx | 14 ++++++- src/pages/CafePage.jsx | 62 ++++++++++++++++++++++++++++ src/pages/LandingPage.jsx | 33 ++++++++++++++- src/pages/ReviewsPage.jsx | 2 +- 7 files changed, 211 insertions(+), 23 deletions(-) create mode 100644 src/pages/CafePage.jsx diff --git a/src/App.jsx b/src/App.jsx index 05dee09..49e5fbc 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -2,6 +2,7 @@ import { BrowserRouter as Router, Route, Routes } from "react-router-dom"; import LandingPage from './pages/LandingPage'; import ReviewPostPage from "./pages/ReviewPostPage"; import ReviewsPage from "./pages/ReviewsPage"; +import CafePage from "./pages/CafePage"; const App = () => { return ( @@ -10,6 +11,7 @@ const App = () => { }/> }/> }/> + }/> ) diff --git a/src/components/Banner.css b/src/components/Banner.css index f3016a4..670039d 100644 --- a/src/components/Banner.css +++ b/src/components/Banner.css @@ -16,7 +16,7 @@ } .logo-div { - height:100%; + height: 100%; display: flex; align-items: center; margin-right: 3rem; @@ -31,6 +31,7 @@ .logo-search-div form { display: flex; align-items: center; + position: relative; /* Allow positioning of the autocomplete dropdown */ } .text-input { @@ -57,12 +58,12 @@ justify-content: center; align-items: center; border-top: none; - border-left:none; - border-right:none; + border-left: none; + border-right: none; border-top-left-radius: 0; border-bottom-left-radius: 0; border-bottom: 1px solid black; - border-top-right-radius: 0.25; + border-top-right-radius: 0.25rem; } .banner-buttons { @@ -73,7 +74,7 @@ .banner-btn { text-align: center; - height:2.45rem; + height: 2.45rem; width: 8rem; background-color: #8A3323; color: white; @@ -88,4 +89,30 @@ .banner-btn:hover { background-color: #a85c43; -} \ No newline at end of file +} + +/* Autocomplete Dropdown */ +.autocomplete-dropdown { + position: absolute; + top: 100%; /* Align directly below the input field */ + left: 0; + right: 0; + background-color: white; + border: 1px solid #ddd; + border-radius: 0.25rem; + max-height: 200px; + overflow-y: auto; + z-index: 10; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + width: 22rem; /* Match the width of the input field */ +} + +.autocomplete-item { + padding: 10px; + cursor: pointer; + font-family: 'Hind Vadodara', sans-serif; +} + +.autocomplete-item:hover { + background-color: #f0f0f0; +} diff --git a/src/components/Banner.jsx b/src/components/Banner.jsx index 308290c..99609df 100644 --- a/src/components/Banner.jsx +++ b/src/components/Banner.jsx @@ -3,26 +3,84 @@ import Icon from '../Icon.svg'; import Search from '../Search.svg'; import { handleSignIn } from '../utilities/firebase'; import { useNavigate } from 'react-router-dom'; +import { useState } from 'react'; -export const Banner = () => { +export const Banner = ({ cafes }) => { const navigate = useNavigate(); - const navigateToReviews = () => { - navigate('/cafes') - } + const [searchText, setSearchText] = useState(''); + const [filteredCafes, setFilteredCafes] = useState([]); + const handleInputChange = (e) => { + const text = e.target.value; + setSearchText(text); + + if (text) { + const matches = cafes.filter(cafe => + cafe.name.toLowerCase().includes(text.toLowerCase()) + ); + setFilteredCafes(matches); + } else { + setFilteredCafes([]); + } + }; + + const handleSelectCafe = (cafe) => { + setSearchText(cafe.name); + setFilteredCafes([]); + navigate(`/cafe/${cafe.placeId}`); + }; + + + const handleSearch = (e) => { + e.preventDefault(); + + if (searchText.trim() === '') { + navigate('/cafes'); + } else { + const selectedCafe = cafes.find(cafe => + cafe.name.toLowerCase() === searchText.toLowerCase() + ); + + if (selectedCafe) { + navigate(`/cafe/${selectedCafe.placeId}`); + } else { + alert('Cafe not found.'); + } + } + }; + return (
- + CafeWay Logo

CafeWay

-
- - + {filteredCafes.length > 0 && ( +
    + {filteredCafes.map((cafe) => ( +
  • handleSelectCafe(cafe)} + className="autocomplete-item" + > + {cafe.name} - {cafe.vicinity} +
  • + ))} +
+ )}
@@ -34,7 +92,7 @@ export const Banner = () => {
- ) -} + ); +}; -export default Banner; \ No newline at end of file +export default Banner; diff --git a/src/components/CafeList.jsx b/src/components/CafeList.jsx index 8a7d1bc..c0c16e0 100644 --- a/src/components/CafeList.jsx +++ b/src/components/CafeList.jsx @@ -1,6 +1,12 @@ import './CafeList.css'; +import { useNavigate } from 'react-router-dom'; const CafeList = ({ cafes }) => { + const navigate = useNavigate(); + const handleClick = (placeId) => { + navigate(`/cafe/${placeId}`); + }; + return (
@@ -22,8 +28,12 @@ const CafeList = ({ cafes }) => {
- {cafes.map((cafe, index) => ( -
+ {cafes.map((cafe) => ( +
handleClick(cafe.placeId)} + >

{cafe.name}

{cafe.vicinity}

diff --git a/src/pages/CafePage.jsx b/src/pages/CafePage.jsx new file mode 100644 index 0000000..c29b1c2 --- /dev/null +++ b/src/pages/CafePage.jsx @@ -0,0 +1,62 @@ +import { useParams } from 'react-router-dom'; +import { useState, useEffect } from 'react'; +import { findZipcode } from '../utilities/findZipcode'; +import { findCafes } from '../utilities/findCafes'; +import { useAuthState } from '../utilities/firebase'; + +const CafePage = () => { + const { place_id } = useParams(); + const [user] = useAuthState(); + const [zipcode, setZipcode] = useState(null); + const [cafes, setCafes] = useState([]); + const [cafe, setCafe] = useState(null); + + useEffect(() => { + if (user && user.email) { + const fetchZipCode = async () => { + const userZipcode = await findZipcode(user.email); + setZipcode(userZipcode); + }; + fetchZipCode(); + } + }, [user]); + + useEffect(() => { + if (zipcode) { + const fetchCafes = async () => { + try { + await findCafes(5, zipcode, setCafes); + } catch (error) { + console.error('Error fetching cafes:', error); + setCafes([]); + } + }; + fetchCafes(); + } + }, [zipcode]); + + useEffect(() => { + if (cafes.length > 0) { + const foundCafe = cafes.find((cafe) => cafe.placeId === place_id); + setCafe(foundCafe); + } + }, [cafes, place_id]); + + if (!cafe) { + return ( +
+

Cafe not found

+

Sorry, we couldn't find a cafe with the specified ID.

+
+ ); + } + + return ( +
+

{cafe.name}

+

Location: {cafe.vicinity}

+
+ ); +} + +export default CafePage; diff --git a/src/pages/LandingPage.jsx b/src/pages/LandingPage.jsx index b38d666..0ed5ef1 100644 --- a/src/pages/LandingPage.jsx +++ b/src/pages/LandingPage.jsx @@ -3,10 +3,39 @@ import '../App.css'; import MapComponent from "../MapComponent"; import Banner from '../components/Banner'; import { useAuthState } from '../utilities/firebase'; +import { useState, useEffect } from "react"; +import { findZipcode } from "../utilities/findZipcode"; +import { findCafes } from "../utilities/findCafes"; const LandingPage = () => { const [user] = useAuthState(); - console.log(user) + const [zipcode, setZipcode] = useState(null); + const [cafes, setCafes] = useState([]); + + useEffect(() => { + const fetchData = async () => { + if (user && user.email) { + try { + const userZipcode = await findZipcode(user.email); + setZipcode(userZipcode); + + if (userZipcode) { + await findCafes(2, userZipcode, setCafes); + } + } catch (error) { + console.error("Error fetching zipcode or cafes:", error); + setZipcode(null); + setCafes([]); + } + } + }; + fetchData(); + }, [user]); + + useEffect(() => { + console.log(cafes); + }, [cafes]); + const cafesMap = [ { name: "Cafe Blue", lat: 42.046, lng: -87.688 }, { name: "Green Bean Cafe", lat: 42.048, lng: -87.684 }, @@ -15,7 +44,7 @@ const LandingPage = () => { return (
- + {/* Integrate MapComponent with hardcoded data */}
diff --git a/src/pages/ReviewsPage.jsx b/src/pages/ReviewsPage.jsx index 6fbc6fa..f7c8750 100644 --- a/src/pages/ReviewsPage.jsx +++ b/src/pages/ReviewsPage.jsx @@ -36,7 +36,7 @@ const ReviewsPage = () => { return (
- +
); From fac5ddcd02a64fdfc09e2913fde9524bd6a21db8 Mon Sep 17 00:00:00 2001 From: angelp03 Date: Fri, 8 Nov 2024 13:30:16 -0600 Subject: [PATCH 4/7] populate cafe page with posts and responses --- database.rules.json | 4 +-- src/dummy.json | 31 +++++++++++++++++ src/pages/CafePage.jsx | 50 +++++++++++++++++++++++++++ src/utilities/{posts.js => posts.jsx} | 3 +- 4 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 src/dummy.json rename src/utilities/{posts.js => posts.jsx} (94%) diff --git a/database.rules.json b/database.rules.json index f54493d..f122d7b 100644 --- a/database.rules.json +++ b/database.rules.json @@ -1,7 +1,7 @@ { /* Visit https://firebase.google.com/docs/database/security to learn more about security rules. */ "rules": { - ".read": false, - ".write": false + ".read": true, + ".write": true } } \ No newline at end of file diff --git a/src/dummy.json b/src/dummy.json new file mode 100644 index 0000000..bd2b4d8 --- /dev/null +++ b/src/dummy.json @@ -0,0 +1,31 @@ +{ + "test_id": { + "cafeId": "ChIJ60vPsg_QD4gRE61MVG65nSs", + "content": "Great place for coffee and work!", + "category": "Review", + "date": "2024-11-07T14:30:00Z", + "replies": { + "replyId_1": "I agree, the coffee is fantastic!", + "replyId_2": "The WiFi is also great for working!" + }, + "uid": "user_1" + }, + "test_id_2": { + "cafeId": "ChIJ60vPsg_QD4gRE61MVG65nSs", + "content": "Could use more seating during peak hours.", + "category": "Suggestion", + "date": "2024-11-08T09:00:00Z", + "replies": {}, + "uid": "user_2" + }, + "test_id_3": { + "cafeId": "ChIJ60vPsg_QD4gRE61MVG65nSs", + "content": "Is the cafe open late on weekends?", + "category": "Question", + "date": "2024-11-09T11:45:00Z", + "replies": { + "replyId_1": "Yes, they stay open until 10 PM on Saturdays and Sundays!" + }, + "uid": "user_3" + } +} \ No newline at end of file diff --git a/src/pages/CafePage.jsx b/src/pages/CafePage.jsx index c29b1c2..e5f722e 100644 --- a/src/pages/CafePage.jsx +++ b/src/pages/CafePage.jsx @@ -3,6 +3,7 @@ import { useState, useEffect } from 'react'; import { findZipcode } from '../utilities/findZipcode'; import { findCafes } from '../utilities/findCafes'; import { useAuthState } from '../utilities/firebase'; +import { findCafePosts } from '../utilities/posts'; // Import the hook const CafePage = () => { const { place_id } = useParams(); @@ -42,6 +43,10 @@ const CafePage = () => { } }, [cafes, place_id]); + // Use the findCafePosts hook correctly here + const [posts, postError] = findCafePosts(cafe?.placeId); // Using the hook directly + console.log(posts); // Check the format of posts + if (!cafe) { return (
@@ -51,10 +56,55 @@ const CafePage = () => { ); } + // Assuming posts are in the form of an object with keys like test_id + const postItems = posts ? Object.values(posts) : []; // Convert the object into an array if needed + return (

{cafe.name}

Location: {cafe.vicinity}

+ + {/* Render posts if they exist */} + {postItems && postItems.length > 0 ? ( +
+

Posts

+
    + {postItems.map((post, index) => ( +
  • +

    Category: {post.category}

    +

    Content: {post.content}

    + {new Date(post.date).toLocaleString()} + + {/* Render replies if they exist */} + {post.replies && Object.entries(post.replies).length > 0 ? ( +
    +

    Replies:

    +
      + {Object.entries(post.replies).map(([replyId, message], replyIndex) => ( +
    • +

      {message}

      +
    • + ))} +
    +
    + ) : ( +

    No replies for this post.

    + )} +
  • + ))} +
+
+ ) : ( +

No posts available for this cafe.

+ )} + + {/* Handle errors in fetching posts */} + {postError && ( +
+

Error loading posts

+

{postError.message}

+
+ )}
); } diff --git a/src/utilities/posts.js b/src/utilities/posts.jsx similarity index 94% rename from src/utilities/posts.js rename to src/utilities/posts.jsx index d1f47f1..050ff36 100644 --- a/src/utilities/posts.js +++ b/src/utilities/posts.jsx @@ -11,11 +11,11 @@ export const findCafePosts = (cafeId) => { const postsRef = ref(database, `/posts`); const postsQuery = query(postsRef, orderByChild('cafeId'), equalTo(cafeId)); - const unsubscribe = onValue( postsQuery, (snapshot) => { if (snapshot.exists()) { + console.log(snapshot.val()); setData(snapshot.val()); } else { setData([]); @@ -28,6 +28,5 @@ export const findCafePosts = (cafeId) => { return () => unsubscribe(); }, [cafeId]); - return [data, error]; }; \ No newline at end of file From 0db40fdbab72ab97907cd43b66e9278a9b3dbdc4 Mon Sep 17 00:00:00 2001 From: angelp03 Date: Fri, 8 Nov 2024 13:36:10 -0600 Subject: [PATCH 5/7] improve frontend of cafe page --- src/pages/CafePage.css | 90 ++++++++++++++++++++++++++++++++++++ src/pages/CafePage.jsx | 102 +++++++++++++++++++++-------------------- 2 files changed, 143 insertions(+), 49 deletions(-) create mode 100644 src/pages/CafePage.css diff --git a/src/pages/CafePage.css b/src/pages/CafePage.css new file mode 100644 index 0000000..5aa8304 --- /dev/null +++ b/src/pages/CafePage.css @@ -0,0 +1,90 @@ +.cafe-page-container { + padding: 2rem; + font-family: 'Hind Vadodara', sans-serif; +} + +.cafe-header { + margin-bottom: 2rem; + border-bottom: 1px solid #ddd; + padding-bottom: 1rem; + text-align: center; +} + +.cafe-header h1 { + font-size: 2.5rem; + color: #8A3323; + margin-bottom: 0.5rem; +} + +.cafe-header p { + font-size: 1.2rem; +} + +.posts-section { + margin-top: 2rem; +} + +.posts-section h2 { + font-size: 1.8rem; + margin-bottom: 1rem; + color: #8A3323; +} +.posts-list { + list-style-type: none; + padding: 0; +} + +.post-item { + padding: 1rem; + margin-bottom: 1rem; + border: 1px solid #ddd; + border-radius: 8px; + background-color: #fafafa; +} + +.post-header { + margin-bottom: 1rem; + font-size: 1rem; + color: #555; +} + +.post-header strong { + color: #8A3323; +} + +.post-content p { + font-size: 1.2rem; + color: #333; +} + +.replies-section { + margin-top: 1rem; + padding-left: 20px; + background-color: #f9f9f9; + border-left: 3px solid #8A3323; +} + +.replies-section h3 { + font-size: 1.5rem; + margin-bottom: 1rem; +} + +.reply-item { + margin-bottom: 0.8rem; + font-size: 1.1rem; + color: #333; +} + +.error-message { + text-align: center; + padding: 2rem; + background-color: #ffebee; + border: 1px solid #f44336; + border-radius: 8px; + color: #d32f2f; +} + +.error-message h1, +.error-message h3 { + color: #d32f2f; +} diff --git a/src/pages/CafePage.jsx b/src/pages/CafePage.jsx index e5f722e..939e376 100644 --- a/src/pages/CafePage.jsx +++ b/src/pages/CafePage.jsx @@ -1,9 +1,11 @@ +import './CafePage.css'; import { useParams } from 'react-router-dom'; import { useState, useEffect } from 'react'; import { findZipcode } from '../utilities/findZipcode'; import { findCafes } from '../utilities/findCafes'; import { useAuthState } from '../utilities/firebase'; -import { findCafePosts } from '../utilities/posts'; // Import the hook +import { findCafePosts } from '../utilities/posts'; +import Banner from '../components/Banner'; const CafePage = () => { const { place_id } = useParams(); @@ -43,68 +45,70 @@ const CafePage = () => { } }, [cafes, place_id]); - // Use the findCafePosts hook correctly here - const [posts, postError] = findCafePosts(cafe?.placeId); // Using the hook directly - console.log(posts); // Check the format of posts + const [posts, postError] = findCafePosts(cafe?.placeId); + console.log(posts); if (!cafe) { return ( -
+

Cafe not found

Sorry, we couldn't find a cafe with the specified ID.

); } - // Assuming posts are in the form of an object with keys like test_id - const postItems = posts ? Object.values(posts) : []; // Convert the object into an array if needed + const postItems = posts ? Object.values(posts) : []; return (
-

{cafe.name}

-

Location: {cafe.vicinity}

- - {/* Render posts if they exist */} - {postItems && postItems.length > 0 ? ( -
-

Posts

-
    - {postItems.map((post, index) => ( -
  • -

    Category: {post.category}

    -

    Content: {post.content}

    - {new Date(post.date).toLocaleString()} - - {/* Render replies if they exist */} - {post.replies && Object.entries(post.replies).length > 0 ? ( -
    -

    Replies:

    -
      - {Object.entries(post.replies).map(([replyId, message], replyIndex) => ( -
    • -

      {message}

      -
    • - ))} -
    -
    - ) : ( -

    No replies for this post.

    - )} -
  • - ))} -
+ +
+
+

{cafe.name}

+

Location: {cafe.vicinity}

- ) : ( -

No posts available for this cafe.

- )} - {/* Handle errors in fetching posts */} - {postError && ( -
-

Error loading posts

-

{postError.message}

-
- )} + {postItems && postItems.length > 0 ? ( +
+

Posts

+
    + {postItems.map((post, index) => ( +
  • +
    +

    Category: {post.category}

    + {new Date(post.date).toLocaleString()} +
    +
    +

    {post.content}

    +
    + {post.replies && Object.entries(post.replies).length > 0 ? ( +
    +

    Replies:

    +
      + {Object.entries(post.replies).map(([replyId, message], replyIndex) => ( +
    • +

      {message}

      +
    • + ))} +
    +
    + ) : ( +

    No replies for this post.

    + )} +
  • + ))} +
+
+ ) : ( +

No posts available for this cafe.

+ )} + {postError && ( +
+

Error loading posts

+

{postError.message}

+
+ )} +
); } From 13f6919cb23b3ab8d03bf8fa508125575c4e4fef Mon Sep 17 00:00:00 2001 From: angelp03 Date: Fri, 8 Nov 2024 13:41:07 -0600 Subject: [PATCH 6/7] update login/logout button based on auth state --- src/components/Banner.jsx | 23 +++++++++++++++-------- src/pages/CafePage.jsx | 10 ++++++---- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/components/Banner.jsx b/src/components/Banner.jsx index 99609df..b5a7a2f 100644 --- a/src/components/Banner.jsx +++ b/src/components/Banner.jsx @@ -1,14 +1,17 @@ import './Banner.css'; import Icon from '../Icon.svg'; import Search from '../Search.svg'; -import { handleSignIn } from '../utilities/firebase'; import { useNavigate } from 'react-router-dom'; import { useState } from 'react'; +import { handleSignIn, handleLogout, useAuthState } from '../utilities/firebase'; export const Banner = ({ cafes }) => { const navigate = useNavigate(); const [searchText, setSearchText] = useState(''); const [filteredCafes, setFilteredCafes] = useState([]); + + const [user] = useAuthState(); + const handleInputChange = (e) => { const text = e.target.value; setSearchText(text); @@ -29,7 +32,6 @@ export const Banner = ({ cafes }) => { navigate(`/cafe/${cafe.placeId}`); }; - const handleSearch = (e) => { e.preventDefault(); @@ -53,9 +55,7 @@ export const Banner = ({ cafes }) => {
CafeWay Logo -

- CafeWay -

+

CafeWay

{ )}
+
- + {user ? ( + + ) : ( + + )}
); diff --git a/src/pages/CafePage.jsx b/src/pages/CafePage.jsx index 939e376..08b1485 100644 --- a/src/pages/CafePage.jsx +++ b/src/pages/CafePage.jsx @@ -57,22 +57,24 @@ const CafePage = () => { ); } - const postItems = posts ? Object.values(posts) : []; + const postItems = posts ? Object.values(posts) : []; + + const sortedPosts = postItems.sort((a, b) => new Date(b.date) - new Date(a.date)); return (
- +

{cafe.name}

Location: {cafe.vicinity}

- {postItems && postItems.length > 0 ? ( + {sortedPosts && sortedPosts.length > 0 ? (

Posts

    - {postItems.map((post, index) => ( + {sortedPosts.map((post, index) => (
  • Category: {post.category}

    From cd0b6028a785ca830035694228220fb4587e8880 Mon Sep 17 00:00:00 2001 From: angelp03 Date: Fri, 8 Nov 2024 14:00:50 -0600 Subject: [PATCH 7/7] allow replies and fix zipcode lookup --- src/pages/CafePage.jsx | 52 +++++++++++++++++++++++++++++++---- src/pages/ReviewsPage.jsx | 1 + src/utilities/findZipcode.jsx | 3 +- src/utilities/posts.jsx | 34 +++++++++++++++++++++-- 4 files changed, 81 insertions(+), 9 deletions(-) diff --git a/src/pages/CafePage.jsx b/src/pages/CafePage.jsx index 08b1485..8a2fd53 100644 --- a/src/pages/CafePage.jsx +++ b/src/pages/CafePage.jsx @@ -4,7 +4,7 @@ import { useState, useEffect } from 'react'; import { findZipcode } from '../utilities/findZipcode'; import { findCafes } from '../utilities/findCafes'; import { useAuthState } from '../utilities/firebase'; -import { findCafePosts } from '../utilities/posts'; +import { findCafePosts, addReplyToPost } from '../utilities/posts'; import Banner from '../components/Banner'; const CafePage = () => { @@ -13,6 +13,7 @@ const CafePage = () => { const [zipcode, setZipcode] = useState(null); const [cafes, setCafes] = useState([]); const [cafe, setCafe] = useState(null); + const [replyMessages, setReplyMessages] = useState({}); useEffect(() => { if (user && user.email) { @@ -46,7 +47,6 @@ const CafePage = () => { }, [cafes, place_id]); const [posts, postError] = findCafePosts(cafe?.placeId); - console.log(posts); if (!cafe) { return ( @@ -57,9 +57,30 @@ const CafePage = () => { ); } - const postItems = posts ? Object.values(posts) : []; + const postItems = posts ? Object.entries(posts) : []; + const sortedPosts = postItems.sort((a, b) => new Date(b[1].date) - new Date(a[1].date)); - const sortedPosts = postItems.sort((a, b) => new Date(b.date) - new Date(a.date)); + const handleReplyChange = (e, postId) => { + setReplyMessages((prev) => ({ + ...prev, + [postId]: e.target.value, + })); + }; + + const handleAddReply = async (postId) => { + const replyMessage = replyMessages[postId]; + if (replyMessage && replyMessage.trim()) { + const error = await addReplyToPost(postId, user?.uid, replyMessage); + if (error) { + console.error('Error adding reply:', error); + } else { + setReplyMessages((prev) => ({ + ...prev, + [postId]: '', + })); + } + } + }; return (
    @@ -74,7 +95,7 @@ const CafePage = () => {

    Posts

      - {sortedPosts.map((post, index) => ( + {sortedPosts.map(([postKey, post], index) => (
    • Category: {post.category}

      @@ -83,6 +104,7 @@ const CafePage = () => {

      {post.content}

      + {post.replies && Object.entries(post.replies).length > 0 ? (

      Replies:

      @@ -97,6 +119,24 @@ const CafePage = () => { ) : (

      No replies for this post.

      )} + + {user ? ( +
      +