diff --git a/packages/web/src/components/ConfirmationDialog/index.jsx b/packages/web/src/components/ConfirmationDialog/index.jsx index abc5cd71..61cff3ae 100644 --- a/packages/web/src/components/ConfirmationDialog/index.jsx +++ b/packages/web/src/components/ConfirmationDialog/index.jsx @@ -6,6 +6,7 @@ import DialogActions from '@mui/material/DialogActions'; import DialogContent from '@mui/material/DialogContent'; import DialogContentText from '@mui/material/DialogContentText'; import DialogTitle from '@mui/material/DialogTitle'; +import Alert from '@mui/material/Alert'; function ConfirmationDialog(props) { const { @@ -16,6 +17,7 @@ function ConfirmationDialog(props) { cancelButtonChildren, confirmButtonChildren, open = true, + errorMessage, } = props; const dataTest = props['data-test']; return ( @@ -44,6 +46,11 @@ function ConfirmationDialog(props) { )} + {errorMessage && ( + + {errorMessage} + + )} ); } @@ -57,6 +64,7 @@ ConfirmationDialog.propTypes = { confirmButtonChildren: PropTypes.node.isRequired, open: PropTypes.bool, 'data-test': PropTypes.string, + errorMessage: PropTypes.string, }; export default ConfirmationDialog; diff --git a/packages/web/src/components/DeleteRoleButton/index.ee.jsx b/packages/web/src/components/DeleteRoleButton/index.ee.jsx index 5a40c1f9..4e70c34d 100644 --- a/packages/web/src/components/DeleteRoleButton/index.ee.jsx +++ b/packages/web/src/components/DeleteRoleButton/index.ee.jsx @@ -4,6 +4,7 @@ import IconButton from '@mui/material/IconButton'; import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar'; import * as React from 'react'; +import { getGeneralErrorMessage, getFieldErrorMessage } from 'helpers/errors'; import Can from 'components/Can'; import ConfirmationDialog from 'components/ConfirmationDialog'; import useFormatMessage from 'hooks/useFormatMessage'; @@ -15,7 +16,21 @@ function DeleteRoleButton(props) { const formatMessage = useFormatMessage(); const enqueueSnackbar = useEnqueueSnackbar(); - const { mutateAsync: deleteRole } = useAdminDeleteRole(roleId); + const { + mutateAsync: deleteRole, + error: deleteRoleError, + reset: resetDeleteRole, + } = useAdminDeleteRole(roleId); + + const roleErrorMessage = getFieldErrorMessage({ + fieldName: 'role', + error: deleteRoleError, + }); + + const generalErrorMessage = getGeneralErrorMessage({ + error: deleteRoleError, + fallbackMessage: formatMessage('deleteRoleButton.generalError'), + }); const handleConfirm = React.useCallback(async () => { try { @@ -28,24 +43,14 @@ function DeleteRoleButton(props) { 'data-test': 'snackbar-delete-role-success', }, }); - } catch (error) { - const errors = Object.values( - error.response.data.errors || [['Failed while deleting!']], - ); - - for (const [error] of errors) { - enqueueSnackbar(error, { - variant: 'error', - SnackbarProps: { - 'data-test': 'snackbar-delete-role-error', - }, - }); - } - - throw new Error('Failed while deleting!'); - } + } catch {} }, [deleteRole, enqueueSnackbar, formatMessage]); + const handleClose = () => { + setShowConfirmation(false); + resetDeleteRole(); + }; + return ( <> @@ -65,11 +70,12 @@ function DeleteRoleButton(props) { open={showConfirmation} title={formatMessage('deleteRoleButton.title')} description={formatMessage('deleteRoleButton.description')} - onClose={() => setShowConfirmation(false)} + onClose={handleClose} onConfirm={handleConfirm} cancelButtonChildren={formatMessage('deleteRoleButton.cancel')} confirmButtonChildren={formatMessage('deleteRoleButton.confirm')} data-test="delete-role-modal" + errorMessage={roleErrorMessage || generalErrorMessage} /> ); diff --git a/packages/web/src/components/DeleteUserButton/index.ee.jsx b/packages/web/src/components/DeleteUserButton/index.ee.jsx index de4b918f..96ea717f 100644 --- a/packages/web/src/components/DeleteUserButton/index.ee.jsx +++ b/packages/web/src/components/DeleteUserButton/index.ee.jsx @@ -3,6 +3,7 @@ import DeleteIcon from '@mui/icons-material/Delete'; import IconButton from '@mui/material/IconButton'; import { useQueryClient } from '@tanstack/react-query'; +import { getGeneralErrorMessage } from 'helpers/errors'; import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar'; import * as React from 'react'; import ConfirmationDialog from 'components/ConfirmationDialog'; @@ -12,12 +13,21 @@ import useAdminUserDelete from 'hooks/useAdminUserDelete'; function DeleteUserButton(props) { const { userId } = props; const [showConfirmation, setShowConfirmation] = React.useState(false); - const { mutateAsync: deleteUser } = useAdminUserDelete(userId); + const { + mutateAsync: deleteUser, + error: deleteUserError, + reset: resetDeleteUser, + } = useAdminUserDelete(userId); const formatMessage = useFormatMessage(); const enqueueSnackbar = useEnqueueSnackbar(); const queryClient = useQueryClient(); + const generalErrorMessage = getGeneralErrorMessage({ + error: deleteUserError, + fallbackMessage: formatMessage('deleteUserButton.deleteError'), + }); + const handleConfirm = React.useCallback(async () => { try { await deleteUser(); @@ -29,16 +39,14 @@ function DeleteUserButton(props) { 'data-test': 'snackbar-delete-user-success', }, }); - } catch (error) { - enqueueSnackbar( - error?.message || formatMessage('deleteUserButton.deleteError'), - { - variant: 'error', - }, - ); - } + } catch {} }, [deleteUser]); + const handleClose = () => { + setShowConfirmation(false); + resetDeleteUser(); + }; + return ( <> setShowConfirmation(false)} + onClose={handleClose} onConfirm={handleConfirm} cancelButtonChildren={formatMessage('deleteUserButton.cancel')} confirmButtonChildren={formatMessage('deleteUserButton.confirm')} data-test="delete-user-modal" + errorMessage={generalErrorMessage} /> ); diff --git a/packages/web/src/helpers/errors.js b/packages/web/src/helpers/errors.js new file mode 100644 index 00000000..a872a8b6 --- /dev/null +++ b/packages/web/src/helpers/errors.js @@ -0,0 +1,29 @@ +// Helpers to extract errors received from the API + +export const getGeneralErrorMessage = ({ error, fallbackMessage }) => { + if (!error) { + return; + } + + const errors = error?.response?.data?.errors; + const generalError = errors?.general; + + if (generalError && Array.isArray(generalError)) { + return generalError.join(' '); + } + + if (!errors) { + return error?.message || fallbackMessage; + } +}; + +export const getFieldErrorMessage = ({ fieldName, error }) => { + const errors = error?.response?.data?.errors; + const fieldErrors = errors?.[fieldName]; + + if (fieldErrors && Array.isArray(fieldErrors)) { + return fieldErrors.join(', '); + } + + return ''; +}; diff --git a/packages/web/src/locales/en.json b/packages/web/src/locales/en.json index b121f5e2..24d7c546 100644 --- a/packages/web/src/locales/en.json +++ b/packages/web/src/locales/en.json @@ -245,6 +245,7 @@ "deleteRoleButton.cancel": "Cancel", "deleteRoleButton.confirm": "Delete", "deleteRoleButton.successfullyDeleted": "The role has been deleted.", + "deleteRoleButton.generalError": "Failed while deleting!", "editRolePage.title": "Edit role", "createRolePage.title": "Create role", "roleForm.name": "Name",