From 92a97619351dd81cdb2e8cf7d4aa53afe73e9f5e Mon Sep 17 00:00:00 2001 From: rosy Date: Sat, 31 Jan 2026 14:43:03 +0300 Subject: [PATCH 1/4] add a way to restrict review for user only --- client/package.json | 1 - client/src/components/common/MapSection.jsx | 3 +- client/src/components/common/Popups.jsx | 33 ---- client/src/components/home/FeaturedCard.jsx | 20 +- client/src/components/layout/Navbar.jsx | 82 ++++----- .../owner/OwnerAddRestaurantForm.jsx | 25 ++- client/src/context/AuthContext.jsx | 2 +- client/src/context/RestaurantsContext.jsx | 13 +- client/src/main.jsx | 2 - client/src/pages/OwnerDashboard.jsx | 15 +- client/src/pages/RestaurantDetailsPage.jsx | 22 ++- client/src/pages/UserProfilePage.jsx | 2 + client/src/styles/layout.css | 174 +++++++++++++++++- package-lock.json | 34 +--- package.json | 8 +- server/package.json | 2 +- 16 files changed, 287 insertions(+), 151 deletions(-) delete mode 100644 client/src/components/common/Popups.jsx diff --git a/client/package.json b/client/package.json index e340a82..19a36e0 100755 --- a/client/package.json +++ b/client/package.json @@ -14,7 +14,6 @@ "axios": "^1.13.2", "jwt-decode": "^4.0.0", "leaflet": "^1.9.4", - "bootstrap": "^5.3.2", "react": "^19.2.0", "react-dom": "^19.2.0", "react-leaflet": "^5.0.0", diff --git a/client/src/components/common/MapSection.jsx b/client/src/components/common/MapSection.jsx index fb1b924..ef4298d 100755 --- a/client/src/components/common/MapSection.jsx +++ b/client/src/components/common/MapSection.jsx @@ -19,7 +19,8 @@ L.Icon.Default.mergeOptions({ const MapSection = ({ location, name }) => { // Default to Addis Ababa if no location - const position = location && location.lat && location.lng ? [location.lat, location.lng] : [9.03, 38.74]; + const isLatAndLogProvided = location && location.lat && location.lng && location.lat != '' && location.lng != '' + const position = isLatAndLogProvided ? [location.lat, location.lng] : [9.03, 38.74]; return (
diff --git a/client/src/components/common/Popups.jsx b/client/src/components/common/Popups.jsx deleted file mode 100644 index 87e15ce..0000000 --- a/client/src/components/common/Popups.jsx +++ /dev/null @@ -1,33 +0,0 @@ -import React, { useState, useRef, useEffect } from "react"; -import { Link } from "react-router-dom"; - -function PopupButton() { - const [open, setOpen] = useState(false); - const containerRef = useRef(null); - - // Close the popup if clicked outside - useEffect(() => { - const handleClickOutside = (event) => { - if (containerRef.current && !containerRef.current.contains(event.target)) { - setOpen(false); - } - }; - document.addEventListener("mousedown", handleClickOutside); - return () => document.removeEventListener("mousedown", handleClickOutside); - }, []); - - return ( -
- { e.preventDefault(); setOpen(!open); }}>Sign up - - {open && ( -
- User - Owner -
- )} -
- ); -} - -export default PopupButton; \ No newline at end of file diff --git a/client/src/components/home/FeaturedCard.jsx b/client/src/components/home/FeaturedCard.jsx index 5aabf20..1b0d183 100755 --- a/client/src/components/home/FeaturedCard.jsx +++ b/client/src/components/home/FeaturedCard.jsx @@ -11,7 +11,7 @@ import TomocaImg from '../../assets/tomoca.png'; import Placeholder from "../../assets/addis-cafe.jpg"; -function FeaturedCard({ restaurant, showDeleteButton }) { +function FeaturedCard({ restaurant, showDeleteButton, onDelete }) { const { toggleFavorite, isFavorite, deleteRestaurant } = useContext(RestaurantsContext); const fav = isFavorite ? isFavorite(restaurant._id) : false; @@ -78,7 +78,23 @@ function FeaturedCard({ restaurant, showDeleteButton }) {
{restaurant.address &&
{restaurant.address}
} View - {showDeleteButton && } + {showDeleteButton && ( + + )}
diff --git a/client/src/components/layout/Navbar.jsx b/client/src/components/layout/Navbar.jsx index 95f9355..726fb79 100755 --- a/client/src/components/layout/Navbar.jsx +++ b/client/src/components/layout/Navbar.jsx @@ -38,31 +38,36 @@ function Navbar({ onSearch }) { }; return ( - + ); } diff --git a/client/src/components/owner/OwnerAddRestaurantForm.jsx b/client/src/components/owner/OwnerAddRestaurantForm.jsx index 1ee067b..ad5a2c0 100644 --- a/client/src/components/owner/OwnerAddRestaurantForm.jsx +++ b/client/src/components/owner/OwnerAddRestaurantForm.jsx @@ -6,11 +6,13 @@ import AuthContext from "../../context/AuthContext"; import axios from "axios"; import RestaurantsContext from "../../context/RestaurantsContext"; -function OwnerRestaurantRegistrationForm() { - const [form, setForm] = useState({ name: "", category: "", address: "", hours: "", description: "", menu: "" }); +function OwnerRestaurantRegistrationForm({ onSuccess }) { + const initialForm = { name: "", category: "", address: "", hours: "", description: "", menu: "", latitude: "", longitude: "" }; + const [form, setForm] = useState(initialForm); const [imageDataUrl, setImageDataUrl] = useState(""); // full data URL with prefix const [imagePreview, setImagePreview] = useState(null); const { token } = useContext(AuthContext); + const { addRestaurant } = useContext(RestaurantsContext); function handleChange(e) { setForm({ ...form, [e.target.name]: e.target.value }); @@ -43,6 +45,7 @@ function OwnerRestaurantRegistrationForm() { reader.readAsDataURL(file); }; + async function handleSubmit(e) { e.preventDefault(); try { @@ -53,6 +56,10 @@ function OwnerRestaurantRegistrationForm() { hours: form.hours, description: form.description, menu: form.menu ? form.menu.split(",").map(item => item.trim()) : [], + location: { + lat: form.latitude, + lng: form.longitude + }, images: imageDataUrl ? [imageDataUrl] : [] // send as an array with full data URL prefix }; @@ -65,9 +72,18 @@ function OwnerRestaurantRegistrationForm() { }); if (res.status >= 200 && res.status < 300) { + const created = res.data; alert("Restaurant created successfully!"); + + // notify parent (OwnerDashboard) if provided, otherwise add to global context + if (typeof onSuccess === 'function') { + onSuccess(created); + } else if (typeof addRestaurant === 'function') { + addRestaurant(created); + } + // reset form - setForm({ name: "", category: "", address: "", hours: "", description: "", menu: "", price: "" }); + setForm(initialForm); setImageDataUrl(""); setImagePreview(null); } else { @@ -87,7 +103,8 @@ function OwnerRestaurantRegistrationForm() { - + +
diff --git a/client/src/context/AuthContext.jsx b/client/src/context/AuthContext.jsx index 6baf26d..9def606 100755 --- a/client/src/context/AuthContext.jsx +++ b/client/src/context/AuthContext.jsx @@ -88,7 +88,7 @@ export function AuthProvider({ children }) { const isOwner = () => user?.role === "restaurant_owner" || !!user?.managedRestaurantId; return ( - + {children} ); diff --git a/client/src/context/RestaurantsContext.jsx b/client/src/context/RestaurantsContext.jsx index 844d8db..02e3e41 100755 --- a/client/src/context/RestaurantsContext.jsx +++ b/client/src/context/RestaurantsContext.jsx @@ -105,18 +105,21 @@ export function RestaurantsProvider({ children }) { } async function deleteRestaurant(restaurantId) { - // remove from DB - axios.delete(`http://localhost:3000/api/restaurants/${restaurantId}`, { + // remove from DB and return the promise so callers can react + return axios.delete(`http://localhost:3000/api/restaurants/${restaurantId}`, { headers: { Authorization: `Bearer ${token}` } }) .then(() => { // remove from local state - setRestaurants((prev) => prev.filter(r => r._id != restaurantId)); - alert('Your restaurant is removed from our system') + setRestaurants((prev) => prev.filter((r) => r._id !== restaurantId)); + alert('Your restaurant is removed from our system'); }) - .catch((err) => console.error("Error deleting review", err)); + .catch((err) => { + console.error("Error deleting restaurant", err); + throw err; + }); } function updateRestaurantImage(restaurantId, dataUrl) { diff --git a/client/src/main.jsx b/client/src/main.jsx index 1cf2c5a..094deea 100755 --- a/client/src/main.jsx +++ b/client/src/main.jsx @@ -1,8 +1,6 @@ import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App"; -import 'bootstrap/dist/css/bootstrap.min.css'; -import 'bootstrap/dist/js/bootstrap.bundle.min.js'; ReactDOM.createRoot(document.getElementById("root")).render( diff --git a/client/src/pages/OwnerDashboard.jsx b/client/src/pages/OwnerDashboard.jsx index c685168..ecc04f1 100644 --- a/client/src/pages/OwnerDashboard.jsx +++ b/client/src/pages/OwnerDashboard.jsx @@ -8,7 +8,7 @@ import axios from "axios"; function OwnerDashboard() { const { user, token } = useContext(AuthContext); - const { setRestaurants } = useContext(RestaurantsContext); // update global context + const { addRestaurant } = useContext(RestaurantsContext); // update global context via addRestaurant const [ownerRestaurants, setOwnerRestaurants] = useState([]); // local state for this dashboard const [showForm, setShowForm] = useState(false); const navigate = useNavigate(); @@ -46,10 +46,10 @@ function OwnerDashboard() { { - setOwnerRestaurants([...ownerRestaurants, newRestaurant]); // update local state - setRestaurants((prev) => [...prev, newRestaurant]); // update global context + setOwnerRestaurants((prev) => [...prev, newRestaurant]); // update local state + addRestaurant(newRestaurant); // update global context setShowForm(false); // hide form - navigate(`/owner/restaurants/${newRestaurant._id}/edit`); + // stay on the dashboard — no redirect }} /> )} @@ -61,7 +61,12 @@ function OwnerDashboard() { ) : (
{ownerRestaurants.map((r) => ( - + setOwnerRestaurants((prev) => prev.filter((rr) => rr._id !== id))} + /> ))}
)} diff --git a/client/src/pages/RestaurantDetailsPage.jsx b/client/src/pages/RestaurantDetailsPage.jsx index 615a5ea..e68e354 100755 --- a/client/src/pages/RestaurantDetailsPage.jsx +++ b/client/src/pages/RestaurantDetailsPage.jsx @@ -37,17 +37,19 @@ function RestaurantDetailsPage() { + {user.role == 'user' + && (
+

Reviews

+ {r.reviews && r.reviews.length === 0 &&
No reviews yet—be the first.
} +
+ {r.reviews.map((rev) => )} +
+
+ +
+
) + } -
-

Reviews

- {r.reviews && r.reviews.length === 0 &&
No reviews yet—be the first.
} -
- {r.reviews.map((rev) => )} -
-
- -
-