Skip to content

feat: platform admin dashboard - #79

Merged
ethnjs merged 26 commits into
mainfrom
feat/admin-dashboard
Sep 17, 2026
Merged

ethnjs merged 26 commits into
mainfrom
feat/admin-dashboard

Conversation

@ethnjs

@ethnjs ethnjs commented Sep 14, 2026

Copy link
Copy Markdown
Owner

Summary

A platform-admin dashboard: accounts, tournaments, and the university/event catalog, each as a real table instead of psql. Admins get inline editing, bulk-safe row actions, and a toast on every change instead of a silent success or a stack trace.

Along the way: an admin can no longer demote, deactivate, or delete their own account through the admin routes, and a handful of pre-existing bugs surfaced by building this got fixed in place.

Closes #78

What changed

Backend

Schemas and routes

  • New AdminTournamentRead (schemas/tournament/__init__.py) — the platform-admin audience gets the owner's name and event/volunteer counts; member-facing TournamentRead is untouched.
  • New POST /admin/users/{user_id}/password-reset/ — sends the same email the public forgot-password flow sends, but reports real errors (400/429/500) instead of the enumeration-safe generic 200, since the caller already knows who the user is.
  • Removed AdminRegisterRequest, POST /admin/auth/register/, and POST /admin/auth/account-setup/resend/ — dead code for an invite flow that's been dormant since nothing mints an account_setup token; deleting from the API surface it was actually reachable through.
  • DELETE /universities/{id}/ now also 409s when a tournament track still references it. (fix — previously only chapters and users were checked, so deleting a university that only a track pointed at nulled the track's university_id and left it satisfying neither the university-or-location invariant, which failed as a 500 later.)

Logic

  • PATCH /admin/users/{id}/ revokes sessions on any move off status="active", not just "locked". (fix — deactivating a user used to leave their existing sessions live.)
  • An admin can't demote their own role or move their own status off "active" through this route — both are irreversible from the caller's side. Self-deactivation still exists at POST /users/me/deactivate/, password-confirmed.
  • An admin can't delete their own account through DELETE /admin/users/{id}/; DELETE /users/me/ is the intended path, also password-confirmed.
  • DELETE /tournaments/{id}/ now lets an admin delete an archived tournament; the owner-facing 403 against deleting archived tournaments is unchanged. (fix — admin cleanup couldn't reach anything once it was archived.)

Frontend

Admin dashboard

  • New /dashboard/admin section: flat admin rail (AdminSidebar), sharing the generic sidebar extracted out for it.
  • Accounts table (admin/users) — role/status inline edit, AdminUserPanel profile side panel opens from anywhere in the row, step arrows between rows, admin-triggered password reset.
  • Tournaments table (admin/tournaments) — owner, event/volunteer counts, one season picker replacing separate event tabs, with staged season adds.
  • Universities table (admin/universities) — inline editing, NewUniversityModal.
  • Catalog table (admin/events) — events/categories, CategoriesModal, NewEventModal, season grid.
  • AccountBadges and a header badges slot (ProfileHeader) surface admin account state (role, status) on the profile page itself, so an admin viewing any member's profile sees it without cross-referencing the accounts table.
  • useActionToast — every admin action reports its outcome (success or the server's error message) instead of failing silently.
  • ConfirmModal for destructive row actions.

Shared UI fixes

  • Input merges a caller's style prop into the computed style instead of letting it replace the whole object. (fix — passing style used to wipe border, height, padding, background, and font along with whatever the caller actually meant to override.)
  • Dropdown footer padding now matches the option rows, and hovering the footer clears the option highlight instead of leaving a stale one lit.
  • formatDates puts the year on each run instead of hoisting one to the end, when a tournament's days cross a year boundary. (fix — a state tournament with quals in Sept and finals the next April was silently mislabeling every day outside the hoisted year.)
  • Stopped dimming locked rows in the accounts table so their actions still read as available.

Out of scope

  • The dormant invite/account-setup flow itself (User.status="invited") — only its unreachable admin routes were removed here; re-enabling it is a separate piece of work.

Test plan

  • pytest passes locally
  • Migration included for every model change — N/A, no schema change this branch
  • Clicked through the UI change in a browser

Automated

  • Extended test_users.py — self-demote/deactivate/delete guards, session revocation on any non-active status, admin password-reset (success, no-password 400, propagated errors).
  • Extended test_universities.py — delete blocked by a referencing track.
  • New tournament/test_admin.pyAdminTournamentRead shape, owner/count fields.
  • Extended tournament/test_core.py — admin can delete an archived tournament.
  • Trimmed test_auth.py — removed coverage for the deleted admin-register/resend routes.

Manual

  • Edited role and status inline on the accounts table — toast confirms, locked user's session drops immediately.
  • Tried to demote/deactivate/delete my own admin account from the dashboard — blocked with the right message, self-service paths still work.
  • Deleted an archived tournament as admin — succeeds; tried it as the owner — still 403.
  • Deleted a university referenced only by a track — 409 instead of a later 500.
  • Viewed a tournament spanning two years in the tournaments table and on its card — both days show their own year.
  • Passed a style override into a few Input usages — border/height/padding intact.

…lear the option highlight when hovering the footer
@vercel

vercel Bot commented Sep 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
nexus Ready Ready Preview Sep 14, 2026 7:58am UTC

@railway-app
railway-app Bot temporarily deployed to nexus / nexus-pr-79 September 14, 2026 07:49 Destroyed
@railway-app

railway-app Bot commented Sep 14, 2026

Copy link
Copy Markdown

🚅 Deployed to the nexus-pr-79 environment in nexus

Service Status Web Updated
nexus ✅ Success (View Logs) Sep 14, 2026 at 7:57 am UTC

@ethnjs
ethnjs marked this pull request as ready for review September 17, 2026 05:19
@ethnjs
ethnjs merged commit 37a1152 into main Sep 17, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: platform admin dashboard

1 participant