Upload feature (e): wire inline upload (registratie beroep) + documenten step (herregistratie)
- Fold UploadState into both wizard machines; route via { tag: 'Upload', msg }
- Gate step validation on requiredCategoriesSatisfied; include deliveryRefs in submit
- Shared createUploadController (effectful glue: categories, transport, focus-poll, File map)
- rejectReason pure format validator + specs; bump registratie storage key to v2
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,15 @@
|
||||
import { Result, ok, err, assertNever } from '@shared/kernel/fp';
|
||||
import { Postcode, parsePostcode } from '@registratie/domain/value-objects/postcode';
|
||||
import { Email, parseEmail } from '@registratie/domain/value-objects/email';
|
||||
import {
|
||||
UploadState,
|
||||
UploadMsg,
|
||||
DeliveryChannel,
|
||||
initialUpload,
|
||||
reduceUpload,
|
||||
requiredCategoriesSatisfied,
|
||||
deliveryRefs,
|
||||
} from '@shared/upload/upload.machine';
|
||||
|
||||
/**
|
||||
* A FIXED 3-step registration wizard. The steps never change in number (always
|
||||
@@ -49,6 +58,7 @@ export interface ValidRegistratie {
|
||||
diplomaHerkomst: DiplomaHerkomst;
|
||||
beroep: string;
|
||||
antwoorden: Record<string, string>;
|
||||
documents: Array<{ categoryId: string; channel: DeliveryChannel; documentId?: string }>;
|
||||
}
|
||||
|
||||
/** Text fields settable via SetField. */
|
||||
@@ -63,17 +73,18 @@ export interface Errors {
|
||||
email?: string;
|
||||
correspondentie?: string;
|
||||
diploma?: string;
|
||||
documenten?: string;
|
||||
antwoorden?: Record<string, string>;
|
||||
}
|
||||
|
||||
export type RegistratieState =
|
||||
| { tag: 'Invullen'; draft: Draft; cursor: number; errors: Errors }
|
||||
| { tag: 'Invullen'; draft: Draft; cursor: number; errors: Errors; upload: UploadState }
|
||||
| { tag: 'Indienen'; data: ValidRegistratie }
|
||||
| { tag: 'Ingediend'; data: ValidRegistratie; referentie: string }
|
||||
| { tag: 'Mislukt'; data: ValidRegistratie; error: string };
|
||||
|
||||
const emptyDraft: Draft = { antwoorden: {} };
|
||||
export const initial: RegistratieState = { tag: 'Invullen', draft: emptyDraft, cursor: 0, errors: {} };
|
||||
export const initial: RegistratieState = { tag: 'Invullen', draft: emptyDraft, cursor: 0, errors: {}, upload: initialUpload };
|
||||
|
||||
/** Which step the cursor currently points at (clamped to the fixed list). */
|
||||
export function currentStep(s: Extract<RegistratieState, { tag: 'Invullen' }>): StepId {
|
||||
@@ -81,7 +92,7 @@ export function currentStep(s: Extract<RegistratieState, { tag: 'Invullen' }>):
|
||||
}
|
||||
|
||||
/** Validate every question currently visible in ONE step. Errors keyed per field. */
|
||||
function validateStep(step: StepId, d: Draft): Result<Errors, void> {
|
||||
function validateStep(step: StepId, d: Draft, upload: UploadState): Result<Errors, void> {
|
||||
const errors: Errors = {};
|
||||
switch (step) {
|
||||
case 'adres': {
|
||||
@@ -111,6 +122,10 @@ function validateStep(step: StepId, d: Draft): Result<Errors, void> {
|
||||
if (!(d.antwoorden[id] ?? '').trim()) open[id] = $localize`:@@validation.beantwoordVraag:Beantwoord deze vraag.`;
|
||||
}
|
||||
if (Object.keys(open).length > 0) errors.antwoorden = open;
|
||||
// Required documents for this wizard attach to the beroep step (inline upload).
|
||||
if (!requiredCategoriesSatisfied(upload)) {
|
||||
errors.documenten = $localize`:@@validation.documenten:Lever de verplichte documenten aan (upload of kies "per post nasturen").`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'controle':
|
||||
@@ -122,10 +137,10 @@ function validateStep(step: StepId, d: Draft): Result<Errors, void> {
|
||||
}
|
||||
|
||||
/** Parse the whole wizard into a ValidRegistratie (called on submit). */
|
||||
function validateAll(d: Draft): Result<Errors, ValidRegistratie> {
|
||||
function validateAll(d: Draft, upload: UploadState): Result<Errors, ValidRegistratie> {
|
||||
const errors: Errors = {};
|
||||
for (const step of STEPS) {
|
||||
const r = validateStep(step, d);
|
||||
const r = validateStep(step, d, upload);
|
||||
if (!r.ok) Object.assign(errors, r.error);
|
||||
}
|
||||
if (Object.keys(errors).length > 0) return err(errors);
|
||||
@@ -147,6 +162,7 @@ function validateAll(d: Draft): Result<Errors, ValidRegistratie> {
|
||||
diplomaHerkomst: d.diplomaHerkomst ?? 'handmatig',
|
||||
beroep: d.beroep,
|
||||
antwoorden,
|
||||
documents: deliveryRefs(upload),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -197,7 +213,7 @@ export function setAntwoord(s: RegistratieState, vraagId: string, value: string)
|
||||
|
||||
export function next(s: RegistratieState): RegistratieState {
|
||||
if (s.tag !== 'Invullen') return s;
|
||||
const r = validateStep(currentStep(s), s.draft);
|
||||
const r = validateStep(currentStep(s), s.draft, s.upload);
|
||||
if (!r.ok) return { ...s, errors: r.error };
|
||||
return { ...s, cursor: Math.min(s.cursor + 1, STEPS.length - 1), errors: {} };
|
||||
}
|
||||
@@ -216,10 +232,16 @@ export function gaNaarStap(s: RegistratieState, cursor: number): RegistratieStat
|
||||
|
||||
export function submit(s: RegistratieState): RegistratieState {
|
||||
if (s.tag !== 'Invullen') return s;
|
||||
const r = validateAll(s.draft);
|
||||
const r = validateAll(s.draft, s.upload);
|
||||
return r.ok ? { tag: 'Indienen', data: r.value } : { ...s, errors: r.error };
|
||||
}
|
||||
|
||||
/** Route an upload sub-message through the pure upload reducer (Invullen only). */
|
||||
export function upload(s: RegistratieState, msg: UploadMsg): RegistratieState {
|
||||
if (s.tag !== 'Invullen') return s;
|
||||
return { ...s, upload: reduceUpload(s.upload, msg) };
|
||||
}
|
||||
|
||||
export function resolve(s: RegistratieState, r: Result<string, string>): RegistratieState {
|
||||
if (s.tag !== 'Indienen') return s;
|
||||
return r.ok ? { tag: 'Ingediend', data: s.data, referentie: r.value } : { tag: 'Mislukt', data: s.data, error: r.error };
|
||||
@@ -240,6 +262,7 @@ export type RegistratieMsg =
|
||||
| { tag: 'Retry' }
|
||||
| { tag: 'SubmitConfirmed'; referentie: string }
|
||||
| { tag: 'SubmitFailed'; error: string }
|
||||
| { tag: 'Upload'; msg: UploadMsg }
|
||||
| { tag: 'Seed'; state: RegistratieState };
|
||||
|
||||
export function reduce(s: RegistratieState, m: RegistratieMsg): RegistratieState {
|
||||
@@ -272,6 +295,8 @@ export function reduce(s: RegistratieState, m: RegistratieMsg): RegistratieState
|
||||
return s.tag === 'Indienen' ? { tag: 'Ingediend', data: s.data, referentie: m.referentie } : s;
|
||||
case 'SubmitFailed':
|
||||
return s.tag === 'Indienen' ? { tag: 'Mislukt', data: s.data, error: m.error } : s;
|
||||
case 'Upload':
|
||||
return upload(s, m.msg);
|
||||
case 'Seed':
|
||||
return m.state;
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user