Naalya Handbook

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.

src/lib/auth/get-user-destination.ts
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:

src/lib/auth/get-user-destination.ts
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.

CapabilityGuestStudentGuardianStaffPlatform
Apply for admission / jobsYes
View own gradesYes
View children & pay feesYes
Manage classes & enrollmentsYes
Author CBT examsYes
Manage admissions & recruitmentYes
Onboard schools & curriculumYes

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.

Where to go next

On this page