Landing page introducing StaffVertex — the platform's workforce management features and value proposition. Organization dashboard showing a summary overview — team activity, project status, and key metrics at a glance. Timesheets view showing time logs for the team, with tracked hours per member. (Sensitive data has been redacted for confidentiality.) Taskboard for a selected project, showing tasks organized into columns by status (Custom columns can be added)
On this page
The Problem
Companies that run distributed or hybrid teams have to answer three questions every single day: who worked, on what, and for how long. Most of them answer it with a stack of disconnected tools. A spreadsheet for hours. A separate project board for tasks. A screenshot tool nobody trusts. An accountant who rebuilds payroll by hand at the end of the month. Client invoices are then assembled from whichever of those sources looks least wrong.
That patchwork fails in expensive ways:
- Hours are unverifiable. When a client disputes an invoice, or an employee disputes a payslip, nobody can produce evidence that a specific hour was actually worked. Manually typed timesheets are trusted right up until money is on the line.
- Every role sees the same data. Off-the-shelf trackers tend to be all-or-nothing: either someone is an admin who can see the entire company, or a member who can see almost nothing. Real organizations need a team lead who sees their team, a project manager who sees their projects, an HR user who sees people but not billing, and a client-facing viewer who sees a filtered slice.
- One tenant, one deployment. Tools built for a single company do not scale to an agency running several client organizations, each with its own working week, timezone, holidays, leave policy, screenshot rules and billing.
- Time data goes in but nothing useful comes out. Raw logs are not reports. Managers need attendance, payroll, per-project cost, per-client billing, weekly summaries and activity breakdowns, and they need them exportable.
- A tracker is only half a product. The desktop agent that records the time is worthless without a server that owns identity, permissions, org settings, billing and the historical record, and that keeps honouring the contract of every desktop build still installed in the wild.
The brief was to replace that entire patchwork with one multi-tenant platform where the recorded hour, the task it belongs to, the payroll line it produces and the client invoice it lands on are all the same piece of data.
What I Built
StaffVertex Web is a multi-tenant SaaS application and the authoritative backend for the whole product, including the cross-platform desktop time tracker. It owns the database, authentication, permissions, billing and every business rule. The desktop app is a client.
Scale of the build
- Roughly 140,000 lines of TypeScript across the app, components, hooks, API and data layer
- 323 API route handlers under a versioned
/api/v1surface, plus a separate super-admin API - 66 Mongoose schemas and around 405 React components
- 98 application pages, organised by access level using Next.js route groups (guest, public, private, shared, admin, super-admin)
Multi-tenancy and access control
Every query is scoped to an organization, so one deployment serves many independent companies with their own settings, members, projects, billing and data retention rules.
On top of that sits a permission engine I designed and migrated the platform onto: a flat permission-key model (for example projects.view.team) checked through a single can() / effectiveScope() API. Permissions are not just on or off, they carry a scope: own, team, project, or organization-wide. A role builder lets an admin compose custom roles from a central permission catalog, while system roles stay immutable so a tenant cannot lock itself out. Team scope is backed by a real Team collection with members and leaders, so "my team's timesheets" resolves correctly instead of being faked in the UI.
Time tracking, projects and people
- Timesheets, time logs, manual entry with optional approval, and back-dated entry rules
- Projects, tasks, task boards, configurable task statuses, task relations, labels and clients
- Teams, members, invitations, join requests and an offboarding flow that preserves history
- Leave management, leave types, holidays and attendance with grace periods
- Screenshot gallery with optional blurring, plus application and website usage tracking with configurable category rules to separate productive from unproductive time
Reporting and money
Eleven report surfaces including time and activity, attendance, payroll, member, project, client, task, weekly summary and activity log, plus a report builder for custom outputs and PDF/data export. Invoicing and payments sit on the same data, so a billable hour flows to an invoice line without re-entry. Stripe powers subscriptions, plans and the billing lifecycle, with a reconciliation console for when the real world disagrees with the webhook.
Notifications
A central notification layer fans a single business event out to four channels (in-app bell, email, browser push and native desktop push) with per-user, per-event preferences. Channel failures never block the originating action, so assigning a task always succeeds even if email is down.
The desktop contract
A dedicated /api/v1/desktop/* surface handles authentication (including SSO through a staffvertex:// deep link), session validation, org and settings sync, project/task fetch, time log sync, screenshot upload via presigned Cloudflare R2 URLs, app usage ingestion and the auto-updater manifest. Because real users run older desktop builds that do not update on command, I hold this contract to a strict additive-only rule: new fields are optional with safe server defaults, old endpoints stay alive after replacements ship, and no migration is allowed to strand a client that never updates.
Time integrity
Hours are only worth something if you can defend them. I built server-side integrity rules that are deliberately not tenant-configurable, because a check a customer can switch off produces time logs nobody can stand behind in a dispute:
- A trusted-clock rule validates client timestamps against the server's own clock, so a machine with a manipulated system time cannot mint hours
- A physical daily ceiling caps any day's total at the real hours elapsed in that day, independent of the org's configured maximum
- Open timers are bounded at read time from the last heartbeat instead of being blind-closed by a scheduled sweep, so a user who is offline but genuinely working is never truncated
- A per-organization sync floor rejects unverifiable offline rows from outside a known-good window, and every integrity decision is written to an auditable log
Operations and internal tooling
A super-admin console for organizations, users, plans, subscriptions, invoices, platform analytics, billing reconciliation and a soft-delete trash. I also built an AI provider registry (Anthropic, OpenAI and Google behind one interface) where every model call is metered into a usage and cost ledger attributable to the feature that spent it, and an AI-assisted QA system that generates test scenarios and runs them through Playwright against the live app.
Outcomes
- Shipped and running in production. StaffVertex Web is live with paying organizations, serving both the browser app and every installed copy of the desktop tracker.
- One system replaced the patchwork. A tracked hour now carries its project, task, activity level and evidence from capture through to the timesheet, the payroll report and the client invoice, with no re-keying between tools and no month-end reconstruction.
- Permissions that match how companies are actually shaped. The scoped permission model plus the role builder lets an organization express "team lead sees their team, PM sees their projects, HR sees people but not billing" without a code change. Migrating the whole platform from the legacy nested permission object to the flat key model was done incrementally behind a stable
can()API, so no shipped screen broke during the transition. - Hours that survive a dispute. Clock manipulation, impossible daily totals and unverifiable offline submissions are rejected or flagged at the server, and every decision is auditable rather than silent. That is the difference between a tracker a client tolerates and one a client will pay against.
- Zero-break compatibility with the desktop fleet. The API is versioned and additive-only. Multiple server-side features have shipped since the current desktop generation without breaking a single older installed build.
- Built to keep changing. RTK Query with tag-based cache invalidation, a versioned API surface, Zod-validated inputs, strict TypeScript and route groups by access level mean features land without regressions rippling outward. Around 2,800 commits of continuous iteration on a codebase that has stayed shippable throughout.
- Reporting became the reason people log in. Eleven ready-made reports plus a custom report builder and export turned the product from a data-collection tool into a decision-making one, which is where its value to managers and finance teams actually sits.