diff --git a/src/frontend/apps/desk/src/features/mail-domains/api/useMailDomainMailboxes.tsx b/src/frontend/apps/desk/src/features/mail-domains/api/useMailDomainMailboxes.tsx new file mode 100644 index 0000000..b55c1f5 --- /dev/null +++ b/src/frontend/apps/desk/src/features/mail-domains/api/useMailDomainMailboxes.tsx @@ -0,0 +1,57 @@ +import { UseQueryOptions, useQuery } from '@tanstack/react-query'; + +import { APIError, APIList, errorCauses, fetchAPI } from '@/api'; + +import { MailDomainMailbox } from '../types'; + +export type MailDomainMailboxesParams = { + id: string; + page: number; + ordering?: string; +}; + +type MailDomainMailboxesResponse = APIList; + +export const getMailDomainMailboxes = async ({ + id, + page, + ordering, +}: MailDomainMailboxesParams): Promise => { + let url = `mail-domains/${id}/mailboxes/?page=${page}`; + + if (ordering) { + url += '&ordering=' + ordering; + } + + const response = await fetchAPI(url); + + if (!response.ok) { + throw new APIError( + `Failed to get the mailboxes of mail domain ${id}`, + await errorCauses(response), + ); + } + + return response.json() as Promise; +}; + +const KEY_LIST_MAILBOX = 'mailboxes'; + +export function useMailDomainMailboxes( + param: MailDomainMailboxesParams, + queryConfig?: UseQueryOptions< + MailDomainMailboxesResponse, + APIError, + MailDomainMailboxesResponse + >, +) { + return useQuery< + MailDomainMailboxesResponse, + APIError, + MailDomainMailboxesResponse + >({ + queryKey: [KEY_LIST_MAILBOX, param], + queryFn: () => getMailDomainMailboxes(param), + ...queryConfig, + }); +} diff --git a/src/frontend/apps/desk/src/features/mail-domains/assets/mail-domains-logo.svg b/src/frontend/apps/desk/src/features/mail-domains/assets/mail-domains-logo.svg new file mode 100644 index 0000000..62e8ea0 --- /dev/null +++ b/src/frontend/apps/desk/src/features/mail-domains/assets/mail-domains-logo.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + 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 ed6d3ba..d2c0c5f 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,51 +1,123 @@ -import { DataGrid } from '@openfun/cunningham-react'; +import { + DataGrid, + Loader, + SortModel, + usePagination, +} from '@openfun/cunningham-react'; +import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Card } from '@/components'; +import { Box, Card, Text, TextErrors } from '@/components'; +import { MailDomain } from '@/features/mail-domains'; +import { useMailDomainMailboxes } from '@/features/mail-domains/api/useMailDomainMailboxes'; +import { PAGE_SIZE } from '@/features/mail-domains/conf'; -export function MailDomainsContent() { +import { default as MailDomainsLogo } from '../assets/mail-domains-logo.svg'; + +export type ViewMailbox = { email: string; id: string }; + +// FIXME : ask Cunningham to export this type +type SortModelItem = { + field: string; + sort: 'asc' | 'desc' | null; +}; + +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, +) { + const { field, sort } = sortModel; + const orderingField = mapping[field] || field; + return sort === 'desc' ? `-${orderingField}` : orderingField; +} + +export function MailDomainsContent({ mailDomain }: { mailDomain: MailDomain }) { + const [sortModel, setSortModel] = useState([]); const { t } = useTranslation(); + const pagination = usePagination({ + defaultPage: 1, + pageSize: PAGE_SIZE, + }); - const dataset = [ - { - id: '1', - name: 'John Doe', - email: 'john@doe.com', - state: 'Active', - lastConnection: '2021-09-01', - }, - { - id: '2', - name: 'Jane Doe', - email: 'jane@doe.com', - state: 'Inactive', - lastConnection: '2021-09-02', - }, - ]; + const { page, pageSize, setPagesCount } = pagination; + const ordering = sortModel.length ? formatSortModel(sortModel[0]) : undefined; - return ( - - - + const { data, isLoading, error } = useMailDomainMailboxes({ + id: mailDomain.id, + page, + ordering, + }); + + const viewMailboxes: ViewMailbox[] = + mailDomain && data?.results?.length + ? data.results.map((mailbox) => ({ + email: `${mailbox.local_part}@${mailDomain.name}`, + id: mailbox.id, + })) + : []; + + useEffect(() => { + setPagesCount(data?.count ? Math.ceil(data.count / pageSize) : 0); + }, [data?.count, pageSize, setPagesCount]); + return isLoading ? ( + + + + ) : ( + <> + + + {error && } + + + ); } + +const TopBanner = ({ name }: { name: string }) => { + const { t } = useTranslation(); + + return ( + + + + {name} + + + ); +}; 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 84febdc..3c1c97e 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 @@ -51,7 +51,7 @@ export const PanelMailDomains = ({ mailDomain }: MailDomainProps) => { > => + await page.locator('menu').first().getByLabel(`Mail Domains button`).click(); + +test.describe('Mail domain', () => { + test.beforeEach(async ({ page, browserName }) => { + await page.goto('/'); + await keyCloakSignIn(page, browserName); + }); + + test('redirects to 404 page when the mail domain requested does not exist', async ({ + page, + }) => { + await page.route('**/api/v1.0/mail-domains/?page=*', async (route) => { + await route.fulfill({ + json: { + count: 0, + next: null, + previous: null, + results: [], + }, + }); + }); + + await page.goto('/mail-domains/unknown-domain.fr'); + await expect( + page.getByText( + 'It seems that the page you are looking for does not exist or cannot be displayed correctly.', + ), + ).toBeVisible({ + timeout: 15000, + }); + }); + + 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/456ac6ca-0402-4615-8005-69bc1efde43f/mailboxes/?page=1', + async (route) => { + await route.fulfill({ + json: { + count: 0, + next: null, + previous: null, + results: [], + }, + }); + }, + ); + + await page.route( + '**/api/v1.0/mail-domains/456ac6ca-0402-4615-8005-69bc1efde43f**', + 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, + }, + }); + }); + }; + + await interceptApiCalls(); + + await clickOnMailDomainsNavButton(page); + + await expect(page).toHaveURL(/mail-domains/); + + await page.getByRole('listbox').first().getByText('domain.fr').click(); + await expect(page).toHaveURL( + /mail-domains\/456ac6ca-0402-4615-8005-69bc1efde43f/, + ); + + await expect( + page.getByRole('heading', { name: /domain\.fr/ }).first(), + ).toBeVisible(); + + await expect(page.getByText('This table is empty')).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`, + local_part: `local_part-${i}`, + secondary_email: `secondary_email-${i}`, + })), + page2: Array.from({ length: 2 }, (_, i) => ({ + id: `456ac6ca-0402-4615-8005-69bc1efde${i}d`, + 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/456ac6ca-0402-4615-8005-69bc1efde43f', + async (route) => { + await route.fulfill({ + json: mailDomainDomainFrFixture, + }); + }, + ); + await page.route( + '**/api/v1.0/mail-domains/456ac6ca-0402-4615-8005-69bc1efde43f/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/456ac6ca-0402-4615-8005-69bc1efde43f/mailboxes/?page=2', + previous: null, + results: mailboxesFixtures.domainFr.page1, + }, + }); + }, + ); + await page.route( + '**/api/v1.0/mail-domains/456ac6ca-0402-4615-8005-69bc1efde43f/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/456ac6ca-0402-4615-8005-69bc1efde43f/mailboxes/?page=1', + results: mailboxesFixtures.domainFr.page2, + }, + }); + }, + ); + }; + + await interceptApiCalls(); + + await clickOnMailDomainsNavButton(page); + + await expect(page).toHaveURL(/mail-domains/); + + await page.getByRole('listbox').first().getByText('domain.fr').click(); + await expect(page).toHaveURL( + /mail-domains\/456ac6ca-0402-4615-8005-69bc1efde43f/, + ); + + await expect( + page.getByRole('heading', { name: 'domain.fr' }), + ).toBeVisible(); + + await expect( + page.getByRole('button', { name: /Emails/ }).first(), + ).toBeVisible(); + + await Promise.all( + mailboxesFixtures.domainFr.page1.map((mailbox) => + expect( + page.getByText( + `${mailbox.local_part}@${mailDomainDomainFrFixture.name}`, + ), + ).toBeVisible(), + ), + ); + + 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( + mailboxesFixtures.domainFr.page2.map((mailbox) => + expect( + page.getByText( + `${mailbox.local_part}@${mailDomainDomainFrFixture.name}`, + ), + ).toBeVisible(), + ), + ); + }); +});