What I’m trying to implement is routing with tabs where the available tabs change depending on the user type (upon authentication the user type gets stored in a pinia store).
This is what I ended up implementing:
const mappingAudienceGuard = async (to: RouteLocation): Promise<true | '/mypath'> => {
const userStore = useUserStore()
// if user not in memory get user
if (userStore.user == null) {
await userStore.getUser()
}
if (to.meta.enabledTypes?.includes(userStore.user.type) ?? true) {
return true
}
return '/mypath'
}
const routes: RouteRecordRaw[] = [
{
path: '/mypath',
redirect: () => (isCustom(useUserStore().user) ? '/mypath/orders-custom' : '/mypath/orders'),
name: 'My Path',
beforeEnter: authenticationGuard,
component: DynamicInlineMenu,
props: {
links: [
{
to: '/mypath/orders',
text: 'Orders',
audience: [UserType.STANDARD]
},
{
to: '/mypath/orders-custom',
text: 'Orders Custom',
audience: [UserType.CUSTOM]
}
// [...]
],
title: 'My Title'
},
children: [
{
path: 'orders',
components: { tabContent: OrderListPage },
beforeEnter: mappingAudienceGuard,
meta: { enabledTypes: [UserType.STANDARD] }
},
{
path: 'orders-custom',
components: { tabContent: OrderListCustomPage },
beforeEnter: mappingAudienceGuard,
meta: { enabledTypes: [UserType.CUSTOM] }
}
// [...]
],
meta: {
requiresAuth: true,
title: 'My Title'
}
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
<template>
<div>
<TitleBar :title="title" />
<div>
<ul>
<li v-for="(link, index) in filteredLinks" :key="index">
<RouterLink
:to="link.to"
>
{{ link.text }}
</RouterLink>
</li>
</ul>
<router-view name="tabContent" />
</div>
</div>
</template>
<script setup lang="ts">
const userStore = useUserStore()
const filteredLinks = computed(() =>
props.links.filter(({ audience }) => audience.includes(userStore.user.type))
)
interface DynamicInlineMenuProps {
links: Array<{
to: string
text: string
audience: UserType[]
}>
title: string
}
const props = defineProps<DynamicInlineMenuProps>()
</script>
Basically we duplicate tabs information inside the props
variable of the route, while we already had such information inside the children
array.
I have to declare the audience for each tab twice (inside props and inside children) and I have to filter the tabs twice: I have to filter the props inside the template to not render the tab and I have to add a mappingAudienceGuard inside each children to prevent access if the user manually types an url which is not supposed to be available.
Also there is the redirect issue: ‘/mypath’ is supposed to show by default the first allowed path and I have to duplicate (triplicate?) such information inside the redirect
getter as well. I would like to have a single source of thuth.
One possible solution to avoid information duplication would be to remove props
and extrapolate the links from children
:
const route = useRoute()
const router = useRouter()
const links = router.options.routes.find(({ path }) => path === route.matched[0].path)?.children
but it’s cumbersome and I would still need to add redundant fields like to
to the meta
property. Also I will still have to duplicate information inside the redirect
getter. Maybe I could access the children from the redirect getter as well, but this whole solution doesn’t look like the proper way to handle such routing pattern anyway.
What do you suggest?