Skip to content

Commit 9edd644

Browse files
committed
feat(zimbra): add deletion of multiple selected email accounts
ref: #PRDCOL-219 Signed-off-by: Atef ZAAFOURI <[email protected]>
1 parent 07ea727 commit 9edd644

File tree

9 files changed

+326
-7
lines changed

9 files changed

+326
-7
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,4 +135,4 @@
135135
"node": "^22",
136136
"yarn": ">=1.21.1"
137137
}
138-
}
138+
}

packages/manager/apps/zimbra/public/translations/accounts/Messages_fr_FR.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"zimbra_account_account_add": "Créer un compte",
1010
"zimbra_account_account_order": "Commander un compte",
1111
"zimbra_account_account_migrate": "Migrer un compte",
12+
"zimbra_account_delete_all": "Supprimer les comptes ({{ count }})",
1213
"zimbra_account_tooltip_need_domain": "Veuillez d'abord configurer un domaine.",
1314
"zimbra_account_tooltip_need_slot": "Aucun compte disponible, veuillez en commander.",
1415
"zimbra_account_cancel_modal_content": "Le compte email <strong>{{ email }}</strong> et toutes ses données seront définitivement supprimées à la fin de la période d'engagement du service.",
@@ -19,6 +20,9 @@
1920
"zimbra_slot_modal_renew_date": "Fin de l'engagement: {{ renewDate }}",
2021
"zimbra_account_delete_modal_content_step1": "Êtes-vous sûr(e) de vouloir supprimer le compte email <strong>{{ email }}</strong> ?",
2122
"zimbra_account_delete_modal_content_step2": "Êtes-vous vraiment sûr(e) de vouloir supprimer ce compte email <strong>{{ email }}</strong> ?",
23+
"zimbra_account_delete_all_modal_content_step1": "Êtes-vous sûr(e) de vouloir supprimer les comptes suivants ? <br/><strong>{{ emails }}</strong>",
24+
"zimbra_account_delete_all_modal_content_step2": "Êtes-vous vraiment sûr(e) de vouloir supprimer ces comptes email suivants ? <br/><strong>{{ emails }}</strong>",
25+
"zimbra_account_delete_all_confirm_label": "Saisissez <strong>{{ label }}</strong> pour confirmer la suppression: ",
2226
"zimbra_account_delete_modal_warn_message": "Attention cette action est irréversible et entraîne la suppression des données.",
2327
"zimbra_account_upgrade_cta_back": "Retour vers mes comptes emails",
2428
"zimbra_account_upgrade_new_offer": "Nous lançons en version bêta notre <Link href=\"{{ zimbra_emails_link }}\">nouvelle offre Zimbra Pro</Link>.",

packages/manager/apps/zimbra/public/translations/common/Messages_fr_FR.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"add_email_account": "Créer un compte email",
4848
"email_account_settings": "Paramètres du compte",
4949
"delete_email_account": "Supprimer le compte",
50+
"delete_email_accounts": "Supprimer les comptes",
5051
"order_zimbra_accounts": "Commander des comptes Zimbra",
5152
"select_slot": "Sélectionner une offre",
5253
"form_at_least_one_digit": "Doit contenir au minimum un chiffre",

packages/manager/apps/zimbra/src/pages/dashboard/emailAccounts/DatagridTopBar.component.tsx

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,15 @@ import {
2727
} from '@/tracking.constants';
2828
import { IAM_ACTIONS } from '@/utils/iamAction.constants';
2929

30-
export const DatagridTopbar = () => {
30+
import { EmailAccountItem } from './EmailAccounts.types';
31+
32+
export type DatagridTopbarProps = {
33+
selectedRows?: EmailAccountItem[];
34+
};
35+
36+
export const DatagridTopbar: React.FC<DatagridTopbarProps> = ({
37+
selectedRows,
38+
}: DatagridTopbarProps) => {
3139
const { t } = useTranslation(['accounts', 'common']);
3240
const { trackClick } = useOvhTracking();
3341
const navigate = useNavigate();
@@ -36,6 +44,7 @@ export const DatagridTopbar = () => {
3644

3745
const hrefAddEmailAccount = useGenerateUrl('./add', 'path');
3846
const hrefOrderEmailAccount = useGenerateUrl('./order', 'path');
47+
const hrefDeleteSelectedEmailAccounts = useGenerateUrl('./delete_all', 'path');
3948

4049
const { data: domains, isLoading: isLoadingDomains } = useDomains();
4150

@@ -76,6 +85,16 @@ export const DatagridTopbar = () => {
7685
});
7786
window.open(GUIDES_LIST.ovh_mail_migrator.url.DEFAULT, '_blank');
7887
};
88+
const handleSelectEmailAccounts = () => {
89+
navigate(hrefDeleteSelectedEmailAccounts, {
90+
state: {
91+
selectedEmailAccounts: selectedRows.map((row) => ({
92+
id: row?.id,
93+
email: row?.email,
94+
})),
95+
},
96+
});
97+
};
7998

8099
return (
81100
<div className="flex gap-6">
@@ -125,6 +144,17 @@ export const DatagridTopbar = () => {
125144
iconAlignment={ODS_BUTTON_ICON_ALIGNMENT.right}
126145
icon={ODS_ICON_NAME.externalLink}
127146
/>
147+
{!!selectedRows?.length && (
148+
<ManagerButton
149+
id="ovh-mail-delete-selected-btn"
150+
color={ODS_BUTTON_COLOR.critical}
151+
size={ODS_BUTTON_SIZE.sm}
152+
onClick={handleSelectEmailAccounts}
153+
label={t('zimbra_account_delete_all', { count: selectedRows.length })}
154+
iconAlignment={ODS_BUTTON_ICON_ALIGNMENT.right}
155+
icon={ODS_ICON_NAME.trash}
156+
/>
157+
)}
128158
</div>
129159
);
130160
};

packages/manager/apps/zimbra/src/pages/dashboard/emailAccounts/EmailAccountsDatagrid.component.tsx

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { useEffect, useMemo, useState } from 'react';
1+
import { useCallback, useEffect, useMemo, useState } from 'react';
2+
3+
import { useLocation } from 'react-router-dom';
24

35
import { useTranslation } from 'react-i18next';
46

@@ -21,9 +23,13 @@ import { EmailAccountItem } from './EmailAccounts.types';
2123

2224
export const EmailAccountsDatagrid = () => {
2325
const { t } = useTranslation(['accounts', 'common', NAMESPACES.STATUS]);
26+
const [rowSelection, setRowSelection] = useState({});
27+
const [selectedRows, setSelectedRows] = useState([]);
2428
const [hasADeletingAccount, setHasADeletingAccount] = useState(false);
2529
const isOverridedPage = useOverridePage();
2630
const { formatBytes } = useBytes();
31+
const location = useLocation();
32+
const { clearSelectedEmailAccounts } = location.state || {};
2733

2834
const [searchInput, setSearchInput, debouncedSearchInput, setDebouncedSearchInput] =
2935
useDebouncedValue('');
@@ -48,6 +54,13 @@ export const EmailAccountsDatagrid = () => {
4854
enabled: !isOverridedPage,
4955
});
5056

57+
useEffect(() => {
58+
if (clearSelectedEmailAccounts) {
59+
setRowSelection({});
60+
setSelectedRows([]);
61+
}
62+
}, [clearSelectedEmailAccounts]);
63+
5164
/* This is necessary to enable back the "Create account" button when your
5265
* slots are full and you delete an account and the account goes
5366
* from "DELETING" state to actually being deleted, because we invalidate
@@ -84,6 +97,11 @@ export const EmailAccountsDatagrid = () => {
8497
);
8598
}, [accounts, services]);
8699

100+
const isRowSelectable = useCallback(
101+
(item: EmailAccountItem) => item.status === ResourceStatus.READY,
102+
[],
103+
);
104+
87105
const columns: DatagridColumn<EmailAccountItem>[] = useMemo(
88106
() => [
89107
{
@@ -132,12 +150,18 @@ export const EmailAccountsDatagrid = () => {
132150

133151
return (
134152
<Datagrid
135-
topbar={<DatagridTopbar />}
153+
topbar={<DatagridTopbar selectedRows={selectedRows} />}
136154
search={{
137155
searchInput,
138156
setSearchInput,
139157
onSearch: (search) => setDebouncedSearchInput(search),
140158
}}
159+
rowSelection={{
160+
rowSelection,
161+
setRowSelection,
162+
enableRowSelection: ({ original: item }) => isRowSelectable(item),
163+
onRowSelectionChange: setSelectedRows,
164+
}}
141165
columns={columns.map((column) => ({
142166
...column,
143167
label: t(column.label),
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import React, { useState } from 'react';
2+
3+
import { useLocation, useNavigate, useParams } from 'react-router-dom';
4+
5+
import { useMutation } from '@tanstack/react-query';
6+
import { Trans, useTranslation } from 'react-i18next';
7+
8+
import { ODS_INPUT_TYPE, ODS_MODAL_COLOR, ODS_TEXT_PRESET } from '@ovhcloud/ods-components';
9+
import { OdsFormField, OdsInput, OdsText } from '@ovhcloud/ods-components/react';
10+
11+
import { NAMESPACES } from '@ovh-ux/manager-common-translations';
12+
import { ApiError } from '@ovh-ux/manager-core-api';
13+
import { Modal, useNotifications } from '@ovh-ux/manager-react-components';
14+
import {
15+
ButtonType,
16+
PageLocation,
17+
PageType,
18+
useOvhTracking,
19+
} from '@ovh-ux/manager-react-shell-client';
20+
21+
import { deleteZimbraPlatformAccount, getZimbraPlatformListQueryKey } from '@/data/api';
22+
import { useGenerateUrl } from '@/hooks';
23+
import queryClient from '@/queryClient';
24+
import { CANCEL, CONFIRM, DELETE_EMAIL_ACCOUNT } from '@/tracking.constants';
25+
26+
export const DeleteAllEmailAccountModal = () => {
27+
const { trackClick, trackPage } = useOvhTracking();
28+
const { platformId } = useParams();
29+
const { t } = useTranslation(['accounts', 'common', NAMESPACES.ACTIONS]);
30+
const { addError, addSuccess } = useNotifications();
31+
const navigate = useNavigate();
32+
const location = useLocation();
33+
const [isConfirmed, seIsConfirmed] = useState<boolean>(false);
34+
const { selectedEmailAccounts }: { selectedEmailAccounts: Array<{ id: string; email: string }> } =
35+
location.state || {};
36+
37+
const goBackUrl = useGenerateUrl('..', 'path');
38+
const onClose = (clear: boolean) =>
39+
navigate(goBackUrl, { state: { clearSelectedEmailAccounts: clear } });
40+
41+
const [step, setStep] = useState(1);
42+
43+
const { mutate: deleteAllEmailAccount, isPending: isSending } = useMutation({
44+
mutationFn: async () => {
45+
await Promise.all(
46+
selectedEmailAccounts.map((account) => deleteZimbraPlatformAccount(platformId, account.id)),
47+
);
48+
},
49+
onSuccess: () => {
50+
trackPage({
51+
pageType: PageType.bannerSuccess,
52+
pageName: DELETE_EMAIL_ACCOUNT,
53+
});
54+
addSuccess(
55+
<OdsText preset={ODS_TEXT_PRESET.paragraph}>{t('common:delete_success_message')}</OdsText>,
56+
true,
57+
);
58+
},
59+
onError: (error: ApiError) => {
60+
trackPage({
61+
pageType: PageType.bannerError,
62+
pageName: DELETE_EMAIL_ACCOUNT,
63+
});
64+
addError(
65+
<OdsText preset={ODS_TEXT_PRESET.paragraph}>
66+
{t('common:delete_error_message', {
67+
error: error?.response?.data?.message,
68+
})}
69+
</OdsText>,
70+
true,
71+
);
72+
},
73+
onSettled: async () => {
74+
await queryClient.invalidateQueries({
75+
queryKey: getZimbraPlatformListQueryKey(),
76+
});
77+
78+
onClose(true);
79+
},
80+
});
81+
82+
const handleDeleteClick = () => {
83+
trackClick({
84+
location: PageLocation.popup,
85+
buttonType: ButtonType.button,
86+
actionType: 'action',
87+
actions: [DELETE_EMAIL_ACCOUNT, CONFIRM],
88+
});
89+
deleteAllEmailAccount();
90+
};
91+
92+
const handleCancelClick = () => {
93+
trackClick({
94+
location: PageLocation.popup,
95+
buttonType: ButtonType.button,
96+
actionType: 'action',
97+
actions: [DELETE_EMAIL_ACCOUNT, CANCEL],
98+
});
99+
onClose(false);
100+
};
101+
102+
return (
103+
<Modal
104+
heading={t('common:delete_email_accounts')}
105+
type={ODS_MODAL_COLOR.critical}
106+
onDismiss={() => onClose(false)}
107+
isOpen
108+
primaryLabel={t(`${NAMESPACES.ACTIONS}:delete`)}
109+
isPrimaryButtonLoading={step === 1 ? false : isSending}
110+
isPrimaryButtonDisabled={step === 2 && !isConfirmed}
111+
onPrimaryButtonClick={step === 1 ? () => setStep(2) : handleDeleteClick}
112+
primaryButtonTestId="primary-btn"
113+
secondaryLabel={t(`${NAMESPACES.ACTIONS}:cancel`)}
114+
onSecondaryButtonClick={handleCancelClick}
115+
>
116+
<>
117+
{step === 1 && (
118+
<>
119+
<OdsText preset={ODS_TEXT_PRESET.paragraph} data-testid="text-step-1" className="mb-4">
120+
<Trans
121+
t={t}
122+
i18nKey={'zimbra_account_delete_all_modal_content_step1'}
123+
values={{
124+
emails: selectedEmailAccounts?.map((account) => account?.email).join(', '),
125+
}}
126+
/>
127+
</OdsText>
128+
</>
129+
)}
130+
131+
{step === 2 && (
132+
<div className="flex flex-col gap-6 select-none">
133+
<OdsText preset={ODS_TEXT_PRESET.paragraph} data-testid="text-step-2" className="mb-4">
134+
<Trans
135+
t={t}
136+
i18nKey={'zimbra_account_delete_all_modal_content_step2'}
137+
values={{
138+
emails: selectedEmailAccounts?.map((account) => account?.email).join(', '),
139+
}}
140+
/>
141+
</OdsText>
142+
<OdsFormField className="w-full">
143+
<label htmlFor="confirmation-delete" slot="label">
144+
<Trans
145+
t={t}
146+
i18nKey={'zimbra_account_delete_all_confirm_label'}
147+
values={{
148+
label: t(`${NAMESPACES.ACTIONS}:delete`),
149+
}}
150+
/>
151+
</label>
152+
<OdsInput
153+
type={ODS_INPUT_TYPE.text}
154+
data-testid="input-delete-confirm"
155+
name="confirmation-delete"
156+
id="confirmation-delete"
157+
onPaste={(e) => e.preventDefault()}
158+
onOdsChange={(e) =>
159+
seIsConfirmed(
160+
String(e.target.value).toLocaleLowerCase() ===
161+
t(`${NAMESPACES.ACTIONS}:delete`).toLowerCase(),
162+
)
163+
}
164+
/>
165+
</OdsFormField>
166+
</div>
167+
)}
168+
</>
169+
</Modal>
170+
);
171+
};
172+
173+
export default DeleteAllEmailAccountModal;

0 commit comments

Comments
 (0)