diff --git a/CHANGELOG.md b/CHANGELOG.md index f027083..8b06bde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ and this project adheres to ## [Unreleased] +### Added + +- ✨(frontend) user can add mail domains + ## [1.0.0] - 2024-08-09 ### Added diff --git a/src/frontend/apps/desk/src/features/teams/teams-panel/assets/icon-add.svg b/src/frontend/apps/desk/src/assets/icons/icon-add.svg similarity index 100% rename from src/frontend/apps/desk/src/features/teams/teams-panel/assets/icon-add.svg rename to src/frontend/apps/desk/src/assets/icons/icon-add.svg diff --git a/src/frontend/apps/desk/src/features/mail-domains/assets/icon-open-close.svg b/src/frontend/apps/desk/src/assets/icons/icon-open-close.svg similarity index 100% rename from src/frontend/apps/desk/src/features/mail-domains/assets/icon-open-close.svg rename to src/frontend/apps/desk/src/assets/icons/icon-open-close.svg diff --git a/src/frontend/apps/desk/src/features/mail-domains/assets/icon-sort.svg b/src/frontend/apps/desk/src/assets/icons/icon-sort.svg similarity index 100% rename from src/frontend/apps/desk/src/features/mail-domains/assets/icon-sort.svg rename to src/frontend/apps/desk/src/assets/icons/icon-sort.svg diff --git a/src/frontend/apps/desk/src/features/mail-domains/api/index.tsx b/src/frontend/apps/desk/src/features/mail-domains/api/index.tsx index 7c08880..2c5993d 100644 --- a/src/frontend/apps/desk/src/features/mail-domains/api/index.tsx +++ b/src/frontend/apps/desk/src/features/mail-domains/api/index.tsx @@ -2,3 +2,4 @@ export * from './useMailDomains'; export * from './useMailDomain'; export * from './useCreateMailbox'; export * from './useMailboxes'; +export * from './useCreateMailDomain'; diff --git a/src/frontend/apps/desk/src/features/mail-domains/api/useCreateMailDomain.tsx b/src/frontend/apps/desk/src/features/mail-domains/api/useCreateMailDomain.tsx new file mode 100644 index 0000000..fe1f721 --- /dev/null +++ b/src/frontend/apps/desk/src/features/mail-domains/api/useCreateMailDomain.tsx @@ -0,0 +1,41 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import { APIError, errorCauses, fetchAPI } from '@/api'; +import { MailDomain } from '@/features/mail-domains'; + +import { KEY_LIST_MAIL_DOMAIN } from './useMailDomains'; + +export const createMailDomain = async (name: string): Promise => { + const response = await fetchAPI(`mail-domains/`, { + method: 'POST', + body: JSON.stringify({ + name, + }), + }); + + if (!response.ok) { + throw new APIError( + 'Failed to add the mail domain', + await errorCauses(response), + ); + } + + return response.json() as Promise; +}; + +export function useCreateMailDomain({ + onSuccess, +}: { + onSuccess: (data: MailDomain) => void; +}) { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: createMailDomain, + onSuccess: (data) => { + void queryClient.invalidateQueries({ + queryKey: [KEY_LIST_MAIL_DOMAIN], + }); + onSuccess(data); + }, + }); +} diff --git a/src/frontend/apps/desk/src/features/mail-domains/api/useMailDomain.tsx b/src/frontend/apps/desk/src/features/mail-domains/api/useMailDomain.tsx index aa2c450..95ef86a 100644 --- a/src/frontend/apps/desk/src/features/mail-domains/api/useMailDomain.tsx +++ b/src/frontend/apps/desk/src/features/mail-domains/api/useMailDomain.tsx @@ -13,7 +13,7 @@ type MailDomainResponse = MailDomain; export const getMailDomain = async ({ slug, }: MailDomainParams): Promise => { - const response = await fetchAPI(`mail-domains/${slug}`); + const response = await fetchAPI(`mail-domains/${slug}/`); if (!response.ok) { throw new APIError( diff --git a/src/frontend/apps/desk/src/features/mail-domains/api/useMailDomains.tsx b/src/frontend/apps/desk/src/features/mail-domains/api/useMailDomains.tsx index a9216a2..08ca730 100644 --- a/src/frontend/apps/desk/src/features/mail-domains/api/useMailDomains.tsx +++ b/src/frontend/apps/desk/src/features/mail-domains/api/useMailDomains.tsx @@ -40,7 +40,7 @@ export const getMailDomains = async ({ return response.json() as Promise; }; -export const KEY_LIST_MAIL_DOMAINS = 'mail-domains'; +export const KEY_LIST_MAIL_DOMAIN = 'mail-domains'; export function useMailDomains( param: MailDomainsParams, @@ -60,7 +60,7 @@ export function useMailDomains( number >({ initialPageParam: 1, - queryKey: [KEY_LIST_MAIL_DOMAINS, param], + queryKey: [KEY_LIST_MAIL_DOMAIN, param], queryFn: ({ pageParam }) => getMailDomains({ ...param, page: pageParam }), getNextPageParam(lastPage, allPages) { return lastPage.next ? allPages.length + 1 : undefined; diff --git a/src/frontend/apps/desk/src/features/mail-domains/components/MailDomainsContent.tsx b/src/frontend/apps/desk/src/features/mail-domains/components/MailDomainsContent.tsx index f43f8a0..5d8ecb7 100644 --- a/src/frontend/apps/desk/src/features/mail-domains/components/MailDomainsContent.tsx +++ b/src/frontend/apps/desk/src/features/mail-domains/components/MailDomainsContent.tsx @@ -1,16 +1,18 @@ import { UUID } from 'crypto'; import { + Alert, Button, DataGrid, Loader, SortModel, + VariantType, usePagination, } from '@openfun/cunningham-react'; import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Box, Card, Text, TextErrors } from '@/components'; +import { Box, Card, Text, TextErrors, TextStyled } from '@/components'; import { useMailboxes } from '../api/useMailboxes'; import { default as MailDomainsLogo } from '../assets/mail-domains-logo.svg'; @@ -35,12 +37,6 @@ const defaultOrderingMapping: Record = { email: 'local_part', }; -/** - * Formats the sorting model based on a given mapping. - * @param {SortModelItem} sortModel The sorting model item containing field and sort direction. - * @param {Record} mapping The mapping object to map field names. - * @returns {string} The formatted sorting string. - */ function formatSortModel( sortModel: SortModelItem, mapping = defaultOrderingMapping, @@ -98,9 +94,8 @@ export function MailDomainsContent({ mailDomain }: { mailDomain: MailDomain }) { ) : null} void; - abilities: MailDomain['abilities']; + mailDomain: MailDomain; + showMailBoxCreationForm: (value: boolean) => void; }) => { const { t } = useTranslation(); return ( - <> + - - - {abilities.post ? ( - - ) : null} + + + - + {mailDomain?.abilities.post && ( + + + + + + )} + + ); +}; + +const AlertStatus = ({ status }: { status: MailDomain['status'] }) => { + const { t } = useTranslation(); + + const getStatusAlertProps = (status?: string) => { + switch (status) { + case 'pending': + return { + variant: VariantType.WARNING, + message: t( + 'Your domain name is being validated. ' + + 'You will not be able to create mailboxes until your domain name has been validated by our team.', + ), + }; + case 'disabled': + return { + variant: VariantType.NEUTRAL, + message: t( + 'This domain name is deactivated. No new mailboxes can be created.', + ), + }; + case 'failed': + return { + variant: VariantType.ERROR, + message: ( + + {t( + 'The domain name encounters an error. Please contact our support team to solve the problem:', + )}{' '} + + suiteterritoriale@anct.gouv.fr + + . + + ), + }; + } + }; + + const alertStatusProps = getStatusAlertProps(status); + + if (!alertStatusProps) { + return null; + } + + return ( + + {alertStatusProps.message} + ); }; diff --git a/src/frontend/apps/desk/src/features/mail-domains/components/ModalAddMailDomain.tsx b/src/frontend/apps/desk/src/features/mail-domains/components/ModalAddMailDomain.tsx new file mode 100644 index 0000000..c3fef7c --- /dev/null +++ b/src/frontend/apps/desk/src/features/mail-domains/components/ModalAddMailDomain.tsx @@ -0,0 +1,143 @@ +import { zodResolver } from '@hookform/resolvers/zod'; +import { + Button, + Input, + Loader, + Modal, + ModalSize, +} from '@openfun/cunningham-react'; +import { useRouter } from 'next/navigation'; +import React from 'react'; +import { Controller, FormProvider, useForm } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; +import { z } from 'zod'; + +import { Box, StyledLink, Text, TextErrors } from '@/components'; +import { useCreateMailDomain } from '@/features/mail-domains'; + +import { default as MailDomainsLogo } from '../assets/mail-domains-logo.svg'; + +const FORM_ID = 'form-add-mail-domain'; + +export const ModalCreateMailDomain = () => { + const { t } = useTranslation(); + const router = useRouter(); + + const createMailDomainValidationSchema = z.object({ + name: z.string().min(1, t('Example: saint-laurent.fr')), + }); + + const methods = useForm<{ name: string }>({ + delayError: 0, + defaultValues: { + name: '', + }, + mode: 'onChange', + reValidateMode: 'onChange', + resolver: zodResolver(createMailDomainValidationSchema), + }); + + const { + mutate: createMailDomain, + isPending, + error, + } = useCreateMailDomain({ + onSuccess: (mailDomain) => { + router.push(`/mail-domains/${mailDomain.slug}`); + }, + }); + + const onSubmitCallback = () => { + void methods.handleSubmit(({ name }, event) => { + event?.preventDefault(); + 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) { + return null; + } + + return ( + + + + } + hideCloseButton + closeOnClickOutside + closeOnEsc + onClose={() => router.push('/mail-domains')} + rightActions={ + + } + size={ModalSize.MEDIUM} + title={ + <> + + ); +}; diff --git a/src/frontend/apps/desk/src/features/mail-domains/components/panel/Panel.tsx b/src/frontend/apps/desk/src/features/mail-domains/components/panel/Panel.tsx index 8c009f3..10b6082 100644 --- a/src/frontend/apps/desk/src/features/mail-domains/components/panel/Panel.tsx +++ b/src/frontend/apps/desk/src/features/mail-domains/components/panel/Panel.tsx @@ -1,11 +1,10 @@ import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; +import IconOpenClose from '@/assets/icons/icon-open-close.svg'; import { Box, BoxButton, Text } from '@/components'; import { useCunninghamTheme } from '@/cunningham'; -import IconOpenClose from '../../assets/icon-open-close.svg'; - import { ItemList } from './ItemList'; import { PanelActions } from './PanelActions'; @@ -39,7 +38,7 @@ export const Panel = () => { transition: ${transition}; `} $height="inherit" - aria-label="mail domains panel" + aria-label={t('Mail domains panel')} {...closedOverridingStyles} > { const { t } = useTranslation(); const { changeOrdering, ordering } = useMailDomainsStore(); @@ -42,6 +42,16 @@ export const PanelActions = () => { > + + + + + ); }; diff --git a/src/frontend/apps/desk/src/features/mail-domains/components/panel/PanelItem.tsx b/src/frontend/apps/desk/src/features/mail-domains/components/panel/PanelItem.tsx index 9eac2fc..1065ce4 100644 --- a/src/frontend/apps/desk/src/features/mail-domains/components/panel/PanelItem.tsx +++ b/src/frontend/apps/desk/src/features/mail-domains/components/panel/PanelItem.tsx @@ -1,5 +1,6 @@ import { useRouter } from 'next/router'; import React from 'react'; +import { useTranslation } from 'react-i18next'; import { Box, StyledLink, Text } from '@/components'; import { useCunninghamTheme } from '@/cunningham'; @@ -11,6 +12,7 @@ interface MailDomainProps { } export const PanelMailDomains = ({ mailDomain }: MailDomainProps) => { + const { t } = useTranslation(); const { colorsTokens } = useCunninghamTheme(); const { query: { slug }, @@ -18,10 +20,23 @@ export const PanelMailDomains = ({ mailDomain }: MailDomainProps) => { const isActive = mailDomain.slug === slug; + const getStatusText = (status: MailDomain['status']) => { + switch (status) { + case 'pending': + return t('[pending]'); + case 'enabled': + return t('[enabled]'); + case 'disabled': + return t('[disabled]'); + case 'failed': + return t('[failed]'); + } + }; + const activeStyle = ` border-right: 4px solid ${colorsTokens()['primary-600']}; background: ${colorsTokens()['primary-400']}; - span{ + span { color: ${colorsTokens()['primary-text']}; } `; @@ -31,12 +46,14 @@ export const PanelMailDomains = ({ mailDomain }: MailDomainProps) => { border-right: 4px solid ${colorsTokens()['primary-400']}; background: ${colorsTokens()['primary-300']}; - span{ + span { color: ${colorsTokens()['primary-text']}; } } `; + const statusText = getStatusText(mailDomain.status); + return ( { > - - diff --git a/src/frontend/apps/desk/src/features/mail-domains/types.tsx b/src/frontend/apps/desk/src/features/mail-domains/types.tsx index 1b064ba..039c57d 100644 --- a/src/frontend/apps/desk/src/features/mail-domains/types.tsx +++ b/src/frontend/apps/desk/src/features/mail-domains/types.tsx @@ -6,6 +6,7 @@ export interface MailDomain { created_at: string; updated_at: string; slug: string; + status: 'pending' | 'enabled' | 'failed' | 'disabled'; abilities: { get: boolean; patch: boolean; diff --git a/src/frontend/apps/desk/src/features/teams/teams-panel/assets/icon-open-close.svg b/src/frontend/apps/desk/src/features/teams/teams-panel/assets/icon-open-close.svg deleted file mode 100644 index c87764a..0000000 --- a/src/frontend/apps/desk/src/features/teams/teams-panel/assets/icon-open-close.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/frontend/apps/desk/src/features/teams/teams-panel/assets/icon-sort.svg b/src/frontend/apps/desk/src/features/teams/teams-panel/assets/icon-sort.svg deleted file mode 100644 index ac4565d..0000000 --- a/src/frontend/apps/desk/src/features/teams/teams-panel/assets/icon-sort.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - diff --git a/src/frontend/apps/desk/src/features/teams/teams-panel/components/Panel.tsx b/src/frontend/apps/desk/src/features/teams/teams-panel/components/Panel.tsx index b0ef91e..f99843d 100644 --- a/src/frontend/apps/desk/src/features/teams/teams-panel/components/Panel.tsx +++ b/src/frontend/apps/desk/src/features/teams/teams-panel/components/Panel.tsx @@ -1,11 +1,10 @@ import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; +import IconOpenClose from '@/assets/icons/icon-open-close.svg'; import { Box, BoxButton, Text } from '@/components'; import { useCunninghamTheme } from '@/cunningham'; -import IconOpenClose from '../assets/icon-open-close.svg'; - import { PanelActions } from './PanelActions'; import { TeamList } from './TeamList'; diff --git a/src/frontend/apps/desk/src/features/teams/teams-panel/components/PanelActions.tsx b/src/frontend/apps/desk/src/features/teams/teams-panel/components/PanelActions.tsx index bbdb1a5..2397b27 100644 --- a/src/frontend/apps/desk/src/features/teams/teams-panel/components/PanelActions.tsx +++ b/src/frontend/apps/desk/src/features/teams/teams-panel/components/PanelActions.tsx @@ -1,12 +1,12 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; +import IconAdd from '@/assets/icons/icon-add.svg'; +import IconSort from '@/assets/icons/icon-sort.svg'; import { Box, BoxButton, StyledLink } from '@/components'; import { useCunninghamTheme } from '@/cunningham'; import { TeamsOrdering } from '@/features/teams/team-management/api'; -import IconAdd from '../assets/icon-add.svg'; -import IconSort from '../assets/icon-sort.svg'; import { useTeamStore } from '../store/useTeamsStore'; export const PanelActions = () => { diff --git a/src/frontend/apps/desk/src/i18n/translations.json b/src/frontend/apps/desk/src/i18n/translations.json index 80e6fab..962a1a6 100644 --- a/src/frontend/apps/desk/src/i18n/translations.json +++ b/src/frontend/apps/desk/src/i18n/translations.json @@ -14,7 +14,9 @@ "Add a member": "Ajouter un membre", "Add a team": "Ajouter un groupe", "Add members to the team": "Ajouter des membres à l'équipe", + "Add the domain": "Ajouter le domaine", "Add to group": "Ajouter au groupe", + "Add your mail domain": "Ajouter votre nom de domaine", "Address: National Agency for Territorial Cohesion - 20, avenue de Ségur TSA 10717 75 334 Paris Cedex 07 Paris": "Adresse : Agence Nationale de la Cohésion des Territoires - 20, avenue de Ségur TSA 10717 75 334 Paris Cedex 07", "Administration": "Administration", "All fields are mandatory.": "Tous les champs sont obligatoires.", @@ -28,6 +30,7 @@ "Close the teams panel": "Fermer le panneau des groupes", "Compliance status": "État de conformité", "Confirm deletion": "Confirmer la suppression", + "Contact our support at \"suiteterritoriale@anct.gouv.fr\"": "Contacter notre support à \"suiteterritoriale@anct.gouv.fr\"", "Content modal to delete the team": "Contenu modal pour supprimer le groupe", "Content modal to update the team": "Contenu modal pour mettre à jour le groupe", "Cookies placed": "Cookies déposés", @@ -45,12 +48,14 @@ "Defender of Rights - Free response - 71120 75342 Paris CEDEX 07": "Défenseur des droits\nLibre réponse 71120 75342 Paris CEDEX 07", "Delete the team": "Supprimer le groupe", "Deleting the {{teamName}} team": "Suppression du groupe {{teamName}}", + "Domain name": "Nom de domaine", "E-mail:": "E-mail:", "E.g. : jean.dupont@mail.fr": "Ex. : jean.dupont@mail.fr", "Email address prefix": "Préfixe de l'adresse mail", "Emails": "Emails", "Empty team icon": "Icône équipe vide", "Enter the new name of the selected team": "Entrez le nouveau nom du groupe sélectionné", + "Example: saint-laurent.fr": "Exemple : saint-laurent.fr", "Failed to add {{name}} in the team": "Impossible d'ajouter {{name}} au groupe", "Failed to create the invitation for {{email}}": "Impossible de créer l'invitation pour {{email}}", "Find a member to add to the team": "Trouver un membre à ajouter au groupe", @@ -78,6 +83,7 @@ "List members card": "Carte liste des membres", "Logout": "Se déconnecter", "Mail Domains": "Domaines de messagerie", + "Mail domains panel": "Panel des domaines de messagerie", "Mailbox created!": "Boîte mail créée !", "Mailboxes list": "Liste des boîtes mail", "Marianne Logo": "Logo Marianne", @@ -127,6 +133,7 @@ "Team name": "Nom du groupe", "Teams": "Équipes", "The National Agency for Territorial Cohesion undertakes to make its\n service accessible, in accordance with article 47 of law no. 2005-102\n of February 11, 2005.": "L'Agence Nationale de la Cohésion des Territoires s’engage à rendre son service accessible, conformément à l’article 47 de la loi n° 2005-102 du 11 février 2005.", + "The domain name encounters an error. Please contact our support team to solve the problem:": "Le nom de domaine rencontre une erreur. Veuillez contacter notre support pour résoudre le problème :", "The member has been removed from the team": "Le membre a été supprimé de votre groupe", "The role has been updated": "Le rôle a bien été mis à jour", "The team has been removed.": "Le groupe a été supprimé.", @@ -134,7 +141,9 @@ "The team in charge of the digital workspace \"La Suite numérique\" can be contacted directly at": "L'équipe responsable de l'espace de travail numérique \"La Suite numérique\" peut être contactée directement à l'adresse", "This accessibility statement applies to La Régie (Suite Territoriale)": "Cette déclaration d’accessibilité s’applique à La Régie (Suite Territoriale)", "This allows us to measure the number of visits and understand which pages are the most viewed.": "Cela nous permet de mesurer le nombre de visites et de comprendre quelles pages sont les plus consultées.", + "This domain name is deactivated. No new mailboxes can be created.": "Ce nom de domaine est désactivé. Aucune nouvelle boîte mail ne peut être créée.", "This email prefix is already used.": "Ce préfixe d'email est déjà utilisé.", + "This mail domain is already used. Please, choose another one.": "Ce domaine de messagerie est déjà utilisé. Veuillez en choisir un autre.", "This procedure is to be used in the following case: you have reported to the website \n manager an accessibility defect which prevents you from accessing content or one of the \n portal's services and you have not obtained a satisfactory response.": "Cette procédure est à utiliser dans le cas suivant : vous avez signalé au responsable du site internet un défaut d’accessibilité qui vous empêche d’accéder à un contenu ou à un des services du portail et vous n’avez pas obtenu de réponse satisfaisante.", "This site does not display a cookie consent banner, why?": "Ce site n'affiche pas de bannière de consentement des cookies, pourquoi?", "This site places a small text file (a \"cookie\") on your computer when you visit it.": "Ce site place un petit fichier texte (un « cookie ») sur votre ordinateur lorsque vous le visitez.", @@ -154,6 +163,11 @@ "You cannot remove other owner.": "Vous ne pouvez pas supprimer un autre propriétaire.", "You cannot update the role of other owner.": "Vous ne pouvez pas mettre à jour les rôles d'autre propriétaire.", "You must have minimum 1 character": "Vous devez entrer au moins 1 caractère", + "Your domain name is being validated. You will not be able to create mailboxes until your domain name has been validated by our team.": "Votre nom de domaine est en cours de validation. Vous ne pourrez créer de boîtes mail que lorsque votre nom de domaine sera validé par notre équipe.", + "[disabled]": "[désactivé]", + "[enabled]": "[actif]", + "[failed]": "[erroné]", + "[pending]": "[en attente]", "accessibility-contact-defenseurdesdroits": "Contacter le délégué du<1>Défenseur des droits dans votre région", "accessibility-form-defenseurdesdroits": "Écrire un message au<1>Défenseur des droits", "mail domains list loading": "chargement de la liste des domaines de messagerie", diff --git a/src/frontend/apps/desk/src/pages/mail-domains/add.tsx b/src/frontend/apps/desk/src/pages/mail-domains/add.tsx new file mode 100644 index 0000000..2efb607 --- /dev/null +++ b/src/frontend/apps/desk/src/pages/mail-domains/add.tsx @@ -0,0 +1,20 @@ +import React, { ReactElement } from 'react'; + +import { Box } from '@/components'; +import { MailDomainsLayout } from '@/features/mail-domains'; +import { ModalCreateMailDomain } from '@/features/mail-domains/components/ModalAddMailDomain'; +import { NextPageWithLayout } from '@/types/next'; + +const Page: NextPageWithLayout = () => { + return ( + + + + ); +}; + +Page.getLayout = function getLayout(page: ReactElement) { + return {page}; +}; + +export default Page; diff --git a/src/frontend/apps/desk/src/pages/mail-domains/index.tsx b/src/frontend/apps/desk/src/pages/mail-domains/index.tsx index 1a96904..622c0d7 100644 --- a/src/frontend/apps/desk/src/pages/mail-domains/index.tsx +++ b/src/frontend/apps/desk/src/pages/mail-domains/index.tsx @@ -1,10 +1,29 @@ -import { ReactElement } from 'react'; +import { Button } from '@openfun/cunningham-react'; +import { useRouter } from 'next/router'; +import type { ReactElement } from 'react'; +import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; +import { Box } from '@/components'; import { MailDomainsLayout } from '@/features/mail-domains'; import { NextPageWithLayout } from '@/types/next'; +const StyledButton = styled(Button)` + width: fit-content; +`; + const Page: NextPageWithLayout = () => { - return null; + const { t } = useTranslation(); + + const router = useRouter(); + + return ( + + void router.push('/mail-domains/add')}> + {t('Add your mail domain')} + + + ); }; Page.getLayout = function getLayout(page: ReactElement) { diff --git a/src/frontend/apps/e2e/__tests__/app-desk/mail-domain-create-mailbox.spec.ts b/src/frontend/apps/e2e/__tests__/app-desk/mail-domain-create-mailbox.spec.ts index 9393565..646acbd 100644 --- a/src/frontend/apps/e2e/__tests__/app-desk/mail-domain-create-mailbox.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-desk/mail-domain-create-mailbox.spec.ts @@ -15,6 +15,7 @@ const mailDomainsFixtures: MailDomain[] = [ created_at: currentDateIso, updated_at: currentDateIso, slug: 'domainfr', + status: 'enabled', abilities: { get: true, patch: true, @@ -30,6 +31,7 @@ const mailDomainsFixtures: MailDomain[] = [ created_at: currentDateIso, updated_at: currentDateIso, slug: 'mailsfr', + status: 'enabled', abilities: { get: true, patch: true, @@ -45,6 +47,7 @@ const mailDomainsFixtures: MailDomain[] = [ created_at: currentDateIso, updated_at: currentDateIso, slug: 'versaillesnet', + status: 'enabled', abilities: { get: true, patch: true, @@ -60,6 +63,7 @@ const mailDomainsFixtures: MailDomain[] = [ created_at: currentDateIso, updated_at: currentDateIso, slug: 'parisfr', + status: 'enabled', abilities: { get: true, patch: true, @@ -95,7 +99,7 @@ const interceptCommonApiRequests = (page: Page) => { }); }); - void page.route('**/api/v1.0/mail-domains/domainfr', (route) => { + void page.route('**/api/v1.0/mail-domains/domainfr/', (route) => { void route.fulfill({ json: mailDomainDomainFrFixture, }); @@ -275,7 +279,7 @@ test.describe('Mail domain create mailbox', () => { }); }); - void page.route('**/api/v1.0/mail-domains/domainfr', (route) => { + void page.route('**/api/v1.0/mail-domains/domainfr/', (route) => { void route.fulfill({ json: localMailDomainDomainFr, }); diff --git a/src/frontend/apps/e2e/__tests__/app-desk/mail-domain.spec.ts b/src/frontend/apps/e2e/__tests__/app-desk/mail-domain.spec.ts index 20f78cf..c273fd5 100644 --- a/src/frontend/apps/e2e/__tests__/app-desk/mail-domain.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-desk/mail-domain.spec.ts @@ -5,74 +5,113 @@ import { keyCloakSignIn } from './common'; const currentDateIso = new Date().toISOString(); -const mailDomainsFixtures: MailDomain[] = [ - { - name: 'domain.fr', - id: '456ac6ca-0402-4615-8005-69bc1efde43f', - created_at: currentDateIso, - updated_at: currentDateIso, - slug: 'domainfr', - abilities: { - get: true, - patch: true, - put: true, - post: true, - delete: true, - manage_accesses: true, - }, - }, - { - name: 'mails.fr', - id: '456ac6ca-0402-4615-8005-69bc1efde43e', - created_at: currentDateIso, - updated_at: currentDateIso, - slug: 'mailsfr', - abilities: { - get: true, - patch: true, - put: true, - post: true, - delete: true, - manage_accesses: true, - }, - }, - { - name: 'versailles.net', - id: '456ac6ca-0402-4615-8005-69bc1efde43g', - created_at: currentDateIso, - updated_at: currentDateIso, - slug: 'versaillesnet', - abilities: { - get: true, - patch: true, - put: true, - post: true, - delete: true, - manage_accesses: true, - }, - }, - { - name: 'paris.fr', - id: '456ac6ca-0402-4615-8005-69bc1efde43h', - created_at: currentDateIso, - updated_at: currentDateIso, - slug: 'parisfr', - abilities: { - get: true, - patch: true, - put: true, - post: true, - delete: true, - manage_accesses: true, - }, - }, -]; +const interceptCommonApiCalls = async ( + page: Page, + arrayMailDomains: MailDomain[], +) => { + const singleMailDomain = arrayMailDomains[0]; + await page.route('**/api/v1.0/mail-domains/?page=*', async (route) => { + await route.fulfill({ + json: { + count: arrayMailDomains.length, + next: null, + previous: null, + results: arrayMailDomains, + }, + }); + }); -const mailDomainDomainFrFixture = mailDomainsFixtures[0]; + await page.route('**/api/v1.0/mail-domains/domainfr/', async (route) => { + await route.fulfill({ + json: singleMailDomain, + }); + }); + await page.route( + '**/api/v1.0/mail-domains/domainfr/mailboxes/?page=1', + async (route) => { + await route.fulfill({ + json: { + count: 0, + next: null, + previous: null, + results: [], + }, + }); + }, + ); +}; const clickOnMailDomainsNavButton = async (page: Page): Promise => await page.locator('menu').first().getByLabel(`Mail Domains button`).click(); +const assertMailDomainUpperElementsAreVisible = async (page: Page) => { + await expect(page).toHaveURL(/mail-domains\//); + + await page.getByRole('listbox').first().getByText('domain.fr').click(); + await expect(page).toHaveURL(/mail-domains\/domainfr\//); + + await expect(page.getByRole('heading', { name: 'domain.fr' })).toBeVisible(); +}; + +const assertFilledMailboxesTableElementsAreVisible = async ( + page: Page, + domainFr: object & { name: string }, + multiLevelArrayMailboxes: object & Array<{ local_part: string }[]>, +) => { + await expect(page).toHaveURL(/mail-domains\//); + + await expect( + page.getByRole('button', { name: /Names/ }).first(), + ).toBeVisible(); + + await expect( + page.getByRole('button', { name: /Emails/ }).first(), + ).toBeVisible(); + + await Promise.all( + multiLevelArrayMailboxes[0].map((mailbox) => + expect( + page.getByText(`${mailbox.local_part}@${domainFr.name}`), + ).toBeVisible(), + ), + ); + + const table = page.locator('table'); + await expect(table).toBeVisible(); + + const tdNames = await table.getByText('John Doe').all(); + expect(tdNames.length).toEqual(20); + + await expect( + page.locator('.c__pagination__list').getByRole('button', { name: '1' }), + ).toBeVisible(); + + await expect( + page.locator('.c__pagination__list').getByText('navigate_next'), + ).toBeVisible(); + + await page + .locator('.c__pagination__list') + .getByRole('button', { name: '2' }) + .click(); + + await expect( + page.locator('.c__pagination__list').getByText('navigate_next'), + ).toBeHidden(); + + await expect( + page.locator('.c__pagination__list').getByText('navigate_before'), + ).toBeVisible(); + + await Promise.all( + multiLevelArrayMailboxes[1].map((mailbox) => + expect( + page.getByText(`${mailbox.local_part}@${domainFr.name}`), + ).toBeVisible(), + ), + ); +}; + test.describe('Mail domain', () => { test.beforeEach(async ({ page, browserName }) => { await page.goto('/'); @@ -103,195 +142,645 @@ test.describe('Mail domain', () => { }); }); - test('checks all the elements are visible when domain exist but contains no mailboxes', async ({ - page, - }) => { - const interceptApiCalls = async () => { - await page.route( - '**/api/v1.0/mail-domains/domainfr/mailboxes/?page=1', - async (route) => { - await route.fulfill({ - json: { - count: 0, - next: null, - previous: null, - results: [], - }, - }); - }, - ); - - await page.route('**/api/v1.0/mail-domains/domainfr**', async (route) => { - await route.fulfill({ - json: mailDomainDomainFrFixture, - }); - }); - await page.route('**/api/v1.0/mail-domains/?page=*', async (route) => { - await route.fulfill({ - json: { - count: mailDomainsFixtures.length, - next: null, - previous: null, - results: mailDomainsFixtures, + test.describe('user is administrator or owner', () => { + test.describe('mail domain is enabled', () => { + const mailDomainsFixtures: MailDomain[] = [ + { + name: 'domain.fr', + id: '456ac6ca-0402-4615-8005-69bc1efde43f', + created_at: currentDateIso, + updated_at: currentDateIso, + slug: 'domainfr', + status: 'enabled', + abilities: { + get: true, + patch: true, + put: true, + post: true, + delete: true, + manage_accesses: true, }, - }); + }, + { + name: 'mails.fr', + id: '456ac6ca-0402-4615-8005-69bc1efde43e', + created_at: currentDateIso, + updated_at: currentDateIso, + slug: 'mailsfr', + status: 'enabled', + abilities: { + get: true, + patch: true, + put: true, + post: true, + delete: true, + manage_accesses: true, + }, + }, + { + name: 'versailles.net', + id: '456ac6ca-0402-4615-8005-69bc1efde43g', + created_at: currentDateIso, + updated_at: currentDateIso, + slug: 'versaillesnet', + status: 'enabled', + abilities: { + get: true, + patch: true, + put: true, + post: true, + delete: true, + manage_accesses: true, + }, + }, + { + name: 'paris.fr', + id: '456ac6ca-0402-4615-8005-69bc1efde43h', + created_at: currentDateIso, + updated_at: currentDateIso, + slug: 'parisfr', + status: 'enabled', + abilities: { + get: true, + patch: true, + put: true, + post: true, + delete: true, + manage_accesses: true, + }, + }, + ]; + + test('checks all the elements are visible when domain exist but contains no mailboxes', async ({ + page, + }) => { + await interceptCommonApiCalls(page, mailDomainsFixtures); + + await clickOnMailDomainsNavButton(page); + + await assertMailDomainUpperElementsAreVisible(page); + + await expect( + page.getByRole('button', { name: 'Create a mailbox' }), + ).toBeEnabled(); + + await expect( + page.getByText('No mail box was created with this mail domain.'), + ).toBeVisible(); }); - }; - await interceptApiCalls(); + test('checks all the elements are visible when domain exists and contains 2 pages of mailboxes', async ({ + page, + }) => { + const mailboxesFixtures = { + domainFr: { + page1: Array.from({ length: 20 }, (_, i) => ({ + id: `456ac6ca-0402-4615-8005-69bc1efde${i}f`, + first_name: 'john', + last_name: 'doe', + local_part: `local_part-${i}`, + secondary_email: `secondary_email-${i}`, + })), + page2: Array.from({ length: 2 }, (_, i) => ({ + id: `456ac6ca-0402-4615-8005-69bc1efde${i}d`, + first_name: 'john', + last_name: 'doe', + local_part: `local_part-${i}`, + secondary_email: `secondary_email-${i}`, + })), + }, + }; + const interceptApiCalls = async () => { + await page.route( + '**/api/v1.0/mail-domains/?page=*', + async (route) => { + await route.fulfill({ + json: { + count: mailDomainsFixtures.length, + next: null, + previous: null, + results: mailDomainsFixtures, + }, + }); + }, + ); + await page.route( + '**/api/v1.0/mail-domains/domainfr/', + async (route) => { + await route.fulfill({ + json: mailDomainsFixtures[0], + }); + }, + ); + await page.route( + '**/api/v1.0/mail-domains/domainfr/mailboxes/?page=1**', + async (route) => { + await route.fulfill({ + json: { + count: + mailboxesFixtures.domainFr.page1.length + + mailboxesFixtures.domainFr.page2.length, + next: 'http://localhost:8071/api/v1.0/mail-domains/domainfr/mailboxes/?page=2', + previous: null, + results: mailboxesFixtures.domainFr.page1, + }, + }); + }, + ); + await page.route( + '**/api/v1.0/mail-domains/domainfr/mailboxes/?page=2**', + async (route) => { + await route.fulfill({ + json: { + count: + mailboxesFixtures.domainFr.page1.length + + mailboxesFixtures.domainFr.page2.length, + next: null, + previous: + 'http://localhost:8071/api/v1.0/mail-domains/domainfr/mailboxes/?page=1', + results: mailboxesFixtures.domainFr.page2, + }, + }); + }, + ); + }; - await clickOnMailDomainsNavButton(page); + await interceptApiCalls(); - await expect(page).toHaveURL(/mail-domains\//); + await clickOnMailDomainsNavButton(page); - await page.getByRole('listbox').first().getByText('domain.fr').click(); - await expect(page).toHaveURL(/mail-domains\/domainfr\//); + await assertMailDomainUpperElementsAreVisible(page); - await expect( - page.getByRole('heading', { name: /domain\.fr/ }).first(), - ).toBeVisible(); + await expect( + page.getByRole('button', { name: 'Create a mailbox' }), + ).toBeEnabled(); - await expect( - page.getByText('No mail box was created with this mail domain.'), - ).toBeVisible(); + await assertFilledMailboxesTableElementsAreVisible( + page, + mailDomainsFixtures[0], + [mailboxesFixtures.domainFr.page1, mailboxesFixtures.domainFr.page2], + ); + }); + }); + + test.describe('mail domain creation is pending', () => { + const mailDomainsFixtures: MailDomain[] = [ + { + name: 'domain.fr', + id: '456ac6ca-0402-4615-8005-69bc1efde43f', + created_at: currentDateIso, + updated_at: currentDateIso, + slug: 'domainfr', + status: 'pending', + abilities: { + get: true, + patch: true, + put: true, + post: true, + delete: true, + manage_accesses: true, + }, + }, + ]; + + test('checks expected elements are visible', async ({ page }) => { + await interceptCommonApiCalls(page, mailDomainsFixtures); + + await clickOnMailDomainsNavButton(page); + + await expect(page).toHaveURL(/mail-domains\//); + + await page.getByRole('listbox').first().getByText('domain.fr').click(); + await expect(page).toHaveURL(/mail-domains\/domainfr\//); + + await expect( + page.getByRole('heading', { name: 'domain.fr' }), + ).toBeVisible(); + + await expect( + page.getByText( + 'Your domain name is being validated. ' + + 'You will not be able to create mailboxes until your domain name has been validated by our team.', + ), + ).toBeVisible(); + + await expect( + page.getByRole('button', { name: 'Create a mailbox' }), + ).toBeDisabled(); + + await expect( + page.getByText('No mail box was created with this mail domain.'), + ).toBeVisible(); + }); + }); + + test.describe('mail domain is disabled', () => { + const mailDomainsFixtures: MailDomain[] = [ + { + name: 'domain.fr', + id: '456ac6ca-0402-4615-8005-69bc1efde43f', + created_at: currentDateIso, + updated_at: currentDateIso, + slug: 'domainfr', + status: 'disabled', + abilities: { + get: true, + patch: true, + put: true, + post: true, + delete: true, + manage_accesses: true, + }, + }, + ]; + + test('checks expected elements are visible', async ({ page }) => { + await interceptCommonApiCalls(page, mailDomainsFixtures); + + await clickOnMailDomainsNavButton(page); + + await assertMailDomainUpperElementsAreVisible(page); + + await expect( + page.getByText( + 'This domain name is deactivated. No new mailboxes can be created.', + ), + ).toBeVisible(); + + await expect( + page.getByRole('button', { name: 'Create a mailbox' }), + ).toBeDisabled(); + + await expect( + page.getByText('No mail box was created with this mail domain.'), + ).toBeVisible(); + }); + }); + + test.describe('mail domain creation has failed', () => { + const mailDomainsFixtures: MailDomain[] = [ + { + name: 'domain.fr', + id: '456ac6ca-0402-4615-8005-69bc1efde43f', + created_at: currentDateIso, + updated_at: currentDateIso, + slug: 'domainfr', + status: 'failed', + abilities: { + get: true, + patch: true, + put: true, + post: true, + delete: true, + manage_accesses: true, + }, + }, + ]; + + test('checks expected elements are visible', async ({ page }) => { + await interceptCommonApiCalls(page, mailDomainsFixtures); + + await clickOnMailDomainsNavButton(page); + + await assertMailDomainUpperElementsAreVisible(page); + + await expect( + page.getByText( + 'The domain name encounters an error. Please contact our support team to solve the problem:', + ), + ).toBeVisible(); + + await expect( + page.getByRole('link', { name: 'suiteterritoriale@anct.gouv.fr' }), + ).toBeVisible(); + + await expect( + page.getByRole('button', { name: 'Create a mailbox' }), + ).toBeDisabled(); + + await expect( + page.getByText('No mail box was created with this mail domain.'), + ).toBeVisible(); + }); + }); }); - test('checks all the elements are visible when domain exists and contains 2 pages of mailboxes', async ({ - page, - }) => { - const mailboxesFixtures = { - domainFr: { - page1: Array.from({ length: 20 }, (_, i) => ({ - id: `456ac6ca-0402-4615-8005-69bc1efde${i}f`, - first_name: 'john', - last_name: 'doe', - local_part: `local_part-${i}`, - secondary_email: `secondary_email-${i}`, - })), - page2: Array.from({ length: 2 }, (_, i) => ({ - id: `456ac6ca-0402-4615-8005-69bc1efde${i}d`, - first_name: 'john', - last_name: 'doe', - local_part: `local_part-${i}`, - secondary_email: `secondary_email-${i}`, - })), - }, - }; - const interceptApiCalls = async () => { - await page.route('**/api/v1.0/mail-domains/?page=*', async (route) => { - await route.fulfill({ - json: { - count: mailDomainsFixtures.length, - next: null, - previous: null, - results: mailDomainsFixtures, + test.describe('user is member', () => { + test.describe('mail domain is enabled', () => { + const mailDomainsFixtures: MailDomain[] = [ + { + name: 'domain.fr', + id: '456ac6ca-0402-4615-8005-69bc1efde43f', + created_at: currentDateIso, + updated_at: currentDateIso, + slug: 'domainfr', + status: 'enabled', + abilities: { + get: true, + patch: false, + put: false, + post: false, + delete: false, + manage_accesses: false, }, - }); - }); - await page.route('**/api/v1.0/mail-domains/domainfr', async (route) => { - await route.fulfill({ - json: mailDomainDomainFrFixture, - }); - }); - await page.route( - '**/api/v1.0/mail-domains/domainfr/mailboxes/?page=1**', - async (route) => { - await route.fulfill({ - json: { - count: - mailboxesFixtures.domainFr.page1.length + - mailboxesFixtures.domainFr.page2.length, - next: 'http://localhost:8071/api/v1.0/mail-domains/domainfr/mailboxes/?page=2', - previous: null, - results: mailboxesFixtures.domainFr.page1, - }, - }); }, - ); - await page.route( - '**/api/v1.0/mail-domains/domainfr/mailboxes/?page=2**', - async (route) => { - await route.fulfill({ - json: { - count: - mailboxesFixtures.domainFr.page1.length + - mailboxesFixtures.domainFr.page2.length, - next: null, - previous: - 'http://localhost:8071/api/v1.0/mail-domains/domainfr/mailboxes/?page=1', - results: mailboxesFixtures.domainFr.page2, - }, - }); + { + name: 'mails.fr', + id: '456ac6ca-0402-4615-8005-69bc1efde43e', + created_at: currentDateIso, + updated_at: currentDateIso, + slug: 'mailsfr', + status: 'enabled', + abilities: { + get: true, + patch: false, + put: false, + post: false, + delete: false, + manage_accesses: false, + }, }, - ); - }; + { + name: 'versailles.net', + id: '456ac6ca-0402-4615-8005-69bc1efde43g', + created_at: currentDateIso, + updated_at: currentDateIso, + slug: 'versaillesnet', + status: 'enabled', + abilities: { + get: true, + patch: false, + put: false, + post: false, + delete: false, + manage_accesses: false, + }, + }, + { + name: 'paris.fr', + id: '456ac6ca-0402-4615-8005-69bc1efde43h', + created_at: currentDateIso, + updated_at: currentDateIso, + slug: 'parisfr', + status: 'enabled', + abilities: { + get: true, + patch: false, + put: false, + post: false, + delete: false, + manage_accesses: false, + }, + }, + ]; - await interceptApiCalls(); + test('checks all the elements are visible when domain exist but contains no mailboxes', async ({ + page, + }) => { + await interceptCommonApiCalls(page, mailDomainsFixtures); - await clickOnMailDomainsNavButton(page); + await clickOnMailDomainsNavButton(page); - await expect(page).toHaveURL(/mail-domains\//); + await assertMailDomainUpperElementsAreVisible(page); - await page.getByRole('listbox').first().getByText('domain.fr').click(); - await expect(page).toHaveURL(/mail-domains\/domainfr\//); + await expect( + page.getByRole('button', { name: 'Create a mailbox' }), + ).not.toBeInViewport(); - await expect( - page.getByRole('heading', { name: 'domain.fr' }), - ).toBeVisible(); + await expect( + page.getByText('No mail box was created with this mail domain.'), + ).toBeVisible(); + }); - await expect( - page.getByRole('button', { name: /Names/ }).first(), - ).toBeVisible(); + test('checks all the elements are visible when domain exists and contains 2 pages of mailboxes', async ({ + page, + }) => { + const mailboxesFixtures = { + domainFr: { + page1: Array.from({ length: 20 }, (_, i) => ({ + id: `456ac6ca-0402-4615-8005-69bc1efde${i}f`, + first_name: 'john', + last_name: 'doe', + local_part: `local_part-${i}`, + secondary_email: `secondary_email-${i}`, + })), + page2: Array.from({ length: 2 }, (_, i) => ({ + id: `456ac6ca-0402-4615-8005-69bc1efde${i}d`, + first_name: 'john', + last_name: 'doe', + local_part: `local_part-${i}`, + secondary_email: `secondary_email-${i}`, + })), + }, + }; + const interceptApiCalls = async () => { + await page.route( + '**/api/v1.0/mail-domains/?page=*', + async (route) => { + await route.fulfill({ + json: { + count: mailDomainsFixtures.length, + next: null, + previous: null, + results: mailDomainsFixtures, + }, + }); + }, + ); + await page.route( + '**/api/v1.0/mail-domains/domainfr/', + async (route) => { + await route.fulfill({ + json: mailDomainsFixtures[0], + }); + }, + ); + await page.route( + '**/api/v1.0/mail-domains/domainfr/mailboxes/?page=1**', + async (route) => { + await route.fulfill({ + json: { + count: + mailboxesFixtures.domainFr.page1.length + + mailboxesFixtures.domainFr.page2.length, + next: 'http://localhost:8071/api/v1.0/mail-domains/domainfr/mailboxes/?page=2', + previous: null, + results: mailboxesFixtures.domainFr.page1, + }, + }); + }, + ); + await page.route( + '**/api/v1.0/mail-domains/domainfr/mailboxes/?page=2**', + async (route) => { + await route.fulfill({ + json: { + count: + mailboxesFixtures.domainFr.page1.length + + mailboxesFixtures.domainFr.page2.length, + next: null, + previous: + 'http://localhost:8071/api/v1.0/mail-domains/domainfr/mailboxes/?page=1', + results: mailboxesFixtures.domainFr.page2, + }, + }); + }, + ); + }; - await expect( - page.getByRole('button', { name: /Emails/ }).first(), - ).toBeVisible(); + await interceptApiCalls(); - await Promise.all( - mailboxesFixtures.domainFr.page1.map((mailbox) => - expect( + await clickOnMailDomainsNavButton(page); + + await assertMailDomainUpperElementsAreVisible(page); + + await expect( + page.getByRole('button', { name: 'Create a mailbox' }), + ).not.toBeInViewport(); + + await assertFilledMailboxesTableElementsAreVisible( + page, + mailDomainsFixtures[0], + [mailboxesFixtures.domainFr.page1, mailboxesFixtures.domainFr.page2], + ); + }); + }); + + test.describe('mail domain creation is pending', () => { + const mailDomainsFixtures: MailDomain[] = [ + { + name: 'domain.fr', + id: '456ac6ca-0402-4615-8005-69bc1efde43f', + created_at: currentDateIso, + updated_at: currentDateIso, + slug: 'domainfr', + status: 'pending', + abilities: { + get: true, + patch: false, + put: false, + post: false, + delete: false, + manage_accesses: false, + }, + }, + ]; + + test('checks expected elements are visible', async ({ page }) => { + await interceptCommonApiCalls(page, mailDomainsFixtures); + + await clickOnMailDomainsNavButton(page); + + await expect(page).toHaveURL(/mail-domains\//); + + await page.getByRole('listbox').first().getByText('domain.fr').click(); + await expect(page).toHaveURL(/mail-domains\/domainfr\//); + + await expect( + page.getByRole('heading', { name: 'domain.fr' }), + ).toBeVisible(); + + await expect( page.getByText( - `${mailbox.local_part}@${mailDomainDomainFrFixture.name}`, + 'Your domain name is being validated. ' + + 'You will not be able to create mailboxes until your domain name has been validated by our team.', ), - ).toBeVisible(), - ), - ); + ).toBeVisible(); - const table = page.locator('table'); - await expect(table).toBeVisible(); + await expect( + page.getByRole('button', { name: 'Create a mailbox' }), + ).not.toBeInViewport(); - const tdNames = await table.getByText('John Doe').all(); - expect(tdNames.length).toEqual(20); + await expect( + page.getByText('No mail box was created with this mail domain.'), + ).toBeVisible(); + }); + }); - await expect( - page.locator('.c__pagination__list').getByRole('button', { name: '1' }), - ).toBeVisible(); + test.describe('mail domain is disabled', () => { + const mailDomainsFixtures: MailDomain[] = [ + { + name: 'domain.fr', + id: '456ac6ca-0402-4615-8005-69bc1efde43f', + created_at: currentDateIso, + updated_at: currentDateIso, + slug: 'domainfr', + status: 'disabled', + abilities: { + get: true, + patch: false, + put: false, + post: false, + delete: false, + manage_accesses: false, + }, + }, + ]; - await expect( - page.locator('.c__pagination__list').getByText('navigate_next'), - ).toBeVisible(); + test('checks expected elements are visible', async ({ page }) => { + await interceptCommonApiCalls(page, mailDomainsFixtures); - await page - .locator('.c__pagination__list') - .getByRole('button', { name: '2' }) - .click(); + await clickOnMailDomainsNavButton(page); - await expect( - page.locator('.c__pagination__list').getByText('navigate_next'), - ).toBeHidden(); + await assertMailDomainUpperElementsAreVisible(page); - await expect( - page.locator('.c__pagination__list').getByText('navigate_before'), - ).toBeVisible(); - - await Promise.all( - mailboxesFixtures.domainFr.page2.map((mailbox) => - expect( + await expect( page.getByText( - `${mailbox.local_part}@${mailDomainDomainFrFixture.name}`, + 'This domain name is deactivated. No new mailboxes can be created.', ), - ).toBeVisible(), - ), - ); + ).toBeVisible(); + + await expect( + page.getByRole('button', { name: 'Create a mailbox' }), + ).not.toBeInViewport(); + + await expect( + page.getByText('No mail box was created with this mail domain.'), + ).toBeVisible(); + }); + }); + + test.describe('mail domain creation has failed', () => { + const mailDomainsFixtures: MailDomain[] = [ + { + name: 'domain.fr', + id: '456ac6ca-0402-4615-8005-69bc1efde43f', + created_at: currentDateIso, + updated_at: currentDateIso, + slug: 'domainfr', + status: 'failed', + abilities: { + get: true, + patch: false, + put: false, + post: false, + delete: false, + manage_accesses: false, + }, + }, + ]; + + test('checks expected elements are visible', async ({ page }) => { + await interceptCommonApiCalls(page, mailDomainsFixtures); + + await clickOnMailDomainsNavButton(page); + + await assertMailDomainUpperElementsAreVisible(page); + + await expect( + page.getByText( + 'The domain name encounters an error. Please contact our support team to solve the problem:', + ), + ).toBeVisible(); + + await expect( + page.getByRole('button', { name: 'Create a mailbox' }), + ).not.toBeInViewport(); + + await expect( + page.getByText('No mail box was created with this mail domain.'), + ).toBeVisible(); + }); + }); }); }); diff --git a/src/frontend/apps/e2e/__tests__/app-desk/mail-domains-add.spec.ts b/src/frontend/apps/e2e/__tests__/app-desk/mail-domains-add.spec.ts new file mode 100644 index 0000000..82dc60b --- /dev/null +++ b/src/frontend/apps/e2e/__tests__/app-desk/mail-domains-add.spec.ts @@ -0,0 +1,176 @@ +import { expect, test } from '@playwright/test'; + +import { keyCloakSignIn, randomName } from './common'; + +test.beforeEach(async ({ page, browserName }) => { + await page.goto('/'); + await keyCloakSignIn(page, browserName); +}); + +test.describe('Add Mail Domains', () => { + test('checks all the elements are visible', async ({ page }) => { + await page.goto('/mail-domains'); + + const buttonFromHomePage = page.getByRole('button', { + name: 'Add your mail domain', + }); + + await expect(buttonFromHomePage).toBeVisible(); + await buttonFromHomePage.click(); + + await expect(buttonFromHomePage).toBeHidden(); + + await expect( + page.getByRole('heading', { + name: 'Add your mail domain', + level: 3, + }), + ).toBeVisible(); + + const form = page.locator('form'); + + await expect(form.getByLabel('Domain name')).toBeVisible(); + + await expect(page.getByText('Example: saint-laurent.fr')).toBeVisible(); + + await expect( + page.getByRole('button', { + name: 'Add the domain', + }), + ).toBeVisible(); + await expect( + page.getByRole('button', { + name: 'Cancel', + }), + ).toBeVisible(); + }); + + test('checks the cancel button interaction', async ({ page }) => { + await page.goto('/mail-domains'); + + const buttonFromHomePage = page.getByRole('button', { + name: 'Add your mail domain', + }); + await buttonFromHomePage.click(); + + await expect(buttonFromHomePage).toBeHidden(); + + await page + .getByRole('button', { + name: 'Cancel', + }) + .click(); + + await expect(buttonFromHomePage).toBeVisible(); + }); + + test('checks form invalid status', async ({ page }) => { + await page.goto('/mail-domains'); + + const buttonFromHomePage = page.getByRole('button', { + name: 'Add your mail domain', + }); + await buttonFromHomePage.click(); + + const form = page.locator('form'); + + const inputName = form.getByLabel('Domain name'); + const buttonSubmit = page.getByRole('button', { + name: 'Add the domain', + }); + + await expect(inputName).toBeVisible(); + await expect(page.getByText('Example: saint-laurent.fr')).toBeVisible(); + + await expect( + page.getByRole('button', { + name: 'Cancel', + }), + ).toBeEnabled(); + + await expect(buttonSubmit).toBeDisabled(); + + await inputName.fill('s'); + await expect(page.getByText('Example: saint-laurent.fr')).toBeVisible(); + + await inputName.clear(); + + await expect(page.getByText('Example: saint-laurent.fr')).toBeVisible(); + }); + + test('checks the routing on new mail domain added', async ({ + page, + browserName, + }) => { + const mailDomainName = randomName('versailles.fr', browserName, 1)[0]; + const mailDomainSlug = mailDomainName.replace('.', ''); + + await page.goto('/mail-domains'); + + const panel = page.getByLabel('Mail domains panel').first(); + + await panel.getByRole('link', { name: 'Add your mail domain' }).click(); + + const form = page.locator('form'); + + await form.getByLabel('Domain name').fill(mailDomainName); + await page.getByRole('button', { name: 'Add the domain' }).click(); + + await expect(page).toHaveURL(`/mail-domains\/${mailDomainSlug}/`); + + await expect( + page.getByRole('heading', { + name: mailDomainName, + }), + ).toBeVisible(); + }); + + test('checks error when duplicate mail domain', async ({ + page, + browserName, + }) => { + await page.goto('/mail-domains'); + + const panel = page.getByLabel('Mail domains panel').first(); + const additionLink = panel.getByRole('link', { + name: 'Add your 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 mailDomainSlug = mailDomainName.replace('.', ''); + + await additionLink.click(); + await inputName.fill(mailDomainName); + await submitButton.click(); + + await expect(page).toHaveURL(`/mail-domains\/${mailDomainSlug}\/`); + + await additionLink.click(); + + await inputName.fill(mailDomainName); + await submitButton.click(); + + await expect( + page.getByText( + 'This mail domain is already used. Please, choose another one.', + ), + ).toBeVisible(); + }); + + test('checks 404 on mail-domains/[slug] page', async ({ page }) => { + await page.goto('/mail-domains/unknown-domain'); + + await expect( + page.getByText( + 'It seems that the page you are looking for does not exist or cannot be displayed correctly.', + ), + ).toBeVisible({ + timeout: 15000, + }); + }); +}); diff --git a/src/frontend/apps/e2e/__tests__/app-desk/mail-domains.spec.ts b/src/frontend/apps/e2e/__tests__/app-desk/mail-domains.spec.ts index 1d431d1..6b57529 100644 --- a/src/frontend/apps/e2e/__tests__/app-desk/mail-domains.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-desk/mail-domains.spec.ts @@ -11,6 +11,7 @@ const mailDomainsFixtures: MailDomain[] = [ created_at: currentDateIso, updated_at: currentDateIso, slug: 'domainfr', + status: 'pending', abilities: { get: true, patch: true, @@ -26,6 +27,7 @@ const mailDomainsFixtures: MailDomain[] = [ created_at: currentDateIso, updated_at: currentDateIso, slug: 'mailsfr', + status: 'enabled', abilities: { get: true, patch: true, @@ -41,6 +43,7 @@ const mailDomainsFixtures: MailDomain[] = [ created_at: currentDateIso, updated_at: currentDateIso, slug: 'versaillesnet', + status: 'disabled', abilities: { get: true, patch: true, @@ -56,6 +59,7 @@ const mailDomainsFixtures: MailDomain[] = [ created_at: currentDateIso, updated_at: currentDateIso, slug: 'parisfr', + status: 'failed', abilities: { get: true, patch: true, @@ -99,7 +103,7 @@ test.describe('Mail domains', () => { response.status() === 200, ); - const panel = page.getByLabel('mail domains panel').first(); + const panel = page.getByLabel('Mail domains panel').first(); await panel .getByRole('button', { @@ -139,7 +143,7 @@ test.describe('Mail domains', () => { .click(); await expect(page).toHaveURL(/mail-domains\//); await expect( - page.getByLabel('mail domains panel', { exact: true }), + page.getByLabel('Mail domains panel', { exact: true }), ).toBeVisible(); await expect(page.getByText('No domains exist.')).toBeVisible(); }); @@ -163,13 +167,17 @@ test.describe('Mail domains', () => { .click(); await expect(page).toHaveURL(/mail-domains\//); await expect( - page.getByLabel('mail domains panel', { exact: true }), + page.getByLabel('Mail domains panel', { exact: true }), ).toBeVisible(); await expect(page.getByText('No domains exist.')).toHaveCount(0); - await expect(page.getByText('domain.fr')).toBeVisible(); - await expect(page.getByText('mails.fr')).toBeVisible(); - await expect(page.getByText('versailles.net')).toBeVisible(); - await expect(page.getByText('paris.fr')).toBeVisible(); + + await Promise.all( + mailDomainsFixtures.map(async ({ name, status }) => { + const linkName = page.getByRole('link', { name }); + await expect(linkName).toBeVisible(); + await expect(linkName.getByText(`[${status}]`)).toBeVisible(); + }), + ); }); }); });