Enhance Role-Based Visibility and Navigation Logic
- Introduced role visibility checks in AreaTabs.vue to filter displayed areas based on the user's active role, improving user experience and accessibility. - Updated RoleSwitcher.vue to enhance accessibility with a high-contrast checkmark for better visibility. - Modified useActiveRole.ts to streamline role initialization and refresh logic for role-based product visibility. - Added explicit keys for role-based data fetching in product-related pages to ensure accurate data refresh. - Enhanced API endpoint for product retrieval to return 404 if a product is not accessible based on the user's role, ensuring security and clarity.
This commit is contained in:
@@ -3,6 +3,8 @@ import { Wrench, FlaskConical, Ticket, Sparkles, GraduationCap, Home } from 'luc
|
||||
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
|
||||
type RoleCode = 'private' | 'educator' | 'company'
|
||||
|
||||
interface ProductArea {
|
||||
id: string
|
||||
label: string
|
||||
@@ -11,6 +13,7 @@ interface ProductArea {
|
||||
visible: boolean
|
||||
badge?: string
|
||||
route: string
|
||||
roleVisibility?: 'all' | RoleCode[]
|
||||
}
|
||||
|
||||
const areas: ProductArea[] = [
|
||||
@@ -21,6 +24,7 @@ const areas: ProductArea[] = [
|
||||
enabled: true,
|
||||
visible: true,
|
||||
route: '/',
|
||||
roleVisibility: 'all',
|
||||
},
|
||||
{
|
||||
id: 'makerspace',
|
||||
@@ -29,6 +33,7 @@ const areas: ProductArea[] = [
|
||||
enabled: true,
|
||||
visible: true,
|
||||
route: '/products',
|
||||
roleVisibility: 'all',
|
||||
},
|
||||
{
|
||||
id: 'educator',
|
||||
@@ -38,6 +43,7 @@ const areas: ProductArea[] = [
|
||||
visible: true,
|
||||
badge: 'Demnächst',
|
||||
route: '/educator',
|
||||
roleVisibility: ['educator'],
|
||||
},
|
||||
{
|
||||
id: 'experimenta',
|
||||
@@ -47,6 +53,7 @@ const areas: ProductArea[] = [
|
||||
visible: true,
|
||||
badge: 'Demnächst',
|
||||
route: '/experimenta',
|
||||
roleVisibility: 'all',
|
||||
},
|
||||
{
|
||||
id: 'labs',
|
||||
@@ -56,22 +63,41 @@ const areas: ProductArea[] = [
|
||||
visible: false,
|
||||
badge: 'Demnächst',
|
||||
route: '/labs',
|
||||
roleVisibility: ['educator', 'company'],
|
||||
},
|
||||
]
|
||||
|
||||
const route = useRoute()
|
||||
const { activeRole } = useActiveRole()
|
||||
|
||||
// Filter areas by role visibility
|
||||
const visibleAreas = computed(() => {
|
||||
return areas.filter(area => {
|
||||
// Legacy visible flag check
|
||||
if (!area.visible) return false
|
||||
|
||||
// No role requirement = visible to all (backward compatible)
|
||||
if (!area.roleVisibility) return true
|
||||
|
||||
// Explicitly set to 'all'
|
||||
if (area.roleVisibility === 'all') return true
|
||||
|
||||
// Check if user's active role matches
|
||||
return area.roleVisibility.includes(activeRole.value as RoleCode)
|
||||
})
|
||||
})
|
||||
|
||||
const currentArea = computed(() => {
|
||||
// Determine current area based on route - check areas array dynamically
|
||||
// Determine current area based on route - check visibleAreas array dynamically
|
||||
const currentPath = route.path
|
||||
|
||||
// Exact match for root path
|
||||
if (currentPath === '/') {
|
||||
return areas.find(area => area.route === '/')?.id || ''
|
||||
return visibleAreas.value.find(area => area.route === '/')?.id || ''
|
||||
}
|
||||
|
||||
// Find area where route path starts with area.route
|
||||
const matchedArea = areas.find(area =>
|
||||
const matchedArea = visibleAreas.value.find(area =>
|
||||
area.route !== '/' && currentPath.startsWith(area.route)
|
||||
)
|
||||
|
||||
@@ -90,7 +116,7 @@ function navigateToArea(area: ProductArea) {
|
||||
<!-- Desktop: Tabs -->
|
||||
<Tabs :model-value="currentArea" class="hidden md:block">
|
||||
<TabsList class="h-auto p-2 bg-white/5">
|
||||
<TabsTrigger v-for="area in areas.filter(area => area.visible)" :key="area.id" :value="area.id"
|
||||
<TabsTrigger v-for="area in visibleAreas" :key="area.id" :value="area.id"
|
||||
:disabled="!area.enabled" :class="[
|
||||
'gap-2 py-3 md:py-4 data-[state=active]:bg-accent data-[state=active]:text-white data-[state=active]:shadow-md',
|
||||
!area.enabled && 'opacity-50 cursor-not-allowed',
|
||||
@@ -112,7 +138,7 @@ function navigateToArea(area: ProductArea) {
|
||||
<!-- Mobile: Horizontal scroll with cards (matching desktop styling) -->
|
||||
<div class="md:hidden overflow-x-auto scrollbar-hide">
|
||||
<div class="inline-flex h-auto items-center justify-center rounded-[35px] bg-white/5 p-2 min-w-max">
|
||||
<button v-for="area in areas.filter(area => area.visible)" :key="area.id" :disabled="!area.enabled" :class="[
|
||||
<button v-for="area in visibleAreas" :key="area.id" :disabled="!area.enabled" :class="[
|
||||
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-[25px] px-4 py-3 text-lg font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-0',
|
||||
currentArea === area.id
|
||||
? 'bg-accent text-white shadow-md'
|
||||
|
||||
Reference in New Issue
Block a user