Embedded reference demo
The React demo at /demo is an actual API consumer included in every application image. It does not import server internals or keep a second state model. It offers three canonical attendees through a demo-only identity selector and creates a short-lived HttpOnly session cookie for the selected attendee; every later request passes through the normal API authentication, authorization, tenant scoping, and MySQL store.
The demo intentionally exposes only attendee schedules, purchases, access, and bookings. The selector changes only between the three attendee histories; it does not expose an administrator persona. Owners, teachers, and other administrators use /admin.
Maja demonstrates an exhausted intro pass followed by a regularly used monthly membership. Jonas demonstrates an intro pass that expired with unused credits, two drop-ins, a short cancelled membership, and a 10-class pass with three credits remaining. Anna starts without history and sees the once-only DKK 299 intro pass in the featured position.
Use Reset demo data beside the attendee selector to replace mutations in the canonical demo tenant with the fixture-defined histories. The reset leaves separately-created sandbox tenants intact and returns the visitor to Maja.
Confirmed classes are marked as booked in both the upcoming schedule and the attendee booking list. Attendees can cancel from either location; cancelled bookings remain visible as history while the upcoming class becomes bookable again when policy permits.
The schedule offers Book with my access only while an eligible membership or class-pass credit is available. When a finite pass reaches zero, booking another class starts an explicit MobilePay drop-in checkout instead of silently charging or over-consuming the exhausted pass.
The final Payment history section lists the attendee's payments and reimbursements newest first. Pass and membership entries name the purchased product; a drop-in payment and reimbursement name the exact class, its scheduled date and time, and the single-class product. Cancelling a paid single-class booking records the cancellation time beneath its Cancelled label and automatically adds a full reimbursement to that history.
Fixture and reset model
Canonical content lives in demo-data/harbour-yoga.json. The runtime validates the file before writing it. Change that file to adjust people, locations, durations, recurring events, products, entitlements, or roster bookings.
npm run build
DATABASE_URL="<mysql-url>" npm run db:migrate:runtime
DATABASE_URL="<mysql-url>" npm run demo:reset:runtimedemo:reset:runtime replaces all application state and is used for CI and pull requests. demo:seed:runtime removes and recreates only the account whose slug is harbour-yoga-demo; production demo deployment uses this mode so other sandbox accounts in the demo database remain intact.
Environment boundary
- The real production container has
DEMO_AUTH_ENABLED=falseand uses the production database. - A second production-demo container has
DEMO_AUTH_ENABLED=true,DEMO_PUBLIC_API_BASE=/demo/api, and uses the dedicated demo database. - Caddy sends
/demoand/demo/apito that second container. - PRs use their isolated database and reset it before every redeploy.
- GitHub Actions starts MySQL 8.4, migrates, resets, starts the compiled application, and captures Playwright evidence from
/demobefore reports are generated.
Email delivery and MobilePay are the only mocked boundaries in CI, previews, and the canonical production demo. The API still creates real payment, entitlement, and booking records in the applicable demo database.