A production-shaped client portal where the entire theme (colour, shape, type, density, and the transactional email) is generated from a handful of values on a tenant row. Change them and the whole product follows, instantly.
Switch tenant: watch everything below repaint
Outstanding balance
$65,700
12.4%vs last month
Hours logged this month
619
8.9%vs last month
Billed value · last 12 months
Theming is the easy half. The parts that decide whether a portal survives contact with real customers are the ones below.
Ramps, neutrals, foregrounds, and dark mode are generated in OKLCH from two hexes. Contrast is computed, not guessed.
Invitations, approvals, and invoice reminders resolve the same ramp to inline hex, because email clients have no CSS variables.
Row-level security scopes every table by membership, so a query bug can't leak another client's files.
⌘K palette, keyboard-navigable tabs, focus-visible rings, reduced-motion support, and a table view behind every chart.
Projects and milestones, approvals with an audit trail, versioned files, threaded messages, and billing.
A render.yaml that stands the service up, with per-pull-request previews and the schema checked into the repo.
Postgres with row-level security, auth (password, magic link, SSO), and storage for deliverables. The schema and its policies are in supabase/schema.sql.
A Web Service running the Next.js server, defined by render.yaml so the environment is reproducible and every pull request gets a preview.
Transactional email themed per tenant. Invitations, approval requests, and invoice reminders all render from the tenant's own palette.
Open the brand studio, paste in a hex, and the portal (and its email) is theirs in about four seconds.