Step 2 (code quality): dedup + stop FE recomputing a server rule
- H1: tasksFromProfile takes the server's eligibleForHerregistratie decision
instead of recomputing isHerregistratieEligible — the FE renders the rule,
doesn't own it (ADR-0001). Policy reference impl kept for tests.
- M1: one shared runSubmit(fn, fallback) wrapper; the 4 submit-* commands keep
only their payload mapping. +spec.
- M2: whenTag() kernel helper removes 10 repeated `as Extract<U,{tag}>` casts
across the wizard/form components.
M4 (shared JA_NEE) folded into the upcoming i18n pass (clean dedup needs
$localize labels to sit in shared without breaking the English-shared-UI rule).
L1 already resolved by the restyle commit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,18 +1,15 @@
|
||||
import { Result, ok, err } from '@shared/kernel/fp';
|
||||
import { Result } from '@shared/kernel/fp';
|
||||
import { Valid } from '../domain/herregistratie.machine';
|
||||
import { ApiClient } from '@shared/infrastructure/api-client';
|
||||
import { problemDetail } from '@shared/infrastructure/api-error';
|
||||
import { runSubmit, SUBMIT_FAILED } from '@shared/application/submit';
|
||||
|
||||
/**
|
||||
* Command: POST a herregistratie application to the backend (`/api/herregistraties`).
|
||||
* The "uren must be > 0" rule now lives server-side; the backend returns a 422
|
||||
* ProblemDetails on rejection, which we surface as the error.
|
||||
* The "uren must be > 0" rule lives server-side; a 422 ProblemDetails is surfaced
|
||||
* as the error by `runSubmit`.
|
||||
*/
|
||||
export async function submitHerregistratie(client: ApiClient, data: Valid): Promise<Result<string, void>> {
|
||||
try {
|
||||
export function submitHerregistratie(client: ApiClient, data: Valid): Promise<Result<string, void>> {
|
||||
return runSubmit<void>(async () => {
|
||||
await client.herregistraties({ uren: data.uren });
|
||||
return ok(undefined);
|
||||
} catch (e) {
|
||||
return err(problemDetail(e, 'Het indienen is niet gelukt. Probeer het later opnieuw.'));
|
||||
}
|
||||
}, SUBMIT_FAILED);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
import { Result, ok, err } from '@shared/kernel/fp';
|
||||
import { Result } from '@shared/kernel/fp';
|
||||
import { ValidIntake } from '../domain/intake.machine';
|
||||
import { ApiClient } from '@shared/infrastructure/api-client';
|
||||
import { problemDetail } from '@shared/infrastructure/api-error';
|
||||
import { runSubmit, SUBMIT_FAILED } from '@shared/application/submit';
|
||||
|
||||
/**
|
||||
* Command: POST the intake questionnaire to the backend (`/api/intakes`). The
|
||||
* "uren must be > 0" rule now lives server-side; the backend returns a 422
|
||||
* ProblemDetails on rejection, which we surface as the error.
|
||||
* "uren must be > 0" rule lives server-side; a 422 ProblemDetails is surfaced as
|
||||
* the error by `runSubmit`.
|
||||
*/
|
||||
export async function submitIntake(client: ApiClient, data: ValidIntake): Promise<Result<string, void>> {
|
||||
try {
|
||||
export function submitIntake(client: ApiClient, data: ValidIntake): Promise<Result<string, void>> {
|
||||
return runSubmit<void>(async () => {
|
||||
await client.intakes({ uren: data.uren });
|
||||
return ok(undefined);
|
||||
} catch (e) {
|
||||
return err(problemDetail(e, 'Het indienen is niet gelukt. Probeer het later opnieuw.'));
|
||||
}
|
||||
}, SUBMIT_FAILED);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { TextInputComponent } from '@shared/ui/text-input/text-input.component';
|
||||
import { AlertComponent } from '@shared/ui/alert/alert.component';
|
||||
import { WizardShellComponent, WizardError, WizardStatus } from '@shared/layout/wizard-shell/wizard-shell.component';
|
||||
import { createStore } from '@shared/application/store';
|
||||
import { whenTag } from '@shared/kernel/fp';
|
||||
import { BigProfileStore } from '@registratie/application/big-profile.store';
|
||||
import { WizardState, WizardMsg, Draft, initial, reduce } from '@herregistratie/domain/herregistratie.machine';
|
||||
import { submitHerregistratie } from '@herregistratie/application/submit-herregistratie';
|
||||
@@ -71,13 +72,13 @@ export class HerregistratieWizardComponent {
|
||||
readonly stepLabels = ['Werkervaring', 'Nascholing'];
|
||||
private stepTitles = ['Werkervaring (afgelopen 5 jaar)', 'Nascholing'];
|
||||
|
||||
private editing = computed(() => (this.state().tag === 'Editing' ? (this.state() as Extract<WizardState, { tag: 'Editing' }>) : null));
|
||||
private editing = computed(() => whenTag(this.state(), 'Editing'));
|
||||
protected step = computed(() => this.editing()?.step ?? 1);
|
||||
protected draft = computed<Draft>(() => this.editing()?.draft ?? { uren: '', jaren: '', punten: '' });
|
||||
protected errUren = computed(() => this.editing()?.errors.uren ?? '');
|
||||
protected errJaren = computed(() => this.editing()?.errors.jaren ?? '');
|
||||
protected errPunten = computed(() => this.editing()?.errors.punten ?? '');
|
||||
protected failedError = computed(() => (this.state().tag === 'Failed' ? (this.state() as Extract<WizardState, { tag: 'Failed' }>).error : ''));
|
||||
protected failedError = computed(() => whenTag(this.state(), 'Failed')?.error ?? '');
|
||||
|
||||
// --- Presentational wiring for the shared wizard shell ---------------------
|
||||
protected stepTitle = computed(() => this.stepTitles[this.step() - 1]);
|
||||
|
||||
@@ -8,6 +8,7 @@ import { AlertComponent } from '@shared/ui/alert/alert.component';
|
||||
import { DataRowComponent } from '@shared/ui/data-row/data-row.component';
|
||||
import { WizardShellComponent, WizardError, WizardStatus } from '@shared/layout/wizard-shell/wizard-shell.component';
|
||||
import { createStore } from '@shared/application/store';
|
||||
import { whenTag } from '@shared/kernel/fp';
|
||||
import { BigProfileStore } from '@registratie/application/big-profile.store';
|
||||
import {
|
||||
IntakeState,
|
||||
@@ -126,7 +127,7 @@ export class IntakeWizardComponent {
|
||||
readonly state = this.store.model;
|
||||
readonly dispatch = this.store.dispatch;
|
||||
|
||||
private answering = computed(() => (this.state().tag === 'Answering' ? (this.state() as Extract<IntakeState, { tag: 'Answering' }>) : null));
|
||||
private answering = computed(() => whenTag(this.state(), 'Answering'));
|
||||
/** Public so the showcase can render the (fixed) step list next to the wizard. */
|
||||
readonly steps = STEPS;
|
||||
protected cursor = computed(() => this.answering()?.cursor ?? 0);
|
||||
@@ -136,7 +137,7 @@ export class IntakeWizardComponent {
|
||||
protected scholingThreshold = computed(() => this.answering()?.scholingThreshold ?? SCHOLING_THRESHOLD_DEFAULT);
|
||||
/** Whether the inline scholing question is shown (and required) in the 'werk' step. */
|
||||
protected scholingZichtbaar = computed(() => lageUren(this.answers(), this.scholingThreshold()));
|
||||
protected failedError = computed(() => (this.state().tag === 'Failed' ? (this.state() as Extract<IntakeState, { tag: 'Failed' }>).error : ''));
|
||||
protected failedError = computed(() => whenTag(this.state(), 'Failed')?.error ?? '');
|
||||
|
||||
// --- Presentational wiring for the shared wizard shell ---------------------
|
||||
readonly stepLabels = ['Buitenland', 'Werk', 'Controle'];
|
||||
|
||||
Reference in New Issue
Block a user