Drop-in email waitlist with position tracking and optional referral loop for Next.js apps.
npm install @cyguin/waitlist// lib/waitlist.ts
import { createSQLiteAdapter } from '@cyguin/waitlist/adapters/sqlite';
export const waitlistAdapter = createSQLiteAdapter();// app/api/waitlist/[...cyguin]/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { createWaitlistHandler } from '@cyguin/waitlist/next';
import { waitlistAdapter } from '@/lib/waitlist';
const handler = createWaitlistHandler({ adapter: waitlistAdapter });
export async function GET(req: NextRequest) {
return handler(req);
}
export async function POST(req: NextRequest) {
return handler(req);
}// app/page.tsx
import { WaitlistForm } from '@cyguin/waitlist';
export default function Home() {
return (
<main>
<h1>Join the waitlist</h1>
<WaitlistForm
placeholder="Enter your email"
buttonText="Join waitlist"
/>
</main>
);
}Join the waitlist.
Request:
{
"email": "user@example.com",
"referred_by": "TOKEN" // optional
}Response 201:
{
"id": "abc123",
"email": "user@example.com",
"position": 42,
"referral_token": "abc123"
}Response 409 — email already registered:
{ "error": "already_registered" }Get your position on the waitlist.
Response 200:
{
"id": "abc123",
"email": "user@example.com",
"position": 42,
"referral_token": "abc123",
"joined_at": 1712000000000
}Response 404 — not found:
{ "error": "not_found" }When a user joins via ?ref=TOKEN, their entry records who referred them. Position is never adjusted based on referral count — referrals are informational only.
- On
POST, areferral_token(the entry'sid) is returned. - Share links like
https://yoursite.com/?ref=TOKEN. - New joins with
?ref=TOKENstore the referrer's id.
| Prop | Type | Default | Description |
|---|---|---|---|
className |
string |
'' |
CSS class for the form container |
placeholder |
string |
'Enter your email' |
Input placeholder text |
buttonText |
string |
'Join waitlist' |
Button label |
redirectTo |
string |
— | If provided, redirects here after success with ?ref=TOKEN |
theme |
'light' | 'dark' |
'dark' |
Visual theme. Pass 'light' to opt into the light theme. |
onSuccess |
(data: JoinResponse) => void |
— | Callback on successful join |
onError |
(error: string) => void |
— | Callback on error |
All styling defaults to the cyguin dark theme and uses --cyguin-* CSS custom properties. Default tokens:
--cyguin-bg: #0a0d17
--cyguin-bg-subtle: #101521
--cyguin-border: #252b3a
--cyguin-border-focus: #f5a800
--cyguin-fg: #f1f3f6
--cyguin-fg-muted: #888888
--cyguin-accent: #f5a800
--cyguin-accent-dark: #c47f00
--cyguin-accent-fg: #0a0a0a
--cyguin-radius: 6px
--cyguin-shadow: 0 1px 4px rgba(0,0,0,0.08)Apply a light theme with the theme prop:
<WaitlistForm theme="light" />| Export | Description |
|---|---|
@cyguin/waitlist |
Main package entry — types, adapters, handler |
@cyguin/waitlist/next |
Next.js route handler for API route |
@cyguin/waitlist/react |
WaitlistForm component |
@cyguin/waitlist/adapters/sqlite |
SQLite adapter (better-sqlite3) |
@cyguin/waitlist/adapters/postgres |
Postgres adapter (postgres.js) |