feat: event registration branding with vertical wizard layout
- Add registration_banner_url, registration_welcome_text, registration_logo_url columns to events table with migration - Add uploadImage endpoint (POST .../upload-image) with form request validation for banner and logo images (jpg/png/webp, max 5MB) - Include branding fields in EventResource and PublicRegistrationDataController - Build registration settings UI in organizer event settings page with banner/logo upload and welcome text editor - Redesign portal registration page: hero banner with gradient overlay, welcome text card, vertical step navigation (desktop) / horizontal chips (mobile), two-column form fields with density="comfortable" - Update success page with event banner and consistent branding - Seed welcome text for Echt Feesten 2026 - Add 9 PHPUnit tests covering image upload, branding fields in API responses Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,19 +1,234 @@
|
||||
<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 { useAuthStore } from '@/stores/useAuthStore'
|
||||
import type { EventItem } from '@/types/event'
|
||||
|
||||
definePage({
|
||||
meta: {
|
||||
navActiveLink: 'events',
|
||||
},
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
const orgId = computed(() => authStore.currentOrganisation?.id ?? '')
|
||||
const eventId = computed(() => String((route.params as { id: string }).id))
|
||||
|
||||
const { mutate: updateEvent, isPending: isUpdating } = useUpdateEvent(orgId, eventId)
|
||||
const { mutate: uploadImage, isPending: isUploading } = useUploadEventImage(orgId, eventId)
|
||||
|
||||
const welcomeText = ref('')
|
||||
const showSuccess = ref(false)
|
||||
const refVForm = ref<VForm>()
|
||||
|
||||
function initForm(event: EventItem) {
|
||||
welcomeText.value = event.registration_welcome_text ?? ''
|
||||
}
|
||||
|
||||
function onSaveWelcomeText() {
|
||||
updateEvent(
|
||||
{ registration_welcome_text: welcomeText.value || null },
|
||||
{
|
||||
onSuccess: () => {
|
||||
showSuccess.value = true
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
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 any)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<EventTabsNav>
|
||||
<VCard class="ma-4">
|
||||
<VCardText>
|
||||
Deze module is binnenkort beschikbaar.
|
||||
</VCardText>
|
||||
</VCard>
|
||||
<EventTabsNav v-slot="{ event }">
|
||||
<div @vue:mounted="initForm(event)">
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="8"
|
||||
>
|
||||
<!-- Registration Branding -->
|
||||
<VCard class="mb-6">
|
||||
<VCardTitle class="d-flex align-center gap-2">
|
||||
<VIcon
|
||||
icon="tabler-palette"
|
||||
size="20"
|
||||
/>
|
||||
Registratie-uiterlijk
|
||||
</VCardTitle>
|
||||
<VCardSubtitle>
|
||||
Pas het uiterlijk van het vrijwilligersregistratieformulier aan
|
||||
</VCardSubtitle>
|
||||
|
||||
<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"
|
||||
/>
|
||||
|
||||
<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"
|
||||
/>
|
||||
|
||||
<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" />
|
||||
|
||||
<!-- Welcome Text -->
|
||||
<div>
|
||||
<h6 class="text-subtitle-1 font-weight-medium mb-2">
|
||||
Welkomstbericht
|
||||
</h6>
|
||||
|
||||
<VForm
|
||||
ref="refVForm"
|
||||
@submit.prevent="onSaveWelcomeText"
|
||||
>
|
||||
<VTextarea
|
||||
v-model="welcomeText"
|
||||
label="Welkomstbericht"
|
||||
hint="Dit bericht wordt getoond boven het vrijwilligersregistratieformulier."
|
||||
persistent-hint
|
||||
:counter="1000"
|
||||
rows="4"
|
||||
auto-grow
|
||||
class="mb-4"
|
||||
/>
|
||||
|
||||
<VBtn
|
||||
type="submit"
|
||||
color="primary"
|
||||
:loading="isUpdating"
|
||||
>
|
||||
Opslaan
|
||||
</VBtn>
|
||||
</VForm>
|
||||
</div>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="4"
|
||||
>
|
||||
<!-- Preview hint -->
|
||||
<VCard variant="tonal">
|
||||
<VCardText>
|
||||
<div class="d-flex align-center gap-2 mb-2">
|
||||
<VIcon
|
||||
icon="tabler-eye"
|
||||
size="18"
|
||||
/>
|
||||
<span class="text-subtitle-2 font-weight-medium">Preview</span>
|
||||
</div>
|
||||
<p class="text-body-2 text-medium-emphasis mb-0">
|
||||
Open het registratieformulier om een preview te zien van de aanpassingen.
|
||||
</p>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</div>
|
||||
|
||||
<VSnackbar
|
||||
v-model="showSuccess"
|
||||
color="success"
|
||||
:timeout="3000"
|
||||
>
|
||||
Registratie-instellingen opgeslagen
|
||||
</VSnackbar>
|
||||
</EventTabsNav>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user