fix: compact options layout, consistent ImageUploadField across app
- Replace card-based multi-line options with compact single-line rows (grip + label + description + delete all on one row) - Standardize event registration appearance page on ImageUploadField (was VFileInput + manual preview, now consistent with email branding) - Fix EmailBrandingTab logoUrl ref to properly handle null from ImageUploadField, ensuring existing image preview works on page load Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -323,46 +323,39 @@ defineExpose({ setErrors })
|
||||
<div
|
||||
v-for="(option, index) in form.options"
|
||||
:key="index"
|
||||
class="mb-3"
|
||||
class="mb-2"
|
||||
>
|
||||
<VCard
|
||||
variant="outlined"
|
||||
class="pa-3"
|
||||
>
|
||||
<div class="d-flex align-start gap-2">
|
||||
<VIcon
|
||||
icon="tabler-grip-vertical"
|
||||
class="mt-2 text-medium-emphasis"
|
||||
style="cursor: grab;"
|
||||
/>
|
||||
<div class="flex-grow-1">
|
||||
<AppTextField
|
||||
v-model="option.label"
|
||||
:placeholder="`Optie ${index + 1}`"
|
||||
density="compact"
|
||||
hide-details="auto"
|
||||
class="mb-2"
|
||||
/>
|
||||
<AppTextField
|
||||
v-model="option.description"
|
||||
label="Beschrijving (optioneel)"
|
||||
density="compact"
|
||||
placeholder="Korte toelichting die onder de optie verschijnt"
|
||||
counter="200"
|
||||
maxlength="200"
|
||||
hide-details="auto"
|
||||
/>
|
||||
</div>
|
||||
<VBtn
|
||||
icon="tabler-trash"
|
||||
variant="text"
|
||||
color="error"
|
||||
size="small"
|
||||
class="mt-1"
|
||||
@click="removeOption(index)"
|
||||
/>
|
||||
</div>
|
||||
</VCard>
|
||||
<div class="d-flex align-center gap-2">
|
||||
<VIcon
|
||||
icon="tabler-grip-vertical"
|
||||
size="16"
|
||||
class="text-medium-emphasis flex-shrink-0"
|
||||
style="cursor: grab;"
|
||||
/>
|
||||
<AppTextField
|
||||
v-model="option.label"
|
||||
placeholder="Optie"
|
||||
density="compact"
|
||||
hide-details
|
||||
class="flex-grow-1"
|
||||
style="max-inline-size: 200px;"
|
||||
/>
|
||||
<AppTextField
|
||||
v-model="option.description"
|
||||
placeholder="Beschrijving (optioneel)"
|
||||
density="compact"
|
||||
hide-details
|
||||
class="flex-grow-1"
|
||||
/>
|
||||
<VBtn
|
||||
icon="tabler-trash"
|
||||
variant="text"
|
||||
color="error"
|
||||
size="x-small"
|
||||
class="flex-shrink-0"
|
||||
@click="removeOption(index)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
||||
@@ -19,7 +19,7 @@ const form = ref<InstanceType<typeof VForm>>()
|
||||
const snackbar = ref(false)
|
||||
const serverErrors = ref<Record<string, string[]>>({})
|
||||
|
||||
const logoUrl = ref('')
|
||||
const logoUrl = ref<string | null>(null)
|
||||
const primaryColor = ref('#6366F1')
|
||||
const secondaryColor = ref('#4F46E5')
|
||||
const footerText = ref('')
|
||||
@@ -31,7 +31,7 @@ const showSecondaryPicker = ref(false)
|
||||
|
||||
watch(settings, s => {
|
||||
if (s) {
|
||||
logoUrl.value = s.logo_url ?? ''
|
||||
logoUrl.value = s.logo_url ?? null
|
||||
primaryColor.value = s.primary_color ?? '#6366F1'
|
||||
secondaryColor.value = s.secondary_color ?? '#4F46E5'
|
||||
footerText.value = s.footer_text ?? ''
|
||||
|
||||
@@ -562,46 +562,39 @@ function activate(template: RegistrationFieldTemplate) {
|
||||
<div
|
||||
v-for="(option, index) in form.options"
|
||||
:key="index"
|
||||
class="mb-3"
|
||||
class="mb-2"
|
||||
>
|
||||
<VCard
|
||||
variant="outlined"
|
||||
class="pa-3"
|
||||
>
|
||||
<div class="d-flex align-start gap-2">
|
||||
<VIcon
|
||||
icon="tabler-grip-vertical"
|
||||
class="mt-2 text-medium-emphasis"
|
||||
style="cursor: grab;"
|
||||
/>
|
||||
<div class="flex-grow-1">
|
||||
<AppTextField
|
||||
v-model="option.label"
|
||||
:placeholder="`Optie ${index + 1}`"
|
||||
density="compact"
|
||||
hide-details="auto"
|
||||
class="mb-2"
|
||||
/>
|
||||
<AppTextField
|
||||
v-model="option.description"
|
||||
label="Beschrijving (optioneel)"
|
||||
density="compact"
|
||||
placeholder="Korte toelichting die onder de optie verschijnt"
|
||||
counter="200"
|
||||
maxlength="200"
|
||||
hide-details="auto"
|
||||
/>
|
||||
</div>
|
||||
<VBtn
|
||||
icon="tabler-trash"
|
||||
variant="text"
|
||||
color="error"
|
||||
size="small"
|
||||
class="mt-1"
|
||||
@click="removeOption(index)"
|
||||
/>
|
||||
</div>
|
||||
</VCard>
|
||||
<div class="d-flex align-center gap-2">
|
||||
<VIcon
|
||||
icon="tabler-grip-vertical"
|
||||
size="16"
|
||||
class="text-medium-emphasis flex-shrink-0"
|
||||
style="cursor: grab;"
|
||||
/>
|
||||
<AppTextField
|
||||
v-model="option.label"
|
||||
placeholder="Optie"
|
||||
density="compact"
|
||||
hide-details
|
||||
class="flex-grow-1"
|
||||
style="max-inline-size: 200px;"
|
||||
/>
|
||||
<AppTextField
|
||||
v-model="option.description"
|
||||
placeholder="Beschrijving (optioneel)"
|
||||
density="compact"
|
||||
hide-details
|
||||
class="flex-grow-1"
|
||||
/>
|
||||
<VBtn
|
||||
icon="tabler-trash"
|
||||
variant="text"
|
||||
color="error"
|
||||
size="x-small"
|
||||
class="flex-shrink-0"
|
||||
@click="removeOption(index)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { VForm } from 'vuetify/components/VForm'
|
||||
import EventTabsNav from '@/components/events/EventTabsNav.vue'
|
||||
import { useUpdateEvent, useUploadEventImage } from '@/composables/api/useEvents'
|
||||
import ImageUploadField from '@/components/common/ImageUploadField.vue'
|
||||
import { useUpdateEvent } from '@/composables/api/useEvents'
|
||||
import { useAuthStore } from '@/stores/useAuthStore'
|
||||
import type { EventItem, UpdateEventPayload } from '@/types/event'
|
||||
|
||||
@@ -24,16 +25,29 @@ const settingsTab = computed(() => {
|
||||
})
|
||||
|
||||
const { mutate: updateEvent, isPending: isUpdating } = useUpdateEvent(orgId, eventId)
|
||||
const { mutate: uploadImage, isPending: isUploading } = useUploadEventImage(orgId, eventId)
|
||||
|
||||
const bannerUrl = ref<string | null>(null)
|
||||
const logoUrl = ref<string | null>(null)
|
||||
const welcomeText = ref('')
|
||||
const showSuccess = ref(false)
|
||||
const refVForm = ref<VForm>()
|
||||
|
||||
function initForm(event: EventItem) {
|
||||
bannerUrl.value = event.registration_banner_url ?? null
|
||||
logoUrl.value = event.registration_logo_url ?? null
|
||||
welcomeText.value = event.registration_welcome_text ?? ''
|
||||
}
|
||||
|
||||
function onBannerChange(url: string | null) {
|
||||
bannerUrl.value = url
|
||||
updateEvent({ registration_banner_url: url } as UpdateEventPayload)
|
||||
}
|
||||
|
||||
function onLogoChange(url: string | null) {
|
||||
logoUrl.value = url
|
||||
updateEvent({ registration_logo_url: url } as UpdateEventPayload)
|
||||
}
|
||||
|
||||
function onSaveWelcomeText() {
|
||||
updateEvent(
|
||||
{ registration_welcome_text: welcomeText.value || null },
|
||||
@@ -44,19 +58,6 @@ function onSaveWelcomeText() {
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
function onFileSelected(files: File[], type: 'banner' | 'logo') {
|
||||
const file = files[0]
|
||||
if (!file) return
|
||||
|
||||
uploadImage({ file, type })
|
||||
}
|
||||
|
||||
function onClearImage(event: EventItem, type: 'banner' | 'logo') {
|
||||
const field = type === 'banner' ? 'registration_banner_url' : 'registration_logo_url'
|
||||
|
||||
updateEvent({ [field]: null } as UpdateEventPayload)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -104,91 +105,28 @@ function onClearImage(event: EventItem, type: 'banner' | 'logo') {
|
||||
<VCardText>
|
||||
<!-- Banner Image -->
|
||||
<div class="mb-6">
|
||||
<h6 class="text-subtitle-1 font-weight-medium mb-2">
|
||||
Bannerafbeelding
|
||||
</h6>
|
||||
<p class="text-body-2 text-medium-emphasis mb-3">
|
||||
Wordt bovenaan het registratieformulier getoond. Aanbevolen: 1200x400px.
|
||||
</p>
|
||||
|
||||
<VImg
|
||||
v-if="event.registration_banner_url"
|
||||
:src="event.registration_banner_url"
|
||||
height="160"
|
||||
cover
|
||||
rounded="lg"
|
||||
class="mb-3"
|
||||
<ImageUploadField
|
||||
:model-value="bannerUrl"
|
||||
label="Bannerafbeelding"
|
||||
purpose="banner"
|
||||
hint="Wordt bovenaan het registratieformulier getoond. Aanbevolen: 1200x400px."
|
||||
:preview-height="160"
|
||||
@update:model-value="onBannerChange"
|
||||
/>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<VFileInput
|
||||
accept="image/jpeg,image/png,image/webp"
|
||||
label="Afbeelding uploaden"
|
||||
prepend-icon=""
|
||||
prepend-inner-icon="tabler-upload"
|
||||
density="compact"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
:loading="isUploading"
|
||||
class="flex-grow-1"
|
||||
style="max-inline-size: 350px;"
|
||||
@update:model-value="(files: File[]) => onFileSelected(files, 'banner')"
|
||||
/>
|
||||
<VBtn
|
||||
v-if="event.registration_banner_url"
|
||||
variant="outlined"
|
||||
color="error"
|
||||
density="compact"
|
||||
@click="onClearImage(event, 'banner')"
|
||||
>
|
||||
Verwijder
|
||||
</VBtn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<VDivider class="mb-6" />
|
||||
|
||||
<!-- Logo Image -->
|
||||
<div class="mb-6">
|
||||
<h6 class="text-subtitle-1 font-weight-medium mb-2">
|
||||
Logo
|
||||
</h6>
|
||||
<p class="text-body-2 text-medium-emphasis mb-3">
|
||||
Wordt in de header van het registratieformulier getoond. Aanbevolen: vierkant, max 200x200px.
|
||||
</p>
|
||||
|
||||
<VAvatar
|
||||
v-if="event.registration_logo_url"
|
||||
:image="event.registration_logo_url"
|
||||
size="80"
|
||||
rounded="lg"
|
||||
class="mb-3"
|
||||
<ImageUploadField
|
||||
:model-value="logoUrl"
|
||||
label="Logo"
|
||||
purpose="logo"
|
||||
hint="Wordt in de header van het registratieformulier getoond. Aanbevolen: vierkant, max 200x200px."
|
||||
:preview-height="80"
|
||||
@update:model-value="onLogoChange"
|
||||
/>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<VFileInput
|
||||
accept="image/jpeg,image/png,image/webp"
|
||||
label="Logo uploaden"
|
||||
prepend-icon=""
|
||||
prepend-inner-icon="tabler-upload"
|
||||
density="compact"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
:loading="isUploading"
|
||||
class="flex-grow-1"
|
||||
style="max-inline-size: 350px;"
|
||||
@update:model-value="(files: File[]) => onFileSelected(files, 'logo')"
|
||||
/>
|
||||
<VBtn
|
||||
v-if="event.registration_logo_url"
|
||||
variant="outlined"
|
||||
color="error"
|
||||
density="compact"
|
||||
@click="onClearImage(event, 'logo')"
|
||||
>
|
||||
Verwijder
|
||||
</VBtn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<VDivider class="mb-6" />
|
||||
|
||||
Reference in New Issue
Block a user