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>
96 lines
4.4 KiB
TypeScript
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 });
|
|
}
|
|
}
|