Replace entire repo content with updated code from /root/shahikitchen-website/

This commit is contained in:
root
2026-06-30 12:19:55 +00:00
parent 50e3a34895
commit c8b7dc95e4
26 changed files with 1153 additions and 161 deletions
+48 -33
View File
@@ -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">