diff --git a/web/messages/en/modal.json b/web/messages/en/modal.json index d9c7437d17..8065a6ead5 100644 --- a/web/messages/en/modal.json +++ b/web/messages/en/modal.json @@ -3,8 +3,7 @@ "modal_change_password_title": "Change password", "modal_change_password_submit": "Change password", "modal_delete_authorized_app_title": "Delete authorized app", - "modal_delete_authorized_app_content_1": "Are you sure you want to remove this application? If you do, you won't be able to use it to log in with defguard anymore.", - "modal_delete_authorized_app_content_2": "Please note: until you log out from this application, it will remain signed in with DefGuard.", + "modal_delete_authorized_app": "Are you sure you want to remove this application? This action cannot be undone.", "modal_delete_logstream_destination": " Are you sure you want to delete this activity log destination? All related logs will stop being sent to this location. This action cannot be undone.", "modal_delete_gateway_title": "Delete Gateway", "modal_delete_gateway_body": "Deleting gateway {name} may cause location {locationName} to stop working if it is the only Gateway connected to this location or if other connected Gateways are currently unavailable. This action cannot be undone.", @@ -14,10 +13,16 @@ "modal_delete_network_device_body": "Are you sure you want to delete this device? This action cannot be undone.", "modal_delete_user_title": "Delete user", "modal_delete_user_body": "Are you sure you want to delete user {name}? This action cannot be undone.", + "modal_delete_user_success": "User deleted", + "modal_delete_user_error": "Failed to delete user", "modal_delete_edge_title": "Delete edge", "modal_delete_edge_body": "Are you sure you want to delete edge {name}? This action cannot be undone.", + "modal_delete_edge_success": "Edge deleted", + "modal_delete_edge_error": "Failed to delete edge", "modal_delete_group_title": "Delete group", "modal_delete_group_body": "Are you sure you want to delete group {name}? This action cannot be undone.", + "modal_delete_group_success": "Group deleted", + "modal_delete_group_error": "Failed to delete group", "modal_delete_user_device_title": "Delete device", "modal_delete_user_device_body": "Are you sure you want to delete the {name} device? This action cannot be undone.", "modal_delete_openid_client_title": "Delete application", @@ -168,5 +173,35 @@ "modal_assign_user_device_ip_error": "Failed to update IP addresses", "modal_no_available_locations_title": "Cannot add network device", "modal_no_available_locations_body": "Network devices can only be added to locations that have MFA disabled. You don't have any locations that meet this requirement. You can create a new location in the Locations section.", - "modal_no_available_locations_go_to_locations": "Go to locations" + "modal_no_available_locations_go_to_locations": "Go to locations", + "modal_disable_mfa_email_title": "Disable Email MFA", + "modal_disable_mfa_email_content": "Are you sure you want to disable Email MFA method? This action cannot be undone.", + "modal_disable_mfa_email_success": "Email MFA disabled", + "modal_disable_mfa_email_error": "Failed to disable Email MFA", + "modal_disable_mfa_totp_title": "Disable TOTP", + "modal_disable_mfa_totp_content": "Are you sure you want to disable Time-based One-Time Password authentication method? This action cannot be undone.", + "modal_disable_mfa_totp_success": "TOTP disabled", + "modal_disable_mfa_totp_error": "Failed to disable TOTP", + "modal_disable_mfa_passkeys_title": "Delete all Passkeys", + "modal_disable_mfa_passkeys_content": "Are you sure you want to delete all passkeys? This action cannot be undone.", + "modal_disable_mfa_passkeys_success": "All passkeys deleted", + "modal_disable_mfa_passkeys_error": "Failed to delete passkeys", + "modal_disable_mfa_all_title": "Disable all MFA", + "modal_disable_mfa_all_content": "Are you sure you want to disable all MFA methods? This will remove all configured MFA methods and recovery codes.", + "modal_disable_mfa_all_success": "MFA disabled", + "modal_disable_mfa_all_error": "Failed to disable MFA", + "modal_delete_mfa_passkey_title": "Delete passkey", + "modal_delete_mfa_passkey_content": "Are you sure you want to delete passkey {name}? This action cannot be undone.", + "modal_delete_mfa_passkey_success": "Passkey deleted", + "modal_delete_mfa_passkey_error": "Failed to delete passkey", + "modal_delete_auth_key_title": "Delete authentication key", + "modal_delete_auth_key_content": "Are you sure you want to delete authentication key {name}? This action cannot be undone.", + "modal_delete_auth_key_success": "Authentication key deleted", + "modal_delete_auth_key_error": "Failed to delete authentication key", + "modal_delete_api_token_title": "Delete API token", + "modal_delete_api_token_content": "Are you sure you want to delete API token {name}? This action cannot be undone.", + "modal_delete_api_token_success": "API token deleted", + "modal_delete_api_token_error": "Failed to delete API token", + "modal_delete_authorized_app_success": "Application removed", + "modal_delete_authorized_app_error": "Failed to remove application" } diff --git a/web/messages/en/settings.json b/web/messages/en/settings.json index e48eab2b8f..d9cb2ccb94 100644 --- a/web/messages/en/settings.json +++ b/web/messages/en/settings.json @@ -44,6 +44,7 @@ "settings_activity_log_streaming_no_upstreams_subtitle": "Click the button below to add an activity log provider and start streaming events.", "settings_activity_log_streaming_add_log_streaming_button": "Add log streaming.", "settings_activity_log_streaming_delete_log_streaming_title": "Delete destination confirmation", + "settings_activity_log_streaming_delete_log_streaming_success": "Log stream destination deleted", "settings_activity_log_streaming_delete_log_streaming_failed": "Failed to delete log stream destination.", "settings_activity_log_streaming_table_title": "All log streams", "settings_activity_log_streaming_table_header_name": "Name", diff --git a/web/messages/en/users.json b/web/messages/en/users.json index 3a8c261fb3..6374f40751 100644 --- a/web/messages/en/users.json +++ b/web/messages/en/users.json @@ -30,8 +30,16 @@ "users_row_menu_initiate_self_enrollment": "Initiate self-enrollment", "users_row_menu_ip_settings": "User devices IP settings", "users_modal_disable_mfa_title": "Disable MFA", - "users_modal_disable_mfa_content": "Are you sure you want to disable MFA for **{name}**? This will remove all configured MFA methods and recovery codes.", + "users_modal_disable_mfa_content": "Are you sure you want to disable MFA for {name}? This will remove all configured MFA methods and recovery codes.", "users_disable_mfa_success": "MFA disabled", "users_disable_mfa_error": "Failed to disable MFA", + "users_modal_disable_title": "Disable account", + "users_modal_disable_content": "Are you sure you want to disable {name} account?", + "users_disable_success": "Account disabled", + "users_disable_error": "Failed to disable account", + "users_modal_enable_title": "Enable account", + "users_modal_enable_content": "Are you sure you want to enable {name} account?", + "users_enable_success": "Account enabled", + "users_enable_error": "Failed to enable account", "modal_edit_user_groups_title": "Edit user groups" } diff --git a/web/src/pages/EdgesPage/EdgesTable.tsx b/web/src/pages/EdgesPage/EdgesTable.tsx index 3ea13d2cbb..d01178a0f3 100644 --- a/web/src/pages/EdgesPage/EdgesTable.tsx +++ b/web/src/pages/EdgesPage/EdgesTable.tsx @@ -23,6 +23,7 @@ import { TableBody } from '../../shared/defguard-ui/components/table/TableBody/T import { TableCell } from '../../shared/defguard-ui/components/table/TableCell/TableCell'; import { TableEditCell } from '../../shared/defguard-ui/components/table/TableEditCell/TableEditCell'; import { TableTop } from '../../shared/defguard-ui/components/table/TableTop/TableTop'; +import { Snackbar } from '../../shared/defguard-ui/providers/snackbar/snackbar'; import { isPresent } from '../../shared/defguard-ui/utils/isPresent'; import { openModal } from '../../shared/hooks/modalControls/modalsSubjects'; import { ModalName } from '../../shared/hooks/modalControls/modalTypes'; @@ -240,6 +241,8 @@ export const EdgesTable = () => { actionPromise: () => api.edge.deleteEdge(rowData.id), invalidateKeys: [['edge']], submitProps: { text: m.controls_delete(), variant: 'critical' }, + onSuccess: () => Snackbar.default(m.modal_delete_edge_success()), + onError: () => Snackbar.error(m.modal_delete_edge_error()), }); }, }, diff --git a/web/src/pages/GroupsPage/components/GroupsTable/GroupsTable.tsx b/web/src/pages/GroupsPage/components/GroupsTable/GroupsTable.tsx index c23cc29d41..3946e9b970 100644 --- a/web/src/pages/GroupsPage/components/GroupsTable/GroupsTable.tsx +++ b/web/src/pages/GroupsPage/components/GroupsTable/GroupsTable.tsx @@ -17,6 +17,7 @@ import { TableBody } from '../../../../shared/defguard-ui/components/table/Table import { TableCell } from '../../../../shared/defguard-ui/components/table/TableCell/TableCell'; import { TableEditCell } from '../../../../shared/defguard-ui/components/table/TableEditCell/TableEditCell'; import { TableTop } from '../../../../shared/defguard-ui/components/table/TableTop/TableTop'; +import { Snackbar } from '../../../../shared/defguard-ui/providers/snackbar/snackbar'; import { openModal } from '../../../../shared/hooks/modalControls/modalsSubjects'; import { ModalName } from '../../../../shared/hooks/modalControls/modalTypes'; @@ -122,6 +123,8 @@ export const GroupsTable = ({ groups, users }: Props) => { actionPromise: () => api.group.deleteGroup(rowData.name), invalidateKeys: [['group'], ['group-info']], submitProps: { text: m.controls_delete(), variant: 'critical' }, + onSuccess: () => Snackbar.default(m.modal_delete_group_success()), + onError: () => Snackbar.error(m.modal_delete_group_error()), }); }, }, diff --git a/web/src/pages/UsersOverviewPage/UsersTable.tsx b/web/src/pages/UsersOverviewPage/UsersTable.tsx index b8dbde8348..80ec2b6d3b 100644 --- a/web/src/pages/UsersOverviewPage/UsersTable.tsx +++ b/web/src/pages/UsersOverviewPage/UsersTable.tsx @@ -98,13 +98,6 @@ export const UsersTable = () => { [groups?.map, groups], ); - const { mutate: changeAccountActiveState } = useMutation({ - mutationFn: api.user.activeStateChange, - meta: { - invalidate: [['user-overview'], ['user']], - }, - }); - const { mutate: editUser } = useMutation({ mutationFn: api.user.editUser, meta: { @@ -263,10 +256,40 @@ export const UsersTable = () => { icon: rowData.is_active ? 'disabled' : 'check-circle', testId: 'change-account-status', onClick: () => { - changeAccountActiveState({ - active: !rowData.is_active, - username: rowData.username, - }); + if (rowData.is_active) { + openModal(ModalName.ConfirmAction, { + title: m.users_modal_disable_title(), + contentMd: m.users_modal_disable_content({ name: rowData.name }), + actionPromise: () => + api.user.activeStateChange({ + active: false, + username: rowData.username, + }), + invalidateKeys: [['user-overview'], ['user']], + submitProps: { + text: m.users_row_menu_disable(), + variant: 'critical', + }, + onSuccess: () => Snackbar.default(m.users_disable_success()), + onError: () => Snackbar.error(m.users_disable_error()), + }); + } else { + openModal(ModalName.ConfirmAction, { + title: m.users_modal_enable_title(), + contentMd: m.users_modal_enable_content({ name: rowData.name }), + actionPromise: () => + api.user.activeStateChange({ + active: true, + username: rowData.username, + }), + invalidateKeys: [['user-overview'], ['user']], + submitProps: { + text: m.users_row_menu_enable(), + }, + onSuccess: () => Snackbar.default(m.users_enable_success()), + onError: () => Snackbar.error(m.users_enable_error()), + }); + } }, }, ], @@ -370,6 +393,8 @@ export const UsersTable = () => { text: m.users_row_menu_delete(), variant: 'critical', }, + onSuccess: () => Snackbar.default(m.modal_delete_user_success()), + onError: () => Snackbar.error(m.modal_delete_user_error()), }); }, }, @@ -455,7 +480,6 @@ export const UsersTable = () => { navigate, reservedEmails, reservedUsernames, - changeAccountActiveState, groupsOptions, handleEditGroups, groups, diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/ActivityLogStreamTable.tsx b/web/src/pages/settings/SettingsActivityLogStreamingPage/ActivityLogStreamTable.tsx index aaaf5de295..4132dc9b5c 100644 --- a/web/src/pages/settings/SettingsActivityLogStreamingPage/ActivityLogStreamTable.tsx +++ b/web/src/pages/settings/SettingsActivityLogStreamingPage/ActivityLogStreamTable.tsx @@ -87,6 +87,10 @@ export const ActivityLogStreamTable = ({ data: rowData }: Props) => { actionPromise: () => api.activityLogStream.deleteStream(row.id), invalidateKeys: [['activity_log_stream']], submitProps: { text: m.controls_delete(), variant: 'critical' }, + onSuccess: () => + Snackbar.default( + m.settings_activity_log_streaming_delete_log_streaming_success(), + ), onError: () => Snackbar.error( m.settings_activity_log_streaming_delete_log_streaming_failed(), diff --git a/web/src/pages/user-profile/UserProfilePage/tabs/ProfileApiTokensTab/components/ProfileApiTokensTable/ProfileApiTokensTable.tsx b/web/src/pages/user-profile/UserProfilePage/tabs/ProfileApiTokensTab/components/ProfileApiTokensTable/ProfileApiTokensTable.tsx index 0722d715d4..5b83a91d05 100644 --- a/web/src/pages/user-profile/UserProfilePage/tabs/ProfileApiTokensTab/components/ProfileApiTokensTable/ProfileApiTokensTable.tsx +++ b/web/src/pages/user-profile/UserProfilePage/tabs/ProfileApiTokensTab/components/ProfileApiTokensTable/ProfileApiTokensTable.tsx @@ -1,4 +1,3 @@ -import { useMutation } from '@tanstack/react-query'; import { createColumnHelper, getCoreRowModel, @@ -13,6 +12,7 @@ import { tableEditColumnSize } from '../../../../../../../shared/defguard-ui/com import { TableBody } from '../../../../../../../shared/defguard-ui/components/table/TableBody/TableBody'; import { TableCell } from '../../../../../../../shared/defguard-ui/components/table/TableCell/TableCell'; import { TableEditCell } from '../../../../../../../shared/defguard-ui/components/table/TableEditCell/TableEditCell'; +import { Snackbar } from '../../../../../../../shared/defguard-ui/providers/snackbar/snackbar'; import { openModal } from '../../../../../../../shared/hooks/modalControls/modalsSubjects'; import { ModalName } from '../../../../../../../shared/hooks/modalControls/modalTypes'; import { tableSortingFns } from '../../../../../../../shared/utils/dateSortingFn'; @@ -27,13 +27,6 @@ export const ProfileApiTokensTable = () => { const username = useUserProfile((s) => s.user.username); const data = useUserProfile((s) => s.apiTokens); - const { mutate: deleteApiToken } = useMutation({ - mutationFn: api.user.deleteApiToken, - meta: { - invalidate: [['user-overview'], ['user', username, 'api_token']], - }, - }); - const columns = useMemo( () => [ columnHelper.accessor('name', { @@ -92,9 +85,21 @@ export const ProfileApiTokensTable = () => { variant: 'danger', text: m.controls_delete(), onClick: () => { - deleteApiToken({ - id: rowData.id, - username, + openModal(ModalName.ConfirmAction, { + title: m.modal_delete_api_token_title(), + contentMd: m.modal_delete_api_token_content({ + name: rowData.name, + }), + actionPromise: () => + api.user.deleteApiToken({ id: rowData.id, username }), + invalidateKeys: [ + ['user-overview'], + ['user', username, 'api_token'], + ], + submitProps: { text: m.controls_delete(), variant: 'critical' }, + onSuccess: () => + Snackbar.default(m.modal_delete_api_token_success()), + onError: () => Snackbar.error(m.modal_delete_api_token_error()), }); }, }, @@ -106,7 +111,7 @@ export const ProfileApiTokensTable = () => { }, }), ], - [deleteApiToken, username], + [username], ); const table = useReactTable({ diff --git a/web/src/pages/user-profile/UserProfilePage/tabs/ProfileAuthKeysTab/ProfileAuthKeysTable.tsx b/web/src/pages/user-profile/UserProfilePage/tabs/ProfileAuthKeysTab/ProfileAuthKeysTable.tsx index eb73423c84..82d50b6064 100644 --- a/web/src/pages/user-profile/UserProfilePage/tabs/ProfileAuthKeysTab/ProfileAuthKeysTable.tsx +++ b/web/src/pages/user-profile/UserProfilePage/tabs/ProfileAuthKeysTab/ProfileAuthKeysTable.tsx @@ -1,4 +1,3 @@ -import { useMutation } from '@tanstack/react-query'; import { createColumnHelper, getCoreRowModel, @@ -19,6 +18,7 @@ import { TableBody } from '../../../../../shared/defguard-ui/components/table/Ta import { TableCell } from '../../../../../shared/defguard-ui/components/table/TableCell/TableCell'; import { TableEditCell } from '../../../../../shared/defguard-ui/components/table/TableEditCell/TableEditCell'; import { useClipboard } from '../../../../../shared/defguard-ui/hooks/useClipboard'; +import { Snackbar } from '../../../../../shared/defguard-ui/providers/snackbar/snackbar'; import { isPresent } from '../../../../../shared/defguard-ui/utils/isPresent'; import { openModal } from '../../../../../shared/hooks/modalControls/modalsSubjects'; import { ModalName } from '../../../../../shared/hooks/modalControls/modalTypes'; @@ -84,13 +84,6 @@ export const ProfileAuthKeysTable = () => { const authKeys = useUserProfile((s) => s.authKeys); const mapped = useMemo(() => mapData(authKeys), [authKeys]); - const { mutate: deleteAuthKey } = useMutation({ - mutationFn: api.user.deleteAuthKey, - meta: { - invalidate: [['user-overview'], ['user', username, 'auth_key']], - }, - }); - const columns = useMemo( () => [ columnHelper.accessor('name', { @@ -188,9 +181,16 @@ export const ProfileAuthKeysTable = () => { variant: 'danger', text: m.controls_delete(), onClick: () => { - deleteAuthKey({ - id: rowData.id, - username, + openModal(ModalName.ConfirmAction, { + title: m.modal_delete_auth_key_title(), + contentMd: m.modal_delete_auth_key_content({ name: rowData.name }), + actionPromise: () => + api.user.deleteAuthKey({ id: rowData.id, username }), + invalidateKeys: [['user-overview'], ['user', username, 'auth_key']], + submitProps: { text: m.controls_delete(), variant: 'critical' }, + onSuccess: () => + Snackbar.default(m.modal_delete_auth_key_success()), + onError: () => Snackbar.error(m.modal_delete_auth_key_error()), }); }, }, @@ -201,7 +201,7 @@ export const ProfileAuthKeysTable = () => { }, }), ], - [deleteAuthKey, username, writeToClipboard, mapped], + [username, writeToClipboard, mapped], ); const table = useReactTable({ diff --git a/web/src/pages/user-profile/UserProfilePage/tabs/ProfileDetailsTab/components/ProfileAuthCard/ProfileAuthCard.tsx b/web/src/pages/user-profile/UserProfilePage/tabs/ProfileDetailsTab/components/ProfileAuthCard/ProfileAuthCard.tsx index 5e0760fcbe..c2d91e814b 100644 --- a/web/src/pages/user-profile/UserProfilePage/tabs/ProfileDetailsTab/components/ProfileAuthCard/ProfileAuthCard.tsx +++ b/web/src/pages/user-profile/UserProfilePage/tabs/ProfileDetailsTab/components/ProfileAuthCard/ProfileAuthCard.tsx @@ -22,6 +22,7 @@ import { type UserMfaMethodValue, } from '../../../../../../../shared/api/types'; import { Badge } from '../../../../../../../shared/defguard-ui/components/Badge/Badge'; +import { Snackbar } from '../../../../../../../shared/defguard-ui/providers/snackbar/snackbar'; import { ThemeSpacing } from '../../../../../../../shared/defguard-ui/types'; import { openModal } from '../../../../../../../shared/hooks/modalControls/modalsSubjects'; import { ModalName } from '../../../../../../../shared/hooks/modalControls/modalTypes'; @@ -66,43 +67,10 @@ export const ProfileAuthCard = () => { meta: invalidateAfterMfaChange, }); - const { mutate: disableMfaMutation } = useMutation({ - mutationFn: () => { - if (user.username === authUsername) { - return api.auth.mfa.disable(); - } - return api.user.disableMfa(user.username); - }, - meta: invalidateAfterMfaChange, - }); - const { mutate: mutateEnableMfa } = useMutation({ mutationFn: api.auth.mfa.enable, meta: invalidateAfterMfaChange, }); - - const { mutate: mutateDisableEmailMfa } = useMutation({ - mutationFn: api.user.mfa.email.disable, - meta: invalidateAfterMfaChange, - }); - - const { mutate: mutateDisableTotp } = useMutation({ - mutationFn: api.user.mfa.totp.disable, - meta: invalidateAfterMfaChange, - }); - - const { mutate: mutateDisableWebauthn } = useMutation({ - mutationFn: () => { - const res = securityKeys.map((key) => - api.auth.mfa.webauthn.deleteKey({ - username: user.username, - keyId: key.id, - }), - ); - return Promise.all(res); - }, - meta: invalidateAfterMfaChange, - }); const emailMenuItems = useMemo(() => { const items: MenuItemProps[] = []; if (!user.email_mfa_enabled && user.username === authUsername) { @@ -124,7 +92,16 @@ export const ProfileAuthCard = () => { items.push({ text: m.controls_disable(), icon: 'minus-circle', - onClick: () => mutateDisableEmailMfa(user.username), + onClick: () => + openModal(ModalName.ConfirmAction, { + title: m.modal_disable_mfa_email_title(), + contentMd: m.modal_disable_mfa_email_content(), + actionPromise: () => api.user.mfa.email.disable(user.username), + invalidateKeys: invalidateAfterMfaChange.invalidate, + submitProps: { text: m.controls_disable(), variant: 'critical' }, + onSuccess: () => Snackbar.default(m.modal_disable_mfa_email_success()), + onError: () => Snackbar.error(m.modal_disable_mfa_email_error()), + }), }); } const res: MenuItemsGroup = { @@ -133,11 +110,11 @@ export const ProfileAuthCard = () => { return items.length > 0 ? res : null; }, [ user.email_mfa_enabled, - mutateDisableEmailMfa, mutateSetDefaultMfa, user.mfa_method, user.username, authUsername, + invalidateAfterMfaChange, ]); const mfaMenuItems = useMemo(() => { @@ -167,7 +144,21 @@ export const ProfileAuthCard = () => { icon: 'disabled', variant: 'danger', text: m.profile_auth_card_2fa_controls_disable_all(), - onClick: disableMfaMutation, + onClick: () => + openModal(ModalName.ConfirmAction, { + title: m.modal_disable_mfa_all_title(), + contentMd: m.modal_disable_mfa_all_content(), + actionPromise: () => { + if (user.username === authUsername) { + return api.auth.mfa.disable(); + } + return api.user.disableMfa(user.username); + }, + invalidateKeys: invalidateAfterMfaChange.invalidate, + submitProps: { text: m.controls_disable(), variant: 'critical' }, + onSuccess: () => Snackbar.default(m.modal_disable_mfa_all_success()), + onError: () => Snackbar.error(m.modal_disable_mfa_all_error()), + }), }, ], }); @@ -177,8 +168,10 @@ export const ProfileAuthCard = () => { user.email_mfa_enabled, user.mfa_enabled, user.totp_enabled, + user.username, + authUsername, mutateEnableMfa, - disableMfaMutation, + invalidateAfterMfaChange, ]); const webauthnMenuItems = useMemo(() => { @@ -203,17 +196,34 @@ export const ProfileAuthCard = () => { text: m.profile_auth_card_disable_passkeys(), variant: 'danger', icon: 'delete', - onClick: () => mutateDisableWebauthn(), + onClick: () => + openModal(ModalName.ConfirmAction, { + title: m.modal_disable_mfa_passkeys_title(), + contentMd: m.modal_disable_mfa_passkeys_content(), + actionPromise: () => + Promise.all( + securityKeys.map((key) => + api.auth.mfa.webauthn.deleteKey({ + username: user.username, + keyId: key.id, + }), + ), + ), + invalidateKeys: invalidateAfterMfaChange.invalidate, + submitProps: { text: m.controls_delete(), variant: 'critical' }, + onSuccess: () => Snackbar.default(m.modal_disable_mfa_passkeys_success()), + onError: () => Snackbar.error(m.modal_disable_mfa_passkeys_error()), + }), }); } return items.length > 0 ? { items } : null; }, [ - mutateDisableWebauthn, - securityKeys.length, + securityKeys, mutateSetDefaultMfa, user.mfa_method, user.username, authUsername, + invalidateAfterMfaChange, ]); const totpMenuItems = useMemo(() => { @@ -242,18 +252,27 @@ export const ProfileAuthCard = () => { items.push({ icon: 'minus-circle', text: m.controls_disable(), - onClick: () => mutateDisableTotp(user.username), + onClick: () => + openModal(ModalName.ConfirmAction, { + title: m.modal_disable_mfa_totp_title(), + contentMd: m.modal_disable_mfa_totp_content(), + actionPromise: () => api.user.mfa.totp.disable(user.username), + invalidateKeys: invalidateAfterMfaChange.invalidate, + submitProps: { text: m.controls_disable(), variant: 'critical' }, + onSuccess: () => Snackbar.default(m.modal_disable_mfa_totp_success()), + onError: () => Snackbar.error(m.modal_disable_mfa_totp_error()), + }), }); } return items.length > 0 ? { items } : null; }, [ - mutateDisableTotp, user.totp_enabled, mutateSetDefaultMfa, user.mfa_method, user.username, authUsername, + invalidateAfterMfaChange, ]); return ( @@ -363,13 +382,6 @@ const WebauthnRow = ({ securityKey: SecurityKey; username: string; }) => { - const { mutate } = useMutation({ - mutationFn: api.auth.mfa.webauthn.deleteKey, - meta: { - invalidate: [['user', username]], - }, - }); - const menuItems = useMemo(() => { const items: MenuItemProps[] = []; items.push({ @@ -377,15 +389,21 @@ const WebauthnRow = ({ icon: 'delete', variant: 'danger', onClick: () => - mutate({ - keyId: securityKey.id, - username, + openModal(ModalName.ConfirmAction, { + title: m.modal_delete_mfa_passkey_title(), + contentMd: m.modal_delete_mfa_passkey_content({ name: securityKey.name }), + actionPromise: () => + api.auth.mfa.webauthn.deleteKey({ keyId: securityKey.id, username }), + invalidateKeys: [['user', username]], + submitProps: { text: m.controls_delete(), variant: 'critical' }, + onSuccess: () => Snackbar.default(m.modal_delete_mfa_passkey_success()), + onError: () => Snackbar.error(m.modal_delete_mfa_passkey_error()), }), }); return { items, }; - }, [mutate, securityKey.id, username]); + }, [securityKey.id, securityKey.name, username]); return (
diff --git a/web/src/pages/user-profile/UserProfilePage/tabs/ProfileDetailsTab/components/ProfileAuthorizedApps/ProfileAuthorizedApps.tsx b/web/src/pages/user-profile/UserProfilePage/tabs/ProfileDetailsTab/components/ProfileAuthorizedApps/ProfileAuthorizedApps.tsx index d82585fe52..e69d84c5e0 100644 --- a/web/src/pages/user-profile/UserProfilePage/tabs/ProfileDetailsTab/components/ProfileAuthorizedApps/ProfileAuthorizedApps.tsx +++ b/web/src/pages/user-profile/UserProfilePage/tabs/ProfileDetailsTab/components/ProfileAuthorizedApps/ProfileAuthorizedApps.tsx @@ -1,12 +1,14 @@ import './style.scss'; -import { useMutation } from '@tanstack/react-query'; import { cloneDeep } from 'lodash-es'; import { useCallback } from 'react'; import { m } from '../../../../../../../paraglide/messages'; import api from '../../../../../../../shared/api/api'; import type { OAuth2AuthorizedApps, User } from '../../../../../../../shared/api/types'; import { IconButton } from '../../../../../../../shared/defguard-ui/components/IconButton/IconButton'; +import { Snackbar } from '../../../../../../../shared/defguard-ui/providers/snackbar/snackbar'; import { isPresent } from '../../../../../../../shared/defguard-ui/utils/isPresent'; +import { openModal } from '../../../../../../../shared/hooks/modalControls/modalsSubjects'; +import { ModalName } from '../../../../../../../shared/hooks/modalControls/modalTypes'; import { ProfileCard } from '../../../../components/ProfileCard/ProfileCard'; import { useUserProfile } from '../../../../hooks/useUserProfilePage'; import { AuthorizedAppIconPlaceholder } from './icons/AuthorizedAppIconPlaceholder'; @@ -51,13 +53,6 @@ const AuthorizedApp = ({ data }: Props) => { }); }, [data.oauth2client_id, username]); - const { mutate } = useMutation({ - mutationFn: deleteAuthorizedApp, - meta: { - invalidate: [['oauth'], ['user', username]], - }, - }); - return (
@@ -66,7 +61,15 @@ const AuthorizedApp = ({ data }: Props) => { { - mutate(); + openModal(ModalName.ConfirmAction, { + title: m.modal_delete_authorized_app_title(), + contentMd: m.modal_delete_authorized_app(), + actionPromise: deleteAuthorizedApp, + invalidateKeys: [['oauth'], ['user', username]], + submitProps: { text: m.controls_delete(), variant: 'critical' }, + onSuccess: () => Snackbar.default(m.modal_delete_authorized_app_success()), + onError: () => Snackbar.error(m.modal_delete_authorized_app_error()), + }); }} />