SAAS_PLAN.md 2.2: there was no /signup page. The only self-serve path was
Google sign-in on /login, which auto-provisions a Firebase account with no
role or org claim at all - previously that meant "viewer role, full read
access" the moment the AuthProvider cookie logic (previous commit) let it
through. That's closed now regardless; this commit is the other side of it
- giving people an actual way in.
app/signup/page.tsx: email/password (createUserWithEmailAndPassword) or
Google, same visual language as /login. It only creates the Firebase
account - org naming is deliberately not on this page, so every path that
produces an account with no org (this one, and Google-via-/login) converges
on the same next screen.
app/onboarding/page.tsx: that screen. Shown to any signed-in user with no
orgId (ChromeSwitcher's redirect, previous commit), collects an org name,
calls the new c2api.signup() -> POST /auth/signup (routers/links.py,
already shipped), then refreshClaims() to force-refetch the ID token so
orgId picks up immediately and the same redirect effect sends them on to
/dashboard - no manual reload needed.
lib/c2api.ts also gained getOrg/updateOrg and the enrollment-token
mint/list/revoke calls (routers/org.py, already shipped on the backend)
and joinWaitlist (routers/waitlist.py) - none consumed yet, wired in ahead
of the settings/legal commits that use them so this stays one add per
concept rather than scattering client additions across later commits.
/login gained a "Don't have an account? Sign up" link to /signup. This is
signup plumbing, not marketing copy - pricing/plan copy (app/pricing,
lib/billing.ts) is untouched in this pass, that's a separate, still-open
decision (SAAS_PLAN.md section 6).
Typecheck: clean (tsc --noEmit via the WSL-native ~/drb-frontend copy).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The UI worked but read as an operator console: no public face, no way to
describe or sell the thing, and no account surface beyond the node list. This
adds the missing halves and reorganises what was already there around the
incident, which is the unit of value the rest of the pipeline is built to
produce.
A shared design system replaces per-page styling: components/ui (Button, Card,
Badge, EmptyState, Skeleton, PageHeader), a type scale and shadow set in the
Tailwind config, and light-mode tokens in globals.css. The existing
html:not(.dark) remap mechanism is extended rather than replaced -- a parallel
theming system would have been two sources of truth for the same colours.
Public marketing pages (/, /features, /pricing, /faq) load without a session.
middleware.ts gained a PUBLIC_PATHS allowlist to permit that; it remains a UX
redirect and is still NOT an authorisation boundary, which the comment there
says explicitly. Real enforcement is unchanged and still lives server-side in
c2-core's auth.py. Chrome switching is done by pathname in ChromeSwitcher
instead of by route group, because a route group would have collided on / and
forced most of app/ to move for no behavioural gain.
Billing and API keys ship as typed stubs, not integrations. lib/billing.ts and
lib/apiKeys.ts define the data model and the screens consume it, but every
mutating call throws with a message naming the backend route that has to exist
first, and the sample data is labelled as sample. Nothing here can charge
anyone or mint a real credential -- picking a payment processor and holding its
keys is a decision for a human, and a half-wired checkout is worse than an
obviously absent one.
The severity work from the c2-core change lands here too. severity is now a
filter and sort dimension on the incident list rather than decoration, since
a busy dispatch channel is only readable if you can collapse it to moderate and
above. routine gets a muted treatment because it is the majority of traffic,
legacy "unknown" still renders nothing, and TypeBadge handles the new "other"
incident type. Severity rendering moved into lib/severity.tsx so the incident
list, incident detail and call rows cannot drift apart.
Deliberately not touched: calls, map, alerts, nodes, systems, tokens, trips and
admin. They already share the palette and stay coherent, and rewriting them
would have buried the parts that actually needed to change. No colour tokens
were renamed, so nothing regressed there.
Verified with tsc --noEmit (npm run typecheck), clean. No runtime verification
was possible and none was done. No new environment variables.