Add guest/account auth flows, order tracking, and order history UI
- Wire up Better Auth (email/password + Google/Apple SSO) with a sign-in/sign-up UI, header account menu, and a basket that follows the signed-in-or-guest state. - Give the order status page a vertical timeline per preparation step (with a distinct customer-facing name separate from the machine tag, a done/in-progress/queued/pending look, and a sonar-ping highlight on the active step), a formatted ETA (relative under 3h, absolute clock time beyond that), and Buy again / Get help / Cancel actions. - Expand Pizza's recipe into 6 steps across a shared prep station and the existing oven/assembly stations, with matching schema, seed, and domain changes plus updated/added test coverage. - Add an Orders list (account orders via session, guest orders via a locally remembered access-token list) with a shared getOrdersForCustomer domain summary, and remember a guest's name/contact after their first order so checkout doesn't ask again. - Improve page transitions with the View Transitions API: a shared element morph between an order card and its detail page, a stable (non-jumping) header across navigations, and reduced-motion handling. - Add a Ghost Kitchen wordmark (Space Grotesk, faded "Ghost", ghost icon standing in for the "o") and shrink the basket icon to match the rest of the header controls. - Add a run-ghost-kitchen agent skill documenting local setup, the dev-server driver, and the gotchas hit along the way. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { formatDuration, formatEtaClock, formatEtaMessage } from '#/lib/format.ts'
|
||||
|
||||
describe('formatDuration', () => {
|
||||
it('rounds up to at least 1 minute', () => {
|
||||
expect(formatDuration(0)).toBe('1 min')
|
||||
expect(formatDuration(10)).toBe('1 min')
|
||||
})
|
||||
|
||||
it('formats minutes under an hour', () => {
|
||||
expect(formatDuration(90)).toBe('2 min')
|
||||
expect(formatDuration(59 * 60)).toBe('59 min')
|
||||
})
|
||||
|
||||
it('formats whole hours without minutes', () => {
|
||||
expect(formatDuration(60 * 60)).toBe('1 hr')
|
||||
expect(formatDuration(3 * 60 * 60)).toBe('3 hr')
|
||||
})
|
||||
|
||||
it('formats hours with remaining minutes', () => {
|
||||
expect(formatDuration(230 * 60)).toBe('3 hr 50 min')
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatEtaClock', () => {
|
||||
const noon = new Date(2026, 0, 15, 12, 0, 0)
|
||||
|
||||
it('labels a time later the same day as today', () => {
|
||||
expect(formatEtaClock(30 * 60, noon)).toBe('today, 12:30 PM')
|
||||
})
|
||||
|
||||
it('labels a time past midnight as tomorrow', () => {
|
||||
expect(formatEtaClock(13 * 60 * 60, noon)).toBe('tomorrow, 01:00 AM')
|
||||
})
|
||||
|
||||
it('labels a time two or more days out by weekday', () => {
|
||||
expect(formatEtaClock(2 * 24 * 60 * 60, noon)).toBe('Saturday, 12:00 PM')
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatEtaMessage', () => {
|
||||
const noon = new Date(2026, 0, 15, 12, 0, 0)
|
||||
|
||||
it('shows the relative time at or under the 3 hour threshold', () => {
|
||||
expect(formatEtaMessage(34 * 60, noon)).toBe('Ready in about 34 min')
|
||||
expect(formatEtaMessage(3 * 60 * 60, noon)).toBe('Ready in about 3 hr')
|
||||
})
|
||||
|
||||
it('shows the absolute time once the eta passes 3 hours', () => {
|
||||
expect(formatEtaMessage(3 * 60 * 60 + 60, noon)).toBe('Ready today, 03:01 PM')
|
||||
expect(formatEtaMessage(230 * 60, noon)).toBe('Ready today, 03:50 PM')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user