The Product
What the Education Hub is for — the five user types, the school-to-class hierarchy, and where each hub lives.
The Education Hub is one frontend that wears five faces. A school administrator, a campus teacher, a platform operator, a student, and a parent applying for admission all open the same TanStack Start app — and each lands somewhere completely different. Before you touch a single component, it pays to hold the map in your head: who uses this, and where their features live.
This page is that map. Two ideas carry the whole product: a small set of user types that decides which hub you see, and an academic hierarchy that everything in those hubs hangs off of.
Five user types
Every authenticated user carries a me.user.type. There are exactly five, and the value is the single most important fact about the person in front of the screen:
- guest — a prospective member of the community: someone applying for admission, or a job applicant.
- student — an enrolled learner.
- guardian — a parent or guardian of one or more students.
- staff — anyone who works for a school: teachers, admissions officers, admins.
- platform_admin — Naalya operators who run the platform across all schools.
The type isn't just a label — it's a fork in the road. The moment login resolves, getUserDestination reads it and hands back where to send you.
export function getUserDestination(me: AuthMeResponseDto): Destination {
switch (me.user.type) {
case 'platform_admin': return { to: '/platform/hub' }
case 'guest': return { to: '/guest' }
case 'student': return { to: '/student/hub' }
case 'guardian': return { to: '/guardian/hub' }
// 'staff' is the interesting one — see below
}
}Four of the five are a one-liner. Staff is the exception, because a staff member can operate at the school level or the campus level, and which one depends on their permissions:
case 'staff': {
const hasSchoolDashPermissions = me.permissions.some(
(perm) =>
(perm.action === 'read' && perm.resource === 'school_dashboard') ||
(perm.action === 'manage' && perm.resource === 'all'),
)
if (hasSchoolDashPermissions) return { to: '/staff-hub/school/hub' }
const campusId =
('campusId' in me.user.profile ? me.user.profile.campusId : undefined)
?? me.scope.campuses[0]?.id
if (campusId) return { to: '/staff-hub/campus/$campusId/hub', params: { campusId } }
return { to: '/staff-hub/no-campus' }
}Type chooses the door, permissions choose the room
The user type decides which hub you enter. Permissions decide what you can do once inside — and for staff, even which floor of the building you start on. Keep those two concerns separate in your head; the codebase keeps them separate too. See Permissions and User types for the full picture.
The academic hierarchy
Almost every page in the staff hub is a window onto the same nested structure. Learn this tree once and the routes stop feeling arbitrary:
School
└── Campus (academic structure is campus-scoped)
├── Academic Year
│ └── Term
│ └── Stream
├── Class
│ └── Subject
│ └── CBT Exam (assigned to Streams)
└── Enrollment (a student in a class)A School is the top-level tenant. It splits into one or more Campuses, and this is the line that matters most: academic structure is campus-scoped. A class, a stream, a subject — they all belong to a campus, never floating loose at the school. That's why the campus routes carry a $campusId and the school routes don't.
Inside a campus, time is organized as Academic Year → Term → Stream, while people-and-content are organized as Class → Subject, with Enrollment linking a student to a class. Two join concepts wire people into the structure: a Class-Teacher owns a class, and a Subject-Teacher owns a subject. Year-owned records (enrollments, grades, exams, fees) are pinned to the campus's academic year, and the hub always shows one year at a time — see Viewing Academic Year.
CBT exams live under a Subject
Computer-based-test (CBT) exams are authored under a Subject — you build them in My Subjects (my-subjects/$subjectId/exams/$examId) — and then assigned to Streams, where students sit them (classrooms/$classId/streams/$streamId/exams/$examId). The exam belongs to the subject; the stream is just where it's taken. Mixing those up is the most common early mistake.
Where features live
Each user type maps to a route area under src/routes/_authenticated. Here's the lay of the land.
Staff Hub — School level
Lives at /staff-hub/school. This is the whole-school cockpit, reached only by staff with school-dashboard permissions. It owns everything that spans campuses:
- Inbounds — admissions, jobs (recruitment), and inquiries.
- People — staff, students, guardians, and guests, in one directory.
- Structure — campuses, departments, school-wide academic year, and roles.
- Trust & integrations — audit logs, directory sync (Microsoft), API keys, and service health.
- AI — a knowledge base ("Rover") that powers the school's AI assistant.
Staff Hub — Campus level
Lives at /staff-hub/campus/$campusId. This is where the day-to-day teaching work happens, scoped to a single campus:
- classes and classrooms (classes broken into streams).
- subjects and my-subjects — the latter is where a teacher authors CBT exams.
- students, staff, guardians, enrollments, and the campus academic year.
Platform console
Lives at /platform, for platform_admin operators only. It sits above any single school:
- schools — onboard and manage every tenant.
- curriculum — versioned curriculum definitions shared across schools.
- operators — the platform admins themselves.
Portals
The three community-facing areas are deliberately thin and focused:
- /guest — apply for admission, and browse/apply for jobs (with interview tracking).
- /student — classes, grades, enrollments, and the "Naalya Bot" AI tutor.
- /guardian — children, grades, and payments.
Capability matrix
A condensed view of who can do what. This is the readable summary, not the source of truth — the source of truth is permissions, enforced per resource.
| Capability | Guest | Student | Guardian | Staff | Platform |
|---|---|---|---|---|---|
| Apply for admission / jobs | Yes | — | — | — | — |
| View own grades | — | Yes | — | — | — |
| View children & pay fees | — | — | Yes | — | — |
| Manage classes & enrollments | — | — | — | Yes | — |
| Author CBT exams | — | — | — | Yes | — |
| Manage admissions & recruitment | — | — | — | Yes | — |
| Onboard schools & curriculum | — | — | — | — | Yes |
The matrix is a sketch, not a gate
Treat this table as orientation only. Real access is decided at request time by the permissions on me — two staff members can see wildly different things. Never reason about access from the user type alone. Permissions explains how it's actually enforced.