Files
strangler-fig-demo/portal-frontend/src/app/case-detail/edit-applicant-details/edit-applicant-details.ts
T
ehoandClaude Haiku 4.5 5f22156e6d feat(ownership): add take-ownership preflight endpoint
Enables dry-run checking before committing to case adoption. The preflight
shares the same side-effect-free checks (steps 1–3) as the real take-ownership
handler, so it cannot drift from what will actually succeed. Returns the same
status codes and error shapes as the real endpoint (200 with wouldSucceed:true,
or 409/404/422 if it would fail).

Portal renders a "Vooraf controleren" button for legacy cases, surfaced through
the existing actions block pattern. Confirmed in smoke.sh with two cases: one
where preflight predicts success (and writes nothing), one where it predicts
a named invariant failure (matching what the real call reproduces).

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-08-01 09:15:18 +02:00

101 lines
3.1 KiB
TypeScript

import { HttpErrorResponse } from '@angular/common/http';
import { Component, EventEmitter, Input, OnInit, Output, inject, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ErrorsResponse, InvariantViolationResponse, MessageResponse } from '../../shared/api-error.types';
import { CaseDetailService } from '../case-detail.service';
import { ActionLink, ApplicantDetailsRequest } from '../case-detail.types';
@Component({
selector: 'app-edit-applicant-details',
imports: [FormsModule],
templateUrl: './edit-applicant-details.html',
styleUrl: './edit-applicant-details.css',
})
export class EditApplicantDetails implements OnInit {
private readonly caseDetailService = inject(CaseDetailService);
@Input({ required: true }) actionLink!: ActionLink;
@Input({ required: true }) initial!: ApplicantDetailsRequest;
@Output() readonly saved = new EventEmitter<void>();
surname = '';
initials = '';
street = '';
number = '';
postalCode = '';
city = '';
email = '';
phone = '';
preferredChannel = '';
fieldErrors: Record<string, string> = {};
banner: string | null = null;
readonly saving = signal(false);
get isWriteThrough(): boolean {
return this.actionLink.mode === 'writeThrough';
}
ngOnInit(): void {
this.surname = this.initial.surname;
this.initials = this.initial.initials;
this.street = this.initial.address?.street ?? '';
this.number = this.initial.address?.number ?? '';
this.postalCode = this.initial.address?.postalCode ?? '';
this.city = this.initial.address?.city ?? '';
this.email = this.initial.email ?? '';
this.phone = this.initial.phone ?? '';
this.preferredChannel = this.initial.preferredChannel;
}
submit(): void {
this.fieldErrors = {};
this.banner = null;
this.saving.set(true);
const body: ApplicantDetailsRequest = {
surname: this.surname,
initials: this.initials,
address: this.street ? { street: this.street, number: this.number, postalCode: this.postalCode, city: this.city } : null,
email: this.email || null,
phone: this.phone || null,
preferredChannel: this.preferredChannel,
};
this.caseDetailService.updateDetails(this.actionLink.href, body).subscribe({
next: () => {
this.saving.set(false);
this.saved.emit();
},
error: (error: HttpErrorResponse) => {
this.saving.set(false);
this.handleError(error);
},
});
}
private handleError(error: HttpErrorResponse): void {
switch (error.status) {
case 400: {
const body = error.error as ErrorsResponse;
this.fieldErrors = Object.fromEntries(body.errors.map((fieldError) => [fieldError.field, fieldError.message]));
break;
}
case 422: {
const body = error.error as InvariantViolationResponse;
this.banner = `${body.invariant}: ${body.message}`;
break;
}
case 409: {
const body = error.error as MessageResponse;
this.banner = body.message;
break;
}
default:
this.banner = 'Onbekende fout bij opslaan.';
}
}
}