Files
atomic-design-poc/src/app/registratie/ui/change-request-form/change-request-form.component.ts
Edwin van den Houdt 474c040410 Step 2 (i18n): $localize sweep + JA_NEE dedup (M3, M4)
Wrap every user-facing Dutch string in Angular's first-party i18n — `i18n`/
`i18n-<attr>` in templates, `$localize` in TS (value-objects, machines, commands,
label constants, shared-component defaults). Source locale stays nl; a second
locale is now a translation file, not a code change.

- M3: ~145 strings localized with stable @@ ids across registratie,
  herregistratie, auth, shared/ui, shared/layout. Skipped: showcase, debug-state,
  scenario interceptor, generated client, specs/stories, raw status enum tags,
  internal parse* diagnostics.
- M4: single shared JA_NEE (localized labels) in radio-group; both wizard copies
  removed.

Gate green: lint, check:tokens, build, test 77/77.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 14:00:10 +02:00

96 lines
4.4 KiB
TypeScript

import { Component, computed, inject, input } from '@angular/core';
import { ButtonComponent } from '@shared/ui/button/button.component';
import { HeadingComponent } from '@shared/ui/heading/heading.component';
import { AlertComponent } from '@shared/ui/alert/alert.component';
import { AddressFieldsComponent, AdresValue, AdresErrors } from '@registratie/ui/address-fields/address-fields.component';
import { createStore } from '@shared/application/store';
import { whenTag } from '@shared/kernel/fp';
import { State, Msg, initial, reduce } from '@registratie/domain/change-request.machine';
import { submitChangeRequest } from '@registratie/application/submit-change-request';
import { ApiClient } from '@shared/infrastructure/api-client';
/**
* Organism: change-request (adreswijziging) form. Uses the SAME idiom as the
* wizards — all state in one signal driven by the pure `reduce`
* (change-request.machine.ts), submitted via a `submit-*` command returning
* `Result`. Renders the shared `<app-address-fields>`; the server re-validates.
*/
@Component({
selector: 'app-change-request-form',
imports: [ButtonComponent, HeadingComponent, AlertComponent, AddressFieldsComponent],
template: `
@if (state().tag === 'Submitted') {
<app-alert type="ok" i18n="@@changeRequest.success">
Uw adreswijziging is ontvangen (referentie {{ referentie() }}). U ontvangt binnen 5 werkdagen bericht.
</app-alert>
<div class="app-section">
<app-button variant="secondary" (click)="dispatch({ tag: 'Reset' })" i18n="@@changeRequest.nieuwe">Nieuwe wijziging doorgeven</app-button>
</div>
} @else {
<app-heading [level]="2" i18n="@@changeRequest.heading">Adreswijziging doorgeven</app-heading>
<form (ngSubmit)="onSubmit()" class="app-form app-form-panel app-section">
<app-address-fields
idPrefix="cr"
[value]="adres()"
[errors]="errors()"
(fieldChange)="dispatch({ tag: 'SetField', key: $event.key, value: $event.value })" />
@if (failedError()) {
<app-alert type="error"><ng-container i18n="@@changeRequest.failed">Het indienen is niet gelukt:</ng-container> {{ failedError() }}</app-alert>
}
<app-button type="submit" variant="primary" [disabled]="state().tag === 'Submitting'">
{{ state().tag === 'Submitting' ? submitBezigLabel : submitLabel }}
</app-button>
</form>
}
`,
})
export class ChangeRequestFormComponent {
private apiClient = inject(ApiClient);
private store = createStore<State, Msg>(initial, reduce);
/** Optional seed so Storybook / tests can mount any state directly. */
seed = input<State>(initial);
readonly state = this.store.model;
protected dispatch = this.store.dispatch;
protected readonly submitLabel = $localize`:@@changeRequest.submit:Wijziging indienen`;
protected readonly submitBezigLabel = $localize`:@@changeRequest.submitBezig:Bezig met indienen…`;
private editing = computed(() => whenTag(this.state(), 'Editing'));
protected errors = computed<AdresErrors>(() => this.editing()?.errors ?? {});
protected failedError = computed(() => whenTag(this.state(), 'Failed')?.error ?? '');
protected referentie = computed(() => whenTag(this.state(), 'Submitted')?.referentie ?? '');
/** The address shown in the fields — the live draft while editing, the parsed
data while submitting/failed (so the user sees what they sent). */
protected adres = computed<AdresValue>(() => {
const s = this.state();
if (s.tag === 'Editing') return s.draft;
if (s.tag === 'Submitting' || s.tag === 'Failed') {
return { straat: s.data.straat, postcode: s.data.postcode, woonplaats: s.data.woonplaats };
}
return { straat: '', postcode: '', woonplaats: '' }; // Submitted shows the success alert, not the fields
});
constructor() {
queueMicrotask(() => this.dispatch({ tag: 'Seed', state: this.seed() }));
}
onSubmit() {
this.dispatch({ tag: 'Submit' });
this.runIfSubmitting();
}
/** Effect: when we entered Submitting, call the command, then dispatch the outcome. */
private async runIfSubmitting() {
const s = this.state();
if (s.tag !== 'Submitting') return;
const r = await submitChangeRequest(this.apiClient, s.data);
if (r.ok) this.dispatch({ tag: 'SubmitConfirmed', referentie: r.value });
else this.dispatch({ tag: 'SubmitFailed', error: r.error });
}
}