A modern web app for running monthly home poker games — RSVPs, waitlists, live buy-in tracking, balanced cash-outs, and lifetime player stats.
🃏 Live: pokerbros.xyz
- 🎴 Game management — schedule games, edit details, cancel with calendar updates
- 📋 Smart RSVPs — 8-seat limit with automatic waitlist promotion when someone drops
- 💰 Live buy-in tracking — admins record buy-ins and rebuys in real time during the game
- 📊 Cash-out validation — books must balance (total in = total out) before a game can be finalized
- 🏆 Lifetime player stats — leaderboards, biggest win/loss, profit history, ranking
- 📧 Email notifications — RSVP confirmations with
.icscalendar invites, waitlist promotions, game updates, cancellations (powered by Resend + React Email) - 🔐 Google OAuth — admin-only access for game/player management; superadmin role for elevated permissions
- 🚩 Feature flags — runtime toggles via the admin settings page (e.g., email safety mode)
- 📱 Mobile-first — casino glassmorphism design that works edge-to-edge on phones
| Layer | Choice |
|---|---|
| Framework | Next.js 16 (App Router, Server Components, Server Actions) |
| Language | TypeScript (strict) |
| Styling | Tailwind CSS — casino glassmorphism theme with gold accents |
| Icons | Phosphor Icons |
| Database | Supabase Postgres (with Row Level Security) |
| Auth | Supabase Auth + Google OAuth |
Resend + React Email + .ics calendar invites |
|
| Testing | Jest + React Testing Library |
| Hosting | Vercel |
| Package manager | pnpm |
- Node.js 18+
- pnpm
- Docker Desktop (for local Supabase)
- Supabase CLI
git clone https://github.com/goeric/pokerbros.git
cd pokerbros
pnpm installsupabase startCopy the API URL and anon key from the output — you'll need them next.
Create .env.local from the template:
cp .env.local.example .env.localFill in:
NEXT_PUBLIC_SUPABASE_URL=http://localhost:54321 # use localhost, NOT 127.0.0.1
NEXT_PUBLIC_SUPABASE_ANON_KEY=<from supabase start output>
NEXT_PUBLIC_APP_URL=http://localhost:3000
GOOGLE_CLIENT_ID=<your Google OAuth client id>
GOOGLE_CLIENT_SECRET=<your Google OAuth client secret>
RESEND_API_KEY=<optional — for email>After signing in with Google for the first time, promote yourself in Supabase Studio (http://localhost:54323) by running:
-- scripts/add-superadmin.sql
-- (edit the email to match your Google login)pnpm devOpen http://localhost:3000.
📖 Full setup walkthrough: SETUP_AUTH.md · Quick reference: LOCAL_SETUP_SUMMARY.md · Backend architecture: backend.md
pnpm dev # Dev server (localhost:3000)
pnpm build # Production build
pnpm start # Run production build locally
pnpm lint # Next.js ESLint
pnpm tsc --noEmit # Type-check (no emit)
pnpm test # All tests
pnpm test:watch # Watch mode
pnpm test:p0 # P0 critical tests (cash-out, RSVPs, auth)
pnpm test:coverage # Coverage report
supabase start # Start local Supabase stack (Docker)
supabase stop # Stop it
supabase db push # Apply pending migrations (preserves data — preferred)
supabase migration new <name> # Scaffold a new migration
⚠️ Never runsupabase db resetcasually — it wipes all local data. Usesupabase db pushfor incremental changes.
pokerbros/
├── app/ # Next.js App Router
│ ├── admin/ # Admin CMS (player + game management)
│ ├── auth/callback/ # OAuth callback route
│ ├── game/[id]/ # Game detail
│ │ ├── live/ # Live buy-in tracker
│ │ ├── cashout/ # Cash-out + validation
│ │ ├── results/ # Read-only results
│ │ └── action/ # Server actions for the game flow
│ ├── login/ # OAuth sign-in
│ ├── stats/ # Leaderboard
│ └── layout.tsx # Root layout (force-dynamic for auth freshness)
├── components/ # Reusable UI (GameCard, PodiumCard, Modal, …)
├── lib/
│ ├── auth-server.ts # Server-side auth helper (getServerAuth)
│ ├── auth-context.tsx # Client-side auth actions only
│ ├── supabase.ts # Browser Supabase client
│ ├── email/ # Resend integration + .ics generation
│ └── utils.ts # Currency, date, name formatters
├── emails/templates/ # React Email templates
├── supabase/migrations/ # Versioned SQL migrations
├── __tests__/ # Jest test suites (incl. P0 critical)
└── types/ # Shared TypeScript types
- Server-first auth.
getServerAuth()resolves the session in Server Components and is passed down as props — no client-side flash, no race conditions. - Server Actions for mutations. No REST/RPC layer; mutations live next to the pages that trigger them, with
revalidatePathfor cache invalidation. - Row Level Security. Public read where appropriate; writes gated to authenticated admins at the database level. Auth isn't just a frontend gate.
- Automatic live detection. Games flip to "live" once their scheduled start passes, even if the status hasn't been updated explicitly.
- Calendar integration. RSVP confirmations include a
.icsattachment with a stable UID so updates modify the same calendar event.
P0 (critical) suites cover the business logic with the highest blast radius:
- Cash-out validation — financial integrity (12 tests)
- RSVP auto-promotion — waitlist handling (8 tests)
- RSVP seat limit — 8-seat cap enforcement (7 tests)
- Authorization — session checks and admin-only actions (6 tests)
Before deploying to production, all of the following must pass:
pnpm test:p0
pnpm test
pnpm tsc --noEmit
pnpm buildDeployed to Vercel. The required production env vars are:
NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_ANON_KEYNEXT_PUBLIC_APP_URL— e.g.https://pokerbros.xyzRESEND_API_KEY
Google OAuth is configured via the Supabase Dashboard → Authentication → Providers. Make sure both https://pokerbros.xyz/auth/callback and any preview/www variants are in the allowed redirect URLs.
This started as a personal project for monthly home games, but PRs are welcome — bug fixes, polish, new features. Open an issue first for anything substantial.
MIT — use it for your own poker night.