# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview Super Bowl Squares — a web application that runs a 10x10 grid-based betting game for the Super Bowl. Players purchase squares, random numbers (0-9) are assigned to each row/column for AFC and NFC teams, and winners are determined by the last digit of each team's score at the end of each quarter. This is a rewrite of a legacy PHP/MySQL application (preserved in `legacy/` for reference). The legacy app has significant security issues (SQL injection, plaintext passwords, no CSRF protection) and uses early-2000s HTML patterns. ## Tech Stack - **Framework**: Next.js 14 (App Router) with TypeScript - **Database**: PostgreSQL 16 via Prisma ORM - **Auth**: NextAuth.js with JWT strategy and credentials provider - **Styling**: Tailwind CSS with custom theme (dark mode, `primary`/`accent` color scales, glow shadows) - **Real-time**: WebSocket server (ws) for live chat, mounted at `/ws/chat` - **Deployment**: Docker Compose (app + postgres) ## Development Commands ```bash # Start everything (build + run with Docker Compose) docker compose up --build -d # View logs docker compose logs app --tail 50 docker compose logs app -f # follow # Rebuild after code changes docker compose up --build -d # Stop docker compose down # Database operations (run from host with node_modules present) npx prisma db push # sync schema to DB npx prisma generate # regenerate client after schema changes npx tsx prisma/seed.ts # seed default data npm run db:migrate # deploy prisma migrations (production) npm run db:generate # regenerate Prisma client (alias) # Dev server (requires local postgres with DATABASE_URL pointing to it) npm run dev # runs tsx server.ts -> server.js ``` **Note**: The app runs inside Docker where `DATABASE_URL` points to the `db` service. For local dev outside Docker, update `DATABASE_URL` to `postgresql://superbowl:superbowl@localhost:5432/superbowl`. ## Environment Variables Required variables (see `.env.example`): - `DATABASE_URL` — PostgreSQL connection string - `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB` — Database credentials - `NEXTAUTH_SECRET` — Random secret for JWT signing (generate with `openssl rand -base64 32`) - `NEXTAUTH_URL` — Full URL where app is deployed (e.g., `http://localhost:3000`) ## Architecture ### Custom Server (`server.js`) Next.js runs behind a custom HTTP server that also handles WebSocket upgrades. This is required for the live chat and real-time grid update features. The server has two boot modes: - **Dev mode** (`NODE_ENV !== 'production'`): Uses the standard `next()` API with `app.prepare()`, creates its own HTTP server, and adds WebSocket upgrade handling on `/ws/chat`. - **Production standalone mode**: Monkey-patches `http.createServer` to intercept the HTTP server that Next.js's `startServer()` creates, injecting WebSocket upgrade handling for `/ws/chat` before Next.js registers its own upgrade handler. Reads the embedded `nextConfig` from `server.standalone.js` (saved during Docker build) and sets `__NEXT_PRIVATE_STANDALONE_CONFIG` env var so Next.js skips webpack loading. The server also runs: - Chat message broadcasting with blacklist filtering; user identity resolved by looking up `userId` (sent from client session) in the DB — the NextAuth session cookie is HttpOnly so the JWT cannot be read client-side - `squares:changed` → `squares:refresh` broadcast for real-time grid updates - Payment reminder scheduler (15-minute interval) that checks unconfirmed squares approaching grace period deadline `server.ts` is just a dev shim that requires `server.js`. **Critical Dockerfile detail**: The standalone build output includes its own `server.js`. The Dockerfile saves it as `server.standalone.js` then copies our custom `server.js` over it. Extra node_modules not included in standalone output (`ws`, `nodemailer`, `next-auth`, `jose`, `@panva`, `uuid`, `@babel`, `preact`, `oauth`, `openid-client`, `cookie`) are explicitly copied in the Dockerfile. ### Authentication Flow - `src/lib/auth.ts` — NextAuth config with credentials provider (email/password, bcrypt) - `src/middleware.ts` — Route protection: public routes (`/`, `/login`, `/register`, `/signup`, `/setup`, `/api/auth`, `/api/setup`), admin routes require ADMIN or VIEWER role, `/my-squares` requires any authenticated user - `src/types/index.ts` — Augments NextAuth session/JWT types with `role` and `id` - JWT tokens carry `role` (ADMIN/VIEWER/PLAYER) and `id` fields ### Database Pattern - Prisma schema at `prisma/schema.prisma` - Singleton pattern: `GameSettings`, `Score`, and `EmailSettings` use `id: "singleton"` — there's always exactly one row - `prisma/seed.ts` — Seeds 100 squares (positions "00"-"99"), default settings, default score row, email settings, and 8 email templates (welcome, square_confirmation, square_confirmed, square_released, payment_reminder, numbers_assigned, game_results, custom). Uses upserts so it's idempotent. Email templates use `update: {}` to preserve manual edits (only creates, never overwrites). - Shared Prisma client at `src/lib/prisma.ts` (global singleton to avoid connection exhaustion in dev) ### Page Structure - `/` — Main page: 10x10 grid + chat window. Redirects to `/setup` if no admin exists. - `/setup` — First-run admin account creation - `/login`, `/register` — Auth pages - `/signup?squares=01,02,...` — Square purchase form (guest or authenticated) - `/my-squares` — Player's own squares (requires auth) - `/admin/*` — Admin panel with sidebar nav (`layout.tsx` is a client component with role-based nav filtering) Admin sub-pages: dashboard, squares, numbers, scores, balance, settings, users, email, chat, backup. Viewers are restricted from settings, users, and backup. ### API Routes (`src/app/api/`) All API routes use `getServerSession(authOptions)` for auth checks. Pattern: GET for reads, POST for creates, PUT for full updates, PATCH for partial updates. Key routes: - `squares/` — GET (list all), POST (purchase), PATCH (admin: confirm/release/reserve/bulk-reserve/edit) - `settings/` — GET (public), PUT (admin-only, handles payment methods separately) - `numbers/` — POST generates random 0-9 shuffles for NFC/AFC axes - `scores/` — PUT updates quarter scores and determines winners - `backup/` — Export/import game data - `upload/` — Image upload for team/SB logos ### Component Patterns - Server components fetch data directly via Prisma (e.g., `page.tsx` for `/`) - Client components use `'use client'` and fetch via API routes - `src/components/Providers.tsx` wraps app with `SessionProvider` + `WebSocketProvider` + `ToastProvider` - Grid components: `SquareGrid` (10x10 table with real-time WS refresh), `SquareCell` (individual cell), `GridHeader` (team logos/matchup banner/event info) - `src/lib/ws.tsx` — React Context `WebSocketProvider` shares a single WebSocket connection app-wide. Exports `useWebSocket()` hook returning `{ messages, connected, sendMessage, deleteMessage, squaresVersion, notifySquaresChanged }`. The `squaresVersion` counter increments on `squares:refresh` events; components watch it to re-fetch grid data. ### Styling Conventions - Dark theme throughout: `bg-gray-950` base, `bg-gray-900` cards - Custom Tailwind classes in `globals.css`: `.btn-primary`, `.btn-secondary`, `.btn-danger`, `.btn-success`, `.input-field`, `.card`, `.card-elevated` - Custom color scales: `primary` (blue), `accent` (purple), `success`, `warning`, `danger` - Custom glow shadows: `shadow-glow-sm`, `shadow-glow`, `shadow-glow-lg`, `shadow-glow-green`, `shadow-glow-amber` ### Docker Setup - `Dockerfile` — Multi-stage build: builder (npm install, next build, compile seed script) -> runner (standalone output, prisma CLI for runtime migrations). The runner stage saves the original standalone `server.js` as `server.standalone.js` then copies our custom `server.js` over it, and copies extra node_modules not included in standalone output. - `docker-entrypoint.sh` — Runs `prisma db push`, seeds DB, then starts `node server.js` - `docker-compose.yml` — Two services: `app` (port 3000) and `db` (postgres:16-alpine, port 5432, healthcheck) ### Next.js Configuration `next.config.js` settings: - `output: 'standalone'` — Minimal production build with embedded dependencies for Docker - `serverComponentsExternalPackages: ['nodemailer']` — Prevents bundling nodemailer (requires native modules) - `images: { unoptimized: true }` — Disables Next.js image optimization for simpler Docker deployment ### Real-time Updates Two real-time channels share a single WebSocket connection per client: - **Chat**: Messages are stored in DB and broadcast to all connected clients - **Grid refresh**: When squares change (purchase, admin action), the acting client sends `{type: 'squares:changed'}` via WS. The server broadcasts `{type: 'squares:refresh'}` to all clients. Clients increment `squaresVersion` which triggers a re-fetch of `/api/squares`. ### Path Alias `@/*` maps to `./src/*` (configured in `tsconfig.json`). Use `@/lib/prisma`, `@/components/ui/Button`, etc. ## Game Logic - 100 squares in a 10x10 grid, positions "00" through "99" (row digit + column digit) - Row = NFC team axis, Column = AFC team axis - Numbers (0-9) are randomly shuffled independently for each axis; can only be generated when all 100 squares are claimed - Winners: last digit of each team's quarter score maps to the grid number → intersecting square wins - Four quarters: Q1 (first), Q2 (half), Q3 (third), Final — each with configurable payout percentage - Max 10 squares per purchase submission - Squares can be guest-purchased (name+email) or purchased by authenticated players ## Feature Requirements ### Role-Based Access (3 roles) **Admin** (full control): manage all accounts, game settings (bet amount, teams, logos, payouts, payment methods, rules), generate numbers, edit squares, edit scores, email system, backup/restore, chat moderation **Viewer** (limited admin — for co-commissioners): view balance sheet, edit squares, update scores, send emails, moderate chat **Player** (authenticated participant): view own squares, purchase squares, live chat, change own name/password ### Email System - Templates with `{{placeholder}}` variables (stored in DB, seeded with defaults) - Configurable SMTP/SSL transport via `EmailSettings` - `src/lib/email.ts` — `sendEmail()`, `renderTemplate()`, `getTransporter()` core functions - `src/lib/autoEmail.ts` — Fire-and-forget automated emails triggered by game events: - `sendWelcomeEmail` — on user registration (from `POST /api/users`) - `sendPurchaseConfirmationEmail` — on square purchase (from `POST /api/squares`) - `sendSquareConfirmedEmail` / `sendSquareReleasedEmail` — on admin confirm/release (from `PATCH /api/squares`) - `sendNumbersAssignedEmails` — to all participants when numbers generated (from `POST /api/numbers`) - `sendGameResultsEmails` — to all participants when final score entered (from `PUT /api/scores`) - `checkPaymentReminders` — scheduled in server.js every 15 minutes, sends reminders 2 hours before grace period deadline - Admin Send tab (`POST /api/email`) populates all template variables per-recipient from DB (squares, amount, payment info, winners) - Available template variables: `{{name}}`, `{{email}}`, `{{squares}}`, `{{amountDue}}`, `{{commissioner}}`, `{{eventName}}`, `{{gameUrl}}`, `{{graceHours}}`, `{{paymentInstructions}}`, `{{paymentMethods}}`, `{{winners}}`, `{{rulesText}}` ### Live Chat - WebSocket-based, embedded on front page (YouTube/Twitch live-chat style) - Blacklist-based word filtering - Admin/viewer can delete messages ### Backup/Restore - Config-only or full (config + square/game data) export/import ## Testing No testing framework is currently configured. To add tests, consider installing Jest or Vitest with React Testing Library. ## Assets Team logos in `public/images/`: `afc-{team}.png` and `nfc-{team}.png` for all 32 NFL teams, plus conference logos, generic placeholders, Super Bowl event logos, and background images.