-
Notifications
You must be signed in to change notification settings - Fork 0
Home
A complete, self-hosted, open-source staff management platform built with Next.js, Supabase, and Tailwind CSS.
StaffPortal gives a small-to-medium organisation a single role-based portal for attendance, leave, timesheets, expenses, visitors, wellness, and IT support, with single sign-on, an immutable audit trail, GDPR data exports, monthly leave accruals, a mobile-friendly kiosk, and an optional conversational AI assistant. You run it on your own Supabase project and Vercel deployment, so staff data never leaves infrastructure you control. This page is the deep reference. For a fast install, jump to Quick-Start; for the module-level breakdown, see Architecture; for what is planned, see Roadmap.
The newest capabilities each have a dedicated page: Single-Sign-On, Leave-Accruals, GDPR-Export, Kiosk-Mode, and Audit-Log. Per-area deep dives live at Attendance, Leave, Kiosk, Visitors, Crons, Reception, Diary, and Exports.
| Area | Capability |
|---|---|
| Attendance | Clock in/out, work-from-home toggle, late-arrival detection, correction requests |
| Timesheets | Weekly hours against contracted hours, Excel export |
| Leave | Annual, sick, maternity/paternity, unpaid; multi-step approval; PDF certificates; withdrawal with balance reversal |
| Expenses | Claims with receipt upload, category and merchant, PDF claim forms |
| Purchase requests | Submit and route to admin for approval |
| Visitors | Pre-registration with QR references, host email on check-in, PDF passes, reception desk, public kiosk |
| Wellness | Mood check-ins, breathing exercises, stretch reminders, admin dashboard |
| IT support | Ticket submission and tracking, auto-cleanup of resolved tickets via cron |
| Admin | Users, departments, schedules, leave allowances, leave accruals, corrections, audit log, analytics with CSV export, bank reconciliation |
| Single sign-on | Route staff to Microsoft Entra ID, Google Workspace, GitHub, GitLab, or SAML 2.0 by email domain |
| Leave accruals | Monthly accrual top-ups per balance, capped at full entitlement, run by cron or on demand |
| GDPR export | One-click portable JSON export of every record held about a member |
| AI assistant (the assistant) | Optional conversational assistant aware of your attendance, leave, expenses, and team |
StaffPortal is a single Next.js App Router application. There is no separate backend service: data access happens through React Server Components and server actions that talk to Supabase directly, and a small set of API route handlers cover the AI assistant and scheduled jobs.
flowchart TD
subgraph Client
Browser["Staff browser"]
Kiosk["Public kiosk (/kiosk)"]
end
subgraph App["Next.js on Vercel"]
MW["middleware.ts: session refresh + role guard"]
RSC["Server components + pages"]
Actions["Server actions (lib/actions)"]
ChatAPI["/api/chat (the assistant)"]
CronAPI["/api/cron/* handlers"]
end
subgraph Supabase
Auth["Supabase Auth + SSO"]
DB[("PostgreSQL + Row Level Security")]
Storage["Storage (receipts, assets)"]
end
Resend["Resend (transactional email)"]
Groq["Groq API (optional LLM)"]
IdP["Identity provider (Entra ID / Google / SAML)"]
Scheduler["Vercel Cron / GitHub Actions"]
Browser --> MW --> RSC
Kiosk --> RSC
RSC --> Actions --> DB
Actions --> Auth
Auth --> IdP
Actions --> Storage
ChatAPI --> Groq
ChatAPI --> DB
CronAPI --> DB
CronAPI --> Resend
Scheduler -->|Bearer CRON_SECRET| CronAPI
- Every request passes through
middleware.ts, which refreshes the Supabase session cookie via@supabase/ssrand redirects unauthenticated users to the login page. - Authenticated pages render as server components. They read data through server actions in
lib/actions, which use a request-scoped Supabase client carrying the user's session. - Authorisation is enforced twice: the middleware gates routes by role, and Postgres Row Level Security policies filter every row by the authenticated user and their role. The service-role key is used only in trusted server contexts (cron handlers, admin operations) and is never shipped to the client.
- The public kiosk at
/kioskis the one route that bypasses login. It uses scoped server actions for clock in/out and visitor check-in only, authenticated with a per-user kiosk PIN. - Single sign-on is layered on Supabase Auth. When a member enters an email whose domain has an active connection in
sso_connections, the login screen callssignInWithOAuth(Entra ID, Google, GitHub, GitLab) orsignInWithSSO(SAML 2.0) and redirects to the identity provider. The OAuth or SAML response returns through/auth/callback, where a first-time SSO user is bootstrapped with a profile and standard leave balances and the login is audited. See Single-Sign-On.
| Role | Access |
|---|---|
employee |
Own attendance, timesheets, leave, expenses, diary, calendar, announcements |
reception |
Employee access plus visitors, reception desk, kiosk settings |
director |
Employee access plus analytics, all timesheets (read-only), staff summary |
accounts |
Employee access plus all timesheets (read-only), expense reports |
admin |
Full access |
Cron endpoints under /api/cron/* are authenticated with an Authorization: Bearer <CRON_SECRET> header so only your scheduler can trigger them. Drive them with Vercel Cron (vercel.json), the bundled GitHub Actions workflows, or any external scheduler.
| Job | Endpoint | Suggested schedule |
|---|---|---|
| Birthday reminders | /api/cron/birthday-reminder |
Daily 08:00 |
| Absent reminders | /api/cron/absent-reminder |
Daily 10:00 |
| Missing attendance | /api/cron/missing-attendance |
Daily 18:00 |
| Forgotten clock-out | /api/cron/forgotten-clockout |
Daily 20:00 |
| Stretch reminder | /api/cron/stretch-reminder |
Weekdays 14:00 |
| IT ticket cleanup | /api/cron/it-ticket-cleanup |
Weekly |
| Diary reminders | /api/cron/diary-reminders |
Daily |
| Leave accrual | /api/cron/leave-accrual |
Monthly, 1st at 00:10 UTC |
| Year-end rollover | /api/cron/year-end-rollover |
Yearly, 1 Jan at 00:05 UTC |
An admin creates the account under Admin then User Management, assigns the employee role and a department, and sets contracted hours and working days under Work Schedule Management. The new starter signs up with the seeded email domain, verifies their address, and lands on a personalised dashboard showing leave balances and the week's hours. Their leave allowance is set under Leave Allowances, including any carry-forward cap.
An employee opens Expenses, enters the merchant, category, and amount, and uploads a receipt photo, which is stored in Supabase Storage. A Resend email notifies the approver. The approver reviews and approves in the admin UI, and a PDF claim form is generated on demand by PDFKit. The claim then appears in the accounts export for bank-statement reconciliation.
Reception pre-registers a visitor, which generates a QR reference. On arrival the visitor is checked in at the reception desk or the public kiosk, the host receives an email notification, and a printable PDF visitor pass is produced. Resolved records age out automatically.
An accounts user sets a balance's monthly accrual rate, for example two days of annual leave per month towards a twenty-four-day entitlement. On the first of each month the leave-accrual cron tops the balance up by the months that have elapsed since it last ran, never exceeding the entitlement. The job is idempotent, so a manual re-run the same day grants nothing further. The preview under Admin, Leave Accruals shows exactly what the next run will grant. See Leave-Accruals.
A member opens Settings, Privacy and data, and selects Download my data. The app gathers their profile, attendance, leave, expenses, diary, visitors, feedback, complaints, and the audit events attributed to them into a single JSON document and streams it back as a download. The export is recorded in the audit log. An administrator can export another member by adding ?userId=<id> to the endpoint. See GDPR-Export.
All configuration is via environment variables. Copy .env.local.example to .env.local and fill in real values. The required set is the Supabase URL and keys, the Resend API key and from-address, the public app URL, and CRON_SECRET. The Groq API key and notification routing addresses are optional. Never commit .env.local or expose SUPABASE_SERVICE_ROLE_KEY or CRON_SECRET to the client.
Build fails locally with missing environment variables. The production build reads public Supabase variables at build time. Ensure .env.local exists, or pass placeholder values on the command line as the CI workflow does. A successful build does not require live credentials.
Login redirect loop or "invalid redirect URL". Your Supabase auth configuration does not match the app URL. Under Authentication then URL Configuration, set the Site URL and add <your-url>/auth/callback to the redirect URLs for both http://localhost:3000 in development and your production domain.
Signup is rejected. The app restricts signups to a configured email domain. Check NEXT_AUTH_DOMAIN in your environment and make sure the address you are testing ends with that domain.
Cron endpoints return 401. The scheduler is not sending the bearer token, or it does not match. Confirm CRON_SECRET is set in the deployment environment and that your scheduler sends Authorization: Bearer <CRON_SECRET>.
Emails are not delivered. The RESEND_FROM_EMAIL must use a domain verified in Resend. Unverified domains are silently dropped. Verify the domain in the Resend dashboard and check the API key is valid.
The AI assistant does not respond. the assistant is optional and disabled without GROQ_API_KEY. Add a valid key, or comma-separate several keys for load balancing. The provider is OpenAI-compatible and can be swapped in app/api/chat/route.ts.
RLS errors or empty data for a valid user. A user is missing a role or department, so Row Level Security filters everything out. Check the user's row in the Supabase table editor and confirm the role and department are set.
SSO login does nothing or the password form keeps showing. The email domain has no active row in sso_connections. Add one under Admin, Single Sign-On, set it active, and confirm the provider app or SAML connection is configured in the Supabase dashboard under Authentication, Providers or Authentication, SSO.
Leave accruals grant nothing. Only balances with an accrual_rate greater than zero accrue, and a balance only grants once per elapsed calendar month. If the migration has not been applied the preview reports a pending migration; run migration 025 first.
GDPR export returns 403. Exporting another member is restricted to administrators. A non-admin can only export their own data, which is the default when no userId is supplied.
- Quick-Start: install on your own Supabase and Vercel
- Architecture: module map, data flow, and cron detail
- Single-Sign-On: map email domains to identity providers
- Leave-Accruals: monthly accrual mechanics
- GDPR-Export: the data portability endpoint
- Kiosk-Mode: the public PIN-based kiosk
- Audit-Log: what is recorded and where
- Roadmap: planned features
- Repository: https://github.com/sarmakska/staff-portal
- Product page: https://sarmalinux.com/products/staff-portal
- Licence: MIT