🐛(frontend) fix mail domain creation form
- allow to submit form by pressing "Enter" key - force focus on form when form is submited but is invalid - add error 500 handling - update related e2e tests
This commit is contained in:
committed by
Sebastien Nobour
parent
237d64b4c5
commit
b5d8e92d1e
@@ -30,6 +30,10 @@ and this project adheres to
|
|||||||
|
|
||||||
- 👽️(mailboxes) fix mailbox creation after dimail api improvement (#360)
|
- 👽️(mailboxes) fix mailbox creation after dimail api improvement (#360)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- 🐛(frontend) user can submit form to add mail domain by pressing "Enter" key
|
||||||
|
|
||||||
## [1.0.1] - 2024-08-19
|
## [1.0.1] - 2024-08-19
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -2,10 +2,16 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
|||||||
import { Button, Input, Loader, ModalSize } from '@openfun/cunningham-react';
|
import { Button, Input, Loader, ModalSize } from '@openfun/cunningham-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Controller, FormProvider, useForm } from 'react-hook-form';
|
import {
|
||||||
|
Controller,
|
||||||
|
FormProvider,
|
||||||
|
UseFormReturn,
|
||||||
|
useForm,
|
||||||
|
} from 'react-hook-form';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { APIError } from '@/api';
|
||||||
import { Box, Text, TextErrors } from '@/components';
|
import { Box, Text, TextErrors } from '@/components';
|
||||||
import { Modal } from '@/components/Modal';
|
import { Modal } from '@/components/Modal';
|
||||||
import { useCreateMailDomain } from '@/features/mail-domains';
|
import { useCreateMailDomain } from '@/features/mail-domains';
|
||||||
@@ -14,7 +20,68 @@ import { default as MailDomainsLogo } from '../assets/mail-domains-logo.svg';
|
|||||||
|
|
||||||
const FORM_ID = 'form-add-mail-domain';
|
const FORM_ID = 'form-add-mail-domain';
|
||||||
|
|
||||||
export const ModalCreateMailDomain = () => {
|
const useAddMailDomainApiError = ({
|
||||||
|
error,
|
||||||
|
methods,
|
||||||
|
}: {
|
||||||
|
error: APIError | null;
|
||||||
|
methods: UseFormReturn<{ name: string }> | null;
|
||||||
|
}): string[] | undefined => {
|
||||||
|
const [errorCauses, setErrorCauses] = React.useState<undefined | string[]>(
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (methods && t && error) {
|
||||||
|
let causes = undefined;
|
||||||
|
|
||||||
|
if (error.cause?.length) {
|
||||||
|
const parseCauses = (causes: string[]) =>
|
||||||
|
causes.reduce((arrayCauses, cause) => {
|
||||||
|
switch (cause) {
|
||||||
|
case 'Mail domain with this name already exists.':
|
||||||
|
case 'Mail domain with this Slug already exists.':
|
||||||
|
methods.setError('name', {
|
||||||
|
type: 'manual',
|
||||||
|
message: t(
|
||||||
|
'This mail domain is already used. Please, choose another one.',
|
||||||
|
),
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
arrayCauses.push(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
return arrayCauses;
|
||||||
|
}, [] as string[]);
|
||||||
|
|
||||||
|
causes = parseCauses(error.cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error.status === 500 || !error.cause) {
|
||||||
|
causes = [
|
||||||
|
t(
|
||||||
|
'Your request cannot be processed because the server is experiencing an error. If the problem ' +
|
||||||
|
'persists, please contact our support to resolve the issue: suiteterritoriale@anct.gouv.fr.',
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
setErrorCauses(causes);
|
||||||
|
}
|
||||||
|
}, [methods, t, error]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (errorCauses && methods) {
|
||||||
|
methods.setFocus('name');
|
||||||
|
}
|
||||||
|
}, [methods, errorCauses]);
|
||||||
|
|
||||||
|
return errorCauses;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ModalAddMailDomain = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -42,28 +109,16 @@ export const ModalCreateMailDomain = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSubmitCallback = () => {
|
const errorCauses = useAddMailDomainApiError({ error, methods });
|
||||||
void methods.handleSubmit(({ name }, event) => {
|
|
||||||
event?.preventDefault();
|
const onSubmitCallback = (event: React.FormEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
void methods.handleSubmit(({ name }) => {
|
||||||
void createMailDomain(name);
|
void createMailDomain(name);
|
||||||
})();
|
})();
|
||||||
};
|
};
|
||||||
|
|
||||||
const causes = error?.cause?.filter((cause) => {
|
|
||||||
const isFound = cause === 'Mail domain with this name already exists.';
|
|
||||||
|
|
||||||
if (isFound) {
|
|
||||||
methods.setError('name', {
|
|
||||||
type: 'manual',
|
|
||||||
message: t(
|
|
||||||
'This mail domain is already used. Please, choose another one.',
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return !isFound;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!methods) {
|
if (!methods) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -82,7 +137,8 @@ export const ModalCreateMailDomain = () => {
|
|||||||
onClose={() => router.push('/mail-domains/')}
|
onClose={() => router.push('/mail-domains/')}
|
||||||
rightActions={
|
rightActions={
|
||||||
<Button
|
<Button
|
||||||
onClick={onSubmitCallback}
|
type="submit"
|
||||||
|
form={FORM_ID}
|
||||||
disabled={!methods.watch('name') || isPending}
|
disabled={!methods.watch('name') || isPending}
|
||||||
>
|
>
|
||||||
{t('Add the domain')}
|
{t('Add the domain')}
|
||||||
@@ -98,8 +154,16 @@ export const ModalCreateMailDomain = () => {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
{!!errorCauses?.length ? (
|
||||||
|
<TextErrors
|
||||||
|
$margin={{ bottom: 'small' }}
|
||||||
|
$textAlign="left"
|
||||||
|
causes={errorCauses}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<FormProvider {...methods}>
|
<FormProvider {...methods}>
|
||||||
<form action="" id={FORM_ID}>
|
<form id={FORM_ID} onSubmit={onSubmitCallback}>
|
||||||
<Controller
|
<Controller
|
||||||
control={methods.control}
|
control={methods.control}
|
||||||
name="name"
|
name="name"
|
||||||
@@ -123,7 +187,6 @@ export const ModalCreateMailDomain = () => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
{!!causes?.length ? <TextErrors causes={causes} /> : null}
|
|
||||||
|
|
||||||
{isPending && (
|
{isPending && (
|
||||||
<Box $align="center">
|
<Box $align="center">
|
||||||
|
|||||||
@@ -1,7 +1,32 @@
|
|||||||
import { expect, test } from '@playwright/test';
|
import { Page, expect, test } from '@playwright/test';
|
||||||
|
|
||||||
import { keyCloakSignIn, randomName } from './common';
|
import { keyCloakSignIn, randomName } from './common';
|
||||||
|
|
||||||
|
const getElements = (page: Page) => {
|
||||||
|
const panel = page.getByLabel('Mail domains panel').first();
|
||||||
|
const linkIndexPageAddDomain = page.getByRole('link', {
|
||||||
|
name: 'Add a mail domain',
|
||||||
|
});
|
||||||
|
const form = page.locator('form');
|
||||||
|
const inputName = form.getByLabel('Domain name');
|
||||||
|
const buttonSubmit = page.getByRole('button', {
|
||||||
|
name: 'Add the domain',
|
||||||
|
});
|
||||||
|
|
||||||
|
const buttonCancel = page.getByRole('button', {
|
||||||
|
name: 'Cancel',
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
panel,
|
||||||
|
linkIndexPageAddDomain,
|
||||||
|
form,
|
||||||
|
inputName,
|
||||||
|
buttonCancel,
|
||||||
|
buttonSubmit,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
test.beforeEach(async ({ page, browserName }) => {
|
test.beforeEach(async ({ page, browserName }) => {
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
await keyCloakSignIn(page, browserName);
|
await keyCloakSignIn(page, browserName);
|
||||||
@@ -9,16 +34,14 @@ test.beforeEach(async ({ page, browserName }) => {
|
|||||||
|
|
||||||
test.describe('Add Mail Domains', () => {
|
test.describe('Add Mail Domains', () => {
|
||||||
test('checks all the elements are visible', async ({ page }) => {
|
test('checks all the elements are visible', async ({ page }) => {
|
||||||
await page.goto('/mail-domains');
|
await page.goto('/mail-domains/');
|
||||||
|
|
||||||
const buttonFromHomePage = page.getByRole('button', {
|
const { linkIndexPageAddDomain, inputName } = getElements(page);
|
||||||
name: 'Add a mail domain',
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(buttonFromHomePage).toBeVisible();
|
await expect(linkIndexPageAddDomain).toBeVisible();
|
||||||
await buttonFromHomePage.click();
|
await linkIndexPageAddDomain.click();
|
||||||
|
|
||||||
await expect(buttonFromHomePage).toBeHidden();
|
await expect(linkIndexPageAddDomain).toBeHidden();
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('heading', {
|
page.getByRole('heading', {
|
||||||
@@ -27,9 +50,7 @@ test.describe('Add Mail Domains', () => {
|
|||||||
}),
|
}),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
|
|
||||||
const form = page.locator('form');
|
await expect(inputName).toBeVisible();
|
||||||
|
|
||||||
await expect(form.getByLabel('Domain name')).toBeVisible();
|
|
||||||
|
|
||||||
await expect(page.getByText('Example: saint-laurent.fr')).toBeVisible();
|
await expect(page.getByText('Example: saint-laurent.fr')).toBeVisible();
|
||||||
|
|
||||||
@@ -46,38 +67,25 @@ test.describe('Add Mail Domains', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('checks the cancel button interaction', async ({ page }) => {
|
test('checks the cancel button interaction', async ({ page }) => {
|
||||||
await page.goto('/mail-domains');
|
await page.goto('/mail-domains/');
|
||||||
|
|
||||||
const buttonFromHomePage = page.getByRole('button', {
|
const { linkIndexPageAddDomain, buttonCancel } = getElements(page);
|
||||||
name: 'Add a mail domain',
|
|
||||||
});
|
|
||||||
await buttonFromHomePage.click();
|
|
||||||
|
|
||||||
await expect(buttonFromHomePage).toBeHidden();
|
await linkIndexPageAddDomain.click();
|
||||||
|
await buttonCancel.click();
|
||||||
|
|
||||||
await page
|
await expect(buttonCancel).toBeHidden();
|
||||||
.getByRole('button', {
|
|
||||||
name: 'Cancel',
|
|
||||||
})
|
|
||||||
.click();
|
|
||||||
|
|
||||||
await expect(buttonFromHomePage).toBeVisible();
|
await expect(linkIndexPageAddDomain).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('checks form invalid status', async ({ page }) => {
|
test('checks form invalid status', async ({ page }) => {
|
||||||
await page.goto('/mail-domains');
|
await page.goto('/mail-domains/');
|
||||||
|
|
||||||
const buttonFromHomePage = page.getByRole('button', {
|
const { linkIndexPageAddDomain, inputName, buttonSubmit } =
|
||||||
name: 'Add a mail domain',
|
getElements(page);
|
||||||
});
|
|
||||||
await buttonFromHomePage.click();
|
|
||||||
|
|
||||||
const form = page.locator('form');
|
await linkIndexPageAddDomain.click();
|
||||||
|
|
||||||
const inputName = form.getByLabel('Domain name');
|
|
||||||
const buttonSubmit = page.getByRole('button', {
|
|
||||||
name: 'Add the domain',
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(inputName).toBeVisible();
|
await expect(inputName).toBeVisible();
|
||||||
await expect(page.getByText('Example: saint-laurent.fr')).toBeVisible();
|
await expect(page.getByText('Example: saint-laurent.fr')).toBeVisible();
|
||||||
@@ -105,18 +113,17 @@ test.describe('Add Mail Domains', () => {
|
|||||||
const mailDomainName = randomName('versailles.fr', browserName, 1)[0];
|
const mailDomainName = randomName('versailles.fr', browserName, 1)[0];
|
||||||
const mailDomainSlug = mailDomainName.replace('.', '');
|
const mailDomainSlug = mailDomainName.replace('.', '');
|
||||||
|
|
||||||
await page.goto('/mail-domains');
|
await page.goto('/mail-domains/');
|
||||||
|
|
||||||
const panel = page.getByLabel('Mail domains panel').first();
|
const { linkIndexPageAddDomain, inputName, buttonSubmit } =
|
||||||
|
getElements(page);
|
||||||
|
|
||||||
await panel.getByRole('link', { name: 'Add a mail domain' }).click();
|
await linkIndexPageAddDomain.click();
|
||||||
|
|
||||||
const form = page.locator('form');
|
await inputName.fill(mailDomainName);
|
||||||
|
await buttonSubmit.click();
|
||||||
|
|
||||||
await form.getByLabel('Domain name').fill(mailDomainName);
|
await expect(page).toHaveURL(`/mail-domains/${mailDomainSlug}/`);
|
||||||
await page.getByRole('button', { name: 'Add the domain' }).click();
|
|
||||||
|
|
||||||
await expect(page).toHaveURL(`/mail-domains\/${mailDomainSlug}/`);
|
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('heading', {
|
page.getByRole('heading', {
|
||||||
@@ -125,41 +132,140 @@ test.describe('Add Mail Domains', () => {
|
|||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('checks error when duplicate mail domain', async ({
|
test('checks form submits at "Enter" key press', async ({ page }) => {
|
||||||
|
void page.route('**/api/v1.0/mail-domains/', (route) => {
|
||||||
|
if (route.request().method() === 'POST') {
|
||||||
|
void route.fulfill({
|
||||||
|
json: {
|
||||||
|
id: '2ebcfcfb-1dfa-4ed1-8e4a-554c63307b7c',
|
||||||
|
name: 'enter.fr',
|
||||||
|
slug: 'enterfr',
|
||||||
|
status: 'pending',
|
||||||
|
abilities: {
|
||||||
|
get: true,
|
||||||
|
patch: true,
|
||||||
|
put: true,
|
||||||
|
post: true,
|
||||||
|
delete: true,
|
||||||
|
manage_accesses: true,
|
||||||
|
},
|
||||||
|
created_at: '2024-08-21T10:55:21.081994Z',
|
||||||
|
updated_at: '2024-08-21T10:55:21.082109Z',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
void route.continue();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.goto('/mail-domains/');
|
||||||
|
|
||||||
|
const { linkIndexPageAddDomain, inputName } = getElements(page);
|
||||||
|
|
||||||
|
await linkIndexPageAddDomain.click();
|
||||||
|
|
||||||
|
await inputName.fill('enter.fr');
|
||||||
|
await page.keyboard.press('Enter');
|
||||||
|
|
||||||
|
await expect(page).toHaveURL(`/mail-domains/enterfr/`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('checks error when duplicate mail domain name', async ({
|
||||||
page,
|
page,
|
||||||
browserName,
|
browserName,
|
||||||
}) => {
|
}) => {
|
||||||
await page.goto('/mail-domains');
|
await page.goto('/mail-domains/');
|
||||||
|
|
||||||
const panel = page.getByLabel('Mail domains panel').first();
|
const { linkIndexPageAddDomain, inputName, buttonSubmit } =
|
||||||
const additionLink = panel.getByRole('link', {
|
getElements(page);
|
||||||
name: 'Add a mail domain',
|
|
||||||
});
|
|
||||||
const form = page.locator('form');
|
|
||||||
const inputName = form.getByLabel('Domain name');
|
|
||||||
const submitButton = page.getByRole('button', {
|
|
||||||
name: 'Add the domain',
|
|
||||||
});
|
|
||||||
|
|
||||||
const mailDomainName = randomName('duplicate.fr', browserName, 1)[0];
|
const mailDomainName = randomName('duplicate.fr', browserName, 1)[0];
|
||||||
const mailDomainSlug = mailDomainName.replace('.', '');
|
const mailDomainSlug = mailDomainName.replace('.', '');
|
||||||
|
|
||||||
await additionLink.click();
|
await linkIndexPageAddDomain.click();
|
||||||
await inputName.fill(mailDomainName);
|
await inputName.fill(mailDomainName);
|
||||||
await submitButton.click();
|
await buttonSubmit.click();
|
||||||
|
|
||||||
await expect(page).toHaveURL(`/mail-domains\/${mailDomainSlug}\/`);
|
await expect(page).toHaveURL(`/mail-domains\/${mailDomainSlug}\/`);
|
||||||
|
|
||||||
await additionLink.click();
|
await linkIndexPageAddDomain.click();
|
||||||
|
|
||||||
await inputName.fill(mailDomainName);
|
await inputName.fill(mailDomainName);
|
||||||
await submitButton.click();
|
await buttonSubmit.click();
|
||||||
|
|
||||||
|
await expect(page).toHaveURL(/mail-domains\//);
|
||||||
await expect(
|
await expect(
|
||||||
page.getByText(
|
page.getByText(
|
||||||
'This mail domain is already used. Please, choose another one.',
|
'This mail domain is already used. Please, choose another one.',
|
||||||
),
|
),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
|
await expect(inputName).toBeFocused();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('checks error when duplicate mail domain slug', async ({
|
||||||
|
page,
|
||||||
|
browserName,
|
||||||
|
}) => {
|
||||||
|
await page.goto('/mail-domains/');
|
||||||
|
|
||||||
|
const { linkIndexPageAddDomain, inputName, buttonSubmit } =
|
||||||
|
getElements(page);
|
||||||
|
|
||||||
|
const mailDomainSlug = randomName('duplicate', browserName, 1)[0];
|
||||||
|
|
||||||
|
await linkIndexPageAddDomain.click();
|
||||||
|
await inputName.fill(mailDomainSlug);
|
||||||
|
await buttonSubmit.click();
|
||||||
|
|
||||||
|
await expect(page).toHaveURL(`/mail-domains\/${mailDomainSlug}\/`);
|
||||||
|
|
||||||
|
await linkIndexPageAddDomain.click();
|
||||||
|
|
||||||
|
await inputName.fill(mailDomainSlug);
|
||||||
|
await buttonSubmit.click();
|
||||||
|
|
||||||
|
await expect(page).toHaveURL(/mail-domains\//);
|
||||||
|
await expect(
|
||||||
|
page.getByText(
|
||||||
|
'This mail domain is already used. Please, choose another one.',
|
||||||
|
),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(inputName).toBeFocused();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('checks unknown api error causes are displayed', async ({ page }) => {
|
||||||
|
await page.route(
|
||||||
|
'**/api/v1.0/mail-domains/',
|
||||||
|
async (route) => {
|
||||||
|
if (route.request().method() === 'POST') {
|
||||||
|
await route.fulfill({
|
||||||
|
status: 500,
|
||||||
|
json: {
|
||||||
|
unknown_error: ['Unknown error from server'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ times: 1 },
|
||||||
|
);
|
||||||
|
|
||||||
|
await page.goto('/mail-domains/');
|
||||||
|
|
||||||
|
const { linkIndexPageAddDomain, inputName, buttonSubmit } =
|
||||||
|
getElements(page);
|
||||||
|
|
||||||
|
await linkIndexPageAddDomain.click();
|
||||||
|
await inputName.fill('server-error.fr');
|
||||||
|
await buttonSubmit.click();
|
||||||
|
|
||||||
|
await expect(page).toHaveURL(/mail-domains\//);
|
||||||
|
await expect(
|
||||||
|
page.getByText(
|
||||||
|
'Your request cannot be processed because the server is experiencing an error. If the problem ' +
|
||||||
|
'persists, please contact our support to resolve the issue: suiteterritoriale@anct.gouv.fr.',
|
||||||
|
),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(inputName).toBeFocused();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('checks 404 on mail-domains/[slug] page', async ({ page }) => {
|
test('checks 404 on mail-domains/[slug] page', async ({ page }) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user