KICKOFF is a football social super-app built with Next.js, React, TypeScript, Zustand, and Prisma.
The app combines:
- Social feed – posts, replies, reposts, polls, bookmarks, hashtags, mentions
- Live match center – ticker, xG, timeline, momentum, shot map, reminders
- Chat rooms – group and direct messages
- Discovery – players, transfers, standings, trending
- AI – FootballGPT (Claude + xAI), scout reports, match previews
- Profile – XP/level/streak/badges, notifications, settings
Design: Dark-first UI with a full black theme; emerald green accents. Light mode supported. Responsive layout with sidebar navigation.
- Framework: Next.js 16 (App Router)
- UI: React 19 + Tailwind CSS + Radix UI primitives
- Language: TypeScript (strict mode)
- State: Zustand + persist middleware
- Data layer: Prisma ORM + SQLite (default local db)
- AI: Anthropic Claude via server-side API routes
- Testing: Vitest + Testing Library, Playwright (theme/accessibility audit)
- Analytics: Vercel Analytics
Theme: Full black dark mode (#000000 / #0a0a0a), emerald green accents (#22c55e). CSS variables in app/globals.css; Tailwind theme in tailwind.config.ts. Browser theme color follows light/dark preference.
- Node.js 20+ recommended
- npm
npm installCreate .env from .env.example:
cp .env.example .envSet values (see .env.example for a full template):
DATABASE_URL– Prisma (default SQLite:file:./dev.db)ANTHROPIC_API_KEY– FootballGPT with Claude (Anthropic Console)XAI_API_KEY– FootballGPT with xAI (Grok) (xAI Console)NEXT_PUBLIC_SUPABASE_URLandNEXT_PUBLIC_SUPABASE_ANON_KEY– optional; for Supabase (Auth/DB). From project Settings → API.
npm run db:migrate
npm run db:seedTo seed the production database (e.g. on Vercel), set DATABASE_URL to your production connection string and run the same command once:
DATABASE_URL="your-production-url" npm run db:seed
Profile (name, handle, bio, avatar, header), posts, bookmarks, likes, and comments are stored in the browser via Zustand + localStorage. Reloading the page keeps your data on the same device.
For cross-device sync and real multi-user behaviour (e.g. other users see your posts, one account on phone and desktop), add a backend such as Supabase. The app includes @supabase/supabase-js and a client in lib/supabase/client.ts. Set NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY in .env (from the Supabase dashboard → Settings → API), then use getSupabaseClient() where Supabase is optional or createClient() where it is required.
npm run devOpen: http://localhost:3000
npm run dev- Start Next.js dev servernpm run build- Production buildnpm run start- Run built appnpm run lint- ESLint checksnpm run test- Run Vitestnpm run test:coverage- Vitest with coverage reportnpm run test:ui- Run Playwright UI tests (theme audit, etc.)npm run discover-routes- Discover app routes intotests/ui/routes.json(used by theme audit)npm run audit:theme- Run Dark Mode UI audit (discover routes + Playwright theme-audit tests)npm run db:migrate- Run Prisma migrationsnpm run db:seed- Seed local DBnpm run db:studio- Open Prisma Studio
/feed- Main social feed/chat- Chat room list/chat/[id]- Single chat room/matches- Live and upcoming matches/ai- FootballGPT chat/discovery- Players, transfers, standings, trending/profile- Current user profile, XP, badges, bookmarks/settings- Account, notifications, favorites, theme
/player/[id]- Player profile + AI scout report/club/[id]- Club profile + squad/match/[id]- Match detail + related posts (legacy data path)/user/[id]- User profile (legacy data path)
/design- Design component demo wrapper/kickoff- Legacy standalone prototype page/- Redirects to/feed
POST /api/footballgpt- AI football Q&A
- Input validation and length limits
POST /api/scout-report- AI scout report for player payload
POST /api/match-preview- AI fixture preview
GET /api/messages?roomId=...- Fetch room messages
POST /api/messages- Send message
GET /api/posts- Fetch feed posts
POST /api/posts- Create post
DELETE /api/posts/[id]- Delete own post
POST /api/posts/[id]/like- Toggle like
POST /api/posts/[id]/repost- Toggle repost
GET /api/posts/[id]/reply- Fetch replies
POST /api/posts/[id]/reply- Create reply
store/feedStore.ts- Posts, bookmarks, poll voting, replies, repost/undo, mute/block/hide, trending topics
store/matchStore.ts- Live matches, upcoming fixtures, predictions, reminders, minute ticker
store/chatStore.ts- Rooms, active room, room messages, read state (persisted per-room history so messages survive refresh)
store/userStore.ts- Current user, notifications, settings, XP/levels, streaks, badges, follows
store/uiStore.ts- Global UI state (post modal, sidebar open)
store/toastStore.ts- Global toast with optional undo callback
Defined in lib/constants.ts:
kickoff-feedkickoff-userkickoff-matcheskickoff-chatkickoff-uikickoff-last-streak-datekickoff-ai(FootballGPT conversation history)
Local storage access is wrapped with safe utilities in lib/safeStorage.ts.
Canonical shared types are in types/index.ts, including:
User,Post,Reply,Poll,Match,Player,TransferNotification,Prediction,ChatRoom,Message,AppSettings- Legacy interfaces used by older pages (
Legacy*)
Schema: prisma/schema.prisma
Models:
UserPost(supports reply threading viaparentId)LikeUserRepostMessage
Prisma client output is generated to:
lib/generated/prisma
Automated checks for dark mode and accessibility across all navigable routes.
-
Route discovery
scripts/discoverRoutes.tsfinds everyapp/**/page.tsx(andpage.jsx), ignores route groups like(auth)/(marketing), and writestests/ui/routes.json. Dynamic segments (e.g.[id],[tag]) are filled with placeholders fromscripts/audit-routes.config.ts. -
Playwright tests (
tests/ui/theme-audit.spec.ts)
For each route (except those that require auth, which are skipped with a clear reason):- Light and dark mode
Each page is opened with the correct theme:localStorage.themeis set before load, and the documentdarkclass is applied or removed so Axe runs against the intended theme. - Axe
Axe runs with WCAG 2 AA tags. The rules color-contrast, nested-interactive, and scrollable-region-focusable are disabled to avoid flakiness from theme timing and known UI patterns; other accessibility violations fail the run. - Screenshots
A full-page screenshot is taken in dark mode and compared to the last accepted snapshot. Changes cause a failure (regression).
- Light and dark mode
-
Auth
Routes listed as protected inscripts/audit-routes.config.ts(e.g./settings,/profile/edit) redirect to login when unauthenticated. They are skipped in the audit with a message like: "Requires auth (redirects to login); add test user or run with auth state to include." To audit them, run the app with a logged-in session and use a Playwright storage state (or add a test-user login helper) and extend the config.
-
First time: install Playwright browsers (e.g. Chromium):
npx playwright install chromium
-
Generate routes (optional; also run automatically by
audit:theme):npm run discover-routes
-
Run the theme audit (starts dev server if needed, then runs the audit):
npm run audit:theme
Or run all Playwright UI tests (same theme-audit spec if that’s all you have):
npm run test:ui
For the first run, accept the dark-mode snapshots as baseline:
npx playwright test tests/ui/theme-audit.spec.ts --update-snapshots -
CI
- Run the app (e.g.
npm run build && npm run start), then run the audit against that URL, e.g.:PLAYWRIGHT_BASE_URL=http://localhost:3000 npm run audit:theme
- Or start the app in the background and use
PLAYWRIGHT_BASE_URL; Playwright will not start its ownwebServerwhenCIis set. - Commit
tests/ui/snapshots/theme-audit/(andtests/ui/routes.jsonif you want) so CI can compare screenshots and fail on contrast or visual regression.
- Run the app (e.g.
- Axe violations
The test output and report (e.g.playwright-report/index.html) list failing rules and nodes. Fix contrast (or other a11y issues) or add justified exceptions in the test. - Screenshot diff
Dark-mode UI changed compared to the last accepted snapshot. If the change is intentional, update the baseline:Then commit the updated files undernpx playwright test tests/ui/theme-audit.spec.ts --update-snapshotstests/ui/snapshots/theme-audit/.
scripts/discoverRoutes.ts– discovers routes, writestests/ui/routes.jsonscripts/audit-routes.config.ts– base URL, protected paths, dynamic-segment placeholders, ignored route groupstests/ui/theme-audit.spec.ts– Playwright tests (light/dark Axe + dark screenshot regression)tests/ui/routes.json– generated list of routes (committed sotest:uiworks without running discovery first)playwright.config.ts– Playwright config (test dir, base URL, snapshot path, optional dev server)
Client AI helpers:
lib/claudeClient.tscalls internal API routes only
Server Anthropic wrapper:
lib/anthropic.ts
Behavior:
- Request timeout
- Retry once on failure
- User-safe error messages (no raw crash output)
- Uses
ANTHROPIC_API_KEYfrom server env
FootballGPT on /ai persists the last conversation locally (kickoff-ai), so users can return later and continue chatting from their previous history instead of starting from scratch every time.
Configured with:
vitest.config.tsvitest.setup.ts
Test coverage currently includes:
- Store logic tests (
store/__tests__) - Component tests (
components/__tests__) - AI client tests (
lib/__tests__)
Run:
npm run test
npm run test:coverageCoverage HTML output:
coverage/index.html
app/ Next.js app routes + API routes
components/ UI, feed, match, shared, and layout components
store/ Zustand stores
lib/ Utilities, API clients, constants, storage helpers
data/ Mock and suggestion datasets
types/ Shared TypeScript interfaces
prisma/ Schema, migration, seed
hooks/ Custom React hooks
styles/ Additional global styles
This repository contains two parallel UI layers:
- Primary production path: typed Next.js pages and modern components (
app/*,store/*,types/*) - Legacy/demo path: large JSX prototype modules (
app/kickoff/page.jsx,components/NewComponents.jsx,lib/mock-data.ts, legacy pages/components)
Both currently coexist. When extending the app, prefer the typed modern path unless intentionally working on prototype/demo pages.
- Keep
.envout of source control (already ignored in.gitignore) - Do not hardcode API keys in client code
- AI key should stay server-side and be read from
process.env.ANTHROPIC_API_KEY
- If Prisma client types are missing:
- Run
npm install(postinstall runsprisma generate)
- Run
- If DB is out of sync:
- Run
npm run db:migrate - Then
npm run db:seed
- Run
- If AI replies fallback:
- Confirm
ANTHROPIC_API_KEYis set in.env
- Confirm