Description
Context
The waitlist is already backed by Supabase (Docker + public.waitlist table) from a previous issue. The form (src/components/WaitlistForm.tsx) must submit to our API so that the data is stored only in the database (public.waitlist).
Prerequisites: Supabase local setup and public.waitlist table exist (migrations + seed applied). The Supabase client is available (src/lib/supabase.ts with fallbacks).
Form to connect
- File:
src/components/WaitlistForm.tsx
- Fields:
email (required), company (optional), message (optional, use case text)
- Goal: Submit to
POST /api/waitlist; data must be persisted only in public.waitlist.
- UX to keep: Validation (email format), loading state, success/error messages, form reset on success
- Honeypot: Hidden field for bot protection; if filled, do not submit
Goals
-
Add API route POST /api/waitlist
- Location:
src/app/api/waitlist/route.ts (or equivalent in your App Router structure).
- Behavior:
- Accept
POST with JSON body: { email: string, company_name?: string, use_case?: string }.
- Validate
email (required, valid format). Return 400 with a clear message if invalid or missing.
- Use the server-side Supabase client (
createServerClient() or client built with SUPABASE_SERVICE_ROLE_KEY) to insert one row into public.waitlist with email, company_name, use_case (all optional except email).
- On success, return
201 and optionally { ok: true } or similar.
- If Supabase returns a unique constraint violation on
email, return 409 Conflict with a message like "This email is already on the waitlist" so the frontend can show a friendly message.
- If Supabase client is using placeholders (no real credentials), the insert will fail; handle that gracefully (return
503 or 500 with a generic message like "Waitlist is temporarily unavailable").
- Security: Validate and sanitize input; do not expose internal errors. Optional: rate limiting or CAPTCHA in a follow-up.
-
Update WaitlistForm.tsx so data goes only to the database
- Endpoint:
POST /api/waitlist (fetch('/api/waitlist', { ... })).
- Body:
{ email, company_name: company, use_case: message } (map form fields company → company_name, message → use_case). Do not send the honeypot to the API; use it only on the client (if set, do not submit).
- UX: Required email, optional company and message, validation, loading state ("Submitting..."), success message ("Thank you! We will contact you soon."), form reset on success.
- Error handling:
- 409 (email already on waitlist): show "This email is already on the waitlist."
- 400: show validation message or "Please check your email and try again."
- 5xx / network error: show "Something went wrong. Please try again later."
Acceptance criteria
References
- Table schema:
public.waitlist with id, email (unique), company_name, use_case, created_at.
- Supabase server client: use the same pattern as in the Supabase setup issue (
createServerClient() in src/lib/supabase.ts).
- Existing form:
src/components/WaitlistForm.tsx (fields email, company, message; honeypot botField).
Description
Context
The waitlist is already backed by Supabase (Docker +
public.waitlisttable) from a previous issue. The form (src/components/WaitlistForm.tsx) must submit to our API so that the data is stored only in the database (public.waitlist).Prerequisites: Supabase local setup and
public.waitlisttable exist (migrations + seed applied). The Supabase client is available (src/lib/supabase.tswith fallbacks).Form to connect
src/components/WaitlistForm.tsxemail(required),company(optional),message(optional, use case text)POST /api/waitlist; data must be persisted only inpublic.waitlist.Goals
Add API route
POST /api/waitlistsrc/app/api/waitlist/route.ts(or equivalent in your App Router structure).POSTwith JSON body:{ email: string, company_name?: string, use_case?: string }.email(required, valid format). Return400with a clear message if invalid or missing.createServerClient()or client built withSUPABASE_SERVICE_ROLE_KEY) to insert one row intopublic.waitlistwithemail,company_name,use_case(all optional exceptemail).201and optionally{ ok: true }or similar.email, return409 Conflictwith a message like "This email is already on the waitlist" so the frontend can show a friendly message.503or500with a generic message like "Waitlist is temporarily unavailable").Update
WaitlistForm.tsxso data goes only to the databasePOST /api/waitlist(fetch('/api/waitlist', { ... })).{ email, company_name: company, use_case: message }(map form fieldscompany→company_name,message→use_case). Do not send the honeypot to the API; use it only on the client (if set, do not submit).Acceptance criteria
POST /api/waitlistexists, validatesemail, inserts intopublic.waitlistvia Supabase server client, returns201on success.409whenemailis already in the table (unique constraint) with a clear body/message.WaitlistFormsubmits toPOST /api/waitlistwith body{ email, company_name, use_case }; honeypot and client-side validation unchanged.public.waitlist); no other submission target.References
public.waitlistwithid,email(unique),company_name,use_case,created_at.createServerClient()insrc/lib/supabase.ts).src/components/WaitlistForm.tsx(fieldsemail,company,message; honeypotbotField).