Replace entire repo content with updated code from /root/shahikitchen-website/
This commit is contained in:
+48
-33
@@ -26,6 +26,11 @@ import {
|
||||
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 { useState } from 'react';
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { MapPin, Calendar, Clock, Users, User, Phone, Mail, MessageCircle, Plus, Minus, X, ArrowRight, Search, CheckCircle } from "lucide-react";
|
||||
@@ -93,10 +98,16 @@ export default function ReservePage() {
|
||||
// Flatten menu for selector (with search)
|
||||
const allDishes = menuCategories.flatMap((cat) => cat.items);
|
||||
|
||||
const filteredDishes = allDishes.filter((dish) =>
|
||||
dish.name.toLowerCase().includes(search.toLowerCase()) ||
|
||||
(dish.description && dish.description.toLowerCase().includes(search.toLowerCase()))
|
||||
);
|
||||
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)));
|
||||
@@ -173,7 +184,7 @@ export default function ReservePage() {
|
||||
<div className="mb-6 inline-flex h-16 w-16 items-center justify-center rounded-2xl border border-[#c99a2e]/30 bg-white/5 p-3 backdrop-blur mx-auto">
|
||||
<img src={logoUrl()} alt="Shahi Kitchen" className="h-full w-full object-contain" />
|
||||
</div>
|
||||
<div className="font-serif text-4xl tracking-[-1.5px] mb-1">Shahi Kitchen</div>
|
||||
<div className="font-serif text-2xl sm:text-3xl md:text-4xl tracking-[-1.5px] mb-1">Shahi Kitchen</div>
|
||||
<div className="text-sm font-medium tracking-[2.5px] text-[#c99a2e] mb-4">TABLE RESERVATIONS</div>
|
||||
<p className="text-sm leading-relaxed text-white/70 max-w-xs mx-auto mb-4">
|
||||
Two locations. Unforgettable evenings. Pre-order your favorites for a perfect arrival.
|
||||
@@ -187,7 +198,7 @@ export default function ReservePage() {
|
||||
<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-6 sm:p-8 shadow-xl md:p-10">
|
||||
<h2 className="text-3xl tracking-tight font-serif mb-2">{t.booking.formTitle}</h2>
|
||||
<h2 className="text-2xl sm:text-3xl tracking-tight font-serif mb-2">{t.booking.formTitle}</h2>
|
||||
<p className="text-[#6B665F] mb-8">Fill in your details to secure your table.</p>
|
||||
|
||||
<form onSubmit={handleContinue} className="space-y-6">
|
||||
@@ -195,21 +206,21 @@ export default function ReservePage() {
|
||||
<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 left-4 top-4 z-10 h-5 w-5 text-[#B38B4D]" />
|
||||
<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 pl-12 pr-10 text-[17px] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
|
||||
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 right-4 top-4 text-[#B38B4D]">▾</div>
|
||||
<div className="pointer-events-none absolute end-4 top-4 text-[#B38B4D]">▾</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -218,7 +229,7 @@ export default function ReservePage() {
|
||||
<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 left-4 top-4 h-5 w-5 text-[#B38B4D]" />
|
||||
<Calendar className="pointer-events-none absolute start-4 top-4 h-5 w-5 text-[#B38B4D]" />
|
||||
<input
|
||||
id="date"
|
||||
name="date"
|
||||
@@ -227,7 +238,7 @@ export default function ReservePage() {
|
||||
onChange={handleBookingChange}
|
||||
min={new Date().toISOString().split('T')[0]}
|
||||
required
|
||||
className="w-full rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 pl-12 pr-4 text-[17px] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
|
||||
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>
|
||||
@@ -236,7 +247,7 @@ export default function ReservePage() {
|
||||
<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 left-4 top-4 h-5 w-5 text-[#B38B4D]" />
|
||||
<Clock className="pointer-events-none absolute start-4 top-4 h-5 w-5 text-[#B38B4D]" />
|
||||
<input
|
||||
id="time"
|
||||
name="time"
|
||||
@@ -244,7 +255,7 @@ export default function ReservePage() {
|
||||
value={booking.time}
|
||||
onChange={handleBookingChange}
|
||||
required
|
||||
className="w-full rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 pl-12 pr-4 text-[17px] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
|
||||
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>
|
||||
@@ -254,19 +265,19 @@ export default function ReservePage() {
|
||||
<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 left-4 top-4 h-5 w-5 text-[#B38B4D]" />
|
||||
<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 pl-12 pr-10 text-[17px] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
|
||||
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>
|
||||
{[1,2,3,4,5,6,7,8,9,10].map(n => <option key={n} value={n}>{n}</option>)}
|
||||
</select>
|
||||
<div className="pointer-events-none absolute right-4 top-4 text-[#B38B4D]">▾</div>
|
||||
<div className="pointer-events-none absolute end-4 top-4 text-[#B38B4D]">▾</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -274,7 +285,7 @@ export default function ReservePage() {
|
||||
<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 left-4 top-4 h-5 w-5 text-[#B38B4D]" />
|
||||
<User className="pointer-events-none absolute start-4 top-4 h-5 w-5 text-[#B38B4D]" />
|
||||
<input
|
||||
id="name"
|
||||
name="name"
|
||||
@@ -283,7 +294,7 @@ export default function ReservePage() {
|
||||
onChange={handleBookingChange}
|
||||
placeholder={t.booking.namePlaceholder}
|
||||
required
|
||||
className="w-full rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 pl-12 pr-4 text-[17px] placeholder:text-[#8A8478] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
|
||||
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>
|
||||
@@ -292,7 +303,7 @@ export default function ReservePage() {
|
||||
<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 left-4 top-4 h-5 w-5 text-[#B38B4D]" />
|
||||
<Phone className="pointer-events-none absolute start-4 top-4 h-5 w-5 text-[#B38B4D]" />
|
||||
<input
|
||||
id="phone"
|
||||
name="phone"
|
||||
@@ -301,7 +312,7 @@ export default function ReservePage() {
|
||||
onChange={handleBookingChange}
|
||||
placeholder={t.booking.phonePlaceholder}
|
||||
required
|
||||
className="w-full rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 pl-12 pr-4 text-[17px] placeholder:text-[#8A8478] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
|
||||
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>
|
||||
@@ -310,7 +321,7 @@ export default function ReservePage() {
|
||||
<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 left-4 top-4 h-5 w-5 text-[#B38B4D]" />
|
||||
<Mail className="pointer-events-none absolute start-4 top-4 h-5 w-5 text-[#B38B4D]" />
|
||||
<input
|
||||
id="email"
|
||||
name="email"
|
||||
@@ -318,7 +329,7 @@ export default function ReservePage() {
|
||||
value={booking.email}
|
||||
onChange={handleBookingChange}
|
||||
placeholder={t.booking.emailPlaceholder}
|
||||
className="w-full rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 pl-12 pr-4 text-[17px] placeholder:text-[#8A8478] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
|
||||
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>
|
||||
@@ -327,7 +338,7 @@ export default function ReservePage() {
|
||||
<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 left-4 top-4 h-5 w-5 text-[#B38B4D]" />
|
||||
<MessageCircle className="pointer-events-none absolute start-4 top-4 h-5 w-5 text-[#B38B4D]" />
|
||||
<textarea
|
||||
id="notes"
|
||||
name="notes"
|
||||
@@ -335,7 +346,7 @@ export default function ReservePage() {
|
||||
onChange={handleBookingChange}
|
||||
placeholder={t.booking.notesPlaceholder}
|
||||
rows={3}
|
||||
className="w-full rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 pl-12 pr-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"
|
||||
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>
|
||||
@@ -360,18 +371,18 @@ export default function ReservePage() {
|
||||
exit={{ opacity: 0 }}
|
||||
className="rounded-3xl border border-[#EDE6D9] bg-white p-6 sm:p-8 shadow-xl md:p-10"
|
||||
>
|
||||
<h2 className="text-3xl tracking-tight font-serif mb-2">{t.booking.menuTitle}</h2>
|
||||
<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 left-4 top-4 h-5 w-5 text-[#B38B4D]" />
|
||||
<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 pl-12 pr-4 text-[17px] placeholder:text-[#8A8478] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
|
||||
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>
|
||||
|
||||
@@ -380,7 +391,11 @@ export default function ReservePage() {
|
||||
{filteredDishes.length > 0 ? (
|
||||
filteredDishes.map((dish) => {
|
||||
const posterSrc = getMenuPosterSrc(dish);
|
||||
const shortDesc = dish.description ? (dish.description.length > 80 ? dish.description.slice(0, 80) + '...' : dish.description) : '';
|
||||
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
|
||||
@@ -390,13 +405,13 @@ export default function ReservePage() {
|
||||
onMouseEnter={() => playHoverSound()}
|
||||
>
|
||||
{/* Item Name */}
|
||||
<div className="font-semibold tracking-[-0.3px] text-base mb-1 text-[#2C2A26] line-clamp-1">{dish.name}</div>
|
||||
<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={dish.name}
|
||||
alt={dishName}
|
||||
className="absolute inset-0 w-full h-full object-cover"
|
||||
loading="lazy"
|
||||
onError={(e) => {
|
||||
@@ -464,11 +479,11 @@ export default function ReservePage() {
|
||||
{/* Small poster thumbnail in summary for better visualization */}
|
||||
<img
|
||||
src={thumb}
|
||||
alt={item.name}
|
||||
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">{item.name}</div>
|
||||
<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)}`
|
||||
@@ -520,7 +535,7 @@ export default function ReservePage() {
|
||||
<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-4xl tracking-tight font-serif mb-4">{t.booking.successTitle}</h2>
|
||||
<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">
|
||||
|
||||
Reference in New Issue
Block a user