Skip to main content

Admin app

apps/web is the internal, authenticated Campus OS surface today — and it is admin-only, by deliberate scoping rather than accident. The architecture docs describe admin, faculty, HoD, IQAC, warden, and placement portals; no /portal/* route tree exists yet.

Built on TanStack Start (Vite dev server on port 3000) with TanStack Router, TanStack Query, and TanStack Table. API access goes through a single typed Hono RPC client (apps/web/src/lib/api-client.ts) — hc<AppType> importing the API's route type, with credentials: "include" so the Better Auth session cookie rides along. That is what gives end-to-end type safety without hand-written per-endpoint wrappers.

Routes​

Route filePathWhat it does
__root.tsx—Root layout, Header/Footer, providers
index.tsx/Landing / redirect
login.tsx/loginEmail + password sign-in against apps/api
about.tsx/aboutStatic
admin.tsx/adminAdmin shell layout
admin.index.tsx/adminDashboard
admin.departments.tsx/admin/departmentsOrg structure — departments and related reference lists
admin.people.tsx/admin/peoplePeople list with search + "Add Person" flow
admin.people.index.tsx/admin/peopleList index
admin.people.$personId.tsx/admin/people/:idPerson detail: addresses, sensitive identifiers, employment, enrollment, attendance history, plus edit forms
admin.curriculum.tsx/admin/curriculumSubjects and class sections
admin.attendance.tsx/admin/attendanceAttendance: assignments, delegations, roster marking, records
admin.settings.tsx/admin/settingsModules, custom fields, reference data, audit log

routeTree.gen.ts is generated — run bun --filter '@campus/web' generate-routes (tsr generate) after adding a route.

What the settings screen actually contains​

Four sections in one page, each backed by a real endpoint:

  • Modules — a badge per module the tenant is entitled to, from GET /modules. Read-only: there is no UI or endpoint to enable or disable an entitlement yet.
  • Custom Fields — a table of this tenant's custom_field_definitions plus a create form (entity, key, label, type, enum options when type is enum, required flag, visibility). This is the no-migration extension mechanism exposed end to end. The option lists are duplicated as literals in this file with a comment pointing at packages/db/src/schema/tenancy.ts as the source of truth — a candidate for a shared export, since the two can drift.
  • Reference Data — all six global lookup tables as badge groups with counts. Read-only; the reference tables are platform-managed and intentionally not tenant-writable.
  • Audit Log — the paginated audit trail (50 per page) with a Previous/Next pager, action badges coloured by create/delete/update, and the acting person resolved by name where available. Note this is requirePermission({ auditLog: ["view"] }), so a non-admin gets an error here.

Components​

components/ui/ holds the primitives — badge, button, card, input, label, select, table — written as local components rather than pulled from a UI library, styled with Tailwind CSS v4 via @tailwindcss/vite. There is a Header, Footer, ThemeToggle, and a person-picker used wherever a route needs to choose a person.

There is no shared design system package between web and mobile yet; mobile does not exist yet, so the token-drift problem the architecture doc anticipates has not arrived.

Known limitations​

  • No self-service surface. Students, faculty, and parents have no UI. Faculty cannot mark attendance through the app today even though POST /attendance/records supports them — the authorization logic for it is built and tested at the API layer.
  • No permission-aware navigation. The shell renders the same nav regardless of role. Combined with the read-side authorization gap in API Surface, a low-privilege user logging in will see screens whose writes fail with 403 rather than being hidden.
  • No real-time updates. Everything is request/response through TanStack Query. The TanStack DB + ElectricSQL live-view architecture in TECHNICAL_ARCHITECTURE.md §7 is intentionally a later concern.
  • No custom-field rendering on forms. Custom fields can be defined in settings, and the schema stores their values in the target row's attributes JSONB — but the person/enrollment/employment forms do not yet render inputs for the definitions that exist.