import { Result, assertNever } from '@shared/kernel/fp'; import { Postcode, parsePostcode } from '@registratie/domain/value-objects/postcode'; /** What the user is typing (raw, possibly invalid). */ export interface Draft { straat: string; postcode: string; woonplaats: string; } /** After parsing — postcode is the branded type, so downstream can't get a raw one. */ export interface Valid { straat: string; postcode: Postcode; woonplaats: string; } export type Errors = Partial>; /** * The change-request (adreswijziging) form as one tagged union — the SAME idiom * as the wizards, just single-step. `draft`/`errors` exist only while Editing; * Submitting/Submitted/Failed carry the parsed `Valid`. Illegal states (submitting * an invalid draft, a success screen with errors) are unrepresentable. */ export type ChangeRequestState = | { tag: 'Editing'; draft: Draft; errors: Errors } | { tag: 'Submitting'; data: Valid } | { tag: 'Submitted'; data: Valid; referentie: string } | { tag: 'Failed'; data: Valid; error: string }; export const initial: ChangeRequestState = { tag: 'Editing', draft: { straat: '', postcode: '', woonplaats: '' }, errors: {}, }; /** Parse via the value objects; on success hand back a Valid, else per-field errors. */ function validate(draft: Draft): Result { const straat = draft.straat.trim(); const postcode = parsePostcode(draft.postcode); const errors: Errors = {}; if (!straat) errors.straat = $localize`:@@validation.straat:Vul straat en huisnummer in.`; if (!postcode.ok) errors.postcode = postcode.error; if (straat && postcode.ok) { return { ok: true, value: { straat, postcode: postcode.value, woonplaats: draft.woonplaats.trim() }, }; } return { ok: false, error: errors }; } export type ChangeRequestMsg = | { tag: 'SetField'; key: keyof Draft; value: string } | { tag: 'Submit' } | { tag: 'Retry' } | { tag: 'SubmitConfirmed'; referentie: string } | { tag: 'SubmitFailed'; error: string } | { tag: 'Reset' } | { tag: 'Seed'; state: ChangeRequestState }; // mount a specific state (stories/tests) export function reduce(s: ChangeRequestState, m: ChangeRequestMsg): ChangeRequestState { switch (m.tag) { case 'SetField': return s.tag === 'Editing' ? { ...s, draft: { ...s.draft, [m.key]: m.value } } : s; case 'Submit': { if (s.tag !== 'Editing') return s; const r = validate(s.draft); return r.ok ? { tag: 'Submitting', data: r.value } : { ...s, errors: r.error }; } case 'Retry': return s.tag === 'Failed' ? { tag: 'Submitting', data: s.data } : s; case 'SubmitConfirmed': return s.tag === 'Submitting' ? { tag: 'Submitted', data: s.data, referentie: m.referentie } : s; case 'SubmitFailed': return s.tag === 'Submitting' ? { tag: 'Failed', data: s.data, error: m.error } : s; case 'Reset': return initial; case 'Seed': return m.state; default: return assertNever(m); } }