From d606e5ca26253ea8df5dfa0f134dd67fa6f88a42 Mon Sep 17 00:00:00 2001 From: Talal Date: Sat, 7 Dec 2024 20:15:01 +0300 Subject: [PATCH 01/42] Initialized routing for the review system --- src/App.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 274d401..47b0a0f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,6 +6,7 @@ import Navbar from './components/Navbar'; import ListPage from './pages/ListPage'; import MapPage from './pages/MapPage'; import NotFoundPage from './pages/NotFoundPage'; +import RatingFormPage from './pages/RatingFormPage'; // Import the new page import { queryLocations, getLocationStatus } from './util/queryLocations'; import './App.css'; import { @@ -14,13 +15,13 @@ import { } from './types/locationTypes'; const CMU_EATS_API_URL = 'https://dining.apis.scottylabs.org/locations'; -// const CMU_EATS_API_URL = 'http://localhost:5173/example-response.json'; // for debugging purposes (note that you need an example-response.json file in the /public folder) -// const CMU_EATS_API_URL = 'http://localhost:5010/locations'; // for debugging purposes (note that you need an example-response.json file in the /public folder) + function App() { // Load locations const [locations, setLocations] = useState(); const [extendedLocationData, setExtendedLocationData] = useState(); + useEffect(() => { queryLocations(CMU_EATS_API_URL).then((parsedLocations) => { setLocations(parsedLocations); @@ -31,8 +32,6 @@ function App() { const intervalId = setInterval( (function updateExtendedLocationData() { if (locations !== undefined) { - // Remove .setZone('America/New_York') and change time in computer settings when testing - // Alternatively, simply set now = DateTime.local(2023, 12, 22, 18, 33); where the parameters are Y,M,D,H,M const now = DateTime.now().setZone('America/New_York'); setExtendedLocationData( locations.map((location) => ({ @@ -41,8 +40,8 @@ function App() { })), ); } - return updateExtendedLocationData; // returns itself here - })(), // self-invoking function + return updateExtendedLocationData; + })(), 1 * 1000, // updates every second ); return () => clearInterval(intervalId); @@ -92,6 +91,10 @@ function App() { } /> + } // Add the new route + /> } /> @@ -103,3 +106,4 @@ function App() { } export default App; + From 0a9e9d140513e2dd48ab7e03848c3a701da74084 Mon Sep 17 00:00:00 2001 From: Talal Date: Sat, 7 Dec 2024 20:26:07 +0300 Subject: [PATCH 02/42] Addition of Review system button in the navigation bar with the proper routing --- src/components/Navbar.tsx | 40 +++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 996a983..c469c11 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -19,9 +19,9 @@ function Navbar() { strokeLinecap="round" strokeLinejoin="round" d="M8.25 6.75h12M8.25 12h12m-12 5.25h12M3.75 6.75h.007v.008H3.75V6.75zm.375 - 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zM3.75 12h.007v.008H3.75V12zm.375 - 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm-.375 5.25h.007v.008H3.75v-.008zm.375 - 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z" + 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zM3.75 12h.007v.008H3.75V12zm.375 + 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm-.375 5.25h.007v.008H3.75v-.008zm.375 + 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z" /> Locations @@ -38,16 +38,39 @@ function Navbar() { strokeLinecap="round" strokeLinejoin="round" d="M9 6.75V15m6-6v8.25m.503 - 3.498l4.875-2.437c.381-.19.622-.58.622-1.006V4.82c0-.836-.88-1.38-1.628-1.006l-3.869 - 1.934c-.317.159-.69.159-1.006 0L9.503 3.252a1.125 1.125 0 00-1.006 0L3.622 5.689C3.24 - 5.88 3 6.27 3 6.695V19.18c0 .836.88 1.38 1.628 1.006l3.869-1.934c.317-.159.69-.159 - 1.006 0l4.994 2.497c.317.158.69.158 1.006 0z" + 3.498l4.875-2.437c.381-.19.622-.58.622-1.006V4.82c0-.836-.88-1.38-1.628-1.006l-3.869 + 1.934c-.317.159-.69.159-1.006 0L9.503 3.252a1.125 1.125 0 00-1.006 0L3.622 5.689C3.24 + 5.88 3 6.27 3 6.695V19.18c0 .836.88 1.38 1.628 1.006l3.869-1.934c-.317-.159.69-.159 + 1.006 0l4.994 2.497c.317.158.69.158 1.006 0z" /> Map + + + + + Leave a Review +
@@ -55,3 +78,4 @@ function Navbar() { } export default Navbar; + From f8e15ae43701dedce653c72b66d5b35994e27a76 Mon Sep 17 00:00:00 2001 From: Talal Date: Sat, 7 Dec 2024 20:28:42 +0300 Subject: [PATCH 03/42] Added a new file for the review system frotend, addition of dependencies and email input as first addition --- src/pages/RatingFormPage.tsx | 42 ++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/pages/RatingFormPage.tsx diff --git a/src/pages/RatingFormPage.tsx b/src/pages/RatingFormPage.tsx new file mode 100644 index 0000000..f4a0c0b --- /dev/null +++ b/src/pages/RatingFormPage.tsx @@ -0,0 +1,42 @@ +import { Typography, Button, TextField, Checkbox, FormControlLabel } from '@mui/material'; +import { useState } from 'react'; + +const RatingFormPage = () => { + const [userEmail, setUserEmail] = useState(''); + const [selectedRestaurant, setSelectedRestaurant] = useState(''); + const [wouldRecommend, setWouldRecommend] = useState(false); + const [foodExperience, setFoodExperience] = useState(''); + const [serviceExperience, setServiceExperience] = useState(''); + const [cleanlinessExperience, setCleanlinessExperience] = useState(''); + const [additionalComments, setAdditionalComments] = useState(''); + + return ( +
+
+ + Leave a Review + +
+
+ {/* Email Input */} + setUserEmail(e.target.value)} + /> + + + + From 082ac7cd45d796898ead0bad04eb91fd0369024d Mon Sep 17 00:00:00 2001 From: Talal Date: Sat, 7 Dec 2024 20:30:57 +0300 Subject: [PATCH 04/42] Added food experience describtion input and the restaurant selection dropdown with all of the restaurants of CMUeats website --- src/pages/RatingFormPage.tsx | 63 ++++++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/src/pages/RatingFormPage.tsx b/src/pages/RatingFormPage.tsx index f4a0c0b..6c6ad89 100644 --- a/src/pages/RatingFormPage.tsx +++ b/src/pages/RatingFormPage.tsx @@ -37,6 +37,65 @@ const RatingFormPage = () => { onChange={(e) => setUserEmail(e.target.value)} /> + {/* Restaurant Selection */} + setSelectedRestaurant(e.target.value)} + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + {/* Food Experience */} + setFoodExperience(e.target.value)} + /> From bebb0fd711e03079b86b66c22643b3cf51351264 Mon Sep 17 00:00:00 2001 From: Talal Date: Sat, 7 Dec 2024 20:32:00 +0300 Subject: [PATCH 05/42] Addition of service and cleanliness experiences input for the form --- src/pages/RatingFormPage.tsx | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/pages/RatingFormPage.tsx b/src/pages/RatingFormPage.tsx index 6c6ad89..d5e039d 100644 --- a/src/pages/RatingFormPage.tsx +++ b/src/pages/RatingFormPage.tsx @@ -99,3 +99,31 @@ const RatingFormPage = () => { value={foodExperience} onChange={(e) => setFoodExperience(e.target.value)} /> + + {/* Service Experience */} + setServiceExperience(e.target.value)} + /> + + {/* Cleanliness Experience */} + setCleanlinessExperience(e.target.value)} + /> + + + + From 32dfa8b8b76c3ea655a593febe4f16323e72b86b Mon Sep 17 00:00:00 2001 From: Filippos Date: Sat, 7 Dec 2024 21:44:33 +0300 Subject: [PATCH 06/42] Add a checkbox inquiring people on whether they would recommend this restaurant to a friend --- src/pages/RatingFormPage.tsx | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/pages/RatingFormPage.tsx b/src/pages/RatingFormPage.tsx index d5e039d..19d59e4 100644 --- a/src/pages/RatingFormPage.tsx +++ b/src/pages/RatingFormPage.tsx @@ -124,6 +124,19 @@ const RatingFormPage = () => { onChange={(e) => setCleanlinessExperience(e.target.value)} /> +{/* Recommend Checkbox */} + setWouldRecommend(e.target.checked)} + /> + } + label="Would you recommend this restaurant to a friend?" + /> + +
+ ); +}; - - +export default RatingFormPage; From 3d968ebd93dd9f7615beebf394b638bf32d31cbe Mon Sep 17 00:00:00 2001 From: Filippos Date: Sat, 7 Dec 2024 21:45:25 +0300 Subject: [PATCH 07/42] Added a textfield for people to add any comments or suggestions --- src/pages/RatingFormPage.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/pages/RatingFormPage.tsx b/src/pages/RatingFormPage.tsx index 19d59e4..1246275 100644 --- a/src/pages/RatingFormPage.tsx +++ b/src/pages/RatingFormPage.tsx @@ -134,6 +134,18 @@ const RatingFormPage = () => { } label="Would you recommend this restaurant to a friend?" /> + + {/* Additional Comments */} + setAdditionalComments(e.target.value)} + /> + ); From a9970757508afce0687470be1783462511e8ff5b Mon Sep 17 00:00:00 2001 From: Filippos Date: Sat, 7 Dec 2024 21:52:14 +0300 Subject: [PATCH 08/42] Added a button for people to submit their feedback and send the email --- src/pages/RatingFormPage.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/pages/RatingFormPage.tsx b/src/pages/RatingFormPage.tsx index 1246275..301c323 100644 --- a/src/pages/RatingFormPage.tsx +++ b/src/pages/RatingFormPage.tsx @@ -146,6 +146,19 @@ const RatingFormPage = () => { onChange={(e) => setAdditionalComments(e.target.value)} /> + {/* Submit Button */} + ); From b4b8cc32e8342527bb44334408e7b61647fcb82e Mon Sep 17 00:00:00 2001 From: mthani2 Date: Sun, 8 Dec 2024 15:41:51 +0300 Subject: [PATCH 09/42] Enhanced TextField UX by updating focus colors and adding hover functionality --- .env | 4 ++++ .env.example | 8 ++++---- src/pages/RatingFormPage.tsx | 40 ++++++++++++++++++++++++++---------- 3 files changed, 37 insertions(+), 15 deletions(-) create mode 100644 .env diff --git a/.env b/.env new file mode 100644 index 0000000..065238c --- /dev/null +++ b/.env @@ -0,0 +1,4 @@ +MAPKIT_JS_TEAM_ID=4Y39FMA838 +MAPKIT_JS_AUTH_KEY=LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR1RBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJIa3dkd0lCQVFRZ1Y4Q2crcXRMaEQxcU4zcU0KUkN4Yi9ZU1dQWkg3RElPQXdqSEFpNTl1RkphZ0NnWUlLb1pJemowREFRZWhSQU5DQUFUUlJiRkpPaW5rcGNDbQpxZnRiOWY3eUhlZzkxbTRvTExrMyt0UTl5Wkd4M2k0LzUybUlJZnVOZHQ1VjgrVENKM3NjQjRyeHhIUGJOZ3Z5CmpLdUVOME9rCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= +MAPKIT_JS_KEY_ID=WZ9QVAGR4F +MAPKIT_JS_ORIGIN=http://localhost \ No newline at end of file diff --git a/.env.example b/.env.example index 53efeca..065238c 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,4 @@ -MAPKIT_JS_TEAM_ID= -MAPKIT_JS_AUTH_KEY= -MAPKIT_JS_KEY_ID= -MAPKIT_JS_ORIGIN= +MAPKIT_JS_TEAM_ID=4Y39FMA838 +MAPKIT_JS_AUTH_KEY=LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR1RBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJIa3dkd0lCQVFRZ1Y4Q2crcXRMaEQxcU4zcU0KUkN4Yi9ZU1dQWkg3RElPQXdqSEFpNTl1RkphZ0NnWUlLb1pJemowREFRZWhSQU5DQUFUUlJiRkpPaW5rcGNDbQpxZnRiOWY3eUhlZzkxbTRvTExrMyt0UTl5Wkd4M2k0LzUybUlJZnVOZHQ1VjgrVENKM3NjQjRyeHhIUGJOZ3Z5CmpLdUVOME9rCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= +MAPKIT_JS_KEY_ID=WZ9QVAGR4F +MAPKIT_JS_ORIGIN=http://localhost \ No newline at end of file diff --git a/src/pages/RatingFormPage.tsx b/src/pages/RatingFormPage.tsx index 301c323..69da917 100644 --- a/src/pages/RatingFormPage.tsx +++ b/src/pages/RatingFormPage.tsx @@ -1,7 +1,26 @@ -import { Typography, Button, TextField, Checkbox, FormControlLabel } from '@mui/material'; import { useState } from 'react'; +import { Typography, Button, TextField, Checkbox, FormControlLabel } from '@mui/material'; const RatingFormPage = () => { + // Defined textFieldStyles + const textFieldStyles = { + backgroundColor: '#2D2F32', // Adjust the background color if needed + '& label.Mui-focused': { + color: 'green', // Changes the label color to green when focused + }, + '& .MuiOutlinedInput-root': { + '& fieldset': { + borderColor: 'grey', // Default border color + }, + '&:hover fieldset': { + borderColor: 'green', // Border color changes to green on hover + }, + '&.Mui-focused fieldset': { + borderColor: 'green', // Border color when the input is focused + }, + } + }; + const [userEmail, setUserEmail] = useState(''); const [selectedRestaurant, setSelectedRestaurant] = useState(''); const [wouldRecommend, setWouldRecommend] = useState(false); @@ -13,21 +32,14 @@ const RatingFormPage = () => { return (
- + Leave a Review
{/* Email Input */} { {/* Restaurant Selection */} { {/* Food Experience */} { {/* Service Experience */} { {/* Cleanliness Experience */} { setWouldRecommend(e.target.checked)} /> @@ -137,6 +154,7 @@ const RatingFormPage = () => { {/* Additional Comments */} { type="submit" fullWidth style={{ - backgroundColor: '#007bff', + backgroundColor: 'green', color: 'white', padding: '10px 20px', marginTop: '20px', From 60a113f158cb86d2e547a3360384cf055104e591 Mon Sep 17 00:00:00 2001 From: mthani2 Date: Sun, 8 Dec 2024 18:28:47 +0300 Subject: [PATCH 10/42] Adjust navbar pill sizes and styles for enhanced UI consistency --- src/components/Navbar.css | 79 ++++++++++++--------------- src/components/Navbar.tsx | 110 ++++++++++++-------------------------- 2 files changed, 68 insertions(+), 121 deletions(-) diff --git a/src/components/Navbar.css b/src/components/Navbar.css index 3b605ca..a3b57db 100644 --- a/src/components/Navbar.css +++ b/src/components/Navbar.css @@ -1,61 +1,48 @@ .Navbar { - height: var(--navbar-height); - background-color: #1e1e1e; - padding: 14px; - box-sizing: border-box; - border-top: 2px solid #31373e; + height: var(--navbar-height); + background-color: #1e1e1e; + padding: 14px; + box-sizing: border-box; + border-top: 2px solid #31373e; } .Navbar-links { - position: relative; - display: grid; - grid-auto-columns: 1fr; - grid-auto-flow: column; - height: 40px; - margin: 0 auto; - max-width: 500px; + position: relative; + display: grid; + grid-template-columns: repeat(3, 1fr); /* Adjusted for 3 links now */ + grid-auto-flow: column; + height: 40px; + margin: 0 auto; + max-width: 600px; /* Adjusted width to accommodate new link */ } .Navbar-links a { - position: relative; - z-index: 5; - display: flex; - align-items: center; - justify-content: center; - font-family: 'Zilla Slab', sans-serif; - font-weight: 500; - color: white; - font-size: 16px; - text-decoration: none; + position: relative; + z-index: 1; + display: flex; + align-items: center; + justify-content: center; + font-family: 'Zilla Slab', sans-serif; + font-weight: 500; + color: white; + font-size: 16px; + text-decoration: none; } .Navbar-links svg { - width: 24px; - height: 24px; - margin-right: 0.4em; + width: 24px; + height: 24px; + margin-right: 0.4em; } .Navbar-active { - position: absolute; - z-index: 2; - left: 0; - top: 0; - width: 50%; - height: 100%; - background-color: #2b2f33; - border-radius: 999px; - transition: - transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), - box-shadow 0.3s ease-in-out; + position: absolute; + background-color: #2b2f33; + border-radius: 999px; + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); + box-shadow: 0 0 5px #ee6f52, 0 0 10px #ee6f52, 0 0 15px #ee6f52; /* Glowing effect */ + width: calc(215% / 3); /* Adapt width to the number of links */ + height: 100%; } -.Navbar-active_map { - transform: translateX(100%); -} - -.Navbar-active_glow { - box-shadow: - 0 0 5px #ee6f52, - 0 0 10px #ee6f52, - 0 0 15px #ee6f52; -} +/* Active and hover styling managed via NavLink in Navbar.tsx */ diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index c469c11..1fdd1da 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -1,81 +1,41 @@ -import { Link, useLocation } from 'react-router-dom'; +import { NavLink } from 'react-router-dom'; import './Navbar.css'; function Navbar() { - const location = useLocation(); - - return ( - - ); + return ( + + ); } export default Navbar; - From 709bc9f52eb67d1130663e5ef7fc9920bea1f084 Mon Sep 17 00:00:00 2001 From: mthani2 Date: Sun, 8 Dec 2024 19:09:13 +0300 Subject: [PATCH 11/42] added rating form css page --- src/pages/RatingFormPage.css | 45 ++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/pages/RatingFormPage.css diff --git a/src/pages/RatingFormPage.css b/src/pages/RatingFormPage.css new file mode 100644 index 0000000..ebc601a --- /dev/null +++ b/src/pages/RatingFormPage.css @@ -0,0 +1,45 @@ +.RatingFormPage { + max-width: 600px; + margin: 0 auto; + padding: 20px; + color: white; /* Maintaining consistency with the inline style */ + background-color: #1e1e1e; /* Background for the form page */ +} + +.RatingFormPage header { + margin-bottom: 20px; +} + +.RatingFormPage form { + display: flex; + flex-direction: column; + gap: 20px; +} + +.TextField { + background-color: #2D2F32; /* Background color for input fields */ + border-color: grey; /* Default border color */ +} + +.TextField:hover { + border-color: green; /* Border color on hover */ +} + +.TextField.Mui-focused { + border-color: green; /* Border color when input is focused */ +} + +.TextField label.Mui-focused { + color: green; /* Color of the label when input is focused */ +} + +.Checkbox { + color: green; /* Color for the checkbox when active/focused */ +} + +.Button { + background-color: green; /* Background color for the submit button */ + color: white; /* Text color for the submit button */ + padding: 10px 20px; + margin-top: 20px; +} From 4663c8a50d78279b1cc4b668ae36a7404f28f5b2 Mon Sep 17 00:00:00 2001 From: mthani2 Date: Sun, 8 Dec 2024 20:41:06 +0300 Subject: [PATCH 12/42] fixed rating form UI and fixed select restaurant toggle --- src/pages/RatingFormPage.css | 3 ++- src/pages/RatingFormPage.tsx | 22 +++++++++++++++------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/pages/RatingFormPage.css b/src/pages/RatingFormPage.css index ebc601a..190c359 100644 --- a/src/pages/RatingFormPage.css +++ b/src/pages/RatingFormPage.css @@ -3,7 +3,7 @@ margin: 0 auto; padding: 20px; color: white; /* Maintaining consistency with the inline style */ - background-color: #1e1e1e; /* Background for the form page */ + background-color: #2D2F32 /* Background for the form page */ } .RatingFormPage header { @@ -19,6 +19,7 @@ .TextField { background-color: #2D2F32; /* Background color for input fields */ border-color: grey; /* Default border color */ + color: white; /* Setting text color to white for better visibility */ } .TextField:hover { diff --git a/src/pages/RatingFormPage.tsx b/src/pages/RatingFormPage.tsx index 69da917..abb6980 100644 --- a/src/pages/RatingFormPage.tsx +++ b/src/pages/RatingFormPage.tsx @@ -1,10 +1,14 @@ import { useState } from 'react'; import { Typography, Button, TextField, Checkbox, FormControlLabel } from '@mui/material'; +import './RatingFormPage.css'; const RatingFormPage = () => { // Defined textFieldStyles const textFieldStyles = { - backgroundColor: '#2D2F32', // Adjust the background color if needed + backgroundColor: '#2D2F32', // Background color of the input fields + '& label': { + color: 'white', // Set default label color to white + }, '& label.Mui-focused': { color: 'green', // Changes the label color to green when focused }, @@ -18,6 +22,9 @@ const RatingFormPage = () => { '&.Mui-focused fieldset': { borderColor: 'green', // Border color when the input is focused }, + '& .MuiInputBase-input': { + color: 'white', // Changes the text color inside the input + } } }; @@ -30,12 +37,12 @@ const RatingFormPage = () => { const [additionalComments, setAdditionalComments] = useState(''); return ( -
-
- +
+
+ Leave a Review - -
+ +
{/* Email Input */} { required value={selectedRestaurant} onChange={(e) => setSelectedRestaurant(e.target.value)} + placeholder="Select a Restaurant" > - + From 00fe0cfeef6d63dbca26c44d435fd32aec48f8ab Mon Sep 17 00:00:00 2001 From: mthani2 Date: Sun, 8 Dec 2024 20:45:02 +0300 Subject: [PATCH 13/42] added description to review form --- src/pages/RatingFormPage.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pages/RatingFormPage.tsx b/src/pages/RatingFormPage.tsx index abb6980..c37ac3c 100644 --- a/src/pages/RatingFormPage.tsx +++ b/src/pages/RatingFormPage.tsx @@ -42,6 +42,9 @@ const RatingFormPage = () => { Leave a Review + + Got a review? Submit your feedback and it'll be sent right to the restaurant! + {/* Email Input */} From 67876b58dc330ab7bd2b6daf88f9e8e131ff3bad Mon Sep 17 00:00:00 2001 From: Nour Alseaf Date: Sun, 8 Dec 2024 20:54:03 +0300 Subject: [PATCH 14/42] Edited the previously named RatingPage to ReviewPage since we'll be adding a new Rating sysystem page --- src/App.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 274d401..9c2063b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,6 +6,7 @@ import Navbar from './components/Navbar'; import ListPage from './pages/ListPage'; import MapPage from './pages/MapPage'; import NotFoundPage from './pages/NotFoundPage'; +import ReviewFormPage from './pages/ReviewFormPage'; // Import the new page import { queryLocations, getLocationStatus } from './util/queryLocations'; import './App.css'; import { @@ -14,13 +15,13 @@ import { } from './types/locationTypes'; const CMU_EATS_API_URL = 'https://dining.apis.scottylabs.org/locations'; -// const CMU_EATS_API_URL = 'http://localhost:5173/example-response.json'; // for debugging purposes (note that you need an example-response.json file in the /public folder) -// const CMU_EATS_API_URL = 'http://localhost:5010/locations'; // for debugging purposes (note that you need an example-response.json file in the /public folder) + function App() { // Load locations const [locations, setLocations] = useState(); const [extendedLocationData, setExtendedLocationData] = useState(); + useEffect(() => { queryLocations(CMU_EATS_API_URL).then((parsedLocations) => { setLocations(parsedLocations); @@ -31,8 +32,6 @@ function App() { const intervalId = setInterval( (function updateExtendedLocationData() { if (locations !== undefined) { - // Remove .setZone('America/New_York') and change time in computer settings when testing - // Alternatively, simply set now = DateTime.local(2023, 12, 22, 18, 33); where the parameters are Y,M,D,H,M const now = DateTime.now().setZone('America/New_York'); setExtendedLocationData( locations.map((location) => ({ @@ -41,8 +40,8 @@ function App() { })), ); } - return updateExtendedLocationData; // returns itself here - })(), // self-invoking function + return updateExtendedLocationData; + })(), 1 * 1000, // updates every second ); return () => clearInterval(intervalId); @@ -92,6 +91,10 @@ function App() { } /> + } // Add the new route + /> } />
From aa43d3b685b988cfc7c1fa276210dba717ca95f0 Mon Sep 17 00:00:00 2001 From: Nour Alseaf Date: Sun, 8 Dec 2024 20:54:46 +0300 Subject: [PATCH 15/42] Changed the route to better fit the naming changes from Rate to Review --- src/components/Navbar.tsx | 88 ++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 52 deletions(-) diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 996a983..c4df6d0 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -1,57 +1,41 @@ -import { Link, useLocation } from 'react-router-dom'; +import { NavLink } from 'react-router-dom'; import './Navbar.css'; function Navbar() { - const location = useLocation(); - - return ( - - ); + return ( + + ); } -export default Navbar; +export default Navbar; \ No newline at end of file From e0c50be6d93520b2596d74d68e71979db733738a Mon Sep 17 00:00:00 2001 From: Nour Alseaf Date: Sun, 8 Dec 2024 21:49:38 +0300 Subject: [PATCH 16/42] Added the Rate A Restaurant button and icon on the NAV bar --- src/components/Navbar.tsx | 10 +- src/pages/ReviewFormPage.tsx | 185 +++++++++++++++++++++++++++++++++++ 2 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 src/pages/ReviewFormPage.tsx diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index c4df6d0..fb82867 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -1,5 +1,6 @@ import { NavLink } from 'react-router-dom'; import './Navbar.css'; +import StarOutlineIcon from '@mui/icons-material/StarOutline'; // Import the empty star icon function Navbar() { return ( @@ -27,6 +28,13 @@ function Navbar() { isActive ? "nav-link Navbar-active" : "nav-link"}> + {/* Empty Star Icon and text for Rate a Restaurant */} + {/* Empty Star Icon */} + Rate a Restaurant + + isActive ? "nav-link Navbar-active" : "nav-link"}> {/* SVG and text for Leave a Review */} @@ -38,4 +46,4 @@ function Navbar() { ); } -export default Navbar; \ No newline at end of file +export default Navbar; diff --git a/src/pages/ReviewFormPage.tsx b/src/pages/ReviewFormPage.tsx new file mode 100644 index 0000000..9e39525 --- /dev/null +++ b/src/pages/ReviewFormPage.tsx @@ -0,0 +1,185 @@ +import { useState } from 'react'; +import { Typography, Button, TextField, Checkbox, FormControlLabel } from '@mui/material'; + +const RatingFormPage = () => { + // Defined textFieldStyles + const textFieldStyles = { + backgroundColor: '#2D2F32', // Adjust the background color if needed + '& label.Mui-focused': { + color: 'green', // Changes the label color to green when focused + }, + '& .MuiOutlinedInput-root': { + '& fieldset': { + borderColor: 'grey', // Default border color + }, + '&:hover fieldset': { + borderColor: 'green', // Border color changes to green on hover + }, + '&.Mui-focused fieldset': { + borderColor: 'green', // Border color when the input is focused + }, + } + }; + + const [userEmail, setUserEmail] = useState(''); + const [selectedRestaurant, setSelectedRestaurant] = useState(''); + const [wouldRecommend, setWouldRecommend] = useState(false); + const [foodExperience, setFoodExperience] = useState(''); + const [serviceExperience, setServiceExperience] = useState(''); + const [cleanlinessExperience, setCleanlinessExperience] = useState(''); + const [additionalComments, setAdditionalComments] = useState(''); + + return ( +
+
+ + Leave a Review + +
+ + {/* Email Input */} + setUserEmail(e.target.value)} + /> + + {/* Restaurant Selection */} + setSelectedRestaurant(e.target.value)} + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* Food Experience */} + setFoodExperience(e.target.value)} + /> + + {/* Service Experience */} + setServiceExperience(e.target.value)} + /> + + {/* Cleanliness Experience */} + setCleanlinessExperience(e.target.value)} + /> + +{/* Recommend Checkbox */} + setWouldRecommend(e.target.checked)} + /> + } + label="Would you recommend this restaurant to a friend?" + /> + + {/* Additional Comments */} + setAdditionalComments(e.target.value)} + /> + + {/* Submit Button */} + + +
+ ); +}; + +export default RatingFormPage; From e6c9e72ef3df8eb144efc2cfb3119684f3a25e25 Mon Sep 17 00:00:00 2001 From: Nour Alseaf Date: Sun, 8 Dec 2024 21:50:10 +0300 Subject: [PATCH 17/42] Fixed the routing to the Rating page in the App.tsx file --- src/App.tsx | 171 +++++++++++++++++++++++++++------------------------- 1 file changed, 88 insertions(+), 83 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 9c2063b..18097bb 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,103 +6,108 @@ import Navbar from './components/Navbar'; import ListPage from './pages/ListPage'; import MapPage from './pages/MapPage'; import NotFoundPage from './pages/NotFoundPage'; -import ReviewFormPage from './pages/ReviewFormPage'; // Import the new page +import ReviewFormPage from './pages/ReviewFormPage'; // Import the ReviewFormPage +import RatingPage from './pages/RatingFormPage'; // Import the new RatingPage import { queryLocations, getLocationStatus } from './util/queryLocations'; import './App.css'; import { - IReadOnlyExtendedLocation, - IReadOnlyLocation, + IReadOnlyExtendedLocation, + IReadOnlyLocation, } from './types/locationTypes'; const CMU_EATS_API_URL = 'https://dining.apis.scottylabs.org/locations'; function App() { - // Load locations - const [locations, setLocations] = useState(); - const [extendedLocationData, setExtendedLocationData] = - useState(); + // Load locations + const [locations, setLocations] = useState(); + const [extendedLocationData, setExtendedLocationData] = + useState(); - useEffect(() => { - queryLocations(CMU_EATS_API_URL).then((parsedLocations) => { - setLocations(parsedLocations); - }); - }, []); + useEffect(() => { + queryLocations(CMU_EATS_API_URL).then((parsedLocations) => { + setLocations(parsedLocations); + }); + }, []); - useEffect(() => { - const intervalId = setInterval( - (function updateExtendedLocationData() { - if (locations !== undefined) { - const now = DateTime.now().setZone('America/New_York'); - setExtendedLocationData( - locations.map((location) => ({ - ...location, - ...getLocationStatus(location.times, now), // populate location with more detailed info relevant to current time - })), - ); - } - return updateExtendedLocationData; - })(), - 1 * 1000, // updates every second - ); - return () => clearInterval(intervalId); - }, [locations]); + useEffect(() => { + const intervalId = setInterval( + (function updateExtendedLocationData() { + if (locations !== undefined) { + const now = DateTime.now().setZone('America/New_York'); + setExtendedLocationData( + locations.map((location) => ({ + ...location, + ...getLocationStatus(location.times, now), // populate location with more detailed info relevant to current time + })) + ); + } + return updateExtendedLocationData; + })(), + 1 * 1000, // updates every second + ); + return () => clearInterval(intervalId); + }, [locations]); - // Auto-refresh the page when the user goes online after previously being offline - useEffect(() => { - function handleOnline() { - if (navigator.onLine) { - // Refresh the page - window.location.reload(); - } - } + // Auto-refresh the page when the user goes online after previously being offline + useEffect(() => { + function handleOnline() { + if (navigator.onLine) { + // Refresh the page + window.location.reload(); + } + } - window.addEventListener('online', handleOnline); + window.addEventListener('online', handleOnline); - return () => window.removeEventListener('online', handleOnline); - }, []); + return () => window.removeEventListener('online', handleOnline); + }, []); - return ( - - -
-
- Pre-register for{' '} - - TartanHacks - - , Pittsburgh's LARGEST hackathon! 🖥️ -
-
- - - } - /> - - } - /> - } // Add the new route - /> - } /> - -
- -
-
-
- ); + return ( + + +
+
+ Pre-register for{' '} + + TartanHacks + + , Pittsburgh's LARGEST hackathon! 🖥️ +
+
+ + + } + /> + + } + /> + } // Add the new ReviewFormPage route + /> + } // New route for RatingPage + /> + } /> + +
+ +
+
+
+ ); } export default App; From 36a71181a3f696a2c1444723361fb235a377c550 Mon Sep 17 00:00:00 2001 From: Nour Alseaf Date: Sun, 8 Dec 2024 21:52:07 +0300 Subject: [PATCH 18/42] Created a Rating form Page with the rating questions from 1-5 --- src/pages/RatingFormPage.tsx | 329 +++++++++++++++++++++++++++++++++++ 1 file changed, 329 insertions(+) create mode 100644 src/pages/RatingFormPage.tsx diff --git a/src/pages/RatingFormPage.tsx b/src/pages/RatingFormPage.tsx new file mode 100644 index 0000000..a266402 --- /dev/null +++ b/src/pages/RatingFormPage.tsx @@ -0,0 +1,329 @@ +import { useState } from 'react'; +import { Typography, Button, RadioGroup, FormControlLabel, Radio, TextField } from '@mui/material'; + +const RatingPage = () => { + const [userEmail, setUserEmail] = useState(''); // Email state + const [foodRating, setFoodRating] = useState(3); // Food rating state + const [locationRating, setLocationRating] = useState(3); // Location rating state + const [cleanlinessRating, setCleanlinessRating] = useState(3); // Cleanliness rating state + const [serviceRating, setServiceRating] = useState(3); // Service rating state + const [valueForMoneyRating, setValueForMoneyRating] = useState(3); // Value for money rating state + const [menuVarietyRating, setMenuVarietyRating] = useState(3); // Menu variety rating state + const [waitTimeRating, setWaitTimeRating] = useState(3); // Wait time rating state + const [staffRating, setStaffRating] = useState(3); // Staff rating state + const [overallSatisfactionRating, setOverallSatisfactionRating] = useState(3); // Overall satisfaction rating state + const [selectedRestaurant, setSelectedRestaurant] = useState(''); // Restaurant selection state + + // Define textFieldStyles for styling + const textFieldStyles = { + backgroundColor: '#2D2F32', + '& label.Mui-focused': { + color: 'green', + }, + '& .MuiOutlinedInput-root': { + '& fieldset': { + borderColor: 'grey', + }, + '&:hover fieldset': { + borderColor: 'green', + }, + '&.Mui-focused fieldset': { + borderColor: 'green', + }, + }, + }; + + return ( +
+
+ + Rate Our Services + + + Thank you for dining with us! We appreciate the time you’ve taken to share your feedback with us. Your thoughts help us continue to improve and provide you with the best possible experience. + +
+ +
+ {/* Email Input */} + setUserEmail(e.target.value)} + /> + + {/* Restaurant Selection */} + setSelectedRestaurant(e.target.value)} + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* Food Rating */} +
+ + How would you rate the food? (1 = Poor, 5 = Excellent) + + setFoodRating(parseInt(e.target.value))} + row + > + {[1, 2, 3, 4, 5].map((value) => ( + } + label={value} + labelPlacement="bottom" + /> + ))} + +
+ + {/* Location Rating */} +
+ + How would you rate the location? (1 = Poor, 5 = Excellent) + + setLocationRating(parseInt(e.target.value))} + row + > + {[1, 2, 3, 4, 5].map((value) => ( + } + label={value} + labelPlacement="bottom" + /> + ))} + +
+ + + {/* Cleanliness Rating */} +
+ + How would you rate the cleanliness? (1 = Poor, 5 = Excellent) + + setCleanlinessRating(parseInt(e.target.value))} + row + > + {[1, 2, 3, 4, 5].map((value) => ( + } + label={value} + labelPlacement="bottom" + /> + ))} + +
+ + {/* Service Rating */} +
+ + How would you rate the service? (1 = Poor, 5 = Excellent) + + setServiceRating(parseInt(e.target.value))} + row + > + {[1, 2, 3, 4, 5].map((value) => ( + } + label={value} + labelPlacement="bottom" + /> + ))} + +
+ + {/* Value for Money */} +
+ + How would you rate the value for money of your meal? (1 = Poor, 5 = Excellent) + + setValueForMoneyRating(parseInt(e.target.value))} + row + > + {[1, 2, 3, 4, 5].map((value) => ( + } + label={value} + labelPlacement="bottom" + /> + ))} + +
+ + {/* Menu Variety Rating */} +
+ + How satisfied were you with the variety of options on the menu? (1 = Poor, 5 = Excellent) + + setMenuVarietyRating(parseInt(e.target.value))} + row + > + {[1, 2, 3, 4, 5].map((value) => ( + } + label={value} + labelPlacement="bottom" + /> + ))} + +
+ + {/* Wait Time Rating */} +
+ + How would you rate the wait time for your food? (1 = Too long, 5 = Perfect) + + setWaitTimeRating(parseInt(e.target.value))} + row + > + {[1, 2, 3, 4, 5].map((value) => ( + } + label={value} + labelPlacement="bottom" + /> + ))} + +
+ + {/* Staff Rating */} +
+ + How friendly and welcoming was the staff? (1 = Not friendly, 5 = Very friendly) + + setStaffRating(parseInt(e.target.value))} + row + > + {[1, 2, 3, 4, 5].map((value) => ( + } + label={value} + labelPlacement="bottom" + /> + ))} + +
+ + {/* Overall Satisfaction Rating */} +
+ + Overall, how satisfied were you with your experience? (1 = Very dissatisfied, 5 = Very satisfied) + + setOverallSatisfactionRating(parseInt(e.target.value))} + row + > + {[1, 2, 3, 4, 5].map((value) => ( + } + label={value} + labelPlacement="bottom" + /> + ))} + +
+ + {/* Submit Button */} + + +
+ ); +}; + +export default RatingPage; From 775eae5a608fcef05b39b4b13fa4f60c62be913b Mon Sep 17 00:00:00 2001 From: Nour Alseaf Date: Sun, 8 Dec 2024 21:58:42 +0300 Subject: [PATCH 19/42] Changed the colors of the text fields for better visibility --- src/pages/RatingFormPage.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/RatingFormPage.tsx b/src/pages/RatingFormPage.tsx index a266402..16e2297 100644 --- a/src/pages/RatingFormPage.tsx +++ b/src/pages/RatingFormPage.tsx @@ -22,7 +22,7 @@ const RatingPage = () => { }, '& .MuiOutlinedInput-root': { '& fieldset': { - borderColor: 'grey', + borderColor: 'white', }, '&:hover fieldset': { borderColor: 'green', @@ -61,7 +61,6 @@ const RatingPage = () => { Date: Sun, 8 Dec 2024 23:00:37 +0300 Subject: [PATCH 20/42] modifying React Frontend to receive ratings on backend --- src/pages/RatingFormPage.tsx | 62 ++++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/src/pages/RatingFormPage.tsx b/src/pages/RatingFormPage.tsx index 16e2297..acfe269 100644 --- a/src/pages/RatingFormPage.tsx +++ b/src/pages/RatingFormPage.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import React, { useState } from 'react'; import { Typography, Button, RadioGroup, FormControlLabel, Radio, TextField } from '@mui/material'; const RatingPage = () => { @@ -33,6 +33,40 @@ const RatingPage = () => { }, }; + // Define the handleSubmit function + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + const ratings = { + userEmail, + foodRating, + locationRating, + cleanlinessRating, + serviceRating, + valueForMoneyRating, + menuVarietyRating, + waitTimeRating, + staffRating, + overallSatisfactionRating, + restaurantId: selectedRestaurant, // assuming each restaurant has a unique ID + }; + + try { + const response = await fetch('/api/ratings', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(ratings), + }); + + if (response.ok) { + console.log("Rating submitted successfully"); + } else { + console.error("Failed to submit rating"); + } + } catch (error) { + console.error("Error submitting rating: ", error); + } + }; + return (
@@ -44,7 +78,7 @@ const RatingPage = () => {
-
+ {/* Email Input */} { value={userEmail} onChange={(e) => setUserEmail(e.target.value)} /> - - {/* Restaurant Selection */} - setSelectedRestaurant(e.target.value)} - > + + {/* Restaurant Selection */} + setSelectedRestaurant(e.target.value)} + > From 479063cc0db02f03806dbb617fb892458391a349 Mon Sep 17 00:00:00 2001 From: mthani2 Date: Sun, 8 Dec 2024 23:05:03 +0300 Subject: [PATCH 21/42] adding a Ratings.js model file --- src/models/Rating.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/models/Rating.js diff --git a/src/models/Rating.js b/src/models/Rating.js new file mode 100644 index 0000000..14f94a0 --- /dev/null +++ b/src/models/Rating.js @@ -0,0 +1,19 @@ +const mongoose = require('mongoose'); + +const ratingSchema = new mongoose.Schema({ + restaurantId: { type: String, required: true }, + userEmail: { type: String, required: true }, + foodRating: Number, + locationRating: Number, + cleanlinessRating: Number, + serviceRating: Number, + valueForMoneyRating: Number, + menuVarietyRating: Number, + waitTimeRating: Number, + staffRating: Number, + overallSatisfactionRating: Number, +}); + +const Rating = mongoose.model('Rating', ratingSchema); + +module.exports = Rating; From 398f27883f2407c1d4cca087233d99f72fdb5331 Mon Sep 17 00:00:00 2001 From: mthani2 Date: Sun, 8 Dec 2024 23:08:26 +0300 Subject: [PATCH 22/42] adding a routes file for the ratings --- src/models/Rating.js | 19 ------------------- src/services/ratingRoutes.js | 26 ++++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 19 deletions(-) delete mode 100644 src/models/Rating.js create mode 100644 src/services/ratingRoutes.js diff --git a/src/models/Rating.js b/src/models/Rating.js deleted file mode 100644 index 14f94a0..0000000 --- a/src/models/Rating.js +++ /dev/null @@ -1,19 +0,0 @@ -const mongoose = require('mongoose'); - -const ratingSchema = new mongoose.Schema({ - restaurantId: { type: String, required: true }, - userEmail: { type: String, required: true }, - foodRating: Number, - locationRating: Number, - cleanlinessRating: Number, - serviceRating: Number, - valueForMoneyRating: Number, - menuVarietyRating: Number, - waitTimeRating: Number, - staffRating: Number, - overallSatisfactionRating: Number, -}); - -const Rating = mongoose.model('Rating', ratingSchema); - -module.exports = Rating; diff --git a/src/services/ratingRoutes.js b/src/services/ratingRoutes.js new file mode 100644 index 0000000..d627118 --- /dev/null +++ b/src/services/ratingRoutes.js @@ -0,0 +1,26 @@ +const express = require('express'); +const router = express.Router(); +const Rating = require('./RatingModel'); // Adjust path as necessary + +// Post a new rating +router.post('/ratings', async (req, res) => { + try { + const newRating = new Rating(req.body); + const savedRating = await newRating.save(); + res.status(201).send(savedRating); + } catch (error) { + res.status(400).send(error); + } +}); + +// Get all ratings for a restaurant +router.get('/ratings/:restaurantId', async (req, res) => { + try { + const ratings = await Rating.find({ restaurantId: req.params.restaurantId }); + res.status(200).send(ratings); + } catch (error) { + res.status(500).send(error); + } +}); + +module.exports = router; From b41623c486332e990be385fc7540081d48203017 Mon Sep 17 00:00:00 2001 From: mthani2 Date: Sun, 8 Dec 2024 23:09:20 +0300 Subject: [PATCH 23/42] adding the rating model to the services folder --- src/services/RatingModel.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/services/RatingModel.js diff --git a/src/services/RatingModel.js b/src/services/RatingModel.js new file mode 100644 index 0000000..14f94a0 --- /dev/null +++ b/src/services/RatingModel.js @@ -0,0 +1,19 @@ +const mongoose = require('mongoose'); + +const ratingSchema = new mongoose.Schema({ + restaurantId: { type: String, required: true }, + userEmail: { type: String, required: true }, + foodRating: Number, + locationRating: Number, + cleanlinessRating: Number, + serviceRating: Number, + valueForMoneyRating: Number, + menuVarietyRating: Number, + waitTimeRating: Number, + staffRating: Number, + overallSatisfactionRating: Number, +}); + +const Rating = mongoose.model('Rating', ratingSchema); + +module.exports = Rating; From 2af48a9e84ab8dfeb44cd84015c55dc5b5cf7a28 Mon Sep 17 00:00:00 2001 From: mthani2 Date: Sun, 8 Dec 2024 23:11:20 +0300 Subject: [PATCH 24/42] adding rating average calculations to ratingModel --- src/services/RatingModel.js | 61 ++++++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/src/services/RatingModel.js b/src/services/RatingModel.js index 14f94a0..02b9b44 100644 --- a/src/services/RatingModel.js +++ b/src/services/RatingModel.js @@ -1,19 +1,44 @@ -const mongoose = require('mongoose'); +// services/ratingModel.ts -const ratingSchema = new mongoose.Schema({ - restaurantId: { type: String, required: true }, - userEmail: { type: String, required: true }, - foodRating: Number, - locationRating: Number, - cleanlinessRating: Number, - serviceRating: Number, - valueForMoneyRating: Number, - menuVarietyRating: Number, - waitTimeRating: Number, - staffRating: Number, - overallSatisfactionRating: Number, -}); - -const Rating = mongoose.model('Rating', ratingSchema); - -module.exports = Rating; +interface Rating { + userEmail: string; + foodRating: number; + locationRating: number; + cleanlinessRating: number; + serviceRating: number; + valueForMoneyRating: number; + menuVarietyRating: number; + waitTimeRating: number; + staffRating: number; + overallSatisfactionRating: number; + restaurantId: string; + } + + const ratingsDb: Rating[] = []; + + export const RatingModel = { + async create(ratingData: Rating): Promise { + ratingsDb.push(ratingData); + return ratingData; + }, + + async getRatingsByRestaurant(restaurantId: string): Promise { + return ratingsDb.filter(rating => rating.restaurantId === restaurantId); + }, + + async getAverageRating(restaurantId: string): Promise { + const ratings = await this.getRatingsByRestaurant(restaurantId); + const totalRatings = ratings.length; + + if (totalRatings === 0) return 0; + + const totalScore = ratings.reduce((acc, rating) => { + return acc + rating.foodRating + rating.locationRating + rating.cleanlinessRating + + rating.serviceRating + rating.valueForMoneyRating + rating.menuVarietyRating + + rating.waitTimeRating + rating.staffRating + rating.overallSatisfactionRating; + }, 0); + + return totalScore / (totalRatings * 9); // Divide by 9 for each rating type + } + }; + \ No newline at end of file From 0ebd05df053cf6fcba3d18548a503f721f3cb319 Mon Sep 17 00:00:00 2001 From: mthani2 Date: Sun, 8 Dec 2024 23:51:51 +0300 Subject: [PATCH 25/42] Add initial implementation for average restaurant ratings feature - Added backend functionality to calculate and fetch average ratings - Integrated frontend logic to display average ratings on eatery cards - Encountered some errors related to module imports and TypeScript definitions --- src/components/EateryCard.tsx | 33 +++++++++++++++++++++++-- src/pages/RatingFormPage.tsx | 6 ++--- src/services/RatingModel.js | 45 +++++++++++++++++++---------------- src/services/ratingModel.d.ts | 14 +++++++++++ src/services/ratingRoutes.js | 26 -------------------- src/services/ratingRoutes.ts | 21 ++++++++++++++++ src/util/server.ts | 17 +++++++++++++ 7 files changed, 110 insertions(+), 52 deletions(-) create mode 100644 src/services/ratingModel.d.ts delete mode 100644 src/services/ratingRoutes.js create mode 100644 src/services/ratingRoutes.ts create mode 100644 src/util/server.ts diff --git a/src/components/EateryCard.tsx b/src/components/EateryCard.tsx index 5c570ae..7a5afd7 100644 --- a/src/components/EateryCard.tsx +++ b/src/components/EateryCard.tsx @@ -1,4 +1,5 @@ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; + import { Card, CardHeader, @@ -164,6 +165,7 @@ const SpecialsContent = styled(Accordion)({ backgroundColor: '#23272A', }); + function EateryCard({ location }: { location: IReadOnlyExtendedLocation }) { const { name, @@ -177,9 +179,33 @@ function EateryCard({ location }: { location: IReadOnlyExtendedLocation }) { } = location; const changesSoon = !location.closedLongTerm && location.changesSoon; const isOpen = !location.closedLongTerm && location.isOpen; - const [modalOpen, setModalOpen] = useState(false); + + const [averageRatings, setAverageRatings] = useState<{ + overall: number; + }>({ overall: 0 }); // Default values + + useEffect(() => { + const fetchAverageRating = async () => { + try { + const response = await fetch(`/api/ratings/average/${name}`); + if (response.ok) { + const data = await response.json(); + setAverageRatings(data.averageRating || { overall: 0 }); + } else { + console.warn(`No ratings available for ${name}`); + } + } catch (error) { + console.error('Error fetching average ratings:', error); + } + }; + + fetchAverageRating(); + }, [name]); + + + return ( <> @@ -227,6 +253,9 @@ function EateryCard({ location }: { location: IReadOnlyExtendedLocation }) { {locationText} {shortDescription} + + Average Rating: {averageRatings.overall ? averageRatings.overall.toFixed(1) : 'Not Rated'} + {menu && ( diff --git a/src/pages/RatingFormPage.tsx b/src/pages/RatingFormPage.tsx index acfe269..33bf759 100644 --- a/src/pages/RatingFormPage.tsx +++ b/src/pages/RatingFormPage.tsx @@ -38,6 +38,7 @@ const RatingPage = () => { event.preventDefault(); const ratings = { userEmail, + restaurantName: selectedRestaurant, // Use name instead of ID foodRating, locationRating, cleanlinessRating, @@ -47,16 +48,15 @@ const RatingPage = () => { waitTimeRating, staffRating, overallSatisfactionRating, - restaurantId: selectedRestaurant, // assuming each restaurant has a unique ID }; - + try { const response = await fetch('/api/ratings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(ratings), }); - + if (response.ok) { console.log("Rating submitted successfully"); } else { diff --git a/src/services/RatingModel.js b/src/services/RatingModel.js index 02b9b44..c9e6e1c 100644 --- a/src/services/RatingModel.js +++ b/src/services/RatingModel.js @@ -1,7 +1,6 @@ -// services/ratingModel.ts - -interface Rating { +type Rating = { userEmail: string; + restaurantName: string; // Use name instead of ID foodRating: number; locationRating: number; cleanlinessRating: number; @@ -11,34 +10,38 @@ interface Rating { waitTimeRating: number; staffRating: number; overallSatisfactionRating: number; - restaurantId: string; - } + }; - const ratingsDb: Rating[] = []; + const ratingsDb: Rating[] = []; // Simulated in-memory database export const RatingModel = { - async create(ratingData: Rating): Promise { + create: async (ratingData: Rating) => { ratingsDb.push(ratingData); return ratingData; }, - async getRatingsByRestaurant(restaurantId: string): Promise { - return ratingsDb.filter(rating => rating.restaurantId === restaurantId); + getRatingsByRestaurant: async (restaurantName: string) => { + return ratingsDb.filter((rating) => rating.restaurantName === restaurantName); }, - async getAverageRating(restaurantId: string): Promise { - const ratings = await this.getRatingsByRestaurant(restaurantId); - const totalRatings = ratings.length; - - if (totalRatings === 0) return 0; + getAverageRating: async (restaurantName: string) => { + const restaurantRatings = ratingsDb.filter((rating) => rating.restaurantName === restaurantName); + if (restaurantRatings.length === 0) return null; - const totalScore = ratings.reduce((acc, rating) => { - return acc + rating.foodRating + rating.locationRating + rating.cleanlinessRating + - rating.serviceRating + rating.valueForMoneyRating + rating.menuVarietyRating + - rating.waitTimeRating + rating.staffRating + rating.overallSatisfactionRating; - }, 0); + const totalRatings = restaurantRatings.length; + const averageRating = { + foodRating: restaurantRatings.reduce((sum, rating) => sum + rating.foodRating, 0) / totalRatings, + locationRating: restaurantRatings.reduce((sum, rating) => sum + rating.locationRating, 0) / totalRatings, + cleanlinessRating: restaurantRatings.reduce((sum, rating) => sum + rating.cleanlinessRating, 0) / totalRatings, + serviceRating: restaurantRatings.reduce((sum, rating) => sum + rating.serviceRating, 0) / totalRatings, + valueForMoneyRating: restaurantRatings.reduce((sum, rating) => sum + rating.valueForMoneyRating, 0) / totalRatings, + menuVarietyRating: restaurantRatings.reduce((sum, rating) => sum + rating.menuVarietyRating, 0) / totalRatings, + waitTimeRating: restaurantRatings.reduce((sum, rating) => sum + rating.waitTimeRating, 0) / totalRatings, + staffRating: restaurantRatings.reduce((sum, rating) => sum + rating.staffRating, 0) / totalRatings, + overallSatisfactionRating: restaurantRatings.reduce((sum, rating) => sum + rating.overallSatisfactionRating, 0) / totalRatings, + }; - return totalScore / (totalRatings * 9); // Divide by 9 for each rating type - } + return averageRating; + }, }; \ No newline at end of file diff --git a/src/services/ratingModel.d.ts b/src/services/ratingModel.d.ts new file mode 100644 index 0000000..c465c70 --- /dev/null +++ b/src/services/ratingModel.d.ts @@ -0,0 +1,14 @@ +declare module './RatingModel.js' { + type Rating = { + restaurant: string; + rating: number; + comment?: string; + }; + + export const RatingModel: { + create: (rating: Rating) => Rating; + getRatingsByRestaurant: (restaurantName: string) => Rating[]; + getAverageRating: (restaurantName: string) => number | null; + }; + } + \ No newline at end of file diff --git a/src/services/ratingRoutes.js b/src/services/ratingRoutes.js deleted file mode 100644 index d627118..0000000 --- a/src/services/ratingRoutes.js +++ /dev/null @@ -1,26 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const Rating = require('./RatingModel'); // Adjust path as necessary - -// Post a new rating -router.post('/ratings', async (req, res) => { - try { - const newRating = new Rating(req.body); - const savedRating = await newRating.save(); - res.status(201).send(savedRating); - } catch (error) { - res.status(400).send(error); - } -}); - -// Get all ratings for a restaurant -router.get('/ratings/:restaurantId', async (req, res) => { - try { - const ratings = await Rating.find({ restaurantId: req.params.restaurantId }); - res.status(200).send(ratings); - } catch (error) { - res.status(500).send(error); - } -}); - -module.exports = router; diff --git a/src/services/ratingRoutes.ts b/src/services/ratingRoutes.ts new file mode 100644 index 0000000..5295e39 --- /dev/null +++ b/src/services/ratingRoutes.ts @@ -0,0 +1,21 @@ +import { RatingModel } from './RatingModel.js'; + +export const ratingRoutes = (app: any) => { + app.post('/api/ratings', async (req: any, res: any) => { + const ratingData = await req.json(); + const newRating = await RatingModel.create(ratingData); + return res.json(newRating); + }); + + app.get('/api/ratings/:restaurantName', async (req: any, res: any) => { + const restaurantName = req.params.restaurantName; // Use name instead of ID + const ratings = await RatingModel.getRatingsByRestaurant(restaurantName); + return res.json(ratings); + }); + + app.get('/api/ratings/average/:restaurantName', async (req: any, res: any) => { + const restaurantName = req.params.restaurantName; // Use name instead of ID + const averageRating = await RatingModel.getAverageRating(restaurantName); + return res.json({ averageRating }); + }); +}; diff --git a/src/util/server.ts b/src/util/server.ts new file mode 100644 index 0000000..28f72c5 --- /dev/null +++ b/src/util/server.ts @@ -0,0 +1,17 @@ +import { RatingModel } from './services/RatingModel.js'; +import { ratingRoutes } from './services/ratingRoutes.ts'; + +const server = Bun.serve({ + port: 3000, + fetch(req) { + // Handle routes using ratingRoutes + const url = new URL(req.url); + if (url.pathname.startsWith('/api/ratings')) { + return ratingRoutes(req); + } + return new Response('Not Found', { status: 404 }); + }, +}); + +console.log(`Server running on http://localhost:${server.port}`); + From b6b1f276dccfc6efaf6eef3f3382211ea73bc0e5 Mon Sep 17 00:00:00 2001 From: seif khelifi Date: Mon, 9 Dec 2024 13:52:07 +0300 Subject: [PATCH 26/42] Test: Added unit tests for RatingModel to validate create, fetch, and average rating functionalities --- tests/ratingModel.test.ts | 29 +++++++++++++++++++++++++++++ tests/ratingRoutes.test.ts | 0 2 files changed, 29 insertions(+) create mode 100644 tests/ratingModel.test.ts create mode 100644 tests/ratingRoutes.test.ts diff --git a/tests/ratingModel.test.ts b/tests/ratingModel.test.ts new file mode 100644 index 0000000..4616a9a --- /dev/null +++ b/tests/ratingModel.test.ts @@ -0,0 +1,29 @@ +import { describe, it, expect } from 'vitest'; +import { RatingModel } from '../src/services/ratingModel'; + +describe('RatingModel Tests', () => { + it('should create a new rating', async () => { + const newRating = await RatingModel.create({ + restaurant: 'Test Restaurant', + rating: 4, + comment: 'Great!', + }); + expect(newRating).toMatchObject({ + restaurant: 'Test Restaurant', + rating: 4, + comment: 'Great!', + }); + }); + + it('should fetch ratings by restaurant name', async () => { + const ratings = await RatingModel.getRatingsByRestaurant('Test Restaurant'); + expect(ratings).toBeInstanceOf(Array); + expect(ratings.length).toBeGreaterThan(0); + }); + + it('should calculate the average rating for a restaurant', async () => { + const average = await RatingModel.getAverageRating('Test Restaurant'); + expect(average).toBeGreaterThanOrEqual(1); + expect(average).toBeLessThanOrEqual(5); + }); +}); diff --git a/tests/ratingRoutes.test.ts b/tests/ratingRoutes.test.ts new file mode 100644 index 0000000..e69de29 From 57c63f13242f91a9e1582028f41934a624f0214f Mon Sep 17 00:00:00 2001 From: seif khelifi Date: Mon, 9 Dec 2024 14:16:25 +0300 Subject: [PATCH 27/42] Add: Created two test files - ratingModel.test.ts and ratingRoutes.test.ts --- tests/ratingModel.test.ts | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/tests/ratingModel.test.ts b/tests/ratingModel.test.ts index 4616a9a..e69de29 100644 --- a/tests/ratingModel.test.ts +++ b/tests/ratingModel.test.ts @@ -1,29 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { RatingModel } from '../src/services/ratingModel'; - -describe('RatingModel Tests', () => { - it('should create a new rating', async () => { - const newRating = await RatingModel.create({ - restaurant: 'Test Restaurant', - rating: 4, - comment: 'Great!', - }); - expect(newRating).toMatchObject({ - restaurant: 'Test Restaurant', - rating: 4, - comment: 'Great!', - }); - }); - - it('should fetch ratings by restaurant name', async () => { - const ratings = await RatingModel.getRatingsByRestaurant('Test Restaurant'); - expect(ratings).toBeInstanceOf(Array); - expect(ratings.length).toBeGreaterThan(0); - }); - - it('should calculate the average rating for a restaurant', async () => { - const average = await RatingModel.getAverageRating('Test Restaurant'); - expect(average).toBeGreaterThanOrEqual(1); - expect(average).toBeLessThanOrEqual(5); - }); -}); From d8bf23a4928e1e0074b2497f3799aaa146100490 Mon Sep 17 00:00:00 2001 From: seif khelifi Date: Mon, 9 Dec 2024 14:27:04 +0300 Subject: [PATCH 28/42] Test: Added integration tests for Rating Routes to validate POST, GET, and average rating calculations --- tests/ratingRoutes.test.ts | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/ratingRoutes.test.ts b/tests/ratingRoutes.test.ts index e69de29..f898ca1 100644 --- a/tests/ratingRoutes.test.ts +++ b/tests/ratingRoutes.test.ts @@ -0,0 +1,33 @@ +import { describe, it, expect } from 'vitest'; +import request from 'supertest'; +import app from '../src/app'; // Replace with the correct path to your Express app + +describe('Rating Routes Tests', () => { + it('should create a new rating via POST /api/ratings', async () => { + const response = await request(app).post('/api/ratings').send({ + restaurant: 'Test Restaurant', + rating: 5, + comment: 'Excellent!', + }); + expect(response.status).toBe(201); + expect(response.body).toMatchObject({ + restaurant: 'Test Restaurant', + rating: 5, + comment: 'Excellent!', + }); + }); + + it('should fetch ratings via GET /api/ratings/:restaurantName', async () => { + const response = await request(app).get('/api/ratings/Test Restaurant'); + expect(response.status).toBe(200); + expect(response.body).toBeInstanceOf(Array); + }); + + it('should calculate the average rating via GET /api/ratings/average/:restaurantName', async () => { + const response = await request(app).get('/api/ratings/average/Test Restaurant'); + expect(response.status).toBe(200); + expect(response.body).toHaveProperty('averageRating'); + expect(response.body.averageRating).toBeGreaterThanOrEqual(1); + expect(response.body.averageRating).toBeLessThanOrEqual(5); + }); +}); From e802b9d501699017f468f6a5cdb48cb6a1d4cb20 Mon Sep 17 00:00:00 2001 From: seif khelifi Date: Mon, 9 Dec 2024 14:31:25 +0300 Subject: [PATCH 29/42] Test: Added server error handling test for Rating Routes --- tests/ratingModel.test.ts | 29 +++++++++++++++++++++++++++++ tests/ratingRoutes.test.ts | 10 ++++++++++ 2 files changed, 39 insertions(+) diff --git a/tests/ratingModel.test.ts b/tests/ratingModel.test.ts index e69de29..4616a9a 100644 --- a/tests/ratingModel.test.ts +++ b/tests/ratingModel.test.ts @@ -0,0 +1,29 @@ +import { describe, it, expect } from 'vitest'; +import { RatingModel } from '../src/services/ratingModel'; + +describe('RatingModel Tests', () => { + it('should create a new rating', async () => { + const newRating = await RatingModel.create({ + restaurant: 'Test Restaurant', + rating: 4, + comment: 'Great!', + }); + expect(newRating).toMatchObject({ + restaurant: 'Test Restaurant', + rating: 4, + comment: 'Great!', + }); + }); + + it('should fetch ratings by restaurant name', async () => { + const ratings = await RatingModel.getRatingsByRestaurant('Test Restaurant'); + expect(ratings).toBeInstanceOf(Array); + expect(ratings.length).toBeGreaterThan(0); + }); + + it('should calculate the average rating for a restaurant', async () => { + const average = await RatingModel.getAverageRating('Test Restaurant'); + expect(average).toBeGreaterThanOrEqual(1); + expect(average).toBeLessThanOrEqual(5); + }); +}); diff --git a/tests/ratingRoutes.test.ts b/tests/ratingRoutes.test.ts index f898ca1..2ba95ab 100644 --- a/tests/ratingRoutes.test.ts +++ b/tests/ratingRoutes.test.ts @@ -30,4 +30,14 @@ describe('Rating Routes Tests', () => { expect(response.body.averageRating).toBeGreaterThanOrEqual(1); expect(response.body.averageRating).toBeLessThanOrEqual(5); }); + it('should handle server errors gracefully', async () => { + // Simulate a database error + vi.spyOn(RatingModel, 'getRatingsByRestaurant').mockImplementation(() => { + throw new Error('Database error'); + }); + + const response = await request(app).get('/api/ratings/Test Restaurant'); + expect(response.status).toBe(500); + expect(response.body).toHaveProperty('error', 'Failed to fetch ratings'); + }); }); From 1610b00ebd7217cb000a411c03839b8f4200dfeb Mon Sep 17 00:00:00 2001 From: seif khelifi Date: Mon, 9 Dec 2024 14:36:42 +0300 Subject: [PATCH 30/42] Test: Implemented mocking for RatingModel methods to simulate database interactions in unit tests --- tests/ratingModel.test.ts | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/tests/ratingModel.test.ts b/tests/ratingModel.test.ts index 4616a9a..5c5eaac 100644 --- a/tests/ratingModel.test.ts +++ b/tests/ratingModel.test.ts @@ -1,4 +1,14 @@ -import { describe, it, expect } from 'vitest'; +import { describe, it, expect, vi } from 'vitest'; + +// Mock the RatingModel +vi.mock('../src/services/ratingModel', () => ({ + RatingModel: { + create: vi.fn().mockResolvedValue({ restaurant: 'Mock Restaurant', rating: 5, comment: 'Mock Comment' }), + getRatingsByRestaurant: vi.fn().mockResolvedValue([{ restaurant: 'Mock Restaurant', rating: 5 }]), + getAverageRating: vi.fn().mockResolvedValue(4.5), + }, +})); + import { RatingModel } from '../src/services/ratingModel'; describe('RatingModel Tests', () => { @@ -9,9 +19,9 @@ describe('RatingModel Tests', () => { comment: 'Great!', }); expect(newRating).toMatchObject({ - restaurant: 'Test Restaurant', - rating: 4, - comment: 'Great!', + restaurant: 'Mock Restaurant', + rating: 5, + comment: 'Mock Comment', }); }); @@ -19,11 +29,11 @@ describe('RatingModel Tests', () => { const ratings = await RatingModel.getRatingsByRestaurant('Test Restaurant'); expect(ratings).toBeInstanceOf(Array); expect(ratings.length).toBeGreaterThan(0); + expect(ratings[0]).toMatchObject({ restaurant: 'Mock Restaurant', rating: 5 }); }); it('should calculate the average rating for a restaurant', async () => { const average = await RatingModel.getAverageRating('Test Restaurant'); - expect(average).toBeGreaterThanOrEqual(1); - expect(average).toBeLessThanOrEqual(5); + expect(average).toBe(4.5); }); }); From a488096cb983a1cb89c8706300d2eae0ce3131d3 Mon Sep 17 00:00:00 2001 From: Filippos Date: Mon, 9 Dec 2024 23:26:52 +0300 Subject: [PATCH 31/42] Created the backend directory which was populated with a controllers folder, in which I created feedbackController.js. Implemented core functions for managing feedback, including create, read, update, and delete operations. Structured the controller to integrate with the feedback model and routes.Added basic validation and error handling to ensure robust feedback processing. --- backend/controllers/feedbackController.js | 93 +++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 backend/controllers/feedbackController.js diff --git a/backend/controllers/feedbackController.js b/backend/controllers/feedbackController.js new file mode 100644 index 0000000..f591f1a --- /dev/null +++ b/backend/controllers/feedbackController.js @@ -0,0 +1,93 @@ +import transporter from '../util/emailTransporter.js'; + +const restaurantEmails = { + "stephanies@example.com": "Stephanie's - Market c", + "scottys@example.com": "Scotty's Market By Salem's", + "aubonpain@example.com": "Au Bon Pain At Skibo Café", + "rohrcafe@example.com": "Rohr Café - La Prima", + "schatz@example.com": "Schatz Dining Room", + "defercafe@example.com": "De Fer Coffee & Tea At Maggie Murph Café", + "exchange@example.com": "The Exchange", + "capitalgrains@example.com": "Capital Grains - Rohr Commons", + "milliescreamery@example.com": "Millie's Coffee 'n' Creamery - Rohr Commons", + "stackddessert@example.com": "Stack'd Dessert Bar", + "stackdunderground@example.com": "Stack'd Underground", + "entropy@example.com": "Entropy+", + "elgallo@example.com": "El Gallo De Oro", + "revolutionnoodle@example.com": "Revolution Noodle", + "tasteofindia@example.com": "Taste Of India", + "trueburger@example.com": "True Burger", + "hunanexpress@example.com": "Hunan Express", + "edgecafe@example.com": "The Edge Cafe & Market", + "tahini@example.com": "Tahini", + "eggshoppe@example.com": "Egg Shoppe - Grubhub Only", + "laprimaespresso@example.com": "La Prima Espresso", + "redhawkcoffee@example.com": "Redhawk Coffee", + "zebralounge@example.com": "Zebra Lounge", + "ciaobella@example.com": "Ciao Bella", + "crispandcrust@example.com": "Crisp And Crust", + "nourish@example.com": "Nourish", + "buildpizza@example.com": "Build Pizza - Rohr Commons", + "forbesavesubs@example.com": "Forbes Avenue Subs - Rohr Commons", + "rohrcommons@example.com": "Rohr Commons - Tepper Eatery", + "teppertaqueria@example.com": "Tepper Taqueria", + "teppertaqueriaexp@example.com": "Tepper Taqueria Express", + "wildbluesushi@example.com": "Wild Blue Sushi - Ruge Atrium", + "shakesmart@example.com": "Shake Smart", + "eatevenings@example.com": "E.a.t. (evenings At Tepper) - Rohr Commons", + "urbanrevolution@example.com": "Urban Revolution - Grubhub Only", + "testconcept@example.com": "Test Concept", + "tartantruck@example.com": "Tartan Food Truck - Mr. Bulgogi" +}; + +export const sendFeedbackEmail = async (req, res) => { + const { + userEmail, + selectedRestaurant, + wouldRecommend, + foodExperience, + serviceExperience, + cleanlinessExperience, + additionalComments, + } = req.body; + + // Validate request data + if (!userEmail || !selectedRestaurant || !foodExperience || !serviceExperience || !cleanlinessExperience) { + return res.status(400).send({ message: 'All required fields must be filled.' }); + } + + try { + // Get the restaurant name and email + const restaurantName = restaurantEmails[selectedRestaurant]; + if (!restaurantName) { + return res.status(404).send({ message: 'Invalid restaurant selected.' }); + } + + // Construct the email subject and body + const subject = `New Feedback for ${restaurantName}`; + const body = ` + Feedback received for ${restaurantName}: + + - User Email: ${userEmail} + - Food Experience: ${foodExperience} + - Service Experience: ${serviceExperience} + - Cleanliness Experience: ${cleanlinessExperience} + - Would Recommend: ${wouldRecommend ? 'Yes' : 'No'} + - Additional Comments: ${additionalComments} + `; + + // Send the email + await transporter.sendMail({ + from: userEmail, + to: selectedRestaurant, // Email of the selected restaurant + subject: subject, + text: body, + }); + + // Respond with success + res.status(200).send({ message: 'Feedback sent successfully!' }); + } catch (error) { + console.error('Error sending email:', error); + res.status(500).send({ message: 'Failed to send feedback email.' }); + } +}; From a9de32dd488f157f05015c77de915f63efce3ba3 Mon Sep 17 00:00:00 2001 From: Filippos Date: Mon, 9 Dec 2024 23:32:13 +0300 Subject: [PATCH 32/42] Created in the directory to define the schema and model for feedback data using Mongoose. Included fields for user information, feedback content, timestamps, and associated references, ensuring appropriate data validation and default values. This model serves as the foundation for storing and managing feedback records in the database. --- backend/routes/feedback.js | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 backend/routes/feedback.js diff --git a/backend/routes/feedback.js b/backend/routes/feedback.js new file mode 100644 index 0000000..807ea3b --- /dev/null +++ b/backend/routes/feedback.js @@ -0,0 +1,8 @@ +import express from 'express'; +import { sendFeedbackEmail } from '../controllers/feedbackController.js'; + +const router = express.Router(); + +router.post('/', sendFeedbackEmail); + +export default router; From 1110bdf2c35d4b7f3c6563aeaa6446eca990b64f Mon Sep 17 00:00:00 2001 From: Filippos Date: Mon, 9 Dec 2024 23:36:14 +0300 Subject: [PATCH 33/42] Created server.js to set up and configure the Express server, including middleware for JSON parsing, CORS handling, and logging. Integrated route handling for API endpoints and established a connection to the database using Mongoose. Configured the server to listen on a specified port, ensuring a functional entry point for the backend application. --- backend/server.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 backend/server.js diff --git a/backend/server.js b/backend/server.js new file mode 100644 index 0000000..ba0d3b0 --- /dev/null +++ b/backend/server.js @@ -0,0 +1,20 @@ +import express from 'express'; +import bodyParser from 'body-parser'; +import cors from 'cors'; +import dotenv from 'dotenv'; + +import feedbackRoutes from './routes/feedback.js'; + +dotenv.config(); + +const app = express(); +const PORT = process.env.PORT || 5000; + +app.use(bodyParser.json()); +app.use(cors()); + +// Routes +app.use('/api/feedback', feedbackRoutes); + +// Start Server +app.listen(PORT, () => console.log(`Server running on port ${PORT}`)); From d09bb3faededed31a1fb56c48de36b2516def367 Mon Sep 17 00:00:00 2001 From: Filippos Date: Mon, 9 Dec 2024 23:37:29 +0300 Subject: [PATCH 34/42] Created emailTransporter.js in the backend/util directory to configure and export a reusable email transporter using Nodemailer. Included setup for SMTP settings, authentication, and default sender information to streamline email-sending functionality across the application. Ensured flexibility for future enhancements, such as template integration or additional email providers. --- backend/util/emailTransporter.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 backend/util/emailTransporter.js diff --git a/backend/util/emailTransporter.js b/backend/util/emailTransporter.js new file mode 100644 index 0000000..54c364d --- /dev/null +++ b/backend/util/emailTransporter.js @@ -0,0 +1,12 @@ +import nodemailer from 'nodemailer'; + +const transporter = nodemailer.createTransport({ + host: 'ENTER_HOST_URL', // Mailtrap host + port: 'ENTER_PORT', // Mailtrap port + auth: { + user: 'ENTER_USERNAME', // Mailtrap username + pass: 'ENTER_PASSWORD', // Mailtrap password + }, +}); + +export default transporter; From ff59bdd3893daee6c856f78ce6cdabfa4e4be0c4 Mon Sep 17 00:00:00 2001 From: Talal Date: Mon, 9 Dec 2024 23:42:07 +0300 Subject: [PATCH 35/42] Add restaurant email mappings to ReviewFormPage.tsx for improved maintainability and scalability --- src/pages/ReviewFormPage.tsx | 225 +++++++++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 src/pages/ReviewFormPage.tsx diff --git a/src/pages/ReviewFormPage.tsx b/src/pages/ReviewFormPage.tsx new file mode 100644 index 0000000..2b103d9 --- /dev/null +++ b/src/pages/ReviewFormPage.tsx @@ -0,0 +1,225 @@ +import { useState } from 'react'; +import { Typography, Button, TextField, Checkbox, FormControlLabel } from '@mui/material'; +import axios from 'axios'; +import './ReviewFormPage.css'; + +const RatingFormPage = () => { + // Defined textFieldStyles + const textFieldStyles = { + backgroundColor: '#2D2F32', // Background color of the input fields + '& label': { + color: 'white', // Set default label color to white + }, + '& label.Mui-focused': { + color: 'green', // Changes the label color to green when focused + }, + '& .MuiOutlinedInput-root': { + '& fieldset': { + borderColor: 'grey', // Default border color + }, + '&:hover fieldset': { + borderColor: 'green', // Border color changes to green on hover + }, + '&.Mui-focused fieldset': { + borderColor: 'green', // Border color when the input is focused + }, + '& .MuiInputBase-input': { + color: 'white', // Changes the text color inside the input + } + } + }; + + const [userEmail, setUserEmail] = useState(''); + const [selectedRestaurant, setSelectedRestaurant] = useState(''); + const [wouldRecommend, setWouldRecommend] = useState(false); + const [foodExperience, setFoodExperience] = useState(''); + const [serviceExperience, setServiceExperience] = useState(''); + const [cleanlinessExperience, setCleanlinessExperience] = useState(''); + const [additionalComments, setAdditionalComments] = useState(''); + const [isSubmitting, setIsSubmitting] = useState(false); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setIsSubmitting(true); + + const feedbackData = { + userEmail, + selectedRestaurant, + wouldRecommend, + foodExperience, + serviceExperience, + cleanlinessExperience, + additionalComments, + }; + + try { + const response = await axios.post('http://localhost:5000/api/feedback', feedbackData); + alert(response.data.message); + } catch (error) { + console.error(error); + alert('Failed to send feedback. Please try again later.'); + } finally { + setIsSubmitting(false); + } + }; + + return ( +
+
+ + Leave a Review + + + Got a review? Submit your feedback and it'll be sent right to the restaurant! + +
+ + {/* Email Input */} + setUserEmail(e.target.value)} + /> + + {/* Restaurant Selection */} + setSelectedRestaurant(e.target.value)} + placeholder="Select a Restaurant" + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* Food Experience */} + setFoodExperience(e.target.value)} + /> + + {/* Service Experience */} + setServiceExperience(e.target.value)} + /> + + {/* Cleanliness Experience */} + setCleanlinessExperience(e.target.value)} + /> + + {/* Recommend Checkbox */} + setWouldRecommend(e.target.checked)} + /> + } + label="Would you recommend this restaurant to a friend?" + /> + + {/* Additional Comments */} + setAdditionalComments(e.target.value)} + /> + + {/* Submit Button */} + + +
+ ); +}; + +export default RatingFormPage; From 4c404a598afa7d19b57b8b0b504e4b2ef9b40649 Mon Sep 17 00:00:00 2001 From: Talal Date: Mon, 9 Dec 2024 23:42:57 +0300 Subject: [PATCH 36/42] Changed the application so ReviewFormPage.tsx is correctly imported and accessed --- src/App.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 47b0a0f..18fd8c8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,7 +6,7 @@ import Navbar from './components/Navbar'; import ListPage from './pages/ListPage'; import MapPage from './pages/MapPage'; import NotFoundPage from './pages/NotFoundPage'; -import RatingFormPage from './pages/RatingFormPage'; // Import the new page +import ReviewFormPage from './pages/ReviewFormPage'; // Import the new page import { queryLocations, getLocationStatus } from './util/queryLocations'; import './App.css'; import { @@ -93,7 +93,7 @@ function App() { /> } // Add the new route + element={} // Add the new route /> } /> From 98a3b86c5ed04adab177368dfdb71c985ce0c24a Mon Sep 17 00:00:00 2001 From: mthani2 Date: Tue, 10 Dec 2024 13:13:29 +0300 Subject: [PATCH 37/42] fixed navigation bar to adapt to number of pages --- src/components/Navbar.css | 60 ++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/src/components/Navbar.css b/src/components/Navbar.css index 3b605ca..d4f383e 100644 --- a/src/components/Navbar.css +++ b/src/components/Navbar.css @@ -1,52 +1,48 @@ .Navbar { height: var(--navbar-height); - background-color: #1e1e1e; - padding: 14px; - box-sizing: border-box; - border-top: 2px solid #31373e; + background-color: #1e1e1e; + padding: 14px; + box-sizing: border-box; + border-top: 2px solid #31373e; } .Navbar-links { position: relative; - display: grid; - grid-auto-columns: 1fr; - grid-auto-flow: column; - height: 40px; - margin: 0 auto; - max-width: 500px; + display: grid; + grid-template-columns: repeat(4, 1fr); /* Adjusted for 4 links now */ + grid-auto-flow: column; + height: 40px; + margin: 0 auto; + max-width: 600px; /* Adjusted width to accommodate new link */ } .Navbar-links a { position: relative; - z-index: 5; - display: flex; - align-items: center; - justify-content: center; - font-family: 'Zilla Slab', sans-serif; - font-weight: 500; - color: white; - font-size: 16px; - text-decoration: none; + z-index: 1; + display: flex; + align-items: center; + justify-content: center; + font-family: 'Zilla Slab', sans-serif; + font-weight: 500; + color: white; + font-size: 16px; + text-decoration: none; } .Navbar-links svg { width: 24px; - height: 24px; - margin-right: 0.4em; + height: 24px; + margin-right: 0.4em; } .Navbar-active { - position: absolute; - z-index: 2; - left: 0; - top: 0; - width: 50%; - height: 100%; - background-color: #2b2f33; - border-radius: 999px; - transition: - transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), - box-shadow 0.3s ease-in-out; + position: absolute; + background-color: #2b2f33; + border-radius: 999px; + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); + box-shadow: 0 0 5px #ee6f52, 0 0 10px #ee6f52, 0 0 15px #ee6f52; /* Glowing effect */ + width: calc(350% / 4); /* Adapt width to the number of links */ + height: 100%; } .Navbar-active_map { From 31e2d2b93dd94d46259f073d3c1a3b909d7aa3c0 Mon Sep 17 00:00:00 2001 From: mthani2 Date: Tue, 10 Dec 2024 14:01:31 +0300 Subject: [PATCH 38/42] fixed all routing and backend errors associated with rating averages --- src/services/RatingModel.js | 47 ------------------------- src/services/ratingModel.d.ts | 43 +++++++++++++++-------- src/services/ratingModel.js | 64 +++++++++++++++++++++++++++++++++++ src/services/ratingRoutes.ts | 42 +++++++++++++---------- src/util/server.ts | 13 +++---- 5 files changed, 123 insertions(+), 86 deletions(-) delete mode 100644 src/services/RatingModel.js create mode 100644 src/services/ratingModel.js diff --git a/src/services/RatingModel.js b/src/services/RatingModel.js deleted file mode 100644 index c9e6e1c..0000000 --- a/src/services/RatingModel.js +++ /dev/null @@ -1,47 +0,0 @@ -type Rating = { - userEmail: string; - restaurantName: string; // Use name instead of ID - foodRating: number; - locationRating: number; - cleanlinessRating: number; - serviceRating: number; - valueForMoneyRating: number; - menuVarietyRating: number; - waitTimeRating: number; - staffRating: number; - overallSatisfactionRating: number; - }; - - const ratingsDb: Rating[] = []; // Simulated in-memory database - - export const RatingModel = { - create: async (ratingData: Rating) => { - ratingsDb.push(ratingData); - return ratingData; - }, - - getRatingsByRestaurant: async (restaurantName: string) => { - return ratingsDb.filter((rating) => rating.restaurantName === restaurantName); - }, - - getAverageRating: async (restaurantName: string) => { - const restaurantRatings = ratingsDb.filter((rating) => rating.restaurantName === restaurantName); - if (restaurantRatings.length === 0) return null; - - const totalRatings = restaurantRatings.length; - const averageRating = { - foodRating: restaurantRatings.reduce((sum, rating) => sum + rating.foodRating, 0) / totalRatings, - locationRating: restaurantRatings.reduce((sum, rating) => sum + rating.locationRating, 0) / totalRatings, - cleanlinessRating: restaurantRatings.reduce((sum, rating) => sum + rating.cleanlinessRating, 0) / totalRatings, - serviceRating: restaurantRatings.reduce((sum, rating) => sum + rating.serviceRating, 0) / totalRatings, - valueForMoneyRating: restaurantRatings.reduce((sum, rating) => sum + rating.valueForMoneyRating, 0) / totalRatings, - menuVarietyRating: restaurantRatings.reduce((sum, rating) => sum + rating.menuVarietyRating, 0) / totalRatings, - waitTimeRating: restaurantRatings.reduce((sum, rating) => sum + rating.waitTimeRating, 0) / totalRatings, - staffRating: restaurantRatings.reduce((sum, rating) => sum + rating.staffRating, 0) / totalRatings, - overallSatisfactionRating: restaurantRatings.reduce((sum, rating) => sum + rating.overallSatisfactionRating, 0) / totalRatings, - }; - - return averageRating; - }, - }; - \ No newline at end of file diff --git a/src/services/ratingModel.d.ts b/src/services/ratingModel.d.ts index c465c70..47687be 100644 --- a/src/services/ratingModel.d.ts +++ b/src/services/ratingModel.d.ts @@ -1,14 +1,29 @@ -declare module './RatingModel.js' { - type Rating = { - restaurant: string; - rating: number; - comment?: string; - }; - - export const RatingModel: { - create: (rating: Rating) => Rating; - getRatingsByRestaurant: (restaurantName: string) => Rating[]; - getAverageRating: (restaurantName: string) => number | null; - }; - } - \ No newline at end of file +export interface Rating { + userEmail: string; + restaurantName: string; + foodRating: number; + locationRating: number; + cleanlinessRating: number; + serviceRating: number; + valueForMoneyRating: number; + menuVarietyRating: number; + waitTimeRating: number; + staffRating: number; + overallSatisfactionRating: number; +} + +export declare const RatingModel: { + create(ratingData: Rating): Promise; + getRatingsByRestaurant(restaurantName: string): Promise; + getAverageRating(restaurantName: string): Promise<{ + foodRating: number; + locationRating: number; + cleanlinessRating: number; + serviceRating: number; + valueForMoneyRating: number; + menuVarietyRating: number; + waitTimeRating: number; + staffRating: number; + overallSatisfactionRating: number; + } | null>; +}; \ No newline at end of file diff --git a/src/services/ratingModel.js b/src/services/ratingModel.js new file mode 100644 index 0000000..3ec1dec --- /dev/null +++ b/src/services/ratingModel.js @@ -0,0 +1,64 @@ +/** + * @typedef {Object} Rating + * @property {string} userEmail - The user's email. + * @property {string} restaurantName - The name of the restaurant. + * @property {number} foodRating - Rating for food. + * @property {number} locationRating - Rating for location. + * @property {number} cleanlinessRating - Rating for cleanliness. + * @property {number} serviceRating - Rating for service. + * @property {number} valueForMoneyRating - Rating for value for money. + * @property {number} menuVarietyRating - Rating for menu variety. + * @property {number} waitTimeRating - Rating for wait time. + * @property {number} staffRating - Rating for staff. + * @property {number} overallSatisfactionRating - Rating for overall satisfaction. + */ + +/** Simulated in-memory database */ +const ratingsDb = []; + +/** RatingModel provides methods to interact with the ratings database */ +export const RatingModel = { + /** + * Add a new rating. + * @param {Rating} ratingData - The rating data to be added. + * @returns {Promise} The added rating data. + */ + create: async (ratingData) => { + ratingsDb.push(ratingData); + return ratingData; + }, + + /** + * Get all ratings for a specific restaurant. + * @param {string} restaurantName - The name of the restaurant. + * @returns {Promise} List of ratings for the restaurant. + */ + getRatingsByRestaurant: async (restaurantName) => { + return ratingsDb.filter((rating) => rating.restaurantName === restaurantName); + }, + + /** + * Get the average ratings for a specific restaurant. + * @param {string} restaurantName - The name of the restaurant. + * @returns {Promise} The average ratings or null if no ratings exist. + */ + getAverageRating: async (restaurantName) => { + const restaurantRatings = ratingsDb.filter((rating) => rating.restaurantName === restaurantName); + if (restaurantRatings.length === 0) return null; + + const totalRatings = restaurantRatings.length; + const averageRating = { + foodRating: restaurantRatings.reduce((sum, rating) => sum + rating.foodRating, 0) / totalRatings, + locationRating: restaurantRatings.reduce((sum, rating) => sum + rating.locationRating, 0) / totalRatings, + cleanlinessRating: restaurantRatings.reduce((sum, rating) => sum + rating.cleanlinessRating, 0) / totalRatings, + serviceRating: restaurantRatings.reduce((sum, rating) => sum + rating.serviceRating, 0) / totalRatings, + valueForMoneyRating: restaurantRatings.reduce((sum, rating) => sum + rating.valueForMoneyRating, 0) / totalRatings, + menuVarietyRating: restaurantRatings.reduce((sum, rating) => sum + rating.menuVarietyRating, 0) / totalRatings, + waitTimeRating: restaurantRatings.reduce((sum, rating) => sum + rating.waitTimeRating, 0) / totalRatings, + staffRating: restaurantRatings.reduce((sum, rating) => sum + rating.staffRating, 0) / totalRatings, + overallSatisfactionRating: restaurantRatings.reduce((sum, rating) => sum + rating.overallSatisfactionRating, 0) / totalRatings, + }; + + return averageRating; + }, +}; \ No newline at end of file diff --git a/src/services/ratingRoutes.ts b/src/services/ratingRoutes.ts index 5295e39..31a28b3 100644 --- a/src/services/ratingRoutes.ts +++ b/src/services/ratingRoutes.ts @@ -1,21 +1,29 @@ -import { RatingModel } from './RatingModel.js'; +import { RatingModel } from './ratingModel.js'; -export const ratingRoutes = (app: any) => { - app.post('/api/ratings', async (req: any, res: any) => { - const ratingData = await req.json(); - const newRating = await RatingModel.create(ratingData); - return res.json(newRating); - }); +export const ratingRoutes = (req: Request) => { + const url = new URL(req.url); + const method = req.method; - app.get('/api/ratings/:restaurantName', async (req: any, res: any) => { - const restaurantName = req.params.restaurantName; // Use name instead of ID - const ratings = await RatingModel.getRatingsByRestaurant(restaurantName); - return res.json(ratings); - }); + if (url.pathname === '/api/ratings' && method === 'POST') { + return req.json().then(async (ratingData) => { + const newRating = await RatingModel.create(ratingData); + return new Response(JSON.stringify(newRating), { status: 200 }); + }); + } - app.get('/api/ratings/average/:restaurantName', async (req: any, res: any) => { - const restaurantName = req.params.restaurantName; // Use name instead of ID - const averageRating = await RatingModel.getAverageRating(restaurantName); - return res.json({ averageRating }); - }); + if (url.pathname.startsWith('/api/ratings/average/') && method === 'GET') { + const restaurantName = url.pathname.split('/').pop(); + return RatingModel.getAverageRating(restaurantName || '').then((averageRating) => + new Response(JSON.stringify({ averageRating }), { status: 200 }) + ); + } + + if (url.pathname.startsWith('/api/ratings/') && method === 'GET') { + const restaurantName = url.pathname.split('/').pop(); + return RatingModel.getRatingsByRestaurant(restaurantName || '').then((ratings) => + new Response(JSON.stringify(ratings), { status: 200 }) + ); + } + + return Promise.resolve(new Response('Not Found', { status: 404 })); }; diff --git a/src/util/server.ts b/src/util/server.ts index 28f72c5..a511528 100644 --- a/src/util/server.ts +++ b/src/util/server.ts @@ -1,17 +1,14 @@ -import { RatingModel } from './services/RatingModel.js'; -import { ratingRoutes } from './services/ratingRoutes.ts'; +import { ratingRoutes } from '../services/ratingRoutes'; const server = Bun.serve({ port: 3000, - fetch(req) { - // Handle routes using ratingRoutes - const url = new URL(req.url); - if (url.pathname.startsWith('/api/ratings')) { + async fetch(req) { + // Delegate to ratingRoutes for handling API requests + if (req.url.startsWith('/api/ratings')) { return ratingRoutes(req); } return new Response('Not Found', { status: 404 }); }, }); -console.log(`Server running on http://localhost:${server.port}`); - +console.log(`Server running on http://localhost:${server.port}`); \ No newline at end of file From 776bd416696e8a5f6c9bca873b62b9e0282f4dc2 Mon Sep 17 00:00:00 2001 From: mthani2 Date: Tue, 10 Dec 2024 15:02:18 +0300 Subject: [PATCH 39/42] Enhanced rating routes with POST and GET logic for average ratings and handling all ratings per restaurant. --- src/pages/RatingFormPage.tsx | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/pages/RatingFormPage.tsx b/src/pages/RatingFormPage.tsx index 33bf759..e6edde2 100644 --- a/src/pages/RatingFormPage.tsx +++ b/src/pages/RatingFormPage.tsx @@ -1,5 +1,7 @@ import React, { useState } from 'react'; -import { Typography, Button, RadioGroup, FormControlLabel, Radio, TextField } from '@mui/material'; +import { Typography, Button, RadioGroup, FormControlLabel, Radio, TextField, Snackbar } from '@mui/material'; +import MuiAlert from '@mui/material/Alert'; + const RatingPage = () => { const [userEmail, setUserEmail] = useState(''); // Email state @@ -13,6 +15,8 @@ const RatingPage = () => { const [staffRating, setStaffRating] = useState(3); // Staff rating state const [overallSatisfactionRating, setOverallSatisfactionRating] = useState(3); // Overall satisfaction rating state const [selectedRestaurant, setSelectedRestaurant] = useState(''); // Restaurant selection state + const [success, setSuccess] = useState(false); // Success notification state + // Define textFieldStyles for styling const textFieldStyles = { @@ -56,17 +60,19 @@ const RatingPage = () => { headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(ratings), }); - + if (response.ok) { - console.log("Rating submitted successfully"); + setSuccess(true); // Show success notification } else { - console.error("Failed to submit rating"); + console.error('Failed to submit rating'); } } catch (error) { - console.error("Error submitting rating: ", error); + console.error('Error submitting rating:', error); } }; + const handleSnackbarClose = () => setSuccess(false); + return (
@@ -355,8 +361,21 @@ const RatingPage = () => { Submit Rating + + {/* Snackbar Notification */} + + + Successfully submitted your review! + +
); }; + export default RatingPage; From 27ee003b94abe73fd1a865d0c792ff7cb7980f4d Mon Sep 17 00:00:00 2001 From: seif khelifi Date: Wed, 11 Dec 2024 20:30:06 +0300 Subject: [PATCH 40/42] Added rating routes and integrated them into the server --- backend/routes/rating.js | 15 +++++++++++++++ backend/server.js | 9 +++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 backend/routes/rating.js diff --git a/backend/routes/rating.js b/backend/routes/rating.js new file mode 100644 index 0000000..cc9eeaa --- /dev/null +++ b/backend/routes/rating.js @@ -0,0 +1,15 @@ +import express from 'express'; +import { createRating, getRatingsByRestaurant, getAverageRating } from '../controllers/ratingController.js'; + +const router = express.Router(); + +// POST: Create a new rating +router.post('/ratings', createRating); + +// GET: Fetch all ratings for a restaurant +router.get('/ratings/:restaurantName', getRatingsByRestaurant); + +// GET: Fetch the average rating for a restaurant +router.get('/ratings/average/:restaurantName', getAverageRating); + +export default router; diff --git a/backend/server.js b/backend/server.js index ba0d3b0..3e4df53 100644 --- a/backend/server.js +++ b/backend/server.js @@ -3,18 +3,23 @@ import bodyParser from 'body-parser'; import cors from 'cors'; import dotenv from 'dotenv'; -import feedbackRoutes from './routes/feedback.js'; +import feedbackRoutes from './routes/feedback.js'; // Existing feedback routes +import ratingRoutes from './routes/rating.js'; // New rating routes dotenv.config(); const app = express(); const PORT = process.env.PORT || 5000; +// Middleware app.use(bodyParser.json()); app.use(cors()); -// Routes +// Feedback Routes app.use('/api/feedback', feedbackRoutes); +// Rating Routes +app.use('/api/ratings', ratingRoutes); // New addition for rating system + // Start Server app.listen(PORT, () => console.log(`Server running on port ${PORT}`)); From 7279dddab4c197aff348d20f97c58b304c7723ba Mon Sep 17 00:00:00 2001 From: mthani2 Date: Wed, 11 Dec 2024 22:08:34 +0300 Subject: [PATCH 41/42] Revert " Added rating routes and integrated them into the server" This reverts commit 27ee003b94abe73fd1a865d0c792ff7cb7980f4d. --- backend/routes/rating.js | 15 --------------- backend/server.js | 9 ++------- 2 files changed, 2 insertions(+), 22 deletions(-) delete mode 100644 backend/routes/rating.js diff --git a/backend/routes/rating.js b/backend/routes/rating.js deleted file mode 100644 index cc9eeaa..0000000 --- a/backend/routes/rating.js +++ /dev/null @@ -1,15 +0,0 @@ -import express from 'express'; -import { createRating, getRatingsByRestaurant, getAverageRating } from '../controllers/ratingController.js'; - -const router = express.Router(); - -// POST: Create a new rating -router.post('/ratings', createRating); - -// GET: Fetch all ratings for a restaurant -router.get('/ratings/:restaurantName', getRatingsByRestaurant); - -// GET: Fetch the average rating for a restaurant -router.get('/ratings/average/:restaurantName', getAverageRating); - -export default router; diff --git a/backend/server.js b/backend/server.js index 3e4df53..ba0d3b0 100644 --- a/backend/server.js +++ b/backend/server.js @@ -3,23 +3,18 @@ import bodyParser from 'body-parser'; import cors from 'cors'; import dotenv from 'dotenv'; -import feedbackRoutes from './routes/feedback.js'; // Existing feedback routes -import ratingRoutes from './routes/rating.js'; // New rating routes +import feedbackRoutes from './routes/feedback.js'; dotenv.config(); const app = express(); const PORT = process.env.PORT || 5000; -// Middleware app.use(bodyParser.json()); app.use(cors()); -// Feedback Routes +// Routes app.use('/api/feedback', feedbackRoutes); -// Rating Routes -app.use('/api/ratings', ratingRoutes); // New addition for rating system - // Start Server app.listen(PORT, () => console.log(`Server running on port ${PORT}`)); From 26b0ce768f9ed8e51d4e71c024d5111ad5edb7c0 Mon Sep 17 00:00:00 2001 From: mthani2 Date: Wed, 11 Dec 2024 22:09:51 +0300 Subject: [PATCH 42/42] Revert "Added ratingController.js with endpoints for creating, fetching, and averaging ratings" This reverts commit 058834ac931759186aaa9cf1f1b4ca4f9bcd98d2, reversing changes made to 776bd416696e8a5f6c9bca873b62b9e0282f4dc2. --- .env | 4 - .env.example | 8 +- backend/controllers/feedbackController.js | 93 --------- backend/controllers/ratingController.js | 41 ---- backend/routes/feedback.js | 8 - backend/server.js | 20 -- backend/util/emailTransporter.js | 12 -- src/App.tsx | 82 -------- src/components/Navbar.css | 33 +--- src/components/Navbar.tsx | 15 -- src/pages/RatingFormPage.css | 46 ----- src/pages/RatingFormPage.tsx | 181 ------------------ src/pages/ReviewFormPage.tsx | 222 ---------------------- 13 files changed, 14 insertions(+), 751 deletions(-) delete mode 100644 .env delete mode 100644 backend/controllers/feedbackController.js delete mode 100644 backend/controllers/ratingController.js delete mode 100644 backend/routes/feedback.js delete mode 100644 backend/server.js delete mode 100644 backend/util/emailTransporter.js delete mode 100644 src/pages/RatingFormPage.css diff --git a/.env b/.env deleted file mode 100644 index 065238c..0000000 --- a/.env +++ /dev/null @@ -1,4 +0,0 @@ -MAPKIT_JS_TEAM_ID=4Y39FMA838 -MAPKIT_JS_AUTH_KEY=LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR1RBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJIa3dkd0lCQVFRZ1Y4Q2crcXRMaEQxcU4zcU0KUkN4Yi9ZU1dQWkg3RElPQXdqSEFpNTl1RkphZ0NnWUlLb1pJemowREFRZWhSQU5DQUFUUlJiRkpPaW5rcGNDbQpxZnRiOWY3eUhlZzkxbTRvTExrMyt0UTl5Wkd4M2k0LzUybUlJZnVOZHQ1VjgrVENKM3NjQjRyeHhIUGJOZ3Z5CmpLdUVOME9rCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= -MAPKIT_JS_KEY_ID=WZ9QVAGR4F -MAPKIT_JS_ORIGIN=http://localhost \ No newline at end of file diff --git a/.env.example b/.env.example index 065238c..53efeca 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,4 @@ -MAPKIT_JS_TEAM_ID=4Y39FMA838 -MAPKIT_JS_AUTH_KEY=LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR1RBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJIa3dkd0lCQVFRZ1Y4Q2crcXRMaEQxcU4zcU0KUkN4Yi9ZU1dQWkg3RElPQXdqSEFpNTl1RkphZ0NnWUlLb1pJemowREFRZWhSQU5DQUFUUlJiRkpPaW5rcGNDbQpxZnRiOWY3eUhlZzkxbTRvTExrMyt0UTl5Wkd4M2k0LzUybUlJZnVOZHQ1VjgrVENKM3NjQjRyeHhIUGJOZ3Z5CmpLdUVOME9rCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= -MAPKIT_JS_KEY_ID=WZ9QVAGR4F -MAPKIT_JS_ORIGIN=http://localhost \ No newline at end of file +MAPKIT_JS_TEAM_ID= +MAPKIT_JS_AUTH_KEY= +MAPKIT_JS_KEY_ID= +MAPKIT_JS_ORIGIN= diff --git a/backend/controllers/feedbackController.js b/backend/controllers/feedbackController.js deleted file mode 100644 index f591f1a..0000000 --- a/backend/controllers/feedbackController.js +++ /dev/null @@ -1,93 +0,0 @@ -import transporter from '../util/emailTransporter.js'; - -const restaurantEmails = { - "stephanies@example.com": "Stephanie's - Market c", - "scottys@example.com": "Scotty's Market By Salem's", - "aubonpain@example.com": "Au Bon Pain At Skibo Café", - "rohrcafe@example.com": "Rohr Café - La Prima", - "schatz@example.com": "Schatz Dining Room", - "defercafe@example.com": "De Fer Coffee & Tea At Maggie Murph Café", - "exchange@example.com": "The Exchange", - "capitalgrains@example.com": "Capital Grains - Rohr Commons", - "milliescreamery@example.com": "Millie's Coffee 'n' Creamery - Rohr Commons", - "stackddessert@example.com": "Stack'd Dessert Bar", - "stackdunderground@example.com": "Stack'd Underground", - "entropy@example.com": "Entropy+", - "elgallo@example.com": "El Gallo De Oro", - "revolutionnoodle@example.com": "Revolution Noodle", - "tasteofindia@example.com": "Taste Of India", - "trueburger@example.com": "True Burger", - "hunanexpress@example.com": "Hunan Express", - "edgecafe@example.com": "The Edge Cafe & Market", - "tahini@example.com": "Tahini", - "eggshoppe@example.com": "Egg Shoppe - Grubhub Only", - "laprimaespresso@example.com": "La Prima Espresso", - "redhawkcoffee@example.com": "Redhawk Coffee", - "zebralounge@example.com": "Zebra Lounge", - "ciaobella@example.com": "Ciao Bella", - "crispandcrust@example.com": "Crisp And Crust", - "nourish@example.com": "Nourish", - "buildpizza@example.com": "Build Pizza - Rohr Commons", - "forbesavesubs@example.com": "Forbes Avenue Subs - Rohr Commons", - "rohrcommons@example.com": "Rohr Commons - Tepper Eatery", - "teppertaqueria@example.com": "Tepper Taqueria", - "teppertaqueriaexp@example.com": "Tepper Taqueria Express", - "wildbluesushi@example.com": "Wild Blue Sushi - Ruge Atrium", - "shakesmart@example.com": "Shake Smart", - "eatevenings@example.com": "E.a.t. (evenings At Tepper) - Rohr Commons", - "urbanrevolution@example.com": "Urban Revolution - Grubhub Only", - "testconcept@example.com": "Test Concept", - "tartantruck@example.com": "Tartan Food Truck - Mr. Bulgogi" -}; - -export const sendFeedbackEmail = async (req, res) => { - const { - userEmail, - selectedRestaurant, - wouldRecommend, - foodExperience, - serviceExperience, - cleanlinessExperience, - additionalComments, - } = req.body; - - // Validate request data - if (!userEmail || !selectedRestaurant || !foodExperience || !serviceExperience || !cleanlinessExperience) { - return res.status(400).send({ message: 'All required fields must be filled.' }); - } - - try { - // Get the restaurant name and email - const restaurantName = restaurantEmails[selectedRestaurant]; - if (!restaurantName) { - return res.status(404).send({ message: 'Invalid restaurant selected.' }); - } - - // Construct the email subject and body - const subject = `New Feedback for ${restaurantName}`; - const body = ` - Feedback received for ${restaurantName}: - - - User Email: ${userEmail} - - Food Experience: ${foodExperience} - - Service Experience: ${serviceExperience} - - Cleanliness Experience: ${cleanlinessExperience} - - Would Recommend: ${wouldRecommend ? 'Yes' : 'No'} - - Additional Comments: ${additionalComments} - `; - - // Send the email - await transporter.sendMail({ - from: userEmail, - to: selectedRestaurant, // Email of the selected restaurant - subject: subject, - text: body, - }); - - // Respond with success - res.status(200).send({ message: 'Feedback sent successfully!' }); - } catch (error) { - console.error('Error sending email:', error); - res.status(500).send({ message: 'Failed to send feedback email.' }); - } -}; diff --git a/backend/controllers/ratingController.js b/backend/controllers/ratingController.js deleted file mode 100644 index e437163..0000000 --- a/backend/controllers/ratingController.js +++ /dev/null @@ -1,41 +0,0 @@ -import { RatingModel } from '../../src/services/ratingModel.js'; - -// Create a new rating -export const createRating = async (req, res) => { - try { - const ratingData = req.body; // Ensure body-parser middleware is used - const newRating = await RatingModel.create(ratingData); - res.status(201).json(newRating); - } catch (error) { - console.error('Error creating rating:', error); - res.status(500).json({ error: 'Failed to create rating' }); - } -}; - -// Get all ratings for a restaurant -export const getRatingsByRestaurant = async (req, res) => { - try { - const { restaurantName } = req.params; - const ratings = await RatingModel.getRatingsByRestaurant(restaurantName); - res.status(200).json(ratings); - } catch (error) { - console.error('Error fetching ratings:', error); - res.status(500).json({ error: 'Failed to fetch ratings' }); - } -}; - -// Get average ratings for a restaurant -export const getAverageRating = async (req, res) => { - try { - const { restaurantName } = req.params; - const averageRating = await RatingModel.getAverageRating(restaurantName); - if (averageRating) { - res.status(200).json({ averageRating }); - } else { - res.status(404).json({ error: 'No ratings found for this restaurant' }); - } - } catch (error) { - console.error('Error fetching average rating:', error); - res.status(500).json({ error: 'Failed to fetch average rating' }); - } -}; diff --git a/backend/routes/feedback.js b/backend/routes/feedback.js deleted file mode 100644 index 807ea3b..0000000 --- a/backend/routes/feedback.js +++ /dev/null @@ -1,8 +0,0 @@ -import express from 'express'; -import { sendFeedbackEmail } from '../controllers/feedbackController.js'; - -const router = express.Router(); - -router.post('/', sendFeedbackEmail); - -export default router; diff --git a/backend/server.js b/backend/server.js deleted file mode 100644 index ba0d3b0..0000000 --- a/backend/server.js +++ /dev/null @@ -1,20 +0,0 @@ -import express from 'express'; -import bodyParser from 'body-parser'; -import cors from 'cors'; -import dotenv from 'dotenv'; - -import feedbackRoutes from './routes/feedback.js'; - -dotenv.config(); - -const app = express(); -const PORT = process.env.PORT || 5000; - -app.use(bodyParser.json()); -app.use(cors()); - -// Routes -app.use('/api/feedback', feedbackRoutes); - -// Start Server -app.listen(PORT, () => console.log(`Server running on port ${PORT}`)); diff --git a/backend/util/emailTransporter.js b/backend/util/emailTransporter.js deleted file mode 100644 index 54c364d..0000000 --- a/backend/util/emailTransporter.js +++ /dev/null @@ -1,12 +0,0 @@ -import nodemailer from 'nodemailer'; - -const transporter = nodemailer.createTransport({ - host: 'ENTER_HOST_URL', // Mailtrap host - port: 'ENTER_PORT', // Mailtrap port - auth: { - user: 'ENTER_USERNAME', // Mailtrap username - pass: 'ENTER_PASSWORD', // Mailtrap password - }, -}); - -export default transporter; diff --git a/src/App.tsx b/src/App.tsx index afc814d..18097bb 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,12 +6,8 @@ import Navbar from './components/Navbar'; import ListPage from './pages/ListPage'; import MapPage from './pages/MapPage'; import NotFoundPage from './pages/NotFoundPage'; -<<<<<<< HEAD import ReviewFormPage from './pages/ReviewFormPage'; // Import the ReviewFormPage import RatingPage from './pages/RatingFormPage'; // Import the new RatingPage -======= -import ReviewFormPage from './pages/ReviewFormPage'; // Import the new page ->>>>>>> 4c404a598afa7d19b57b8b0b504e4b2ef9b40649 import { queryLocations, getLocationStatus } from './util/queryLocations'; import './App.css'; import { @@ -22,7 +18,6 @@ import { const CMU_EATS_API_URL = 'https://dining.apis.scottylabs.org/locations'; function App() { -<<<<<<< HEAD // Load locations const [locations, setLocations] = useState(); const [extendedLocationData, setExtendedLocationData] = @@ -33,37 +28,6 @@ function App() { setLocations(parsedLocations); }); }, []); -======= - // Load locations - const [locations, setLocations] = useState(); - const [extendedLocationData, setExtendedLocationData] = - useState(); - - useEffect(() => { - queryLocations(CMU_EATS_API_URL).then((parsedLocations) => { - setLocations(parsedLocations); - }); - }, []); - - useEffect(() => { - const intervalId = setInterval( - (function updateExtendedLocationData() { - if (locations !== undefined) { - const now = DateTime.now().setZone('America/New_York'); - setExtendedLocationData( - locations.map((location) => ({ - ...location, - ...getLocationStatus(location.times, now), // populate location with more detailed info relevant to current time - })), - ); - } - return updateExtendedLocationData; - })(), - 1 * 1000, // updates every second - ); - return () => clearInterval(intervalId); - }, [locations]); ->>>>>>> 4c404a598afa7d19b57b8b0b504e4b2ef9b40649 useEffect(() => { const intervalId = setInterval( @@ -95,7 +59,6 @@ function App() { window.addEventListener('online', handleOnline); -<<<<<<< HEAD return () => window.removeEventListener('online', handleOnline); }, []); @@ -145,51 +108,6 @@ function App() { ); -======= - return ( - - -
-
- Pre-register for{' '} - - TartanHacks - - , Pittsburgh's LARGEST hackathon! 🖥️ -
-
- - - } - /> - - } - /> - } // Add the new route - /> - } /> - -
- -
-
-
- ); ->>>>>>> 4c404a598afa7d19b57b8b0b504e4b2ef9b40649 } export default App; - diff --git a/src/components/Navbar.css b/src/components/Navbar.css index 15f85a4..d4f383e 100644 --- a/src/components/Navbar.css +++ b/src/components/Navbar.css @@ -1,9 +1,5 @@ .Navbar { -<<<<<<< HEAD height: var(--navbar-height); -======= - height: var(--navbar-height); ->>>>>>> 4c404a598afa7d19b57b8b0b504e4b2ef9b40649 background-color: #1e1e1e; padding: 14px; box-sizing: border-box; @@ -11,15 +7,9 @@ } .Navbar-links { -<<<<<<< HEAD position: relative; display: grid; grid-template-columns: repeat(4, 1fr); /* Adjusted for 4 links now */ -======= - position: relative; - display: grid; - grid-template-columns: repeat(3, 1fr); /* Adjusted for 3 links now */ ->>>>>>> 4c404a598afa7d19b57b8b0b504e4b2ef9b40649 grid-auto-flow: column; height: 40px; margin: 0 auto; @@ -27,11 +17,7 @@ } .Navbar-links a { -<<<<<<< HEAD position: relative; -======= - position: relative; ->>>>>>> 4c404a598afa7d19b57b8b0b504e4b2ef9b40649 z-index: 1; display: flex; align-items: center; @@ -44,11 +30,7 @@ } .Navbar-links svg { -<<<<<<< HEAD width: 24px; -======= - width: 24px; ->>>>>>> 4c404a598afa7d19b57b8b0b504e4b2ef9b40649 height: 24px; margin-right: 0.4em; } @@ -59,12 +41,17 @@ border-radius: 999px; transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); box-shadow: 0 0 5px #ee6f52, 0 0 10px #ee6f52, 0 0 15px #ee6f52; /* Glowing effect */ -<<<<<<< HEAD width: calc(350% / 4); /* Adapt width to the number of links */ -======= - width: calc(215% / 3); /* Adapt width to the number of links */ ->>>>>>> 4c404a598afa7d19b57b8b0b504e4b2ef9b40649 height: 100%; } -/* Active and hover styling managed via NavLink in Navbar.tsx */ +.Navbar-active_map { + transform: translateX(100%); +} + +.Navbar-active_glow { + box-shadow: + 0 0 5px #ee6f52, + 0 0 10px #ee6f52, + 0 0 15px #ee6f52; +} diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 6c0fcf2..fb82867 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -6,13 +6,8 @@ function Navbar() { return (
); -======= - backgroundColor: '#2D2F32', // Background color of the input fields - '& label': { - color: 'white', // Set default label color to white - }, - '& label.Mui-focused': { - color: 'green', // Changes the label color to green when focused - }, - '& .MuiOutlinedInput-root': { - '& fieldset': { - borderColor: 'grey', // Default border color - }, - '&:hover fieldset': { - borderColor: 'green', // Border color changes to green on hover - }, - '&.Mui-focused fieldset': { - borderColor: 'green', // Border color when the input is focused - }, - '& .MuiInputBase-input': { - color: 'white', // Changes the text color inside the input - } - } - }; - - const [userEmail, setUserEmail] = useState(''); - const [selectedRestaurant, setSelectedRestaurant] = useState(''); - const [wouldRecommend, setWouldRecommend] = useState(false); - const [foodExperience, setFoodExperience] = useState(''); - const [serviceExperience, setServiceExperience] = useState(''); - const [cleanlinessExperience, setCleanlinessExperience] = useState(''); - const [additionalComments, setAdditionalComments] = useState(''); - const [isSubmitting, setIsSubmitting] = useState(false); - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - setIsSubmitting(true); - - const feedbackData = { - userEmail, - selectedRestaurant, - wouldRecommend, - foodExperience, - serviceExperience, - cleanlinessExperience, - additionalComments, - }; - - try { - const response = await axios.post('http://localhost:5000/api/feedback', feedbackData); - alert(response.data.message); - } catch (error) { - console.error(error); - alert('Failed to send feedback. Please try again later.'); - } finally { - setIsSubmitting(false); - } - }; - - return ( -
-
- - Leave a Review - - - Got a review? Submit your feedback and it'll be sent right to the restaurant! - -
-
- {/* Email Input */} - setUserEmail(e.target.value)} - /> - - {/* Restaurant Selection */} - setSelectedRestaurant(e.target.value)} - placeholder="Select a Restaurant" - > - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {/* Food Experience */} - setFoodExperience(e.target.value)} - /> - - {/* Service Experience */} - setServiceExperience(e.target.value)} - /> - - {/* Cleanliness Experience */} - setCleanlinessExperience(e.target.value)} - /> - - {/* Recommend Checkbox */} - setWouldRecommend(e.target.checked)} - /> - } - label="Would you recommend this restaurant to a friend?" - /> - - {/* Additional Comments */} - setAdditionalComments(e.target.value)} - /> - - {/* Submit Button */} - - -
- ); ->>>>>>> 4c404a598afa7d19b57b8b0b504e4b2ef9b40649 }; export default RatingFormPage;