fix: validate tenant and lease contact details
This commit is contained in:
1 parent
8603b98714
commit
bf6d8e6bb6
27 files changed
+444
-54
No files matched your search
@@ -127,6 +127,15 @@
|
||||
overflow: hidden;
|
||||
clip: rect(0 0 0 0);
|
||||
}
|
||||
.upload-action small {
|
||||
color: var(--text-muted);
|
||||
font-weight: 500;
|
||||
}
|
||||
.attachment-error {
|
||||
margin: 8px 0 0;
|
||||
color: var(--danger);
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
.card-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
@@ -63,16 +63,20 @@
|
||||
</button>
|
||||
}
|
||||
@if (tenant.id) {
|
||||
@if (attachmentUploadError() || attachmentError()) {
|
||||
<p class="attachment-error" role="alert">
|
||||
{{ attachmentUploadError() || attachmentError() }}
|
||||
</p>
|
||||
}
|
||||
<label class="upload-action">
|
||||
<input
|
||||
type="file"
|
||||
accept="application/pdf,image/*"
|
||||
[attr.aria-label]="
|
||||
'Ajouter une pièce jointe pour ' + tenant.firstName + ' ' + tenant.lastName
|
||||
"
|
||||
(change)="selectFile(tenant, $event)"
|
||||
/>
|
||||
+ Ajouter une pièce jointe
|
||||
+ Ajouter une pièce jointe <small>· 250 Mo maximum</small>
|
||||
</label>
|
||||
}
|
||||
</div>
|
||||
@@ -136,6 +140,7 @@
|
||||
[ngModel]="email()"
|
||||
(ngModelChange)="email.set($event)"
|
||||
required
|
||||
autocomplete="email"
|
||||
/></mat-form-field>
|
||||
<mat-form-field appearance="outline"
|
||||
><mat-label>Téléphone</mat-label
|
||||
@@ -145,6 +150,7 @@
|
||||
name="phone"
|
||||
[ngModel]="phone()"
|
||||
(ngModelChange)="phone.set($event)"
|
||||
autocomplete="tel"
|
||||
/></mat-form-field>
|
||||
<mat-form-field appearance="outline" class="full-field"
|
||||
><mat-label>Adresse complète</mat-label
|
||||
|
||||
@@ -21,6 +21,23 @@ describe('TenantsPage', () => {
|
||||
expect(component.showForm()).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects malformed tenant contact details', () => {
|
||||
const fixture = TestBed.createComponent(TenantsPage);
|
||||
fixture.componentRef.setInput('tenants', []);
|
||||
const component = fixture.componentInstance;
|
||||
component.firstName.set('Camille');
|
||||
component.lastName.set('Martin');
|
||||
component.email.set('camille-at-example.com');
|
||||
|
||||
component.save();
|
||||
expect(component.error()).toContain('e-mail valide');
|
||||
|
||||
component.email.set('camille@example.com');
|
||||
component.phone.set('invalid');
|
||||
component.save();
|
||||
expect(component.error()).toContain('numéro français');
|
||||
});
|
||||
|
||||
it('emits a selected attachment for an existing tenant', () => {
|
||||
const fixture = TestBed.createComponent(TenantsPage);
|
||||
fixture.componentRef.setInput('tenants', []);
|
||||
@@ -46,6 +63,23 @@ describe('TenantsPage', () => {
|
||||
expect(uploadedFile).toBe(file);
|
||||
});
|
||||
|
||||
it('rejects an attachment larger than 250 Mo before upload', () => {
|
||||
const fixture = TestBed.createComponent(TenantsPage);
|
||||
fixture.componentRef.setInput('tenants', []);
|
||||
const component = fixture.componentInstance;
|
||||
const file = new File(['identity'], 'archive.zip');
|
||||
Object.defineProperty(file, 'size', { value: 250 * 1024 * 1024 + 1 });
|
||||
const input = document.createElement('input');
|
||||
Object.defineProperty(input, 'files', { value: { item: () => file } });
|
||||
|
||||
component.selectFile(
|
||||
{ id: 'tenant-1', firstName: 'Camille', lastName: 'Martin', email: 'camille@example.com', phone: '', address: '' },
|
||||
{ target: input } as unknown as Event,
|
||||
);
|
||||
|
||||
expect(component.attachmentUploadError()).toContain('250 Mo');
|
||||
});
|
||||
|
||||
it('opens, fills, and closes the edit form', () => {
|
||||
const fixture = TestBed.createComponent(TenantsPage);
|
||||
fixture.componentRef.setInput('tenants', []);
|
||||
|
||||
@@ -3,6 +3,7 @@ import { FormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { isValidEmail, isValidPhone } from '../../shared/contact-validation';
|
||||
import { Tenant, TenantAttachment } from '../../services/tenant.service';
|
||||
|
||||
export interface TenantSaveRequest {
|
||||
@@ -23,6 +24,7 @@ export interface TenantAttachmentRequest {
|
||||
})
|
||||
export class TenantsPage {
|
||||
readonly tenants = input.required<Tenant[]>();
|
||||
readonly attachmentError = input('');
|
||||
readonly tenantSaved = output<TenantSaveRequest>();
|
||||
readonly deleteRequested = output<string>();
|
||||
readonly attachmentUploadRequested = output<{ tenantId: string; file: File }>();
|
||||
@@ -36,6 +38,7 @@ export class TenantsPage {
|
||||
readonly phone = signal('');
|
||||
readonly address = signal('');
|
||||
readonly error = signal('');
|
||||
readonly attachmentUploadError = signal('');
|
||||
|
||||
constructor() {
|
||||
effect(() => this.fillForm(this.editingTenant()));
|
||||
@@ -62,6 +65,14 @@ export class TenantsPage {
|
||||
this.error.set('Le prénom, le nom et l’e-mail sont obligatoires.');
|
||||
return;
|
||||
}
|
||||
if (!isValidEmail(this.email())) {
|
||||
this.error.set('Saisissez une adresse e-mail valide.');
|
||||
return;
|
||||
}
|
||||
if (!isValidPhone(this.phone())) {
|
||||
this.error.set('Saisissez un numéro français ou international valide.');
|
||||
return;
|
||||
}
|
||||
this.tenantSaved.emit({
|
||||
tenant: {
|
||||
firstName: this.firstName().trim(),
|
||||
@@ -77,7 +88,10 @@ export class TenantsPage {
|
||||
|
||||
selectFile(tenant: Tenant, event: Event): void {
|
||||
const file = (event.target as HTMLInputElement).files?.item(0);
|
||||
if (tenant.id && file) {
|
||||
if (file && file.size > 250 * 1024 * 1024) {
|
||||
this.attachmentUploadError.set('Une pièce jointe ne peut pas dépasser 250 Mo.');
|
||||
} else if (tenant.id && file) {
|
||||
this.attachmentUploadError.set('');
|
||||
this.attachmentUploadRequested.emit({ tenantId: tenant.id, file });
|
||||
}
|
||||
(event.target as HTMLInputElement).value = '';
|
||||
|
||||
Reference in new issue
Block a user