Skip to main content

Sub-Admin & Access Control

Scholiphi's admin panel has two effective tiers: super admins (platform-wide) and admins (scoped to one or more schools). What we casually call a "sub-admin" is just an admin user assigned to a subset of schools. There are no per-feature permission flags — access is gated by role and school assignment. This page covers creating these admins, the invite/password-set flow, the recently-shipped admin password reset, and account deactivation.

At a glance

Who can do thisSuper admin creates/edits/resets/deletes admin users · Admins can only see admins assigned to schools they share
Where it livesAdmin panel → /dashboard/admins (super-admin sidebar)
Recently shippedReset Password modal — super admin can set a new password for any admin without going through the email invite (commit feat: feature to reset sub admin password)
Related featuresSchool Creation · Admin Profile · Admin Panel Overview

Roles in the admin panel

Role keyWho they areSidebarSchools they see
super_adminPlatform ownerFull sidebar: Dashboard, Admins, Schools, Classes, Icons, Payments, Syllabus KB, Vector Search, Categories, AI Assistant, AI Usage, Profile, SettingsAll schools
adminSchool-scoped admin (a.k.a. "sub-admin")Trimmed sidebar: Dashboard, Schools, Syllabus KB, Vector Search, AI Assistant, Profile, SettingsOnly schools where the admin's assignedSchoolIds contains the school

There is no separate sub_admin role key. The "sub-admin" idea is enforced purely through the multi-select Assigned Schools field on the admin user.

How it flows

Step-by-step

1. Create an admin

  • Route: /dashboard/adminsCreate Admin button (top-right).
  • Modal title: Create Admin.
  • Fields:
    • Email (required, validated as email; debounced live check warns "This email is already registered" before submit)
    • First Name (optional; letters/spaces only)
    • Last Name (optional; letters/spaces only)
    • Assigned Schools (optional, multi-select; empty = unassigned admin who sees no schools)
  • On submit, a set-password email is sent to the address. The row appears in the table tagged PENDING INVITE until the admin sets their password.

2. The admin sets their password

  • They open the email and click the link → /setup-password?token=....
  • Form: Password + Confirm Password (min 6 chars, must match).
  • On success, they're redirected to /login after a 2-second confirmation message.

3. The admin logs in

  • /login accepts email + password (zod validation: valid email format + min 6 chars).
  • After login they land on /dashboard. Their sidebar is the trimmed admin variant. The Schools page shows only schools whose IDs are in their assignment list.

4. Resend the invitation (if they lost the email)

  • In the row's kebab menu → Resend Invitation.
  • Only shown for admins where isPasswordSet === false.
  • A new tokenised email is dispatched.

5. Reset an admin's password (super-admin only)

  • In the row's kebab menu → Reset Password.
  • This action only appears when the logged-in user is super_admin.
  • Modal: Reset Admin Password with a yellow warning banner naming the target admin.
  • Fields: New Password (min 6 chars) + Confirm Password (must match).
  • On success, toast: "Password reset for <email>". Super admin must share the new password through a secure out-of-band channel — there is no email notification for this path.

6. Edit profile / re-assign schools

  • Kebab menu → Edit Profile.
  • Modal: Edit Admin with email, first/last name, Assigned Schools multi-select, and Active Status toggle.
  • Saving an empty assigned-schools list effectively un-scopes the admin (they see no schools but can still log in).

7. Deactivate (revoke access)

  • Two ways:
    • Quick toggle: the System Access switch in the table row (optimistic UI; reverts on backend error).
    • Full edit: Active Status switch inside the Edit Admin modal.
  • An inactive admin cannot log in.

8. Delete an admin

  • Kebab menu → Remove Admin → confirm modal "Access will be revoked immediately".
  • Hard-deletes the admin record. School assignments are detached.

Permissions matrix

There are no fine-grained permission flags. Access is determined entirely by role and assigned schools. The matrix below documents what each tier actually does.

CapabilitySuper adminAdmin (a.k.a. sub-admin)
Create / edit / delete other admins❌ (no Admins entry in sidebar)
Reset another admin's password
Resend admin invitation
Toggle admin active/inactive
Create new schools❌ (button hidden on /dashboard/schools)
Edit / delete schools❌ (only View Dashboard action available)
See all schoolsOnly assigned schools
Manage classes (platform-wide library)❌ (sidebar entry hidden)
Manage icons / Categories / Payments❌ (sidebar entries hidden)
AI Usage dashboard❌ (sidebar entry hidden)
Inside an assigned school: students, teachers, sections, transport, yearly plans, moderation, token approvals
AI Assistant + Vector Search + Syllabus KB

If you need granular flags (e.g. "this admin can edit students but not delete them"), the panel does not currently support that. It's role + school scope, full stop.

Edge cases & things to test

  • Email already in use: enter an email used by a teacher/student/parent — Create Admin should reject with "This email is already registered" (debounced live check).
  • Invalid name characters: paste "John123" into First Name — should fail "Only letters allowed" validation.
  • Empty assigned schools: create an admin with zero schools selected. Confirm they can log in but their Schools list is empty (no crash, just empty state).
  • Reset password for self: as super admin, try to open Reset Password on your own row — confirm what happens (the action is gated by role, not by self/other).
  • Reset password while admin is logged in: reset the password of an admin who is currently in an active session. Their existing tokens — do they get invalidated? (Verify by trying their old session after reset.)
  • Resend invitation after password set: confirm the Resend Invitation action is hidden once isPasswordSet === true.
  • Pending invite + active toggle: try toggling System Access on/off for an admin who hasn't set their password yet. Confirm both states behave (active+pending vs disabled+pending).
  • Deleting a school assigned to an admin: super admin deletes a school that's in an admin's assignedSchoolIds. Confirm the admin's list updates on next refresh and they don't get a crash trying to navigate to the deleted school.
  • Filter by school: in /dashboard/admins use the school filter dropdown — confirm it correctly narrows to admins who include that school in their assignments.
  • Permissions UI vs route-level: a non-super admin manually navigates to /dashboard/admins (typed URL). Confirm ProtectedRoute either redirects or 404s — the sidebar hides it, but the URL must be guarded too.
  • Password reset min length: try setting a 5-char password from the reset modal — should fail "Password must be at least 6 characters". Same rule applies on /setup-password.
  • Sidebar role detection lag: after super admin promotes/demotes someone, the affected admin's sidebar is computed from useUserStore. Confirm a hard refresh shows the correct sidebar (or that login refresh picks up the new role).
  • Search debounce: type rapidly in the admin search box — confirm only one request fires per ~500ms idle window.