---
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
<template>
  <!-- HTML template -->
</template>

<style scoped>
/* Component-specific styles */
</style>

<script>
// Component logic
</script>
```

**Order matters**: Template, Style, Script (as seen throughout the codebase)

### Options API Pattern (Primary)
Use Options API for consistency with existing codebase:

```vue
<script>
export default {
  name: "ComponentName",
  props: ["id"],
  data() {
    return {
      items: [],
      isLoading: false
    }
  },
  async created() {
    // Initialization logic
  },
  methods: {
    async methodName() {
      // Use const/let, never var
      const response = await API.get('/api/endpoint')
      this.items = response.data
    }
  }
}
</script>
```

## 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
<template>
  <h2>{{ $t('char') }}: {{ character.name }}</h2>
  <button>{{ $t('export.exportPDF') }}</button>
</template>
```

### 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
<template>
  <!-- Trigger -->
  <button @click="showDialog = true">Open</button>

  <!-- Modal -->
  <div v-if="showDialog" class="modal-overlay" @click.self="showDialog = false">
    <div class="modal-content">
      <div class="modal-header">
        <h3>{{ $t('modal.title') }}</h3>
        <button @click="showDialog = false" class="close-button">&times;</button>
      </div>
      <div class="modal-body">
        <!-- Content -->
      </div>
      <div class="modal-footer">
        <button @click="showDialog = false" class="btn-cancel">{{ $t('cancel') }}</button>
        <button @click="handleSubmit" class="btn-primary">{{ $t('submit') }}</button>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      showDialog: false
    }
  }
}
</script>
```

**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
<script>
export default {
  props: {
    character: Object,
    id: [String, Number]
  }
}
</script>
```

### Events (Child → Parent)
```vue
<template>
  <button @click="notifyParent">Update</button>
</template>

<script>
export default {
  methods: {
    notifyParent() {
      this.$emit('character-updated', this.character)
    }
  }
}
</script>

<!-- Parent component -->
<template>
  <ChildComponent @character-updated="refreshCharacter" />
</template>
```

## 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
<template>
  <button @click="submit" :disabled="isLoading">
    <span v-if="!isLoading">{{ $t('submit') }}</span>
    <span v-else>{{ $t('loading') }}</span>
  </button>
</template>

<script>
export default {
  data() {
    return { isLoading: false }
  },
  methods: {
    async submit() {
      this.isLoading = true
      try {
        await API.post('/api/endpoint', this.data)
      } finally {
        this.isLoading = false
      }
    }
  }
}
</script>
```

### Disabling Form Elements During Loading
```vue
<select v-model="selected" :disabled="isLoading">
<input type="checkbox" v-model="option" :disabled="isLoading">
```

## Form Validation

### Validation Pattern
```js
methods: {
  validateForm() {
    if (!this.selectedTemplate) {
      alert(this.$t('export.pleaseSelectTemplate'))
      return false
    }
    return true
  },
  async submit() {
    if (!this.validateForm()) return
    
    this.isLoading = true
    try {
      await API.post('/api/endpoint', this.data)
    } finally {
      this.isLoading = false
    }
  }
}
```

## Browser Compatibility

### Popup Blocker Workaround
Open windows **synchronously** before async operations:

```js
async exportToPDF() {
  // Open window FIRST (synchronously)
  const pdfWindow = window.open('', '_blank')
  if (!pdfWindow) {
    alert(this.$t('export.popupBlocked'))
    return
  }
  
  // Show loading page
  pdfWindow.document.write('<html>...</html>')
  
  // Then do async work
  const response = await API.get('/api/pdf/export')
  
  // Update window with result
  pdfWindow.location.href = url
}
```

**Critical**: `window.open()` must be called synchronously in the click handler, not after `await`.

## Common Patterns

### Dynamic Component Loading
```vue
<template>
  <component :is="currentView" :character="character" @character-updated="refresh"/>
</template>

<script>
import ViewA from './ViewA.vue'
import ViewB from './ViewB.vue'

export default {
  components: { ViewA, ViewB },
  data() {
    return { currentView: 'ViewA' }
  }
}
</script>
```

### Conditional Rendering
- Use `v-if` for elements that toggle rarely
- Use `v-show` for frequent toggles
- Use `v-for` with `:key` attribute always

### Event Modifiers
- `@click.self` - only trigger if clicked element itself
- `@submit.prevent` - prevent form submission
- `@keyup.enter` - keyboard event handling

## Best Practices

1. **Use global CSS definition** to ensure consistent styling
2. **Always use scoped styles** to avoid CSS conflicts
3. **Name components with PascalCase** (e.g., `CharacterDetails.vue`)
4. **Use `const` by default, `let` when needed** - never use `var`
5. **Use optional chaining `?.` and nullish coalescing `??`** for safe property access
6. **Handle errors gracefully** with user-friendly messages
7. **Keep template logic simple** - move complex logic to methods
8. **Clean up resources** in `beforeUnmount` if needed
9. **Test with actual API** running in Docker container
10. **Check HMR reload** - view logs: `docker logs bamort-frontend-dev`

## Anti-Patterns to Avoid

❌ Don't use `v-if` and `v-for` on the same element
❌ Don't mutate props directly
❌ Don't use `var` - use `const` or `let`
❌ Don't use `==` - always use `===` for comparisons
❌ Don't forget to handle loading and error states
❌ Don't use inline styles - use scoped CSS
❌ Don't call API methods in template expressions
❌ Don't forget translations - add to both DE and EN
