---
description: 'Instructions for writing Vue 3 components following project conventions and best practices'
applyTo: '**/*.vue, **/*.ts, **/*.js, **/*.scss'
---
# Vue 3 Development Instructions
Follow Vue 3 best practices and project-specific conventions when writing components.
## Component Structure
### Standard Component Layout
```vue
```
**Order matters**: Template, Style, Script (as seen throughout the codebase)
### Options API Pattern (Primary)
Use Options API for consistency with existing codebase:
```vue
```
## API Communication
### Using the API Utility
Always use `API` from `utils/api.js` - it handles authentication automatically:
```js
import API from '../utils/api'
// In methods:
const response = await API.get(`/api/characters/${this.id}`)
const data = await API.post('/api/characters', character)
```
**Never** manually add Authorization headers - the interceptor handles this.
### Environment Variables
API base URL from Vite:
```js
import API from '../utils/api'
// API uses: import.meta.env.VITE_API_URL || 'http://localhost:8180'
```
### API Configuration (`utils/api.js`)
Standard Axios instance with interceptors:
```js
import axios from 'axios'
const API = axios.create({
baseURL: import.meta.env.VITE_API_URL || 'http://localhost:8180'
})
// Request interceptor - adds auth token
API.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
(error) => Promise.reject(error)
)
// Response interceptor - handles 401
API.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
localStorage.removeItem('token')
}
return Promise.reject(error)
}
)
export default API
```
### Error Handling Pattern
```js
try {
const response = await API.get(`/api/endpoint`)
this.data = response.data
} catch (error) {
console.error('Failed to load data:', error)
const errorMsg = error.response?.data?.error ?? error.message
alert(`${this.$t('errors.loadFailed')}: ${errorMsg}`)
}
```
## Internationalization (i18n)
### Using Translations
```vue
{{ $t('char') }}: {{ character.name }}
```
### Adding New Translations
**ALWAYS** add to both `src/locales/de` and `src/locales/en`:
```js
// src/locales/de
export default {
export: {
selectTemplate: 'Vorlage wählen',
exportPDF: 'PDF Export'
}
}
// src/locales/en
export default {
export: {
selectTemplate: 'Select Template',
exportPDF: 'Export PDF'
}
}
```
**Note**: Locale files use `.js` extension and export objects, not JSON.
## Modal Dialog Pattern
### Standard Modal Structure
```vue
{{ $t('modal.title') }}
```
**Key conventions:**
- Use `@click.self` on overlay to close on outside click
- Include close button (×) in header
- Separate header, body, footer sections
## Component Communication
### Props (Parent → Child)
```vue
```
### Events (Child → Parent)
```vue
```
## State Management
### Pinia Store Pattern (`stores/`)
For global state (language, auth, etc.):
```js
import { defineStore } from 'pinia'
export const useLanguageStore = defineStore('language', {
state: () => ({
currentLanguage: localStorage.getItem('language') || 'de'
}),
actions: {
setLanguage(lang) {
this.currentLanguage = lang
localStorage.setItem('language', lang)
}
}
})
```
### Loading States
Always show feedback for async operations:
```vue
```
### Disabling Form Elements During Loading
```vue