| # Layout Components Integration Guide |
|
|
| ## Quick Start |
|
|
| ### 1. Import Layout Components |
|
|
| ```typescript |
| // In your view files |
| import { AppLayout, AuthLayout } from '@/components/layout' |
| ``` |
|
|
| ### 2. Use in Routes |
|
|
| ```typescript |
| // src/router/index.ts |
| import { createRouter, createWebHistory } from 'vue-router' |
| import type { RouteRecordRaw } from 'vue-router' |
| |
| // Views |
| import DashboardView from '@/views/DashboardView.vue' |
| import LoginView from '@/views/auth/LoginView.vue' |
| import RegisterView from '@/views/auth/RegisterView.vue' |
| |
| const routes: RouteRecordRaw[] = [ |
| // Auth routes (no layout needed - views use AuthLayout internally) |
| { |
| path: '/login', |
| name: 'Login', |
| component: LoginView, |
| meta: { requiresAuth: false } |
| }, |
| { |
| path: '/register', |
| name: 'Register', |
| component: RegisterView, |
| meta: { requiresAuth: false } |
| }, |
| |
| // User routes (use AppLayout) |
| { |
| path: '/dashboard', |
| name: 'Dashboard', |
| component: DashboardView, |
| meta: { requiresAuth: true, title: 'Dashboard' } |
| }, |
| { |
| path: '/api-keys', |
| name: 'ApiKeys', |
| component: () => import('@/views/ApiKeysView.vue'), |
| meta: { requiresAuth: true, title: 'API Keys' } |
| }, |
| { |
| path: '/usage', |
| name: 'Usage', |
| component: () => import('@/views/UsageView.vue'), |
| meta: { requiresAuth: true, title: 'Usage Statistics' } |
| }, |
| { |
| path: '/redeem', |
| name: 'Redeem', |
| component: () => import('@/views/RedeemView.vue'), |
| meta: { requiresAuth: true, title: 'Redeem Code' } |
| }, |
| { |
| path: '/profile', |
| name: 'Profile', |
| component: () => import('@/views/ProfileView.vue'), |
| meta: { requiresAuth: true, title: 'Profile Settings' } |
| }, |
| |
| // Admin routes (use AppLayout, admin only) |
| { |
| path: '/admin/dashboard', |
| name: 'AdminDashboard', |
| component: () => import('@/views/admin/DashboardView.vue'), |
| meta: { requiresAuth: true, requiresAdmin: true, title: 'Admin Dashboard' } |
| }, |
| { |
| path: '/admin/users', |
| name: 'AdminUsers', |
| component: () => import('@/views/admin/UsersView.vue'), |
| meta: { requiresAuth: true, requiresAdmin: true, title: 'User Management' } |
| }, |
| { |
| path: '/admin/groups', |
| name: 'AdminGroups', |
| component: () => import('@/views/admin/GroupsView.vue'), |
| meta: { requiresAuth: true, requiresAdmin: true, title: 'Groups' } |
| }, |
| { |
| path: '/admin/accounts', |
| name: 'AdminAccounts', |
| component: () => import('@/views/admin/AccountsView.vue'), |
| meta: { requiresAuth: true, requiresAdmin: true, title: 'Accounts' } |
| }, |
| { |
| path: '/admin/proxies', |
| name: 'AdminProxies', |
| component: () => import('@/views/admin/ProxiesView.vue'), |
| meta: { requiresAuth: true, requiresAdmin: true, title: 'Proxies' } |
| }, |
| { |
| path: '/admin/redeem-codes', |
| name: 'AdminRedeemCodes', |
| component: () => import('@/views/admin/RedeemCodesView.vue'), |
| meta: { requiresAuth: true, requiresAdmin: true, title: 'Redeem Codes' } |
| }, |
| |
| // Default redirect |
| { |
| path: '/', |
| redirect: '/dashboard' |
| } |
| ] |
| |
| const router = createRouter({ |
| history: createWebHistory(), |
| routes |
| }) |
| |
| // Navigation guards |
| router.beforeEach((to, from, next) => { |
| const authStore = useAuthStore() |
| |
| if (to.meta.requiresAuth && !authStore.isAuthenticated) { |
| // Redirect to login if not authenticated |
| next('/login') |
| } else if (to.meta.requiresAdmin && !authStore.isAdmin) { |
| // Redirect to dashboard if not admin |
| next('/dashboard') |
| } else { |
| next() |
| } |
| }) |
| |
| export default router |
| ``` |
|
|
| ### 3. Initialize Stores in main.ts |
|
|
| ```typescript |
| // src/main.ts |
| import { createApp } from 'vue' |
| import { createPinia } from 'pinia' |
| import App from './App.vue' |
| import router from './router' |
| import './style.css' |
| |
| const app = createApp(App) |
| const pinia = createPinia() |
| |
| app.use(pinia) |
| app.use(router) |
| |
| // Initialize auth state on app startup |
| import { useAuthStore } from '@/stores' |
| const authStore = useAuthStore() |
| authStore.checkAuth() |
| |
| app.mount('#app') |
| ``` |
|
|
| ### 4. Update App.vue |
|
|
| ```vue |
| <!-- src/App.vue --> |
| <template> |
| <router-view /> |
| </template> |
| |
| <script setup lang="ts"> |
| // App.vue just renders the router view |
| // Layouts are handled by individual views |
| </script> |
| ``` |
|
|
| --- |
|
|
| ## View Component Templates |
|
|
| ### Authenticated Page Template |
|
|
| ```vue |
| <!-- src/views/DashboardView.vue --> |
| <template> |
| <AppLayout> |
| <div class="space-y-6"> |
| <h1 class="text-3xl font-bold text-gray-900">Dashboard</h1> |
| |
| <!-- Your content here --> |
| </div> |
| </AppLayout> |
| </template> |
| |
| <script setup lang="ts"> |
| import { AppLayout } from '@/components/layout' |
| |
| // Your component logic here |
| </script> |
| ``` |
|
|
| ### Auth Page Template |
|
|
| ```vue |
| <!-- src/views/auth/LoginView.vue --> |
| <template> |
| <AuthLayout> |
| <h2 class="mb-6 text-2xl font-bold text-gray-900">Login</h2> |
| |
| <!-- Your login form here --> |
| |
| <template #footer> |
| <p class="text-gray-600"> |
| Don't have an account? |
| <router-link to="/register" class="text-indigo-600 hover:underline"> Sign up </router-link> |
| </p> |
| </template> |
| </AuthLayout> |
| </template> |
| |
| <script setup lang="ts"> |
| import { AuthLayout } from '@/components/layout' |
| |
| // Your login logic here |
| </script> |
| ``` |
|
|
| --- |
|
|
| ## Customization |
|
|
| ### Changing Colors |
|
|
| The components use Tailwind's indigo color scheme by default. To change: |
|
|
| ```vue |
| <!-- Change all instances of indigo-* to your preferred color --> |
| <div class="bg-blue-600"> <!-- Instead of bg-indigo-600 --> |
| <div class="text-blue-600"> <!-- Instead of text-indigo-600 --> |
| ``` |
|
|
| ### Adding Custom Icons |
|
|
| Replace HTML entity icons with your preferred icon library: |
|
|
| ```vue |
| <!-- Before (HTML entities) --> |
| <span class="text-lg">📈</span> |
| |
| <!-- After (Heroicons example) --> |
| <ChartBarIcon class="h-5 w-5" /> |
| ``` |
|
|
| ### Sidebar Customization |
|
|
| Modify navigation items in `AppSidebar.vue`: |
|
|
| ```typescript |
| // Add/remove/modify navigation items |
| const userNavItems = [ |
| { path: '/dashboard', label: 'Dashboard', icon: '📈' }, |
| { path: '/new-page', label: 'New Page', icon: '📄' } // Add new item |
| // ... |
| ] |
| ``` |
|
|
| ### Header Customization |
|
|
| Modify user dropdown in `AppHeader.vue`: |
|
|
| ```vue |
| <!-- Add new dropdown items --> |
| <router-link |
| to="/settings" |
| @click="closeDropdown" |
| class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" |
| > |
| <span class="mr-2">⚙</span> |
| Settings |
| </router-link> |
| ``` |
|
|
| --- |
|
|
| ## Mobile Responsive Behavior |
|
|
| ### Sidebar |
|
|
| - **Desktop (md+)**: Always visible, can be collapsed to icon-only view |
| - **Mobile**: Hidden by default, shown via menu toggle in header |
|
|
| ### Header |
|
|
| - **Desktop**: Shows full user info and balance |
| - **Mobile**: Shows compact view with hamburger menu |
|
|
| To improve mobile experience, you can add overlay and transitions: |
|
|
| ```vue |
| <!-- AppSidebar.vue enhancement for mobile --> |
| <aside |
| class="fixed left-0 top-0 z-40 h-screen transition-transform duration-300" |
| :class="[ |
| sidebarCollapsed ? 'w-16' : 'w-64', |
| // Hide on mobile when collapsed |
| 'md:translate-x-0', |
| sidebarCollapsed ? '-translate-x-full md:translate-x-0' : 'translate-x-0' |
| ]" |
| > |
| <!-- ... --> |
| </aside> |
| |
| <!-- Add overlay for mobile --> |
| <div |
| v-if="!sidebarCollapsed" |
| @click="toggleSidebar" |
| class="fixed inset-0 z-30 bg-black bg-opacity-50 md:hidden" |
| ></div> |
| ``` |
|
|
| --- |
|
|
| ## State Management Integration |
|
|
| ### Auth Store Usage |
|
|
| ```typescript |
| import { useAuthStore } from '@/stores' |
| |
| const authStore = useAuthStore() |
| |
| // Check if user is authenticated |
| if (authStore.isAuthenticated) { |
| // User is logged in |
| } |
| |
| // Check if user is admin |
| if (authStore.isAdmin) { |
| // User has admin role |
| } |
| |
| // Get current user |
| const user = authStore.user |
| ``` |
|
|
| ### App Store Usage |
|
|
| ```typescript |
| import { useAppStore } from '@/stores' |
| |
| const appStore = useAppStore() |
| |
| // Toggle sidebar |
| appStore.toggleSidebar() |
| |
| // Show notifications |
| appStore.showSuccess('Operation completed!') |
| appStore.showError('Something went wrong') |
| appStore.showInfo('Did you know...') |
| appStore.showWarning('Be careful!') |
| |
| // Loading state |
| appStore.setLoading(true) |
| // ... perform operation |
| appStore.setLoading(false) |
| |
| // Or use helper |
| await appStore.withLoading(async () => { |
| // Your async operation |
| }) |
| ``` |
|
|
| --- |
|
|
| ## Accessibility Features |
|
|
| All layout components include: |
|
|
| - **Semantic HTML**: Proper use of `<nav>`, `<header>`, `<main>`, `<aside>` |
| - **ARIA labels**: Buttons have descriptive labels |
| - **Keyboard navigation**: All interactive elements are keyboard accessible |
| - **Focus management**: Proper focus states with Tailwind's `focus:` utilities |
| - **Color contrast**: WCAG AA compliant color combinations |
|
|
| To enhance further: |
|
|
| ```vue |
| <!-- Add skip to main content link --> |
| <a |
| href="#main-content" |
| class="sr-only rounded bg-white px-4 py-2 focus:not-sr-only focus:absolute focus:left-4 focus:top-4" |
| > |
| Skip to main content |
| </a> |
| |
| <main id="main-content"> |
| <!-- Content --> |
| </main> |
| ``` |
|
|
| --- |
|
|
| ## Testing |
|
|
| ### Unit Testing Layout Components |
|
|
| ```typescript |
| // AppHeader.test.ts |
| import { describe, it, expect, beforeEach } from 'vitest' |
| import { mount } from '@vue/test-utils' |
| import { createPinia, setActivePinia } from 'pinia' |
| import AppHeader from '@/components/layout/AppHeader.vue' |
| |
| describe('AppHeader', () => { |
| beforeEach(() => { |
| setActivePinia(createPinia()) |
| }) |
| |
| it('renders user info when authenticated', () => { |
| const wrapper = mount(AppHeader) |
| // Add assertions |
| }) |
| |
| it('shows dropdown when clicked', async () => { |
| const wrapper = mount(AppHeader) |
| await wrapper.find('button').trigger('click') |
| expect(wrapper.find('.dropdown').exists()).toBe(true) |
| }) |
| }) |
| ``` |
|
|
| --- |
|
|
| ## Performance Optimization |
|
|
| ### Lazy Loading |
|
|
| Views using layouts are already lazy loaded in the router example above. |
|
|
| ### Code Splitting |
|
|
| Layout components are automatically code-split when imported: |
|
|
| ```typescript |
| // This creates a separate chunk for layout components |
| import { AppLayout } from '@/components/layout' |
| ``` |
|
|
| ### Reducing Re-renders |
|
|
| Layout components use `computed` refs to prevent unnecessary re-renders: |
|
|
| ```typescript |
| const sidebarCollapsed = computed(() => appStore.sidebarCollapsed) |
| // This only re-renders when sidebarCollapsed changes |
| ``` |
|
|
| --- |
|
|
| ## Troubleshooting |
|
|
| ### Sidebar not showing |
|
|
| - Check if `useAppStore` is properly initialized |
| - Verify Tailwind classes are being processed |
| - Check z-index conflicts with other components |
|
|
| ### Routes not highlighting in sidebar |
|
|
| - Ensure route paths match exactly |
| - Check `isActive()` function logic |
| - Verify `useRoute()` is working correctly |
|
|
| ### User info not displaying |
|
|
| - Ensure auth store is initialized with `checkAuth()` |
| - Verify user is logged in |
| - Check localStorage for auth data |
|
|
| ### Mobile menu not working |
|
|
| - Verify `toggleSidebar()` is called correctly |
| - Check responsive breakpoints (md:) |
| - Test on actual mobile device or browser dev tools |
|
|