diff --git a/frontend/src/router/README.md b/frontend/src/router/README.md index 9f600f81..4ffacb8f 100644 --- a/frontend/src/router/README.md +++ b/frontend/src/router/README.md @@ -13,38 +13,38 @@ This directory contains the Vue Router configuration for the Sub2API frontend ap ### Public Routes (No Authentication Required) -| Path | Component | Description | -|------|-----------|-------------| -| `/login` | LoginView | User login page | +| Path | Component | Description | +| ----------- | ------------ | ---------------------- | +| `/login` | LoginView | User login page | | `/register` | RegisterView | User registration page | ### User Routes (Authentication Required) -| Path | Component | Description | -|------|-----------|-------------| -| `/` | - | Redirects to `/dashboard` | -| `/dashboard` | DashboardView | User dashboard with stats | -| `/keys` | KeysView | API key management | -| `/usage` | UsageView | Usage records and statistics | -| `/redeem` | RedeemView | Redeem code interface | -| `/profile` | ProfileView | User profile settings | +| Path | Component | Description | +| ------------ | ------------- | ---------------------------- | +| `/` | - | Redirects to `/dashboard` | +| `/dashboard` | DashboardView | User dashboard with stats | +| `/keys` | KeysView | API key management | +| `/usage` | UsageView | Usage records and statistics | +| `/redeem` | RedeemView | Redeem code interface | +| `/profile` | ProfileView | User profile settings | ### Admin Routes (Admin Role Required) -| Path | Component | Description | -|------|-----------|-------------| -| `/admin` | - | Redirects to `/admin/dashboard` | -| `/admin/dashboard` | AdminDashboardView | Admin dashboard | -| `/admin/users` | AdminUsersView | User management | -| `/admin/groups` | AdminGroupsView | Group management | -| `/admin/accounts` | AdminAccountsView | Account management | -| `/admin/proxies` | AdminProxiesView | Proxy management | -| `/admin/redeem` | AdminRedeemView | Redeem code management | +| Path | Component | Description | +| ------------------ | ------------------ | ------------------------------- | +| `/admin` | - | Redirects to `/admin/dashboard` | +| `/admin/dashboard` | AdminDashboardView | Admin dashboard | +| `/admin/users` | AdminUsersView | User management | +| `/admin/groups` | AdminGroupsView | Group management | +| `/admin/accounts` | AdminAccountsView | Account management | +| `/admin/proxies` | AdminProxiesView | Proxy management | +| `/admin/redeem` | AdminRedeemView | Redeem code management | ### Special Routes -| Path | Component | Description | -|------|-----------|-------------| +| Path | Component | Description | +| ----------------- | ------------ | -------------- | | `/:pathMatch(.*)` | NotFoundView | 404 error page | ## Navigation Guards @@ -92,15 +92,16 @@ Each route can define the following meta fields: ```typescript interface RouteMeta { - requiresAuth?: boolean; // Default: true (requires authentication) - requiresAdmin?: boolean; // Default: false (admin access only) - title?: string; // Page title - breadcrumbs?: Array<{ // Breadcrumb navigation - label: string; - to?: string; - }>; - icon?: string; // Icon for navigation menu - hideInMenu?: boolean; // Hide from navigation menu + requiresAuth?: boolean // Default: true (requires authentication) + requiresAdmin?: boolean // Default: false (admin access only) + title?: string // Page title + breadcrumbs?: Array<{ + // Breadcrumb navigation + label: string + to?: string + }> + icon?: string // Icon for navigation menu + hideInMenu?: boolean // Hide from navigation menu } ``` @@ -113,6 +114,7 @@ component: () => import('@/views/user/DashboardView.vue') ``` Benefits: + - Reduced initial bundle size - Faster initial page load - Components loaded on-demand @@ -123,7 +125,7 @@ Benefits: The router integrates with the Pinia auth store (`@/stores/auth`): ```typescript -const authStore = useAuthStore(); +const authStore = useAuthStore() // Check authentication status authStore.isAuthenticated @@ -137,21 +139,21 @@ authStore.isAdmin ### Programmatic Navigation ```typescript -import { useRouter } from 'vue-router'; +import { useRouter } from 'vue-router' -const router = useRouter(); +const router = useRouter() // Navigate to a route -router.push('/dashboard'); +router.push('/dashboard') // Navigate with query parameters router.push({ path: '/usage', query: { filter: 'today' } -}); +}) // Navigate to admin route (will be blocked if not admin) -router.push('/admin/users'); +router.push('/admin/users') ``` ### Route Links @@ -165,24 +167,22 @@ router.push('/admin/users'); API Keys - - Usage - + Usage ``` ### Checking Current Route ```typescript -import { useRoute } from 'vue-router'; +import { useRoute } from 'vue-router' -const route = useRoute(); +const route = useRoute() // Check if on admin page -const isAdminPage = route.path.startsWith('/admin'); +const isAdminPage = route.path.startsWith('/admin') // Get route meta -const requiresAdmin = route.meta.requiresAdmin; +const requiresAdmin = route.meta.requiresAdmin ``` ## Scroll Behavior @@ -199,8 +199,8 @@ The router includes error handling for navigation failures: ```typescript router.onError((error) => { - console.error('Router error:', error); -}); + console.error('Router error:', error) +}) ``` ## Testing Routes @@ -229,7 +229,7 @@ Enable Vue Router debug mode: ```typescript // In browser console -window.__VUE_ROUTER__ = router; +window.__VUE_ROUTER__ = router // Check current route router.currentRoute.value @@ -238,14 +238,17 @@ router.currentRoute.value ### Common Issues **Issue**: 404 on page refresh + - **Cause**: Server not configured for SPA - **Solution**: Configure server to serve `index.html` for all routes **Issue**: Navigation guard runs twice + - **Cause**: Multiple `next()` calls - **Solution**: Ensure only one `next()` call per code path **Issue**: User data not loaded + - **Cause**: Auth store not initialized - **Solution**: Call `authStore.checkAuth()` in App.vue or main.ts diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index d99d7072..ecc18de1 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -3,8 +3,8 @@ * Defines all application routes with lazy loading and navigation guards */ -import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'; -import { useAuthStore } from '@/stores/auth'; +import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router' +import { useAuthStore } from '@/stores/auth' /** * Route definitions with lazy loading @@ -17,8 +17,8 @@ const routes: RouteRecordRaw[] = [ component: () => import('@/views/setup/SetupWizardView.vue'), meta: { requiresAuth: false, - title: 'Setup', - }, + title: 'Setup' + } }, // ==================== Public Routes ==================== @@ -28,8 +28,8 @@ const routes: RouteRecordRaw[] = [ component: () => import('@/views/HomeView.vue'), meta: { requiresAuth: false, - title: 'Home', - }, + title: 'Home' + } }, { path: '/login', @@ -37,8 +37,8 @@ const routes: RouteRecordRaw[] = [ component: () => import('@/views/auth/LoginView.vue'), meta: { requiresAuth: false, - title: 'Login', - }, + title: 'Login' + } }, { path: '/register', @@ -46,8 +46,8 @@ const routes: RouteRecordRaw[] = [ component: () => import('@/views/auth/RegisterView.vue'), meta: { requiresAuth: false, - title: 'Register', - }, + title: 'Register' + } }, { path: '/email-verify', @@ -55,14 +55,23 @@ const routes: RouteRecordRaw[] = [ component: () => import('@/views/auth/EmailVerifyView.vue'), meta: { requiresAuth: false, - title: 'Verify Email', - }, + title: 'Verify Email' + } + }, + { + path: '/auth/callback', + name: 'OAuthCallback', + component: () => import('@/views/auth/OAuthCallbackView.vue'), + meta: { + requiresAuth: false, + title: 'OAuth Callback' + } }, // ==================== User Routes ==================== { path: '/', - redirect: '/home', + redirect: '/home' }, { path: '/dashboard', @@ -73,8 +82,8 @@ const routes: RouteRecordRaw[] = [ requiresAdmin: false, title: 'Dashboard', titleKey: 'dashboard.title', - descriptionKey: 'dashboard.welcomeMessage', - }, + descriptionKey: 'dashboard.welcomeMessage' + } }, { path: '/keys', @@ -85,8 +94,8 @@ const routes: RouteRecordRaw[] = [ requiresAdmin: false, title: 'API Keys', titleKey: 'keys.title', - descriptionKey: 'keys.description', - }, + descriptionKey: 'keys.description' + } }, { path: '/usage', @@ -97,8 +106,8 @@ const routes: RouteRecordRaw[] = [ requiresAdmin: false, title: 'Usage Records', titleKey: 'usage.title', - descriptionKey: 'usage.description', - }, + descriptionKey: 'usage.description' + } }, { path: '/redeem', @@ -109,8 +118,8 @@ const routes: RouteRecordRaw[] = [ requiresAdmin: false, title: 'Redeem Code', titleKey: 'redeem.title', - descriptionKey: 'redeem.description', - }, + descriptionKey: 'redeem.description' + } }, { path: '/profile', @@ -121,8 +130,8 @@ const routes: RouteRecordRaw[] = [ requiresAdmin: false, title: 'Profile', titleKey: 'profile.title', - descriptionKey: 'profile.description', - }, + descriptionKey: 'profile.description' + } }, { path: '/subscriptions', @@ -133,14 +142,14 @@ const routes: RouteRecordRaw[] = [ requiresAdmin: false, title: 'My Subscriptions', titleKey: 'userSubscriptions.title', - descriptionKey: 'userSubscriptions.description', - }, + descriptionKey: 'userSubscriptions.description' + } }, // ==================== Admin Routes ==================== { path: '/admin', - redirect: '/admin/dashboard', + redirect: '/admin/dashboard' }, { path: '/admin/dashboard', @@ -151,8 +160,8 @@ const routes: RouteRecordRaw[] = [ requiresAdmin: true, title: 'Admin Dashboard', titleKey: 'admin.dashboard.title', - descriptionKey: 'admin.dashboard.description', - }, + descriptionKey: 'admin.dashboard.description' + } }, { path: '/admin/users', @@ -163,8 +172,8 @@ const routes: RouteRecordRaw[] = [ requiresAdmin: true, title: 'User Management', titleKey: 'admin.users.title', - descriptionKey: 'admin.users.description', - }, + descriptionKey: 'admin.users.description' + } }, { path: '/admin/groups', @@ -175,8 +184,8 @@ const routes: RouteRecordRaw[] = [ requiresAdmin: true, title: 'Group Management', titleKey: 'admin.groups.title', - descriptionKey: 'admin.groups.description', - }, + descriptionKey: 'admin.groups.description' + } }, { path: '/admin/subscriptions', @@ -187,8 +196,8 @@ const routes: RouteRecordRaw[] = [ requiresAdmin: true, title: 'Subscription Management', titleKey: 'admin.subscriptions.title', - descriptionKey: 'admin.subscriptions.description', - }, + descriptionKey: 'admin.subscriptions.description' + } }, { path: '/admin/accounts', @@ -199,8 +208,8 @@ const routes: RouteRecordRaw[] = [ requiresAdmin: true, title: 'Account Management', titleKey: 'admin.accounts.title', - descriptionKey: 'admin.accounts.description', - }, + descriptionKey: 'admin.accounts.description' + } }, { path: '/admin/proxies', @@ -211,8 +220,8 @@ const routes: RouteRecordRaw[] = [ requiresAdmin: true, title: 'Proxy Management', titleKey: 'admin.proxies.title', - descriptionKey: 'admin.proxies.description', - }, + descriptionKey: 'admin.proxies.description' + } }, { path: '/admin/redeem', @@ -223,8 +232,8 @@ const routes: RouteRecordRaw[] = [ requiresAdmin: true, title: 'Redeem Code Management', titleKey: 'admin.redeem.title', - descriptionKey: 'admin.redeem.description', - }, + descriptionKey: 'admin.redeem.description' + } }, { path: '/admin/settings', @@ -235,8 +244,8 @@ const routes: RouteRecordRaw[] = [ requiresAdmin: true, title: 'System Settings', titleKey: 'admin.settings.title', - descriptionKey: 'admin.settings.description', - }, + descriptionKey: 'admin.settings.description' + } }, { path: '/admin/usage', @@ -247,8 +256,8 @@ const routes: RouteRecordRaw[] = [ requiresAdmin: true, title: 'Usage Records', titleKey: 'admin.usage.title', - descriptionKey: 'admin.usage.description', - }, + descriptionKey: 'admin.usage.description' + } }, // ==================== 404 Not Found ==================== @@ -257,10 +266,10 @@ const routes: RouteRecordRaw[] = [ name: 'NotFound', component: () => import('@/views/NotFoundView.vue'), meta: { - title: '404 Not Found', - }, - }, -]; + title: '404 Not Found' + } + } +] /** * Create router instance @@ -271,48 +280,48 @@ const router = createRouter({ scrollBehavior(_to, _from, savedPosition) { // Scroll to saved position when using browser back/forward if (savedPosition) { - return savedPosition; + return savedPosition } // Scroll to top for new routes - return { top: 0 }; - }, -}); + return { top: 0 } + } +}) /** * Navigation guard: Authentication check */ -let authInitialized = false; +let authInitialized = false router.beforeEach((to, _from, next) => { - const authStore = useAuthStore(); + const authStore = useAuthStore() // Restore auth state from localStorage on first navigation (page refresh) if (!authInitialized) { - authStore.checkAuth(); - authInitialized = true; + authStore.checkAuth() + authInitialized = true } // Set page title if (to.meta.title) { - document.title = `${to.meta.title} - Sub2API`; + document.title = `${to.meta.title} - Sub2API` } else { - document.title = 'Sub2API'; + document.title = 'Sub2API' } // Check if route requires authentication - const requiresAuth = to.meta.requiresAuth !== false; // Default to true - const requiresAdmin = to.meta.requiresAdmin === true; + const requiresAuth = to.meta.requiresAuth !== false // Default to true + const requiresAdmin = to.meta.requiresAdmin === true // If route doesn't require auth, allow access if (!requiresAuth) { // If already authenticated and trying to access login/register, redirect to appropriate dashboard if (authStore.isAuthenticated && (to.path === '/login' || to.path === '/register')) { // Admin users go to admin dashboard, regular users go to user dashboard - next(authStore.isAdmin ? '/admin/dashboard' : '/dashboard'); - return; + next(authStore.isAdmin ? '/admin/dashboard' : '/dashboard') + return } - next(); - return; + next() + return } // Route requires authentication @@ -320,27 +329,27 @@ router.beforeEach((to, _from, next) => { // Not authenticated, redirect to login next({ path: '/login', - query: { redirect: to.fullPath }, // Save intended destination - }); - return; + query: { redirect: to.fullPath } // Save intended destination + }) + return } // Check admin requirement if (requiresAdmin && !authStore.isAdmin) { // User is authenticated but not admin, redirect to user dashboard - next('/dashboard'); - return; + next('/dashboard') + return } // All checks passed, allow navigation - next(); -}); + next() +}) /** * Navigation guard: Error handling */ router.onError((error) => { - console.error('Router error:', error); -}); + console.error('Router error:', error) +}) -export default router; +export default router diff --git a/frontend/src/router/meta.d.ts b/frontend/src/router/meta.d.ts index 0e115b06..e7546537 100644 --- a/frontend/src/router/meta.d.ts +++ b/frontend/src/router/meta.d.ts @@ -3,7 +3,7 @@ * Extends the RouteMeta interface with custom properties */ -import 'vue-router'; +import 'vue-router' declare module 'vue-router' { interface RouteMeta { @@ -11,36 +11,36 @@ declare module 'vue-router' { * Whether this route requires authentication * @default true */ - requiresAuth?: boolean; + requiresAuth?: boolean /** * Whether this route requires admin role * @default false */ - requiresAdmin?: boolean; + requiresAdmin?: boolean /** * Page title for this route */ - title?: string; + title?: string /** * Optional breadcrumb items for navigation */ breadcrumbs?: Array<{ - label: string; - to?: string; - }>; + label: string + to?: string + }> /** * Icon name for this route (for sidebar navigation) */ - icon?: string; + icon?: string /** * Whether to hide this route from navigation menu * @default false */ - hideInMenu?: boolean; + hideInMenu?: boolean } }