Replace monolithic register/[eventSlug].vue with composable field renderer, conditional-logic engine, stepper, and per-field components driven by Form Builder schema. Adds flatpickr for date fields. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
86 lines
2.0 KiB
Vue
86 lines
2.0 KiB
Vue
<script setup lang="ts">
|
|
import type { FieldOption, PublicFormField } from '@/types/formBuilder'
|
|
import { getValidatorsForField } from '@/utils/formValidation'
|
|
|
|
const props = defineProps<{
|
|
field: PublicFormField
|
|
modelValue: unknown
|
|
errorMessages?: string[]
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
(e: 'update:modelValue', v: string): void
|
|
(e: 'blur'): void
|
|
}>()
|
|
|
|
interface NormalizedOption {
|
|
label: string
|
|
description?: string | null
|
|
value: string
|
|
}
|
|
|
|
function normalize(opt: FieldOption): NormalizedOption {
|
|
if (typeof opt === 'string') return { label: opt, value: opt }
|
|
const base = { label: opt.label, description: opt.description ?? null }
|
|
const value = opt.value != null ? String(opt.value) : opt.label
|
|
|
|
return { ...base, value }
|
|
}
|
|
|
|
const options = computed<NormalizedOption[]>(() =>
|
|
(props.field.options ?? []).map(normalize),
|
|
)
|
|
|
|
const rules = computed(() => getValidatorsForField(props.field))
|
|
|
|
const model = computed({
|
|
get: () => (props.modelValue ?? '') as string,
|
|
set: (v: string) => {
|
|
emit('update:modelValue', v)
|
|
emit('blur')
|
|
},
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<div class="text-body-2 mb-1 text-high-emphasis">
|
|
{{ field.label }}<span
|
|
v-if="field.is_required"
|
|
class="text-error"
|
|
> *</span>
|
|
</div>
|
|
<p
|
|
v-if="field.help_text"
|
|
class="text-caption text-medium-emphasis mb-2"
|
|
>
|
|
{{ field.help_text }}
|
|
</p>
|
|
<VRadioGroup
|
|
v-model="model"
|
|
density="comfortable"
|
|
:rules="rules"
|
|
:error-messages="errorMessages"
|
|
hide-details="auto"
|
|
>
|
|
<VRadio
|
|
v-for="opt in options"
|
|
:key="opt.value"
|
|
:value="opt.value"
|
|
>
|
|
<template #label>
|
|
<div>
|
|
<span class="text-body-1">{{ opt.label }}</span>
|
|
<p
|
|
v-if="opt.description"
|
|
class="text-disabled text-caption mt-1 mb-0"
|
|
>
|
|
{{ opt.description }}
|
|
</p>
|
|
</div>
|
|
</template>
|
|
</VRadio>
|
|
</VRadioGroup>
|
|
</div>
|
|
</template>
|