Align landlord profile validation (#47)
This commit is contained in:
1 parent
d1cdc6eaeb
commit
66fa95dabd
13 files changed
+462
-32
No files matched your search
+2
@@ -17,6 +17,8 @@
|
||||
[profile]="profile()"
|
||||
[saving]="saving()"
|
||||
[saveError]="error()"
|
||||
[saveFieldErrors]="saveFieldErrors()"
|
||||
(fieldChanged)="clearSaveFieldError($event)"
|
||||
(saveRequested)="save($event)"
|
||||
>
|
||||
<button
|
||||
|
||||
+37
@@ -101,6 +101,43 @@ describe('LandlordProfileCompletionDialog', () => {
|
||||
expect(fixture.nativeElement.textContent).toContain('Réessayer');
|
||||
});
|
||||
|
||||
it('keeps a structured save error on the field and clears it when corrected', () => {
|
||||
profile.updateProfile.mockReturnValue(
|
||||
throwError(
|
||||
() =>
|
||||
new HttpErrorResponse({
|
||||
error: {
|
||||
code: 'validation_failed',
|
||||
errors: [{ field: 'phoneValid', code: 'assert_true' }],
|
||||
},
|
||||
status: 400,
|
||||
}),
|
||||
),
|
||||
);
|
||||
const fixture = TestBed.createComponent(LandlordProfileCompletionDialog);
|
||||
const component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
|
||||
component.save({
|
||||
firstName: 'Camille',
|
||||
lastName: 'Martin',
|
||||
email: 'camille@example.com',
|
||||
phone: '1234',
|
||||
address: '1 rue A',
|
||||
});
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.nativeElement.textContent).toContain(
|
||||
'Saisissez un numéro de téléphone valide.',
|
||||
);
|
||||
component.clearSaveFieldError('phone');
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.nativeElement.textContent).not.toContain(
|
||||
'Saisissez un numéro de téléphone valide.',
|
||||
);
|
||||
});
|
||||
|
||||
it('retries an initial profile load without releasing the dialog', () => {
|
||||
data.profile = null;
|
||||
profile.getProfile.mockReturnValue(
|
||||
|
||||
+15
-6
@@ -9,7 +9,7 @@ import {
|
||||
LandlordProfileResponse,
|
||||
LandlordProfileService,
|
||||
} from '../../services/landlord-profile.service';
|
||||
import { httpErrorMessage } from '../../services/http-error-message';
|
||||
import { FieldValidationError, saveFailure } from '../../services/http-error-message';
|
||||
import { SessionService } from '../../services/session.service';
|
||||
|
||||
export interface LandlordProfileCompletionDialogData {
|
||||
@@ -33,6 +33,7 @@ export class LandlordProfileCompletionDialog {
|
||||
this.data.profile ? 'ready' : 'error',
|
||||
);
|
||||
readonly saving = signal(false);
|
||||
readonly saveFieldErrors = signal<FieldValidationError[]>([]);
|
||||
readonly error = signal(
|
||||
this.data.profile ? '' : 'Impossible de charger votre profil bailleur. Réessayez.',
|
||||
);
|
||||
@@ -63,6 +64,7 @@ export class LandlordProfileCompletionDialog {
|
||||
save(profile: LandlordProfile): void {
|
||||
if (this.saving()) return;
|
||||
this.error.set('');
|
||||
this.saveFieldErrors.set([]);
|
||||
this.saving.set(true);
|
||||
this.landlordProfile
|
||||
.updateProfile(profile)
|
||||
@@ -70,17 +72,24 @@ export class LandlordProfileCompletionDialog {
|
||||
.subscribe({
|
||||
next: () => this.dialogRef.close(),
|
||||
error: (error: unknown) => {
|
||||
this.error.set(
|
||||
httpErrorMessage(
|
||||
error,
|
||||
'Impossible d’enregistrer votre profil bailleur. Réessayez.',
|
||||
),
|
||||
const failure = saveFailure(
|
||||
error,
|
||||
'Impossible d’enregistrer votre profil bailleur. Réessayez.',
|
||||
);
|
||||
this.error.set(failure.message);
|
||||
this.saveFieldErrors.set(failure.fieldErrors);
|
||||
this.saving.set(false);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
clearSaveFieldError(field: string): void {
|
||||
const remaining = this.saveFieldErrors().filter((error) => error.field !== field);
|
||||
if (remaining.length === this.saveFieldErrors().length) return;
|
||||
this.saveFieldErrors.set(remaining);
|
||||
if (!remaining.length) this.error.set('');
|
||||
}
|
||||
|
||||
logout(): void {
|
||||
this.session.logout();
|
||||
}
|
||||
|
||||
@@ -4,8 +4,11 @@
|
||||
(ngSubmit)="submit()"
|
||||
[attr.aria-busy]="saving()"
|
||||
>
|
||||
@if (error()) {
|
||||
<p class="profile-error" role="alert">{{ error() }}</p>
|
||||
@if (showSaveError()) {
|
||||
<p class="profile-error" role="alert">{{ saveError() }}</p>
|
||||
}
|
||||
@if (validationError()) {
|
||||
<p class="profile-error" role="alert">{{ validationError() }}</p>
|
||||
}
|
||||
<fieldset class="profile-fields" [disabled]="saving()" [attr.inert]="saving() ? '' : null">
|
||||
<mat-form-field appearance="outline"
|
||||
@@ -14,16 +17,26 @@
|
||||
matInput
|
||||
id="profile-firstName"
|
||||
[formField]="profileForm.firstName"
|
||||
(input)="fieldWasChanged('firstName')"
|
||||
autocomplete="given-name"
|
||||
/></mat-form-field>
|
||||
/>
|
||||
@if (firstNameError()) {
|
||||
<mat-error>{{ firstNameError() }}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline"
|
||||
><mat-label>Nom</mat-label
|
||||
><input
|
||||
matInput
|
||||
id="profile-lastName"
|
||||
[formField]="profileForm.lastName"
|
||||
(input)="fieldWasChanged('lastName')"
|
||||
autocomplete="family-name"
|
||||
/></mat-form-field>
|
||||
/>
|
||||
@if (lastNameError()) {
|
||||
<mat-error>{{ lastNameError() }}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline"
|
||||
><mat-label>E-mail</mat-label
|
||||
><input
|
||||
@@ -31,8 +44,13 @@
|
||||
id="profile-email"
|
||||
type="email"
|
||||
[formField]="profileForm.email"
|
||||
(input)="fieldWasChanged('email')"
|
||||
autocomplete="email"
|
||||
/></mat-form-field>
|
||||
/>
|
||||
@if (emailError()) {
|
||||
<mat-error>{{ emailError() }}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline"
|
||||
><mat-label>Téléphone</mat-label
|
||||
><input
|
||||
@@ -40,23 +58,33 @@
|
||||
id="profile-phone"
|
||||
type="tel"
|
||||
[formField]="profileForm.phone"
|
||||
(input)="fieldWasChanged('phone')"
|
||||
autocomplete="tel"
|
||||
/></mat-form-field>
|
||||
/>
|
||||
@if (phoneError()) {
|
||||
<mat-error>{{ phoneError() }}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="full-field"
|
||||
><mat-label>Adresse postale</mat-label
|
||||
><input
|
||||
matInput
|
||||
id="profile-address"
|
||||
[formField]="profileForm.address"
|
||||
(input)="fieldWasChanged('address')"
|
||||
autocomplete="street-address"
|
||||
/></mat-form-field>
|
||||
/>
|
||||
@if (addressError()) {
|
||||
<mat-error>{{ addressError() }}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
</fieldset>
|
||||
<div class="profile-actions">
|
||||
<ng-content select="[profile-form-secondary-action]" />
|
||||
<button mat-flat-button type="submit" class="primary-action">
|
||||
@if (saving()) {
|
||||
<mat-spinner diameter="18" aria-hidden="true" /> Enregistrement…
|
||||
} @else if (error()) {
|
||||
} @else if (showSaveError()) {
|
||||
Réessayer
|
||||
} @else {
|
||||
Enregistrer le profil
|
||||
|
||||
@@ -50,4 +50,68 @@ describe('LandlordProfileForm', () => {
|
||||
address: '1 rue de Paris',
|
||||
});
|
||||
});
|
||||
|
||||
it('focuses and explains the first invalid field after submission', async () => {
|
||||
const fixture = TestBed.createComponent(LandlordProfileForm);
|
||||
const component = fixture.componentInstance;
|
||||
const saveRequested = vi.fn();
|
||||
component.saveRequested.subscribe(saveRequested);
|
||||
fixture.componentRef.setInput('profile', {
|
||||
firstName: 'Camille',
|
||||
lastName: 'Martin',
|
||||
email: 'camille@example.com',
|
||||
phone: '1234',
|
||||
address: '1 rue de Paris',
|
||||
});
|
||||
fixture.detectChanges();
|
||||
|
||||
component.submit();
|
||||
await fixture.whenStable();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(saveRequested).not.toHaveBeenCalled();
|
||||
expect(fixture.nativeElement.textContent).toContain(
|
||||
'Saisissez un numéro de téléphone valide.',
|
||||
);
|
||||
expect(document.activeElement?.id).toBe('profile-phone');
|
||||
});
|
||||
|
||||
it('renders and focuses known server errors inline, then clears only the corrected field error', async () => {
|
||||
const fixture = TestBed.createComponent(LandlordProfileForm);
|
||||
const component = fixture.componentInstance;
|
||||
const changed = vi.fn();
|
||||
component.fieldChanged.subscribe(changed);
|
||||
fixture.componentRef.setInput('profile', {
|
||||
firstName: 'Camille',
|
||||
lastName: 'Martin',
|
||||
email: 'camille@example.com',
|
||||
phone: '1234',
|
||||
address: '1 rue de Paris',
|
||||
});
|
||||
fixture.componentRef.setInput('saveError', 'Veuillez corriger les champs signalés.');
|
||||
fixture.componentRef.setInput('saveFieldErrors', [
|
||||
{ field: 'phoneValid', code: 'assert_true' },
|
||||
]);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(fixture.nativeElement.textContent).toContain(
|
||||
'Saisissez un numéro de téléphone valide.',
|
||||
);
|
||||
expect(fixture.nativeElement.textContent).not.toContain(
|
||||
'Veuillez corriger les champs signalés.',
|
||||
);
|
||||
expect(document.activeElement?.id).toBe('profile-phone');
|
||||
const phone = fixture.nativeElement.querySelector(
|
||||
'input#profile-phone',
|
||||
) as HTMLInputElement;
|
||||
phone.dispatchEvent(new Event('input'));
|
||||
|
||||
expect(changed).toHaveBeenCalledWith('phone');
|
||||
fixture.componentRef.setInput('saveFieldErrors', [{ field: 'email', code: 'email' }]);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(document.activeElement?.id).toBe('profile-email');
|
||||
});
|
||||
});
|
||||
@@ -1,11 +1,13 @@
|
||||
import { Component, computed, effect, input, output, signal } from '@angular/core';
|
||||
import { Component, effect, ElementRef, inject, input, output, signal } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { email, form, FormField, FormRoot, required } from '@angular/forms/signals';
|
||||
import { email, form, FormField, FormRoot, required, validate } from '@angular/forms/signals';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { FieldValidationError } from '../../services/http-error-message';
|
||||
import { LandlordProfile } from '../../services/landlord-profile.service';
|
||||
import { isValidPhoneNumber } from '../../services/phone-number';
|
||||
|
||||
const EMPTY_PROFILE: LandlordProfile = {
|
||||
firstName: '',
|
||||
@@ -30,40 +32,181 @@ const EMPTY_PROFILE: LandlordProfile = {
|
||||
styleUrl: './landlord-profile-form.css',
|
||||
})
|
||||
export class LandlordProfileForm {
|
||||
private readonly element = inject<ElementRef<HTMLElement>>(ElementRef);
|
||||
readonly profile = input<LandlordProfile>(EMPTY_PROFILE);
|
||||
readonly saving = input(false);
|
||||
readonly saveError = input('');
|
||||
readonly saveFieldErrors = input<FieldValidationError[]>([]);
|
||||
readonly saveRequested = output<LandlordProfile>();
|
||||
readonly fieldChanged = output<keyof LandlordProfile>();
|
||||
private previousServerErrorSignature = '';
|
||||
|
||||
readonly profileModel = signal<LandlordProfile>({ ...this.profile() });
|
||||
readonly profileForm = form(this.profileModel, (f) => {
|
||||
required(f.firstName, { message: 'Veuillez renseigner tous les champs obligatoires.' });
|
||||
required(f.lastName, { message: 'Veuillez renseigner tous les champs obligatoires.' });
|
||||
required(f.email, { message: 'Veuillez renseigner tous les champs obligatoires.' });
|
||||
required(f.phone, { message: 'Veuillez renseigner tous les champs obligatoires.' });
|
||||
required(f.address, { message: 'Veuillez renseigner tous les champs obligatoires.' });
|
||||
required(f.firstName, { message: 'Le prénom est obligatoire.' });
|
||||
required(f.lastName, { message: 'Le nom est obligatoire.' });
|
||||
required(f.email, { message: 'L’e-mail est obligatoire.' });
|
||||
required(f.phone, { message: 'Le numéro de téléphone est obligatoire.' });
|
||||
required(f.address, { message: 'L’adresse postale est obligatoire.' });
|
||||
email(f.email, { message: 'Saisissez une adresse e-mail valide.' });
|
||||
validate(f.phone, ({ value }) =>
|
||||
isValidPhoneNumber(value())
|
||||
? null
|
||||
: { kind: 'phone', message: 'Saisissez un numéro de téléphone valide.' },
|
||||
);
|
||||
});
|
||||
private readonly validationError = signal('');
|
||||
readonly error = computed(() => this.validationError() || this.saveError());
|
||||
readonly validationError = signal('');
|
||||
|
||||
constructor() {
|
||||
effect(() => this.profileModel.set({ ...this.profile() }));
|
||||
effect(() => {
|
||||
this.profileModel.set({ ...this.profile() });
|
||||
this.validationError.set('');
|
||||
});
|
||||
effect(() => {
|
||||
const errors = this.saveFieldErrors();
|
||||
const signature = serverErrorSignature(errors);
|
||||
if (signature && signature !== this.previousServerErrorSignature) {
|
||||
queueMicrotask(() => this.focusFirstInvalid());
|
||||
}
|
||||
this.previousServerErrorSignature = signature;
|
||||
});
|
||||
}
|
||||
|
||||
submit(): void {
|
||||
if (this.saving()) return;
|
||||
|
||||
this.profileForm().markAsTouched();
|
||||
if (this.profileForm().invalid()) {
|
||||
if (this.profileForm().invalid() || this.hasClientError()) {
|
||||
this.validationError.set(
|
||||
this.profileForm().errorSummary()[0]?.message ??
|
||||
this.firstError() ??
|
||||
'Saisissez des informations valides.',
|
||||
);
|
||||
queueMicrotask(() => this.focusFirstInvalid());
|
||||
return;
|
||||
}
|
||||
|
||||
this.validationError.set('');
|
||||
this.saveRequested.emit(this.profileModel());
|
||||
}
|
||||
|
||||
fieldWasChanged(field: keyof LandlordProfile): void {
|
||||
this.validationError.set('');
|
||||
this.fieldChanged.emit(field);
|
||||
}
|
||||
|
||||
showSaveError(): boolean {
|
||||
return (
|
||||
Boolean(this.saveError()) &&
|
||||
(this.saveFieldErrors().length === 0 ||
|
||||
this.saveFieldErrors().some((error) => !isInlineServerError(error)))
|
||||
);
|
||||
}
|
||||
|
||||
firstNameError(): string {
|
||||
return this.requiredError('firstName', 'Le prénom est obligatoire.');
|
||||
}
|
||||
|
||||
lastNameError(): string {
|
||||
return this.requiredError('lastName', 'Le nom est obligatoire.');
|
||||
}
|
||||
|
||||
emailError(): string {
|
||||
if (this.profileForm.email().touched() && !this.profileModel().email.trim()) {
|
||||
return 'L’e-mail est obligatoire.';
|
||||
}
|
||||
if (this.profileForm.email().touched() && this.profileForm.email().invalid()) {
|
||||
return 'Saisissez une adresse e-mail valide.';
|
||||
}
|
||||
return this.hasServerError('email', 'not_blank')
|
||||
? 'L’e-mail est obligatoire.'
|
||||
: this.hasServerError('email', 'email')
|
||||
? 'Saisissez une adresse e-mail valide.'
|
||||
: '';
|
||||
}
|
||||
|
||||
phoneError(): string {
|
||||
if (this.profileForm.phone().touched() && !this.profileModel().phone.trim()) {
|
||||
return 'Le numéro de téléphone est obligatoire.';
|
||||
}
|
||||
if (this.profileForm.phone().touched() && !isValidPhoneNumber(this.profileModel().phone)) {
|
||||
return 'Saisissez un numéro de téléphone valide.';
|
||||
}
|
||||
return this.hasServerError('phone', 'not_blank')
|
||||
? 'Le numéro de téléphone est obligatoire.'
|
||||
: this.hasServerError('phoneValid', 'assert_true')
|
||||
? 'Saisissez un numéro de téléphone valide.'
|
||||
: '';
|
||||
}
|
||||
|
||||
addressError(): string {
|
||||
return this.requiredError('address', 'L’adresse postale est obligatoire.');
|
||||
}
|
||||
|
||||
private requiredError(field: 'firstName' | 'lastName' | 'address', message: string): string {
|
||||
if (this.touched(field) && !this.profileModel()[field].trim()) return message;
|
||||
return this.hasServerError(field, 'not_blank') ? message : '';
|
||||
}
|
||||
|
||||
private touched(field: 'firstName' | 'lastName' | 'address'): boolean {
|
||||
return this.profileForm[field]().touched();
|
||||
}
|
||||
|
||||
private hasClientError(): boolean {
|
||||
return Boolean(
|
||||
this.firstNameError() ||
|
||||
this.lastNameError() ||
|
||||
this.emailError() ||
|
||||
this.phoneError() ||
|
||||
this.addressError(),
|
||||
);
|
||||
}
|
||||
|
||||
private firstError(): string {
|
||||
return (
|
||||
this.firstNameError() ||
|
||||
this.lastNameError() ||
|
||||
this.emailError() ||
|
||||
this.phoneError() ||
|
||||
this.addressError()
|
||||
);
|
||||
}
|
||||
|
||||
private hasServerError(field: string, code: string): boolean {
|
||||
return this.saveFieldErrors().some((error) => error.field === field && error.code === code);
|
||||
}
|
||||
|
||||
private focusFirstInvalid(): void {
|
||||
if (!this.element.nativeElement.querySelector('form')) return;
|
||||
const selector = this.firstNameError()
|
||||
? '#profile-firstName'
|
||||
: this.lastNameError()
|
||||
? '#profile-lastName'
|
||||
: this.emailError()
|
||||
? '#profile-email'
|
||||
: this.phoneError()
|
||||
? '#profile-phone'
|
||||
: this.addressError()
|
||||
? '#profile-address'
|
||||
: '';
|
||||
const control = this.element.nativeElement.querySelector(selector);
|
||||
if (control instanceof HTMLElement) control.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function isInlineServerError(error: FieldValidationError): boolean {
|
||||
return (
|
||||
((error.field === 'firstName' || error.field === 'lastName' || error.field === 'address') &&
|
||||
error.code === 'not_blank') ||
|
||||
(error.field === 'email' && (error.code === 'not_blank' || error.code === 'email')) ||
|
||||
(error.field === 'phone' && error.code === 'not_blank') ||
|
||||
(error.field === 'phoneValid' && error.code === 'assert_true')
|
||||
);
|
||||
}
|
||||
|
||||
function serverErrorSignature(errors: FieldValidationError[]): string {
|
||||
return errors
|
||||
.map((error) => `${error.field}:${error.code}`)
|
||||
.sort()
|
||||
.join('|');
|
||||
}
|
||||
@@ -69,6 +69,8 @@
|
||||
[profile]="profileModel()"
|
||||
[saving]="saving()"
|
||||
[saveError]="saveError()"
|
||||
[saveFieldErrors]="saveFieldErrors()"
|
||||
(fieldChanged)="clearSaveFieldError($event)"
|
||||
(saveRequested)="save($event)"
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -97,4 +97,43 @@ describe('SettingsPage', () => {
|
||||
'alex@example.com',
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps structured profile errors beside their field until that field changes', async () => {
|
||||
profile.updateProfile.mockReturnValue(
|
||||
throwError(
|
||||
() =>
|
||||
new HttpErrorResponse({
|
||||
error: {
|
||||
code: 'validation_failed',
|
||||
errors: [{ field: 'phoneValid', code: 'assert_true' }],
|
||||
},
|
||||
status: 400,
|
||||
}),
|
||||
),
|
||||
);
|
||||
const fixture = TestBed.createComponent(SettingsPage);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
fixture.nativeElement.querySelector('button.primary-action').click();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(fixture.nativeElement.textContent).toContain(
|
||||
'Saisissez un numéro de téléphone valide.',
|
||||
);
|
||||
expect(fixture.nativeElement.textContent).not.toContain(
|
||||
'Veuillez corriger les champs signalés.',
|
||||
);
|
||||
const phone = fixture.nativeElement.querySelector(
|
||||
'input#profile-phone',
|
||||
) as HTMLInputElement;
|
||||
phone.value = '+33612345678';
|
||||
phone.dispatchEvent(new Event('input'));
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.nativeElement.textContent).not.toContain(
|
||||
'Saisissez un numéro de téléphone valide.',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -6,7 +6,7 @@ import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { MatRadioModule } from '@angular/material/radio';
|
||||
import { LandlordProfileForm } from '../landlord-profile-form/landlord-profile-form';
|
||||
import { httpErrorMessage } from '../../services/http-error-message';
|
||||
import { FieldValidationError, saveFailure } from '../../services/http-error-message';
|
||||
import { LandlordProfile, LandlordProfileService } from '../../services/landlord-profile.service';
|
||||
import { SessionService } from '../../services/session.service';
|
||||
import { ThemeService } from '../../services/theme.service';
|
||||
@@ -45,6 +45,7 @@ export class SettingsPage {
|
||||
|
||||
readonly profileError = signal('');
|
||||
readonly saveError = signal('');
|
||||
readonly saveFieldErrors = signal<FieldValidationError[]>([]);
|
||||
readonly saveSuccess = signal('');
|
||||
|
||||
constructor() {
|
||||
@@ -79,6 +80,7 @@ export class SettingsPage {
|
||||
if (this.saving()) return;
|
||||
this.saveSuccess.set('');
|
||||
this.saveError.set('');
|
||||
this.saveFieldErrors.set([]);
|
||||
this.saving.set(true);
|
||||
this.landlordProfile
|
||||
.updateProfile(profile)
|
||||
@@ -90,14 +92,21 @@ export class SettingsPage {
|
||||
this.saving.set(false);
|
||||
},
|
||||
error: (error: unknown) => {
|
||||
this.saveError.set(
|
||||
httpErrorMessage(
|
||||
error,
|
||||
'Impossible d’enregistrer votre profil bailleur. Réessayez.',
|
||||
),
|
||||
const failure = saveFailure(
|
||||
error,
|
||||
'Impossible d’enregistrer votre profil bailleur. Réessayez.',
|
||||
);
|
||||
this.saveError.set(failure.message);
|
||||
this.saveFieldErrors.set(failure.fieldErrors);
|
||||
this.saving.set(false);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
clearSaveFieldError(field: string): void {
|
||||
const remaining = this.saveFieldErrors().filter((error) => error.field !== field);
|
||||
if (remaining.length === this.saveFieldErrors().length) return;
|
||||
this.saveFieldErrors.set(remaining);
|
||||
if (!remaining.length) this.saveError.set('');
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user