Skip to content

Architecture

sarmakska edited this page Jun 1, 2026 · 3 revisions

Architecture

How StaffPortal is wired together. This reflects the current code: a single Next.js 16 App Router application on React 19, backed by Supabase, with no separate backend service.

Layers

graph TD
  Browser[Browser / Mobile web] -->|HTTPS| Vercel[Vercel]
  Browser -->|public kiosk| Kiosk[/kiosk, PIN based, no login/]
  Vercel --> NextApp[Next.js 16 App Router, React 19]
  NextApp -->|server actions| Supabase[(Supabase Postgres + Auth + RLS)]
  NextApp -->|OAuth / SAML| IdP[Identity provider]
  NextApp -->|email send| Resend[Resend API]
  NextApp -->|optional AI| Groq[Groq llama-3.3-70b]
  CronJobs[Vercel Cron / GitHub Actions] -->|Bearer CRON_SECRET| NextApp
Loading

Module map

  • app/ App Router routes
    • app/(app)/ authenticated routes (attendance, leave, timesheets, expenses, calendar, directory, wellness, IT, and more)
    • app/(app)/admin/ admin and accounts routes (users, leave allowances, leave accruals, single sign-on, audit log, analytics, notifications, system status)
    • app/(auth)/ login, signup, password reset, email verification
    • app/kiosk/ public kiosk (no login, per-user PIN)
    • app/api/chat/ the assistant AI assistant (Groq, OpenAI-compatible)
    • app/api/cron/ scheduled tasks (reminders, cleanups, leave accrual, year-end rollover)
    • app/api/gdpr/export/ GDPR data portability endpoint
    • app/auth/callback/ OAuth, SAML, and email-link callback handler
  • lib/ shared logic
    • lib/supabase/ server, browser, and admin (service-role) clients
    • lib/actions/ server actions, the only place that mutates the database
    • lib/audit.ts immutable audit-log writer
    • lib/sso.ts SSO provider-resolution helpers (pure, unit tested)
    • lib/leave-accrual.ts accrual calculation (pure, unit tested)
    • lib/gdpr.ts export bundle assembly (pure, unit tested)
    • lib/email.ts Resend templates and PDF generators (PDFKit)
  • components/ UI primitives and shadcn/ui customisations
  • supabase/migrations/ schema as code (ordered SQL files, currently through 025)
  • types/database.ts generated and hand-extended database types
  • tests/ Node test-runner suites with fixtures

Auth model

  • Supabase Auth with email and password plus single sign-on
  • Server-side session via @supabase/ssr cookies, refreshed in middleware.ts
  • Five roles (employee, reception, director, accounts, admin) enforced by Postgres Row Level Security on every table
  • SSO is layered on Supabase Auth: OAuth providers use signInWithOAuth, SAML 2.0 uses signInWithSSO, and both return through /auth/callback
graph LR
  User[User] --> Login[Login screen]
  Login -->|domain has SSO| IdP[Identity provider]
  Login -->|no SSO| Password[Email + password]
  IdP --> Callback[/auth/callback/]
  Password --> Auth[Supabase Auth]
  Callback --> Auth
  Auth -->|JWT in cookie| Middleware[Next middleware]
  Middleware -->|gates routes| Pages[/(app), /admin, /kiosk/]
  Pages -->|server action| RLS[(Postgres + RLS)]
Loading

Data flow: an expense claim

  1. An employee uploads a receipt photo on the expenses page.
  2. A server action stores the image in Supabase Storage and inserts an expenses row.
  3. Optional vision OCR extracts merchant, total, and line items into structured columns.
  4. Resend emails the approver.
  5. The approver approves or rejects in the admin UI.
  6. A PDF claim form is generated on demand by PDFKit and served by a route handler.
  7. The claim appears in the accounts export for bank-statement reconciliation.

Data flow: monthly leave accrual

  1. On the first of each month the scheduler calls /api/cron/leave-accrual with the CRON_SECRET bearer token.
  2. The route reads every balance with a positive accrual_rate for the current year.
  3. computeAccrual in lib/leave-accrual.ts works out the days to grant from the months elapsed since last_accrued_on, capped at the entitlement.
  4. The balance total, accrued_to_date, and last_accrued_on are updated, and the grant is written to the audit log. Re-running the same day grants nothing.

Cron schedule

Cron Frequency Purpose
birthday-reminder daily 08:00 UTC Notify the team of upcoming birthdays
absent-reminder daily 10:00 UTC Remind staff who have not clocked in
missing-attendance daily 18:00 UTC Flag missing attendance
forgotten-clockout daily 20:00 UTC Detect staff who forgot to clock out
stretch-reminder weekdays 14:00 UTC Wellness stretch nudge
diary-reminders daily Date-based diary note reminders
it-ticket-cleanup weekly Archive resolved IT tickets
leave-accrual monthly, 1st 00:10 UTC Top up accruing leave balances
year-end-rollover yearly, 1 Jan 00:05 UTC Carry forward annual leave

Vercel Cron entries live in vercel.json; the bundled GitHub Actions workflows in .github/workflows/ call the same endpoints with the bearer token.

Read next

Clone this wiki locally