Dispense credit codes to event participants (hackathons, conferences, meetups). Admins create events with a list of eligible emails and a pool of unique codes; attendees visit the event's URL (e.g. /hackathon-1), enter their email, and receive a code if their email is on the list.
- Next.js (App Router) — deployed on Vercel
- Convex — database & realtime backend
- Clerk — admin authentication
- Tailwind CSS — monochromatic dark theme
- Admin creates an event (slug auto-generated from the name, or set/edited manually).
- Admin pastes in the list of eligible emails and the pool of unique codes.
- Attendees visit
/<slug>and sign in with Clerk (Google or email code) to prove they own their email address. - If the verified sign-in email is on the list, they're assigned an unclaimed code (idempotent — the same email always gets the same code back).
- If not, they can't claim.
/— public list of events (newest first)/<slug>— claim page for an event (requires signing in to verify email ownership)/admin— admin dashboard (Clerk-protected): create events/admin/events/<id>— manage an event: edit name/slug/description, emails, codes, see claim stats/sign-in— Clerk sign-in page (kept same-origin so protected-route redirects don't break client navigations)
npm install
cp .env.example .env.local # fill in Convex + Clerk keys
npx convex dev # in one terminal
npm run dev # in another- In Clerk, create a JWT template named
convex(see Convex Clerk docs). - Set the issuer domain on your Convex deployment:
npx convex env set CLERK_JWT_ISSUER_DOMAIN https://<your-app>.clerk.accounts.dev
Admin access is controlled by an email allowlist stored in Convex (admins table), managed at /admin/admins:
- Bootstrap: while the list is empty, any signed-in Clerk user is an admin. Sign in and add your own email to lock it down (the app forces your own email to be first, so you can't lock yourself out).
- Once the list has entries, only listed emails can use the admin dashboard or call admin Convex functions. The last remaining admin can't be removed.
- Emergency access: if you ever do get locked out, delete all rows from the
adminstable in the Convex dashboard to re-enter bootstrap mode.
The allowlist is per Convex deployment (dev and prod each have their own admins table).
Use npx convex deploy --cmd 'npm run build' as the build command (with CONVEX_DEPLOY_KEY set to a production deploy key) per the Convex Vercel guide — it pushes functions to the prod Convex deployment and injects NEXT_PUBLIC_CONVEX_URL at build time.
Clerk production instances require a domain you own, but the development instance works from any origin — so a vercel.app deploy runs fine on dev keys (with the "Development mode" badge and dev usage limits):
- In Vercel, set the dev keys (
pk_test_...,sk_test_...),NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in, andCONVEX_DEPLOY_KEY(production deploy key), withnpx convex deploy --cmd 'npm run build'as the build command. - Point the prod Convex deployment at the dev Clerk issuer:
npx convex env set CLERK_JWT_ISSUER_DOMAIN https://<your-app>.clerk.accounts.dev --prod
- First sign-in on the live site is bootstrap mode — add your admin email right away, then load events/emails/codes (prod data is separate from local dev).
Dev and prod are fully separate instances in both Clerk and Convex — none of the dev config carries over. The "Development mode" badge in the Clerk UI goes away once the app runs on production (pk_live_) keys.
Clerk (dashboard → create Production instance):
- Set your production domain; add the DNS records Clerk asks for (
clerk.<domain>Frontend API CNAME, plus the email DNS records if you use email-code sign-in). This must be a domain you own —*.vercel.appdomains can't be used because you can't create DNS records under them. A subdomain you control (e.g.credits.yourdomain.com) works. Enter it bare, withouthttps://. - Google OAuth in production requires your own credentials: create a Google OAuth client and paste its ID/secret into Clerk's Google connection (dev instances use Clerk's shared ones).
- Recreate the
convexJWT template on the production instance (same claims as dev — see Clerk + Convex auth above). - In Vercel, set
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY(pk_live_...),CLERK_SECRET_KEY(sk_live_...), andNEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in.
Convex (prod deployment):
- Set the issuer domain on the prod deployment — for a Clerk production instance this is your custom domain, not
*.clerk.accounts.dev:npx convex env set CLERK_JWT_ISSUER_DOMAIN https://clerk.<your-domain> --prod
- The prod
adminstable starts empty (bootstrap mode: any signed-in user is admin) — sign in and add your email first thing. - Events, emails, and codes live per deployment; re-create/upload them in prod.