Files
bamort/.github/instructions/vue.instructions.md
T
Bardioc26 8c7aa86181 cleanup unused or orphaned files (#9)
* changed vue.instructions to be used for  *.vue, *.ts, *.js, *.scss
* remove unneeded files or files that may conflict with some peoples' protection meanings
* ToDos as the popped up during cleaning
2026-01-01 14:01:15 +01:00

399 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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