This project is a React booking app for fixed 3-hour introductory flight lessons. It uses Supabase Auth for customer sign-in, a single RLS-protected bookings table for persistence, and a Netlify serverless function to generate a friendly Claude-powered confirmation after a successful reservation.
- Vite React app scaffolded in the repo root.
netlify.tomlconfigured for static build output and Netlify functions..env.exampleadded for local Supabase and server-side Claude configuration.
- Supabase client wired with browser-safe anon credentials only.
supabase/schema.sqlcreates a singlebookingstable.time_slot_idis protected with aUNIQUEconstraint to prevent double-booking.- Row Level Security is enabled and policies use
auth.uid() = user_idfor reads and inserts.
- The UI exposes exactly two fixed lesson blocks:
Morning Flight: 09:00 AM – 12:00 PMAfternoon Flight: 01:00 PM – 04:00 PM
- Booking inserts go directly to Supabase without a client-side availability pre-check.
- Postgres unique violations are caught in the frontend and shown as:
Oops! Someone just snatched this slot. Please pick another.
netlify/functions/get-confirmation.jsreadsCLAUDE_API_KEYfromprocess.env.- The frontend calls the function only after a successful booking.
- The dashboard displays the generated confirmation text and keeps the booking visible even if the AI step fails.
flight-lessons-app/
├── netlify/
│ └── functions/
│ └── get-confirmation.js
├── src/
│ ├── components/
│ ├── services/
│ ├── test/
│ │ └── get-confirmation.test.js
│ ├── App.jsx
│ ├── constants.js
│ ├── index.css
│ ├── main.jsx
│ └── supabaseClient.js
├── supabase/
│ └── schema.sql
├── .env.example
├── netlify.toml
├── package.json
└── README.md
- Install dependencies:
npm install
- Copy the values from
.env.exampleinto a local.envfile and provide:VITE_SUPABASE_URLVITE_SUPABASE_ANON_KEYSUPABASE_URLSUPABASE_ANON_KEYCLAUDE_API_KEY
- Apply
supabase/schema.sqlto your Supabase project. - Start the frontend locally:
npm run dev
- For end-to-end testing of the Netlify function locally, run through Netlify rather than plain Vite:
npx netlify-cli dev
Run the unit and integration tests:
npm testBuild the production bundle:
npm run build- Fixed lesson blocks were chosen instead of a free-form calendar so the booking model can stay simple and concurrency-safe.
- The browser uses only the Supabase anon key.
- The Claude API key never appears in client code and is only read by the Netlify function.
- The Netlify function now requires a valid Supabase bearer token before it will call Claude.
- The database derives
user_idfromauth.uid()and rejects any slot outside the two allowed flight blocks.
Double-booking is blocked at the database layer. The app inserts directly into the bookings table, and Postgres rejects any second insert that reuses the same time_slot_id because of the UNIQUE constraint. The frontend then catches that specific constraint violation and shows the user a polite retry message.
CLAUDE_API_KEYstays server-side only inside the Netlify function.- The confirmation endpoint requires a valid Supabase access token.
- Response bodies do not leak upstream Claude headers or secret values.
- Netlify response headers set a CSP, disable framing, and reduce referrer and browser capability exposure.
- RLS protects reads and inserts, and no update or delete policies are granted.
- Add live slot updates with Supabase Realtime.
- Add an admin view for staff-managed bookings.
- Add reminder email delivery.
- Add rescheduling and booking history pagination.