From 6febde0d9337d03ef2933ed8ba8d29fe5172ee39 Mon Sep 17 00:00:00 2001
From: leo <leopold@lchappuis.fr>
Date: Wed, 12 Feb 2025 08:12:36 -0500
Subject: [PATCH] admin-panel: require confirmation before deleting accounts

Admins must now confirm their intent before deleting accounts.

Change-Id: I3e75bedfc636db087c76ff648650e6b5d50b2164
---
 .../AdminSettings/AccountsOverview.tsx        |  17 +++
 .../AdminSettings/ValidationRequiredModal.tsx | 113 ++++++++++++++++++
 server/locale/en/translation.json             |   3 +
 3 files changed, 133 insertions(+)
 create mode 100644 client/src/components/AdminSettings/ValidationRequiredModal.tsx

diff --git a/client/src/components/AdminSettings/AccountsOverview.tsx b/client/src/components/AdminSettings/AccountsOverview.tsx
index fb69e5e9..8ce4acc5 100644
--- a/client/src/components/AdminSettings/AccountsOverview.tsx
+++ b/client/src/components/AdminSettings/AccountsOverview.tsx
@@ -40,6 +40,7 @@ import { ChangeEvent, useMemo, useState } from 'react'
 import { useTranslation } from 'react-i18next'
 
 import { useDeleteAccounts, useGetAllAccounts } from '../../services/adminQueries'
+import ValidationRequiredModal from './ValidationRequiredModal'
 
 function descendingComparator<T>(a: T, b: T, orderBy: keyof T) {
   if (b[orderBy] < a[orderBy]) {
@@ -200,6 +201,7 @@ export default function AccountsOverview() {
   const [rowsPerPage, setRowsPerPage] = useState(rowPerPagesOptions[0])
   const getAllAccounts = useGetAllAccounts()
   const accountDeletionMutation = useDeleteAccounts()
+  const [modalOpen, setModalOpen] = useState(false)
 
   const rows = useMemo(() => {
     if (getAllAccounts.data) {
@@ -240,6 +242,10 @@ export default function AccountsOverview() {
   }
 
   const handleSubmitDeletion = () => {
+    setModalOpen(true)
+  }
+
+  const handleSubmitDeletionValidation = () => {
     const accountsToDelete: AccountOverview[] = []
     for (const account of rows) {
       if (selected.includes(account.accountUri)) {
@@ -248,6 +254,7 @@ export default function AccountsOverview() {
     }
     accountDeletionMutation.mutate(accountsToDelete)
     setSelected([])
+    setModalOpen(false)
   }
 
   const handleChangePage = (event: unknown, newPage: number) => {
@@ -344,6 +351,16 @@ export default function AccountsOverview() {
           />
         </Paper>
       </Box>
+      {modalOpen && (
+        <ValidationRequiredModal
+          info={t('remove_account_info')}
+          title={t('validation_required')}
+          buttonText={t('remove_button')}
+          open={modalOpen}
+          onClose={() => setModalOpen(false)}
+          onValidation={handleSubmitDeletionValidation}
+        />
+      )}
     </Box>
   )
 }
diff --git a/client/src/components/AdminSettings/ValidationRequiredModal.tsx b/client/src/components/AdminSettings/ValidationRequiredModal.tsx
new file mode 100644
index 00000000..45f845e0
--- /dev/null
+++ b/client/src/components/AdminSettings/ValidationRequiredModal.tsx
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2022-2025 Savoir-faire Linux Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program.  If not, see
+ * <https://www.gnu.org/licenses/>.
+ */
+
+import { Box, Button, Card, CardContent, CardHeader, Modal, useTheme } from '@mui/material'
+import { useTranslation } from 'react-i18next'
+
+interface ValidationRequiredModalProps {
+  open: boolean
+  onClose: () => void
+  onValidation: () => void
+  title: string
+  buttonText: string
+  info?: string
+}
+
+export default function ValidationRequiredModal({
+  open,
+  onClose,
+  onValidation,
+  title,
+  buttonText,
+  info,
+}: ValidationRequiredModalProps) {
+  const theme = useTheme()
+  const { t } = useTranslation()
+
+  return (
+    <Modal
+      open={open}
+      onClose={() => {
+        onClose()
+      }}
+      sx={{
+        width: '100%',
+        height: '100%',
+        display: 'flex',
+        justifyContent: 'center',
+        alignContent: 'center',
+        alignItems: 'center',
+      }}
+    >
+      <Card
+        sx={{
+          width: '350px',
+          height: '350px',
+          backgroundColor: theme.SettingsNeedPassword.backgroundColor,
+          borderRadius: '6px',
+          border: 'none',
+        }}
+      >
+        <CardHeader title={title} />
+        <CardContent sx={{ height: '100%' }}>
+          <Box
+            sx={{
+              fontSize: '14px',
+              display: 'flex',
+              color: 'grey',
+              maxWidth: '100%',
+              whiteSpace: 'pre-wrap',
+              wordWrap: 'break-word',
+              overflowWrap: 'break-word',
+              overflow: 'hidden',
+              textOverflow: 'ellipsis',
+              margin: 'auto',
+              textAlign: 'start',
+              marginTop: '-14px',
+            }}
+          >
+            {info}
+          </Box>
+
+          <Box
+            sx={{
+              height: '45%',
+              display: 'flex',
+              justifyContent: 'space-between',
+              alignItems: 'center',
+              flexDirection: 'column',
+            }}
+          ></Box>
+          <Box sx={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between' }}>
+            <Button variant="contained" onClick={onClose}>
+              {t('dialog_cancel')}
+            </Button>
+            <Button
+              variant="contained"
+              onClick={() => {
+                onValidation()
+              }}
+            >
+              {buttonText}
+            </Button>
+          </Box>
+        </CardContent>
+      </Card>
+    </Modal>
+  )
+}
diff --git a/server/locale/en/translation.json b/server/locale/en/translation.json
index 1d2ea652..05cc3a56 100644
--- a/server/locale/en/translation.json
+++ b/server/locale/en/translation.json
@@ -238,6 +238,8 @@
   "registration_success": "Account registered successfully. Logging in…",
   "resetting_display_name_success_alert": "Display name reset successfully.",
   "resetting_display_name_error_alert": "An error occurred while resetting the display name.",
+  "remove_account_info": "You are about to delete the selected accounts.\n This action is irreversible.\n Are you sure you want to proceed?",
+  "remove_button": "Remove",
   "remove_jams_server": "Remove",
   "removing_jams_server_success": "JAMS server address removed successfully.",
   "removing_jams_server_error": "An error occurred while removing the JAMS server.",
@@ -316,6 +318,7 @@
   "username_rule_3": "The username may contain hyphens (-).",
   "username_rule_4": "The username may contain underscores (_).",
   "username_rules_dialog_title": "Username rules",
+  "validation_required": "Validation required",
   "version": "Version",
   "welcome_text": "Welcome to Jami",
   "welcome_to_text": "Welcome to ",
-- 
GitLab