Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/hooks/dashboard/useMenuItems.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ export function useMenuCategories() {
return useQuery({ queryKey: menuKeys.categories(), queryFn: getMenuCategories });
}

export function useMenuItems(filters = {}) {
export function useMenuItems(filters = {}, options = {}) {
return useQuery({
queryKey: menuKeys.items(filters),
queryFn: () => getMenuItems(filters),
...options,
});
}

Expand Down
25 changes: 12 additions & 13 deletions src/hooks/useRestaurantInit.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
import { useEffect } from "react";
import { useRestaurantStore } from "../store";
import { useQueryClient } from "@tanstack/react-query";
import { menuKeys } from "./dashboard/useMenuItems";
import { getMenuItems } from "../services/dashboardService";

/**
* Hook to initialize meal data on app load.
* Hook to prefetch meal data on app load via React Query.
* Call this in App.jsx on mount.
*
* Single-restaurant system — only fetches meals, no restaurant selection.
* Mock now → real API later (no changes needed here).
*/
export const useRestaurantInit = () => {
const { meals, fetchMeals } = useRestaurantStore();
const queryClient = useQueryClient();

useEffect(() => {
// Only fetch if store is empty
if (meals.length === 0) {
console.log("🍽️ Fetching menu...");
fetchMeals();
}
}, []);
useEffect(() => {
queryClient.prefetchQuery({
queryKey: menuKeys.items({}),
queryFn: () => getMenuItems({}),
});
}, [queryClient]);
};

11 changes: 3 additions & 8 deletions src/pages/Home/Sections/PopularMenus.jsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
import { useCallback, useEffect, useRef, useState } from "react";
import useRestaurantStore from "../../../store/restaurantStore";
import { useMenuItems } from "../../../hooks/dashboard/useMenuItems";
import PopularMenuCard from "../../../components/UI/PopularMenuCard";
import LoadingSpinner from "../../../components/UI/LoadingSpinner";
import ScrollArrows from "../../../components/UI/ScrollArrows";

const SCROLL_AMOUNT = 340; // px per button click

const PopularMenus = () => {
const { meals, fetchMeals, loading, error } = useRestaurantStore();
const { data: meals = [], isLoading: loading, error } = useMenuItems({});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Rendering the raw error object will crash the component.

error from useMenuItems({}) is an Error/AxiosError instance, not a string. Passing it directly as a JSX child (<p>{error}</p>) throws "Objects are not valid as a React child" and crashes the section on any fetch failure. RegularFood.jsx and OffersSection.jsx correctly derive a string via error.message — this file wasn't updated to match.

🐛 Proposed fix
   if (loading) return <LoadingSpinner />;
-  if (error) return <p>{error}</p>;
+  if (error) return <p>{error.message || "Error loading popular meals"}</p>;

Also applies to: 41-42

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/Home/Sections/PopularMenus.jsx` at line 10, The PopularMenus
section is rendering the raw error object from useMenuItems({}), which can crash
React because it is not a valid child. Update PopularMenus.jsx to follow the
same pattern used in RegularFood.jsx and OffersSection.jsx by deriving a string
from error.message before rendering. Keep the loading/data flow in the same
component, but ensure any error UI only receives a string fallback when message
is unavailable.

const scrollRef = useRef(null);
const [canScrollLeft, setCanScrollLeft] = useState(false);
const [canScrollRight, setCanScrollRight] = useState(true);

useEffect(() => {
if (meals.length === 0) {
fetchMeals();
}
}, [meals.length, fetchMeals]);

// Track scroll position so buttons hide/show at the edges
const updateScrollState = useCallback(() => {
Expand Down Expand Up @@ -44,7 +39,7 @@ const PopularMenus = () => {
}, []);

if (loading) return <LoadingSpinner />;
if (error) return <p>{error}</p>;
if (error) return <p>{error.message || "Failed to load popular meals"}</p>;

const popular = meals.slice(0, 6);

Expand Down
18 changes: 8 additions & 10 deletions src/pages/Home/Sections/RegularFood.jsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
// src/pages/Home/Sections/RegularFood.jsx
import { useEffect } from "react";
import useRestaurantStore from "../../../store/restaurantStore";
import { useMenuItems } from "../../../hooks/dashboard/useMenuItems";
import RegularFoodCard from "../../../components/UI/RegularFoodCard";
import LoadingSpinner from "../../../components/UI/LoadingSpinner";

const RegularFood = ({ items }) => {
const { meals, fetchMeals, loading, error } = useRestaurantStore();

useEffect(() => {
if (!items && meals.length === 0) fetchMeals(); // ✅ بس لو مفيش items جاي من برا
}, [meals.length, fetchMeals, items]);
const { data: meals = [], isLoading: loading, error } = useMenuItems({}, {
enabled: !items, // Don't fetch if items are passed via prop
});
Comment on lines +7 to +9

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== files ==\n'
git ls-files 'src/pages/Home/Sections/RegularFood.jsx' 'src/hooks/dashboard/useMenuItems.js' 'src/pages/**/Menu.jsx'

printf '\n== outlines ==\n'
for f in src/pages/Home/Sections/RegularFood.jsx src/hooks/dashboard/useMenuItems.js; do
  echo "--- $f"
  ast-grep outline "$f" --view expanded || true
done

printf '\n== search useMenuItems usages ==\n'
rg -n "useMenuItems\\(" src --glob '!**/node_modules/**' || true

printf '\n== relevant file excerpts ==\n'
for f in src/pages/Home/Sections/RegularFood.jsx src/hooks/dashboard/useMenuItems.js; do
  echo "--- $f"
  nl -ba "$f" | sed -n '1,220p'
done

Repository: Revive-Graduation-Project/Revive-Front-End

Length of output: 1839


🏁 Script executed:

#!/bin/bash
set -euo pipefail

for f in src/pages/Home/Sections/RegularFood.jsx src/hooks/dashboard/useMenuItems.js src/pages/Menu/Menu.jsx; do
  echo "=== $f ==="
  sed -n '1,220p' "$f" | cat -n
  echo
done

Repository: Revive-Graduation-Project/Revive-Front-End

Length of output: 14521


Pass useQuery options through useMenuItems

RegularFood passes { enabled: !items }, but useMenuItems(filters = {}) drops that second argument, so the query still runs when items is already provided. Thread an options param through the hook, or remove the extra argument, to avoid the redundant fetch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/Home/Sections/RegularFood.jsx` around lines 7 - 9, The RegularFood
component is passing a `useQuery` options object to `useMenuItems`, but the hook
currently only accepts `filters` and ignores the second argument, so the query
still runs unnecessarily. Update `useMenuItems` to accept and forward an
`options` parameter to the underlying query call, or remove the extra argument
from `RegularFood` if it should never control query behavior. Use the
`useMenuItems` hook and `RegularFood` usage to locate the change.


if (loading && !items) return <LoadingSpinner />;
if (error && !items) return <p>{error}</p>;
if (error && !items) return <p>{error.message || "Error loading food items"}</p>;

const displayMeals = items ?? meals;

const displayMeals = items ?? meals; // ✅ لو في items استخدمها، لو لأ استخدم meals

return (
<section id="regular-food" className="py-12 md:py-16 lg:py-20 bg-gray-50">
<div className="container mx-auto px-4 md:px-6 lg:px-8">
<div className="text-center mb-10 md:mb-12">
<h2 className="text-3xl md:text-4xl lg:text-5xl font-bold text-(--color-green) mb-3">
<h2 className="text-3xl md:text-4xl lg:text-5xl font-bold text-green mb-3">
OUR REGULAR FOOD
</h2>
<p className="text-gray-600 text-lg md:text-xl mb-2">
Expand Down
125 changes: 59 additions & 66 deletions src/pages/Menu/Menu.jsx
Original file line number Diff line number Diff line change
@@ -1,82 +1,75 @@
import {
useMenuStore,
useAuthStore,
useRecommendationStore,
useMenuStore,
useAuthStore,
useRecommendationStore,
} from "../../store";
import useRestaurantStore from "../../store/restaurantStore";
import { useMenuItems } from "../../hooks/dashboard/useMenuItems";
import { useMemo, useEffect } from "react";
import MenuFilter from "./Sections/MenuFilter";
import OffersSection from "./Sections/OffersSection";
import SuggestedMealsSection from "./Sections/SuggestedMealsSection";
import RegularFood from "../Home/Sections/RegularFood";

export default function Menu() {
const { user } = useAuthStore();
const {
recommendations,
fetchRecommendations,
} = useRecommendationStore();
const {
meals,
fetchMeals,
loading: mealsLoading,
error: mealsError,
} = useRestaurantStore();
const { selectedCategory } = useMenuStore();
const { user } = useAuthStore();
const {
recommendations,
fetchRecommendations,
} = useRecommendationStore();
const { data: meals = [], isLoading: mealsLoading, error: mealsErrorObj } = useMenuItems({});
const mealsError = mealsErrorObj ? (mealsErrorObj.message || "Failed to load meals") : null;
const { selectedCategory } = useMenuStore();

const isGuest = !user;
const isGuest = !user;

useEffect(() => {
// Always fetch meals for the regular menu
if (meals.length === 0) {
fetchMeals();
}
// If user is logged in, also fetch personalized recommendations
if (!isGuest) {
fetchRecommendations(user?.id);
}
}, [user, isGuest, meals.length, fetchMeals, fetchRecommendations]);
useEffect(() => {
// If user is logged in, fetch personalized recommendations
if (!isGuest) {
fetchRecommendations(user?.id);
}
}, [user, isGuest, fetchRecommendations]);

const filteredMeals = useMemo(() => {
return meals.filter((item) => {
return selectedCategory === "All" || item.category === selectedCategory;
});
}, [selectedCategory, meals]);

const filteredRecommendations = useMemo(() => {
return (recommendations || []).filter((item) => {
return selectedCategory === "All" || item.category === selectedCategory;
});
}, [selectedCategory, recommendations]);
const filteredMeals = useMemo(() => {
return meals.filter((item) => {
return selectedCategory === "All" || item.category === selectedCategory;
});
}, [selectedCategory, meals]);

return (
<div className="bg-white min-h-screen px-4 md:px-10 lg:px-20 overflow-hidden">
<div className="py-12 md:py-16 lg:py-20 space-y-5 md:space-y-2 lg:space-y-2">
<MenuFilter />
<OffersSection />

{/* Suggested Meals Section (Displayed for logged-in users when recommendations exist) */}
{!isGuest && (
<SuggestedMealsSection items={filteredRecommendations} />
)}
const filteredRecommendations = useMemo(() => {
return (recommendations || []).filter((item) => {
return selectedCategory === "All" || item.category === selectedCategory;
});
}, [selectedCategory, recommendations]);

{/* Regular Food Section (Always displayed for everyone) */}
{mealsLoading && meals.length === 0 ? (
<div className="flex justify-center items-center h-64">
<p className="text-lg font-medium text-gray-600">Loading menu...</p>
</div>
) : mealsError && meals.length === 0 ? (
<div className="flex justify-center items-center h-64">
<p className="text-lg font-medium text-red-500">{mealsError}</p>
</div>
) : filteredMeals.length === 0 ? (
<div className="flex justify-center items-center h-64">
<p className="text-lg font-medium text-gray-500">No meals available in this category</p>
</div>
) : (
<RegularFood items={filteredMeals} />
)}
</div>
</div>
);
return (
<div className="bg-white min-h-screen px-4 md:px-10 lg:px-20 overflow-hidden">
<div className="py-12 md:py-16 lg:py-20 space-y-5 md:space-y-2 lg:space-y-2">
<MenuFilter />
<OffersSection />

{/* Suggested Meals Section (Displayed for logged-in users when recommendations exist) */}
{!isGuest && (
<SuggestedMealsSection items={filteredRecommendations} />
)}

{/* Regular Food Section (Always displayed for everyone) */}
{mealsLoading && meals.length === 0 ? (
<div className="flex justify-center items-center h-64">
<p className="text-lg font-medium text-gray-600">Loading menu...</p>
</div>
) : mealsError && meals.length === 0 ? (
<div className="flex justify-center items-center h-64">
<p className="text-lg font-medium text-red-500">{mealsError}</p>
</div>
) : filteredMeals.length === 0 ? (
<div className="flex justify-center items-center h-64">
<p className="text-lg font-medium text-gray-500">No meals available in this category</p>
</div>
) : (
<RegularFood items={filteredMeals} />
)}
</div>
</div>
);
}
32 changes: 11 additions & 21 deletions src/pages/Menu/Sections/OffersSection.jsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,18 @@
import { useState, useEffect } from "react";
import { useMenuItems } from "../../../hooks/dashboard/useMenuItems";
import RegularFoodCard from "../../../components/UI/RegularFoodCard";
import { getMenu } from "../../../services/menu.service";
import ScrollArrows from "../../../components/UI/ScrollArrows";

const OffersSection = () => {
const [offersMeals, setOffersMeals] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const { data: offersMeals = [], isLoading: loading, error: queryError } = useMenuItems({ hasDiscount: true });
const error = queryError ? (queryError.message || "Failed to load offers") : null;
const [current, setCurrent] = useState(0);
Comment on lines 6 to 9

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the target file and neighboring components.
printf '\n## OffersSection outline\n'
ast-grep outline src/pages/Menu/Sections/OffersSection.jsx --view expanded || true

printf '\n## RegularFoodCard candidates\n'
fd -a 'RegularFoodCard' src || true

printf '\n## Search for useMenuItems definition/usages\n'
rg -n "useMenuItems|RegularFoodCard|current" src/pages src -g '!**/node_modules/**' || true

Repository: Revive-Graduation-Project/Revive-Front-End

Length of output: 24722


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the relevant section of OffersSection.jsx with line numbers.
sed -n '1,220p' src/pages/Menu/Sections/OffersSection.jsx | cat -n

# Find the RegularFoodCard implementation and inspect it.
fd -a 'RegularFoodCard.jsx' src || true
fd -a 'RegularFoodCard.*' src || true

Repository: Revive-Graduation-Project/Revive-Front-End

Length of output: 5208


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## OffersSection outline\n'
ast-grep outline src/pages/Menu/Sections/OffersSection.jsx --view expanded || true

printf '\n## OffersSection lines\n'
sed -n '1,220p' src/pages/Menu/Sections/OffersSection.jsx | cat -n

printf '\n## RegularFoodCard candidates\n'
fd -a 'RegularFoodCard.*' src || true

printf '\n## useMenuItems definition/usages\n'
rg -n "useMenuItems|RegularFoodCard|offersMeals\\[" src -g '!**/node_modules/**' || true

Repository: Revive-Graduation-Project/Revive-Front-End

Length of output: 8477


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## useMenuItems hook\n'
sed -n '1,220p' src/hooks/dashboard/useMenuItems.js | cat -n

printf '\n## RegularFoodCard.jsx\n'
sed -n '1,240p' src/components/ui/RegularFoodCard.jsx | cat -n

Repository: Revive-Graduation-Project/Revive-Front-End

Length of output: 16104


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "setCurrent\\(|currentIndex|offersMeals\\[current\\]|offersMeals\\.length" src/pages src/components -g '!**/node_modules/**' || true

Repository: Revive-Graduation-Project/Revive-Front-End

Length of output: 1277


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## useMenuItems hook\n'
sed -n '1,220p' src/hooks/dashboard/useMenuItems.js | cat -n

printf '\n## RegularFoodCard.jsx\n'
sed -n '1,240p' src/components/ui/RegularFoodCard.jsx | cat -n

printf '\n## Similar index-state patterns\n'
rg -n "setCurrent\\(|currentIndex|offersMeals\\[current\\]|offersMeals\\.length" src/pages src/components -g '!**/node_modules/**' || true

Repository: Revive-Graduation-Project/Revive-Front-End

Length of output: 17234


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,220p' src/hooks/dashboard/useMenuItems.js | cat -n
printf '\n---\n'
sed -n '1,240p' src/components/ui/RegularFoodCard.jsx | cat -n

Repository: Revive-Graduation-Project/Revive-Front-End

Length of output: 16063


Clamp current when offersMeals changes. A refetch can shrink the discounts list while current still points past the end; RegularFoodCard dereferences meal.id immediately, so offersMeals[current] being undefined will crash the carousel. Reset or modulo the index on offersMeals.length changes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/Menu/Sections/OffersSection.jsx` around lines 6 - 9, Clamp the
OffersSection carousel index when the offers list changes, because
useMenuItems({ hasDiscount: true }) can refetch to a shorter offersMeals array
while current still points past the end and RegularFoodCard will read meal.id
from undefined. Update the OffersSection state logic to reset or modulo current
whenever offersMeals.length changes so the selected item always stays within
bounds.


useEffect(() => {
const fetchOffers = async () => {
setLoading(true);
setError(null);
try {
const res = await getMenu(true); // hasDiscount = true
setOffersMeals(res.data);
} catch (err) {
setError(err.message || "Failed to load offers");
} finally {
setLoading(false);
}
};
fetchOffers();
}, []);
if (offersMeals.length > 0 && current >= offersMeals.length) {
setCurrent(current % offersMeals.length);
}
}, [offersMeals.length, current]);

if (loading) {
return (
Expand Down Expand Up @@ -62,8 +51,9 @@ const OffersSection = () => {
);
}

const prevIndex = (current - 1 + offersMeals.length) % offersMeals.length;
const nextIndex = (current + 1) % offersMeals.length;
const safeCurrent = current % offersMeals.length;
const prevIndex = (safeCurrent - 1 + offersMeals.length) % offersMeals.length;
const nextIndex = (safeCurrent + 1) % offersMeals.length;

const goPrev = () => setCurrent(prevIndex);
const goNext = () => setCurrent(nextIndex);
Expand Down Expand Up @@ -112,7 +102,7 @@ const OffersSection = () => {
<RegularFoodCard meal={offersMeals[prevIndex]} />
</div>
<div className="w-95 lg:w-105 scale-105 opacity-100 -translate-y-4 transition-all duration-300">
<RegularFoodCard meal={offersMeals[current]} />
<RegularFoodCard meal={offersMeals[safeCurrent]} />
</div>
<div className="w-85 lg:w-90 scale-95 opacity-80 translate-y-6 transition-all duration-300">
<RegularFoodCard meal={offersMeals[nextIndex]} />
Expand Down
Loading