A production-grade Flight Management Progressive Web Application built for a hiring evaluation. Demonstrates real-world engineering patterns: atomic database operations, realtime subscriptions, SSR-safe auth, and strict TypeScript throughout.
Live stack: Next.js 16.2 (App Router + Turbopack) · Supabase · Zustand 5 · Tailwind CSS · shadcn/ui · TypeScript strict
| Feature | Details |
|---|---|
| Flight search | Filter by origin, destination, date, passenger count |
| Interactive seat map | Supabase Realtime — seat state updates live across sessions |
| Booking flow | Multi-step: search → seat selection → passenger details → PNR |
| My Bookings | View, cancel, and reschedule with confirmation dialogs |
| Auth | Email/password via Supabase Auth with SSR-safe session handling |
| PWA | Installable; manual service worker (StaleWhileRevalidate + CacheFirst) |
| Accessibility | ARIA labels, keyboard navigation, live regions on seat map |
src/
├── app/ # Next.js App Router
│ ├── (main)/ # Shell layout (Navbar + Footer)
│ │ ├── page.tsx # Home (hero + search)
│ │ ├── flights/results/ # Flight results list
│ │ ├── flights/[id]/seats/ # Seat selection (SSR initial seats)
│ │ ├── booking/ # Passenger details + confirmation
│ │ └── my-bookings/ # Booking management
│ └── auth/ # Login / signup pages
├── components/
│ ├── flights/ # FlightCard, FlightSearchForm
│ ├── seat-map/ # SeatMap (Realtime)
│ └── ui/ # shadcn/ui primitives
├── lib/
│ ├── supabase/ # client.ts, server.ts, middleware.ts
│ ├── constants/ # airports, aircraft configs
│ └── utils/ # cn, format, date helpers
├── services/ # Data access layer (bookings, flights, seats)
├── stores/ # Zustand stores (useFlightStore, useUserStore)
└── types/ # database.ts (generated Supabase types)
Atomic seat booking (RPC) Seat reservation uses a Postgres function called via Supabase RPC. The function checks availability and inserts the booking inside a single transaction — preventing double-booking under concurrent load without application-level locking.
SSR-safe auth
@supabase/ssr v0.6 with createServerClient / createBrowserClient. Session cookies are read/written in middleware (src/proxy.ts) on every request. No client-side token storage.
Zustand partialize
useFlightStore persists only selectedSeat and optimisticSeatId to sessionStorage. Fields like bookingStep are intentionally excluded — they are derived from URL navigation, not state, avoiding stale-step bugs on page reload.
Realtime seat eviction
When another user claims a seat the current user has selected, the Supabase Realtime handler detects the conflict (updated.is_available === false && selectedSeat.id === updated.id) and automatically clears the selection, showing a visible warning.
Manual service worker
next-pwa is incompatible with Turbopack. A hand-written public/sw.js registers two strategies:
StaleWhileRevalidatefor Supabase API callsCacheFirstfor static assets (/_next/static/)
TypeScript strict
strict: true, noUncheckedIndexedAccess: true, exactOptionalPropertyTypes: true. All Supabase query results cast through as unknown as T to bridge the generated type/inference gap.
| Table | Key columns |
|---|---|
profiles |
id (FK → auth.users), full_name, email |
flights |
id, origin, destination, departure_time, arrival_time, aircraft_type, base_price, status |
seats |
id, flight_id, seat_number, class, is_available, extra_fee |
bookings |
id, user_id, flight_id, seat_id, pnr_code, passenger_name, status, total_price |
RLS policies restrict all reads/writes to the authenticated user's own rows. The seat-locking RPC runs with SECURITY DEFINER to atomically check + reserve within the same transaction.
- Node.js 20+
- A Supabase project (free tier works)
git clone <repo>
cd <repo>
npm installCreate .env.local:
NEXT_PUBLIC_SUPABASE_URL=https://<project>.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=<anon-key>Run the SQL migrations in supabase/migrations/ (or paste into the Supabase SQL editor), then:
npm run dev # Turbopack dev server → http://localhost:3000
npm run build # Production build
npm run lint # ESLint
npx tsc --noEmit # Type checkThe app passes all PWA installability checks:
public/manifest.jsonwith 192×192 and 512×512 iconspublic/sw.jsservice worker registered in root layout- HTTPS required in production (Vercel / any HTTPS host)
Recommended deployment: Vercel — zero-config Next.js with automatic Edge Functions for middleware.
- All data fetching in Server Components passes
cookies()for SSR auth — no client waterfalls on initial load SeatMapuses a single Supabase Realtime channel per flight, unsubscribed on unmountMyBookingsClientuses auseRefcache pattern to prevent infiniteuseEffectre-runs when booking data changes- Error boundary (
error.tsx) and 404 page (not-found.tsx) are airline-themed for UX consistency BookingCardis extracted into its own component for single-responsibility and testability