From a3b01405c99b09d9516e92857e30457a1ef9202a Mon Sep 17 00:00:00 2001 From: Amit Oved Date: Mon, 29 Jun 2026 10:32:18 +0300 Subject: [PATCH 1/9] feat(ux): make homepage signature dish cards navigate to restaurant highlight --- apps/frontend/src/app/page.tsx | 14 ++++++++++++-- .../src/components/DesktopSignatureDishes.tsx | 10 +++++++++- apps/frontend/src/lib/text.ts | 1 + .../src/styles/components/_dish-card.scss | 9 +++++++++ .../src/styles/components/_mobile-section.scss | 15 +++++++++++++++ apps/frontend/src/styles/pages/_home.scss | 9 ++++++--- libs/shared-types/src/lib/strapi.types.ts | 1 + 7 files changed, 53 insertions(+), 6 deletions(-) diff --git a/apps/frontend/src/app/page.tsx b/apps/frontend/src/app/page.tsx index a1f2336..4b3368e 100644 --- a/apps/frontend/src/app/page.tsx +++ b/apps/frontend/src/app/page.tsx @@ -1,3 +1,4 @@ +import Link from 'next/link'; import { MobileSection } from '../components/MobileSection'; import { MobileChefSection } from '../components/MobileChefSection'; import { MobileAppSection } from '../components/MobileAppSection'; @@ -29,7 +30,9 @@ export default async function HomePage() { const signatureDishes = [...new Map( restaurants.flatMap(r => - (r.dishes ?? []).filter(d => d.isSignatureDish).map(d => [d.id, { ...d, restaurantName: r.name }]) + (r.dishes ?? []) + .filter(d => d.isSignatureDish) + .map(d => [d.id, { ...d, restaurantName: r.name, restaurantId: r.id }]) ) ).values()]; @@ -56,7 +59,14 @@ export default async function HomePage() { linkHref="/restaurants" > {signatureDishes.map(dish => ( - + + + ))} )} diff --git a/apps/frontend/src/components/DesktopSignatureDishes.tsx b/apps/frontend/src/components/DesktopSignatureDishes.tsx index af49e5b..5e0f337 100644 --- a/apps/frontend/src/components/DesktopSignatureDishes.tsx +++ b/apps/frontend/src/components/DesktopSignatureDishes.tsx @@ -1,6 +1,7 @@ 'use client'; import { useRef } from 'react'; +import Link from 'next/link'; import { DishCard } from '@org/ui-components'; import type { Dish } from '@org/shared-types'; import { strapiImageUrl } from '../lib/api'; @@ -25,7 +26,14 @@ export function DesktopSignatureDishes({ dishes }: Props) {

{TEXT.home.signatureDishTitle}

{dishes.map(dish => ( - + + + ))}
diff --git a/apps/frontend/src/lib/text.ts b/apps/frontend/src/lib/text.ts index 760c2c5..cd43330 100644 --- a/apps/frontend/src/lib/text.ts +++ b/apps/frontend/src/lib/text.ts @@ -86,6 +86,7 @@ export const TEXT = { signatureDishTitle: 'SIGNATURE DISH OF:', iconsTitle: 'THE MEANING OF OUR ICONS:', allRestaurantsLink: 'All Restaurants', + dishLinkAriaLabel: (name: string) => `View ${name}'s restaurant`, }, icons: [ { src: '/icons/spicy.svg', label: 'Spicy' }, diff --git a/apps/frontend/src/styles/components/_dish-card.scss b/apps/frontend/src/styles/components/_dish-card.scss index 5bef853..1f1bdcc 100644 --- a/apps/frontend/src/styles/components/_dish-card.scss +++ b/apps/frontend/src/styles/components/_dish-card.scss @@ -83,6 +83,15 @@ } } +.epicure-dish-link { + display: block; + text-decoration: none; + color: inherit; + cursor: pointer; + + &:hover { opacity: 0.92; } +} + @include for-normal-layout { .epicure-dish-card { display: flex; diff --git a/apps/frontend/src/styles/components/_mobile-section.scss b/apps/frontend/src/styles/components/_mobile-section.scss index 2eaea0f..6179150 100644 --- a/apps/frontend/src/styles/components/_mobile-section.scss +++ b/apps/frontend/src/styles/components/_mobile-section.scss @@ -54,6 +54,21 @@ opacity: 1; } } + + .epicure-dish-link { + flex: 0 0 245px; + height: 388px; + display: block; + text-decoration: none; + color: inherit; + scroll-snap-align: start; + + .epicure-dish-card { + height: 100%; + flex: none; + scroll-snap-align: none; + } + } } &__link { diff --git a/apps/frontend/src/styles/pages/_home.scss b/apps/frontend/src/styles/pages/_home.scss index 0b73396..3e286c9 100644 --- a/apps/frontend/src/styles/pages/_home.scss +++ b/apps/frontend/src/styles/pages/_home.scss @@ -6,7 +6,8 @@ .epicure-desktop-popular, .epicure-desktop-signature { .epicure-restaurant-card, - .epicure-dish-card { + .epicure-dish-card, + .epicure-dish-link { opacity: 0; transform: translateY(16px); transition: opacity $duration-slow $ease-out, transform $duration-slow $ease-out; @@ -14,7 +15,8 @@ &--visible { .epicure-restaurant-card, - .epicure-dish-card { + .epicure-dish-card, + .epicure-dish-link { opacity: 1; transform: none; @@ -44,7 +46,8 @@ .epicure-desktop-signature, .epicure-desktop-chef { .epicure-restaurant-card, - .epicure-dish-card { transition: none; opacity: 1; transform: none; } + .epicure-dish-card, + .epicure-dish-link { transition: none; opacity: 1; transform: none; } } } } diff --git a/libs/shared-types/src/lib/strapi.types.ts b/libs/shared-types/src/lib/strapi.types.ts index 94591df..f7bf272 100644 --- a/libs/shared-types/src/lib/strapi.types.ts +++ b/libs/shared-types/src/lib/strapi.types.ts @@ -36,6 +36,7 @@ export interface Dish { image?: StrapiImage; mealTime?: 'Breakfast' | 'Lunch' | 'Dinner'; isSignatureDish?: boolean; + restaurantId?: number; restaurantName?: string; sides?: string[]; changes?: string[]; From 183b73f648d0659962bb4cc782a35aed42f833f8 Mon Sep 17 00:00:00 2001 From: Amit Oved Date: Mon, 29 Jun 2026 10:38:50 +0300 Subject: [PATCH 2/9] fix(ux): remove double scroll-fade on dish cards; guard undefined restaurantId --- apps/frontend/src/app/page.tsx | 20 ++++++---- .../src/components/DesktopSignatureDishes.tsx | 20 ++++++---- apps/frontend/src/styles/pages/_home.scss | 38 ++++++++++++++----- 3 files changed, 53 insertions(+), 25 deletions(-) diff --git a/apps/frontend/src/app/page.tsx b/apps/frontend/src/app/page.tsx index 4b3368e..67c09ed 100644 --- a/apps/frontend/src/app/page.tsx +++ b/apps/frontend/src/app/page.tsx @@ -59,14 +59,18 @@ export default async function HomePage() { linkHref="/restaurants" > {signatureDishes.map(dish => ( - - - + dish.restaurantId != null ? ( + + + + ) : ( + + ) ))} )} diff --git a/apps/frontend/src/components/DesktopSignatureDishes.tsx b/apps/frontend/src/components/DesktopSignatureDishes.tsx index 5e0f337..78dc040 100644 --- a/apps/frontend/src/components/DesktopSignatureDishes.tsx +++ b/apps/frontend/src/components/DesktopSignatureDishes.tsx @@ -26,14 +26,18 @@ export function DesktopSignatureDishes({ dishes }: Props) {

{TEXT.home.signatureDishTitle}

{dishes.map(dish => ( - - - + dish.restaurantId != null ? ( + + + + ) : ( + + ) ))}
diff --git a/apps/frontend/src/styles/pages/_home.scss b/apps/frontend/src/styles/pages/_home.scss index 3e286c9..a6bc7bc 100644 --- a/apps/frontend/src/styles/pages/_home.scss +++ b/apps/frontend/src/styles/pages/_home.scss @@ -3,11 +3,9 @@ // ---- Scroll fade-in — desktop homepage sections ---- @include for-normal-layout { - .epicure-desktop-popular, - .epicure-desktop-signature { + .epicure-desktop-popular { .epicure-restaurant-card, - .epicure-dish-card, - .epicure-dish-link { + .epicure-dish-card { opacity: 0; transform: translateY(16px); transition: opacity $duration-slow $ease-out, transform $duration-slow $ease-out; @@ -15,7 +13,24 @@ &--visible { .epicure-restaurant-card, - .epicure-dish-card, + .epicure-dish-card { + opacity: 1; + transform: none; + + &:nth-child(2) { transition-delay: 60ms; } + &:nth-child(3) { transition-delay: 120ms; } + } + } + } + + .epicure-desktop-signature { + .epicure-dish-link { + opacity: 0; + transform: translateY(16px); + transition: opacity $duration-slow $ease-out, transform $duration-slow $ease-out; + } + + &--visible { .epicure-dish-link { opacity: 1; transform: none; @@ -42,13 +57,18 @@ } @include reduced-motion { - .epicure-desktop-popular, - .epicure-desktop-signature, - .epicure-desktop-chef { + .epicure-desktop-popular { .epicure-restaurant-card, - .epicure-dish-card, + .epicure-dish-card { transition: none; opacity: 1; transform: none; } + } + + .epicure-desktop-signature { .epicure-dish-link { transition: none; opacity: 1; transform: none; } } + + .epicure-desktop-chef { + .epicure-restaurant-card { transition: none; opacity: 1; transform: none; } + } } } From 325bacf77606f1ac3aa266ab0dd3257967872ef4 Mon Sep 17 00:00:00 2001 From: Amit Oved Date: Mon, 29 Jun 2026 10:43:13 +0300 Subject: [PATCH 3/9] feat(ux): highlight and auto-open dish modal via sessionStorage --- apps/frontend/src/app/page.tsx | 27 ++----------- .../src/components/DesktopSignatureDishes.tsx | 3 +- apps/frontend/src/components/DishGrid.tsx | 27 ++++++++++++- .../src/components/MobileSignatureDishes.tsx | 40 +++++++++++++++++++ apps/frontend/src/lib/events.ts | 1 + 5 files changed, 72 insertions(+), 26 deletions(-) create mode 100644 apps/frontend/src/components/MobileSignatureDishes.tsx diff --git a/apps/frontend/src/app/page.tsx b/apps/frontend/src/app/page.tsx index 67c09ed..e223ffc 100644 --- a/apps/frontend/src/app/page.tsx +++ b/apps/frontend/src/app/page.tsx @@ -1,7 +1,7 @@ -import Link from 'next/link'; import { MobileSection } from '../components/MobileSection'; import { MobileChefSection } from '../components/MobileChefSection'; import { MobileAppSection } from '../components/MobileAppSection'; +import { MobileSignatureDishes } from '../components/MobileSignatureDishes'; import { IconsLegend } from '../components/IconsLegend'; import { Hero } from '../components/Hero'; import { DesktopPopularRestaurants } from '../components/DesktopPopularRestaurants'; @@ -10,7 +10,7 @@ import { DesktopChefSection } from '../components/DesktopChefSection'; import { DesktopAboutSection } from '../components/DesktopAboutSection'; import { fetchApi, strapiImageUrl } from '../lib/api'; import type { Restaurant, Chef } from '@org/shared-types'; -import { RestaurantCard, DishCard } from '@org/ui-components'; +import { RestaurantCard } from '@org/ui-components'; import { TEXT } from '../lib/text'; export default async function HomePage() { @@ -52,28 +52,7 @@ export default async function HomePage() { - {signatureDishes.length > 0 && ( - - {signatureDishes.map(dish => ( - dish.restaurantId != null ? ( - - - - ) : ( - - ) - ))} - - )} + diff --git a/apps/frontend/src/components/DesktopSignatureDishes.tsx b/apps/frontend/src/components/DesktopSignatureDishes.tsx index 78dc040..142a077 100644 --- a/apps/frontend/src/components/DesktopSignatureDishes.tsx +++ b/apps/frontend/src/components/DesktopSignatureDishes.tsx @@ -29,9 +29,10 @@ export function DesktopSignatureDishes({ dishes }: Props) { dish.restaurantId != null ? ( sessionStorage.setItem('pending_dish_highlight', String(dish.id))} > diff --git a/apps/frontend/src/components/DishGrid.tsx b/apps/frontend/src/components/DishGrid.tsx index 7339969..2d48fa2 100644 --- a/apps/frontend/src/components/DishGrid.tsx +++ b/apps/frontend/src/components/DishGrid.tsx @@ -1,12 +1,13 @@ 'use client'; -import { useState, useRef } from 'react'; +import { useState, useEffect, useRef } from 'react'; import type { Dish } from '@org/shared-types'; import { DishCard } from '@org/ui-components'; import { strapiImageUrl } from '../lib/api'; import { TEXT } from '../lib/text'; import { useTabIndicator } from '../hooks/useTabIndicator'; import { DishModal } from './DishModal'; +import { PENDING_DISH_KEY } from '../lib/events'; type MealTime = (typeof TEXT.dishGrid.tabs)[number]; @@ -23,6 +24,29 @@ export function DishGrid({ dishes, restaurantId, restaurantName }: DishGridProps const [selectedDish, setSelectedDish] = useState(null); const [selectedImageUrl, setSelectedImageUrl] = useState(''); + useEffect(() => { + const pending = sessionStorage.getItem(PENDING_DISH_KEY); + if (!pending) return; + sessionStorage.removeItem(PENDING_DISH_KEY); + const dishId = parseInt(pending, 10); + if (isNaN(dishId) || dishes.length === 0) return; + const dish = dishes.find(d => d.id === dishId); + if (!dish) return; + // Switch to the dish's meal tab so the card is in the DOM + setActiveTab((dish.mealTime ?? TEXT.dishGrid.tabs[0]) as MealTime); + const isMobile = window.matchMedia('(max-width: 767px)').matches; + let modalTimer: ReturnType; + const scrollTimer = setTimeout(() => { + const el = document.getElementById(`dish-${dishId}`); + if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center' }); + modalTimer = setTimeout(() => { + setSelectedDish(dish); + setSelectedImageUrl(strapiImageUrl(dish.image?.url)); + }, isMobile ? 1200 : 500); + }, 1000); + return () => { clearTimeout(scrollTimer); clearTimeout(modalTimer); }; + }, [dishes]); + const filtered = dishes.filter(d => d.mealTime?.trim() === activeTab); const tabs = [...TEXT.dishGrid.tabs] as MealTime[]; @@ -60,6 +84,7 @@ export function DishGrid({ dishes, restaurantId, restaurantName }: DishGridProps {filtered.map(dish => (
+ {dishes.map(dish => ( + dish.restaurantId != null ? ( + sessionStorage.setItem('pending_dish_highlight', String(dish.id))} + > + + + ) : ( + + ) + ))} + + ); +} diff --git a/apps/frontend/src/lib/events.ts b/apps/frontend/src/lib/events.ts index dbc8896..539e93a 100644 --- a/apps/frontend/src/lib/events.ts +++ b/apps/frontend/src/lib/events.ts @@ -2,6 +2,7 @@ export const CUISINE_FILTER_EVENT = 'epicure:cuisine-filter'; export const CHEF_HIGHLIGHT_EVENT = 'epicure:chef-highlight'; export const PENDING_CUISINE_KEY = 'epicure_pending_cuisine_filter'; export const PENDING_CHEF_KEY = 'epicure_pending_chef_highlight'; +export const PENDING_DISH_KEY = 'pending_dish_highlight'; export function dispatchCuisineFilter(labels: string[]): void { sessionStorage.setItem(PENDING_CUISINE_KEY, JSON.stringify(labels)); From f2c71df4bfb141d9dd57fe5d10e7c9ef544bd211 Mon Sep 17 00:00:00 2001 From: Amit Oved Date: Mon, 29 Jun 2026 10:48:02 +0300 Subject: [PATCH 4/9] fix(ux): import PENDING_DISH_KEY constants; fix Strict Mode sessionStorage removal --- apps/frontend/src/components/DesktopSignatureDishes.tsx | 3 ++- apps/frontend/src/components/DishGrid.tsx | 5 ++--- apps/frontend/src/components/MobileSignatureDishes.tsx | 3 ++- apps/frontend/src/lib/events.ts | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/frontend/src/components/DesktopSignatureDishes.tsx b/apps/frontend/src/components/DesktopSignatureDishes.tsx index 142a077..17fc950 100644 --- a/apps/frontend/src/components/DesktopSignatureDishes.tsx +++ b/apps/frontend/src/components/DesktopSignatureDishes.tsx @@ -7,6 +7,7 @@ import type { Dish } from '@org/shared-types'; import { strapiImageUrl } from '../lib/api'; import { TEXT } from '../lib/text'; import { useIntersectionObserver } from '../hooks/useIntersectionObserver'; +import { PENDING_DISH_KEY } from '../lib/events'; interface Props { dishes: Dish[]; @@ -32,7 +33,7 @@ export function DesktopSignatureDishes({ dishes }: Props) { href={`/restaurants/${dish.restaurantId}`} className="epicure-dish-link" aria-label={TEXT.home.dishLinkAriaLabel(dish.name)} - onClick={() => sessionStorage.setItem('pending_dish_highlight', String(dish.id))} + onClick={() => sessionStorage.setItem(PENDING_DISH_KEY, String(dish.id))} > diff --git a/apps/frontend/src/components/DishGrid.tsx b/apps/frontend/src/components/DishGrid.tsx index 2d48fa2..48f6010 100644 --- a/apps/frontend/src/components/DishGrid.tsx +++ b/apps/frontend/src/components/DishGrid.tsx @@ -27,12 +27,10 @@ export function DishGrid({ dishes, restaurantId, restaurantName }: DishGridProps useEffect(() => { const pending = sessionStorage.getItem(PENDING_DISH_KEY); if (!pending) return; - sessionStorage.removeItem(PENDING_DISH_KEY); const dishId = parseInt(pending, 10); if (isNaN(dishId) || dishes.length === 0) return; const dish = dishes.find(d => d.id === dishId); if (!dish) return; - // Switch to the dish's meal tab so the card is in the DOM setActiveTab((dish.mealTime ?? TEXT.dishGrid.tabs[0]) as MealTime); const isMobile = window.matchMedia('(max-width: 767px)').matches; let modalTimer: ReturnType; @@ -40,12 +38,13 @@ export function DishGrid({ dishes, restaurantId, restaurantName }: DishGridProps const el = document.getElementById(`dish-${dishId}`); if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center' }); modalTimer = setTimeout(() => { + sessionStorage.removeItem(PENDING_DISH_KEY); setSelectedDish(dish); setSelectedImageUrl(strapiImageUrl(dish.image?.url)); }, isMobile ? 1200 : 500); }, 1000); return () => { clearTimeout(scrollTimer); clearTimeout(modalTimer); }; - }, [dishes]); + }, []); // eslint-disable-line react-hooks/exhaustive-deps const filtered = dishes.filter(d => d.mealTime?.trim() === activeTab); const tabs = [...TEXT.dishGrid.tabs] as MealTime[]; diff --git a/apps/frontend/src/components/MobileSignatureDishes.tsx b/apps/frontend/src/components/MobileSignatureDishes.tsx index 1c0f612..2824ad0 100644 --- a/apps/frontend/src/components/MobileSignatureDishes.tsx +++ b/apps/frontend/src/components/MobileSignatureDishes.tsx @@ -6,6 +6,7 @@ import type { Dish } from '@org/shared-types'; import { strapiImageUrl } from '../lib/api'; import { TEXT } from '../lib/text'; import { MobileSection } from './MobileSection'; +import { PENDING_DISH_KEY } from '../lib/events'; interface Props { dishes: Dish[]; @@ -27,7 +28,7 @@ export function MobileSignatureDishes({ dishes }: Props) { href={`/restaurants/${dish.restaurantId}`} className="epicure-dish-link" aria-label={TEXT.home.dishLinkAriaLabel(dish.name)} - onClick={() => sessionStorage.setItem('pending_dish_highlight', String(dish.id))} + onClick={() => sessionStorage.setItem(PENDING_DISH_KEY, String(dish.id))} > diff --git a/apps/frontend/src/lib/events.ts b/apps/frontend/src/lib/events.ts index 539e93a..25c8dc5 100644 --- a/apps/frontend/src/lib/events.ts +++ b/apps/frontend/src/lib/events.ts @@ -2,7 +2,7 @@ export const CUISINE_FILTER_EVENT = 'epicure:cuisine-filter'; export const CHEF_HIGHLIGHT_EVENT = 'epicure:chef-highlight'; export const PENDING_CUISINE_KEY = 'epicure_pending_cuisine_filter'; export const PENDING_CHEF_KEY = 'epicure_pending_chef_highlight'; -export const PENDING_DISH_KEY = 'pending_dish_highlight'; +export const PENDING_DISH_KEY = 'epicure_pending_dish_highlight'; export function dispatchCuisineFilter(labels: string[]): void { sessionStorage.setItem(PENDING_CUISINE_KEY, JSON.stringify(labels)); From c754cb5ccaa932db5308794aacab1f23205075a2 Mon Sep 17 00:00:00 2001 From: Amit Oved Date: Mon, 29 Jun 2026 10:56:08 +0300 Subject: [PATCH 5/9] fix(ux): scroll to dish using reactive useEffect instead of fixed timeout --- apps/frontend/src/components/DishGrid.tsx | 31 ++++++++++++++--------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/apps/frontend/src/components/DishGrid.tsx b/apps/frontend/src/components/DishGrid.tsx index 48f6010..e84ff66 100644 --- a/apps/frontend/src/components/DishGrid.tsx +++ b/apps/frontend/src/components/DishGrid.tsx @@ -24,6 +24,8 @@ export function DishGrid({ dishes, restaurantId, restaurantName }: DishGridProps const [selectedDish, setSelectedDish] = useState(null); const [selectedImageUrl, setSelectedImageUrl] = useState(''); + const pendingScrollRef = useRef<{ dishId: number; dish: Dish } | null>(null); + useEffect(() => { const pending = sessionStorage.getItem(PENDING_DISH_KEY); if (!pending) return; @@ -31,21 +33,26 @@ export function DishGrid({ dishes, restaurantId, restaurantName }: DishGridProps if (isNaN(dishId) || dishes.length === 0) return; const dish = dishes.find(d => d.id === dishId); if (!dish) return; + pendingScrollRef.current = { dishId, dish }; setActiveTab((dish.mealTime ?? TEXT.dishGrid.tabs[0]) as MealTime); - const isMobile = window.matchMedia('(max-width: 767px)').matches; - let modalTimer: ReturnType; - const scrollTimer = setTimeout(() => { - const el = document.getElementById(`dish-${dishId}`); - if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center' }); - modalTimer = setTimeout(() => { - sessionStorage.removeItem(PENDING_DISH_KEY); - setSelectedDish(dish); - setSelectedImageUrl(strapiImageUrl(dish.image?.url)); - }, isMobile ? 1200 : 500); - }, 1000); - return () => { clearTimeout(scrollTimer); clearTimeout(modalTimer); }; }, []); // eslint-disable-line react-hooks/exhaustive-deps + useEffect(() => { + if (!pendingScrollRef.current) return; + const { dishId, dish } = pendingScrollRef.current; + const el = document.getElementById(`dish-${dishId}`); + if (!el) return; + pendingScrollRef.current = null; + el.scrollIntoView({ behavior: 'smooth', block: 'center' }); + const isMobile = window.matchMedia('(max-width: 767px)').matches; + const t = setTimeout(() => { + sessionStorage.removeItem(PENDING_DISH_KEY); + setSelectedDish(dish); + setSelectedImageUrl(strapiImageUrl(dish.image?.url)); + }, isMobile ? 1200 : 500); + return () => clearTimeout(t); + }, [activeTab]); // eslint-disable-line react-hooks/exhaustive-deps + const filtered = dishes.filter(d => d.mealTime?.trim() === activeTab); const tabs = [...TEXT.dishGrid.tabs] as MealTime[]; From 257ba2cc2fa391dfff87f68a6e93787b524fc1d0 Mon Sep 17 00:00:00 2001 From: Amit Oved Date: Mon, 29 Jun 2026 11:13:44 +0300 Subject: [PATCH 6/9] =?UTF-8?q?fix(ux):=20scroll=20to=20DishCard=20child?= =?UTF-8?q?=20=E2=80=94=20dish-grid-item=20has=20display:contents?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/frontend/src/components/DishGrid.tsx | 45 ++++++++++++----------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/apps/frontend/src/components/DishGrid.tsx b/apps/frontend/src/components/DishGrid.tsx index e84ff66..6e6cd2a 100644 --- a/apps/frontend/src/components/DishGrid.tsx +++ b/apps/frontend/src/components/DishGrid.tsx @@ -24,34 +24,37 @@ export function DishGrid({ dishes, restaurantId, restaurantName }: DishGridProps const [selectedDish, setSelectedDish] = useState(null); const [selectedImageUrl, setSelectedImageUrl] = useState(''); - const pendingScrollRef = useRef<{ dishId: number; dish: Dish } | null>(null); + const [highlightDishId, setHighlightDishId] = useState(null); useEffect(() => { const pending = sessionStorage.getItem(PENDING_DISH_KEY); if (!pending) return; const dishId = parseInt(pending, 10); - if (isNaN(dishId) || dishes.length === 0) return; - const dish = dishes.find(d => d.id === dishId); - if (!dish) return; - pendingScrollRef.current = { dishId, dish }; - setActiveTab((dish.mealTime ?? TEXT.dishGrid.tabs[0]) as MealTime); - }, []); // eslint-disable-line react-hooks/exhaustive-deps + if (isNaN(dishId)) return; + sessionStorage.removeItem(PENDING_DISH_KEY); + setHighlightDishId(dishId); + }, []); useEffect(() => { - if (!pendingScrollRef.current) return; - const { dishId, dish } = pendingScrollRef.current; - const el = document.getElementById(`dish-${dishId}`); - if (!el) return; - pendingScrollRef.current = null; - el.scrollIntoView({ behavior: 'smooth', block: 'center' }); - const isMobile = window.matchMedia('(max-width: 767px)').matches; - const t = setTimeout(() => { - sessionStorage.removeItem(PENDING_DISH_KEY); - setSelectedDish(dish); - setSelectedImageUrl(strapiImageUrl(dish.image?.url)); - }, isMobile ? 1200 : 500); - return () => clearTimeout(t); - }, [activeTab]); // eslint-disable-line react-hooks/exhaustive-deps + if (!highlightDishId || dishes.length === 0) return; + const dish = dishes.find(d => d.id === highlightDishId); + if (!dish) return; + setActiveTab((dish.mealTime ?? TEXT.dishGrid.tabs[0]) as MealTime); + let modalTimer: ReturnType; + const scrollTimer = setTimeout(() => { + const el = document.getElementById(`dish-${highlightDishId}`); + if (el) { + const target = (el.firstElementChild as HTMLElement) ?? el; + target.scrollIntoView({ behavior: 'smooth', block: 'center' }); + } + modalTimer = setTimeout(() => { + setSelectedDish(dish); + setSelectedImageUrl(strapiImageUrl(dish.image?.url)); + setHighlightDishId(null); + }, 1200); + }, 1000); + return () => { clearTimeout(scrollTimer); clearTimeout(modalTimer); }; + }, [highlightDishId, dishes]); // eslint-disable-line react-hooks/exhaustive-deps const filtered = dishes.filter(d => d.mealTime?.trim() === activeTab); const tabs = [...TEXT.dishGrid.tabs] as MealTime[]; From b9c724a072b0e6c9fec808633da435596dd0ceee Mon Sep 17 00:00:00 2001 From: Amit Oved Date: Mon, 29 Jun 2026 11:40:32 +0300 Subject: [PATCH 7/9] fix(cart): qty buttons horizontal/right, scroll lock on mobile --- apps/frontend/src/components/CartPanel.tsx | 6 +- .../src/components/cart-panel/CartItem.tsx | 14 ++--- .../src/styles/components/_cart-item.scss | 61 +++++++++---------- 3 files changed, 40 insertions(+), 41 deletions(-) diff --git a/apps/frontend/src/components/CartPanel.tsx b/apps/frontend/src/components/CartPanel.tsx index 2ff3aa0..1c2d8dc 100644 --- a/apps/frontend/src/components/CartPanel.tsx +++ b/apps/frontend/src/components/CartPanel.tsx @@ -16,7 +16,11 @@ export function CartPanel({ onClose }: CartPanelProps) { useEffect(() => { document.body.style.overflow = 'hidden'; - return () => { document.body.style.overflow = ''; }; + document.documentElement.style.overflow = 'hidden'; + return () => { + document.body.style.overflow = ''; + document.documentElement.style.overflow = ''; + }; }, []); return ( diff --git a/apps/frontend/src/components/cart-panel/CartItem.tsx b/apps/frontend/src/components/cart-panel/CartItem.tsx index fc13851..4edcef5 100644 --- a/apps/frontend/src/components/cart-panel/CartItem.tsx +++ b/apps/frontend/src/components/cart-panel/CartItem.tsx @@ -16,6 +16,13 @@ export function CartItem({ item }: CartItemProps) { {item.dish.name}
+
+

+ {item.quantity}x + {item.dish.name} +

+

₪{item.dish.price.toFixed(2)}

+
{!item.pendingRemove && (
)} -
-

- {item.quantity}x - {item.dish.name} -

-

₪{item.dish.price.toFixed(2)}

-
{(item.selectedSide || item.selectedChanges.length > 0) && (

diff --git a/apps/frontend/src/styles/components/_cart-item.scss b/apps/frontend/src/styles/components/_cart-item.scss index ffb044e..bf5a344 100644 --- a/apps/frontend/src/styles/components/_cart-item.scss +++ b/apps/frontend/src/styles/components/_cart-item.scss @@ -9,23 +9,19 @@ flex-direction: row; flex-shrink: 0; background: $cream-secondary; - height: auto; - min-height: 96px; + height: 130px; &__image { - width: 96px; - min-height: 96px; - height: 100%; + width: 130px; + height: 130px; object-fit: cover; flex-shrink: 0; - align-self: stretch; } &__body { flex: 1; min-width: 0; - height: auto; - min-height: 96px; + height: 130px; padding: 0.625rem 0.75rem; display: flex; flex-direction: column; @@ -34,7 +30,8 @@ &__top { display: flex; - align-items: flex-start; + align-items: center; + justify-content: space-between; gap: 0.375rem; margin-bottom: 0.125rem; } @@ -47,23 +44,23 @@ &__qty-row { display: flex; - flex-direction: column; + flex-direction: row; align-items: center; - gap: 2px; + gap: 4px; flex-shrink: 0; user-select: none; } &__qty-btn { - width: 24px; - height: 24px; + width: 16px; + height: 16px; border: 1px solid $black-primary; background: transparent; color: $black-primary; display: flex; align-items: center; justify-content: center; - font-size: 1rem; + font-size: 0.75rem; cursor: pointer; border-radius: 2px; padding: 0; @@ -74,11 +71,11 @@ } &__qty-count { - font-size: 0.875rem; + font-size: 0.8125rem; color: $gold-secondary; font-weight: 400; text-align: center; - min-width: 20px; + min-width: 16px; } &--confirm-remove { @@ -89,13 +86,13 @@ &__confirm-remove { display: flex; align-items: center; - gap: 0.375rem; - margin-top: 0.375rem; - flex-wrap: wrap; + gap: 0.25rem; + margin-top: 0.25rem; + flex-wrap: nowrap; } &__confirm-text { - font-size: 0.75rem; + font-size: 0.6875rem; color: $black-primary; white-space: nowrap; flex-shrink: 0; @@ -103,8 +100,8 @@ &__confirm-yes, &__confirm-no { - font-size: 0.6875rem; - padding: 0.2rem 0.4rem; + font-size: 0.625rem; + padding: 0.15rem 0.3rem; border: 1px solid $black-primary; background: transparent; color: $black-primary; @@ -113,7 +110,8 @@ text-transform: uppercase; letter-spacing: 0.03em; font-family: inherit; - line-height: 1.4; + line-height: 1.3; + white-space: nowrap; &:hover { background: rgba(0, 0, 0, 0.06); } } @@ -183,29 +181,26 @@ @include for-normal-layout { .epicure-cart-item { - height: auto; - min-height: 96px; + height: 130px; } .epicure-cart-item__image { - height: 100%; - min-height: 96px; - align-self: stretch; + width: 130px; + height: 130px; } .epicure-cart-item__body { - height: auto; - min-height: 96px; + height: 130px; padding: 15px 0.5rem 0.5rem 24px; } .epicure-cart-item__qty-row { - gap: 3px; + gap: 5px; } .epicure-cart-item__qty-btn { - width: 31px; - height: 31px; + width: 18px; + height: 18px; border-color: $gold-secondary; color: $gold-secondary; From ac9c291b02c858c702045f92b4cf52b89508df17 Mon Sep 17 00:00:00 2001 From: Amit Oved Date: Mon, 29 Jun 2026 11:40:40 +0300 Subject: [PATCH 8/9] fix(home): remove blank line gap in signature dish cards --- apps/frontend/src/styles/pages/_home.scss | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/frontend/src/styles/pages/_home.scss b/apps/frontend/src/styles/pages/_home.scss index a6bc7bc..a02c99a 100644 --- a/apps/frontend/src/styles/pages/_home.scss +++ b/apps/frontend/src/styles/pages/_home.scss @@ -192,7 +192,11 @@ .epicure-dish-card__restaurant { order: 2; } .epicure-dish-card__type-icon { order: 3; } .epicure-dish-card__description { order: 4; } - .epicure-dish-card__price-row { order: 5; } + .epicure-dish-card__price-row { order: 5; margin-top: 0; } + + .epicure-dish-card__info { + justify-content: space-between; + } } } From 605369527f55e1f166defe3e621c387c314ad607 Mon Sep 17 00:00:00 2001 From: Amit Oved Date: Mon, 29 Jun 2026 11:44:01 +0300 Subject: [PATCH 9/9] fix(lint): remove eslint-disable comment for unavailable react-hooks rule --- apps/frontend/src/components/DishGrid.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/frontend/src/components/DishGrid.tsx b/apps/frontend/src/components/DishGrid.tsx index 6e6cd2a..affec7b 100644 --- a/apps/frontend/src/components/DishGrid.tsx +++ b/apps/frontend/src/components/DishGrid.tsx @@ -54,7 +54,7 @@ export function DishGrid({ dishes, restaurantId, restaurantName }: DishGridProps }, 1200); }, 1000); return () => { clearTimeout(scrollTimer); clearTimeout(modalTimer); }; - }, [highlightDishId, dishes]); // eslint-disable-line react-hooks/exhaustive-deps + }, [highlightDishId, dishes]); const filtered = dishes.filter(d => d.mealTime?.trim() === activeTab); const tabs = [...TEXT.dishGrid.tabs] as MealTime[];