This is the complete, merged codebase across all phases built so far: foundation (schema/RLS/auth scaffolding) → Phase 2 (auth + homepage) → Phase 3 (manager dashboard, match wizard, MatchMapCard, OCR route) → Phase 4 (player, admin, request-meeting, stub coach/staff, plus bug fixes).
npm installCreate .env.local in the project root:
NEXT_PUBLIC_SUPABASE_URL=https://your-project-ref.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
GOOGLE_API_KEY=your-gemini-api-keyRun the three migrations in supabase/migrations/ in order via the
Supabase SQL editor (or CLI): 0001 → 0002 → 0003.
npm run dev- Auth (login, apply, apply/status), middleware-based role/status routing
- Public homepage (hero, roster carousel, results teaser)
- Manager dashboard: overview, calendar, 4-step match creation wizard (map drag-reorder, OCR scoreboard upload), roster management, match detail
- Player dashboard: overview, calendar (timeline), tasks, stats (Recharts), profile
- Admin dashboard: overview, applications queue (approve/reject)
- Shared: notification bell (Realtime), request-meeting page
- Coach/staff dashboards: stubs only
- OCR route (
app/api/ocr/scoreboard/route.ts) does not reuse the Discord bot's existing Tesseract/Google Vision + Gemini/Groq pipeline. It's a fresh Gemini-2.5-Flash-only implementation, which conflicts with the original "reuse, don't rewrite" preference. Still waiting on the bot repo URL to fix this properly. - The match wizard's
player_statsinsert uses a placeholderuser_id(the manager's own id) for every parsed scoreboard row — no IGN-to- roster-member matching yet. - Not built yet:
/dashboard/manager/matches(list page),/dashboard/manager/matches/[id]/edit,/dashboard/admin/users,/dashboard/admin/rosters,/dashboard/admin/calendar, coach dashboard, staff dashboard, chat/DM system, achievements UI. - Avatar upload is URL-input only — no Supabase Storage upload flow yet.
app/layout.tsxhassuppressHydrationWarningon<html>to silence a hydration warning caused by a browser extension injecting ahydratedclass — not an app bug, safe to leave as-is.
There is a chicken-and-egg problem by design: only an admin can approve
applications and promote other users, but nobody starts out as admin. The
/dashboard/admin/applications approve flow now runs through a server
action (app/dashboard/(shell)/admin/applications/actions.ts) using the
service-role key specifically so approvals always work once an admin
exists — but that first admin has to be set by hand, once, directly in the
database:
- Sign up normally through
/apply(pick any role — it doesn't matter, you're about to override it). - In the Supabase dashboard, go to Table Editor → users, find your row,
and set
role = adminandstatus = approvedmanually. - Log back in (or refresh) — you'll land in
/dashboard/adminand can approve every subsequent application normally from the UI from then on.
If you ever see someone stuck being redirected back to /apply/status
after an admin "approved" them, check the Supabase logs for the
approveApplication server action — it now returns and surfaces a real
error via toast instead of failing silently, so this should be visible in
the UI now rather than mysterious.
The "Continue with Discord" button on /login calls
supabase.auth.signInWithOAuth({ provider: 'discord' }), but Discord has to
be enabled as an OAuth provider in your Supabase project before it'll work:
- In the Discord Developer Portal,
create an application → OAuth2 → add a redirect URL:
https://<your-project-ref>.supabase.co/auth/v1/callback - Copy the Discord Client ID and Client Secret.
- In the Supabase dashboard: Authentication → Providers → Discord → paste in the Client ID/Secret → enable the provider.
- In Authentication → URL Configuration, make sure your site's
/auth/callbackURL (e.g.http://localhost:3000/auth/callbackfor local dev, plus your production URL) is in the allowed redirect list.
Note: a brand-new Discord sign-in creates a users/applications row the
same way email signup does (via the handle_new_user trigger), but with
role = null since there's no application form data attached to an OAuth
sign-in. They'll land on /apply/status showing a pending application with
no role — an admin will need to set their role manually in
/dashboard/admin/users (or extend /apply/status with a "complete your
profile" step) since there's no role-selection UI in the OAuth flow yet.
All form errors and key success states now surface via toast (using
sonner, wired up in app/layout.tsx) in addition to any inline error
text that was already there — lib/toast.ts is the shared wrapper
(toast.error, toast.success, toast.info). Covered: login, apply
(including the duplicate-email case below), applications approve/reject,
match creation, meeting requests, profile edits, task creation, roster
creation, admin calendar, user role/suspend actions, and chat.
Supabase Auth intentionally does not return an error when you sign up
with an email that's already registered and confirmed — this prevents
attackers from using the signup form to enumerate which emails have
accounts. The /apply page now detects this the documented way (checking
data.user.identities for an empty array on an otherwise-successful
response) and shows a toast + inline error telling the person to log in
instead.