refactor: WizardStatus to a payload-carrying WizardPhase (RD-10)
The wizard shell took two inputs to say one thing: a flat WizardStatus string and a separate errorMessage input. Each wizard needed three computeds (failedError, errorMessage, shellStatus) to take the state apart and put it back together for the shell. WizardPhase replaces both inputs with one discriminated union. Its Failed variant carries the message directly, so no data travels through a second channel. Each wizard now maps its own tags onto WizardPhase in one computed, composing the localized failure prefix at the same spot errorMessage did before. The three machines and their own vocabulary (Editing/Answering/Invullen, Indienen/Ingediend/Mislukt) are unchanged; only the shell's input contract changes. The shell reads the Failed message via the existing whenTag helper, because @switch cannot narrow a union in an Angular template. Both $localize ids (wizard.indienenMislukt, regWizard.indienenMislukt) keep byte-identical source text, so no locale file changes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,19 @@
|
||||
import { Component, ElementRef, effect, input, output, untracked, viewChild } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
ElementRef,
|
||||
computed,
|
||||
effect,
|
||||
input,
|
||||
output,
|
||||
untracked,
|
||||
viewChild,
|
||||
} from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
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 { StepperComponent } from '@shared/ui/stepper/stepper.component';
|
||||
import { whenTag } from '@shared/kernel/fp';
|
||||
|
||||
/** CIBG procesnavigatie primary-button copy for a non-final step: "Naar stap 2 - Werk".
|
||||
Shared so every wizard's `primaryLabel` reads the same way. */
|
||||
@@ -16,7 +26,13 @@ export interface WizardError {
|
||||
readonly message: string;
|
||||
}
|
||||
|
||||
export type WizardStatus = 'editing' | 'submitting' | 'submitted' | 'failed';
|
||||
/** The wizard shell's lifecycle union. The `Failed` variant carries the localized
|
||||
message intact, so the shell needs no separate input to say what went wrong. */
|
||||
export type WizardPhase =
|
||||
| { tag: 'Editing' }
|
||||
| { tag: 'Submitting' }
|
||||
| { tag: 'Submitted' }
|
||||
| { tag: 'Failed'; message: string };
|
||||
|
||||
/**
|
||||
* Template: the canonical shell every wizard renders into, so they cannot drift.
|
||||
@@ -52,8 +68,8 @@ export type WizardStatus = 'editing' | 'submitting' | 'submitted' | 'failed';
|
||||
`,
|
||||
],
|
||||
template: `
|
||||
@switch (status()) {
|
||||
@case ('editing') {
|
||||
@switch (phase().tag) {
|
||||
@case ('Editing') {
|
||||
<app-stepper
|
||||
class="app-section"
|
||||
[steps]="steps()"
|
||||
@@ -122,14 +138,14 @@ export type WizardStatus = 'editing' | 'submitting' | 'submitted' | 'failed';
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
@case ('submitting') {
|
||||
@case ('Submitting') {
|
||||
<app-spinner /> <span>{{ submittingLabel() }}</span>
|
||||
}
|
||||
@case ('submitted') {
|
||||
@case ('Submitted') {
|
||||
<ng-content select="[wizardSuccess]" />
|
||||
}
|
||||
@case ('failed') {
|
||||
<app-alert type="error">{{ errorMessage() }}</app-alert>
|
||||
@case ('Failed') {
|
||||
<app-alert type="error">{{ failedMessage() }}</app-alert>
|
||||
<div class="app-section">
|
||||
<app-button variant="secondary" (click)="retry.emit()" i18n="@@wizard.opnieuwProberen"
|
||||
>Opnieuw proberen</app-button
|
||||
@@ -145,13 +161,16 @@ export class WizardShellComponent {
|
||||
stepTitle = input.required<string>();
|
||||
/** Overall process name, shown above the step title (e.g. "Herregistratie aanvragen"). */
|
||||
processName = input('');
|
||||
status = input.required<WizardStatus>();
|
||||
phase = input.required<WizardPhase>();
|
||||
primaryLabel = input.required<string>();
|
||||
canGoBack = input(false);
|
||||
errors = input<readonly WizardError[]>([]);
|
||||
errorMessage = input('');
|
||||
submittingLabel = input($localize`:@@wizard.submitting:Aanvraag wordt verwerkt…`);
|
||||
|
||||
/** The `Failed` message, or '' otherwise. `@switch` can't narrow a union in a
|
||||
template, so the narrowing happens here via the shared `whenTag` helper. */
|
||||
protected failedMessage = computed(() => whenTag(this.phase(), 'Failed')?.message ?? '');
|
||||
|
||||
primary = output<void>();
|
||||
back = output<void>();
|
||||
cancel = output<void>();
|
||||
|
||||
@@ -8,8 +8,8 @@ const meta: Meta<WizardShellComponent> = {
|
||||
props: args,
|
||||
template: `
|
||||
<app-wizard-shell
|
||||
[steps]="steps" [current]="current" [stepTitle]="stepTitle" [processName]="processName" [status]="status"
|
||||
[primaryLabel]="primaryLabel" [canGoBack]="canGoBack" [errors]="errors" [errorMessage]="errorMessage"
|
||||
[steps]="steps" [current]="current" [stepTitle]="stepTitle" [processName]="processName" [phase]="phase"
|
||||
[primaryLabel]="primaryLabel" [canGoBack]="canGoBack" [errors]="errors"
|
||||
(goToStep)="goToStep($event)">
|
||||
<p class="rhc-paragraph">Voorbeeld-stapinhoud (de stapvelden worden hier geprojecteerd).</p>
|
||||
<div wizardSuccess><p class="rhc-paragraph">Uw aanvraag is ontvangen.</p></div>
|
||||
@@ -36,23 +36,25 @@ const base = {
|
||||
primaryLabel: 'Volgende',
|
||||
canGoBack: true,
|
||||
errors: [],
|
||||
errorMessage: '',
|
||||
goToStep: () => {},
|
||||
};
|
||||
|
||||
export const Editing: Story = { args: { ...base, status: 'editing' } };
|
||||
export const Editing: Story = { args: { ...base, phase: { tag: 'Editing' } } };
|
||||
export const EditingMetFouten: Story = {
|
||||
args: {
|
||||
...base,
|
||||
status: 'editing',
|
||||
phase: { tag: 'Editing' },
|
||||
errors: [
|
||||
{ id: 'uren', message: 'Vul het aantal gewerkte uren in.' },
|
||||
{ id: 'diploma', message: 'Kies een diploma.' },
|
||||
],
|
||||
},
|
||||
};
|
||||
export const Submitting: Story = { args: { ...base, status: 'submitting' } };
|
||||
export const Submitted: Story = { args: { ...base, status: 'submitted' } };
|
||||
export const Submitting: Story = { args: { ...base, phase: { tag: 'Submitting' } } };
|
||||
export const Submitted: Story = { args: { ...base, phase: { tag: 'Submitted' } } };
|
||||
export const Failed: Story = {
|
||||
args: { ...base, status: 'failed', errorMessage: 'Het indienen is niet gelukt: netwerkfout.' },
|
||||
args: {
|
||||
...base,
|
||||
phase: { tag: 'Failed', message: 'Het indienen is niet gelukt: netwerkfout.' },
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user