Files
LinkDesk/frontend/src/router/index.ts
T

244 lines
6.5 KiB
TypeScript

import { createRouter, createWebHistory } from 'vue-router'
import type { RouteRecordRaw } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
const routes: RouteRecordRaw[] = [
// Public routes
{
path: '/login',
name: 'Login',
component: () => import('@/views/auth/LoginView.vue'),
meta: { requiresGuest: true }
},
{
path: '/register',
name: 'Register',
component: () => import('@/views/auth/RegisterView.vue'),
meta: { requiresGuest: true }
},
// Protected routes
{
path: '/',
name: 'Dashboard',
component: () => import('@/views/DashboardView.vue'),
meta: { requiresAuth: true }
},
{
path: '/tasks',
name: 'Tasks',
component: () => import('@/views/TasksView.vue'),
meta: { requiresAuth: true }
},
{
path: '/projects',
name: 'Projects',
component: () => import('@/views/ProjectsView.vue'),
meta: { requiresAuth: true, roles: ['coordinator'], adminPermission: true }
},
// Project detail routes with tab navigation
{
path: '/projects/:projectId',
component: () => import('@/views/ProjectDetailView.vue'),
meta: { requiresAuth: true },
children: [
{
path: '',
name: 'ProjectOverview',
component: () => import('@/views/project/ProjectOverviewView.vue'),
meta: { tab: 'overview', tabLabel: 'Overview' }
},
{
path: 'shots',
name: 'ProjectShots',
component: () => import('@/views/project/ProjectShotsView.vue'),
meta: { tab: 'shots', tabLabel: 'Shots' }
},
{
path: 'shots/episode/:episodeId',
name: 'ProjectShotsEpisode',
component: () => import('@/views/project/ProjectShotsView.vue'),
meta: { tab: 'shots', tabLabel: 'Shots', hasEpisode: true }
},
{
path: 'shots/:shotId',
name: 'ShotDetail',
component: () => import('@/views/project/ShotDetailView.vue'),
meta: { requiresAuth: true }
},
{
path: 'assets',
name: 'ProjectAssets',
component: () => import('@/views/project/ProjectAssetsView.vue'),
meta: { tab: 'assets', tabLabel: 'Assets' }
},
{
path: 'tasks',
name: 'ProjectTasks',
component: () => import('@/views/project/ProjectTasksView.vue'),
meta: { tab: 'tasks', tabLabel: 'Tasks' }
},
{
path: 'settings',
name: 'ProjectSettings',
component: () => import('@/views/ProjectSettingsView.vue'),
meta: { tab: 'settings', tabLabel: 'Settings' }
}
]
},
{
path: '/projects/:projectId/episodes',
name: 'Episodes',
component: () => import('@/views/EpisodesView.vue'),
meta: { requiresAuth: true }
},
{
path: '/users',
name: 'Users',
component: () => import('@/views/UsersView.vue'),
meta: { requiresAuth: true, roles: ['coordinator'], adminPermission: true }
},
{
path: '/reviews',
name: 'Reviews',
component: () => import('@/views/ReviewsView.vue'),
meta: { requiresAuth: true, roles: ['director'], adminPermission: true }
},
{
path: '/settings',
name: 'Settings',
component: () => import('@/views/SettingsView.vue'),
meta: { requiresAuth: true, adminPermission: 'required' }
},
// Admin routes
{
path: '/admin',
name: 'Admin',
redirect: '/admin/deleted-items',
meta: { requiresAuth: true, adminPermission: 'required' }
},
{
path: '/admin/deleted-items',
name: 'RecoveryManagement',
component: () => import('@/views/admin/DeletedItemsManagementView.vue'),
meta: {
requiresAuth: true,
adminPermission: 'required',
title: 'Recovery Management'
}
},
// Developer routes
{
path: '/developer',
name: 'Developer',
redirect: '/developer/api-keys',
meta: { requiresAuth: true, roles: ['developer'] }
},
{
path: '/developer/api-keys',
name: 'DeveloperAPIKeys',
component: () => import('@/views/developer/APIKeysView.vue'),
meta: { requiresAuth: true, roles: ['developer'] }
},
{
path: '/developer/projects',
name: 'DeveloperProjects',
component: () => import('@/views/developer/ProjectsView.vue'),
meta: { requiresAuth: true, roles: ['developer'] }
},
{
path: '/developer/tasks',
name: 'DeveloperTasks',
component: () => import('@/views/developer/TasksView.vue'),
meta: { requiresAuth: true, roles: ['developer'] }
},
{
path: '/developer/analytics',
name: 'DeveloperAnalytics',
component: () => import('@/views/developer/AnalyticsView.vue'),
meta: { requiresAuth: true, roles: ['developer'] }
},
// Profile route
{
path: '/profile',
name: 'Profile',
component: () => import('@/views/ProfileView.vue'),
meta: { requiresAuth: true }
},
// Catch all route
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: () => import('@/views/NotFoundView.vue')
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
// Navigation guards
router.beforeEach(async (to, from, next) => {
const authStore = useAuthStore()
// Initialize auth if not already done
if (!authStore.user && authStore.accessToken) {
try {
await authStore.initializeAuth()
} catch (error) {
console.error('Auth initialization failed:', error)
}
}
const requiresAuth = to.meta.requiresAuth
const requiresGuest = to.meta.requiresGuest
const requiredRoles = to.meta.roles as string[] | undefined
const adminPermission = to.meta.adminPermission
const isAuthenticated = authStore.isAuthenticated
const userRole = authStore.userRole
const isAdmin = authStore.isAdmin
// Handle guest-only routes (login, register)
if (requiresGuest && isAuthenticated) {
return next('/')
}
// Handle protected routes
if (requiresAuth && !isAuthenticated) {
return next('/login')
}
// Handle role-based access with admin permission support
if (requiredRoles && userRole) {
const hasRequiredRole = requiredRoles.includes(userRole)
const hasAdminPermission = adminPermission && isAdmin
if (!hasRequiredRole && !hasAdminPermission) {
return next('/')
}
}
// Handle admin-only routes
if (adminPermission === 'required' && !isAdmin) {
return next('/')
}
next()
})
// Set page title from route meta
router.afterEach((to) => {
const title = to.meta.title as string
if (title) {
document.title = `${title} - VFX Studio`
} else {
document.title = 'VFX Studio'
}
})
export default router