← All work

Own product · 2026

Team Desk.

A franchise network’s whole operation in one system, with each person seeing only what is theirs.

Multi-tenant SaaSImmutable audit log~2k tests
Team Desk dashboard in dark mode
Team Desk dashboard on a phone

The problem

Missing metrics · Rework

A franchisor has to standardize many units and know what is happening in each one, without spreadsheets or WhatsApp groups. Owners, consultants, franchisees, managers and staff all see and do different things, and nothing can leak from one unit to another.

What I built

I built the whole product, from PRD to deploy. SLAs count business hours and escalate on their own. An inspection submitted more than 200 meters from the unit goes to review. Every "non-compliant" item becomes a 5W2H action plan due in 7 days. An approved lead becomes a unit, with its chat and rollout project created in the same transaction. The schedule recalculates its critical path every night, and the corporate university issues certificates with a public verification page.

Technical decisions

  1. 01An audit log the app itself cannot erase

    The audit table is partitioned by month, and the database role the app uses has no UPDATE, DELETE or TRUNCATE on it. A bug, or someone with access to the app, cannot rewrite history.

    Where it lives in the code: drizzle/0002_audit_partition_grants.sql

  2. 02Signed webhooks with SSRF protection

    Every delivery carries an HMAC-SHA256 over the timestamp and raw body, retries 6 times with backoff and disables the endpoint after 10 failures. Before calling a customer URL, its own DNS lookup refuses private network ranges.

    Where it lives in the code: lib/webhooks/signature.ts · url.ts · deliver.ts

  3. 03A public API that survives retries

    Every POST requires an Idempotency-Key, pagination uses cursors, the limit is 300 requests per minute and the contract ships as OpenAPI 3.0. A client that repeats a call never creates anything twice.

    Where it lives in the code: lib/api/idempotency.ts · openapi.ts

  4. 04Three processes, one domain

    Web (Next), workers (BullMQ) and realtime (Socket.io with a Redis adapter) run as separate processes over the same domain code. A heavy job never freezes anyone’s screen.

  5. 05Tests against a real database

    About 2,050 Vitest tests and 85 end-to-end tests. The integration ones run against real Postgres and Redis, and CI also checks that migrations match the schema. Uploads verify the file’s magic bytes, and rich text is stored as validated JSON, never as HTML.

Numbers

552

commits, single author

~2k

Vitest tests

85

end-to-end tests

61

tables

55

pages

13

webhook events

Stack

  • Next.js 15
  • React 19
  • TypeScript
  • Tailwind 4
  • shadcn/ui
  • Better Auth
  • PostgreSQL 16
  • Drizzle
  • Redis
  • BullMQ
  • Socket.io
  • S3
  • Vitest
  • Playwright
  • Docker

Screens

Unit rollout with a Gantt chart
Ticket queue with SLAs
Network chat
Next case

Finance Web