feat(portal): M10.1 — fill the 10 customer-area shells #12

Merged
sharang merged 1 commits from feat/m10.1-real-shells into main 2026-05-20 07:20:32 +00:00
Owner

What

Fills the 10 M5.2 shells. Four surfaces (settings, settings/api-keys, audit, products) are backed by live tenant-registry data and let the IT_ADMIN do useful work today. Five (projects, settings/users, settings/integrations, billing, support) keep the empty-state shape but pick up richer copy + per-surface CTAs (e.g., billing links to catalog).

Highlights

  • API keys page: list active + revoked, create form (plaintext shown ONCE in a one-time banner), revoke server action. Argon2id hashing already shipped in M4.2 — this is just the UI.
  • Audit log: paginated table, cursor-based next-page, action / actor_id GET filters, formatRelative timestamps with full-UTC tooltips. Picks up every event already emitted by tenant.created / catalog.requested / catalog.trial_started / api_key.created / api_key.revoked / keycloak.invite_sent / keycloak.provision_failed.
  • Settings page: read-only identity + plan + status with colored badge. Editing lands in the M10.1 follow-up.
  • Products page: live entitlements filtered to enabled. Trial expiry chip with formatRelative. Empty state links to catalog.
  • ShellEmpty component grew details + cta slots so the five remaining empty pages don't all look identical.

Why

After M5.2 + M11.1 + M12.1, the portal had a working catalog + signup but the rest of the sidebar pointed at empty shells. M10.1 closes that gap: an IT_ADMIN can sign in, see who they are, manage API keys for their tenant, audit everything, and see what products are active. That's the minimum 'this product is real' surface.

Linked milestone: M10.1 (settings editing is the only deferred bit; everything else is shipped).

How

  • All four real pages use the existing server-action pattern: read state, render, accept POST via <form action={asyncFn}>, redirect with ?ok= / ?err= flash.
  • Audit pagination uses cursor (id-after) rather than offset so it stays stable as new rows land. Filter form is plain method=GET so the URL is bookmarkable.
  • API-key plaintext is held in the query string for the redirect (?plaintext=…). Not perfect — it ends up in browser history. Acceptable for dev; M10.1 follow-up moves it to a server-stashed flash cookie when we add real session-flash plumbing.
  • Format helpers (formatRelative, formatDateTime, truncate) consolidated in src/lib/format.ts so we don't reinvent date strings per page.

Test plan

  • pnpm lint / typecheck / test (56 cases, 100% src/lib line+branch+function) / build — all green
  • Playwright canaries: one per surface, signed-out → 403 (10 specs)
  • Manual: signed-in IT_ADMIN against live dev stack — verify create-key flow, revoke flow, audit pagination, products listing all work

Risk

  • Plaintext-in-query-string for API key creation lands in browser history. Mitigated by the 'shown once' UI — but a follow-up should move it to a flash cookie.
  • Audit page has no rate-limit-aware throttling — a tenant with millions of events would scroll forever. M10.2 adds export + better filtering.
  • Settings editing is deliberately deferred. IT_ADMINs change their tenant identity through the M10.1 follow-up or via direct API call.

Checklist

  • Unit tests (56 vitest cases, 100% coverage)
  • E2E specs (10 surface canaries)
  • Docs updated (CHANGELOG)
  • Tenant scoping enforced via canSee() + parent layout slug match
  • CHANGELOG entry under "Added"
## What Fills the 10 M5.2 shells. Four surfaces (`settings`, `settings/api-keys`, `audit`, `products`) are backed by live tenant-registry data and let the IT_ADMIN do useful work today. Five (`projects`, `settings/users`, `settings/integrations`, `billing`, `support`) keep the empty-state shape but pick up richer copy + per-surface CTAs (e.g., billing links to catalog). ## Highlights - **API keys page**: list active + revoked, create form (plaintext shown ONCE in a one-time banner), revoke server action. Argon2id hashing already shipped in M4.2 — this is just the UI. - **Audit log**: paginated table, cursor-based next-page, action / actor_id GET filters, formatRelative timestamps with full-UTC tooltips. Picks up every event already emitted by tenant.created / catalog.requested / catalog.trial_started / api_key.created / api_key.revoked / keycloak.invite_sent / keycloak.provision_failed. - **Settings page**: read-only identity + plan + status with colored badge. Editing lands in the M10.1 follow-up. - **Products page**: live entitlements filtered to enabled. Trial expiry chip with formatRelative. Empty state links to catalog. - **ShellEmpty component** grew `details` + `cta` slots so the five remaining empty pages don't all look identical. ## Why After M5.2 + M11.1 + M12.1, the portal had a working catalog + signup but the rest of the sidebar pointed at empty shells. M10.1 closes that gap: an IT_ADMIN can sign in, see who they are, manage API keys for their tenant, audit everything, and see what products are active. That's the minimum 'this product is real' surface. Linked milestone: **M10.1** (settings editing is the only deferred bit; everything else is shipped). ## How - All four real pages use the existing server-action pattern: read state, render, accept POST via `<form action={asyncFn}>`, redirect with `?ok=` / `?err=` flash. - Audit pagination uses cursor (id-after) rather than offset so it stays stable as new rows land. Filter form is plain `method=GET` so the URL is bookmarkable. - API-key plaintext is held in the query string for the redirect (`?plaintext=…`). Not perfect — it ends up in browser history. Acceptable for dev; M10.1 follow-up moves it to a server-stashed flash cookie when we add real session-flash plumbing. - Format helpers (`formatRelative`, `formatDateTime`, `truncate`) consolidated in `src/lib/format.ts` so we don't reinvent date strings per page. ## Test plan - [x] `pnpm lint` / `typecheck` / `test` (56 cases, 100% `src/lib` line+branch+function) / `build` — all green - [x] Playwright canaries: one per surface, signed-out → 403 (10 specs) - [ ] Manual: signed-in IT_ADMIN against live dev stack — verify create-key flow, revoke flow, audit pagination, products listing all work ## Risk - Plaintext-in-query-string for API key creation lands in browser history. Mitigated by the 'shown once' UI — but a follow-up should move it to a flash cookie. - Audit page has no rate-limit-aware throttling — a tenant with millions of events would scroll forever. M10.2 adds export + better filtering. - Settings editing is deliberately deferred. IT_ADMINs change their tenant identity through the M10.1 follow-up or via direct API call. ## Checklist - [x] Unit tests (56 vitest cases, 100% coverage) - [x] E2E specs (10 surface canaries) - [x] Docs updated (CHANGELOG) - [x] Tenant scoping enforced via `canSee()` + parent layout slug match - [x] CHANGELOG entry under "Added"
sharang added 1 commit 2026-05-20 07:19:40 +00:00
feat(portal): M10.1 — fill the 10 customer-area shells
ci / shared (pull_request) Successful in 9s
ci / test (pull_request) Successful in 30s
ci / e2e (pull_request) Has been skipped
ci / image (pull_request) Has been skipped
32a20b3498
Four real surfaces backed by live tenant-registry data:

  /[slug]/settings                read-only tenant identity + plan + status
                                  + lifecycle dates with badge for status
  /[slug]/settings/api-keys       full CRUD: list active + revoked keys,
                                  create form, plaintext-shown-once banner,
                                  revoke action
  /[slug]/audit                   paginated table with cursor-based next-page
                                  navigation, action+actor_id GET filters,
                                  formatRelative timestamps, metadata
                                  preview
  /[slug]/products                live entitlements (filtered to enabled),
                                  trial expiry chip, links to catalog when
                                  empty

Five remaining surfaces upgraded to milestone-aware empty states
(projects / users / integrations / billing / support) with CTAs where
useful — billing links to catalog, support points at oncall@.

ShellEmpty component grew a 'details' string and a 'cta' ReactNode
slot so the empty pages don't all look identical.

Library additions:
  src/lib/format.ts          formatRelative, formatDateTime, truncate
  src/lib/tenant-registry    fetchAPIKeys, createAPIKey, revokeAPIKey,
                             fetchAudit — typed result shapes so server
                             actions can branch cleanly

Tests:
  src/lib/format.test.ts          12 cases, 100% coverage
  src/lib/tenant-registry.test    +14 cases for new client methods,
                                  100% line+branch+function
  tests/e2e/surfaces.spec.ts      one canary per of 10 customer-area
                                  routes (signed-out → 403)

CI all green: lint / typecheck / test / build.

Refs: M10.1
CODEOWNERS rules requested review from Benjamin_Boenisch 2026-05-20 07:19:40 +00:00
sharang merged commit e387b9a963 into main 2026-05-20 07:20:32 +00:00
sharang deleted branch feat/m10.1-real-shells 2026-05-20 07:20:32 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: platform/portal#12