feat(menu-app): full redesign to Olive & Thyme design spec
Rebuild the public-facing digital menu from the design handoff bundle. Matches high-fidelity spec: Bricolage Grotesque + Hanken Grotesk fonts, cream/green/gold palette, per-category tinted panels, product cards with gradient art, tag icon badges, bottom-sheet product detail, search overlay, cart → checkout bottom-sheet flow posting to real API, floating cart button with bump animation, and restyled order-confirm page. - New: src/components/primitives.jsx (DishArt, Badge, DietChip, TagIcons, Price, DiscountFlag, Stepper, price helpers) - Rewrite: MenuPage.jsx — all screens in one component tree, API-driven, lang toggle (EN/GR) persisted to localStorage, scroll-spy category bar - Rewrite: OrderConfirm.jsx — design-system styling, brand colors - Update: App.jsx — remove /order route (cart flow is now a bottom sheet) - Update: tailwind.config.js — font families, brand tokens, animations - Update: index.html — Google Fonts for Bricolage Grotesque + Hanken Grotesk - Delete: CartPage, ProductModal, ProductCard, CategoryNav, CartButton Cart checkout POSTs to real submitOrder API and redirects to /confirm/:ref for live order status polling. Restaurant info falls back to hardcoded placeholder until backend includes it in the fetchMenu response (see REWORK_REVISIT.md). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,16 +1,16 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { CheckCircle, Clock, XCircle, ChefHat, Truck, Package } from 'lucide-react'
|
||||
import { Check, Clock, X, ChefHat, Truck, Package, Leaf } from 'lucide-react'
|
||||
import { fetchOrderStatus } from '../api'
|
||||
|
||||
const STATUS_CONFIG = {
|
||||
pending_acceptance: { label: 'Waiting for confirmation', Icon: Clock, color: 'text-amber-500', bg: 'bg-amber-50' },
|
||||
accepted: { label: 'Order accepted!', Icon: CheckCircle, color: 'text-emerald-500', bg: 'bg-emerald-50' },
|
||||
rejected: { label: 'Order declined', Icon: XCircle, color: 'text-red-500', bg: 'bg-red-50' },
|
||||
preparing: { label: 'Being prepared', Icon: ChefHat, color: 'text-blue-500', bg: 'bg-blue-50' },
|
||||
ready: { label: 'Ready for pickup!', Icon: Package, color: 'text-emerald-500', bg: 'bg-emerald-50' },
|
||||
out_for_delivery: { label: 'Out for delivery', Icon: Truck, color: 'text-blue-500', bg: 'bg-blue-50' },
|
||||
delivered: { label: 'Delivered!', Icon: CheckCircle, color: 'text-emerald-500', bg: 'bg-emerald-50' },
|
||||
pending_acceptance: { label: 'Waiting for confirmation', Icon: Clock, color: '#c9a24b', bg: '#f6edd6' },
|
||||
accepted: { label: 'Order accepted!', Icon: Check, color: '#3f7d4e', bg: '#e7f1e7' },
|
||||
rejected: { label: 'Order declined', Icon: X, color: '#c2602f', bg: '#f7e6dc' },
|
||||
preparing: { label: 'Being prepared', Icon: ChefHat, color: '#2d3b2d', bg: '#e7ede7' },
|
||||
ready: { label: 'Ready for pickup!', Icon: Package, color: '#3f7d4e', bg: '#e7f1e7' },
|
||||
out_for_delivery: { label: 'Out for delivery', Icon: Truck, color: '#2d3b2d', bg: '#e7ede7' },
|
||||
delivered: { label: 'Delivered!', Icon: Check, color: '#3f7d4e', bg: '#e7f1e7' },
|
||||
}
|
||||
|
||||
const TERMINAL = new Set(['rejected', 'delivered'])
|
||||
@@ -42,48 +42,73 @@ export default function OrderConfirm() {
|
||||
return () => clearTimeout(timer)
|
||||
}, [ref])
|
||||
|
||||
const cfg = STATUS_CONFIG[orderStatus] || STATUS_CONFIG['pending_acceptance']
|
||||
const cfg = STATUS_CONFIG[orderStatus] ?? STATUS_CONFIG.pending_acceptance
|
||||
const { label, Icon, color, bg } = cfg
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-slate-50 flex items-center justify-center p-6">
|
||||
<div className="bg-white rounded-2xl shadow-sm p-8 w-full max-w-sm text-center space-y-5">
|
||||
<div className="relative mx-auto min-h-dvh max-w-[480px] bg-[#faf7f0] shadow-[0_0_60px_-20px_rgba(45,42,31,0.3)] flex flex-col items-center justify-center px-6 py-12">
|
||||
|
||||
<div className={`w-20 h-20 ${bg} rounded-full flex items-center justify-center mx-auto`}>
|
||||
<Icon className={color} size={40} />
|
||||
{/* Ornament top */}
|
||||
<div className="mb-8 flex w-32 items-center gap-2">
|
||||
<span className="h-px flex-1 bg-gradient-to-r from-transparent to-[#d8cfb6]" />
|
||||
<Leaf className="h-3.5 w-3.5 text-[#c9a24b]" strokeWidth={1.6} />
|
||||
<span className="h-px flex-1 bg-gradient-to-l from-transparent to-[#d8cfb6]" />
|
||||
</div>
|
||||
|
||||
{/* Status icon */}
|
||||
<div
|
||||
className="flex h-20 w-20 items-center justify-center rounded-full"
|
||||
style={{ background: bg }}
|
||||
>
|
||||
<Icon size={38} style={{ color }} strokeWidth={2.2} />
|
||||
</div>
|
||||
|
||||
{/* Ref number */}
|
||||
<p className="mt-5 font-sans text-[11px] font-semibold uppercase tracking-[0.22em] text-[#b3aa90]">
|
||||
{ref}
|
||||
</p>
|
||||
|
||||
{/* Status label */}
|
||||
<h1 className="mt-2 font-display text-[28px] font-semibold leading-tight text-center text-[#2d3b2d]">
|
||||
{error ? 'Could not load status' : label}
|
||||
</h1>
|
||||
|
||||
{/* Rejection reason */}
|
||||
{rejectionReason && (
|
||||
<p className="mt-2 text-[14px] text-center text-[#7d7660]">
|
||||
Reason: {rejectionReason}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Subtext */}
|
||||
{orderStatus === 'delivered' && (
|
||||
<p className="mt-3 max-w-[280px] text-center text-[14px] leading-relaxed text-[#7d7660]">
|
||||
Thank you for your order! Enjoy your meal.
|
||||
</p>
|
||||
)}
|
||||
{orderStatus === 'rejected' && (
|
||||
<p className="mt-3 max-w-[280px] text-center text-[14px] leading-relaxed text-[#7d7660]">
|
||||
We're sorry we couldn't take your order this time. Please try again or speak to staff.
|
||||
</p>
|
||||
)}
|
||||
{!TERMINAL.has(orderStatus) && !error && (
|
||||
<p className="mt-3 text-[13px] text-[#9a917a]">
|
||||
We'll update this page automatically. Keep it open.
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Spinner */}
|
||||
{!TERMINAL.has(orderStatus) && !error && (
|
||||
<div className="mt-6 flex justify-center">
|
||||
<div className="h-5 w-5 rounded-full border-2 border-[#2d3b2d] border-t-transparent animate-spin" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-1">
|
||||
<p className="text-xs text-slate-400 font-mono tracking-widest uppercase">{ref}</p>
|
||||
<h1 className="text-2xl font-bold text-slate-800">
|
||||
{error ? 'Could not load status' : label}
|
||||
</h1>
|
||||
{rejectionReason && (
|
||||
<p className="text-sm text-slate-500 mt-1">Reason: {rejectionReason}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!TERMINAL.has(orderStatus) && !error && (
|
||||
<p className="text-xs text-slate-400">
|
||||
We'll update this page automatically. Keep it open.
|
||||
</p>
|
||||
)}
|
||||
|
||||
{orderStatus === 'rejected' && (
|
||||
<p className="text-sm text-slate-500">
|
||||
We're sorry we couldn't take your order this time. Please try again or visit us in person.
|
||||
</p>
|
||||
)}
|
||||
|
||||
{orderStatus === 'delivered' && (
|
||||
<p className="text-sm text-slate-500">Thank you for your order! Enjoy your meal.</p>
|
||||
)}
|
||||
|
||||
{!TERMINAL.has(orderStatus) && !error && (
|
||||
<div className="flex justify-center">
|
||||
<div className="w-5 h-5 rounded-full border-2 border-emerald-400 border-t-transparent animate-spin" />
|
||||
</div>
|
||||
)}
|
||||
{/* Ornament bottom */}
|
||||
<div className="mt-10 flex w-32 items-center gap-2">
|
||||
<span className="h-px flex-1 bg-gradient-to-r from-transparent to-[#d8cfb6]" />
|
||||
<Leaf className="h-3 w-3 text-[#c9a24b]" strokeWidth={1.6} />
|
||||
<span className="h-px flex-1 bg-gradient-to-l from-transparent to-[#d8cfb6]" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user