mirror of
https://github.com/hanzoai/waitlist.git
synced 2026-10-30 18:18:18 +00:00
Three concerns were braided inside <Waitlist>:
1. Joining (POST + form state machine)
2. The gamified post-join view (rank + points + share + invite)
3. Persistence (a localStorage key shape duplicated in the host page)
Pulled them apart. Each is independently useful; <Waitlist> is now a
thin composer of the three.
New surface — every piece exported
• <WaitlistJoin waitlist baseUrl onSuccess onError ...> — pure form.
No localStorage, no view machine. Drops a POST and notifies. Useful
when the consumer wants a custom success page or a multi-step flow.
• <WaitlistStatus entry waitlist baseUrl onEntryChange ...> — pure
presentational rank + points + share + invite. Pass it any entry;
it renders. No fetch on mount, no storage write. Useful on /me-style
pages where the entry is server-rendered.
• storage.ts — single owner of `hanzo-waitlist:<slug>` key shape:
usePersistedEntry(slug) — reactive hook (same-tab + cross-tab)
readPersistedEntry(slug) — sync getter (SSR-safe)
writePersistedEntry(slug,e) — single writer; emits a custom event
so same-tab subscribers update
clearPersistedEntry(slug) — sign-out helper
captureReferrerFromUrl() — pulls inbound ?ref= for later submit
readPendingReferrer() — what Join reads at submit time
entryKey(slug) — for consumers who need the raw key
<Waitlist> behavior is unchanged
It now: uses usePersistedEntry to know whether to render <WaitlistJoin>
or <WaitlistStatus>, calls writePersistedEntry on a successful join, and
re-fetches /status on mount when the cache says we're already in.
Demo page
apps/web/app/page.tsx dropped the inline localStorage parse and its ad-hoc
CachedJoin type. It calls usePersistedEntry(slug) — same hook the widget
uses — and the topbar pill flips reactively when the widget writes. No
prop drilling between widget and page. No duplicated key shape.
Rich Hickey nodding somewhere.