feat(registratie): WP-34 — phone field + BRP address read-only
Reshape the adreswijziging form into a contact-change form: the BRP address is
authoritative and shown read-only (you change it at the gemeente), and the phone
number becomes the editable/submittable field. New Telefoonnummer value object
(parse-don't-validate); backend RejectPhoneChange re-validates as authority.
POST /change-requests now carries { telefoon } (typed client regenerated).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -3,11 +3,9 @@ import { FormsModule } from '@angular/forms';
|
||||
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 { FormFieldComponent } from '@shared/ui/form-field/form-field.component';
|
||||
import { TextInputComponent } from '@shared/ui/text-input/text-input.component';
|
||||
import { Adres } from '@registratie/domain/person';
|
||||
import { createStore } from '@shared/application/store';
|
||||
import { whenTag } from '@shared/kernel/fp';
|
||||
import {
|
||||
@@ -19,19 +17,44 @@ import {
|
||||
import { createSubmitChangeRequest } from '@registratie/application/submit-change-request';
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Organism: contact-change (telefoonwijziging) form. The BRP address is authoritative
|
||||
* and shown READ-ONLY (WP-34) — you change your address at the gemeente, not here — so
|
||||
* only the phone number is editable. 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`. The server re-validates.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-change-request-form',
|
||||
imports: [FormsModule, ButtonComponent, HeadingComponent, AlertComponent, AddressFieldsComponent],
|
||||
imports: [
|
||||
FormsModule,
|
||||
ButtonComponent,
|
||||
HeadingComponent,
|
||||
AlertComponent,
|
||||
FormFieldComponent,
|
||||
TextInputComponent,
|
||||
],
|
||||
styles: [
|
||||
`
|
||||
.brp {
|
||||
margin-block-end: var(--rhc-space-max-lg);
|
||||
}
|
||||
.brp dt {
|
||||
font-weight: var(--rhc-text-font-weight-semi-bold);
|
||||
}
|
||||
.brp dd {
|
||||
margin: 0 0 var(--rhc-space-max-sm) 0;
|
||||
}
|
||||
.brp .source {
|
||||
color: var(--rhc-color-grijs-700);
|
||||
font-size: var(--rhc-text-font-size-sm);
|
||||
}
|
||||
`,
|
||||
],
|
||||
template: `
|
||||
@if (state().tag === 'Submitted') {
|
||||
<app-alert type="ok" i18n="@@changeRequest.success">
|
||||
Uw adreswijziging is ontvangen (referentie {{ referentie() }}). U ontvangt binnen 5
|
||||
werkdagen bericht.
|
||||
Uw wijziging is ontvangen (referentie {{ referentie() }}). U ontvangt binnen 5 werkdagen
|
||||
bericht.
|
||||
</app-alert>
|
||||
<div class="app-section">
|
||||
<app-button
|
||||
@@ -42,19 +65,43 @@ import { createSubmitChangeRequest } from '@registratie/application/submit-chang
|
||||
>
|
||||
</div>
|
||||
} @else {
|
||||
<app-heading [level]="2" i18n="@@changeRequest.heading">Adreswijziging doorgeven</app-heading>
|
||||
<app-heading [level]="2" i18n="@@changeRequest.heading">Contactgegevens wijzigen</app-heading>
|
||||
|
||||
@if (brpAdres(); as a) {
|
||||
<dl class="brp app-section">
|
||||
<dt i18n="@@changeRequest.brpAdresLabel">Adres (BRP)</dt>
|
||||
<dd>{{ a.straat }}<br />{{ a.postcode }} {{ a.woonplaats }}</dd>
|
||||
<dd class="source" i18n="@@changeRequest.brpAdresBron">
|
||||
Uw adres komt uit de Basisregistratie Personen en kan hier niet worden gewijzigd. Wijzig
|
||||
het bij uw gemeente.
|
||||
</dd>
|
||||
</dl>
|
||||
}
|
||||
|
||||
<form (ngSubmit)="onSubmit()" class="form-horizontal app-section">
|
||||
<div class="form-header">
|
||||
<div class="form-action">
|
||||
<span class="meta" i18n="@@form.verplichteVelden">* verplichte velden</span>
|
||||
</div>
|
||||
</div>
|
||||
<app-address-fields
|
||||
idPrefix="cr"
|
||||
[value]="adres()"
|
||||
[errors]="errors()"
|
||||
(fieldChange)="dispatch({ tag: 'SetField', key: $event.key, value: $event.value })"
|
||||
/>
|
||||
<app-form-field
|
||||
i18n-label="@@changeRequest.telefoonLabel"
|
||||
label="Telefoonnummer"
|
||||
fieldId="cr-telefoon"
|
||||
required
|
||||
[error]="errors().telefoon"
|
||||
>
|
||||
<app-text-input
|
||||
inputId="cr-telefoon"
|
||||
[invalid]="!!errors().telefoon"
|
||||
[ngModel]="telefoon()"
|
||||
(ngModelChange)="dispatch({ tag: 'SetField', key: 'telefoon', value: $event })"
|
||||
name="telefoon"
|
||||
i18n-placeholder="@@changeRequest.telefoonPlaceholder"
|
||||
placeholder="0612345678"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
/>
|
||||
</app-form-field>
|
||||
|
||||
@if (failedError()) {
|
||||
<app-alert type="error"
|
||||
@@ -77,6 +124,9 @@ export class ChangeRequestFormComponent {
|
||||
private submit = createSubmitChangeRequest();
|
||||
private store = createStore<ChangeRequestState, ChangeRequestMsg>(initial, reduce);
|
||||
|
||||
/** BRP address, shown read-only. Undefined until the profile loads. */
|
||||
brpAdres = input<Adres | undefined>(undefined);
|
||||
|
||||
/** Optional seed so Storybook / tests can mount any state directly. */
|
||||
seed = input<ChangeRequestState>(initial);
|
||||
|
||||
@@ -87,19 +137,17 @@ export class ChangeRequestFormComponent {
|
||||
protected readonly submitBezigLabel = $localize`:@@changeRequest.submitBezig:Bezig met indienen…`;
|
||||
|
||||
private editing = computed(() => whenTag(this.state(), 'Editing'));
|
||||
protected errors = computed<AdresErrors>(() => this.editing()?.errors ?? {});
|
||||
protected errors = computed(() => 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>(() => {
|
||||
/** The phone shown in the field — the live draft while editing, the parsed value
|
||||
while submitting/failed (so the user sees what they sent). */
|
||||
protected telefoon = computed(() => {
|
||||
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
|
||||
if (s.tag === 'Editing') return s.draft.telefoon;
|
||||
if (s.tag === 'Submitting' || s.tag === 'Failed') return s.data.telefoon;
|
||||
return '';
|
||||
});
|
||||
|
||||
constructor() {
|
||||
|
||||
@@ -3,38 +3,31 @@ import { applicationConfig } from '@storybook/angular';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { ChangeRequestFormComponent } from './change-request-form.component';
|
||||
import { provideApiClient } from '@shared/infrastructure/api-client.provider';
|
||||
import { Postcode } from '@registratie/domain/value-objects/postcode';
|
||||
import { Telefoonnummer } from '@registratie/domain/value-objects/telefoonnummer';
|
||||
|
||||
const validData = {
|
||||
straat: 'Lange Voorhout 9',
|
||||
postcode: '2514 EA' as Postcode,
|
||||
woonplaats: 'Den Haag',
|
||||
};
|
||||
const brpAdres = { straat: 'Lange Voorhout 9', postcode: '2514 EA', woonplaats: 'Den Haag' };
|
||||
const validData = { telefoon: '0612345678' as Telefoonnummer };
|
||||
|
||||
const meta: Meta<ChangeRequestFormComponent> = {
|
||||
title: 'Domein/Registratie/Change Request Form',
|
||||
component: ChangeRequestFormComponent,
|
||||
// The form injects ApiClient (over HttpClient) for the submit command.
|
||||
decorators: [applicationConfig({ providers: [provideHttpClient(), provideApiClient()] })],
|
||||
args: { brpAdres },
|
||||
};
|
||||
export default meta;
|
||||
type Story = StoryObj<ChangeRequestFormComponent>;
|
||||
|
||||
// One render per state of the machine.
|
||||
export const Empty: Story = {
|
||||
args: {
|
||||
seed: { tag: 'Editing', draft: { straat: '', postcode: '', woonplaats: '' }, errors: {} },
|
||||
},
|
||||
args: { seed: { tag: 'Editing', draft: { telefoon: '' }, errors: {} } },
|
||||
};
|
||||
export const WithErrors: Story = {
|
||||
args: {
|
||||
seed: {
|
||||
tag: 'Editing',
|
||||
draft: { straat: '', postcode: 'nope', woonplaats: '' },
|
||||
errors: {
|
||||
straat: 'Vul straat en huisnummer in.',
|
||||
postcode: 'Voer een geldige postcode in, bijv. 1234 AB.',
|
||||
},
|
||||
draft: { telefoon: 'nope' },
|
||||
errors: { telefoon: 'Voer een geldig telefoonnummer in, bijv. 0612345678.' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -33,7 +33,7 @@ import { BigProfileStore } from '@registratie/application/big-profile.store';
|
||||
</app-async>
|
||||
|
||||
<div class="app-section">
|
||||
<app-change-request-form />
|
||||
<app-change-request-form [brpAdres]="profile()?.person?.adres" />
|
||||
</div>
|
||||
</app-page-shell>
|
||||
`,
|
||||
|
||||
Reference in New Issue
Block a user