feat(portal): restructure into three-screen architecture with event tabs
Replace scattered dashboard pages with a three-screen volunteer portal: 1. Mijn evenementen (/evenementen) - landing page with visual event cards in a responsive grid, sorted upcoming-first 2. Event-pagina (/evenementen/:eventId) - single page with hash-based tabs (Overzicht, Mijn rooster, Diensten claimen, Informatie) replacing the old separate dashboard/my-shifts/claim-shifts pages 3. Mijn profiel (/profiel) - unchanged, platform-level settings Key changes: - Extract page content into tab components (RoosterTab, ClaimenTab, OverzichtTab, InformatieTab) that receive eventId as prop - Dual-mode navbar: platform mode (Crewli logo) vs event mode (org name + event name + back link) - StatusCard now emits switchTab events instead of route navigation - Smart login redirect: 1 event → direct to event, 2+ → overview - Backward-compat redirects for /dashboard/* → /evenementen - Delete EventSwitcher (replaced by events overview page) - Update UserAvatarMenu with "Mijn evenementen" link Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
<script lang="ts" setup>
|
||||
import EventSwitcher from '@/components/portal/EventSwitcher.vue'
|
||||
import UserAvatarMenu from '@/components/portal/UserAvatarMenu.vue'
|
||||
import { useAuthStore } from '@/stores/useAuthStore'
|
||||
import { usePortalStore } from '@/stores/usePortalStore'
|
||||
@@ -14,41 +13,20 @@ const route = useRoute()
|
||||
|
||||
const isMobileMenuOpen = ref(false)
|
||||
|
||||
const hideEventMenu = computed(() => route.meta.hideEventMenu === true)
|
||||
// Navbar mode: 'event' shows org name + event name + back link
|
||||
// Default ('platform') shows Crewli logo + page title
|
||||
const isEventMode = computed(() => route.meta.navMode === 'event')
|
||||
const navTitle = computed(() => (route.meta as any).navTitle as string | undefined)
|
||||
|
||||
const isApproved = computed(() => portal.currentPerson?.status === 'approved')
|
||||
const eventName = computed(() => portal.activeEvent?.event_name ?? '')
|
||||
const orgName = computed(() => portal.activeEvent?.organisation_name ?? '')
|
||||
|
||||
const hasActiveEvent = computed(() => !!portal.activeEventId)
|
||||
|
||||
const showEventMenu = computed(() => {
|
||||
if (hideEventMenu.value) return false
|
||||
if (!hasActiveEvent.value) return false
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
const menuItems = computed(() => {
|
||||
if (!showEventMenu.value) return []
|
||||
|
||||
const items = [
|
||||
{ title: 'Dashboard', to: '/dashboard', icon: 'tabler-layout-dashboard' },
|
||||
]
|
||||
|
||||
if (isApproved.value) {
|
||||
items.push(
|
||||
{ title: 'Mijn Diensten', to: '/dashboard/my-shifts', icon: 'tabler-calendar-check' },
|
||||
{ title: 'Diensten Claimen', to: '/dashboard/claim-shifts', icon: 'tabler-calendar-plus' },
|
||||
)
|
||||
}
|
||||
|
||||
return items
|
||||
})
|
||||
|
||||
// Mobile drawer items include profile + logout
|
||||
// Mobile nav items
|
||||
const mobileNavItems = computed(() => {
|
||||
const items = [...menuItems.value]
|
||||
|
||||
items.push({ title: 'Mijn Profiel', to: '/profiel', icon: 'tabler-user' })
|
||||
const items = [
|
||||
{ title: 'Mijn evenementen', to: '/evenementen', icon: 'tabler-calendar-event' },
|
||||
{ title: 'Mijn Profiel', to: '/profiel', icon: 'tabler-user' },
|
||||
]
|
||||
|
||||
return items
|
||||
})
|
||||
@@ -89,46 +67,73 @@ async function logout() {
|
||||
class="d-flex align-center py-0"
|
||||
style="max-inline-size: 1440px;"
|
||||
>
|
||||
<!-- Left section: Logo + Event Switcher -->
|
||||
<RouterLink
|
||||
to="/dashboard"
|
||||
class="d-flex align-center gap-x-2 text-decoration-none flex-shrink-0"
|
||||
>
|
||||
<VIcon
|
||||
icon="tabler-users-group"
|
||||
size="26"
|
||||
color="primary"
|
||||
/>
|
||||
<span class="text-h6 font-weight-bold text-high-emphasis d-none d-sm-inline">
|
||||
Crewli
|
||||
<!-- Event mode: Org name + Event name + Back link -->
|
||||
<template v-if="isEventMode">
|
||||
<!-- Org name / logo placeholder -->
|
||||
<div class="d-flex align-center gap-x-2 flex-shrink-0">
|
||||
<VIcon
|
||||
icon="tabler-building"
|
||||
size="24"
|
||||
color="primary"
|
||||
/>
|
||||
<span
|
||||
v-if="orgName"
|
||||
class="text-subtitle-1 font-weight-medium text-high-emphasis d-none d-sm-inline text-truncate"
|
||||
style="max-width: 200px;"
|
||||
>
|
||||
{{ orgName }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Event name -->
|
||||
<span
|
||||
v-if="eventName"
|
||||
class="text-body-1 text-medium-emphasis ms-2 text-truncate d-none d-sm-inline"
|
||||
style="max-width: 250px;"
|
||||
>
|
||||
{{ eventName }}
|
||||
</span>
|
||||
</RouterLink>
|
||||
|
||||
<EventSwitcher class="min-w-0 flex-grow-1 flex-sm-grow-0" />
|
||||
|
||||
<!-- Center section: Desktop menu items -->
|
||||
<div
|
||||
v-if="menuItems.length > 0"
|
||||
class="d-none d-md-flex align-center gap-1 ms-4"
|
||||
>
|
||||
<!-- Back link -->
|
||||
<VBtn
|
||||
v-for="item in menuItems"
|
||||
:key="item.to"
|
||||
:to="item.to"
|
||||
variant="text"
|
||||
color="default"
|
||||
size="small"
|
||||
exact
|
||||
class="portal-nav-btn"
|
||||
color="default"
|
||||
class="text-medium-emphasis ms-2 d-none d-md-flex"
|
||||
to="/evenementen"
|
||||
>
|
||||
<VIcon
|
||||
start
|
||||
:icon="item.icon"
|
||||
size="18"
|
||||
icon="tabler-arrow-left"
|
||||
size="16"
|
||||
/>
|
||||
{{ item.title }}
|
||||
Evenementen
|
||||
</VBtn>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Platform mode: Crewli logo + optional page title -->
|
||||
<template v-else>
|
||||
<RouterLink
|
||||
to="/evenementen"
|
||||
class="d-flex align-center gap-x-2 text-decoration-none flex-shrink-0"
|
||||
>
|
||||
<VIcon
|
||||
icon="tabler-users-group"
|
||||
size="26"
|
||||
color="primary"
|
||||
/>
|
||||
<span class="text-h6 font-weight-bold text-high-emphasis d-none d-sm-inline">
|
||||
Crewli
|
||||
</span>
|
||||
</RouterLink>
|
||||
|
||||
<span
|
||||
v-if="navTitle"
|
||||
class="text-body-1 text-medium-emphasis ms-4 d-none d-md-inline"
|
||||
>
|
||||
{{ navTitle }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<VSpacer />
|
||||
|
||||
@@ -226,31 +231,3 @@ async function logout() {
|
||||
</VFooter>
|
||||
</VApp>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.portal-nav-btn {
|
||||
position: relative;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
.portal-nav-btn::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
width: 0;
|
||||
height: 2px;
|
||||
background: rgb(var(--v-theme-primary));
|
||||
transition: width 0.2s ease, left 0.2s ease;
|
||||
}
|
||||
|
||||
.portal-nav-btn.router-link-active {
|
||||
color: rgb(var(--v-theme-primary)) !important;
|
||||
}
|
||||
|
||||
.portal-nav-btn.router-link-active::after {
|
||||
width: 60%;
|
||||
left: 20%;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user