feat(shared-ui): move the role label from the portal-shell sidebar into the user menu #165
Reference in New Issue
Block a user
Delete Branch "feat/move-role-to-user-menu"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Moves the "Role: …" widget from the bottom of the
portal-shellsidebar into the user-menu panel header. Result: the role chip appears only when the reader is authenticated (the user menu only renders in that state), removing the noise of "Role: Anonymous" on signed-out traffic and de-duplicating the surface that already carries displayName / username / capability-gated actions.What lands
Shared component —
UserMenugets an optionalroleinputWhen set, a small brand-tinted pill (
data-testid="user-menu-role") renders inside the panel header below the username. When undefined, the entire chip is omitted — keeps backwards compat with any future caller that doesn't carry a role concept.Visually echoes the
.role-chipalready used on the admin/profilepage (rounded brand-tinted pill), tightened for the menu header density andalign-self: flex-startso the pill doesn't stretch.Portal-shell
roleLabelcomputed that derivesAdministrator/UserfromCapabilitiesService.canAccessAdmin— the same logic that previously lived in the sidebar. The template binds it as[role]="roleLabel()"on<lib-user-menu>.roleLabel+roleAriaLabelcomputeds, and theAuthService+CapabilitiesServiceinjections (the sidebar no longer needs them).data-testid="sidebar-role"no longer exists, and reverts to the pre-#151 setup (no Http testing infrastructure — the sidebar no longer fires/auth/meor/me/capabilities).AdministratorwhencanAccessAdmin: true,Userotherwise).Portal-admin
'Administrator'through[role]. Every reader who reaches the admin app already carriesPortal.Admin(it's theAdminRoleGuardprecondition for/api/admin/*); no need to re-derive. No i18n marks per ADR-0020's source-locale-only chrome.i18n
Five translation units gone from
messages.fr.xlf:Replaced by two new units under a generic prefix (the new owners are the user menu in either app, not the sidebar):
The
Anonymousstring is gone too — the chip is hidden on anonymous traffic, the label served no purpose without the user menu rendering it.Notes for the reviewer
common.role.*prefix rather thanuserMenu.role.*? The strings are reusable in any context that needs a curated role display (profile page header in a future PR, an audit log "actor role" column, …). Generic prefix avoids the next rename when a second consumer lands.roles? The admin SPA'sCurrentUser.rolescarries the raw Entra role string (Portal.Admin). Displaying that in the menu header would leak the internal nomenclature into the UI — fine for the/profilepage (it's explicit context there), wrong for the casual menu glance. The hardcoded label keeps the menu consistent with portal-shell ("Administrator" in both surfaces). If a non-admin role ever reaches portal-admin (the guard rejects it today, but the surface could evolve), this is the one place to revisit.roleson/api/admin/auth/me, and the chip is unconditional. Keeps the admin bundle untouched by the user-portal capabilities plumbing.role="menu"+aria-label. The chip doesn't need an extra label — it reads "Administrator" / "User" in context after the displayName and username, which conveys the meaning. No focus state needed (not interactive).Test plan
pnpm nx run-many -t lint test build --projects=shared-ui,portal-shell,portal-admin— green.shared-ui— 10 specs pass (was 8; +2 for the role chip).portal-shell— 43 specs pass (header +2, sidebar -3 + 1 guard = -2 net, balanced to +0 on the total → 43 unchanged).portal-admin— 50 specs pass (no spec edits; the[role]binding is exercised by the existing user-menu spec via the shared component).pnpm nx build portal-shell --configuration=production— i18n-strict prod build passes; confirms the xlf cleanup + newcommon.role.*keys are consistent with usage.Signed in as / displayName / username / [User]chip.Administrator, and the menu carries theOpen Portal Adminrow above Sign out.[Administrator]chip regardless of which admin user signed in.<p>element — directly hits the collapse toggle button.The "Role: Anonymous / User / Administrator" widget at the bottom of the portal-shell sidebar always struck the same audience as the user menu — it told the reader who they were and what they could do. With the menu now carrying the full identity card (displayName, username, role-curated actions like "Open Portal Admin"), keeping the role in the sidebar duplicates the surface and surfaces the label on anonymous traffic where it carries no real information ("Role: Anonymous" was a verbose synonym for "Sign in"). This PR moves the role chip into the user-menu panel header, where it sits between the username and the menu rows. Hidden on anonymous traffic by construction — the `<lib-user-menu>` only renders for the authenticated case in both apps' headers. Shape: * `UserMenu` (shared-ui) gains an **optional `role` input**. When set, a small brand-tinted pill renders inside the panel header below the username (`data-testid="user-menu-role"`). When undefined, the entire chip is omitted — keeps backwards compat with any future caller that doesn't carry a role concept. * `portal-shell`'s header computes `roleLabel` from the existing `CapabilitiesService.canAccessAdmin` signal (the same logic that previously lived in the sidebar), passing the localised string through the new input. The sidebar role widget — HTML + `roleLabel` / `roleAriaLabel` computeds + the `AuthService` and `CapabilitiesService` injections — is removed. * `portal-admin`'s header passes a hardcoded `'Administrator'`. Every reader who reaches the admin app already carries `Portal.Admin` (guard precondition); no need to re-derive. No i18n marks per ADR-0020's source-locale-only chrome. * The `sidebar.role.{anonymous,administrator,user,aria,label}` translation units are removed from `messages.fr.xlf`. Replaced by `common.role.{administrator,user}` (the new owners are the user menu in either app, hence the generic prefix). The "Anonymous" string is gone too — the chip is hidden on anonymous traffic, the label served no purpose without the user menu rendering it. * The portal-shell sidebar spec drops its three "role label" tests (the widget is gone) and grows a guard test asserting the `data-testid="sidebar-role"` element no longer exists. The setup is simplified back to the pre-#151 shape — no Http testing infrastructure needed, since the sidebar no longer injects `AuthService` / `CapabilitiesService`. * The portal-shell header spec gains two assertions for the role chip inside the open menu panel (Administrator / User). * The shared `UserMenu` spec gains two assertions covering the new input's presence/absence semantics. Verification: * 43 portal-shell specs pass (header +2, sidebar -2 net, balanced). * 50 portal-admin specs pass (no spec edits). * 10 shared-ui specs pass (was 8, +2 for the role chip). * `pnpm nx run portal-shell:build:production` (i18n-strict) green — confirms the xlf cleanup + new `common.role.*` keys are consistent with the source-locale usage.