Rijkshuisstijl restyle + wizard fixes
Chrome: two-tier Rijksoverheid header (white brand bar + lint-blue
breadcrumb bar, route-driven), dark multi-column footer, white page
surface. Session shown via a shared SESSION_PORT token (keeps shared/
free of the auth context).
Overview ("Mijn overzicht") rebuilt to the NL Design System #392 pattern:
side-nav + "Wat moet ik regelen" task list (derived) + "Mijn registratie"
cards. New shared components: card, task-list, side-nav; pure
tasksFromProfile (+spec).
Wizards: grey form panel, connected numbered stepper, form-field
"(verplicht)" markers + styled description/error, full-width inputs.
Propagated to login, detail, change-request, address-fields.
Bug fixes:
- wizard-shell: add FormsModule so NgForm intercepts submit (wizards now
advance; no native GET leaking choices into the URL).
- wizard-shell: error-summary links focus the field instead of navigating
(a fragment href resolved against <base href="/"> reloaded to "/" and
bounced to login).
- wizard-shell: error-summary focus only on the rising edge, so typing
while errors are shown no longer scrolls the page up.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -19,20 +19,25 @@ export type AdresErrors = Partial<Record<keyof AdresValue, string>>;
|
||||
@Component({
|
||||
selector: 'app-address-fields',
|
||||
imports: [FormsModule, FormFieldComponent, TextInputComponent],
|
||||
styles: [`
|
||||
fieldset{border:0;margin:0;padding:0;min-inline-size:0}
|
||||
legend{padding:0;font-weight:var(--rhc-text-font-weight-semi-bold);margin-block-end:var(--rhc-space-max-md)}
|
||||
app-form-field + app-form-field{display:block;margin-block-start:var(--rhc-space-max-lg)}
|
||||
`],
|
||||
template: `
|
||||
<fieldset class="utrecht-form-fieldset utrecht-form-fieldset--html-fieldset">
|
||||
<legend class="utrecht-form-fieldset__legend utrecht-form-fieldset__legend--html-legend">{{ legend() }}</legend>
|
||||
<app-form-field label="Straat en huisnummer" [fieldId]="idPrefix() + '-straat'" [error]="errors().straat">
|
||||
<app-form-field label="Straat en huisnummer" [fieldId]="idPrefix() + '-straat'" required [error]="errors().straat">
|
||||
<app-text-input [inputId]="idPrefix() + '-straat'" [invalid]="!!errors().straat"
|
||||
[ngModel]="value().straat" (ngModelChange)="fieldChange.emit({ key: 'straat', value: $event })"
|
||||
name="straat" [ngModelOptions]="{ standalone: true }" />
|
||||
</app-form-field>
|
||||
<app-form-field label="Postcode" [fieldId]="idPrefix() + '-postcode'" [error]="errors().postcode">
|
||||
<app-form-field label="Postcode" [fieldId]="idPrefix() + '-postcode'" required [error]="errors().postcode">
|
||||
<app-text-input [inputId]="idPrefix() + '-postcode'" [invalid]="!!errors().postcode"
|
||||
[ngModel]="value().postcode" (ngModelChange)="fieldChange.emit({ key: 'postcode', value: $event })"
|
||||
name="postcode" placeholder="1234 AB" [ngModelOptions]="{ standalone: true }" />
|
||||
</app-form-field>
|
||||
<app-form-field label="Woonplaats" [fieldId]="idPrefix() + '-woonplaats'" [error]="errors().woonplaats">
|
||||
<app-form-field label="Woonplaats" [fieldId]="idPrefix() + '-woonplaats'" required [error]="errors().woonplaats">
|
||||
<app-text-input [inputId]="idPrefix() + '-woonplaats'" [invalid]="!!errors().woonplaats"
|
||||
[ngModel]="value().woonplaats" (ngModelChange)="fieldChange.emit({ key: 'woonplaats', value: $event })"
|
||||
name="woonplaats" [ngModelOptions]="{ standalone: true }" />
|
||||
|
||||
@@ -22,12 +22,12 @@ import { ApiClient } from '@shared/infrastructure/api-client';
|
||||
<app-alert type="ok">
|
||||
Uw adreswijziging is ontvangen (referentie {{ referentie() }}). U ontvangt binnen 5 werkdagen bericht.
|
||||
</app-alert>
|
||||
<div style="margin-top:1rem">
|
||||
<div class="app-section">
|
||||
<app-button variant="secondary" (click)="dispatch({ tag: 'Reset' })">Nieuwe wijziging doorgeven</app-button>
|
||||
</div>
|
||||
} @else {
|
||||
<app-heading [level]="2">Adreswijziging doorgeven</app-heading>
|
||||
<form (ngSubmit)="onSubmit()" style="max-width:28rem">
|
||||
<form (ngSubmit)="onSubmit()" class="app-form app-form-panel app-section">
|
||||
<app-address-fields
|
||||
idPrefix="cr"
|
||||
[value]="adres()"
|
||||
@@ -35,14 +35,12 @@ import { ApiClient } from '@shared/infrastructure/api-client';
|
||||
(fieldChange)="dispatch({ tag: 'SetField', key: $event.key, value: $event.value })" />
|
||||
|
||||
@if (failedError()) {
|
||||
<div style="margin-top:1rem"><app-alert type="error">Het indienen is niet gelukt: {{ failedError() }}</app-alert></div>
|
||||
<app-alert type="error">Het indienen is niet gelukt: {{ failedError() }}</app-alert>
|
||||
}
|
||||
|
||||
<div style="margin-top:1rem">
|
||||
<app-button type="submit" variant="primary" [disabled]="state().tag === 'Submitting'">
|
||||
{{ state().tag === 'Submitting' ? 'Bezig met indienen…' : 'Wijziging indienen' }}
|
||||
</app-button>
|
||||
</div>
|
||||
<app-button type="submit" variant="primary" [disabled]="state().tag === 'Submitting'">
|
||||
{{ state().tag === 'Submitting' ? 'Bezig met indienen…' : 'Wijziging indienen' }}
|
||||
</app-button>
|
||||
</form>
|
||||
}
|
||||
`,
|
||||
|
||||
@@ -5,82 +5,122 @@ import { LinkComponent } from '@shared/ui/link/link.component';
|
||||
import { AlertComponent } from '@shared/ui/alert/alert.component';
|
||||
import { SkeletonComponent } from '@shared/ui/skeleton/skeleton.component';
|
||||
import { DataRowComponent } from '@shared/ui/data-row/data-row.component';
|
||||
import { CardComponent } from '@shared/ui/card/card.component';
|
||||
import { TaskListComponent } from '@shared/ui/task-list/task-list.component';
|
||||
import { SideNavComponent, NavItem } from '@shared/layout/side-nav/side-nav.component';
|
||||
import { ASYNC } from '@shared/ui/async/async.component';
|
||||
import { RegistrationSummaryComponent } from '@registratie/ui/registration-summary/registration-summary.component';
|
||||
import { RegistrationTableComponent } from '@registratie/ui/registration-table/registration-table.component';
|
||||
import { BigProfileStore } from '@registratie/application/big-profile.store';
|
||||
import { Registration } from '@registratie/domain/registration';
|
||||
import { tasksFromProfile } from '@registratie/domain/tasks';
|
||||
|
||||
/** Page: "Mijn overzicht" — the portal home, following the NL Design System
|
||||
"Mijn omgeving" pattern (side nav + "Wat moet ik regelen" + "Mijn zaken"). */
|
||||
@Component({
|
||||
selector: 'app-dashboard-page',
|
||||
imports: [
|
||||
PageShellComponent, HeadingComponent, LinkComponent, AlertComponent, SkeletonComponent,
|
||||
DataRowComponent, ...ASYNC, RegistrationSummaryComponent, RegistrationTableComponent,
|
||||
DataRowComponent, CardComponent, TaskListComponent, SideNavComponent, ...ASYNC,
|
||||
RegistrationSummaryComponent, RegistrationTableComponent,
|
||||
],
|
||||
template: `
|
||||
<app-page-shell heading="Mijn BIG-registratie">
|
||||
<div class="app-stack">
|
||||
@if (store.pendingHerregistratie()) {
|
||||
<app-alert type="info">Uw herregistratie-aanvraag is in behandeling.</app-alert>
|
||||
}
|
||||
<app-page-shell heading="Mijn overzicht" intro="Welkom in uw persoonlijke omgeving van het BIG-register. Hier ziet u uw registratie en regelt u uw zaken.">
|
||||
<div class="app-overview">
|
||||
<app-side-nav [items]="nav" />
|
||||
|
||||
<!-- ONE state for two combined services (BIG-register + BRP). -->
|
||||
<app-async [data]="store.profile()">
|
||||
<ng-template appAsyncLoaded let-p>
|
||||
<app-registration-summary [reg]="$any(p).registration" />
|
||||
<div class="rhc-card rhc-card--default app-section">
|
||||
<app-heading [level]="2">Persoonsgegevens (BRP)</app-heading>
|
||||
<dl class="rhc-data-summary rhc-data-summary--row">
|
||||
<app-data-row key="Straat" [value]="$any(p).person.adres.straat" />
|
||||
<app-data-row key="Postcode" [value]="$any(p).person.adres.postcode" />
|
||||
<app-data-row key="Woonplaats" [value]="$any(p).person.adres.woonplaats" />
|
||||
</dl>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template appAsyncLoading>
|
||||
<app-skeleton height="2.5rem" [count]="6" />
|
||||
</ng-template>
|
||||
</app-async>
|
||||
<div class="app-stack">
|
||||
@if (store.pendingHerregistratie()) {
|
||||
<app-alert type="info">Uw herregistratie-aanvraag is in behandeling.</app-alert>
|
||||
}
|
||||
|
||||
<section>
|
||||
<app-heading [level]="2">Specialismen en aantekeningen</app-heading>
|
||||
<app-async [data]="store.aantekeningen()">
|
||||
<ng-template appAsyncLoaded let-r>
|
||||
<app-registration-table [rows]="$any(r)" />
|
||||
<app-async [data]="store.profile()">
|
||||
<ng-template appAsyncLoaded let-p>
|
||||
@let tasks = tasksFor($any(p).registration);
|
||||
|
||||
<section>
|
||||
<app-heading [level]="2">Wat moet ik regelen</app-heading>
|
||||
@if (tasks.length) {
|
||||
<app-task-list class="app-section" [tasks]="tasks" />
|
||||
} @else {
|
||||
<p class="rhc-paragraph app-text-subtle">U heeft op dit moment niets openstaan.</p>
|
||||
}
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<app-heading [level]="2">Mijn registratie</app-heading>
|
||||
<div class="app-section">
|
||||
<app-registration-summary [reg]="$any(p).registration" />
|
||||
</div>
|
||||
<app-card class="app-section" heading="Persoonsgegevens (BRP)">
|
||||
<dl class="rhc-data-summary rhc-data-summary--row">
|
||||
<app-data-row key="Straat" [value]="$any(p).person.adres.straat" />
|
||||
<app-data-row key="Postcode" [value]="$any(p).person.adres.postcode" />
|
||||
<app-data-row key="Woonplaats" [value]="$any(p).person.adres.woonplaats" />
|
||||
</dl>
|
||||
</app-card>
|
||||
</section>
|
||||
</ng-template>
|
||||
<ng-template appAsyncLoading>
|
||||
<app-skeleton height="2.5rem" [count]="3" />
|
||||
</ng-template>
|
||||
<ng-template appAsyncEmpty>
|
||||
<p class="rhc-paragraph">U heeft nog geen specialismen of aantekeningen.</p>
|
||||
<app-skeleton height="2.5rem" [count]="6" />
|
||||
</ng-template>
|
||||
</app-async>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<app-heading [level]="2">Wat wilt u doen?</app-heading>
|
||||
<ul class="app-card-grid app-section">
|
||||
@for (a of acties; track a.to) {
|
||||
<li class="rhc-card rhc-card--default">
|
||||
<app-heading [level]="3">{{ a.titel }}</app-heading>
|
||||
<p class="rhc-paragraph">{{ a.tekst }}</p>
|
||||
<app-link [to]="a.to">{{ a.actie }} →</app-link>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</section>
|
||||
<section>
|
||||
<app-heading [level]="2">Specialismen en aantekeningen</app-heading>
|
||||
<div class="app-section">
|
||||
<app-async [data]="store.aantekeningen()">
|
||||
<ng-template appAsyncLoaded let-r>
|
||||
<app-registration-table [rows]="$any(r)" />
|
||||
</ng-template>
|
||||
<ng-template appAsyncLoading>
|
||||
<app-skeleton height="2.5rem" [count]="3" />
|
||||
</ng-template>
|
||||
<ng-template appAsyncEmpty>
|
||||
<p class="rhc-paragraph app-text-subtle">U heeft nog geen specialismen of aantekeningen.</p>
|
||||
</ng-template>
|
||||
</app-async>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<app-heading [level]="2">Wat wilt u doen?</app-heading>
|
||||
<ul class="app-card-grid app-section">
|
||||
@for (a of acties; track a.to) {
|
||||
<li>
|
||||
<app-card [heading]="a.titel">
|
||||
<p class="rhc-paragraph">{{ a.tekst }}</p>
|
||||
<app-link [to]="a.to">{{ a.actie }} →</app-link>
|
||||
</app-card>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</app-page-shell>
|
||||
`,
|
||||
})
|
||||
export class DashboardPage {
|
||||
protected store = inject(BigProfileStore);
|
||||
private readonly today = new Date();
|
||||
|
||||
/** Primary actions, rendered as an accessible card grid. */
|
||||
protected tasksFor(reg: Registration) {
|
||||
return tasksFromProfile(reg, this.today);
|
||||
}
|
||||
|
||||
/** Portal sections (navigation, not actions). */
|
||||
protected readonly nav: NavItem[] = [
|
||||
{ label: 'Overzicht', to: '/dashboard' },
|
||||
{ label: 'Mijn gegevens', to: '/registratie' },
|
||||
{ label: 'Herregistratie', to: '/herregistratie' },
|
||||
{ label: 'Inschrijven', to: '/registreren' },
|
||||
];
|
||||
|
||||
/** Primary transactional actions, as a card grid. */
|
||||
protected readonly acties = [
|
||||
{ to: '/registreren', titel: 'Inschrijven', tekst: 'Schrijf u in in het BIG-register via de registratiewizard.', actie: 'Start inschrijving' },
|
||||
{ to: '/registratie', titel: 'Mijn gegevens', tekst: 'Bekijk uw gegevens of geef een wijziging door.', actie: 'Bekijk gegevens' },
|
||||
{ to: '/herregistratie', titel: 'Herregistratie', tekst: 'Vraag uw herregistratie aan.', actie: 'Vraag aan' },
|
||||
{ to: '/intake', titel: 'Herregistratie-intake', tekst: 'Vragenlijst met vertakkingen.', actie: 'Start intake' },
|
||||
{ to: '/concepts', titel: 'Functionele patronen', tekst: 'Onmogelijke toestanden onmogelijk maken.', actie: 'Bekijk patronen' },
|
||||
{ to: '/herregistratie', titel: 'Herregistratie aanvragen', tekst: 'Verleng uw registratie voor de komende periode.', actie: 'Vraag aan' },
|
||||
{ to: '/registratie', titel: 'Gegevens wijzigen', tekst: 'Bekijk uw gegevens of geef een wijziging door.', actie: 'Bekijk gegevens' },
|
||||
];
|
||||
}
|
||||
|
||||
@@ -83,12 +83,12 @@ const HANDMATIG = '__handmatig__'; // sentinel option: "my diploma isn't listed"
|
||||
[value]="{ straat: draft().straat ?? '', postcode: draft().postcode ?? '', woonplaats: draft().woonplaats ?? '' }"
|
||||
[errors]="{ straat: err('straat'), postcode: err('postcode'), woonplaats: err('woonplaats') }"
|
||||
(fieldChange)="set($event.key, $event.value)" />
|
||||
<app-form-field label="Hoe wilt u correspondentie ontvangen?" fieldId="correspondentie" [error]="err('correspondentie')">
|
||||
<app-form-field label="Hoe wilt u correspondentie ontvangen?" fieldId="correspondentie" required [error]="err('correspondentie')">
|
||||
<app-radio-group name="correspondentie" [options]="kanalen" [invalid]="!!err('correspondentie')"
|
||||
[ngModel]="draft().correspondentie ?? ''" (ngModelChange)="setKanaal($event)" />
|
||||
</app-form-field>
|
||||
@if (draft().correspondentie === 'email') {
|
||||
<app-form-field label="E-mailadres" fieldId="email" [error]="err('email')">
|
||||
<app-form-field label="E-mailadres" fieldId="email" required [error]="err('email')">
|
||||
<app-text-input inputId="email" type="email" [invalid]="!!err('email')" [ngModel]="draft().email ?? ''" (ngModelChange)="set('email', $event)" name="email" placeholder="naam@voorbeeld.nl" />
|
||||
</app-form-field>
|
||||
}
|
||||
@@ -97,7 +97,7 @@ const HANDMATIG = '__handmatig__'; // sentinel option: "my diploma isn't listed"
|
||||
@case ('beroep') {
|
||||
<app-async [data]="lookupRd()">
|
||||
<ng-template appAsyncLoaded let-data>
|
||||
<app-form-field label="Kies het diploma waarmee u zich wilt registreren" fieldId="diploma" [error]="err('diploma')">
|
||||
<app-form-field label="Kies het diploma waarmee u zich wilt registreren" fieldId="diploma" required [error]="err('diploma')">
|
||||
<app-radio-group name="diploma" [options]="diplomaOptions($any(data))" [invalid]="!!err('diploma')"
|
||||
[ngModel]="diplomaKeuze()" (ngModelChange)="onDiplomaKeuze($any(data), $event)" />
|
||||
</app-form-field>
|
||||
|
||||
@@ -11,8 +11,7 @@ import { RegistratieWizardComponent } from '@registratie/ui/registratie-wizard/r
|
||||
template: `
|
||||
<app-page-shell
|
||||
heading="Inschrijven in het BIG-register"
|
||||
backLink="/dashboard"
|
||||
[breadcrumb]="[{ label: 'Mijn omgeving', link: '/dashboard' }, { label: 'Inschrijven in het BIG-register' }]">
|
||||
backLink="/dashboard">
|
||||
<app-alert type="info">
|
||||
In drie stappen schrijft u zich in: uw adres en correspondentievoorkeur, het
|
||||
diploma waarmee u zich registreert, en een controle. Uw gegevens blijven bewaard
|
||||
|
||||
@@ -23,7 +23,7 @@ import { BigProfileStore } from '@registratie/application/big-profile.store';
|
||||
</ng-template>
|
||||
</app-async>
|
||||
|
||||
<div style="margin-top:2rem">
|
||||
<div class="app-section">
|
||||
<app-change-request-form />
|
||||
</div>
|
||||
</app-page-shell>
|
||||
|
||||
@@ -4,13 +4,14 @@ import { Registration } from '@registratie/domain/registration';
|
||||
import { statusColor, statusLabel } from '@registratie/domain/registration.policy';
|
||||
import { DataRowComponent } from '@shared/ui/data-row/data-row.component';
|
||||
import { StatusBadgeComponent } from '@shared/ui/status-badge/status-badge.component';
|
||||
import { CardComponent } from '@shared/ui/card/card.component';
|
||||
|
||||
/** Organism: registration summary card. Composes data-row molecules + status-badge atom. */
|
||||
@Component({
|
||||
selector: 'app-registration-summary',
|
||||
imports: [DatePipe, DataRowComponent, StatusBadgeComponent],
|
||||
imports: [DatePipe, DataRowComponent, StatusBadgeComponent, CardComponent],
|
||||
template: `
|
||||
<div class="rhc-card rhc-card--default">
|
||||
<app-card>
|
||||
<dl class="rhc-data-summary rhc-data-summary--row">
|
||||
<app-data-row key="BIG-nummer" [value]="reg().bigNummer" />
|
||||
<app-data-row key="Naam" [value]="reg().naam" />
|
||||
@@ -34,7 +35,7 @@ import { StatusBadgeComponent } from '@shared/ui/status-badge/status-badge.compo
|
||||
}
|
||||
}
|
||||
</dl>
|
||||
</div>
|
||||
</app-card>
|
||||
`,
|
||||
})
|
||||
export class RegistrationSummaryComponent {
|
||||
|
||||
Reference in New Issue
Block a user