From 40c39bd9ba30a0578941ff5c8e485c1ba4919772 Mon Sep 17 00:00:00 2001 From: elvoisin <95469923+elvoisin@users.noreply.github.com> Date: Wed, 11 Feb 2026 10:29:03 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(invitations)=20allow=20delete=20invit?= =?UTF-8?q?ations=20by=20an=20admin=20(#1052)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit allow delete invitations mails domains access by an admin --- CHANGELOG.md | 1 + .../api/useCreateInvitation.tsx | 23 +- .../components/AccessAction.tsx | 38 +-- .../components/InvitationAction.tsx | 50 ++-- .../ModalDomainAccessesManagement.tsx | 18 +- .../__tests__/app-desk/mail-domain.spec.ts | 229 ++++++++++++++++++ 6 files changed, 318 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea1edd4..98e68a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to ### Added +- ✨(invitations) allow delete invitations mails domains access by an admin - ✨(front) delete invitations mails domains access - ✨(front) add show invitations mails domains access #1040 - ✨(invitations) can delete domain invitations diff --git a/src/frontend/apps/desk/src/features/mail-domains/access-management/api/useCreateInvitation.tsx b/src/frontend/apps/desk/src/features/mail-domains/access-management/api/useCreateInvitation.tsx index cfc71b2..fa30f2d 100644 --- a/src/frontend/apps/desk/src/features/mail-domains/access-management/api/useCreateInvitation.tsx +++ b/src/frontend/apps/desk/src/features/mail-domains/access-management/api/useCreateInvitation.tsx @@ -1,10 +1,16 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; import { APIError, errorCauses, fetchAPI } from '@/api'; import { User } from '@/core/auth'; +import { + KEY_LIST_MAIL_DOMAIN, + KEY_MAIL_DOMAIN, + MailDomain, + Role, +} from '@/features/mail-domains/domains'; import { Invitation, OptionType } from '@/features/teams/member-add/types'; -import { MailDomain, Role } from '../../domains'; +import { KEY_LIST_INVITATION_DOMAIN_ACCESSES } from './useInvitationMailDomainAccesses'; interface CreateInvitationParams { email: User['email']; @@ -42,7 +48,20 @@ export const createInvitation = async ({ }; export function useCreateInvitation() { + const queryClient = useQueryClient(); + return useMutation({ mutationFn: createInvitation, + onSuccess: () => { + void queryClient.invalidateQueries({ + queryKey: [KEY_LIST_INVITATION_DOMAIN_ACCESSES], + }); + void queryClient.invalidateQueries({ + queryKey: [KEY_MAIL_DOMAIN], + }); + void queryClient.invalidateQueries({ + queryKey: [KEY_LIST_MAIL_DOMAIN], + }); + }, }); } diff --git a/src/frontend/apps/desk/src/features/mail-domains/access-management/components/AccessAction.tsx b/src/frontend/apps/desk/src/features/mail-domains/access-management/components/AccessAction.tsx index 83e988d..e267b9e 100644 --- a/src/frontend/apps/desk/src/features/mail-domains/access-management/components/AccessAction.tsx +++ b/src/frontend/apps/desk/src/features/mail-domains/access-management/components/AccessAction.tsx @@ -45,10 +45,17 @@ export const AccessAction = ({ }; }, [isDropOpen]); - if ( - currentRole === Role.VIEWER || - (access.role === Role.OWNER && currentRole === Role.ADMIN) - ) { + if (currentRole === Role.VIEWER) { + return null; + } + + const canUpdateRole = + (mailDomain.abilities.put || mailDomain.abilities.patch) && + access.can_set_role_to && + access.can_set_role_to.length > 0; + const canDelete = mailDomain.abilities.delete; + + if (!canUpdateRole && !canDelete) { return null; } @@ -94,7 +101,7 @@ export const AccessAction = ({ } }} > - {(mailDomain.abilities.put || mailDomain.abilities.patch) && ( + {canUpdateRole && ( )} - {mailDomain.abilities.delete && ( + {canDelete && ( + {canDelete && ( + + )} )} diff --git a/src/frontend/apps/desk/src/features/mail-domains/access-management/components/ModalDomainAccessesManagement.tsx b/src/frontend/apps/desk/src/features/mail-domains/access-management/components/ModalDomainAccessesManagement.tsx index 2a99a23..28d9f4c 100644 --- a/src/frontend/apps/desk/src/features/mail-domains/access-management/components/ModalDomainAccessesManagement.tsx +++ b/src/frontend/apps/desk/src/features/mail-domains/access-management/components/ModalDomainAccessesManagement.tsx @@ -96,18 +96,32 @@ export const ModalDomainAccessesManagement = ({ switchActions(selectedMembers), ); + let hasInvitation = false; + let hasError = false; + settledPromises.forEach((settledPromise) => { switch (settledPromise.status) { case 'rejected': onError((settledPromise.reason as APIErrorMember).data); + hasError = true; break; case 'fulfilled': - onSuccess(settledPromise.value); + const option = settledPromise.value; + onSuccess(option); + if (!isOptionNewMember(option)) { + hasInvitation = true; + } break; } - onClose(); }); + + if (hasInvitation && !hasError) { + setSelectedMembers([]); + setRole(Role.VIEWER); + } else if (!hasInvitation) { + onClose(); + } }; return ( 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 11b4f90..4903448 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 @@ -278,6 +278,137 @@ test.describe('Mail domain', () => { .getByText('Enabled'), ).toBeVisible(); }); + + test('admin can delete invitation', async ({ page, browserName }) => { + // Intercept API calls to include enabled-domain.com in the list + await page.route('**/api/v1.0/mail-domains/\?*', async (route) => { + await route.fulfill({ + json: { + count: 1, + next: null, + previous: null, + results: [ + { + name: 'enabled-domain.com', + id: '456ac6ca-0402-4615-8005-69bc1efde43i', + created_at: currentDateIso, + updated_at: currentDateIso, + slug: 'enabled-domaincom', + status: 'enabled', + abilities: { + get: true, + patch: true, + put: true, + post: true, + delete: true, + manage_accesses: true, + }, + }, + ], + }, + }); + }); + + await page.route( + '**/api/v1.0/mail-domains/enabled-domaincom/', + async (route) => { + await route.fulfill({ + json: { + name: 'enabled-domain.com', + id: '456ac6ca-0402-4615-8005-69bc1efde43i', + created_at: currentDateIso, + updated_at: currentDateIso, + slug: 'enabled-domaincom', + status: 'enabled', + abilities: { + get: true, + patch: true, + put: true, + post: true, + delete: true, + manage_accesses: true, + }, + }, + }); + }, + ); + + await page.goto('/'); + await keyCloakSignIn(page, browserName, 'mail-administrator'); + + await clickOnMailDomainsNavButton(page); + + await expect(page).toHaveURL(/mail-domains\//); + await expect( + page.getByText('enabled-domain.com', { exact: true }), + ).toBeVisible(); + await page + .getByLabel(`enabled-domain.com listboxDomains button`) + .click(); + await expect(page).toHaveURL(/mail-domains\/enabled-domaincom\//); + await expect( + page.getByRole('heading', { name: 'enabled-domain.com' }), + ).toBeVisible(); + + await page.route( + '**/api/v1.0/mail-domains/enabled-domaincom/invitations/', + async (route) => { + await route.fulfill({ + json: { + count: 1, + next: null, + previous: null, + results: [ + { + id: '123e4567-e89b-12d3-a456-426614174000', + email: 'people@people.world', + role: 'administrator', + created_at: currentDateIso, + can_set_role_to: [], + }, + ], + }, + }); + }, + ); + + let deleteInvitationCalled = false; + await page.route( + '**/api/v1.0/mail-domains/enabled-domaincom/invitations/123e4567-e89b-12d3-a456-426614174000/', + async (route) => { + if (route.request().method() === 'DELETE') { + deleteInvitationCalled = true; + await route.fulfill({ + status: 204, + json: {}, + }); + } else { + await route.continue(); + } + }, + ); + + await page.getByText('Access management').click(); + await expect(page.getByText('Invitations')).toBeVisible(); + await expect(page.getByText('people@people.world')).toBeVisible(); + + await expect( + page.getByLabel('Open the invitation options modal'), + ).toBeVisible(); + await page.getByLabel('Open the invitation options modal').click(); + + await page.getByText('Delete invitation').click(); + + // Verify invitation is deleted (API call was made) + await expect(() => { + expect(deleteInvitationCalled).toBe(true); + }).toPass(); + + // Verify success toast appears + await expect( + page.getByText('The invitation has been deleted'), + ).toBeVisible(); + }); }); test.describe('mail domain creation is pending', () => { @@ -609,6 +740,104 @@ test.describe('Mail domain', () => { [mailboxesFixtures.domainFr.page1, mailboxesFixtures.domainFr.page2], ); }); + + test('viewer cannot delete invitation', async ({ page, browserName }) => { + // Intercept API calls to include enabled-domain.com in the list + await page.route('**/api/v1.0/mail-domains/\?*', async (route) => { + await route.fulfill({ + json: { + count: 1, + next: null, + previous: null, + results: [ + { + name: 'enabled-domain.com', + id: '456ac6ca-0402-4615-8005-69bc1efde43i', + created_at: currentDateIso, + updated_at: currentDateIso, + slug: 'enabled-domaincom', + status: 'enabled', + abilities: { + get: true, + patch: false, + put: false, + post: false, + delete: false, + manage_accesses: false, + }, + }, + ], + }, + }); + }); + + await page.route( + '**/api/v1.0/mail-domains/enabled-domaincom/', + async (route) => { + await route.fulfill({ + json: { + name: 'enabled-domain.com', + id: '456ac6ca-0402-4615-8005-69bc1efde43i', + created_at: currentDateIso, + updated_at: currentDateIso, + slug: 'enabled-domaincom', + status: 'enabled', + abilities: { + get: true, + patch: false, + put: false, + post: false, + delete: false, + manage_accesses: false, + }, + }, + }); + }, + ); + + // Intercept invitations API call BEFORE navigation + await page.route( + '**/api/v1.0/mail-domains/enabled-domaincom/invitations/', + async (route) => { + await route.fulfill({ + json: { + count: 1, + next: null, + previous: null, + results: [ + { + id: '123e4567-e89b-12d3-a456-426614174000', + email: 'people@people.world', + role: 'administrator', + created_at: currentDateIso, + can_set_role_to: [], + }, + ], + }, + }); + }, + ); + + await page.goto('/'); + await keyCloakSignIn(page, browserName, 'mail-member'); + + await clickOnMailDomainsNavButton(page); + + await expect(page).toHaveURL(/mail-domains\//); + await expect( + page.getByText('enabled-domain.com', { exact: true }), + ).toBeVisible(); + await page + .getByLabel(`enabled-domain.com listboxDomains button`) + .click(); + await expect(page).toHaveURL(/mail-domains\/enabled-domaincom\//); + await expect( + page.getByRole('heading', { name: 'enabled-domain.com' }), + ).toBeVisible(); + + // Verify that "Access management" button is not visible for a viewer + await expect(page.getByText('Access management')).toBeHidden(); + }); }); test.describe('mail domain creation is pending', () => {