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>
This commit is contained in:
131
apps/app/src/components/common/ImageUploadField.vue
Normal file
131
apps/app/src/components/common/ImageUploadField.vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user