Files
crewli/apps/app/src/components/common/ImageUploadField.vue
bert.hausmans 6a8d21a5b6 feat: registration field polish, multi-category tags, file uploads, Partner icon
- Restructure field editor dialog: move Options section to bottom with
  divider and subheader, fix delete button with flex layout
- Change tag_category (single string) to tag_categories (JSON array)
  supporting multiple category selection in tag picker fields
- Portal tag picker now groups tags by category with subheaders
- Add generic file upload endpoint (FileUploadService + UploadController)
- Replace email branding logo URL text field with ImageUploadField
- Update Partner crowd type default icon to tabler-affiliate
- Apply changes consistently to both field and template dialogs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 18:03:49 +02:00

132 lines
2.7 KiB
Vue

<script setup lang="ts">
import { apiClient } from '@/lib/axios'
interface Props {
modelValue: string | null
label?: string
purpose: 'logo' | 'banner' | 'icon' | 'avatar'
hint?: string
previewHeight?: number
}
const props = withDefaults(defineProps<Props>(), {
label: undefined,
hint: undefined,
previewHeight: 120,
})
const emit = defineEmits<{
'update:modelValue': [url: string | null]
}>()
const fileInput = ref<HTMLInputElement | null>(null)
const uploading = ref(false)
const error = ref('')
function triggerFileInput() {
fileInput.value?.click()
}
async function onFileSelected(event: Event) {
const input = event.target as HTMLInputElement
const file = input.files?.[0]
if (!file) return
uploading.value = true
error.value = ''
try {
const formData = new FormData()
formData.append('file', file)
formData.append('purpose', props.purpose)
const { data } = await apiClient.post<{ data: { url: string } }>('/upload/image', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
})
emit('update:modelValue', data.data.url)
}
catch (e: unknown) {
const axiosError = e as { response?: { data?: { message?: string } } }
error.value = axiosError.response?.data?.message ?? 'Upload mislukt'
}
finally {
uploading.value = false
if (input) input.value = ''
}
}
function remove() {
emit('update:modelValue', null)
error.value = ''
}
</script>
<template>
<div>
<label
v-if="label"
class="text-body-2 text-medium-emphasis d-block mb-1"
>
{{ label }}
</label>
<div
v-if="modelValue"
class="mb-2"
>
<VImg
:src="modelValue"
:max-height="previewHeight"
max-width="300"
class="rounded border"
cover
/>
</div>
<div class="d-flex align-center gap-2">
<VBtn
:variant="modelValue ? 'tonal' : 'elevated'"
color="primary"
size="small"
prepend-icon="tabler-upload"
:loading="uploading"
@click="triggerFileInput"
>
{{ modelValue ? 'Vervangen' : 'Uploaden' }}
</VBtn>
<VBtn
v-if="modelValue"
variant="text"
color="error"
size="small"
prepend-icon="tabler-trash"
@click="remove"
>
Verwijderen
</VBtn>
</div>
<input
ref="fileInput"
type="file"
accept="image/*"
style="display: none;"
@change="onFileSelected"
>
<div
v-if="error"
class="text-caption text-error mt-1"
>
{{ error }}
</div>
<div
v-else-if="hint"
class="text-caption text-medium-emphasis mt-1"
>
{{ hint }}
</div>
</div>
</template>