Skip to content

feat(portal): master-admin portal console UI (FF-EPIC-17-S7) - #709

Merged
izzywdev merged 3 commits into
masterfrom
feat/portal-master-admin-console-s7
Aug 26, 2026
Merged

feat(portal): master-admin portal console UI (FF-EPIC-17-S7)#709
izzywdev merged 3 commits into
masterfrom
feat/portal-master-admin-console-s7

Conversation

@claude

@claude claude Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

📋 Description

Implements the master-admin portal fleet console UI for FF-EPIC-17-S7, against design/frames/portal-admin-consoles/** (flow master-admin-portals) and the merged org-tree portal contract (@fuzefront/security-client 0.7.0, PR #704).

packages/portal-admin-ui's MasterAdminPortalsFlow already existed against the anticipated, now-superseded @fuzefront/portal-client model (/api/v1/admin/portals, a standalone portals-table entity). This PR migrates it onto the REAL, frozen contract: a portal is now an organizations row whose parentOrgId is the platform root, reached via GET/POST /api/v1/security/portals and POST /api/v1/security/portals/{portalOrgId}/(suspend|resume). Only the master-admin-portals flow is touched — PortalAdminConsoleFlow (portal-console / portal-billing flows, still anticipated contracts) is untouched.

🔄 Type of Change

  • ✨ New feature (non-breaking change which adds functionality)
  • 🔨 Build system or dependency update (adds @fuzefront/security-client to portal-admin-ui)

🧪 Testing

  • Unit tests (vitest, both packages)
  • Integration tests
  • E2E tests (frontend-test-engineer's Playwright suite — see notes below)
  • Manual testing (Chrome DevTools MCP not installed this session — see notes below)

Results

  • packages/portal-admin-ui: npm run -w @fuzefront/portal-admin-ui test40/40 passed (4 files: adminPortalsClient.test.ts, MasterAdminPortalsFlow.test.tsx rewritten for the new contract; portalConsoleClient.test.ts + PortalAdminConsoleFlow.test.tsx untouched, still green — confirms no cross-flow breakage).
  • packages/portal-admin-ui: npm run -w @fuzefront/portal-admin-ui type-check → clean (with @fuzefront/security-client + @fuzefront/portal-client dist built first, matching the CI ordering convention identity-ui/auth-ui already use).
  • packages/portal-admin-ui: npm run -w @fuzefront/portal-admin-ui build → clean (vite build + tsc --emitDeclarationOnly).
  • New frontend/src/__tests__/MasterAdminPortalsPage.test.tsx — covers the flag gate (OFF → no console chrome, zero fetches; ON → fetches the real contract) and the frame states (empty, error+retry, 403 FORBIDDEN in place) through the real host wiring → 5/5 passed.
  • frontend: npm run type-check / npm run lint / npm run build → all clean. npx vitest run218/218 passed (38 files, includes the new page test).
  • python3 scripts/gate_ds_conformance.py packages/portal-admin-ui/srcOK, no raw design values.
  • git diff --check → clean (LF line endings).

🔧 Implementation Details

Changes Made

  • packages/portal-admin-ui (frontend package, master-admin flow only):
    • src/api/adminPortalsClient.ts — rewritten against @fuzefront/security-client's generated components['schemas'] (Portal, PortalCreate, PortalPage, PortalStatus, PortalBranding, PortalBillingMode, PortalAppCatalogMode), hitting /api/v1/security/portals* via this package's own HttpClient (same convention as identity-ui's employeeClient.tssecurity-client ships types only, no runtime client).
    • src/types.ts — adds AdminPortal* type aliases sourced straight from the generated contract, deliberately named distinctly from the existing Portal/PortalStatus/BillingMode (which the other, still-anticipated portal-console flow keeps using — the two models have genuinely different shapes: orgId vs id/organizationId, a single customDomain vs a domains[] list).
    • src/flows/MasterAdminPortalsFlow.tsx, src/components/master/* — updated for the real shape. The platform root org is never returned by GET /api/v1/security/portals (per contract), so the fresh-install empty state is simply zero portals and no root-row suspend-guard is reachable through this UI — both are documented deviations from the (unapproved) frame, which was built against the superseded model.
    • CreatePortalDialog — extended with the tenant-attribute fields the task calls for: custom domain, branding (display name + tagline), app-catalog mode (inherit/custom), plus the existing billing-mode picker.
    • src/api/http.ts — fixes two latent bugs found while wiring the new client: (1) the fetch fallback wasn't bound to globalThis, which throws "Illegal invocation" in a real browser; (2) HttpError.code preferred the ErrorBody's human error message over its machine-readable code enum, backwards from the family { error, code } contract — this is what isPortalsForbidden/isSlugConflict rely on.
  • packages/feature-flagsfuzefront.platform.multi-tenant-portals is now declared in WEB_EXPOSED_FLAGS (src/catalog.ts) and flag-registry.yaml's web_exposed flipped false → true. The flag already gated the console in the host (frontend/src/pages/MasterAdminPortalsPage.tsx, pre-existing) via useFlag, but wasn't disclosed to the browser by GET /api/flags — same class of gap as feat(feature-flags): web-expose the FF-EPIC-17 identity UI flags #697, so the gate was silently always falling back to its OFF default regardless of the Unleash toggle.
  • frontend/src/__tests__/MasterAdminPortalsPage.test.tsx (new) — end-to-end coverage of the flag gate (both states) and the frame states through the real host wiring.

Frame / contract deviations (frames are approved: false, superseded-model)

design/frames/portal-admin-consoles/{01,02,03,04}-*.html were authored against the anticipated @fuzefront/portal-client model and are explicitly marked "Contract (anticipated — @fuzefront/portal-client, not frozen today)" in their own notes. Building against the real frozen contract instead required:

  • No "root" row / root-guard UI (platform root is never listed — see above).
  • No Free/Pro/Scale plan tiers — billingMode (free|platform|reseller) is rendered honestly, matching the pre-existing PlanBadge rationale.
  • No per-domain verification table (portal_domains, FF-EPIC-16) — a single nullable customDomain field.
  • 409 error code is generic CONFLICT (not SLUG_TAKEN/ROOT_PORTAL_PROTECTED) — data-error-code="CONFLICT" on the inline slug-taken message.

📋 Checklist

  • Self-review completed
  • TypeScript strict mode passes (tsc --noEmit, both packages)
  • ESLint passes without errors (frontend, --max-warnings 0)
  • New and existing unit tests pass locally
  • No raw design values (gate-ds-conformance clean); no base/DS primitive forked or shadowed — design-system/ untouched
  • Chrome DevTools MCP render — not run this session (plugin not installed); see notes below

📝 Additional Notes

  • Frames approval: all three flows in design/frames/portal-admin-consoles/manifest.json are approved: false (owner's design-approval step in progress separately). The manifest declares no implementation.paths, so this PR lands under gate-frames-first's warn mode for uncovered paths (same as S4/S5/S9) — not blocked, per repo convention. Did not hand-edit approved.
  • Runtime validation caveat: the Chrome DevTools MCP was not installed in this session, so the mandatory real-Chromium console-clean render was not performed. Self-check is limited to build/vitest/tsc/eslint above. frontend-test-engineer (or a future session with the MCP available) should render /admin/portals (flag ON, desktop + small-screen) and confirm a clean console before this ships to production traffic.

OUT OF SCOPE — NOT DONE

  • Backend/API/services (the contract itself — @fuzefront/security-client 0.7.0 — already merged in feat(contract): portal CRUD as org-tree operations (FF-EPIC-17-S7) #704; no backend work here).
  • The independent API acceptance/contract test suite (test-engineer).
  • Playwright / pre- and post-production UI verification (frontend-test-engineer) — frontend/tests/portal-admin-consoles.red.spec.ts still targets the superseded @fuzefront/portal-client model/root-row shape and will need reconciliation once the frames are re-approved against the real contract; not touched here (out of frontend-engineer scope).
  • Helm/Argo/CI/CD (devops-engineer) — no CI workflow currently builds/tests packages/portal-admin-ui standalone (it's exercised only indirectly via frontend's vite/vitest source aliases); worth a follow-up but out of this PR's scope.
  • Feature-flag administration/Unleash config (feature-flags-engineer) — only the web-exposure fix above, which the task explicitly called out.
  • The portal-console / portal-billing flows (PortalAdminConsoleFlow, PortalBillingFlow) — separate stories (FF-EPIC-14-S3/S4), still targeting anticipated contracts, untouched.

Generated by Claude Code

Migrate the master-admin portal fleet console (@fuzefront/portal-admin-ui)
off the anticipated, now-superseded @fuzefront/portal-client model onto the
REAL, merged org-tree portal contract (@fuzefront/security-client 0.7.0,
PR #704): GET/POST /api/v1/security/portals, GET/POST(suspend|resume)
/api/v1/security/portals/{portalOrgId}. A portal is now an organizations row
with parentOrgId = the platform root, not a portals-table entity, so the
platform root is never listed — the fleet table, create form, detail panel,
and suspend dialog all update accordingly (orgId, customDomain, real
branding, appCatalogMode; no root-row guard, no domains[] table).

Adds the tenant-attribute fields (custom domain, branding, app-catalog mode,
reseller-billing mode) to the create-portal form per the real PortalCreate
body. Wires the console behind fuzefront.platform.multi-tenant-portals via
useFlag in the host (MasterAdminPortalsPage, pre-existing), and exposes that
flag to the browser via WEB_EXPOSED_FLAGS/flag-registry.yaml (it wasn't
web-exposed, so the gate always fell back to its OFF default — same class of
gap as #697).

Fixes two latent bugs in portal-admin-ui's HttpClient found while wiring the
new client: the global fetch fallback wasn't bound to globalThis (breaks in
a real browser with "Illegal invocation"), and HttpError.code preferred the
ErrorBody's human message over its machine-readable `code` enum.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017zdYnxKQVVhFyRubDa1BmK
@claude
claude Bot requested a review from izzywdev as a code owner August 18, 2026 07:47
@claude claude Bot added the auto-merge Enable squash auto-merge once CI passes label Aug 18, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Automated code review (gate-code-review)

Credit balance is too low

Report-only — this check never blocks merge.

@github-actions
github-actions Bot enabled auto-merge (squash) August 18, 2026 18:49
@github-actions

Copy link
Copy Markdown
Contributor

Automated code review (gate-code-review)

Credit balance is too low

Report-only — this check never blocks merge.

@github-actions

Copy link
Copy Markdown
Contributor

Automated code review (gate-code-review)

Credit balance is too low

Report-only — this check never blocks merge.

@izzywdev izzywdev left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All CI gates pass (gate-authz, gate-ds-conformance, gate-identifier, gate-frames-first, gate-test, gate-lint, gate-build, gate-sast, gate-toolchain, gate-version, gate-localup, etc.). Approving per governance policy.

@izzywdev
izzywdev merged commit 78e4924 into master Aug 26, 2026
66 of 67 checks passed
@izzywdev
izzywdev deleted the feat/portal-master-admin-console-s7 branch August 26, 2026 05:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto-merge Enable squash auto-merge once CI passes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants