Getting started

Setup & deploy

From a fresh clone to a live deployment. A working setup takes about 30–60 minutes.

Quick start

1

Create a Supabase project

Sign up at supabase.com → New project. In the SQL Editor, paste the entire supabase/schema.sql and run it — this creates every table, Row Level Security policy, the auto-onboarding trigger, and the storage bucket.

2

Add environment variables

Copy .env.local.example → .env.local and fill in your three Supabase keys, NEXT_PUBLIC_SITE_URL, and ADMIN_EMAILS. Email, payments, and Zoom are optional and can be added anytime.

3

Run it

npm install then npm run dev, and open localhost:3000. Create your account — your business and booking page are generated automatically.

4

Deploy to Vercel

Import the repo (or use the Vercel CLI), add the same env vars, and deploy. Then set your Supabase Auth Site URL + /auth/callback redirect to your live URL.

The full step-by-step guide (with every click) ships in SETUP.md.

Integrations

Every integration is optional and independent — the app degrades gracefully without any of them.

Email — Resend

Add RESEND_API_KEY + EMAIL_FROM. For real delivery, verify your domain in Resend. Powers confirmations, reminders, and team invites.

Payments — Stripe

Enable Connect, then add your secret + webhook keys. Optionally create a monthly Pro price for the platform subscription.

Video — Zoom

Create a Server-to-Server OAuth app with the meeting:write:admin scope; add the three ZOOM_ keys to auto-generate meeting links.

Reminders — Vercel Cron

A daily job (vercel.json) emails tomorrow’s customers. Set CRON_SECRET in Vercel to secure it.

Environment variables

VariableRequired
NEXT_PUBLIC_SUPABASE_URLRequired
NEXT_PUBLIC_SUPABASE_ANON_KEYRequired
SUPABASE_SERVICE_ROLE_KEYRequired
NEXT_PUBLIC_SITE_URLRequired
ADMIN_EMAILSRequired
RESEND_API_KEY / EMAIL_FROMOptional
STRIPE_SECRET_KEY / STRIPE_WEBHOOK_SECRETOptional
STRIPE_PRO_PRICE_IDOptional
ZOOM_ACCOUNT_ID / CLIENT_ID / CLIENT_SECRETOptional
CRON_SECRETOptional

Security & architecture

Multi-tenant isolation

Every row is scoped to a business via Postgres Row Level Security — one business can never read or write another’s data. The public booking page reads and writes through the server (service-role) so customers never touch other tenants’ data.

Server-enforced permissions

Team roles are enforced in the UI and on the server: every mutating action checks the member’s permission, and the database restricts direct writes to owners — so a limited member can’t escalate via the API.

Hardened surface

Cryptographically-random booking tokens, HTML-escaped email templates, owner-gated billing, a secret rotatable calendar-feed token, and a fail-closed cron endpoint.

Going live

  • Turn "Confirm email" back ON in Supabase Auth.
  • Verify your sending domain in Resend and point EMAIL_FROM at it.
  • Activate Stripe, switch to live keys, and create a live-mode webhook.
  • Re-create the Pro price in live mode (if you use subscriptions).
  • Set CRON_SECRET in Vercel, point NEXT_PUBLIC_SITE_URL at your domain, and redeploy.

Troubleshooting

Everything shows sample data / no login

You’re in demo mode — the Supabase env variables aren’t set. Add them to switch to the real app.

Signup says "check your email"

Turn OFF "Confirm email" in Supabase Auth for testing, or ensure your Redirect URLs include /auth/callback.

Emails not arriving

On the shared Resend sender you can only email your own account address — verify a domain to reach anyone. New domains may land in spam at first; check Resend → Logs.

Payment button doesn’t appear

The service price is 0, the business hasn’t connected Stripe, or STRIPE_SECRET_KEY is unset. Priced services fall back to free until Stripe is connected.

column ... does not exist

The schema didn’t fully run — re-run supabase/schema.sql (safe on a fresh project).