Skip to content

Latest commit

 

History

History
107 lines (80 loc) · 4.23 KB

File metadata and controls

107 lines (80 loc) · 4.23 KB

BonVoyage

A Next.js 15 ferry / sea-travel ticketing system for the Philippines. Reserve online, pay over the counter, board with an offline-capable QR e-ticket. Built to the conventions in CLAUDE.md.

Stack

Next.js 15 (App Router, RSC) · React 19 · TypeScript · Supabase (Postgres + Auth + RLS) · TanStack Query + Table · Tailwind v4 + shadcn/ui · Serwist (PWA) · Vercel.

Getting started

pnpm install
cp .env.example .env.local   # fill in Supabase + app secrets
pnpm dev

The app runs without Supabase for UI work — voyage search shows demo data. Booking, auth, and manifests require a connected database.

Database setup (Supabase)

Apply the migrations in order, then (optionally) the seed and the cron job:

# Using the Supabase SQL editor or psql against your project:
db/migrations/0001_schema.sql
db/migrations/0002_functions.sql
db/migrations/0003_rls.sql
db/migrations/0005_seed_cokaliong.sql   # REAL data: Cokaliong fleet/routes/schedules/fares
db/migrations/0006_booth.sql            # booth (agent) walk-in + voyage-manifest RPCs
db/cron.sql                             # schedules expire_holds() every minute

Use either 0004_seed.sql (tiny demo) or 0005_seed_cokaliong.sql (real Cokaliong network: 16 ports, 11 vessels, 37 routes, 197 sailings, real fares) — not both (their port codes collide). 0005 is the realistic dataset for the booth.

Regenerate typed bindings after schema changes:

pnpm db:types

Creating a booth agent (and other staff)

There is no public sign-up. Create the auth user first, then grant a role in the staff table. Roles: cashier (booth agent — sells walk-in tickets), purser (view + download manifests), ops, admin.

-- 1) create the auth user (Supabase dashboard → Authentication → Add user), then:
insert into staff (id, role, full_name)
values ('<auth-user-uuid>', 'cashier', 'Booth Agent 1');

Ticketing booth (agent) — the current focus

The customer-facing pages are temporarily hidden (they redirect to the agent app; remove the redirect() in app/(public)/layout.tsx to re-enable). The booth flow:

  1. Agent signs in at /login → lands on the dashboard.
  2. /admin/booth — filter by route/date and pick a vessel/voyage.
  3. /admin/booth/[voyageId] — choose a class, enter passenger details, take the cash, type the OR number, and Confirm & issue tickets. The seat hold, booking, passengers, payment, and e-tickets are written atomically (create_walkin_booking), so a voyage can never be oversold. The per-vessel passenger list updates live.

Manifest download (/admin/manifests, CSV) remains limited to purser/ops/admin.

Scripts

Command Description
pnpm dev Dev server
pnpm build Production build (also builds the Serwist service worker)
pnpm typecheck tsc --noEmit
pnpm lint ESLint
pnpm test Vitest unit tests
pnpm format Prettier

Key flows

  • Booth walk-in (agent). Agent picks a vessel/voyage → enters passengers → takes cash → create_walkin_booking holds seats, creates the confirmed booking + passengers + payment + e-tickets in one transaction. Overselling is impossible.
  • Manifests. Per-voyage passenger list is visible to the booth agent live; purser/ops/ admin can also download CSV at /admin/manifests.
  • Holds expire. expire_holds() (scheduled via db/cron.sql) releases unpaid online holds. (Booth sales are paid immediately, so they don't expire.)
  • (Hidden for now) Customer self-service (search → reserve → pay OTC → offline QR e-ticket) is built via create_reservation but disabled while the focus is the agent app.

Security

  • Nonce-based CSP in middleware.ts; static headers (HSTS, X-Frame-Options, etc.) in next.config.ts — targets securityheaders.com Grade A (Referrer-Policy intentionally omitted; see CLAUDE.md §8).
  • RLS on every table; service_role key is server-only.
  • All input validated with Zod at every boundary.

Deploy (Vercel)

Push to a Vercel project, set the environment variables from .env.example, and deploy. After deploy, scan the URL on https://securityheaders.com to confirm the grade.