Founder Dashboard
This dashboard is for the SaaS founder/operator. It aggregates across all schools by default and supports tenant drill-downs.
Routes
/dashboard
→ redirects to/dashboard/overview
/dashboard/overview
→ KPIs across all schools (total schools, active schools, users, students)/dashboard/kanban
→ Kanban demo for task management/dashboard/product
→ Product page (tables temporarily disabled)/dashboard/profile
→ Profile/dashboard/tenants
→ Tenants list (temporarily public for build/demo; will be DEVELOPER-only)
Operator (Platform) Routes
/operator
→ redirects to/operator/overview
/operator/overview
→ Founder/operator KPIs across all tenants/operator/tenants
→ Manage tenants (activate/suspend, impersonate)/operator/domains
→ Domain requests and verification (placeholder)/operator/billing
→ Operator billing overview (placeholder)/operator/observability
→ Logs/errors/backups/jobs (placeholder)- Access control: DEVELOPER only. Guarded by middleware and operator layout.
- Note: Temporarily disabled for public demo; guards are commented in
src/middleware.ts
andsrc/app/(platform)/operator/layout.tsx
. Re-enable by uncommenting.
- Note: Temporarily disabled for public demo; guards are commented in
Sidebar
The dashboard uses a docs-like template sidebar:
- Sidebar component:
src/components/template/dashboard-sidebar/content.tsx
- Sidebar items:
src/components/template/dashboard-sidebar/constant.ts
- Header:
src/components/template/dashboard-header/content.tsx
(Breadcrumbs, notifications, messages, theme, user)
Auth and Tenant
- Uses Auth.js (NextAuth v5). Session extended in
src/auth.ts
withschoolId
,role
, and flags. src/lib/tenant.ts
providesgetTenantContext()
returning{ schoolId, role, isPlatformAdmin, requestId }
.- Middleware protects
/dashboard/**
;/docs/**
public.- Operator guard: intended to restrict
/operator/**
to DEVELOPER via middleware; unauthorized redirects to/403
. - Temporary: guard disabled for demo; see commented code in middleware and layout.
- Operator guard: intended to restrict
KPIs (Overview)
src/app/(platform)/dashboard/overview/layout.tsx
reads counts via Prisma (client dashboard):
db.school.count()
(total and active)db.user.count()
db.student.count()
Operator overview KPIs:
src/app/(platform)/operator/overview/layout.tsx
aggregates the same KPIs across all tenants.
Tables
- Shared block:
src/components/table/*
(DataTable, toolbar, filters, pagination) - Tenants table:
src/app/(platform)/dashboard/tenants/page.tsx
- Columns:
src/components/platform/dashboard/tenants/columns.tsx
- Queries:
src/components/platform/dashboard/tenants/queries.ts
- Wrapper:
src/components/platform/dashboard/tenants/table.tsx
- Note: Server-driven pagination/sort; filters to be added with
nuqs
.
- Columns:
Cleanup & Centralization
- Removed old site demo dashboard.
- Centralized dashboard chrome under
src/components/template/dashboard-header
andsrc/components/template/dashboard-sidebar
.
Next steps
- Implement subdomain →
schoolId
mapping in middleware. - Restrict
/dashboard/tenants
to DEVELOPER when roles are finalized. - Add audit logs with
{ requestId, schoolId, userId }
. - Operator guard planned:
/operator/**
restricted to DEVELOPER via middleware and layout. Currently disabled for demo. - Impersonation UX: banner under the header shows target school and provides a stop button. Controlled by
impersonate_schoolId
cookie.
Dashboards (Operator and Client)
Two dashboards share one central codebase and database:
- Operator Dashboard (platform team): run the SaaS - schools, billing, domains, moderation, observability.
- Client Dashboard (per school): run the school - roles, classes, subjects, students, teachers, timetable, attendance, announcements, billing.
Both follow the same patterns: shadcn/ui components, URL-to-state sync where useful, Prisma with tenant scoping, and server actions for mutations.
Architecture
- Multi-tenant shared schema (Neon + Prisma). Every business table includes schoolId.
- Tenant resolution via subdomain (for example, hogwarts.hogwarts.app) and session.
- Central logic: shared actions, filters, table block, and role gates.
- RBAC: Owner, Admin, Teacher, Student, Parent, Accountant (add Staff or Support for Operator).
Operator Dashboard (Platform)
Scope:
- Schools: list, create, suspend, plan, limits, trial countdown.
- Domains: subdomain issuance, custom domain requests, CNAME verification.
- Billing: invoices, manual receipts, refunds, revenue metrics.
- Observability: logs, error reports, backups status, job queue.
- Moderation and Support: impersonate (audited), reset domain, manage users.
Implementation:
- Routes under app/operator/* or app/(operator)/* with guard middleware.
- Implemented guard checks
req.auth.user.role === "DEVELOPER"
and otherwise redirects to/403
.
- Implemented guard checks
- Lists use the generic table block (/docs/table), filtering by any platform fields.
- Server actions for domain approval, plan changes, suspensions.
- Role gates: RoleGate for platform roles (for example, DEVELOPER, STAFF).
Tenant safety:
- Platform views can search across all schools. Mutations must specify the target schoolId and log an audit entry.
- Impersonation creates an audit trail (who, when, why) and a time-limited session.
Operator MVP Progress and Checklist
Current status vs MVP scope:
-
Overview KPIs
- Implemented:
src/app/(platform)/operator/overview/layout.tsx
shows totals for schools, active schools, users, students. - Next: Add trend metrics and drill‑downs (optional post‑MVP).
- Implemented:
-
Tenants
- Implemented: List/table, server query, activate/suspend action with revalidation.
- Files:
src/app/(platform)/operator/tenants/page.tsx
,src/components/platform/operator/tenants/*
,src/app/(platform)/operator/actions/tenants/toggle-active.ts
.
- Files:
- Remaining (MVP):
- [x] Audit log for activate/suspend with
{ userId, schoolId, action, reason }
. - [x] Confirm dialog (reason prompt) and server revalidation.
- [x] URL‑synced filters (status/plan/search) using
nuqs
.
- [x] Audit log for activate/suspend with
- Implemented: List/table, server query, activate/suspend action with revalidation.
-
Impersonation (Moderation/Support)
- Implemented: Start/stop with cookie + banner + audit log entries.
- Files:
src/app/(platform)/operator/actions/impersonation/*
,src/components/platform/operator/impersonation-banner.tsx
,prisma/models/audit.prisma
.
- Files:
- Remaining (MVP):
- [x] Time‑limited impersonation (30‑min cookie) and explicit reason capture.
- [x] Disable sensitive actions while impersonating (except Stop) — enforced in server actions.
- Implemented: Start/stop with cookie + banner + audit log entries.
-
Domains
- Implemented:
DomainRequest
model (awaiting migration), operator page scaffold with actions wired (disabled until migration).- Files:
prisma/models/domain.prisma
,src/app/(platform)/operator/domains/page.tsx
, actions insrc/app/(platform)/operator/actions/domains/*
.
- Files:
- Remaining (MVP):
- [ ] Run Prisma migration and enable actions (Approve/Reject/Verify) in UI.
- [x] Add audit logs on each action.
- [ ] Add Zod validations for actions.
- [ ] Optional: school‑side request form and status surface (client dashboard).
- Implemented:
-
Billing
- Implemented: Placeholder route.
- Remaining (MVP):
- [ ] Add
Invoice
andReceipt
models (schoolId, amount, status, file/url). - [ ] List invoices + manual receipt review/approval actions with audit.
- [ ] Basic revenue metrics card(s) on overview.
- [ ] Add
-
Observability
- Implemented: AuditLog list/table (
/operator/observability
).- Files:
src/app/(platform)/operator/observability/page.tsx
,src/components/platform/operator/observability/logs-table/*
.
- Files:
- Remaining (MVP):
- [ ] Filters: action, user, school, date range; pagination.
- [ ] Placeholders for errors/backups/jobs; wire when available.
- Implemented: AuditLog list/table (
-
Subdomain → tenant mapping
- Implemented: Middleware attaches
x-subdomain
; server resolves toschoolId
ingetTenantContext()
.- Files:
src/middleware.ts
,src/lib/tenant.ts
.
- Files:
- Remaining (MVP):
- [ ] Add
NEXT_PUBLIC_ROOT_DOMAIN
and verify mapping in all environments. - [ ] Fallback/dev param
?x-school=<subdomain>
documented and gated.
- [ ] Add
- Implemented: Middleware attaches
-
Access control
- Implemented: Guard code present (middleware + layout) and currently disabled for public demo.
- Remaining (MVP):
- [ ] Re‑enable DEVELOPER‑only guard for
/operator/**
insrc/middleware.ts
andsrc/app/(platform)/operator/layout.tsx
.
- [ ] Re‑enable DEVELOPER‑only guard for
Operational checklists (to reach production‑ready MVP):
-
Database & migrations
- [ ] Run
pnpm prisma generate
. - [ ] Run
pnpm prisma migrate dev --name add_audit_and_domain_request
. - [ ] Seed: 3 demo schools, a few
DomainRequest
, andAuditLog
entries.
- [ ] Run
-
Server actions & validation
- [ ] Add Zod schemas for operator actions (toggle active, domains) with typed results.
- [ ] Add
withOperatorAuth
helper to assert DEVELOPER role and capture{ userId, requestId }
. - [ ] Rate‑limit sensitive actions (basic limiter).
-
UX polish
- [ ] Confirm modals for suspend/activate and domain actions.
- [ ] Disable mutations during impersonation; banner shows target school and expiry.
-
Observability & audit
- [ ] Ensure all sensitive actions write
AuditLog
. - [ ] Add minimal filters/pagination to logs table.
- [ ] Ensure all sensitive actions write
-
Guards & security
- [ ] Re‑enable
/operator/**
guard; verify unauthorized →/403
.
- [ ] Re‑enable
-
Docs
- [ ] Update once billing models land and actions are enabled.
Quick re‑enable steps (when demo phase ends):
- Uncomment operator guard block in
src/middleware.ts
. - Uncomment auth/role checks in
src/app/(platform)/operator/layout.tsx
.
Operator Actions
- Toggle tenant active state:
POST /operator/actions/tenants/[tenantId]/toggle-active
(DEVELOPER only). Revalidates/operator/tenants
. - Start impersonation:
POST /operator/actions/impersonation/[schoolId]/start
(DEVELOPER only). Sets cookieimpersonate_schoolId
and logs audit entry. - Stop impersonation:
POST /operator/actions/impersonation/stop
(DEVELOPER only). Clears cookie and logs audit entry.
Client Dashboard (Per School)
Scope:
- Setup: profile, branding, locale and timezone, domain request.
- People: users and roles, parents, students, teachers.
- Structure: classes, subjects, timetable slots.
- Operations: attendance, announcements.
- Billing: plan, trial, invoices, manual receipt upload.
Implementation:
- Subdomain-based routing: school.hogwarts.app resolves to schoolId.
- All server queries include schoolId (inject from middleware or context).
- Lists reuse the generic table; columns define filters via meta.variant and options.
- Server actions for all mutations, revalidate paths after changes.
RBAC examples:
- Owner or Admin: full access. Teacher: classes they teach. Parent: linked students. Student: own data.
Data Flow (Both Dashboards)
- Resolve tenant and role.
- Parse URL for filters, sort, page.
- Build Prisma where and orderBy, with schoolId enforced for client dashboard; optional for operator dashboard.
- Fetch data and total, then compute pageCount.
- Render with table block; server actions mutate and revalidate.
Production Readiness
- Security: least privilege, audit trails for sensitive actions (domain, billing, impersonation).
- Tenant isolation: all client queries and unique constraints scoped by schoolId.
- Observability: request IDs, schoolId, and user ID in logs; error tracking with release tags.
- Backups and disaster recovery: daily backups, retention 7 and 30 days; restore drills.
- Internationalization and RTL: Arabic and English; mirrored icons and arrows.
- Accessibility: keyboard navigation and roles across both UIs.
- Performance: indexes for frequent filters; pagination; avoid N+1; cache lists where safe.
Reuse and Extensibility
- Reuse table, filters, operators, and toolbars for any list (students, teachers, invoices, domains).
- Define column factories per domain; keep server query helpers tenant-aware.
- Centralize role gates and nav items by role; show or hide features by plan.
References
- Multi-tenant database: /docs/database
- Arrangements (SaaS layers): /docs/arrangements
- Table block: /docs/table
- Internationalization: /docs/internationalization
- Contributing: /docs/contribute
Environment & Config (Operator)
NEXT_PUBLIC_ROOT_DOMAIN
: used by middleware to derive subdomain → tenant mapping (e.g.,hogwarts.app
).- Dev convenience param:
?x-school=<subdomain>
will be read by middleware and passed asx-subdomain
header. - Impersonation cookie:
impersonate_schoolId
controls banner and context override.
Operator APIs (server actions / routes)
- Tenants:
POST /operator/actions/tenants/[tenantId]/toggle-active
. - Impersonation:
POST /operator/actions/impersonation/[schoolId]/start
,POST /operator/actions/impersonation/stop
. - Domains (enable after migration):
- Approve:
POST /operator/actions/domains/approve
- Reject:
POST /operator/actions/domains/reject
- Verify:
POST /operator/actions/domains/verify
- Approve:
Data Models (Operator)
- AuditLog (implemented):
model AuditLog {
id String @id @default(cuid())
userId String
schoolId String?
action String
reason String?
ip String?
userAgent String?
createdAt DateTime @default(now())
}
- DomainRequest (scaffolded, pending migration):
model DomainRequest {
id String @id @default(cuid())
schoolId String
domain String
status String @default("pending") // pending|approved|rejected|verified
notes String?
verifiedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
school School @relation(fields: [schoolId], references: [id], onDelete: Cascade)
@@unique([schoolId, domain])
}
- Planned:
Invoice
,Receipt
(billing MVP).
Enable Production Guards (when demo ends)
- Uncomment DEVELOPER guard block in
src/middleware.ts
(redirect unauthorized to/403
). - Uncomment auth/role checks in
src/app/(platform)/operator/layout.tsx
.
Commands
pnpm prisma generate
pnpm prisma migrate dev --name add_audit_and_domain_request