Files
shahikitchen-prod/app/reserve/page.tsx
T

814 lines
42 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import Navbar from "@/components/Navbar";
import Footer from "@/components/Footer";
import { buildBookingWhatsAppMessage } from '@/application/messaging/whatsapp-message-builder';
import { isBookingComplete } from '@/domain/booking/validation';
import type { BookingDetails, PreOrderLine } from '@/domain/booking/entities';
import {
addOrderLine,
calculateLineTotal,
calculateOrderTotal,
formatLineQuantityLabel,
removeOrderLine,
updateOrderLineQuantity,
} from '@/domain/shared/order-line';
import { buildCartLineFromMenuItem } from '@/application/cart/cart-line-builder';
import type { MenuItem } from '@/domain/menu/entities';
import {
getMenuPosterSrc,
getMenuPosterCandidates,
applyNextImageFallback,
SITE_ASSETS,
} from '@/lib/assets';
import { container } from '@/infrastructure/di/container';
import { useLanguage } from '@/presentation/providers/language-provider';
import { getTranslation } from '@/presentation/i18n/translations';
import {
getMenuItemDescription,
getMenuItemName,
localizeOrderLineName,
} from '@/application/i18n/menu-localization';
import { useCallback, useEffect, useState } from 'react';
import { motion, AnimatePresence } from "framer-motion";
import { MapPin, Calendar, Clock, Users, User, Phone, Mail, MessageCircle, Plus, Minus, X, ArrowRight, Search, CheckCircle, UtensilsCrossed, PartyPopper, Building2, Sparkles } from "lucide-react";
import { useMenu } from "@/presentation/providers/menu-provider";
import { playAddSound, playHoverSound, playSuccessSound } from "@/lib/sounds";
import type { BookingMode, EventTypeId } from '@/domain/booking/event-types';
import { EVENT_GUEST_OPTIONS, TABLE_GUEST_OPTIONS } from '@/domain/booking/event-types';
import {
getBookingEventsCopy,
getEventTypeLabel,
toBookingMessageCopy,
} from '@/presentation/i18n/booking-events';
import {
getWhatsAppInquiryLanguage,
getWhatsAppInquiryTranslation,
} from '@/application/messaging/inquiry-language';
import EventTypePicker from '@/components/reserve/EventTypePicker';
import { useCustomerAuth } from '@/presentation/providers/customer-auth-provider';
const RESERVE_DRAFT_STORAGE_KEY = 'shahi-reserve-draft';
function GoogleIcon({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" aria-hidden="true">
<path
fill="#4285F4"
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 0 1-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z"
/>
<path
fill="#34A853"
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
/>
<path
fill="#FBBC05"
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
/>
<path
fill="#EA4335"
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
/>
</svg>
);
}
/**
* =============================================================================
* TABLE RESERVATION / BOOKING PAGE
* =============================================================================
*
* Beautiful, theme-matched table booking experience with optional pre-order.
* - Swap implemented: Reserve Table now in central nav pills, Login is the gold CTA.
* - Form first (location, date, time, guests, name, phone, email, notes).
* - After form: full menu selector for pre-ordering (reuse menu-data).
* - End: builds rich WhatsApp inquiry (booking + pre-order items) using same number as cart.
* - Fully translated (sv default + en/hi/ur).
* - Mobile-first, large targets, elegant cards matching /login and site theme.
* - Uses framer-motion for reveals, lucide icons.
* - No backend: client-side only, like staff login and cart WhatsApp.
*
* Linked from navbar (after swap) and locations.
*/
const EMPTY_BOOKING: BookingDetails = {
bookingMode: 'table',
eventType: '',
eventTypeOther: '',
location: '',
date: '',
time: '',
guests: '',
name: '',
phone: '',
email: '',
notes: '',
};
export default function ReservePage() {
const { categories: menuCategories } = useMenu();
const { language } = useLanguage();
const t = getTranslation(language);
const eventsCopy = getBookingEventsCopy(language);
const { email: customerEmail, name: customerName, isAuthenticated, isLoading: isAuthLoading } =
useCustomerAuth();
const [booking, setBooking] = useState<BookingDetails>(EMPTY_BOOKING);
const [draftRestored, setDraftRestored] = useState(false);
const [preOrder, setPreOrder] = useState<PreOrderLine[]>([]);
const [showMenu, setShowMenu] = useState(false);
const [search, setSearch] = useState('');
const [sent, setSent] = useState(false);
const handleBookingChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
const { name, value } = e.target;
setBooking((prev) => ({ ...prev, [name]: value }));
};
const persistReserveDraft = useCallback((draft: BookingDetails) => {
try {
sessionStorage.setItem(RESERVE_DRAFT_STORAGE_KEY, JSON.stringify(draft));
} catch {
// Ignore storage errors.
}
}, []);
const handleGoogleSignInForBooking = () => {
persistReserveDraft(booking);
window.location.href = '/api/auth/google?returnTo=%2Freserve';
};
useEffect(() => {
if (draftRestored) return;
try {
const raw = sessionStorage.getItem(RESERVE_DRAFT_STORAGE_KEY);
if (!raw) {
setDraftRestored(true);
return;
}
const parsed = JSON.parse(raw) as Partial<BookingDetails>;
setBooking((prev) => ({
...prev,
bookingMode: parsed.bookingMode === 'event' ? 'event' : 'table',
eventType: typeof parsed.eventType === 'string' ? parsed.eventType : '',
eventTypeOther: typeof parsed.eventTypeOther === 'string' ? parsed.eventTypeOther : '',
location: typeof parsed.location === 'string' ? parsed.location : '',
date: typeof parsed.date === 'string' ? parsed.date : '',
time: typeof parsed.time === 'string' ? parsed.time : '',
guests: typeof parsed.guests === 'string' ? parsed.guests : '',
name: typeof parsed.name === 'string' ? parsed.name : '',
phone: typeof parsed.phone === 'string' ? parsed.phone : '',
notes: typeof parsed.notes === 'string' ? parsed.notes : '',
}));
sessionStorage.removeItem(RESERVE_DRAFT_STORAGE_KEY);
} catch {
sessionStorage.removeItem(RESERVE_DRAFT_STORAGE_KEY);
} finally {
setDraftRestored(true);
}
}, [draftRestored]);
useEffect(() => {
if (!isAuthenticated || !customerEmail) return;
setBooking((prev) => ({
...prev,
email: customerEmail,
name: prev.name.trim() ? prev.name : customerName?.trim() ?? prev.name,
}));
}, [isAuthenticated, customerEmail, customerName]);
const validateForm = (): boolean => isBookingComplete(booking);
const setBookingMode = (mode: BookingMode) => {
setBooking((prev) => ({
...prev,
bookingMode: mode,
eventType: mode === 'table' ? '' : prev.eventType,
eventTypeOther: mode === 'table' ? '' : prev.eventTypeOther,
guests: '',
}));
};
const selectEventType = (eventType: EventTypeId) => {
setBooking((prev) => ({
...prev,
eventType,
eventTypeOther: eventType === 'other' ? prev.eventTypeOther : '',
}));
};
const handleContinue = (e: React.FormEvent) => {
e.preventDefault();
if (booking.bookingMode === 'event' && !booking.eventType) {
alert(eventsCopy.eventTypeRequired);
return;
}
if (booking.bookingMode === 'event' && booking.eventType === 'other' && !booking.eventTypeOther.trim()) {
alert(eventsCopy.eventTypeOtherRequired);
return;
}
if (validateForm()) {
setShowMenu(true);
setTimeout(() => {
const menuEl = document.getElementById('preorder-menu');
if (menuEl) menuEl.scrollIntoView({ behavior: 'smooth', block: 'start' });
}, 100);
} else {
alert('Please fill in all required fields (location, date, time, guests, name, phone).');
}
};
// Flatten menu for selector (with search)
const allDishes = menuCategories.flatMap((cat) => cat.items);
const filteredDishes = allDishes.filter((dish) => {
const q = search.toLowerCase();
const localizedName = getMenuItemName(language, dish).toLowerCase();
const localizedDescription = getMenuItemDescription(language, t, dish).toLowerCase();
return (
localizedName.includes(q) ||
localizedDescription.includes(q) ||
dish.name.toLowerCase().includes(q)
);
});
const addToPreOrder = (dish: MenuItem) => {
setPreOrder((prev) => addOrderLine(prev, buildCartLineFromMenuItem(dish)));
};
const updatePreOrderQty = (id: string, newQty: number) => {
setPreOrder((prev) => updateOrderLineQuantity(prev, id, newQty));
};
const removeFromPreOrder = (id: string) => {
setPreOrder((prev) => removeOrderLine(prev, id));
};
const getPreOrderTotal = () => calculateOrderTotal(preOrder);
const handleSendInquiry = () => {
if (!validateForm()) {
alert('Please complete the booking form first.');
return;
}
playSuccessSound();
const inquiryLang = getWhatsAppInquiryLanguage(language);
const inquiryT = getWhatsAppInquiryTranslation(language);
const inquiryEventsCopy = getBookingEventsCopy(inquiryLang);
const message = buildBookingWhatsAppMessage(
booking,
preOrder,
toBookingMessageCopy(inquiryEventsCopy, {
askim: inquiryT.booking.askim,
backaplan: inquiryT.booking.backaplan,
}),
booking.eventType ? getEventTypeLabel(inquiryLang, booking.eventType) : '',
inquiryLang,
);
if (!message) return;
container.messagingGateway.openWhatsApp(message);
setSent(true);
};
const resetAll = () => {
setBooking(EMPTY_BOOKING);
setPreOrder([]);
setShowMenu(false);
setSearch('');
setSent(false);
};
const guestOptions =
booking.bookingMode === 'event' ? EVENT_GUEST_OPTIONS : TABLE_GUEST_OPTIONS;
const editBooking = () => {
setShowMenu(false);
setSent(false);
};
// Location options from translations
const locationOptions = [
{ value: 'askim', label: t.booking.askim },
{ value: 'backaplan', label: t.booking.backaplan },
];
return (
<div className="min-h-screen bg-[#F8F5F0] text-[#2C2A26]">
<Navbar />
<main className="pb-20">
{/* Hero */}
<div className="relative overflow-hidden">
<div className="pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_at_top,rgba(201,154,46,0.12),transparent_55%)]" />
<div className="relative mx-auto max-w-5xl px-4 py-8 text-center sm:px-6 sm:py-14">
<div className="mb-4 inline-flex items-center gap-2 rounded-full border border-[#c99a2e]/25 bg-[#fff6dc] px-4 py-1.5 text-[10px] font-bold uppercase tracking-[0.28em] text-[#8a6a25]">
<Sparkles className="h-3.5 w-3.5" />
{eventsCopy.heroBadge}
</div>
<h1 className="mb-4 font-serif text-3xl leading-[1.1] tracking-[-0.8px] text-[#101724] sm:text-4xl md:text-5xl">
{eventsCopy.heroTitle}
</h1>
<p className="mx-auto max-w-2xl text-sm leading-relaxed text-[#6B665F] md:text-base">
{eventsCopy.heroSubtitle}
</p>
</div>
</div>
<div className="mx-auto max-w-5xl px-4 sm:px-6">
{/* Experience pillars */}
<div className="mb-10 grid gap-4 sm:grid-cols-3">
{[
{ icon: UtensilsCrossed, title: eventsCopy.experienceDiningTitle, desc: eventsCopy.experienceDiningDesc, accent: 'from-[#0f5a4a]/10 to-[#0f5a4a]/5' },
{ icon: PartyPopper, title: eventsCopy.experienceCelebrateTitle, desc: eventsCopy.experienceCelebrateDesc, accent: 'from-[#c99a2e]/15 to-[#fff6dc]' },
{ icon: Building2, title: eventsCopy.experienceCorporateTitle, desc: eventsCopy.experienceCorporateDesc, accent: 'from-[#101724]/8 to-[#101724]/3' },
].map((item) => (
<div
key={item.title}
className={`rounded-3xl border border-[#EDE6D9] bg-gradient-to-br ${item.accent} p-5 shadow-sm`}
>
<div className="mb-3 flex h-11 w-11 items-center justify-center rounded-2xl bg-white shadow-sm">
<item.icon className="h-5 w-5 text-[#B38B4D]" />
</div>
<h2 className="mb-1 font-serif text-lg tracking-tight text-[#101724]">{item.title}</h2>
<p className="text-sm leading-relaxed text-[#6B665F]">{item.desc}</p>
</div>
))}
</div>
<AnimatePresence mode="wait">
{!sent ? (
<div className="max-w-4xl mx-auto space-y-8">
{/* Booking Form Card - now wider (full in max-w-4xl) */}
<div className="rounded-3xl border border-[#EDE6D9] bg-white p-4 shadow-xl sm:p-8 md:p-10">
{/* Booking mode toggle */}
<div className="mb-6 grid grid-cols-1 gap-3 sm:mb-8 sm:grid-cols-2">
{([
{ mode: 'table' as const, label: eventsCopy.modeTable, desc: eventsCopy.modeTableDesc, icon: UtensilsCrossed },
{ mode: 'event' as const, label: eventsCopy.modeEvent, desc: eventsCopy.modeEventDesc, icon: PartyPopper },
]).map((option) => {
const active = booking.bookingMode === option.mode;
return (
<button
key={option.mode}
type="button"
onClick={() => setBookingMode(option.mode)}
className={`flex min-h-[72px] touch-manipulation items-start gap-3 rounded-2xl border p-3.5 text-left transition-all active:scale-[0.99] sm:gap-4 sm:p-4 ${
active
? 'border-[#c99a2e] bg-gradient-to-br from-[#fff6dc] to-white shadow-md ring-2 ring-[#c99a2e]/25'
: 'border-[#EDE6D9] bg-[#FFFCF7] hover:border-[#c99a2e]/40'
}`}
>
<div className={`flex h-11 w-11 shrink-0 items-center justify-center rounded-xl ${active ? 'bg-[#c99a2e] text-[#241806]' : 'bg-[#F8F5F0] text-[#B38B4D]'}`}>
<option.icon className="h-5 w-5" />
</div>
<div>
<div className="font-semibold text-[#101724]">{option.label}</div>
<div className="text-sm text-[#6B665F]">{option.desc}</div>
</div>
</button>
);
})}
</div>
{booking.bookingMode === 'event' && (
<>
<EventTypePicker
language={language}
selected={booking.eventType}
onSelect={selectEventType}
title={eventsCopy.eventPickerTitle}
subtitle={eventsCopy.eventPickerSubtitle}
/>
{booking.eventType === 'other' && (
<div className="mb-8">
<label htmlFor="eventTypeOther" className="mb-2 block text-sm font-semibold tracking-wide text-[#6B665F]">
{eventsCopy.eventTypeOtherLabel}
</label>
<input
id="eventTypeOther"
name="eventTypeOther"
type="text"
value={booking.eventTypeOther}
onChange={handleBookingChange}
placeholder={eventsCopy.eventTypeOtherPlaceholder}
className="w-full rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] px-4 py-4 text-[17px] placeholder:text-[#8A8478] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
/>
</div>
)}
</>
)}
{isAuthLoading ? (
<div className="rounded-2xl border border-[#EDE6D9] bg-[#FFFCF7] px-4 py-8 text-center text-sm text-[#6B665F] sm:px-6 sm:py-10">
Checking your account
</div>
) : !isAuthenticated ? (
<div className="rounded-2xl border border-[#EDE6D9] bg-gradient-to-br from-[#FFFCF7] to-[#FFF6DC]/40 px-4 py-8 text-center sm:px-8 sm:py-10">
<h2 className="mb-2 font-serif text-xl tracking-tight text-[#101724] sm:text-3xl">
{t.booking.signInRequiredTitle}
</h2>
<p className="mx-auto mb-6 max-w-md text-sm leading-relaxed text-[#6B665F] sm:mb-8 sm:text-base">
{t.booking.signInRequiredSubtitle}
</p>
<button
type="button"
onClick={handleGoogleSignInForBooking}
className="mx-auto flex w-full max-w-md min-h-[52px] touch-manipulation items-center justify-center gap-3 rounded-2xl border border-[#EDE6D9] bg-white px-4 py-4 text-base font-semibold text-[#101724] shadow-sm transition hover:border-[#c99a2e]/40 hover:bg-[#FFFCF7] active:scale-[0.985] sm:px-5"
>
<GoogleIcon className="h-5 w-5 shrink-0" />
<span className="text-left leading-snug">{t.auth.customer.signInWithGoogle}</span>
</button>
</div>
) : (
<>
<div className="mb-5 flex items-center gap-3 rounded-2xl border border-[#c99a2e]/25 bg-gradient-to-r from-[#FFF6DC] to-[#FFFCF7] px-3 py-3 sm:mb-6 sm:px-4">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-[#101724] text-sm font-bold text-white">
{(customerEmail ?? '?').charAt(0).toUpperCase()}
</div>
<div className="min-w-0">
<p className="text-xs font-semibold uppercase tracking-wide text-[#8f6b22]">
{t.booking.signedInAs}
</p>
<p className="truncate text-sm font-medium text-[#101724]">{customerEmail ?? ''}</p>
</div>
</div>
<h2 className="mb-2 font-serif text-xl tracking-tight sm:text-3xl">{t.booking.formTitle}</h2>
<p className="mb-6 text-sm text-[#6B665F] sm:mb-8 sm:text-base">
{booking.bookingMode === 'event'
? eventsCopy.formSubtitleEvent
: eventsCopy.formSubtitleTable}
</p>
<form onSubmit={handleContinue} className="space-y-6">
{/* Location */}
<div>
<label htmlFor="location" className="mb-2 block text-sm font-semibold tracking-wide text-[#6B665F]">{t.booking.locationLabel}</label>
<div className="relative">
<MapPin className="pointer-events-none absolute start-4 top-4 z-10 h-5 w-5 text-[#B38B4D]" />
<select
id="location"
name="location"
value={booking.location}
onChange={handleBookingChange}
required
className="w-full cursor-pointer appearance-none rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 ps-12 pe-10 text-[17px] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
>
<option value="">{t.booking.locationPlaceholder}</option>
{locationOptions.map((opt) => (
<option key={opt.value} value={opt.value}>{opt.label}</option>
))}
</select>
<div className="pointer-events-none absolute end-4 top-4 text-[#B38B4D]"></div>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6">
{/* Date */}
<div>
<label htmlFor="date" className="mb-2 block text-sm font-semibold tracking-wide text-[#6B665F]">{t.booking.dateLabel}</label>
<div className="relative">
<Calendar className="pointer-events-none absolute start-4 top-4 h-5 w-5 text-[#B38B4D]" />
<input
id="date"
name="date"
type="date"
value={booking.date}
onChange={handleBookingChange}
min={new Date().toISOString().split('T')[0]}
required
className="w-full rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 ps-12 pe-4 text-[17px] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
/>
</div>
</div>
{/* Time */}
<div>
<label htmlFor="time" className="mb-2 block text-sm font-semibold tracking-wide text-[#6B665F]">{t.booking.timeLabel}</label>
<div className="relative">
<Clock className="pointer-events-none absolute start-4 top-4 h-5 w-5 text-[#B38B4D]" />
<input
id="time"
name="time"
type="time"
value={booking.time}
onChange={handleBookingChange}
required
className="w-full rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 ps-12 pe-4 text-[17px] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
/>
</div>
</div>
</div>
{/* Guests */}
<div>
<label htmlFor="guests" className="mb-2 block text-sm font-semibold tracking-wide text-[#6B665F]">{t.booking.guestsLabel}</label>
<div className="relative">
<Users className="pointer-events-none absolute start-4 top-4 h-5 w-5 text-[#B38B4D]" />
<select
id="guests"
name="guests"
value={booking.guests}
onChange={handleBookingChange}
required
className="w-full cursor-pointer appearance-none rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 ps-12 pe-10 text-[17px] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
>
<option value="">Select</option>
{guestOptions.map((n) => (
<option key={n} value={n}>
{booking.bookingMode === 'event' ? `${n} guests` : n}
</option>
))}
</select>
<div className="pointer-events-none absolute end-4 top-4 text-[#B38B4D]"></div>
</div>
</div>
{/* Name */}
<div>
<label htmlFor="name" className="mb-2 block text-sm font-semibold tracking-wide text-[#6B665F]">{t.booking.nameLabel}</label>
<div className="relative">
<User className="pointer-events-none absolute start-4 top-4 h-5 w-5 text-[#B38B4D]" />
<input
id="name"
name="name"
type="text"
value={booking.name}
onChange={handleBookingChange}
placeholder={t.booking.namePlaceholder}
required
className="w-full rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 ps-12 pe-4 text-[17px] placeholder:text-[#8A8478] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
/>
</div>
</div>
{/* Phone */}
<div>
<label htmlFor="phone" className="mb-2 block text-sm font-semibold tracking-wide text-[#6B665F]">{t.booking.phoneLabel}</label>
<div className="relative">
<Phone className="pointer-events-none absolute start-4 top-4 h-5 w-5 text-[#B38B4D]" />
<input
id="phone"
name="phone"
type="tel"
value={booking.phone}
onChange={handleBookingChange}
placeholder={t.booking.phonePlaceholder}
required
className="w-full rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 ps-12 pe-4 text-[17px] placeholder:text-[#8A8478] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
/>
</div>
</div>
{/* Email */}
<div>
<label htmlFor="email" className="mb-2 block text-sm font-semibold tracking-wide text-[#6B665F]">{t.booking.emailLabel}</label>
<div className="relative">
<Mail className="pointer-events-none absolute start-4 top-4 h-5 w-5 text-[#B38B4D]" />
<input
id="email"
name="email"
type="email"
value={booking.email}
onChange={handleBookingChange}
placeholder={t.booking.emailPlaceholder}
readOnly={Boolean(customerEmail)}
className={`w-full rounded-2xl border border-[#EDE6D9] py-4 ps-12 pe-4 text-[17px] placeholder:text-[#8A8478] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20 ${
customerEmail ? 'bg-[#F8F5F0]/80 text-[#6B665F] cursor-default' : 'bg-[#F8F5F0]'
}`}
/>
</div>
</div>
{/* Notes */}
<div>
<label htmlFor="notes" className="mb-2 block text-sm font-semibold tracking-wide text-[#6B665F]">{t.booking.notesLabel}</label>
<div className="relative">
<MessageCircle className="pointer-events-none absolute start-4 top-4 h-5 w-5 text-[#B38B4D]" />
<textarea
id="notes"
name="notes"
value={booking.notes}
onChange={handleBookingChange}
placeholder={t.booking.notesPlaceholder}
rows={3}
className="w-full rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 ps-12 pe-4 text-[17px] placeholder:text-[#8A8478] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20 resize-y"
/>
</div>
</div>
<button
type="submit"
className="btn-primary mt-2 flex w-full items-center justify-center gap-3 rounded-2xl py-4 text-base font-semibold tracking-[0.5px] active:scale-[0.985] transition"
>
{t.booking.continueBtn}
<ArrowRight className="h-5 w-5" />
</button>
</form>
</>
)}
</div>
{/* Pre-Order Menu Section - revealed after form */}
<AnimatePresence>
{showMenu && isAuthenticated && (
<motion.div
id="preorder-menu"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0 }}
className="rounded-3xl border border-[#EDE6D9] bg-white p-6 sm:p-8 shadow-xl md:p-10"
>
<h2 className="text-2xl sm:text-3xl tracking-tight font-serif mb-2">{t.booking.menuTitle}</h2>
<p className="text-[#6B665F] mb-6">{t.booking.menuSubtitle}</p>
{/* Search */}
<div className="relative mb-6">
<Search className="pointer-events-none absolute start-4 top-4 h-5 w-5 text-[#B38B4D]" />
<input
type="text"
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder={t.booking.searchPlaceholder}
className="w-full rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 ps-12 pe-4 text-[17px] placeholder:text-[#8A8478] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
/>
</div>
{/* Dishes Grid - improved visualization: Name, Poster below, Desc under poster, price, nice Add button */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 max-h-[320px] sm:max-h-[420px] overflow-auto pr-2">
{filteredDishes.length > 0 ? (
filteredDishes.map((dish) => {
const posterSrc = getMenuPosterSrc(dish);
const dishName = getMenuItemName(language, dish);
const dishDescription = getMenuItemDescription(language, t, dish);
const shortDesc = dishDescription
? (dishDescription.length > 80 ? dishDescription.slice(0, 80) + '...' : dishDescription)
: '';
const isAdded = preOrder.some((p) => p.id === dish.id);
return (
<motion.div
key={dish.id}
whileHover={{ y: -2, boxShadow: "0 10px 15px -3px rgb(0 0 0 / 0.1)" }}
className="flex flex-col border border-[#EDE6D9] rounded-2xl p-3 bg-white hover:border-[#B38B4D]/50 transition-all overflow-hidden"
onMouseEnter={() => playHoverSound()}
>
{/* Item Name */}
<div className="font-semibold tracking-[-0.3px] text-base mb-1 text-[#2C2A26] line-clamp-1">{dishName}</div>
{/* Poster under name */}
<div className="relative w-full h-24 sm:h-28 md:h-32 rounded-xl overflow-hidden border border-[#EDE6D9] mb-2 bg-[#F8F5F0]">
<img
src={posterSrc}
alt={dishName}
className="absolute inset-0 w-full h-full object-cover"
loading="lazy"
onError={(e) => {
applyNextImageFallback(
e.target as HTMLImageElement,
getMenuPosterCandidates(dish),
(e.target as HTMLImageElement).src
);
}}
/>
<div className="absolute inset-0 bg-gradient-to-b from-black/5 via-transparent to-black/10" />
</div>
{/* Description under the poster */}
<div className="text-xs text-[#6B665F] leading-snug mb-2 flex-1 line-clamp-2">{shortDesc}</div>
<div className="flex items-center justify-between mt-auto pt-1">
<span className="text-[#B38B4D] font-semibold text-sm tabular-nums leading-tight text-right">
{dish.pricing === 'weight' ? (
<>
<span className="block">{dish.pricePerHalfKg} kr / ½ kg</span>
<span className="block text-xs text-[#8A8478]">{dish.pricePerKg} {t.menu.perKg}</span>
</>
) : (
<span>{dish.price} kr</span>
)}
</span>
{/* Improved graphics for add button */}
<button
onClick={() => {
playAddSound();
addToPreOrder(dish);
}}
className={`flex items-center gap-1.5 rounded-full px-4 py-2.5 text-xs font-semibold shadow-sm active:scale-[0.96] transition-all min-h-[44px] touch-manipulation ${
isAdded
? "bg-[#3F5C4A] text-white hover:bg-[#2a4033]"
: "bg-[#B38B4D] text-white hover:bg-[#8f6b22] hover:shadow-md"
}`}
>
<Plus className="h-3.5 w-3.5" />
{isAdded ? "Add More" : t.booking.addToPreOrder}
</button>
</div>
</motion.div>
);
})
) : (
<p className="text-[#6B665F] col-span-full text-center py-8">No dishes match your search.</p>
)}
</div>
{/* Pre-Order Summary */}
<div className="mt-8 border-t border-[#EDE6D9] pt-6">
<h3 className="font-semibold tracking-wide mb-4 flex items-center gap-2">{t.booking.yourPreOrder} <span className="text-xs text-[#B38B4D] font-normal">({preOrder.length})</span></h3>
{preOrder.length === 0 ? (
<p className="text-sm text-[#6B665F] italic">{t.booking.emptyPreOrder}</p>
) : (
<div className="space-y-3 mb-4">
{preOrder.map((item) => {
const dishInfo = allDishes.find((d) => d.id === item.id);
const thumb = dishInfo ? getMenuPosterSrc(dishInfo) : SITE_ASSETS.fallbacks.logo;
return (
<div key={item.id} className="flex items-center gap-3 bg-[#F8F5F0] rounded-2xl px-3 py-2">
{/* Small poster thumbnail in summary for better visualization */}
<img
src={thumb}
alt={localizeOrderLineName(language, item)}
className="w-10 h-10 object-cover rounded-lg border border-[#EDE6D9] flex-shrink-0"
/>
<div className="flex-1 min-w-0">
<div className="font-medium text-sm truncate">{localizeOrderLineName(language, item)}</div>
<div className="text-[10px] text-[#6B665F]">
{item.pricingMode === 'weight'
? `${item.pricePerHalfKg ?? item.price} kr / ½ kg · ${formatLineQuantityLabel(item)}`
: `${item.price} kr × ${item.quantity}`}
{' '}= {calculateLineTotal(item)} kr
</div>
</div>
<div className="flex items-center gap-1 flex-shrink-0">
<button onClick={() => updatePreOrderQty(item.id, item.quantity - 1)} className="min-w-11 min-h-11 flex items-center justify-center border border-[#EDE6D9] rounded-lg text-sm hover:bg-white active:bg-white active:scale-95 touch-manipulation">-</button>
<span className="min-w-10 text-center font-medium text-sm tabular-nums">
{formatLineQuantityLabel(item)}
</span>
<button onClick={() => updatePreOrderQty(item.id, item.quantity + 1)} className="min-w-11 min-h-11 flex items-center justify-center border border-[#EDE6D9] rounded-lg text-sm hover:bg-white active:bg-white active:scale-95 touch-manipulation">+</button>
<button onClick={() => removeFromPreOrder(item.id)} className="ml-1 text-[#B38B4D] hover:text-red-600 p-0.5 active:scale-95"><X className="h-4 w-4" /></button>
</div>
</div>
);
})}
</div>
)}
<div className="flex justify-between text-lg font-medium border-t border-[#EDE6D9] pt-4">
<span>{t.booking.total}</span>
<span>{getPreOrderTotal()} kr</span>
</div>
<button
onClick={handleSendInquiry}
disabled={!validateForm()}
className="mt-6 w-full py-4 rounded-2xl text-base font-semibold tracking-[0.5px] flex items-center justify-center gap-3 bg-gradient-to-r from-[#B38B4D] via-[#c99a2e] to-[#B38B4D] text-white shadow-lg hover:brightness-105 active:brightness-95 active:scale-[0.985] transition-all disabled:opacity-60 disabled:cursor-not-allowed disabled:brightness-100"
>
{t.booking.sendInquiry}
<MessageCircle className="h-5 w-5" />
</button>
<p className="mt-3 text-center text-[11px] text-[#8A8478]">{t.booking.inquiryNote}</p>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
) : (
/* Success State */
<div className="max-w-4xl mx-auto">
<motion.div
initial={{ opacity: 0, scale: 0.98 }}
animate={{ opacity: 1, scale: 1 }}
className="max-w-md mx-auto text-center py-12"
>
<div className="mx-auto mb-6 flex h-20 w-20 items-center justify-center rounded-full bg-[#B38B4D]/10">
<CheckCircle className="h-10 w-10 text-[#B38B4D]" />
</div>
<h2 className="text-2xl sm:text-3xl md:text-4xl tracking-tight font-serif mb-4 break-words">{t.booking.successTitle}</h2>
<p className="text-[#6B665F] mb-8 leading-relaxed">{t.booking.successMessage}</p>
<div className="flex flex-col sm:flex-row gap-3 justify-center">
<button onClick={editBooking} className="btn-outline px-8 py-3 rounded-full text-sm font-medium">
{t.booking.editBooking}
</button>
<button onClick={resetAll} className="btn-primary px-8 py-3 rounded-full text-sm font-medium">
{t.booking.backHome}
</button>
</div>
</motion.div>
</div>
)}
</AnimatePresence>
</div>
</main>
<Footer />
</div>
);
}