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
+5
View File
@@ -55,6 +55,11 @@
scroll-margin-top: var(--header-height);
}
/* Prevent horizontal scroll from overflowing children */
html {
overflow-x: clip;
}
/* Base */
body,
.shahi-body {
+1 -1
View File
@@ -113,7 +113,7 @@ export default function RootLayout({
{children}
<CartDrawer />
<WishlistDrawer />
<Toaster position="top-center" richColors closeButton />
<Toaster position="top-center" richColors closeButton offset={124} />
</WishlistProvider>
</CartProvider>
</LanguageProvider>
+2 -2
View File
@@ -55,7 +55,7 @@ export default function LocationsPage() {
{/* Branch 1: Askim */}
<div className="bg-white border border-[#EDE6D9] rounded-2xl p-8">
<div className="uppercase text-[#B38B4D] text-xs tracking-[2px] mb-2">RESTAURANT &amp; BUFFET</div>
<h2 className="text-4xl tracking-[-1.5px] mb-4">Shahi Kitchen Askim (Sisjön)</h2>
<h2 className="text-2xl sm:text-3xl md:text-4xl tracking-[-1.5px] mb-4 break-words">Shahi Kitchen Askim (Sisjön)</h2>
<div className="space-y-5 text-[15px]">
<div>
@@ -95,7 +95,7 @@ export default function LocationsPage() {
{/* Branch 2: Backaplan */}
<div className="bg-white border border-[#EDE6D9] rounded-2xl p-8">
<div className="uppercase text-[#B38B4D] text-xs tracking-[2px] mb-2">SWEETS, SNACKS &amp; CAFÉ</div>
<h2 className="text-4xl tracking-[-1.5px] mb-4">Shahi Sweets Backaplan</h2>
<h2 className="text-2xl sm:text-3xl md:text-4xl tracking-[-1.5px] mb-4 break-words">Shahi Sweets Backaplan</h2>
<div className="space-y-5 text-[15px]">
<div>
+7 -7
View File
@@ -137,7 +137,7 @@ export default function LoginPage() {
{t.auth.usernameLabel}
</label>
<div className="relative">
<User className="absolute left-4 top-4 h-5 w-5 text-[#B38B4D]" />
<User className="absolute start-4 top-4 h-5 w-5 text-[#B38B4D]" />
<input
id="username"
name="username"
@@ -146,7 +146,7 @@ export default function LoginPage() {
onChange={handleChange}
placeholder={t.auth.usernamePlaceholder}
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>
@@ -157,7 +157,7 @@ export default function LoginPage() {
{t.auth.passwordLabel}
</label>
<div className="relative">
<Lock className="absolute left-4 top-4 h-5 w-5 text-[#B38B4D]" />
<Lock className="absolute start-4 top-4 h-5 w-5 text-[#B38B4D]" />
<input
id="password"
name="password"
@@ -166,7 +166,7 @@ export default function LoginPage() {
onChange={handleChange}
placeholder={t.auth.passwordPlaceholder}
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>
@@ -177,21 +177,21 @@ export default function LoginPage() {
{t.auth.siteLabel}
</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="site"
name="site"
value={formData.site}
onChange={handleChange}
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.auth.sitePlaceholder}</option>
<option value="askim">{t.auth.askim}</option>
<option value="backaplan">{t.auth.backaplan}</option>
</select>
{/* Custom dropdown arrow for polish */}
<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>
<p className="mt-1.5 text-[11px] text-[#8A8478]">Select the location you are working at today.</p>
</div>
+33 -18
View File
@@ -18,6 +18,11 @@ import { useCart } from "@/components/CartContext";
import WishlistButton from "@/components/WishlistButton";
import { useLanguage } from "@/lib/language-context";
import { getTranslation } from "@/lib/translations";
import {
getCategoryName,
getMenuItemDescription,
getMenuItemName,
} from "@/application/i18n/menu-localization";
import {
dishImageUrl,
getMenuPosterSrc,
@@ -45,7 +50,10 @@ export default function MenuPage() {
// Sidebar categories
const sidebarCategories = [
{ id: "All", name: t.menu.allDishes || "All Dishes" },
...menuCategories.map((cat) => ({ id: cat.id, name: cat.name })),
...menuCategories.map((cat) => ({
id: cat.id,
name: getCategoryName(language, cat.id, cat.name),
})),
];
// Filtering logic
@@ -62,11 +70,15 @@ export default function MenuPage() {
// Search
if (searchQuery.trim()) {
const q = searchQuery.toLowerCase().trim();
items = items.filter(
(item) =>
item.name.toLowerCase().includes(q) ||
(item.description && item.description.toLowerCase().includes(q))
);
items = items.filter((item) => {
const localizedName = getMenuItemName(language, item).toLowerCase();
const localizedDescription = getMenuItemDescription(language, t, item).toLowerCase();
return (
localizedName.includes(q) ||
localizedDescription.includes(q) ||
item.name.toLowerCase().includes(q)
);
});
}
// Vegetarian
@@ -77,7 +89,7 @@ export default function MenuPage() {
return { ...category, items };
})
.filter((category) => category.items.length > 0);
}, [searchQuery, showVegetarianOnly, activeCategory]);
}, [searchQuery, showVegetarianOnly, activeCategory, language, t]);
const handleCategorySelect = (id: string) => {
setActiveCategory(id);
@@ -85,9 +97,9 @@ export default function MenuPage() {
};
const getItemDescription = (item: MenuItem) =>
(t as { menuDescriptions?: Record<string, string> }).menuDescriptions?.[item.id] ||
item.description ||
"";
getMenuItemDescription(language, t, item);
const getItemName = (item: MenuItem) => getMenuItemName(language, item);
const openDishDetails = (item: MenuItem) => {
setDetailItem(item);
@@ -173,7 +185,7 @@ export default function MenuPage() {
<div className="sticky top-[116px]">
<div className="mb-6">
<div className="text-xs tracking-[3px] text-[#c99a2e] mb-2">EXPLORE OUR</div>
<h3 className="font-serif text-3xl tracking-tight">Signature Categories</h3>
<h3 className="font-serif text-2xl sm:text-3xl tracking-tight">Signature Categories</h3>
</div>
{/* Smooth scroll: horizontal on mobile, vertical on desktop when list exceeds viewport */}
@@ -265,7 +277,9 @@ export default function MenuPage() {
filteredCategories.map((category) => (
<div key={category.id} className="mb-16">
<div className="flex items-center gap-4 mb-7">
<div className="text-3xl tracking-[-1px] text-[#101724]">{category.name}</div>
<div className="text-xl sm:text-2xl md:text-3xl tracking-[-1px] text-[#101724] min-w-0 break-words">
{getCategoryName(language, category.id, category.name)}
</div>
<div className="flex-1 h-px bg-gradient-to-r from-[#e5e1d7] to-transparent" />
<div className="text-sm font-medium text-[#68717f] tracking-widest">
{category.items.length} {t.menu.dishes}
@@ -314,7 +328,7 @@ export default function MenuPage() {
>
<motion.img
src={getCardImageSrc(item, category.id)}
alt={item.name}
alt={getItemName(item)}
className="absolute inset-0 w-full h-full object-cover"
loading="lazy"
whileHover={!isMobile ? {
@@ -337,18 +351,18 @@ export default function MenuPage() {
)}
<div className="absolute top-3 right-3 z-10">
<WishlistButton
item={{ id: item.id, name: item.name, price: item.price, image: item.image }}
item={{ id: item.id, name: getItemName(item), price: item.price, image: item.image }}
size="sm"
/>
</div>
</div>
<div className="p-6 flex flex-col flex-1">
<div className="flex justify-between items-start gap-4 mb-4">
<h3 className="text-[22px] leading-tight tracking-[-0.4px] text-[#2C2A26]">
{item.name}
<div className="flex justify-between items-start gap-3 mb-4 min-w-0">
<h3 className="min-w-0 flex-1 text-lg sm:text-[22px] leading-tight tracking-[-0.4px] text-[#2C2A26] break-words">
{getItemName(item)}
</h3>
{renderMenuPrice(item)}
<div className="shrink-0">{renderMenuPrice(item)}</div>
</div>
{usesDetailModal ? (
@@ -404,6 +418,7 @@ export default function MenuPage() {
{detailItem && (
<DishDetailsModal
item={detailItem}
displayName={getItemName(detailItem)}
description={getItemDescription(detailItem)}
isOpen={!!detailItem}
onClose={() => setDetailItem(null)}
+68 -26
View File
@@ -15,7 +15,7 @@
import React, { useMemo, useState } from 'react';
import { useSearchParams } from 'next/navigation';
import { motion, AnimatePresence } from 'framer-motion';
import { filterMenuItems } from '@/application/menu/filter-menu';
import { getMenuPosterSrc, logoUrl, SITE_ASSETS } from '@/lib/assets';
import { buildTableOrderPayload } from '@/application/table-order/table-order-use-cases';
import { parseTableId } from '@/domain/table/table-id';
@@ -34,6 +34,11 @@ import type { MenuItem } from '@/domain/menu/entities';
import { playAddSound, playSuccessSound } from '@/infrastructure/audio/web-audio-sound-adapter';
import { useLanguage } from '@/presentation/providers/language-provider';
import { getTranslation } from '@/presentation/i18n/translations';
import {
getMenuItemDescription,
getMenuItemName,
localizeOrderLineName,
} from '@/application/i18n/menu-localization';
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
@@ -66,14 +71,20 @@ export default function OrderFromTablePage() {
const tableDisplay = isValid ? parsed.tableNumber : '';
// Filtered dishes for the selector grid (same data source as everywhere else)
const filteredDishes = useMemo(
() =>
filterMenuItems(allMenuItems, {
searchQuery: search,
vegetarianOnly: vegOnly,
}),
[allMenuItems, search, vegOnly]
);
const filteredDishes = useMemo(() => {
const query = search.toLowerCase().trim();
return allMenuItems.filter((item) => {
if (vegOnly && !item.isVegetarian) return false;
if (!query) return true;
const localizedName = getMenuItemName(language, item).toLowerCase();
const localizedDescription = getMenuItemDescription(language, t, item).toLowerCase();
return (
localizedName.includes(query) ||
localizedDescription.includes(query) ||
item.name.toLowerCase().includes(query)
);
});
}, [allMenuItems, search, vegOnly, language, t]);
const selectedTotal = calculateOrderTotal(selected);
@@ -231,7 +242,7 @@ export default function OrderFromTablePage() {
</div>
</div>
<div className="max-w-6xl mx-auto px-6 pt-8 pb-24">
<div className={`max-w-6xl mx-auto px-6 pt-8 ${selected.length > 0 ? 'pb-36 lg:pb-24' : 'pb-24'}`}>
{/* Intro */}
<div className="mb-8">
<div className="text-[#B38B4D] text-xs tracking-[3.5px] mb-2 font-medium">{to.tableLabel.toUpperCase()} {tableDisplay}</div>
@@ -240,18 +251,18 @@ export default function OrderFromTablePage() {
</div>
<div className="grid lg:grid-cols-12 gap-8">
{/* DISH SELECTOR */}
<div className="lg:col-span-7">
{/* DISH SELECTOR — below summary on mobile for faster checkout */}
<div className="order-2 lg:order-1 lg:col-span-7">
{/* Filters */}
<div className="mb-5 flex flex-col sm:flex-row gap-3 items-stretch sm:items-center">
<div className="relative flex-1">
<Search className="pointer-events-none absolute left-4 top-3.5 h-4 w-4 text-[#B38B4D]" />
<Search className="pointer-events-none absolute start-4 top-3.5 h-4 w-4 text-[#B38B4D]" />
<input
type="text"
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder={to.searchPlaceholder}
className="w-full rounded-2xl border border-[#EDE6D9] bg-white py-3 pl-11 pr-4 text-sm placeholder:text-[#8A8478] focus:border-[#B38B4D] focus:ring-1 focus:ring-[#B38B4D]/20 outline-none"
className="w-full rounded-2xl border border-[#EDE6D9] bg-white py-3 ps-11 pe-4 text-sm placeholder:text-[#8A8478] focus:border-[#B38B4D] focus:ring-1 focus:ring-[#B38B4D]/20 outline-none"
/>
</div>
<button
@@ -273,6 +284,8 @@ export default function OrderFromTablePage() {
filteredDishes.map((dish) => {
const inOrder = selected.some((s) => s.id === dish.id);
const poster = getMenuPosterSrc(dish, 'optimized');
const dishName = getMenuItemName(language, dish);
const dishDescription = getMenuItemDescription(language, t, dish);
return (
<motion.button
key={dish.id}
@@ -283,7 +296,7 @@ export default function OrderFromTablePage() {
<div className="relative h-20 w-20 flex-shrink-0 overflow-hidden rounded-xl bg-[#F2EDE4]">
<img
src={poster}
alt={dish.name}
alt={dishName}
className="absolute inset-0 h-full w-full object-cover group-active:scale-105 transition"
onError={(e) => {
const target = e.currentTarget as HTMLImageElement;
@@ -296,7 +309,7 @@ export default function OrderFromTablePage() {
<div className="flex min-w-0 flex-1 flex-col py-0.5">
<div className="flex items-start justify-between gap-2">
<div className="font-medium tracking-[-0.2px] text-[15px] leading-tight line-clamp-2 pr-1">
{dish.name}
{dishName}
</div>
<div className="shrink-0 text-right font-semibold text-[#B38B4D] tabular-nums text-sm leading-tight">
{dish.pricing === 'weight' ? (
@@ -313,9 +326,9 @@ export default function OrderFromTablePage() {
</div>
</div>
{dish.description && (
{dishDescription && (
<div className="mt-1 text-xs text-[#6B665F] line-clamp-2">
{dish.description}
{dishDescription}
</div>
)}
@@ -338,9 +351,9 @@ export default function OrderFromTablePage() {
</div>
</div>
{/* ORDER SUMMARY (sticky on desktop) */}
<div className="lg:col-span-5">
<div className="lg:sticky lg:top-24">
{/* ORDER SUMMARY (first on mobile, sticky on desktop) */}
<div className="order-1 lg:order-2 lg:col-span-5">
<div className="lg:sticky lg:top-[calc(var(--header-height)+5.5rem)]">
<div className="rounded-3xl border border-[#EDE6D9] bg-white p-6 shadow-sm">
<div className="flex items-center justify-between mb-4">
<div className="font-semibold tracking-tight text-lg">{to.yourOrder}</div>
@@ -364,7 +377,7 @@ export default function OrderFromTablePage() {
{selected.map((item) => (
<div key={item.id} className="flex items-center gap-3 rounded-2xl bg-[#F8F5F0] px-4 py-3">
<div className="min-w-0 flex-1">
<div className="font-medium text-[15px] tracking-[-0.2px]">{item.name}</div>
<div className="font-medium text-[15px] tracking-[-0.2px]">{localizeOrderLineName(language, item)}</div>
<div className="text-xs text-[#6B665F] tabular-nums">
{item.pricingMode === 'weight'
? `${item.pricePerHalfKg ?? item.price} kr / ½ kg · ${formatLineQuantityLabel(item)}`
@@ -376,7 +389,7 @@ export default function OrderFromTablePage() {
<div className="flex items-center gap-1.5">
<button
onClick={() => updateQty(item.id, item.quantity - 1)}
className="h-8 w-8 flex items-center justify-center rounded-full border border-[#EDE6D9] bg-white active:bg-white text-lg leading-none active:scale-95"
className="h-11 w-11 flex items-center justify-center rounded-full border border-[#EDE6D9] bg-white active:bg-white text-lg leading-none active:scale-95 touch-manipulation"
aria-label="Decrease"
>
@@ -386,7 +399,7 @@ export default function OrderFromTablePage() {
</span>
<button
onClick={() => updateQty(item.id, item.quantity + 1)}
className="h-8 w-8 flex items-center justify-center rounded-full border border-[#EDE6D9] bg-white active:bg-white text-lg leading-none active:scale-95"
className="h-11 w-11 flex items-center justify-center rounded-full border border-[#EDE6D9] bg-white active:bg-white text-lg leading-none active:scale-95 touch-manipulation"
aria-label="Increase"
>
+
@@ -418,8 +431,8 @@ export default function OrderFromTablePage() {
/>
</div>
{/* Total + Send */}
<div className="mt-5 border-t border-[#EDE6D9] pt-5">
{/* Total + Send — desktop only; mobile uses sticky bottom bar */}
<div className="hidden lg:block mt-5 border-t border-[#EDE6D9] pt-5">
<div className="flex items-baseline justify-between text-lg mb-4">
<span className="text-[#6B665F]">{to.total}</span>
<span className="font-semibold tabular-nums">{selectedTotal} kr</span>
@@ -437,6 +450,14 @@ export default function OrderFromTablePage() {
Staff will come to your table to confirm.
</p>
</div>
{/* Mobile: show total inline in summary card */}
<div className="lg:hidden mt-5 border-t border-[#EDE6D9] pt-4">
<div className="flex items-baseline justify-between text-lg">
<span className="text-[#6B665F]">{to.total}</span>
<span className="font-semibold tabular-nums">{selectedTotal} kr</span>
</div>
</div>
</div>
{/* Small context line */}
@@ -448,6 +469,27 @@ export default function OrderFromTablePage() {
</div>
</div>
{/* Mobile sticky order bar */}
{selected.length > 0 && (
<div className="lg:hidden fixed inset-x-0 bottom-0 z-50 border-t border-[#EDE6D9] bg-white/95 backdrop-blur-xl shadow-[0_-4px_24px_-4px_rgba(16,23,36,0.12)] px-4 py-3 pb-[max(0.75rem,env(safe-area-inset-bottom))]">
<div className="mx-auto flex max-w-6xl items-center gap-4">
<div className="min-w-0 flex-1">
<div className="text-xs text-[#6B665F]">
{selected.length} {selected.length === 1 ? 'item' : 'items'}
</div>
<div className="font-semibold tabular-nums text-[#101724]">{selectedTotal} kr</div>
</div>
<button
onClick={handleSendOrder}
disabled={isSending}
className="shrink-0 rounded-2xl bg-gradient-to-r from-[#B38B4D] via-[#c99a2e] to-[#B38B4D] px-6 py-3.5 text-sm font-semibold tracking-[0.5px] text-white shadow-lg active:brightness-95 disabled:opacity-60 touch-manipulation"
>
{isSending ? 'Sending...' : to.sendOrder}
</button>
</div>
</div>
)}
<Footer />
</div>
);
+10 -5
View File
@@ -17,6 +17,10 @@ import { menuCategories } from "@/lib/menu-data";
import { buildCartLineFromMenuItem } from "@/application/cart/cart-line-builder";
import type { MenuItem } from "@/domain/menu/entities";
import { getTranslation } from "@/lib/translations";
import {
getMenuItemDescription,
getMenuItemName,
} from "@/application/i18n/menu-localization";
import SignatureMenuMarquee, { type SignatureDish } from "@/components/SignatureMenuMarquee";
type SignatureFilter = "All" | "Rice" | "Curry" | "Meat" | "Street" | "Roll" | "Sweets";
@@ -55,11 +59,12 @@ export default function ShahiKitchenHomepage() {
menuCategories.flatMap((category) =>
category.items.map((item) => ({
...item,
desc: item.description ?? "",
name: getMenuItemName(language, item),
desc: getMenuItemDescription(language, t, item),
filterTag: getDishFilterTag(item, category.id),
}))
),
[]
[language, t]
);
const filtered = useMemo(() => {
@@ -175,9 +180,9 @@ export default function ShahiKitchenHomepage() {
{ title: "Shahi Sweets", desc: "Homemade mithai made daily. From fresh Jalebi to Rasmalai — the perfect sweet ending." },
{ title: "Warm Hospitality", desc: "Whether you're here for a quick lunch or a family celebration, you will always be treated like Shahi." },
]).map((item, index) => (
<div key={index} className="experience-card group relative border border-[#EDE6D9] p-9 rounded-2xl bg-[#F8F5F0] overflow-hidden">
<div className="text-[#B38B4D] text-6xl font-light mb-9 tracking-[-2px]">0{index + 1}</div>
<h4 className="text-[29px] tracking-[-0.8px] mb-5 text-[#2C2A26] leading-tight">{item.title}</h4>
<div key={index} className="experience-card group relative border border-[#EDE6D9] p-6 sm:p-9 rounded-2xl bg-[#F8F5F0] overflow-hidden">
<div className="text-[#B38B4D] text-4xl sm:text-6xl font-light mb-6 sm:mb-9 tracking-[-2px]">0{index + 1}</div>
<h4 className="text-xl sm:text-2xl md:text-[29px] tracking-[-0.8px] mb-4 sm:mb-5 text-[#2C2A26] leading-tight break-words">{item.title}</h4>
<p className="text-[#6B665F] text-[15.5px] leading-relaxed">{item.desc}</p>
</div>
))}
+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">