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
+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>
);