Internal moderation and operations dashboard for the FittMatch platform.
- Framework: Next.js 15 (App Router, server components, server actions)
- Auth: Supabase Auth — magic link + middleware admin guard
- Database: Supabase (service role client, server-only)
- UI: shadcn/ui + Tailwind CSS
| Route | Purpose |
|---|---|
/ |
Dashboard — stat cards (reports, users, subs, matches, active blocks, disputes) + recent reports + signup sparkline |
/reports |
Reports queue with status tabs, reason filter dropdown, and bulk actions |
/reports/[id] |
Report detail + resolution panel |
/users |
User search by name or email, role/status filters |
/users/new |
Create a new coach or client user |
/users/[id] |
User detail, metrics, photos, login info, matches, ban/unban, change role, delete user |
/users/[id]/edit |
Edit coach or client profile fields |
/users/[id]/messages |
Full message history across all matches, grouped by conversation |
/listings |
Job listings with edit, remove, and status filter |
/listings/new |
Create a listing on behalf of a client |
/listings/[id] |
Edit listing title, description, status, pay, boost |
/subscriptions |
Revenue table + MRR estimate |
/audit-log |
Paginated admin action history |
npm install
npx shadcn@latest init
npx shadcn@latest add table badge dialog select tabs card button textarea input avatar separator
npm run devCopy .env.local.example → .env.local and fill in the three Supabase keys.
The first admin can't be granted through the UI (you need an admin to grant admin). Run this once in the Supabase SQL Editor, using the UUID of a user who has already signed in via magic link:
INSERT INTO profiles (id, email, role)
VALUES ('<auth-uuid>', '<email>', 'coach')
ON CONFLICT (id) DO NOTHING;
INSERT INTO admin_users (user_id, role, granted_by)
VALUES ('<auth-uuid>', 'admin', '<auth-uuid>')
ON CONFLICT (user_id) DO UPDATE SET role = 'admin';After that, additional admins can be granted directly from any user's detail page (/users/[id]).
From the Users screen you can:
- Search by name or email, filter by role (coach / client) and status (active / banned)
- Paginate — 50 users per page via
?page=searchParam; all active filters are preserved across page navigation - Create users —
/users/newcreates a coach or client account viaauth.admin.createUser. For client accounts you can optionally set company name and type at creation time. The user receives a magic-link email to activate their account. - View a user — metrics (swipes, matches, messages), reports against them, subscription status, login/auth info (last sign-in, email confirmed date, device fingerprint), and profile photos
- Edit profile —
/users/[id]/editlets you update a coach's bio, title, specialties, certifications, experience level, and open-to-offers toggle, or a client's company name, type, bio, website, and team size - Ban / unban — with a reason; logged to the audit log
- Change role — switch a user between coach and client; creates the missing profile row, preserves the old one
- Grant / revoke admin access — from the user detail page, choose a role (Moderator, Admin, Super Admin) and grant in one click. Revoke is equally one-click.
- View matches — full match list per user with All/Active/Unmatched/Blocked filter tabs, status badges, and per-match Reset or Restore actions (Restore sets status back to active for dispute resolution)
- View blocks — two sub-tables on the user detail page showing blocks the user has given and received; admins can remove any block
- Toggle profile visibility — "Profile visible in deck" toggle on the coach/client card overrides
is_completedirectly - View photos — photo gallery with click-to-expand lightbox dialog
- View messages —
/users/[id]/messagesshows all conversations across all matches in a chat-bubble view; deep-link to a specific match with?matchId= - Delete user — permanent account deletion via
auth.admin.deleteUser; requires typing the user's email to confirm; cascades to profile, matches, and messages
From the Listings screen you can:
- Filter by status (live / draft / removed)
- Paginate — 50 listings per page via
?page=searchParam; status filter is preserved across page navigation - Edit a listing —
/listings/[id]lets you update title, description, status, role type, pay range, and boost expiry. A "Clear boost" button removes an active boost without clearing the date field. - Create a listing —
/listings/newcreates a listing on behalf of any existing client account - Remove a listing — sets status to
removedinline from the list view
Hosted on Vercel. Set these environment variables in the Vercel project settings:
NEXT_PUBLIC_SUPABASE_URL
NEXT_PUBLIC_SUPABASE_ANON_KEY
SUPABASE_SERVICE_ROLE_KEY
After deploying, add the Vercel domain to Supabase → Authentication → URL Configuration → Redirect URLs.