feat(layout): context-switcher for multi-role users

Adds components/shared/ContextSwitcher.vue — a Vuetify menu-button
that renders only when useAuthStore.showContextSwitcher is true (i.e.
the user has both portal and organizer contexts available). Click
calls useAuthStore.setLastContext + resolveLandingRoute and pushes
the new route.

Wired into both layouts:
- PortalLayout.vue: navbar right section, before UserAvatarMenu
- DefaultLayoutWithVerticalNav.vue (organizer navbar host): before
  NavbarThemeSwitcher (OrganizerLayout.vue itself is a 10-line
  wrapper around DefaultLayoutWithVerticalNav, so the component
  wires into the actual navbar host).

Boundaries matrix update: components-shared now allows `stores` so
canonical shared chrome (ContextSwitcher, future global indicators)
can read useAuthStore directly without re-homing to
components/layout/. stores-portal stays disallowed for components-
shared by design — portal-specific state has no place in shared
chrome.

Adds 3 vitest specs covering: visibility gated by
showContextSwitcher, click invokes setLastContext + router.push.

Test count 189 → 192. Frontend lint + typecheck clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-05 21:35:32 +02:00
parent 473b22ac9e
commit 209e0ef682
5 changed files with 137 additions and 2 deletions

View File

@@ -8,6 +8,7 @@
import { useRoute, useRouter } from 'vue-router'
import type AppLoadingIndicator from '@/components/AppLoadingIndicator.vue'
import ContextSwitcher from '@/components/shared/ContextSwitcher.vue'
import UserAvatarMenu from '@/components/portal/UserAvatarMenu.vue'
import { useAuthStore } from '@/stores/useAuthStore'
import { usePortalStore } from '@/stores/portal/usePortalStore'
@@ -138,8 +139,9 @@ async function logout() {
<VSpacer />
<!-- Right section: Avatar menu (desktop) -->
<!-- Right section: context switcher + avatar menu (desktop) -->
<div class="d-none d-md-flex align-center">
<ContextSwitcher class="me-2" />
<UserAvatarMenu />
</div>

View File

@@ -11,6 +11,7 @@ import NavSearchBar from '@/layouts/components/NavSearchBar.vue'
import NavbarShortcuts from '@/layouts/components/NavbarShortcuts.vue'
import NavbarThemeSwitcher from '@/layouts/components/NavbarThemeSwitcher.vue'
import UserProfile from '@/layouts/components/UserProfile.vue'
import ContextSwitcher from '@/components/shared/ContextSwitcher.vue'
import OrganisationSwitcher from '@/components/layout/OrganisationSwitcher.vue'
// @layouts plugin
@@ -76,6 +77,7 @@ const navItems = computed(() => {
<NavSearchBar class="flex-grow-1 ms-lg-n3 min-w-0" />
<ContextSwitcher class="flex-shrink-0 me-2" />
<NavbarThemeSwitcher class="flex-shrink-0 me-2" />
<NavbarShortcuts class="flex-shrink-0" />
<NavBarNotifications class="flex-shrink-0 me-1" />