Skip to content
Merged
19 changes: 6 additions & 13 deletions apps/frontend/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
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';
Expand All @@ -9,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() {
Expand All @@ -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()];

Expand All @@ -49,17 +52,7 @@ export default async function HomePage() {

<DesktopPopularRestaurants restaurants={popularRestaurants} />

{signatureDishes.length > 0 && (
<MobileSection
title={TEXT.home.signatureDishTitle}
linkLabel={TEXT.home.allRestaurantsLink}
linkHref="/restaurants"
>
{signatureDishes.map(dish => (
<DishCard key={dish.id} dish={dish} imageUrl={strapiImageUrl(dish.image?.url)} />
))}
</MobileSection>
)}
<MobileSignatureDishes dishes={signatureDishes} />

<DesktopSignatureDishes dishes={signatureDishes} />

Expand Down
6 changes: 5 additions & 1 deletion apps/frontend/src/components/CartPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
16 changes: 15 additions & 1 deletion apps/frontend/src/components/DesktopSignatureDishes.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
'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';
import { TEXT } from '../lib/text';
import { useIntersectionObserver } from '../hooks/useIntersectionObserver';
import { PENDING_DISH_KEY } from '../lib/events';

interface Props {
dishes: Dish[];
Expand All @@ -25,7 +27,19 @@ export function DesktopSignatureDishes({ dishes }: Props) {
<h2 className="epicure-desktop-signature__title">{TEXT.home.signatureDishTitle}</h2>
<div className="epicure-desktop-signature__grid">
{dishes.map(dish => (
<DishCard key={dish.id} dish={dish} imageUrl={strapiImageUrl(dish.image?.url)} />
dish.restaurantId != null ? (
<Link
key={dish.id}
href={`/restaurants/${dish.restaurantId}`}
className="epicure-dish-link"
aria-label={TEXT.home.dishLinkAriaLabel(dish.name)}
onClick={() => sessionStorage.setItem(PENDING_DISH_KEY, String(dish.id))}
>
<DishCard dish={dish} imageUrl={strapiImageUrl(dish.image?.url)} />
</Link>
) : (
<DishCard key={dish.id} dish={dish} imageUrl={strapiImageUrl(dish.image?.url)} />
)
))}
</div>
</section>
Expand Down
36 changes: 35 additions & 1 deletion apps/frontend/src/components/DishGrid.tsx
Original file line number Diff line number Diff line change
@@ -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];

Expand All @@ -23,6 +24,38 @@ export function DishGrid({ dishes, restaurantId, restaurantName }: DishGridProps
const [selectedDish, setSelectedDish] = useState<Dish | null>(null);
const [selectedImageUrl, setSelectedImageUrl] = useState<string>('');

const [highlightDishId, setHighlightDishId] = useState<number | null>(null);

useEffect(() => {
const pending = sessionStorage.getItem(PENDING_DISH_KEY);
if (!pending) return;
const dishId = parseInt(pending, 10);
if (isNaN(dishId)) return;
sessionStorage.removeItem(PENDING_DISH_KEY);
setHighlightDishId(dishId);
}, []);

useEffect(() => {
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<typeof setTimeout>;
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]);

const filtered = dishes.filter(d => d.mealTime?.trim() === activeTab);
const tabs = [...TEXT.dishGrid.tabs] as MealTime[];

Expand Down Expand Up @@ -60,6 +93,7 @@ export function DishGrid({ dishes, restaurantId, restaurantName }: DishGridProps
{filtered.map(dish => (
<div
key={dish.id}
id={`dish-${dish.id}`}
className="epicure-dish-grid-item"
role="button"
tabIndex={0}
Expand Down
41 changes: 41 additions & 0 deletions apps/frontend/src/components/MobileSignatureDishes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
'use client';

import Link from 'next/link';
import { DishCard } from '@org/ui-components';
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[];
}

export function MobileSignatureDishes({ dishes }: Props) {
if (dishes.length === 0) return null;

return (
<MobileSection
title={TEXT.home.signatureDishTitle}
linkLabel={TEXT.home.allRestaurantsLink}
linkHref="/restaurants"
>
{dishes.map(dish => (
dish.restaurantId != null ? (
<Link
key={dish.id}
href={`/restaurants/${dish.restaurantId}`}
className="epicure-dish-link"
aria-label={TEXT.home.dishLinkAriaLabel(dish.name)}
onClick={() => sessionStorage.setItem(PENDING_DISH_KEY, String(dish.id))}
>
<DishCard dish={dish} imageUrl={strapiImageUrl(dish.image?.url)} />
</Link>
) : (
<DishCard key={dish.id} dish={dish} imageUrl={strapiImageUrl(dish.image?.url)} />
)
))}
</MobileSection>
);
}
14 changes: 7 additions & 7 deletions apps/frontend/src/components/cart-panel/CartItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ export function CartItem({ item }: CartItemProps) {
<img src={item.imageUrl} alt={item.dish.name} className="epicure-cart-item__image" />
<div className="epicure-cart-item__body">
<div className="epicure-cart-item__top">
<div className="epicure-cart-item__details">
<p className="epicure-cart-item__name">
<span className="epicure-cart-item__qty-inline">{item.quantity}x </span>
{item.dish.name}
</p>
<p className="epicure-cart-item__gold-price">&#8362;{item.dish.price.toFixed(2)}</p>
</div>
{!item.pendingRemove && (
<div className="epicure-cart-item__qty-row">
<button
Expand All @@ -33,13 +40,6 @@ export function CartItem({ item }: CartItemProps) {
>+</button>
</div>
)}
<div className="epicure-cart-item__details">
<p className="epicure-cart-item__name">
<span className="epicure-cart-item__qty-inline">{item.quantity}x </span>
{item.dish.name}
</p>
<p className="epicure-cart-item__gold-price">&#8362;{item.dish.price.toFixed(2)}</p>
</div>
</div>
{(item.selectedSide || item.selectedChanges.length > 0) && (
<p className="epicure-cart-item__meta">
Expand Down
1 change: 1 addition & 0 deletions apps/frontend/src/lib/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = 'epicure_pending_dish_highlight';

export function dispatchCuisineFilter(labels: string[]): void {
sessionStorage.setItem(PENDING_CUISINE_KEY, JSON.stringify(labels));
Expand Down
1 change: 1 addition & 0 deletions apps/frontend/src/lib/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
Expand Down
61 changes: 28 additions & 33 deletions apps/frontend/src/styles/components/_cart-item.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}
Expand All @@ -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;
Expand All @@ -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 {
Expand All @@ -89,22 +86,22 @@
&__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;
}

&__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;
Expand All @@ -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); }
}
Expand Down Expand Up @@ -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;

Expand Down
Loading
Loading