Files
harheimertc/components/ImageUpload.vue
2025-12-20 10:17:16 +01:00

164 lines
4.0 KiB
Vue

<template>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
{{ label }}
<span
v-if="!required"
class="text-gray-500 text-xs"
>(optional)</span>
</label>
<div
v-if="imageFilename"
class="mb-2"
>
<div class="relative inline-block">
<img
:src="`/api/personen/${imageFilename}?width=100&height=100`"
:alt="label"
class="w-24 h-24 object-cover rounded-lg border-2 border-gray-300"
>
<button
v-if="!uploading"
class="absolute -top-2 -right-2 bg-red-600 text-white rounded-full p-1 hover:bg-red-700 transition-colors"
type="button"
title="Bild entfernen"
@click="removeImage"
>
<X :size="14" />
</button>
</div>
</div>
<div class="flex items-center gap-2">
<label
:class="[
'flex-1 px-4 py-2 border-2 border-dashed rounded-lg cursor-pointer transition-colors',
uploading ? 'border-gray-300 bg-gray-50 cursor-not-allowed' :
dragOver ? 'border-primary-500 bg-primary-50' :
'border-gray-300 hover:border-primary-400 hover:bg-gray-50'
]"
>
<input
ref="fileInput"
type="file"
accept="image/jpeg,image/jpg,image/png,image/gif,image/webp"
class="hidden"
:disabled="uploading"
@change="handleFileSelect"
>
<div class="text-center">
<div
v-if="uploading"
class="flex items-center justify-center gap-2 text-gray-600"
>
<Loader2
:size="16"
class="animate-spin"
/>
<span>Wird hochgeladen...</span>
</div>
<div
v-else
class="text-sm text-gray-600"
>
<span v-if="!imageFilename">📷 Bild auswählen oder hier ablegen</span>
<span v-else>🔄 Bild ändern</span>
</div>
</div>
</label>
</div>
<p
v-if="error"
class="mt-1 text-sm text-red-600"
>
{{ error }}
</p>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { Loader2, X } from 'lucide-vue-next'
const props = defineProps({
modelValue: {
type: String,
default: null
},
label: {
type: String,
default: 'Bild'
},
required: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['update:modelValue'])
const fileInput = ref(null)
const uploading = ref(false)
const dragOver = ref(false)
const error = ref('')
const imageFilename = computed(() => props.modelValue)
async function handleFileSelect(event) {
const file = event.target.files?.[0]
if (!file) return
await uploadImage(file)
}
async function uploadImage(file) {
if (!file.type.match(/^image\/(jpeg|jpg|png|gif|webp)$/)) {
error.value = 'Nur Bilddateien sind erlaubt (JPEG, PNG, GIF, WebP)'
return
}
if (file.size > 10 * 1024 * 1024) {
error.value = 'Bild darf maximal 10MB groß sein'
return
}
uploading.value = true
error.value = ''
try {
const formData = new FormData()
formData.append('image', file)
const response = await fetch('/api/personen/upload', {
method: 'POST',
body: formData,
credentials: 'include'
})
if (!response.ok) {
const errorData = await response.json().catch(() => ({ statusMessage: 'Fehler beim Hochladen' }))
throw new Error(errorData.statusMessage || 'Fehler beim Hochladen')
}
const data = await response.json()
emit('update:modelValue', data.filename)
} catch (err) {
error.value = err.message || 'Fehler beim Hochladen des Bildes'
console.error('Upload error:', err)
} finally {
uploading.value = false
if (fileInput.value) {
fileInput.value.value = ''
}
}
}
function removeImage() {
emit('update:modelValue', null)
error.value = ''
}
</script>