Getting started
Setup & deploy
From a fresh clone to a live deployment. A working setup takes about 30–60 minutes.
Quick start
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.
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.
Run it
npm install then npm run dev, and open localhost:3000. Create your account — your business and booking page are generated automatically.
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
| Variable | Required |
|---|---|
| NEXT_PUBLIC_SUPABASE_URL | Required |
| NEXT_PUBLIC_SUPABASE_ANON_KEY | Required |
| SUPABASE_SERVICE_ROLE_KEY | Required |
| NEXT_PUBLIC_SITE_URL | Required |
| ADMIN_EMAILS | Required |
| RESEND_API_KEY / EMAIL_FROM | Optional |
| STRIPE_SECRET_KEY / STRIPE_WEBHOOK_SECRET | Optional |
| STRIPE_PRO_PRICE_ID | Optional |
| ZOOM_ACCOUNT_ID / CLIENT_ID / CLIENT_SECRET | Optional |
| CRON_SECRET | Optional |
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).