feat(landlord-profile): enhance profile completion dialog and form layout

This commit is contained in:
nessar committed 2026-08-17 14:18:16 +02:00
1 parent 12316b8604
commit 62d0f171f4
6 files changed
+79 -9

No files matched your search

+45 -3
View File
@@ -1,4 +1,4 @@
import { expect, Page, test } from '@playwright/test';
import { expect, Locator, Page, test } from '@playwright/test';
async function mockUser(page: Page, authenticated: boolean, completeProfile = true): Promise<void> {
await page.unroute('**/api/user');
@@ -43,6 +43,27 @@ async function expectNoHorizontalOverflow(page: Page): Promise<void> {
expect(hasHorizontalOverflow).toBe(false);
}
async function expectNoScrollbars(locator: Locator): Promise<void> {
const scrollableElements = await locator.evaluate((element) => {
return [element, ...element.querySelectorAll<HTMLElement>('*')]
.filter((candidate) => {
const { overflowX, overflowY } = getComputedStyle(candidate);
const hasHorizontalScrollbar =
(overflowX === 'auto' || overflowX === 'scroll') &&
candidate.scrollWidth > candidate.clientWidth;
const hasVerticalScrollbar =
(overflowY === 'auto' || overflowY === 'scroll') &&
candidate.scrollHeight > candidate.clientHeight;
return hasHorizontalScrollbar || hasVerticalScrollbar;
})
.map(
(candidate) =>
`${candidate.tagName.toLowerCase()}.${candidate.getAttribute('class') ?? ''}`,
);
});
expect(scrollableElements).toEqual([]);
}
test.beforeEach(async ({ page }) => {
await mockUser(page, false);
});
@@ -74,13 +95,34 @@ test('requires a newly connected landlord to complete their profile', async ({ p
const dialog = page.getByRole('dialog');
await expect(dialog.getByRole('heading', { name: 'Complétez votre profil' })).toBeVisible();
await expectNoHorizontalOverflow(page);
await expectNoScrollbars(dialog);
await page.keyboard.press('Escape');
await expect(dialog).toBeVisible();
await expect(dialog.getByRole('button', { name: 'Déconnexion' })).toBeVisible();
const logoutButton = dialog.getByRole('button', { name: 'Déconnexion' });
const saveButton = dialog.getByRole('button', { name: 'Enregistrer le profil' });
await expect(logoutButton).toBeVisible();
await expect(saveButton).toBeVisible();
const [logoutBox, saveBox, actionBox, surfaceBox] = await Promise.all([
logoutButton.boundingBox(),
saveButton.boundingBox(),
dialog.locator('.profile-actions').boundingBox(),
dialog.locator('.mat-mdc-dialog-surface').boundingBox(),
]);
if (!logoutBox || !saveBox || !actionBox || !surfaceBox) {
throw new Error('Profile dialog actions must be measurable.');
}
expect(logoutBox.x).toBeCloseTo(actionBox.x, 1);
expect(saveBox.x + saveBox.width).toBeCloseTo(actionBox.x + actionBox.width, 1);
expect(Math.abs(logoutBox.y - saveBox.y)).toBeLessThan(8);
expect(surfaceBox.y + surfaceBox.height - (actionBox.y + actionBox.height)).toBeGreaterThanOrEqual(
16,
);
await dialog.getByLabel('Téléphone').fill('+33612345678');
await dialog.getByLabel('Adresse postale').fill('1 rue des Lilas, Lyon');
await dialog.getByRole('button', { name: 'Enregistrer le profil' }).click();
await saveButton.click();
await expect(dialog).toBeHidden();
await expect(page.getByTestId('primary-navigation')).toBeVisible();
@@ -3,9 +3,8 @@
width: min(36rem, calc(100vw - 3rem));
max-width: 100%;
gap: var(--space-4, 1rem);
max-height: 75vh;
overscroll-behavior-y: contain;
container-type: inline-size;
padding-inline: var(--space-6, 1.5rem);
padding-block-end: var(--space-6, 1.5rem);
}
.profile-dialog-content > p,
@@ -1,5 +1,5 @@
<h2 mat-dialog-title>Complétez votre profil</h2>
<mat-dialog-content class="profile-dialog-content">
<div class="profile-dialog-content">
<p>Vos coordonnées sont nécessaires pour créer vos baux et vos quittances.</p>
@if (profileStatus() === 'loading') {
@@ -13,13 +13,26 @@
</div>
} @else {
<app-landlord-profile-form
class="profile-completion-form"
[profile]="profile()"
[saving]="saving()"
[saveError]="error()"
(saveRequested)="save($event)"
/>
>
<button
profile-form-secondary-action
mat-button
type="button"
(click)="logout()"
[disabled]="saving()"
>
Déconnexion
</button>
</app-landlord-profile-form>
}
</mat-dialog-content>
</div>
@if (profileStatus() !== 'ready') {
<mat-dialog-actions align="end">
<button mat-button type="button" (click)="logout()" [disabled]="saving()">Déconnexion</button>
</mat-dialog-actions>
}
@@ -3,6 +3,11 @@
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);
}
.profile-form {
display: grid;
gap: var(--space-4, 1rem);
@@ -32,6 +37,12 @@
justify-content: flex-end;
}
:host(.profile-completion-form) .profile-actions {
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));
@@ -52,6 +52,7 @@
/></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…
+4
View File
@@ -4,6 +4,10 @@
@include mat.core();
.mat-mdc-dialog-surface:has(app-landlord-profile-completion-dialog) {
overflow-y: visible;
}
@layer base {
html {
@include mat.theme(