import { Component, computed, inject, input } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { FormFieldComponent } from '@shared/ui/form-field/form-field.component'; import { TextInputComponent } from '@shared/ui/text-input/text-input.component'; import { ButtonComponent } from '@shared/ui/button/button.component'; import { AlertComponent } from '@shared/ui/alert/alert.component'; import { SpinnerComponent } from '@shared/ui/spinner/spinner.component'; import { createStore } from '@shared/application/store'; import { BigProfileStore } from '@registratie/application/big-profile.store'; import { WizardState, WizardMsg, Draft, initial, reduce } from '@herregistratie/domain/herregistratie.machine'; import { submitHerregistratie } from '@herregistratie/application/submit-herregistratie'; /** Organism: multi-step herregistratie wizard. ALL state lives in one signal driven by the pure `reduce` function (see herregistratie.machine.ts) via an Elm-style store. The UI just sends messages and folds over the state's tag — no booleans like `submitting`/`submitted` that could contradict each other. Submitting also flips an optimistic flag on the shared BigProfileStore, so the dashboard shows "in behandeling" immediately. */ @Component({ selector: 'app-herregistratie-wizard', imports: [FormsModule, FormFieldComponent, TextInputComponent, ButtonComponent, AlertComponent, SpinnerComponent], template: ` @switch (state().tag) { @case ('Editing') {

Stap {{ step() }} van 2

@if (step() === 1) { Volgende } @else {
Vorige Herregistratie aanvragen
}
} @case ('Submitting') { Aanvraag wordt verwerkt… } @case ('Submitted') { Uw aanvraag tot herregistratie is ontvangen. } @case ('Failed') { Indienen mislukt: {{ failedError() }}
Opnieuw proberen
} } `, }) export class HerregistratieWizardComponent { private profile = inject(BigProfileStore); private store = createStore(initial, reduce); /** Optional seed so Storybook / the showcase can mount any state directly. */ seed = input(initial); readonly state = this.store.model; // public so the showcase can highlight the live state protected dispatch = this.store.dispatch; private editing = computed(() => (this.state().tag === 'Editing' ? (this.state() as Extract) : null)); protected step = computed(() => this.editing()?.step ?? 1); protected draft = computed(() => this.editing()?.draft ?? { uren: '', jaren: '', punten: '' }); protected errUren = computed(() => this.editing()?.errors.uren ?? ''); protected errJaren = computed(() => this.editing()?.errors.jaren ?? ''); protected errPunten = computed(() => this.editing()?.errors.punten ?? ''); protected failedError = computed(() => (this.state().tag === 'Failed' ? (this.state() as Extract).error : '')); constructor() { queueMicrotask(() => this.dispatch({ tag: 'Seed', state: this.seed() })); } onPrimary() { const s = this.state(); if (s.tag !== 'Editing') return; this.dispatch(s.step === 1 ? { tag: 'Next' } : { tag: 'Submit' }); this.runIfSubmitting(); } onRetry() { this.dispatch({ tag: 'Retry' }); this.runIfSubmitting(); } /** The effect: when we entered Submitting, call the backend command, flip the optimistic cross-page flag, then dispatch the result (and commit/rollback). */ private async runIfSubmitting() { const s = this.state(); if (s.tag !== 'Submitting') return; this.profile.beginHerregistratie(); const r = await submitHerregistratie(s.data); if (r.ok) { this.dispatch({ tag: 'SubmitConfirmed' }); this.profile.confirmHerregistratie(); } else { this.dispatch({ tag: 'SubmitFailed', error: r.error }); this.profile.rollbackHerregistratie(); } } }