feat: split name into first_name + last_name across users, persons, and companies
Cross-cutting migration affecting the entire stack: - Database: 3 migrations splitting name columns with data migration - Models: first_name/last_name on User, Person; contact_first_name/contact_last_name on Company; backward-compatible name accessors - API: all resources return first_name, last_name, full_name; assignablePersons endpoint updated - Requests: validation rules updated for all person/user/company forms - Services: VolunteerRegistrationService, ShiftAssignmentService, InvitationService updated - Frontend: TypeScript types, Zod schemas, all forms split into Voornaam/Achternaam fields - Display: all person/user name references use full_name; initials use first_name[0]+last_name[0] - Tests: all 371 tests passing - Docs: SCHEMA.md and API.md updated Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -37,7 +37,8 @@ const submitError = ref<string | null>(null)
|
||||
const { errors, defineField, validateField, setFieldValue } = useForm({
|
||||
validationSchema: toTypedSchema(fullRegistrationSchema),
|
||||
initialValues: {
|
||||
name: '',
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
tshirt_size: '',
|
||||
@@ -50,7 +51,8 @@ const { errors, defineField, validateField, setFieldValue } = useForm({
|
||||
},
|
||||
})
|
||||
|
||||
const [name] = defineField('name')
|
||||
const [firstName] = defineField('first_name')
|
||||
const [lastName] = defineField('last_name')
|
||||
const [email] = defineField('email')
|
||||
const [phone] = defineField('phone')
|
||||
const [tshirtSize] = defineField('tshirt_size')
|
||||
@@ -64,7 +66,8 @@ const [motivationOther] = defineField('motivation_other')
|
||||
// Pre-fill authenticated user data
|
||||
watch(() => authStore.user, user => {
|
||||
if (user) {
|
||||
setFieldValue('name', user.name)
|
||||
setFieldValue('first_name', user.first_name)
|
||||
setFieldValue('last_name', user.last_name)
|
||||
setFieldValue('email', user.email)
|
||||
}
|
||||
}, { immediate: true })
|
||||
@@ -154,10 +157,10 @@ const formattedDates = computed(() => {
|
||||
})
|
||||
|
||||
// Step field mapping for validation (0-based)
|
||||
type FormField = 'name' | 'email' | 'phone' | 'tshirt_size' | 'first_aid' | 'allergies' | 'access_requirements' | 'driving_licence' | 'motivation' | 'motivation_other'
|
||||
type FormField = 'first_name' | 'last_name' | 'email' | 'phone' | 'tshirt_size' | 'first_aid' | 'allergies' | 'access_requirements' | 'driving_licence' | 'motivation' | 'motivation_other'
|
||||
|
||||
const stepFields: Record<number, FormField[]> = {
|
||||
0: ['name', 'email', 'phone'],
|
||||
0: ['first_name', 'last_name', 'email', 'phone'],
|
||||
1: ['tshirt_size', 'first_aid', 'allergies', 'access_requirements', 'driving_licence'],
|
||||
2: ['motivation', 'motivation_other'],
|
||||
}
|
||||
@@ -265,7 +268,8 @@ async function onSubmit() {
|
||||
}))
|
||||
|
||||
const payload: VolunteerRegistrationForm = {
|
||||
name: name.value ?? '',
|
||||
first_name: firstName.value ?? '',
|
||||
last_name: lastName.value ?? '',
|
||||
email: email.value ?? '',
|
||||
phone: phone.value ?? '',
|
||||
tshirt_size: tshirtSize.value ?? '',
|
||||
@@ -452,7 +456,7 @@ async function onSubmit() {
|
||||
variant="tonal"
|
||||
class="mb-4"
|
||||
>
|
||||
Je bent ingelogd als {{ authStore.user?.name }}. Je gegevens zijn automatisch ingevuld.
|
||||
Je bent ingelogd als {{ authStore.user?.full_name }}. Je gegevens zijn automatisch ingevuld.
|
||||
</VAlert>
|
||||
|
||||
<div
|
||||
@@ -624,10 +628,24 @@ async function onSubmit() {
|
||||
md="6"
|
||||
>
|
||||
<VTextField
|
||||
v-model="name"
|
||||
label="Naam *"
|
||||
placeholder="Je volledige naam"
|
||||
:error-messages="errors.name"
|
||||
v-model="firstName"
|
||||
label="Voornaam *"
|
||||
placeholder="Je voornaam"
|
||||
:error-messages="errors.first_name"
|
||||
:disabled="authStore.isAuthenticated"
|
||||
density="comfortable"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VTextField
|
||||
v-model="lastName"
|
||||
label="Achternaam *"
|
||||
placeholder="Je achternaam"
|
||||
:error-messages="errors.last_name"
|
||||
:disabled="authStore.isAuthenticated"
|
||||
density="comfortable"
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
export const step1Schema = z.object({
|
||||
name: z.string().min(1, 'Naam is verplicht').max(255),
|
||||
first_name: z.string().min(1, 'Voornaam is verplicht').max(255),
|
||||
last_name: z.string().min(1, 'Achternaam is verplicht').max(255),
|
||||
email: z.string().min(1, 'E-mailadres is verplicht').email('Ongeldig e-mailadres').max(255),
|
||||
phone: z.string().max(50).optional().or(z.literal('')),
|
||||
})
|
||||
|
||||
@@ -4,7 +4,9 @@ import { apiClient } from '@/lib/axios'
|
||||
|
||||
interface PortalUser {
|
||||
id: string
|
||||
name: string
|
||||
first_name: string
|
||||
last_name: string
|
||||
full_name: string
|
||||
email: string
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,8 @@ export interface VolunteerAvailability {
|
||||
|
||||
export interface VolunteerRegistrationForm {
|
||||
// Step 1
|
||||
name: string
|
||||
first_name: string
|
||||
last_name: string
|
||||
email: string
|
||||
phone: string
|
||||
// Step 2
|
||||
|
||||
Reference in New Issue
Block a user