mirror of
https://github.com/hanzo-apps/waitlist.git
synced 2026-10-30 14:48:32 +00:00
Drops the Next.js + Upstash Redis stack. The viral waitlist now ships as a brand-neutral React widget against a Hanzo Base Go plugin. What changed - packages/widget/: new @hanzo/waitlist — React component, framework-free fetch client, drop-in <hanzo-waitlist> custom element. Zero brand colors, zero logos, zero brand names. Consumer brings logo via `logo` prop or `<slot name="logo">`. Theming via --hw-* CSS variables with fallback chain at usage so ancestor selectors win cleanly. - apps/web/: Next.js demo rebuilt monochrome. Five generic presets (Neutral, Preset A/B/C/D) prove brand swap works without per-brand builds. Demo's placeholder logo lives in the demo, not the package. - apps/api/: removed. Backend logic now lives in ~/work/hanzo/base/plugins/waitlist (committed separately). - packages/sdk/: removed (Preact + Vite). Replaced by packages/widget. - docs/: collapsed to architecture / api / widget. Old planning docs moved to docs/archive/. - Latest deps: Next 16, React 19.2, Vite 8, TS 5.9, vite-plugin-dts 5, @vitejs/plugin-react 6, eslint-config-next 16. Build output - ESM 6.76kB, CJS 5.22kB, IIFE 196.99kB / 62.65kB gzip - Widget styles: 3.89kB / 1.18kB gzip - Demo first-load: 106kB
1.7 KiB
1.7 KiB
API
Three endpoints, mounted by plugins/waitlist at /v1/waitlist/*.
All paths are relative to BASE_API_PREFIX (default /v1). The three
endpoints below assume the default.
POST /v1/waitlist/join
Register a new entry, or return the existing one if (waitlist, email) already exists. Idempotent.
Request
{
"waitlist": "my-product",
"email": "[email protected]",
"referrerCode": "Bcd6789F",
"turnstileToken": "0.AAAA..."
}
waitlist is the slug or the record id. referrerCode and
turnstileToken are optional.
Response
{
"ok": true,
"waitlist": "my-product",
"email": "[email protected]",
"refCode": "Jk9mNpQr",
"rank": 42,
"total": 1234,
"referralCount": 0,
"shareUrl": "?ref=Jk9mNpQr",
"alreadyJoined": false
}
alreadyJoined: true means the email already existed; the response
returns the existing entry unchanged.
Errors
400invalid email / disposable domain / missing fields / captcha failed404waitlist slug not found429per-IP rate limit exceeded500storage error
GET /v1/waitlist/status?waitlist=&email=
Look up an entry's current rank.
Response
{
"ok": true,
"waitlist": "my-product",
"email": "[email protected]",
"refCode": "Jk9mNpQr",
"rank": 42,
"total": 1234,
"aheadOf": 1192,
"referralCount": 3,
"shareUrl": "?ref=Jk9mNpQr"
}
GET /v1/waitlist/export?waitlist=
Admin CSV dump. Either:
Authorization: Bearer <WAITLIST_ADMIN_SECRET>header, or- a Base superuser session.
Response is text/csv with columns:
rank,email,refCode,referredBy,referralCount,createdAt
Rank semantics
Rank = 1 + count(entries with (referralCount > me) OR (referralCount = me AND createdAt < me)). Ties go to the earlier joiner.