Inlines the form-schema source folder (no package.json, alias-only) into apps/app/src/composables/forms. Drops the @form-schema alias from apps/app/vite.config.ts (replaced by @/composables/forms via the existing @ alias). apps/portal vite + vitest configs keep @form-schema as a temporary alias pointing at the new location so portal tests/build keep working until apps/portal is removed at the end of this PR. Two pure-logic form-schema tests moved alongside. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
150 lines
4.4 KiB
TypeScript
150 lines
4.4 KiB
TypeScript
import { emailValidator, regexValidator, requiredValidator, urlValidator } from './validators'
|
|
import { FormFieldType } from '../types/formBuilder'
|
|
import type { PublicFormField } from '../types/formBuilder'
|
|
|
|
export type Validator = (value: unknown) => true | string
|
|
|
|
/**
|
|
* Build a list of client-side validators for a public form field.
|
|
* Runs in VTextField `:rules` and the stepper "current step valid"
|
|
* gate. Mirrors (a subset of) the backend relaxed rule set so the
|
|
* submitter gets feedback before the submit round-trip.
|
|
*/
|
|
export function getValidatorsForField(field: PublicFormField): Validator[] {
|
|
const rules: Validator[] = []
|
|
const v = field.validation_rules ?? {}
|
|
|
|
if (field.is_required) {
|
|
rules.push(value => {
|
|
const ok = requiredValidator(value)
|
|
|
|
return ok === true ? true : 'Dit veld is verplicht.'
|
|
})
|
|
}
|
|
|
|
switch (field.field_type) {
|
|
case FormFieldType.EMAIL:
|
|
rules.push(value => {
|
|
const ok = emailValidator(value)
|
|
|
|
return ok === true ? true : 'Vul een geldig e-mailadres in.'
|
|
})
|
|
break
|
|
|
|
case FormFieldType.URL:
|
|
rules.push(value => {
|
|
const ok = urlValidator(value)
|
|
|
|
return ok === true ? true : 'Vul een geldige URL in (beginnend met http:// of https://).'
|
|
})
|
|
break
|
|
|
|
case FormFieldType.PHONE:
|
|
rules.push(value => {
|
|
if (value === null || value === undefined || value === '') return true
|
|
const s = String(value).replace(/\s+/g, '')
|
|
|
|
return /^\+?[\d()-]{6,}$/.test(s) || 'Vul een geldig telefoonnummer in.'
|
|
})
|
|
break
|
|
|
|
case FormFieldType.NUMBER:
|
|
rules.push(value => {
|
|
if (value === null || value === undefined || value === '') return true
|
|
const n = Number(value)
|
|
|
|
return Number.isFinite(n) || 'Vul een geldig getal in.'
|
|
})
|
|
if (typeof v.min === 'number') {
|
|
const min = v.min
|
|
rules.push(value => {
|
|
if (value === null || value === undefined || value === '') return true
|
|
const n = Number(value)
|
|
|
|
return Number.isFinite(n) && n >= min ? true : `Minimaal ${min}.`
|
|
})
|
|
}
|
|
if (typeof v.max === 'number') {
|
|
const max = v.max
|
|
rules.push(value => {
|
|
if (value === null || value === undefined || value === '') return true
|
|
const n = Number(value)
|
|
|
|
return Number.isFinite(n) && n <= max ? true : `Maximaal ${max}.`
|
|
})
|
|
}
|
|
break
|
|
|
|
case FormFieldType.TEXT:
|
|
case FormFieldType.TEXTAREA:
|
|
if (typeof v.min === 'number') {
|
|
const min = v.min
|
|
rules.push(value => {
|
|
if (value === null || value === undefined || value === '') return true
|
|
|
|
return String(value).length >= min ? true : `Minimaal ${min} tekens.`
|
|
})
|
|
}
|
|
if (typeof v.max === 'number') {
|
|
const max = v.max
|
|
rules.push(value => {
|
|
if (value === null || value === undefined || value === '') return true
|
|
|
|
return String(value).length <= max ? true : `Maximaal ${max} tekens.`
|
|
})
|
|
}
|
|
if (typeof v.pattern === 'string' && v.pattern.length > 0) {
|
|
const pattern = v.pattern
|
|
rules.push(value => {
|
|
if (value === null || value === undefined || value === '') return true
|
|
const ok = regexValidator(value, pattern)
|
|
|
|
return ok === true ? true : 'Ongeldige invoer.'
|
|
})
|
|
}
|
|
break
|
|
|
|
case FormFieldType.MULTISELECT:
|
|
case FormFieldType.CHECKBOX_LIST:
|
|
if (typeof v.min_selections === 'number') {
|
|
const min = v.min_selections
|
|
rules.push(value => {
|
|
const arr = Array.isArray(value) ? value : []
|
|
|
|
return arr.length >= min ? true : `Kies er minimaal ${min}.`
|
|
})
|
|
}
|
|
if (typeof v.max_selections === 'number') {
|
|
const max = v.max_selections
|
|
rules.push(value => {
|
|
const arr = Array.isArray(value) ? value : []
|
|
|
|
return arr.length <= max ? true : `Kies er maximaal ${max}.`
|
|
})
|
|
}
|
|
break
|
|
|
|
default:
|
|
break
|
|
}
|
|
|
|
return rules
|
|
}
|
|
|
|
export function runValidators(rules: Validator[], value: unknown): string | true {
|
|
for (const rule of rules) {
|
|
const r = rule(value)
|
|
if (r !== true) return r
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
export function isFieldValueEmpty(value: unknown): boolean {
|
|
if (value === null || value === undefined) return true
|
|
if (typeof value === 'string') return value.trim() === ''
|
|
if (Array.isArray(value)) return value.length === 0
|
|
|
|
return false
|
|
}
|