---
description: Vue 3, TypeScript, and Vuexy patterns and conventions
globs: ["apps/**/*.{vue,ts,tsx}"]
alwaysApply: true
---
# Vue + Vuexy Rules
## Core Principles
1. **Composition API only** - Use `
Events
Manage your gigs and performances
Create Event
Loading events...
| Title |
Date |
Location |
Status |
Actions |
|
{{ event.title }}
|
{{ event.event_date }} |
{{ event.location?.name ?? '-' }} |
{{ event.status_label }}
|
|
Delete Event?
This action cannot be undone.
Cancel
Delete
```
### Router Configuration
```typescript
// src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
component: () => import('@/layouts/default.vue'),
children: [
{
path: '',
name: 'dashboard',
component: () => import('@/pages/dashboard.vue'),
meta: { requiresAuth: true },
},
{
path: 'events',
name: 'events',
component: () => import('@/pages/events/index.vue'),
meta: { requiresAuth: true },
},
{
path: 'events/create',
name: 'events-create',
component: () => import('@/pages/events/create.vue'),
meta: { requiresAuth: true },
},
{
path: 'events/:id',
name: 'events-show',
component: () => import('@/pages/events/[id].vue'),
meta: { requiresAuth: true },
},
{
path: 'events/:id/edit',
name: 'events-edit',
component: () => import('@/pages/events/[id]/edit.vue'),
meta: { requiresAuth: true },
},
// Add more routes...
],
},
{
path: '/login',
name: 'login',
component: () => import('@/pages/login.vue'),
meta: { layout: 'blank', requiresAuth: false },
},
{
path: '/:pathMatch(.*)*',
name: 'not-found',
component: () => import('@/pages/[...error].vue'),
},
],
})
// Navigation guard
router.beforeEach(async (to, from, next) => {
const authStore = useAuthStore()
// Check if route requires auth
if (to.meta.requiresAuth && !authStore.isAuthenticated) {
// Try to fetch user if we have a token
if (authStore.token) {
const success = await authStore.fetchUser()
if (success) {
return next()
}
}
return next({ name: 'login', query: { redirect: to.fullPath } })
}
// Redirect to dashboard if logged in and going to login
if (to.name === 'login' && authStore.isAuthenticated) {
return next({ name: 'dashboard' })
}
next()
})
export default router
```
### Navigation Menu
```typescript
// src/navigation/vertical/index.ts
import type { VerticalNavItems } from '@/@layouts/types'
export default [
{
title: 'Dashboard',
to: { name: 'dashboard' },
icon: { icon: 'tabler-smart-home' },
},
{
heading: 'Management',
},
{
title: 'Events',
to: { name: 'events' },
icon: { icon: 'tabler-calendar-event' },
},
{
title: 'Members',
to: { name: 'members' },
icon: { icon: 'tabler-users' },
},
{
title: 'Music',
to: { name: 'music' },
icon: { icon: 'tabler-music' },
},
{
title: 'Setlists',
to: { name: 'setlists' },
icon: { icon: 'tabler-playlist' },
},
{
heading: 'CRM',
},
{
title: 'Locations',
to: { name: 'locations' },
icon: { icon: 'tabler-map-pin' },
},
{
title: 'Customers',
to: { name: 'customers' },
icon: { icon: 'tabler-building' },
},
] as VerticalNavItems
```
## Best Practices
### Always Use
- `
```
## Event Handlers
Name handlers with `handle` prefix:
```vue
```
## File Organization
```
src/
├── @core/ # Vuexy core (DON'T MODIFY)
├── @layouts/ # Vuexy layouts (DON'T MODIFY)
├── assets/ # Static assets
│ ├── images/
│ └── styles/
├── components/ # Shared components
│ ├── ui/ # Base UI components
│ └── features/ # Feature-specific components
├── composables/ # Custom composables
│ ├── useEvents.ts
│ ├── useMembers.ts
│ └── useAuth.ts
├── layouts/ # App layouts
├── lib/ # Utilities
│ ├── api-client.ts
│ ├── utils.ts
│ └── query-client.ts
├── navigation/ # Menu configuration
├── pages/ # Route pages
│ ├── dashboard.vue
│ ├── events/
│ │ ├── index.vue
│ │ ├── create.vue
│ │ └── [id]/
│ │ ├── index.vue
│ │ └── edit.vue
│ └── login.vue
├── plugins/ # Vue plugins
├── router/ # Vue Router
├── stores/ # Pinia stores
├── types/ # TypeScript types
│ └── index.ts
└── App.vue
```
## Performance
### Lazy Load Routes
```typescript
// ✅ Good - lazy load all route components
const routes = [
{
path: '/events',
component: () => import('@/pages/events/index.vue'),
},
]
```
### Use Computed for Derived State
```typescript
// ✅ Good - computed is cached
const upcomingEvents = computed(() =>
events.value.filter(e => new Date(e.event_date) > new Date())
)
// ❌ Avoid - recalculates on every render
const upcomingEvents = events.value.filter(e => new Date(e.event_date) > new Date())
```
### Prefetch on Hover
```typescript
// Prefetch event details when user hovers
function handleEventHover(id: string) {
queryClient.prefetchQuery({
queryKey: ['events', id],
queryFn: () => fetchEvent(id),
})
}
```
### Use v-once for Static Content
```vue
Welcome to Band Management
Manage your band operations efficiently.
```
## Error Handling
```vue
{{ error?.message ?? 'Something went wrong' }}
Retry
```
## API Integration Pattern
Create typed API functions:
```typescript
// lib/api/events.ts
import { apiClient } from '@/lib/api-client'
import type { Event, CreateEventData, ApiResponse } from '@/types'
export const eventsApi = {
list: async (params?: { page?: number; status?: string }) => {
const { data } = await apiClient.get>('/events', { params })
return data
},
get: async (id: string) => {
const { data } = await apiClient.get>(`/events/${id}`)
return data.data
},
create: async (eventData: CreateEventData) => {
const { data } = await apiClient.post>('/events', eventData)
return data.data
},
update: async (id: string, eventData: Partial) => {
const { data } = await apiClient.put>(`/events/${id}`, eventData)
return data.data
},
delete: async (id: string) => {
await apiClient.delete(`/events/${id}`)
},
}
```