diff --git a/CHANGELOG.md b/CHANGELOG.md index abb872fed..27189efe9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to ### Added +- ✨(frontend) disable mailbox and allow to create pending mailbox - ✨(organizations) add siret to name conversion #584 - 💄(frontend) redirect home according to abilities #588 - ✨(maildomain_access) add API endpoint to search users #508 diff --git a/src/frontend/apps/desk/src/features/mail-domains/mailboxes/api/useUpdateMailboxStatus.tsx b/src/frontend/apps/desk/src/features/mail-domains/mailboxes/api/useUpdateMailboxStatus.tsx new file mode 100644 index 000000000..90a7fb1bb --- /dev/null +++ b/src/frontend/apps/desk/src/features/mail-domains/mailboxes/api/useUpdateMailboxStatus.tsx @@ -0,0 +1,48 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import { APIError, errorCauses, fetchAPI } from '@/api'; + +import { KEY_LIST_MAILBOX } from './useMailboxes'; + +export interface DisableMailboxParams { + mailDomainSlug: string; + mailboxId: string; + isEnabled: boolean; +} + +export const disableMailbox = async ({ + mailDomainSlug, + mailboxId, + isEnabled, +}: DisableMailboxParams): Promise => { + const response = await fetchAPI( + `mail-domains/${mailDomainSlug}/mailboxes/${mailboxId}/${ + isEnabled ? 'enable' : 'disable' + }/`, + { + method: 'POST', + }, + ); + + if (!response.ok) { + throw new APIError( + 'Failed to disable the mailbox', + await errorCauses(response), + ); + } +}; + +export const useUpdateMailboxStatus = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: disableMailbox, + onSuccess: (_data, variables) => { + void queryClient.invalidateQueries({ + queryKey: [ + KEY_LIST_MAILBOX, + { mailDomainSlug: variables.mailDomainSlug }, + ], + }); + }, + }); +}; diff --git a/src/frontend/apps/desk/src/features/mail-domains/mailboxes/components/MailDomainsActions.tsx b/src/frontend/apps/desk/src/features/mail-domains/mailboxes/components/MailDomainsActions.tsx new file mode 100644 index 000000000..a60b82406 --- /dev/null +++ b/src/frontend/apps/desk/src/features/mail-domains/mailboxes/components/MailDomainsActions.tsx @@ -0,0 +1,115 @@ +import { + Button, + Modal, + ModalSize, + VariantType, + useModal, + useToastProvider, +} from '@openfun/cunningham-react'; +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { Box, DropButton, IconOptions, Text } from '@/components'; + +import { MailDomain } from '../../domains/types'; +import { useUpdateMailboxStatus } from '../api/useUpdateMailboxStatus'; +import { MailDomainMailbox } from '../types'; + +interface MailDomainsActionsProps { + mailbox: MailDomainMailbox; + mailDomain: MailDomain; +} + +export const MailDomainsActions = ({ + mailDomain, + mailbox, +}: MailDomainsActionsProps) => { + const { t } = useTranslation(); + const [isDropOpen, setIsDropOpen] = useState(false); + const isEnabled = mailbox.status === 'enabled'; + const disableModal = useModal(); + const { toast } = useToastProvider(); + + const { mutate: updateMailboxStatus } = useUpdateMailboxStatus(); + + const handleUpdateMailboxStatus = () => { + disableModal.close(); + updateMailboxStatus( + { + mailDomainSlug: mailDomain.slug, + mailboxId: mailbox.id, + isEnabled: !isEnabled, + }, + { + onError: () => + toast(t('Failed to update mailbox status'), VariantType.ERROR), + }, + ); + }; + + if (mailbox.status === 'pending' || mailbox.status === 'failed') { + return null; + } + + return ( + <> + + } + onOpenChange={(isOpen) => setIsDropOpen(isOpen)} + isOpen={isDropOpen} + > + + + + + {t('Disable mailbox')}} + size={ModalSize.MEDIUM} + rightActions={ + + + + + } + > + + {t( + 'Are you sure you want to disable this mailbox? This action results in the deletion of the calendar, address book, etc.', + )} + + + + ); +}; diff --git a/src/frontend/apps/desk/src/features/mail-domains/mailboxes/components/MailDomainsContent.tsx b/src/frontend/apps/desk/src/features/mail-domains/mailboxes/components/MailDomainsContent.tsx index d29ed467d..b836ec584 100644 --- a/src/frontend/apps/desk/src/features/mail-domains/mailboxes/components/MailDomainsContent.tsx +++ b/src/frontend/apps/desk/src/features/mail-domains/mailboxes/components/MailDomainsContent.tsx @@ -9,7 +9,7 @@ import { VariantType, usePagination, } from '@openfun/cunningham-react'; -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Box, Card, Text, TextErrors, TextStyled } from '@/components'; @@ -20,10 +20,14 @@ import { MailDomain } from '../../domains/types'; import { useMailboxes } from '../api/useMailboxes'; import { MailDomainMailbox } from '../types'; +import { MailDomainsActions } from './MailDomainsActions'; + export type ViewMailbox = { name: string; email: string; id: UUID; + status: MailDomainMailbox['status']; + mailbox: MailDomainMailbox; }; // FIXME : ask Cunningham to export this type @@ -72,6 +76,8 @@ export function MailDomainsContent({ mailDomain }: { mailDomain: MailDomain }) { email: `${mailbox.local_part}@${mailDomain.name}`, name: `${mailbox.first_name} ${mailbox.last_name}`, id: mailbox.id, + status: mailbox.status, + mailbox, })) : []; @@ -97,7 +103,16 @@ export function MailDomainsContent({ mailDomain }: { mailDomain: MailDomain }) { showMailBoxCreationForm={setIsCreateMailboxFormVisible} /> - + {error && } ( + + ), + }, ]} rows={viewMailboxes} isLoading={isLoading} @@ -147,6 +175,8 @@ const TopBanner = ({ showMailBoxCreationForm: (value: boolean) => void; }) => { const { t } = useTranslation(); + const canCreateMailbox = + mailDomain.status === 'enabled' || mailDomain.status === 'pending'; return ( @@ -164,7 +194,7 @@ const TopBanner = ({ aria-label={t('Create a mailbox in {{name}} domain', { name: mailDomain?.name, })} - disabled={mailDomain?.status !== 'enabled'} + disabled={!canCreateMailbox} onClick={() => showMailBoxCreationForm(true)} > {t('Create a mailbox')} @@ -181,14 +211,6 @@ const AlertStatus = ({ status }: { status: MailDomain['status'] }) => { 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, diff --git a/src/frontend/apps/desk/src/features/mail-domains/mailboxes/components/__tests__/MailDomainsContent.test.tsx b/src/frontend/apps/desk/src/features/mail-domains/mailboxes/components/__tests__/MailDomainsContent.test.tsx index 709977eee..e4e1a4137 100644 --- a/src/frontend/apps/desk/src/features/mail-domains/mailboxes/components/__tests__/MailDomainsContent.test.tsx +++ b/src/frontend/apps/desk/src/features/mail-domains/mailboxes/components/__tests__/MailDomainsContent.test.tsx @@ -194,10 +194,6 @@ describe('MailDomainsContent', () => { }); const statuses = [ - { - status: 'pending', - regex: /Your domain name is being validated/, - }, { status: 'disabled', regex: diff --git a/src/frontend/apps/desk/src/features/mail-domains/mailboxes/types.ts b/src/frontend/apps/desk/src/features/mail-domains/mailboxes/types.ts index da4d1f595..14c1bd91d 100644 --- a/src/frontend/apps/desk/src/features/mail-domains/mailboxes/types.ts +++ b/src/frontend/apps/desk/src/features/mail-domains/mailboxes/types.ts @@ -6,4 +6,11 @@ export interface MailDomainMailbox { first_name: string; last_name: string; secondary_email: string; + status: MailDomainMailboxStatus; } + +export type MailDomainMailboxStatus = + | 'enabled' + | 'disabled' + | 'pending' + | 'failed'; 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 90fccc651..09bca208b 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 @@ -85,38 +85,41 @@ const mailboxesFixtures = { }, }; -const interceptCommonApiRequests = (page: Page) => { +const interceptCommonApiRequests = (page: Page, mailDomains?: MailDomain[]) => { + const mailDomainsToUse = mailDomains ?? mailDomainsFixtures; void page.route('**/api/v1.0/mail-domains/?page=*', (route) => { void route.fulfill({ json: { - count: mailDomainsFixtures.length, + count: mailDomainsToUse.length, next: null, previous: null, - results: mailDomainsFixtures, + results: mailDomainsToUse, }, }); }); - void page.route('**/api/v1.0/mail-domains/domainfr/', (route) => { - void route.fulfill({ - json: mailDomainDomainFrFixture, - }); - }); - - void page.route( - '**/api/v1.0/mail-domains/domainfr/mailboxes/?page=1**', - (route) => { + mailDomainsToUse.forEach((mailDomain) => { + void page.route(`**/api/v1.0/mail-domains/${mailDomain.slug}/`, (route) => { void route.fulfill({ - json: { - count: mailboxesFixtures.domainFr.page1.length, - next: null, - previous: null, - results: mailboxesFixtures.domainFr.page1, - }, + json: mailDomain, }); - }, - { times: 1 }, - ); + }); + + void page.route( + `**/api/v1.0/mail-domains/${mailDomain.slug}/mailboxes/?page=1**`, + (route) => { + void route.fulfill({ + json: { + count: mailboxesFixtures.domainFr.page1.length, + next: null, + previous: null, + results: mailboxesFixtures.domainFr.page1, + }, + }); + }, + { times: 1 }, + ); + }); }; const navigateToMailboxCreationFormForMailDomainFr = async ( @@ -134,6 +137,57 @@ test.describe('Mail domain create mailbox', () => { await keyCloakSignIn(page, browserName, 'mail-member'); }); + test('checks create mailbox button is visible or not', async ({ page }) => { + const domains = [...mailDomainsFixtures]; + domains[0].status = 'enabled'; + domains[1].status = 'pending'; + domains[2].status = 'disabled'; + domains[3].status = 'failed'; + void interceptCommonApiRequests(page, domains); + + await page + .locator('menu') + .first() + .getByLabel(`Mail Domains button`) + .click(); + const domainFr = page.getByRole('listbox').first().getByText('domain.fr'); + const mailsFr = page.getByRole('listbox').first().getByText('mails.fr'); + const versaillesNet = page + .getByRole('listbox') + .first() + .getByText('versailles.net'); + const parisFr = page.getByRole('listbox').first().getByText('paris.fr'); + + await expect(domainFr).toBeVisible(); + await expect(mailsFr).toBeVisible(); + await expect(versaillesNet).toBeVisible(); + await expect(parisFr).toBeVisible(); + + // Check that the button is enabled when the domain is enabled + await domainFr.click(); + await expect( + page.getByRole('button', { name: 'Create a mailbox' }), + ).toBeEnabled(); + + // Check that the button is enabled when the domain is pending + await mailsFr.click(); + await expect( + page.getByRole('button', { name: 'Create a mailbox' }), + ).toBeEnabled(); + + // Check that the button is disabled when the domain is disabled + await versaillesNet.click(); + await expect( + page.getByRole('button', { name: 'Create a mailbox' }), + ).toBeDisabled(); + + // Check that the button is disabled when the domain is failed + await parisFr.click(); + await expect( + page.getByRole('button', { name: 'Create a mailbox' }), + ).toBeDisabled(); + }); + test('checks user can create a mailbox when he has post ability', async ({ page, }) => { @@ -196,7 +250,7 @@ test.describe('Mail domain create mailbox', () => { }); void interceptRequests(page); - + expect(true).toBeTruthy(); await navigateToMailboxCreationFormForMailDomainFr(page); await page.getByRole('button', { name: 'Cancel' }).click(); 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 e36129f35..ab2fec506 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 @@ -374,16 +374,9 @@ test.describe('Mail domain', () => { 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(); + ).toBeEnabled(); await expect( page.getByText('No mail box was created with this mail domain.'), @@ -702,13 +695,6 @@ test.describe('Mail domain', () => { 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' }), ).not.toBeInViewport();