Web apps

Tutor marketplace with booking, payments and messaging

Parents search tutors by subject, grade and postcode, then book and pay for lessons, with commission, ID checks and masked messaging.

nextjstypescriptsupabasepostgresstripeplaywright

Built 1 time, downloaded 0 times.

Install from your terminal

Run this in your project folder. It signs you in, shows what changes and asks before it overwrites a file.

npx claude-lab add tutor-marketplace-with-booking-payments-and-messaging-c076ad
11 filesDownload zip
CLAUDE.md
# Tutor marketplace

## Purpose

This is a tutor marketplace. Parents search by subject, grade and postcode,
view tutor profiles, and book and pay for a first lesson.
Tutors set weekly availability.
The platform takes 12% commission via Stripe Connect.

## Stack

- Next.js App Router, TypeScript strict, Server Actions
- Supabase: Postgres, Auth, Storage, RLS
- Stripe Connect Express
- Vitest for unit tests, Playwright for e2e
- Supabase CLI for local dev

## Commands

- `npm run dev`: start the app
- `npm run typecheck`
- `npm run lint`
- `npm run test`: Vitest
- `npm run test:e2e`: Playwright
- `npm run test:db`: pgTAP RLS tests
- `npx supabase start`: local Supabase (needs Docker)
- `npx supabase db reset`: local only, never against a remote project
- `npx supabase migration new <name>`
- `npx supabase gen types typescript --local > src/lib/database.types.ts`
- `stripe listen --forward-to localhost:3000/api/stripe/webhook`

## Directory map

- `src/app`: routes
- `src/server`: domain logic in `booking`, `payments`, `messaging`,
  `search` and `gdpr`
- `src/server/admin`: admin-only code, may use the service role key
- `supabase/migrations`: SQL migrations
- `supabase/tests`: pgTAP tests
- `src/lib/stripe.ts`: the only Stripe client

## Workflow

1. Read SPEC.md.
2. Work the current phase in PLAN.md.
3. Run the `verify` skill before saying done.
4. Use `/clear` between phases.

## Invariants

### Money

- Store money in integer minor units (pence or cents)
  with a currency column.
- Commission = round(amount * 0.12), computed in ONE function,
  `platformFee()`, and passed as `application_fee_amount`.
- Never use floats for money.

### Accounts and minors

- The account owner is always an adult parent.
- Students are `student_profiles` rows owned by a parent.
  They have no auth login.
- Never collect a student's email or phone.
- Store the minimum student data: first name, grade and subjects.

### Tutor visibility and ID documents

- A tutor profile is public only when `verification_status = 'approved'`.
  This is enforced in RLS, not only in the UI.
- ID documents live in the private bucket `id-documents`,
  readable only by the owner and admins.
- ID documents are deleted 30 days after a decision.

### Messaging

- Contact details stay hidden until the parent and tutor have
  at least one booking with `payment_status = 'paid'`.
- Masking runs server-side in `src/server/messaging/mask.ts`
  before insert.
- It covers emails, phone numbers (including spaced or written-out
  digits), and URLs or handles.
- Never mask only on render.

### Cancellation

- More than 24h before the start: full refund to the parent,
  no fee kept.
- Within 24h: the lesson is charged in full.
- Tutor cancellation: always a full refund.
- The rule lives in `src/server/booking/cancellation.ts`, with tests
  at the boundary (24h exactly, DST change, timezone).

### Time and booking

- Store times as `timestamptz` in UTC.
- Availability is stored as weekly rules in the tutor's IANA timezone
  and expanded to slots server-side.
- Double-booking is prevented with a Postgres exclusion constraint,
  not app checks.

### Stripe

- Express connected accounts.
- Destination charges with `transfer_data.destination`
  and `on_behalf_of`.
- Payout schedule is weekly, set on the connected account.
- Webhooks are the source of truth for payment state.
- Verify the webhook signature.
- Handlers are idempotent, keyed on `event.id`
  in a `stripe_events` table.

### Search

- Postgres only for now, no map.
- Postcode outward-code or prefix match plus subject and grade filters.
- Index with GIN and btree.
- Keep p95 under 200ms on 10k seeded tutors.
- No PostGIS until a later phase decides on it.

### Database security

- Every new table gets RLS enabled in the same migration
  plus a pgTAP test in `supabase/tests`.
- Never use the service role key in client components.
  Use it only in `src/server/admin` and in webhook routes.

### Secrets

- Secrets come from `.env.local` only:
  `NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY`,
  `SUPABASE_SERVICE_ROLE_KEY`, `STRIPE_SECRET_KEY`,
  `STRIPE_WEBHOOK_SECRET`.
- Never print or commit them.

## Gotchas

- Run `supabase gen types` after every migration.
- Next.js caches fetches by default.
  Mark authenticated pages dynamic.
- Stripe test clocks are needed to test weekly payouts
  and 24h windows.

## Review checklist

@REVIEW.md
Use for my project