chore: add husky pre-commit hook and lint-staged configuration
- Created a pre-commit hook in .husky to run lint-staged. - Added .lintstagedrc.mjs to format frontend files with Prettier and apply Spotless to backend Java files. - Introduced .prettierignore to exclude specific directories from formatting.
This commit is contained in:
1 parent
5d2d85aa8c
commit
878da155bd
564 files changed
+61467
-47179
No files matched your search
@@ -1,54 +1,54 @@
|
||||
:host {
|
||||
display: block;
|
||||
container-type: inline-size;
|
||||
display: block;
|
||||
container-type: inline-size;
|
||||
}
|
||||
|
||||
:host(.profile-completion-form) {
|
||||
--mat-button-text-horizontal-padding: var(--space-1, 0.25rem);
|
||||
--mat-button-filled-horizontal-padding: var(--space-1, 0.25rem);
|
||||
--mat-button-text-horizontal-padding: var(--space-1, 0.25rem);
|
||||
--mat-button-filled-horizontal-padding: var(--space-1, 0.25rem);
|
||||
}
|
||||
|
||||
.profile-form {
|
||||
display: grid;
|
||||
gap: var(--space-4, 1rem);
|
||||
display: grid;
|
||||
gap: var(--space-4, 1rem);
|
||||
}
|
||||
|
||||
.profile-error {
|
||||
margin: 0;
|
||||
color: var(--danger, #b42318);
|
||||
margin: 0;
|
||||
color: var(--danger, #b42318);
|
||||
}
|
||||
|
||||
.profile-fields {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
gap: var(--space-1, 0.25rem) var(--space-3, 0.75rem);
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
gap: var(--space-1, 0.25rem) var(--space-3, 0.75rem);
|
||||
}
|
||||
|
||||
.profile-fields mat-form-field {
|
||||
width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.profile-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
:host(.profile-completion-form) .profile-actions {
|
||||
flex-wrap: nowrap;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-1, 0.25rem);
|
||||
flex-wrap: nowrap;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-1, 0.25rem);
|
||||
}
|
||||
|
||||
@container (min-width: 32rem) {
|
||||
.profile-fields {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
.profile-fields {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.profile-fields .full-field {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
.profile-fields .full-field {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
@@ -1,66 +1,66 @@
|
||||
<form
|
||||
class="profile-form"
|
||||
[formRoot]="profileForm"
|
||||
(ngSubmit)="submit()"
|
||||
[attr.aria-busy]="saving()"
|
||||
class="profile-form"
|
||||
[formRoot]="profileForm"
|
||||
(ngSubmit)="submit()"
|
||||
[attr.aria-busy]="saving()"
|
||||
>
|
||||
@if (error()) {
|
||||
<p class="profile-error" role="alert">{{ error() }}</p>
|
||||
}
|
||||
<fieldset class="profile-fields" [disabled]="saving()" [attr.inert]="saving() ? '' : null">
|
||||
<mat-form-field appearance="outline"
|
||||
><mat-label>Prénom</mat-label
|
||||
><input
|
||||
matInput
|
||||
id="profile-firstName"
|
||||
[formField]="profileForm.firstName"
|
||||
autocomplete="given-name"
|
||||
/></mat-form-field>
|
||||
<mat-form-field appearance="outline"
|
||||
><mat-label>Nom</mat-label
|
||||
><input
|
||||
matInput
|
||||
id="profile-lastName"
|
||||
[formField]="profileForm.lastName"
|
||||
autocomplete="family-name"
|
||||
/></mat-form-field>
|
||||
<mat-form-field appearance="outline"
|
||||
><mat-label>E-mail</mat-label
|
||||
><input
|
||||
matInput
|
||||
id="profile-email"
|
||||
type="email"
|
||||
[formField]="profileForm.email"
|
||||
autocomplete="email"
|
||||
/></mat-form-field>
|
||||
<mat-form-field appearance="outline"
|
||||
><mat-label>Téléphone</mat-label
|
||||
><input
|
||||
matInput
|
||||
id="profile-phone"
|
||||
type="tel"
|
||||
[formField]="profileForm.phone"
|
||||
autocomplete="tel"
|
||||
/></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"
|
||||
autocomplete="street-address"
|
||||
/></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()) {
|
||||
Réessayer
|
||||
} @else {
|
||||
Enregistrer le profil
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
@if (error()) {
|
||||
<p class="profile-error" role="alert">{{ error() }}</p>
|
||||
}
|
||||
<fieldset class="profile-fields" [disabled]="saving()" [attr.inert]="saving() ? '' : null">
|
||||
<mat-form-field appearance="outline"
|
||||
><mat-label>Prénom</mat-label
|
||||
><input
|
||||
matInput
|
||||
id="profile-firstName"
|
||||
[formField]="profileForm.firstName"
|
||||
autocomplete="given-name"
|
||||
/></mat-form-field>
|
||||
<mat-form-field appearance="outline"
|
||||
><mat-label>Nom</mat-label
|
||||
><input
|
||||
matInput
|
||||
id="profile-lastName"
|
||||
[formField]="profileForm.lastName"
|
||||
autocomplete="family-name"
|
||||
/></mat-form-field>
|
||||
<mat-form-field appearance="outline"
|
||||
><mat-label>E-mail</mat-label
|
||||
><input
|
||||
matInput
|
||||
id="profile-email"
|
||||
type="email"
|
||||
[formField]="profileForm.email"
|
||||
autocomplete="email"
|
||||
/></mat-form-field>
|
||||
<mat-form-field appearance="outline"
|
||||
><mat-label>Téléphone</mat-label
|
||||
><input
|
||||
matInput
|
||||
id="profile-phone"
|
||||
type="tel"
|
||||
[formField]="profileForm.phone"
|
||||
autocomplete="tel"
|
||||
/></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"
|
||||
autocomplete="street-address"
|
||||
/></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()) {
|
||||
Réessayer
|
||||
} @else {
|
||||
Enregistrer le profil
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -3,51 +3,51 @@ import { vi } from 'vitest';
|
||||
import { LandlordProfileForm } from './landlord-profile-form';
|
||||
|
||||
describe('LandlordProfileForm', () => {
|
||||
it('explains incomplete profile data without requesting a save', () => {
|
||||
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: '',
|
||||
address: '',
|
||||
});
|
||||
fixture.detectChanges();
|
||||
it('explains incomplete profile data without requesting a save', () => {
|
||||
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: '',
|
||||
address: '',
|
||||
});
|
||||
fixture.detectChanges();
|
||||
|
||||
component.submit();
|
||||
fixture.detectChanges();
|
||||
component.submit();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(saveRequested).not.toHaveBeenCalled();
|
||||
expect(fixture.nativeElement.textContent).toContain(
|
||||
'Veuillez renseigner tous les champs obligatoires.',
|
||||
);
|
||||
});
|
||||
expect(saveRequested).not.toHaveBeenCalled();
|
||||
expect(fixture.nativeElement.textContent).toContain(
|
||||
'Veuillez renseigner tous les champs obligatoires.',
|
||||
);
|
||||
});
|
||||
|
||||
it('emits a complete profile for saving', () => {
|
||||
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: '+33600000000',
|
||||
address: '1 rue de Paris',
|
||||
});
|
||||
fixture.detectChanges();
|
||||
it('emits a complete profile for saving', () => {
|
||||
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: '+33600000000',
|
||||
address: '1 rue de Paris',
|
||||
});
|
||||
fixture.detectChanges();
|
||||
|
||||
component.submit();
|
||||
component.submit();
|
||||
|
||||
expect(saveRequested).toHaveBeenCalledWith({
|
||||
firstName: 'Camille',
|
||||
lastName: 'Martin',
|
||||
email: 'camille@example.com',
|
||||
phone: '+33600000000',
|
||||
address: '1 rue de Paris',
|
||||
});
|
||||
});
|
||||
expect(saveRequested).toHaveBeenCalledWith({
|
||||
firstName: 'Camille',
|
||||
lastName: 'Martin',
|
||||
email: 'camille@example.com',
|
||||
phone: '+33600000000',
|
||||
address: '1 rue de Paris',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -8,61 +8,62 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { LandlordProfile } from '../../services/landlord-profile.service';
|
||||
|
||||
const EMPTY_PROFILE: LandlordProfile = {
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
address: '',
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
address: '',
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: 'app-landlord-profile-form',
|
||||
imports: [
|
||||
FormsModule,
|
||||
FormRoot,
|
||||
FormField,
|
||||
MatButtonModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatProgressSpinnerModule,
|
||||
],
|
||||
templateUrl: './landlord-profile-form.html',
|
||||
styleUrl: './landlord-profile-form.css',
|
||||
selector: 'app-landlord-profile-form',
|
||||
imports: [
|
||||
FormsModule,
|
||||
FormRoot,
|
||||
FormField,
|
||||
MatButtonModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatProgressSpinnerModule,
|
||||
],
|
||||
templateUrl: './landlord-profile-form.html',
|
||||
styleUrl: './landlord-profile-form.css',
|
||||
})
|
||||
export class LandlordProfileForm {
|
||||
readonly profile = input<LandlordProfile>(EMPTY_PROFILE);
|
||||
readonly saving = input(false);
|
||||
readonly saveError = input('');
|
||||
readonly saveRequested = output<LandlordProfile>();
|
||||
readonly profile = input<LandlordProfile>(EMPTY_PROFILE);
|
||||
readonly saving = input(false);
|
||||
readonly saveError = input('');
|
||||
readonly saveRequested = output<LandlordProfile>();
|
||||
|
||||
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.' });
|
||||
email(f.email, { message: 'Saisissez une adresse e-mail valide.' });
|
||||
});
|
||||
private readonly validationError = signal('');
|
||||
readonly error = computed(() => this.validationError() || this.saveError());
|
||||
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.' });
|
||||
email(f.email, { message: 'Saisissez une adresse e-mail valide.' });
|
||||
});
|
||||
private readonly validationError = signal('');
|
||||
readonly error = computed(() => this.validationError() || this.saveError());
|
||||
|
||||
constructor() {
|
||||
effect(() => this.profileModel.set({ ...this.profile() }));
|
||||
}
|
||||
constructor() {
|
||||
effect(() => this.profileModel.set({ ...this.profile() }));
|
||||
}
|
||||
|
||||
submit(): void {
|
||||
if (this.saving()) return;
|
||||
submit(): void {
|
||||
if (this.saving()) return;
|
||||
|
||||
this.profileForm().markAsTouched();
|
||||
if (this.profileForm().invalid()) {
|
||||
this.validationError.set(
|
||||
this.profileForm().errorSummary()[0]?.message ?? 'Saisissez des informations valides.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
this.profileForm().markAsTouched();
|
||||
if (this.profileForm().invalid()) {
|
||||
this.validationError.set(
|
||||
this.profileForm().errorSummary()[0]?.message ??
|
||||
'Saisissez des informations valides.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.validationError.set('');
|
||||
this.saveRequested.emit(this.profileModel());
|
||||
}
|
||||
this.validationError.set('');
|
||||
this.saveRequested.emit(this.profileModel());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user