Builds the main customer-facing flow: browse the Brand's menu, add items to a cart, and check out either as a guest (name + contact) or, if signed in, as the account customer — both call the existing placeOrder server function, which already distinguishes the two. On success, navigates to a new /orders/$orderId status page showing each Order Item's Preparation Steps, queue position, ETA, and (per the domain rules already established) a Cancel button while cancellable and a pickup-confirmation button once ready. Styled with SCSS Modules per CLAUDE.md; added the sass dependency and the src/styles/ structure it describes (globals.scss imported once in __root.tsx, alongside the existing Tailwind stylesheet the not-yet- mounted header-user.tsx component depends on). Verified end-to-end against a throwaway local Postgres: menu renders with seeded items, cart quantities update, guest checkout places a real order, and the status page reflects a real cancellation. Also fixes CLAUDE.md's Commands section, which documented pnpm even though this repo only has npm (package-lock.json, no pnpm installed). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
79 lines
2.3 KiB
TypeScript
79 lines
2.3 KiB
TypeScript
import { createFileRoute, useNavigate } from '@tanstack/react-router'
|
|
import { useState } from 'react'
|
|
|
|
import { CheckoutSection } from '#/components/ordering/CheckoutSection.tsx'
|
|
import { MenuList } from '#/components/ordering/MenuList.tsx'
|
|
import { OrderSummary } from '#/components/ordering/OrderSummary.tsx'
|
|
import { getBrand } from '#/server/brand.ts'
|
|
import { getMenu } from '#/server/menu.ts'
|
|
|
|
import styles from './index.module.scss'
|
|
|
|
export const Route = createFileRoute('/')({
|
|
loader: async () => {
|
|
const brand = await getBrand()
|
|
const menu = await getMenu({ data: { brandId: brand.id } })
|
|
return { brand, menu }
|
|
},
|
|
component: OrderingPage,
|
|
})
|
|
|
|
function OrderingPage() {
|
|
const { brand, menu } = Route.useLoaderData()
|
|
const navigate = useNavigate()
|
|
const [quantities, setQuantities] = useState<Record<number, number>>({})
|
|
|
|
const add = (menuItemId: number) =>
|
|
setQuantities((prev) => ({
|
|
...prev,
|
|
[menuItemId]: (prev[menuItemId] ?? 0) + 1,
|
|
}))
|
|
|
|
const remove = (menuItemId: number) =>
|
|
setQuantities((prev) => {
|
|
const current = prev[menuItemId] ?? 0
|
|
if (current <= 1) {
|
|
const next = { ...prev }
|
|
delete next[menuItemId]
|
|
return next
|
|
}
|
|
return { ...prev, [menuItemId]: current - 1 }
|
|
})
|
|
|
|
const items = Object.entries(quantities).flatMap(([menuItemId, quantity]) =>
|
|
Array.from({ length: quantity }, () => ({
|
|
menuItemId: Number(menuItemId),
|
|
})),
|
|
)
|
|
|
|
return (
|
|
<main className={styles.page}>
|
|
<h1 className={styles.heading}>{brand.name}</h1>
|
|
<div className={styles.layout}>
|
|
<MenuList
|
|
menu={menu}
|
|
quantities={quantities}
|
|
onAdd={add}
|
|
onRemove={remove}
|
|
/>
|
|
<aside className={styles.sidebar}>
|
|
<OrderSummary menu={menu} quantities={quantities} />
|
|
<CheckoutSection
|
|
items={items}
|
|
disabled={items.length === 0}
|
|
onPlaced={(order) =>
|
|
navigate({
|
|
to: '/orders/$orderId',
|
|
params: { orderId: String(order.id) },
|
|
search: order.guestAccessToken
|
|
? { token: order.guestAccessToken }
|
|
: {},
|
|
})
|
|
}
|
|
/>
|
|
</aside>
|
|
</div>
|
|
</main>
|
|
)
|
|
}
|