diff --git a/.github/workflows/lint-web.yml b/.github/workflows/lint-web.yml index b998924a4f..ae2de80f20 100644 --- a/.github/workflows/lint-web.yml +++ b/.github/workflows/lint-web.yml @@ -41,4 +41,4 @@ jobs: run: pnpm run lint - name: Audit working-directory: ./web - run: pnpm audit --prod + run: pnpm audit --prod --ignore-unfixable diff --git a/web/messages/en/common.json b/web/messages/en/common.json index 8af5a20a1d..d17c436882 100644 --- a/web/messages/en/common.json +++ b/web/messages/en/common.json @@ -26,6 +26,7 @@ "controls_close": "Close", "controls_back": "Back", "controls_delete": "Delete", + "controls_reset": "Reset", "controls_edit": "Edit", "controls_logout": "Logout", "controls_keep": "Keep", diff --git a/web/messages/en/modal.json b/web/messages/en/modal.json index 0e0ed3b7af..d9c7437d17 100644 --- a/web/messages/en/modal.json +++ b/web/messages/en/modal.json @@ -12,6 +12,12 @@ "modal_delete_location_body": "Deleting location {name} will also delete related gateways. This action cannot be undone.", "modal_delete_network_device_title": "Delete device", "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_edge_title": "Delete edge", + "modal_delete_edge_body": "Are you sure you want to delete edge {name}? This action cannot be undone.", + "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_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", diff --git a/web/messages/en/openid.json b/web/messages/en/openid.json index 08f4e7c90a..aafd1e4611 100644 --- a/web/messages/en/openid.json +++ b/web/messages/en/openid.json @@ -10,6 +10,10 @@ "openid_table_top_title": "All apps", "openid_delete_success": "OpenID application deleted", "openid_delete_failed": "Failed to delete OpenID application", + "settings_openid_provider_delete_confirm_title": "Delete Identity Provider", + "settings_openid_provider_delete_confirm_body": "Are you sure you want to delete this external identity provider? This action cannot be undone.", + "settings_openid_provider_delete_success": "Identity provider deleted", + "settings_openid_provider_delete_failed": "Failed to delete identity provider", "openid_consent_title": "{name} would like to", "openid_consent_scope_openid": "Use OpenID.", "openid_consent_scope_profile": "Know basic information from your profile.", diff --git a/web/messages/en/settings.json b/web/messages/en/settings.json index 4d54ebb8d9..e48eab2b8f 100644 --- a/web/messages/en/settings.json +++ b/web/messages/en/settings.json @@ -44,9 +44,14 @@ "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_failed": "Failed to delete log stream destination.", "settings_activity_log_streaming_table_title": "All log streams", "settings_activity_log_streaming_table_header_name": "Name", "settings_activity_log_streaming_table_stream_type_name": "Destination", "settings_msg_saved": "Settings saved", - "settings_msg_save_failed": "Failed to save settings" + "settings_msg_save_failed": "Failed to save settings", + "settings_smtp_reset_confirm_title": "Reset SMTP Settings", + "settings_smtp_reset_confirm_body": "Are you sure you want to reset SMTP settings? This action cannot be undone.", + "settings_smtp_reset_success": "SMTP settings reset", + "settings_smtp_reset_failed": "Failed to reset SMTP settings" } diff --git a/web/messages/en/webhooks.json b/web/messages/en/webhooks.json index ce4dd6061c..879e46886b 100644 --- a/web/messages/en/webhooks.json +++ b/web/messages/en/webhooks.json @@ -4,5 +4,9 @@ "webhooks_table_title": "All Webhooks", "webhooks_add": "Add new webhook", "webhooks_empty_title": "You don't have any Webhooks.", - "webhooks_empty_subtitle": "To add one, click the button below." + "webhooks_empty_subtitle": "To add one, click the button below.", + "webhooks_delete_confirm_title": "Delete Webhook", + "webhooks_delete_confirm_body": "Are you sure you want to delete this webhook? This action cannot be undone.", + "webhooks_delete_success": "Webhook deleted", + "webhooks_delete_failed": "Failed to delete webhook" } diff --git a/web/pnpm-workspace.yaml b/web/pnpm-workspace.yaml index 17a31e5ca3..f8ffa1d93f 100644 --- a/web/pnpm-workspace.yaml +++ b/web/pnpm-workspace.yaml @@ -1,3 +1,5 @@ +auditConfig: {} + onlyBuiltDependencies: - '@parcel/watcher' - '@swc/core' diff --git a/web/src/pages/EdgesPage/EdgesTable.tsx b/web/src/pages/EdgesPage/EdgesTable.tsx index d608f823e8..3ea13d2cbb 100644 --- a/web/src/pages/EdgesPage/EdgesTable.tsx +++ b/web/src/pages/EdgesPage/EdgesTable.tsx @@ -24,6 +24,8 @@ import { TableCell } from '../../shared/defguard-ui/components/table/TableCell/T import { TableEditCell } from '../../shared/defguard-ui/components/table/TableEditCell/TableEditCell'; import { TableTop } from '../../shared/defguard-ui/components/table/TableTop/TableTop'; import { isPresent } from '../../shared/defguard-ui/utils/isPresent'; +import { openModal } from '../../shared/hooks/modalControls/modalsSubjects'; +import { ModalName } from '../../shared/hooks/modalControls/modalTypes'; import { getEdgesQueryOptions, getLicenseInfoQueryOptions } from '../../shared/query'; import { displayDate } from '../../shared/utils/displayDate'; import { canUseEnterpriseFeature, licenseActionCheck } from '../../shared/utils/license'; @@ -72,13 +74,6 @@ export const EdgesTable = () => { const navigate = useNavigate(); - const { mutate: deleteEdge } = useMutation({ - mutationFn: api.edge.deleteEdge, - meta: { - invalidate: ['edge'], - }, - }); - const { mutate: toggleEdge } = useMutation({ mutationFn: api.edge.editEdge, meta: { @@ -239,7 +234,13 @@ export const EdgesTable = () => { icon: 'delete', variant: 'danger', onClick: () => { - deleteEdge(rowData.id); + openModal(ModalName.ConfirmAction, { + title: m.modal_delete_edge_title(), + contentMd: m.modal_delete_edge_body({ name: rowData.name }), + actionPromise: () => api.edge.deleteEdge(rowData.id), + invalidateKeys: [['edge']], + submitProps: { text: m.controls_delete(), variant: 'critical' }, + }); }, }, ], @@ -250,7 +251,7 @@ export const EdgesTable = () => { }, }), ], - [deleteEdge, navigate, toggleEdge], + [navigate, toggleEdge], ); const table = useReactTable({ diff --git a/web/src/pages/EditEdgePage/EditEdgePage.tsx b/web/src/pages/EditEdgePage/EditEdgePage.tsx index 48948cc049..d807d566db 100644 --- a/web/src/pages/EditEdgePage/EditEdgePage.tsx +++ b/web/src/pages/EditEdgePage/EditEdgePage.tsx @@ -13,6 +13,8 @@ import { Snackbar } from '../../shared/defguard-ui/providers/snackbar/snackbar'; import { ThemeSpacing } from '../../shared/defguard-ui/types'; import { useAppForm } from '../../shared/form'; import { formChangeLogic } from '../../shared/formLogic'; +import { openModal } from '../../shared/hooks/modalControls/modalsSubjects'; +import { ModalName } from '../../shared/hooks/modalControls/modalTypes'; import { getEdgeQueryOptions } from '../../shared/query'; export const EditEdgePage = () => { @@ -69,23 +71,6 @@ const EditEdgeForm = ({ edge }: { edge: Edge }) => { }, }); - const { mutate: deleteEdge, isPending: deletePending } = useMutation({ - mutationFn: () => api.edge.deleteEdge(edge.id), - meta: { - invalidate: ['edge'], - }, - onSuccess: () => { - navigate({ - to: '/edges', - replace: true, - }); - Snackbar.default(m.edge_delete_success()); - }, - onError: () => { - Snackbar.error(m.edge_delete_failed()); - }, - }); - const defaultValues = useMemo((): FormFields => ({ ...edge }), [edge]); const form = useAppForm({ @@ -142,9 +127,19 @@ const EditEdgeForm = ({ edge }: { edge: Edge }) => { deleteProps={{ text: m.edge_edit_delete(), onClick: () => { - deleteEdge(); + openModal(ModalName.ConfirmAction, { + title: m.modal_delete_edge_title(), + contentMd: m.modal_delete_edge_body({ name: edge.name }), + actionPromise: () => api.edge.deleteEdge(edge.id), + invalidateKeys: [['edge']], + submitProps: { text: m.edge_edit_delete(), variant: 'critical' }, + onSuccess: () => { + navigate({ to: '/edges', replace: true }); + Snackbar.default(m.edge_delete_success()); + }, + onError: () => Snackbar.error(m.edge_delete_failed()), + }); }, - loading: deletePending, disabled: isSubmitting, }} submitProps={{ diff --git a/web/src/pages/EditGatewayPage/EditGatewayPage.tsx b/web/src/pages/EditGatewayPage/EditGatewayPage.tsx index 6a96e3cab1..cdb60f8822 100644 --- a/web/src/pages/EditGatewayPage/EditGatewayPage.tsx +++ b/web/src/pages/EditGatewayPage/EditGatewayPage.tsx @@ -13,7 +13,9 @@ import { Snackbar } from '../../shared/defguard-ui/providers/snackbar/snackbar'; import { ThemeSpacing } from '../../shared/defguard-ui/types'; import { useAppForm } from '../../shared/form'; import { formChangeLogic } from '../../shared/formLogic'; -import { getGatewayQueryOptions } from '../../shared/query'; +import { openModal } from '../../shared/hooks/modalControls/modalsSubjects'; +import { ModalName } from '../../shared/hooks/modalControls/modalTypes'; +import { getGatewayQueryOptions, getLocationQueryOptions } from '../../shared/query'; export const EditGatewayPage = () => { const { gatewayId } = useParams({ @@ -56,6 +58,9 @@ type FormFields = z.infer; const EditGatewayForm = ({ gateway }: { gateway: Gateway }) => { const navigate = useNavigate(); + const { data: location } = useSuspenseQuery( + getLocationQueryOptions(gateway.location_id), + ); const { mutateAsync: editGateway } = useMutation({ mutationFn: api.gateway.editGateway, @@ -70,23 +75,6 @@ const EditGatewayForm = ({ gateway }: { gateway: Gateway }) => { }, }); - const { mutate: deleteGateway, isPending: deletePending } = useMutation({ - mutationFn: () => api.gateway.deleteGateway(gateway.id), - meta: { - invalidate: [['gateway'], ['network']], - }, - onSuccess: () => { - navigate({ - to: '/locations', - replace: true, - }); - Snackbar.default(m.gateway_delete_success()); - }, - onError: () => { - Snackbar.error(m.gateway_delete_failed()); - }, - }); - const defaultValues = useMemo((): FormFields => ({ ...gateway }), [gateway]); const form = useAppForm({ @@ -144,9 +132,22 @@ const EditGatewayForm = ({ gateway }: { gateway: Gateway }) => { deleteProps={{ text: m.gateway_edit_delete(), onClick: () => { - deleteGateway(); + openModal(ModalName.ConfirmAction, { + title: m.modal_delete_gateway_title(), + contentMd: m.modal_delete_gateway_body({ + name: gateway.name, + locationName: location.name, + }), + actionPromise: () => api.gateway.deleteGateway(gateway.id), + invalidateKeys: [['gateway'], ['network']], + submitProps: { text: m.gateway_edit_delete(), variant: 'critical' }, + onSuccess: () => { + navigate({ to: '/locations', replace: true }); + Snackbar.default(m.gateway_delete_success()); + }, + onError: () => Snackbar.error(m.gateway_delete_failed()), + }); }, - loading: deletePending, disabled: isSubmitting, }} submitProps={{ diff --git a/web/src/pages/EditLocationPage/EditLocationPage.tsx b/web/src/pages/EditLocationPage/EditLocationPage.tsx index 1e99bb590c..7ceee535e7 100644 --- a/web/src/pages/EditLocationPage/EditLocationPage.tsx +++ b/web/src/pages/EditLocationPage/EditLocationPage.tsx @@ -25,6 +25,8 @@ import { ThemeSpacing } from '../../shared/defguard-ui/types'; import { isPresent } from '../../shared/defguard-ui/utils/isPresent'; import { useAppForm } from '../../shared/form'; import { formChangeLogic } from '../../shared/formLogic'; +import { openModal } from '../../shared/hooks/modalControls/modalsSubjects'; +import { ModalName } from '../../shared/hooks/modalControls/modalTypes'; import { getLicenseInfoQueryOptions, getLocationQueryOptions } from '../../shared/query'; import { canUseBusinessFeature, @@ -192,19 +194,6 @@ const EditLocationForm = ({ location }: { location: NetworkLocation }) => { }, }); - const { mutate: deleteLocation, isPending: deletePending } = useMutation({ - mutationFn: () => api.location.deleteLocation(location.id), - meta: { - invalidate: ['network'], - }, - onSuccess: () => { - navigate({ - to: '/locations', - replace: true, - }); - }, - }); - const defaultValues = useMemo( (): FormFields => ({ name: location.name, @@ -538,9 +527,19 @@ const EditLocationForm = ({ location }: { location: NetworkLocation }) => { deleteProps={{ text: 'Delete location', onClick: () => { - deleteLocation(); + openModal(ModalName.ConfirmAction, { + title: m.modal_delete_location_title(), + contentMd: m.modal_delete_location_body({ name: location.name }), + actionPromise: () => api.location.deleteLocation(location.id), + invalidateKeys: [['network'], ['enterprise_info']], + submitProps: { text: m.controls_delete(), variant: 'critical' }, + onSuccess: () => { + Snackbar.default(m.location_delete_success()); + navigate({ to: '/locations', replace: true }); + }, + onError: () => Snackbar.error(m.location_delete_failed()), + }); }, - loading: deletePending, disabled: isSubmitting, }} cancelProps={{ diff --git a/web/src/pages/GroupsPage/components/GroupsTable/GroupsTable.tsx b/web/src/pages/GroupsPage/components/GroupsTable/GroupsTable.tsx index 80580dce46..c23cc29d41 100644 --- a/web/src/pages/GroupsPage/components/GroupsTable/GroupsTable.tsx +++ b/web/src/pages/GroupsPage/components/GroupsTable/GroupsTable.tsx @@ -1,4 +1,3 @@ -import { useMutation } from '@tanstack/react-query'; import { createColumnHelper, getCoreRowModel, @@ -33,12 +32,6 @@ const columnHelper = createColumnHelper(); export const GroupsTable = ({ groups, users }: Props) => { const [search, setSearch] = useState(''); const reservedNames = useMemo(() => groups.map((g) => g.name), [groups]); - const { mutate: deleteGroup } = useMutation({ - mutationFn: api.group.deleteGroup, - meta: { - invalidate: [['group'], ['group-info']], - }, - }); const transformedData = useMemo(() => { let data = groups; @@ -123,7 +116,13 @@ export const GroupsTable = ({ groups, users }: Props) => { icon: 'delete', variant: 'danger', onClick: () => { - deleteGroup(rowData.name); + openModal(ModalName.ConfirmAction, { + title: m.modal_delete_group_title(), + contentMd: m.modal_delete_group_body({ name: rowData.name }), + actionPromise: () => api.group.deleteGroup(rowData.name), + invalidateKeys: [['group'], ['group-info']], + submitProps: { text: m.controls_delete(), variant: 'critical' }, + }); }, }, ]; @@ -131,7 +130,7 @@ export const GroupsTable = ({ groups, users }: Props) => { }, }), ], - [deleteGroup, reservedNames, users], + [reservedNames, users], ); const table = useReactTable({ diff --git a/web/src/pages/LocationsPage/components/GatewaysTable.tsx b/web/src/pages/LocationsPage/components/GatewaysTable.tsx index fa8276994b..b699433502 100644 --- a/web/src/pages/LocationsPage/components/GatewaysTable.tsx +++ b/web/src/pages/LocationsPage/components/GatewaysTable.tsx @@ -19,6 +19,7 @@ import { TableBody } from '../../../shared/defguard-ui/components/table/TableBod 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'; import { getGatewaysQueryOptions } from '../../../shared/query'; @@ -211,10 +212,17 @@ export const GatewaysTable = () => { icon: 'delete', variant: 'danger', onClick: () => { - openModal(ModalName.DeleteGateway, { - id: rowData.id, - name: rowData.name, - locationName: rowData.location_name, + openModal(ModalName.ConfirmAction, { + title: m.modal_delete_gateway_title(), + contentMd: m.modal_delete_gateway_body({ + name: rowData.name, + locationName: rowData.location_name, + }), + actionPromise: () => api.gateway.deleteGateway(rowData.id), + invalidateKeys: [['gateway'], ['network']], + submitProps: { text: m.controls_delete(), variant: 'critical' }, + onSuccess: () => Snackbar.default(m.gateway_delete_success()), + onError: () => Snackbar.error(m.gateway_delete_failed()), }); }, }, diff --git a/web/src/pages/LocationsPage/components/LocationsTable.tsx b/web/src/pages/LocationsPage/components/LocationsTable.tsx index 5cbad5a60b..0cf5f74b67 100644 --- a/web/src/pages/LocationsPage/components/LocationsTable.tsx +++ b/web/src/pages/LocationsPage/components/LocationsTable.tsx @@ -8,6 +8,7 @@ import { } from '@tanstack/react-table'; import { useMemo, useState } from 'react'; import { m } from '../../../paraglide/messages'; +import api from '../../../shared/api/api'; import type { NetworkLocation } from '../../../shared/api/types'; import { GatewaysStatusBadge } from '../../../shared/components/GatewaysStatusBadge/GatewaysStatusBadge'; import { TableValuesListCell } from '../../../shared/components/TableValuesListCell/TableValuesListCell'; @@ -23,6 +24,7 @@ import { TableBody } from '../../../shared/defguard-ui/components/table/TableBod 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 { ThemeSpacing, ThemeVariable } from '../../../shared/defguard-ui/types'; import { openModal } from '../../../shared/hooks/modalControls/modalsSubjects'; import { ModalName } from '../../../shared/hooks/modalControls/modalTypes'; @@ -281,9 +283,14 @@ export const LocationsTable = () => { text: m.controls_delete(), variant: 'danger', onClick: () => { - openModal(ModalName.DeleteLocation, { - id: row.id, - name: row.name, + openModal(ModalName.ConfirmAction, { + title: m.modal_delete_location_title(), + contentMd: m.modal_delete_location_body({ name: row.name }), + actionPromise: () => api.location.deleteLocation(row.id), + invalidateKeys: [['network'], ['enterprise_info']], + submitProps: { text: m.controls_delete(), variant: 'critical' }, + onSuccess: () => Snackbar.default(m.location_delete_success()), + onError: () => Snackbar.error(m.location_delete_failed()), }); }, }, diff --git a/web/src/pages/LocationsPage/modals/DeleteGatewayModal/DeleteGatewayModal.tsx b/web/src/pages/LocationsPage/modals/DeleteGatewayModal/DeleteGatewayModal.tsx deleted file mode 100644 index 02b1b81550..0000000000 --- a/web/src/pages/LocationsPage/modals/DeleteGatewayModal/DeleteGatewayModal.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { useMutation } from '@tanstack/react-query'; -import { useEffect, useState } from 'react'; -import { m } from '../../../../paraglide/messages'; -import api from '../../../../shared/api/api'; -import { AppText } from '../../../../shared/defguard-ui/components/AppText/AppText'; -import { Modal } from '../../../../shared/defguard-ui/components/Modal/Modal'; -import { ModalControls } from '../../../../shared/defguard-ui/components/ModalControls/ModalControls'; -import { Snackbar } from '../../../../shared/defguard-ui/providers/snackbar/snackbar'; -import { TextStyle } from '../../../../shared/defguard-ui/types'; -import { - subscribeCloseModal, - subscribeOpenModal, -} from '../../../../shared/hooks/modalControls/modalsSubjects'; -import { ModalName } from '../../../../shared/hooks/modalControls/modalTypes'; -import type { OpenDeleteGatewayModal } from '../../../../shared/hooks/modalControls/types'; - -const modalNameValue = ModalName.DeleteGateway; - -type ModalData = OpenDeleteGatewayModal; - -export const DeleteGatewayModal = () => { - const [isOpen, setOpen] = useState(false); - const [modalData, setModalData] = useState(null); - - const { mutateAsync: deleteGateway, isPending } = useMutation({ - mutationFn: api.gateway.deleteGateway, - meta: { - invalidate: [['gateway'], ['network']], - }, - }); - - useEffect(() => { - const openSub = subscribeOpenModal(modalNameValue, (data) => { - setModalData(data); - setOpen(true); - }); - const closeSub = subscribeCloseModal(modalNameValue, () => setOpen(false)); - return () => { - openSub.unsubscribe(); - closeSub.unsubscribe(); - }; - }, []); - - const handleDelete = async () => { - if (!modalData) return; - try { - await deleteGateway(modalData.id); - Snackbar.default(m.gateway_delete_success()); - setOpen(false); - } catch { - Snackbar.error(m.gateway_delete_failed()); - } - }; - - return ( - setOpen(false)} - afterClose={() => setModalData(null)} - > - - {m.modal_delete_gateway_body({ - name: modalData?.name ?? '', - locationName: modalData?.locationName ?? '', - })} - - setOpen(false), - disabled: isPending, - }} - /> - - ); -}; diff --git a/web/src/pages/LocationsPage/modals/DeleteLocationModal/DeleteLocationModal.tsx b/web/src/pages/LocationsPage/modals/DeleteLocationModal/DeleteLocationModal.tsx deleted file mode 100644 index 6fc6baebbb..0000000000 --- a/web/src/pages/LocationsPage/modals/DeleteLocationModal/DeleteLocationModal.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { useMutation } from '@tanstack/react-query'; -import { useEffect, useState } from 'react'; -import { m } from '../../../../paraglide/messages'; -import api from '../../../../shared/api/api'; -import { AppText } from '../../../../shared/defguard-ui/components/AppText/AppText'; -import { Modal } from '../../../../shared/defguard-ui/components/Modal/Modal'; -import { ModalControls } from '../../../../shared/defguard-ui/components/ModalControls/ModalControls'; -import { Snackbar } from '../../../../shared/defguard-ui/providers/snackbar/snackbar'; -import { TextStyle } from '../../../../shared/defguard-ui/types'; -import { - subscribeCloseModal, - subscribeOpenModal, -} from '../../../../shared/hooks/modalControls/modalsSubjects'; -import { ModalName } from '../../../../shared/hooks/modalControls/modalTypes'; -import type { OpenDeleteLocationModal } from '../../../../shared/hooks/modalControls/types'; - -const modalNameValue = ModalName.DeleteLocation; - -type ModalData = OpenDeleteLocationModal; - -export const DeleteLocationModal = () => { - const [isOpen, setOpen] = useState(false); - const [modalData, setModalData] = useState(null); - - const { mutateAsync: deleteLocation, isPending } = useMutation({ - mutationFn: api.location.deleteLocation, - meta: { - invalidate: [['network'], ['enterprise_info']], - }, - }); - - useEffect(() => { - const openSub = subscribeOpenModal(modalNameValue, (data) => { - setModalData(data); - setOpen(true); - }); - const closeSub = subscribeCloseModal(modalNameValue, () => setOpen(false)); - return () => { - openSub.unsubscribe(); - closeSub.unsubscribe(); - }; - }, []); - - const handleDelete = async () => { - if (!modalData) return; - try { - await deleteLocation(modalData.id); - Snackbar.default(m.location_delete_success()); - setOpen(false); - } catch { - Snackbar.error(m.location_delete_failed()); - } - }; - - return ( - setOpen(false)} - afterClose={() => setModalData(null)} - > - - {m.modal_delete_location_body({ name: modalData?.name ?? '' })} - - setOpen(false), - disabled: isPending, - }} - /> - - ); -}; diff --git a/web/src/pages/LocationsPage/tabs/GatewaysTab.tsx b/web/src/pages/LocationsPage/tabs/GatewaysTab.tsx index 81f5048b3a..04613d6e64 100644 --- a/web/src/pages/LocationsPage/tabs/GatewaysTab.tsx +++ b/web/src/pages/LocationsPage/tabs/GatewaysTab.tsx @@ -1,15 +1,11 @@ import { Suspense } from 'react'; import { TableSkeleton } from '../../../shared/components/skeleton/TableSkeleton/TableSkeleton'; import { GatewaysTable } from '../components/GatewaysTable'; -import { DeleteGatewayModal } from '../modals/DeleteGatewayModal/DeleteGatewayModal'; export const GatewaysTab = () => { return ( - <> - }> - - - - + }> + + ); }; diff --git a/web/src/pages/LocationsPage/tabs/LocationsTab.tsx b/web/src/pages/LocationsPage/tabs/LocationsTab.tsx index bfb8c7f805..d4b5e5dc54 100644 --- a/web/src/pages/LocationsPage/tabs/LocationsTab.tsx +++ b/web/src/pages/LocationsPage/tabs/LocationsTab.tsx @@ -2,7 +2,6 @@ import { Suspense } from 'react'; import { TableSkeleton } from '../../../shared/components/skeleton/TableSkeleton/TableSkeleton'; import { LocationsTable } from '../components/LocationsTable'; import { AddLocationModal } from '../modals/AddLocationModal/AddLocationModal'; -import { DeleteLocationModal } from '../modals/DeleteLocationModal/DeleteLocationModal'; export const LocationsTab = () => { return ( @@ -11,7 +10,6 @@ export const LocationsTab = () => { - ); }; diff --git a/web/src/pages/NetworkDevicesPage/NetworkDevicesPage.tsx b/web/src/pages/NetworkDevicesPage/NetworkDevicesPage.tsx index a5dae90760..c632a361a1 100644 --- a/web/src/pages/NetworkDevicesPage/NetworkDevicesPage.tsx +++ b/web/src/pages/NetworkDevicesPage/NetworkDevicesPage.tsx @@ -3,7 +3,6 @@ import { Page } from '../../shared/components/Page/Page'; import { TablePageLayout } from '../../shared/layout/TablePageLayout/TablePageLayout'; import { getNetworkDevicesQueryOptions } from '../../shared/query'; import { AddNetworkDeviceModal } from './modals/AddNetworkDeviceModal/AddNetworkDeviceModal'; -import { DeleteNetworkDeviceModal } from './modals/DeleteNetworkDeviceModal/DeleteNetworkDeviceModal'; import { EditNetworkDeviceModal } from './modals/EditNetworkDeviceModal/EditNetworkDeviceModal'; import { NetworkDeviceConfigModal } from './modals/NetworkDeviceConfigModal/NetworkDeviceConfigModal'; import { NetworkDeviceTokenModal } from './modals/NetworkDeviceTokenModal/NetworkDeviceTokenModal'; @@ -23,7 +22,6 @@ export const NetworkDevicesPage = () => { - ); }; diff --git a/web/src/pages/NetworkDevicesPage/NetworkDevicesTable.tsx b/web/src/pages/NetworkDevicesPage/NetworkDevicesTable.tsx index 1b6064a4e7..d25aa640ff 100644 --- a/web/src/pages/NetworkDevicesPage/NetworkDevicesTable.tsx +++ b/web/src/pages/NetworkDevicesPage/NetworkDevicesTable.tsx @@ -241,9 +241,15 @@ export const NetworkDevicesTable = ({ networkDevices }: Props) => { text: m.controls_delete(), icon: 'delete', onClick: () => { - openModal(ModalName.DeleteNetworkDevice, { - id: row.id, - name: row.name, + openModal(ModalName.ConfirmAction, { + title: m.modal_delete_network_device_title(), + contentMd: m.modal_delete_network_device_body(), + actionPromise: () => api.network_device.deleteDevice(row.id), + invalidateKeys: [['device', 'network'], ['network']], + submitProps: { text: m.controls_delete(), variant: 'critical' }, + onSuccess: () => + Snackbar.default(m.network_device_delete_success()), + onError: () => Snackbar.error(m.network_device_delete_failed()), }); }, }, diff --git a/web/src/pages/NetworkDevicesPage/modals/DeleteNetworkDeviceModal/DeleteNetworkDeviceModal.tsx b/web/src/pages/NetworkDevicesPage/modals/DeleteNetworkDeviceModal/DeleteNetworkDeviceModal.tsx deleted file mode 100644 index fb12e30005..0000000000 --- a/web/src/pages/NetworkDevicesPage/modals/DeleteNetworkDeviceModal/DeleteNetworkDeviceModal.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { useMutation } from '@tanstack/react-query'; -import { useEffect, useState } from 'react'; -import { m } from '../../../../paraglide/messages'; -import api from '../../../../shared/api/api'; -import { AppText } from '../../../../shared/defguard-ui/components/AppText/AppText'; -import { Modal } from '../../../../shared/defguard-ui/components/Modal/Modal'; -import { ModalControls } from '../../../../shared/defguard-ui/components/ModalControls/ModalControls'; -import { Snackbar } from '../../../../shared/defguard-ui/providers/snackbar/snackbar'; -import { TextStyle } from '../../../../shared/defguard-ui/types'; -import { - subscribeCloseModal, - subscribeOpenModal, -} from '../../../../shared/hooks/modalControls/modalsSubjects'; -import { ModalName } from '../../../../shared/hooks/modalControls/modalTypes'; -import type { OpenDeleteNetworkDeviceModal } from '../../../../shared/hooks/modalControls/types'; - -const modalNameValue = ModalName.DeleteNetworkDevice; - -type ModalData = OpenDeleteNetworkDeviceModal; - -export const DeleteNetworkDeviceModal = () => { - const [isOpen, setOpen] = useState(false); - const [modalData, setModalData] = useState(null); - - const { mutateAsync: deleteDevice, isPending } = useMutation({ - mutationFn: api.network_device.deleteDevice, - meta: { - invalidate: [['device', 'network'], ['network']], - }, - }); - - useEffect(() => { - const openSub = subscribeOpenModal(modalNameValue, (data) => { - setModalData(data); - setOpen(true); - }); - const closeSub = subscribeCloseModal(modalNameValue, () => setOpen(false)); - return () => { - openSub.unsubscribe(); - closeSub.unsubscribe(); - }; - }, []); - - const handleDelete = async () => { - if (!modalData) return; - try { - await deleteDevice(modalData.id); - Snackbar.default(m.network_device_delete_success()); - setOpen(false); - } catch { - Snackbar.error(m.network_device_delete_failed()); - } - }; - - return ( - setOpen(false)} - afterClose={() => setModalData(null)} - > - - {m.modal_delete_network_device_body()} - - setOpen(false), - disabled: isPending, - }} - /> - - ); -}; diff --git a/web/src/pages/OpenIdPage/OpenIdPage.tsx b/web/src/pages/OpenIdPage/OpenIdPage.tsx index 844c27f052..321dad7f3c 100644 --- a/web/src/pages/OpenIdPage/OpenIdPage.tsx +++ b/web/src/pages/OpenIdPage/OpenIdPage.tsx @@ -5,7 +5,6 @@ import { TableSkeleton } from '../../shared/components/skeleton/TableSkeleton/Ta import { SizedBox } from '../../shared/defguard-ui/components/SizedBox/SizedBox'; import { ThemeSpacing } from '../../shared/defguard-ui/types'; import { CeOpenIdClientModal } from './modals/CEOpenIdClientModal/CEOpenIdClientModal'; -import { DeleteOpenIdClientModal } from './modals/DeleteOpenIdClientModal/DeleteOpenIdClientModal'; import { OpenIdClientTable } from './OpenIdTable'; export const OpenIdPage = () => { @@ -18,7 +17,6 @@ export const OpenIdPage = () => { - ); }; diff --git a/web/src/pages/OpenIdPage/OpenIdTable.tsx b/web/src/pages/OpenIdPage/OpenIdTable.tsx index 4d1a166a00..0703b80704 100644 --- a/web/src/pages/OpenIdPage/OpenIdTable.tsx +++ b/web/src/pages/OpenIdPage/OpenIdTable.tsx @@ -20,6 +20,7 @@ import { TableCell } from '../../shared/defguard-ui/components/table/TableCell/T import { TableEditCell } from '../../shared/defguard-ui/components/table/TableEditCell/TableEditCell'; import { TableTop } from '../../shared/defguard-ui/components/table/TableTop/TableTop'; import { useClipboard } from '../../shared/defguard-ui/hooks/useClipboard'; +import { Snackbar } from '../../shared/defguard-ui/providers/snackbar/snackbar'; import { openModal } from '../../shared/hooks/modalControls/modalsSubjects'; import { ModalName } from '../../shared/hooks/modalControls/modalTypes'; import { getOpenIdClientQueryOptions } from '../../shared/query'; @@ -143,9 +144,15 @@ export const OpenIdClientTable = () => { icon: 'delete', variant: 'danger', onClick: () => { - openModal(ModalName.DeleteOpenIdClient, { - client_id: row.client_id, - name: row.name, + openModal(ModalName.ConfirmAction, { + title: m.modal_delete_openid_client_title(), + contentMd: m.modal_delete_openid_client_body(), + actionPromise: () => + api.openIdClient.deleteOpenIdClient(row.client_id), + invalidateKeys: [['oauth']], + submitProps: { text: m.controls_delete(), variant: 'critical' }, + onSuccess: () => Snackbar.default(m.openid_delete_success()), + onError: () => Snackbar.error(m.openid_delete_failed()), }); }, }, diff --git a/web/src/pages/OpenIdPage/modals/DeleteOpenIdClientModal/DeleteOpenIdClientModal.tsx b/web/src/pages/OpenIdPage/modals/DeleteOpenIdClientModal/DeleteOpenIdClientModal.tsx deleted file mode 100644 index 2e043f33e6..0000000000 --- a/web/src/pages/OpenIdPage/modals/DeleteOpenIdClientModal/DeleteOpenIdClientModal.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { useMutation } from '@tanstack/react-query'; -import { useEffect, useState } from 'react'; -import { m } from '../../../../paraglide/messages'; -import api from '../../../../shared/api/api'; -import { AppText } from '../../../../shared/defguard-ui/components/AppText/AppText'; -import { Modal } from '../../../../shared/defguard-ui/components/Modal/Modal'; -import { ModalControls } from '../../../../shared/defguard-ui/components/ModalControls/ModalControls'; -import { Snackbar } from '../../../../shared/defguard-ui/providers/snackbar/snackbar'; -import { TextStyle } from '../../../../shared/defguard-ui/types'; -import { - subscribeCloseModal, - subscribeOpenModal, -} from '../../../../shared/hooks/modalControls/modalsSubjects'; -import { ModalName } from '../../../../shared/hooks/modalControls/modalTypes'; -import type { OpenDeleteOpenIdClientModal } from '../../../../shared/hooks/modalControls/types'; - -const modalNameValue = ModalName.DeleteOpenIdClient; - -type ModalData = OpenDeleteOpenIdClientModal; - -export const DeleteOpenIdClientModal = () => { - const [isOpen, setOpen] = useState(false); - const [modalData, setModalData] = useState(null); - - const { mutateAsync: deleteClient, isPending } = useMutation({ - mutationFn: api.openIdClient.deleteOpenIdClient, - meta: { - invalidate: ['oauth'], - }, - }); - - useEffect(() => { - const openSub = subscribeOpenModal(modalNameValue, (data) => { - setModalData(data); - setOpen(true); - }); - const closeSub = subscribeCloseModal(modalNameValue, () => setOpen(false)); - return () => { - openSub.unsubscribe(); - closeSub.unsubscribe(); - }; - }, []); - - const handleDelete = async () => { - if (!modalData) return; - try { - await deleteClient(modalData.client_id); - Snackbar.default(m.openid_delete_success()); - setOpen(false); - } catch { - Snackbar.error(m.openid_delete_failed()); - } - }; - - return ( - setOpen(false)} - afterClose={() => setModalData(null)} - > - {m.modal_delete_openid_client_body()} - setOpen(false), - disabled: isPending, - }} - /> - - ); -}; diff --git a/web/src/pages/UsersOverviewPage/UsersTable.tsx b/web/src/pages/UsersOverviewPage/UsersTable.tsx index 13bd69f9dc..b8dbde8348 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: deleteUser } = useMutation({ - mutationFn: api.user.deleteUser, - meta: { - invalidate: [['user-overview'], ['user'], ['enterprise_info']], - }, - }); - const { mutate: changeAccountActiveState } = useMutation({ mutationFn: api.user.activeStateChange, meta: { @@ -368,7 +361,16 @@ export const UsersTable = () => { icon: 'delete', variant: 'danger', onClick: () => { - deleteUser(rowData.username); + openModal(ModalName.ConfirmAction, { + title: m.modal_delete_user_title(), + contentMd: m.modal_delete_user_body({ name: rowData.name }), + actionPromise: () => api.user.deleteUser(rowData.username), + invalidateKeys: [['user-overview'], ['user'], ['enterprise_info']], + submitProps: { + text: m.users_row_menu_delete(), + variant: 'critical', + }, + }); }, }, ], @@ -454,7 +456,6 @@ export const UsersTable = () => { reservedEmails, reservedUsernames, changeAccountActiveState, - deleteUser, groupsOptions, handleEditGroups, groups, diff --git a/web/src/pages/WebhooksPage/WebhooksTable.tsx b/web/src/pages/WebhooksPage/WebhooksTable.tsx index dceaba291c..901ff841c5 100644 --- a/web/src/pages/WebhooksPage/WebhooksTable.tsx +++ b/web/src/pages/WebhooksPage/WebhooksTable.tsx @@ -18,6 +18,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 { openModal } from '../../shared/hooks/modalControls/modalsSubjects'; import { ModalName } from '../../shared/hooks/modalControls/modalTypes'; @@ -36,12 +37,6 @@ export const WebhooksTable = ({ webhooks }: Props) => { invalidate: ['webhook'], }, }); - const { mutate: deleteWebhook } = useMutation({ - mutationFn: api.webhook.deleteWebhook, - meta: { - invalidate: ['webhook'], - }, - }); const addButtonProps = useMemo( (): ButtonProps => ({ text: m.webhooks_add(), @@ -131,7 +126,15 @@ export const WebhooksTable = ({ webhooks }: Props) => { testId: 'delete', variant: 'danger', onClick: () => { - deleteWebhook(row.id); + openModal(ModalName.ConfirmAction, { + title: m.webhooks_delete_confirm_title(), + contentMd: m.webhooks_delete_confirm_body(), + actionPromise: () => api.webhook.deleteWebhook(row.id), + invalidateKeys: [['webhook']], + submitProps: { text: m.controls_delete(), variant: 'critical' }, + onSuccess: () => Snackbar.default(m.webhooks_delete_success()), + onError: () => Snackbar.error(m.webhooks_delete_failed()), + }); }, }, ], @@ -141,7 +144,7 @@ export const WebhooksTable = ({ webhooks }: Props) => { }, }), ], - [deleteWebhook, toggleWebhook], + [toggleWebhook], ); const table = useReactTable({ columns, diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/ActivityLogStreamTable.tsx b/web/src/pages/settings/SettingsActivityLogStreamingPage/ActivityLogStreamTable.tsx index b88f55dee4..aaaf5de295 100644 --- a/web/src/pages/settings/SettingsActivityLogStreamingPage/ActivityLogStreamTable.tsx +++ b/web/src/pages/settings/SettingsActivityLogStreamingPage/ActivityLogStreamTable.tsx @@ -6,12 +6,14 @@ import { } from '@tanstack/react-table'; import { useMemo } from 'react'; import { m } from '../../../paraglide/messages'; +import api from '../../../shared/api/api'; import type { ActivityLogStream } from '../../../shared/api/types'; import type { MenuItemsGroup } from '../../../shared/defguard-ui/components/Menu/types'; import { tableEditColumnSize } from '../../../shared/defguard-ui/components/table/consts'; 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'; @@ -78,7 +80,18 @@ export const ActivityLogStreamTable = ({ data: rowData }: Props) => { icon: 'delete', variant: 'danger', onClick: () => { - openModal(ModalName.DeleteLogStreaming, row); + openModal(ModalName.ConfirmAction, { + title: + m.settings_activity_log_streaming_delete_log_streaming_title(), + contentMd: m.modal_delete_logstream_destination(), + actionPromise: () => api.activityLogStream.deleteStream(row.id), + invalidateKeys: [['activity_log_stream']], + submitProps: { text: m.controls_delete(), variant: 'critical' }, + onError: () => + Snackbar.error( + m.settings_activity_log_streaming_delete_log_streaming_failed(), + ), + }); }, }, ], diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/SettingsActivityStreamingTab.tsx b/web/src/pages/settings/SettingsActivityLogStreamingPage/SettingsActivityStreamingTab.tsx index e9d058c10f..18f7eb0a3b 100644 --- a/web/src/pages/settings/SettingsActivityLogStreamingPage/SettingsActivityStreamingTab.tsx +++ b/web/src/pages/settings/SettingsActivityLogStreamingPage/SettingsActivityStreamingTab.tsx @@ -20,7 +20,6 @@ import { import { canUseBusinessFeature, licenseActionCheck } from '../../../shared/utils/license'; import { ActivityLogStreamTable } from './ActivityLogStreamTable'; import { AddLogStreamingModal } from './modals/AddDestinationModal/AddLogStreamingModal'; -import { DeleteLogStreamingModal } from './modals/DeleteDestinationModal/DeleteLogStreamingModal'; import { EditLogStreamingModal } from './modals/EditDestinationModal/EditLogStreamingModal'; export const SettingsActivityLogStreamingPage = () => { @@ -79,7 +78,6 @@ export const SettingsActivityLogStreamingPage = () => { )} - ); }; diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/DeleteDestinationModal/DeleteLogStreamingModal.tsx b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/DeleteDestinationModal/DeleteLogStreamingModal.tsx deleted file mode 100644 index b2f5d3a535..0000000000 --- a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/DeleteDestinationModal/DeleteLogStreamingModal.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { useMutation } from '@tanstack/react-query'; -import { useEffect, useState } from 'react'; -import { m } from '../../../../../paraglide/messages'; -import api from '../../../../../shared/api/api'; -import type { ActivityLogStream } from '../../../../../shared/api/types'; -import { AppText } from '../../../../../shared/defguard-ui/components/AppText/AppText'; -import { Modal } from '../../../../../shared/defguard-ui/components/Modal/Modal'; -import { ModalControls } from '../../../../../shared/defguard-ui/components/ModalControls/ModalControls'; -import { TextStyle } from '../../../../../shared/defguard-ui/types'; -import { - subscribeCloseModal, - subscribeOpenModal, -} from '../../../../../shared/hooks/modalControls/modalsSubjects'; -import { ModalName } from '../../../../../shared/hooks/modalControls/modalTypes'; - -const modalNameValue = ModalName.DeleteLogStreaming; - -type ModalData = ActivityLogStream; - -export const DeleteLogStreamingModal = () => { - const [isOpen, setOpen] = useState(false); - const [modalData, setModalData] = useState(null); - - const { mutateAsync: deleteStream, isPending } = useMutation({ - mutationFn: api.activityLogStream.deleteStream, - meta: { - invalidate: ['activity_log_stream'], - }, - }); - - useEffect(() => { - const openSub = subscribeOpenModal(modalNameValue, (data) => { - setModalData(data); - setOpen(true); - }); - const closeSub = subscribeCloseModal(modalNameValue, () => setOpen(false)); - return () => { - openSub.unsubscribe(); - closeSub.unsubscribe(); - }; - }, []); - - const handleDelete = async () => { - if (!modalData) return; - await deleteStream(modalData.id); - setOpen(false); - }; - - return ( - setOpen(false)} - afterClose={() => setModalData(null)} - > - - {m.modal_delete_logstream_destination()} - - setOpen(false), - disabled: isPending, - }} - /> - - ); -}; diff --git a/web/src/pages/settings/SettingsEditOpenIdProviderPage/SettingsEditOpenIdProviderPage.tsx b/web/src/pages/settings/SettingsEditOpenIdProviderPage/SettingsEditOpenIdProviderPage.tsx index c953189cac..27d0e17826 100644 --- a/web/src/pages/settings/SettingsEditOpenIdProviderPage/SettingsEditOpenIdProviderPage.tsx +++ b/web/src/pages/settings/SettingsEditOpenIdProviderPage/SettingsEditOpenIdProviderPage.tsx @@ -1,10 +1,14 @@ import { useMutation, useSuspenseQuery } from '@tanstack/react-query'; import { Link, useRouter } from '@tanstack/react-router'; import { useCallback, useMemo } from 'react'; +import { m } from '../../../paraglide/messages'; import api from '../../../shared/api/api'; import { type AddOpenIdProvider, OpenIdProviderKind } from '../../../shared/api/types'; import { EditPage } from '../../../shared/components/EditPage/EditPage'; +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 { getExternalProviderQueryOptions } from '../../../shared/query'; import { joinCsv } from '../../../shared/utils/csv'; import { EditCustomProviderForm } from './form/EditCustomProviderForm'; @@ -42,15 +46,20 @@ export const SettingsEditOpenIdProviderPage = () => { }, }); - const { mutateAsync: deleteProvider, isPending: deletePending } = useMutation({ - mutationFn: api.openIdProvider.deleteOpenIdProvider, - onSuccess: () => { - router.history.back(); - }, - meta: { - invalidate: [['settings'], ['info'], ['openid']], - }, - }); + const handleDelete = (name: string) => { + openModal(ModalName.ConfirmAction, { + title: m.settings_openid_provider_delete_confirm_title(), + contentMd: m.settings_openid_provider_delete_confirm_body(), + actionPromise: () => api.openIdProvider.deleteOpenIdProvider(name), + invalidateKeys: [['settings'], ['info'], ['openid']], + submitProps: { text: m.controls_delete(), variant: 'critical' }, + onSuccess: () => { + Snackbar.default(m.settings_openid_provider_delete_success()); + router.history.back(); + }, + onError: () => Snackbar.error(m.settings_openid_provider_delete_failed()), + }); + }; const handleSubmit = useCallback( async (values: Partial) => { @@ -80,50 +89,35 @@ export const SettingsEditOpenIdProviderPage = () => { { - deleteProvider(formData.name); - }} - loading={deletePending} + onDelete={() => handleDelete(formData.name)} /> )} {formData.name === OpenIdProviderKind.Microsoft && ( { - deleteProvider(formData.name); - }} - loading={deletePending} + onDelete={() => handleDelete(formData.name)} /> )} {formData.name === OpenIdProviderKind.Okta && ( { - deleteProvider(formData.name); - }} - loading={deletePending} + onDelete={() => handleDelete(formData.name)} /> )} {formData.name === OpenIdProviderKind.JumpCloud && ( { - deleteProvider(formData.name); - }} - loading={deletePending} + onDelete={() => handleDelete(formData.name)} /> )} {formData.name === OpenIdProviderKind.Custom && ( { - deleteProvider(formData.name); - }} - loading={deletePending} + onDelete={() => handleDelete(formData.name)} /> )} diff --git a/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditCustomProviderForm.tsx b/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditCustomProviderForm.tsx index cc80cd1936..8655b64001 100644 --- a/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditCustomProviderForm.tsx +++ b/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditCustomProviderForm.tsx @@ -17,7 +17,6 @@ type FormFields = z.infer; export const EditCustomProviderForm = ({ provider, - loading, onDelete, onSubmit, }: EditProviderFormProps) => { @@ -98,18 +97,17 @@ export const EditCustomProviderForm = ({ disabled: submitting, text: 'Delete provider', onClick: onDelete, - loading: loading, }} cancelProps={{ text: m.controls_cancel(), - disabled: submitting || loading, + disabled: submitting, onClick: () => { window.history.back(); }, }} submitProps={{ text: m.controls_save_changes(), - loading: submitting || loading, + loading: submitting, type: 'submit', onClick: () => { form.handleSubmit(); diff --git a/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditGoogleProviderForm.tsx b/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditGoogleProviderForm.tsx index 9df8484215..06972a8c35 100644 --- a/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditGoogleProviderForm.tsx +++ b/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditGoogleProviderForm.tsx @@ -42,7 +42,6 @@ type FormFields = z.infer; export const EditGoogleProviderForm = ({ provider, - loading, onDelete, onSubmit, }: EditProviderFormProps) => { @@ -201,18 +200,17 @@ export const EditGoogleProviderForm = ({ disabled: submitting, text: 'Delete provider', onClick: onDelete, - loading: loading, }} cancelProps={{ text: m.controls_cancel(), - disabled: submitting || loading, + disabled: submitting, onClick: () => { window.history.back(); }, }} submitProps={{ text: m.controls_save_changes(), - loading: submitting || loading, + loading: submitting, type: 'submit', onClick: () => { form.handleSubmit(); diff --git a/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditJumpCloudProviderForm.tsx b/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditJumpCloudProviderForm.tsx index 1d5c20a430..652e33811b 100644 --- a/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditJumpCloudProviderForm.tsx +++ b/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditJumpCloudProviderForm.tsx @@ -37,7 +37,6 @@ type FormFields = z.infer; export const EditJumpCloudProviderForm = ({ provider, - loading, onDelete, onSubmit, }: EditProviderFormProps) => { @@ -173,18 +172,17 @@ export const EditJumpCloudProviderForm = ({ disabled: submitting, text: 'Delete provider', onClick: onDelete, - loading: loading, }} cancelProps={{ text: m.controls_cancel(), - disabled: submitting || loading, + disabled: submitting, onClick: () => { window.history.back(); }, }} submitProps={{ text: m.controls_save_changes(), - loading: submitting || loading, + loading: submitting, type: 'submit', onClick: () => { form.handleSubmit(); diff --git a/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditMicrosoftProviderForm.tsx b/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditMicrosoftProviderForm.tsx index 402e28919d..1acb5ac858 100644 --- a/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditMicrosoftProviderForm.tsx +++ b/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditMicrosoftProviderForm.tsx @@ -44,7 +44,6 @@ type FormFields = z.infer; export const EditMicrosoftProviderForm = ({ provider, - loading, onDelete, onSubmit, }: EditProviderFormProps) => { @@ -200,18 +199,17 @@ export const EditMicrosoftProviderForm = ({ disabled: submitting, text: 'Delete provider', onClick: onDelete, - loading: loading, }} cancelProps={{ text: m.controls_cancel(), - disabled: submitting || loading, + disabled: submitting, onClick: () => { window.history.back(); }, }} submitProps={{ text: m.controls_save_changes(), - loading: submitting || loading, + loading: submitting, type: 'submit', onClick: () => { form.handleSubmit(); diff --git a/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditOktaProviderForm.tsx b/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditOktaProviderForm.tsx index 8cae099688..d30c58801e 100644 --- a/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditOktaProviderForm.tsx +++ b/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditOktaProviderForm.tsx @@ -36,7 +36,6 @@ type FormFields = z.infer; export const EditOktaProviderForm = ({ provider, - loading, onDelete, onSubmit, }: EditProviderFormProps) => { @@ -188,18 +187,17 @@ export const EditOktaProviderForm = ({ disabled: submitting, text: 'Delete provider', onClick: onDelete, - loading: loading, }} cancelProps={{ text: m.controls_cancel(), - disabled: submitting || loading, + disabled: submitting, onClick: () => { window.history.back(); }, }} submitProps={{ text: m.controls_save_changes(), - loading: submitting || loading, + loading: submitting, type: 'submit', onClick: () => { form.handleSubmit(); diff --git a/web/src/pages/settings/SettingsEditOpenIdProviderPage/types.ts b/web/src/pages/settings/SettingsEditOpenIdProviderPage/types.ts index d048bb816c..759245d3b5 100644 --- a/web/src/pages/settings/SettingsEditOpenIdProviderPage/types.ts +++ b/web/src/pages/settings/SettingsEditOpenIdProviderPage/types.ts @@ -8,5 +8,4 @@ export interface EditProviderFormProps { provider: OpenIdProvider & OpenIdProviderSettings; onSubmit: (value: Partial) => Promise; onDelete: () => void; - loading: boolean; } diff --git a/web/src/pages/settings/SettingsSmtpPage/SettingsSmtpPage.tsx b/web/src/pages/settings/SettingsSmtpPage/SettingsSmtpPage.tsx index c59cc385ca..ac3c8f7f8e 100644 --- a/web/src/pages/settings/SettingsSmtpPage/SettingsSmtpPage.tsx +++ b/web/src/pages/settings/SettingsSmtpPage/SettingsSmtpPage.tsx @@ -20,6 +20,7 @@ import { Button } from '../../../shared/defguard-ui/components/Button/Button'; import { EvenSplit } from '../../../shared/defguard-ui/components/EvenSplit/EvenSplit'; import type { SelectOption } from '../../../shared/defguard-ui/components/Select/types'; import { SizedBox } from '../../../shared/defguard-ui/components/SizedBox/SizedBox'; +import { Snackbar } from '../../../shared/defguard-ui/providers/snackbar/snackbar'; import { ThemeSpacing } from '../../../shared/defguard-ui/types'; import { isPresent } from '../../../shared/defguard-ui/utils/isPresent'; import { useAppForm } from '../../../shared/form'; @@ -155,16 +156,6 @@ const Content = ({ settings }: { settings: Settings }) => { }, }); - const { mutateAsync: deleteSmtp, isPending: deletePending } = useMutation({ - mutationFn: () => api.settings.patchSettings(emptyValues), - meta: { - invalidate: [['settings'], ['info']], - }, - onSuccess: () => { - form.reset(emptyValues); - }, - }); - const form = useAppForm({ defaultValues, validationLogic: formChangeLogic, @@ -230,9 +221,19 @@ const Content = ({ settings }: { settings: Settings }) => {