diff --git a/web/src/lib/components/admin-page/delete-confirm-dialogue.svelte b/web/src/lib/components/admin-page/delete-confirm-dialogue.svelte
index 6eb603263e..e2d3c86bf3 100644
--- a/web/src/lib/components/admin-page/delete-confirm-dialogue.svelte
+++ b/web/src/lib/components/admin-page/delete-confirm-dialogue.svelte
@@ -47,8 +47,7 @@
 <ConfirmDialog
   title={$t('delete_user')}
   confirmText={forceDelete ? $t('permanently_delete') : $t('delete')}
-  onConfirm={handleDeleteUser}
-  {onCancel}
+  onClose={(confirmed) => (confirmed ? handleDeleteUser() : onCancel())}
   disabled={deleteButtonDisabled}
 >
   {#snippet promptSnippet()}
diff --git a/web/src/lib/components/admin-page/restore-dialogue.svelte b/web/src/lib/components/admin-page/restore-dialogue.svelte
index 1386ae9fc4..7fd51aaf06 100644
--- a/web/src/lib/components/admin-page/restore-dialogue.svelte
+++ b/web/src/lib/components/admin-page/restore-dialogue.svelte
@@ -33,8 +33,7 @@
   title={$t('restore_user')}
   confirmText={$t('continue')}
   confirmColor="success"
-  onConfirm={handleRestoreUser}
-  {onCancel}
+  onClose={(confirmed) => (confirmed ? handleRestoreUser() : onCancel())}
 >
   {#snippet promptSnippet()}
     <p>
diff --git a/web/src/lib/components/admin-page/settings/auth/auth-settings.svelte b/web/src/lib/components/admin-page/settings/auth/auth-settings.svelte
index b2454b06c3..2a270f7438 100644
--- a/web/src/lib/components/admin-page/settings/auth/auth-settings.svelte
+++ b/web/src/lib/components/admin-page/settings/auth/auth-settings.svelte
@@ -49,8 +49,7 @@
 {#if isConfirmOpen}
   <ConfirmDialog
     title={$t('admin.disable_login')}
-    onCancel={() => (isConfirmOpen = false)}
-    onConfirm={() => handleSave(true)}
+    onClose={(confirmed) => (confirmed ? handleSave(true) : (isConfirmOpen = false))}
   >
     {#snippet promptSnippet()}
       <div class="flex flex-col gap-4">
diff --git a/web/src/lib/components/album-page/album-options.svelte b/web/src/lib/components/album-page/album-options.svelte
index 884de8c2a2..4a8c018fbd 100644
--- a/web/src/lib/components/album-page/album-options.svelte
+++ b/web/src/lib/components/album-page/album-options.svelte
@@ -1,27 +1,27 @@
 <script lang="ts">
   import Icon from '$lib/components/elements/icon.svelte';
+  import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
+  import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
+  import ConfirmDialog from '$lib/components/shared-components/dialog/confirm-dialog.svelte';
+  import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
+  import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
+  import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
+  import { handleError } from '$lib/utils/handle-error';
   import {
-    updateAlbumInfo,
+    AlbumUserRole,
+    AssetOrder,
     removeUserFromAlbum,
+    updateAlbumInfo,
+    updateAlbumUser,
     type AlbumResponseDto,
     type UserResponseDto,
-    AssetOrder,
-    AlbumUserRole,
-    updateAlbumUser,
   } from '@immich/sdk';
-  import { mdiArrowDownThin, mdiArrowUpThin, mdiPlus, mdiDotsVertical } from '@mdi/js';
-  import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
-  import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
-  import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
-  import SettingDropdown from '../shared-components/settings/setting-dropdown.svelte';
-  import type { RenderedOption } from '../elements/dropdown.svelte';
-  import { handleError } from '$lib/utils/handle-error';
+  import { mdiArrowDownThin, mdiArrowUpThin, mdiDotsVertical, mdiPlus } from '@mdi/js';
   import { findKey } from 'lodash-es';
   import { t } from 'svelte-i18n';
-  import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
-  import ConfirmDialog from '$lib/components/shared-components/dialog/confirm-dialog.svelte';
+  import type { RenderedOption } from '../elements/dropdown.svelte';
   import { notificationController, NotificationType } from '../shared-components/notification/notification';
-  import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
+  import SettingDropdown from '../shared-components/settings/setting-dropdown.svelte';
 
   interface Props {
     album: AlbumResponseDto;
@@ -195,7 +195,6 @@
     title={$t('album_remove_user')}
     prompt={$t('album_remove_user_confirmation', { values: { user: selectedRemoveUser.name } })}
     confirmText={$t('remove_user')}
-    onConfirm={handleRemoveUser}
-    onCancel={() => (selectedRemoveUser = null)}
+    onClose={(confirmed) => (confirmed ? handleRemoveUser() : (selectedRemoveUser = null))}
   />
 {/if}
diff --git a/web/src/lib/components/album-page/share-info-modal.svelte b/web/src/lib/components/album-page/share-info-modal.svelte
index 35d8a84412..97bbb81ea5 100644
--- a/web/src/lib/components/album-page/share-info-modal.svelte
+++ b/web/src/lib/components/album-page/share-info-modal.svelte
@@ -1,22 +1,22 @@
 <script lang="ts">
+  import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
+  import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
   import {
+    AlbumUserRole,
     getMyUser,
     removeUserFromAlbum,
+    updateAlbumUser,
     type AlbumResponseDto,
     type UserResponseDto,
-    updateAlbumUser,
-    AlbumUserRole,
   } from '@immich/sdk';
   import { mdiDotsVertical } from '@mdi/js';
   import { onMount } from 'svelte';
+  import { t } from 'svelte-i18n';
   import { handleError } from '../../utils/handle-error';
-  import ConfirmDialog from '../shared-components/dialog/confirm-dialog.svelte';
   import MenuOption from '../shared-components/context-menu/menu-option.svelte';
+  import ConfirmDialog from '../shared-components/dialog/confirm-dialog.svelte';
   import { NotificationType, notificationController } from '../shared-components/notification/notification';
   import UserAvatar from '../shared-components/user-avatar.svelte';
-  import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
-  import { t } from 'svelte-i18n';
-  import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
 
   interface Props {
     album: AlbumResponseDto;
@@ -144,8 +144,7 @@
     title={$t('album_leave')}
     prompt={$t('album_leave_confirmation', { values: { album: album.albumName } })}
     confirmText={$t('leave')}
-    onConfirm={handleRemoveUser}
-    onCancel={() => (selectedRemoveUser = null)}
+    onClose={(confirmed) => (confirmed ? handleRemoveUser() : (selectedRemoveUser = null))}
   />
 {/if}
 
@@ -154,7 +153,6 @@
     title={$t('album_remove_user')}
     prompt={$t('album_remove_user_confirmation', { values: { user: selectedRemoveUser.name } })}
     confirmText={$t('remove_user')}
-    onConfirm={handleRemoveUser}
-    onCancel={() => (selectedRemoveUser = null)}
+    onClose={(confirmed) => (confirmed ? handleRemoveUser() : (selectedRemoveUser = null))}
   />
 {/if}
diff --git a/web/src/lib/components/asset-viewer/editor/editor-panel.svelte b/web/src/lib/components/asset-viewer/editor/editor-panel.svelte
index 39ba7ac200..7efe4a1a73 100644
--- a/web/src/lib/components/asset-viewer/editor/editor-panel.svelte
+++ b/web/src/lib/components/asset-viewer/editor/editor-panel.svelte
@@ -1,13 +1,13 @@
 <script lang="ts">
+  import { shortcut } from '$lib/actions/shortcut';
+  import ConfirmDialog from '$lib/components/shared-components/dialog/confirm-dialog.svelte';
+  import { editTypes, showCancelConfirmDialog } from '$lib/stores/asset-editor.store';
   import { websocketEvents } from '$lib/stores/websocket';
   import { type AssetResponseDto } from '@immich/sdk';
   import { mdiClose } from '@mdi/js';
   import { onMount } from 'svelte';
-  import CircleIconButton from '../../elements/buttons/circle-icon-button.svelte';
   import { t } from 'svelte-i18n';
-  import { editTypes, showCancelConfirmDialog } from '$lib/stores/asset-editor.store';
-  import ConfirmDialog from '$lib/components/shared-components/dialog/confirm-dialog.svelte';
-  import { shortcut } from '$lib/actions/shortcut';
+  import CircleIconButton from '../../elements/buttons/circle-icon-button.svelte';
 
   onMount(() => {
     return websocketEvents.on('on_asset_update', (assetUpdate) => {
@@ -31,10 +31,13 @@
   setTimeout(() => {
     onUpdateSelectedType(selectedType);
   }, 1);
+
   function selectType(name: string) {
     selectedType = name;
     onUpdateSelectedType(selectedType);
   }
+
+  const onConfirm = () => (typeof $showCancelConfirmDialog === 'boolean' ? null : $showCancelConfirmDialog());
 </script>
 
 <svelte:window use:shortcut={{ shortcut: { key: 'Escape' }, onShortcut: onClose }} />
@@ -71,9 +74,6 @@
     cancelColor="secondary"
     confirmColor="danger"
     confirmText={$t('close')}
-    onCancel={() => {
-      $showCancelConfirmDialog = false;
-    }}
-    onConfirm={() => (typeof $showCancelConfirmDialog === 'boolean' ? null : $showCancelConfirmDialog())}
+    onClose={(confirmed) => (confirmed ? onConfirm() : ($showCancelConfirmDialog = false))}
   />
 {/if}
diff --git a/web/src/lib/components/photos-page/delete-asset-dialog.svelte b/web/src/lib/components/photos-page/delete-asset-dialog.svelte
index 3053600a47..fbdec86244 100644
--- a/web/src/lib/components/photos-page/delete-asset-dialog.svelte
+++ b/web/src/lib/components/photos-page/delete-asset-dialog.svelte
@@ -1,9 +1,9 @@
 <script lang="ts">
-  import ConfirmDialog from '../shared-components/dialog/confirm-dialog.svelte';
-  import { showDeleteModal } from '$lib/stores/preferences.store';
   import Checkbox from '$lib/components/elements/checkbox.svelte';
-  import { t } from 'svelte-i18n';
   import FormatMessage from '$lib/components/i18n/format-message.svelte';
+  import { showDeleteModal } from '$lib/stores/preferences.store';
+  import { t } from 'svelte-i18n';
+  import ConfirmDialog from '../shared-components/dialog/confirm-dialog.svelte';
 
   interface Props {
     size: number;
@@ -26,8 +26,7 @@
 <ConfirmDialog
   title={$t('permanently_delete_assets_count', { values: { count: size } })}
   confirmText={$t('delete')}
-  onConfirm={handleConfirm}
-  {onCancel}
+  onClose={(confirmed) => (confirmed ? handleConfirm() : onCancel())}
 >
   {#snippet promptSnippet()}
     <p>
diff --git a/web/src/lib/components/shared-components/change-date.svelte b/web/src/lib/components/shared-components/change-date.svelte
index ef682d9048..d6b575f772 100644
--- a/web/src/lib/components/shared-components/change-date.svelte
+++ b/web/src/lib/components/shared-components/change-date.svelte
@@ -1,9 +1,9 @@
 <script lang="ts">
   import { DateTime } from 'luxon';
-  import ConfirmDialog from './dialog/confirm-dialog.svelte';
-  import Combobox, { type ComboBoxOption } from './combobox.svelte';
-  import DateInput from '../elements/date-input.svelte';
   import { t } from 'svelte-i18n';
+  import DateInput from '../elements/date-input.svelte';
+  import Combobox, { type ComboBoxOption } from './combobox.svelte';
+  import ConfirmDialog from './dialog/confirm-dialog.svelte';
 
   interface Props {
     initialDate?: DateTime;
@@ -138,8 +138,7 @@
   title={$t('edit_date_and_time')}
   prompt="Please select a new date:"
   disabled={!date.isValid}
-  onConfirm={handleConfirm}
-  {onCancel}
+  onClose={(confirmed) => (confirmed ? handleConfirm() : onCancel())}
 >
   <!-- @migration-task: migrate this slot by hand, `prompt` would shadow a prop on the parent component -->
   <!-- @migration-task: migrate this slot by hand, `prompt` would shadow a prop on the parent component -->
diff --git a/web/src/lib/components/shared-components/change-location.svelte b/web/src/lib/components/shared-components/change-location.svelte
index f981e85029..1987596026 100644
--- a/web/src/lib/components/shared-components/change-location.svelte
+++ b/web/src/lib/components/shared-components/change-location.svelte
@@ -1,20 +1,20 @@
 <script lang="ts">
-  import ConfirmDialog from './dialog/confirm-dialog.svelte';
   import { timeDebounceOnSearch } from '$lib/constants';
-  import { handleError } from '$lib/utils/handle-error';
   import { lastChosenLocation } from '$lib/stores/asset-editor.store';
+  import { handleError } from '$lib/utils/handle-error';
+  import ConfirmDialog from './dialog/confirm-dialog.svelte';
 
   import { clickOutside } from '$lib/actions/click-outside';
-  import LoadingSpinner from './loading-spinner.svelte';
-  import { delay } from '$lib/utils/asset-utils';
-  import { timeToLoadTheMap } from '$lib/constants';
-  import { searchPlaces, type AssetResponseDto, type PlacesResponseDto } from '@immich/sdk';
-  import SearchBar from '../elements/search-bar.svelte';
   import { listNavigation } from '$lib/actions/list-navigation';
-  import { t } from 'svelte-i18n';
   import CoordinatesInput from '$lib/components/shared-components/coordinates-input.svelte';
   import type Map from '$lib/components/shared-components/map/map.svelte';
+  import { timeToLoadTheMap } from '$lib/constants';
+  import { delay } from '$lib/utils/asset-utils';
+  import { searchPlaces, type AssetResponseDto, type PlacesResponseDto } from '@immich/sdk';
+  import { t } from 'svelte-i18n';
   import { get } from 'svelte/store';
+  import SearchBar from '../elements/search-bar.svelte';
+  import LoadingSpinner from './loading-spinner.svelte';
   interface Point {
     lng: number;
     lat: number;
@@ -112,7 +112,12 @@
   };
 </script>
 
-<ConfirmDialog confirmColor="primary" title={$t('change_location')} width="wide" onConfirm={handleConfirm} {onCancel}>
+<ConfirmDialog
+  confirmColor="primary"
+  title={$t('change_location')}
+  width="wide"
+  onClose={(confirmed) => (confirmed ? handleConfirm() : onCancel())}
+>
   {#snippet promptSnippet()}
     <div class="flex flex-col w-full h-full gap-2">
       <div class="relative w-64 sm:w-96">
diff --git a/web/src/lib/components/shared-components/dialog/confirm-dialog.svelte b/web/src/lib/components/shared-components/dialog/confirm-dialog.svelte
index dad16d52ca..32f4b6a8f4 100644
--- a/web/src/lib/components/shared-components/dialog/confirm-dialog.svelte
+++ b/web/src/lib/components/shared-components/dialog/confirm-dialog.svelte
@@ -1,8 +1,8 @@
 <script lang="ts">
-  import FullScreenModal from '../full-screen-modal.svelte';
-  import { t } from 'svelte-i18n';
-  import type { Snippet } from 'svelte';
   import { Button, type Color } from '@immich/ui';
+  import type { Snippet } from 'svelte';
+  import { t } from 'svelte-i18n';
+  import FullScreenModal from '../full-screen-modal.svelte';
 
   interface Props {
     title?: string;
@@ -14,8 +14,7 @@
     hideCancelButton?: boolean;
     disabled?: boolean;
     width?: 'wide' | 'narrow';
-    onCancel: () => void;
-    onConfirm: () => void;
+    onClose: (confirmed: boolean) => void;
     promptSnippet?: Snippet;
   }
 
@@ -29,17 +28,16 @@
     hideCancelButton = false,
     disabled = false,
     width = 'narrow',
-    onCancel,
-    onConfirm,
+    onClose,
     promptSnippet,
   }: Props = $props();
 
   const handleConfirm = () => {
-    onConfirm();
+    onClose(true);
   };
 </script>
 
-<FullScreenModal {title} onClose={onCancel} {width}>
+<FullScreenModal {title} onClose={() => onClose(false)} {width}>
   <div class="text-md py-5 text-center">
     {#if promptSnippet}{@render promptSnippet()}{:else}
       <p>{prompt}</p>
@@ -48,7 +46,7 @@
 
   {#snippet stickyBottom()}
     {#if !hideCancelButton}
-      <Button shape="round" color={cancelColor} fullWidth onclick={onCancel}>
+      <Button shape="round" color={cancelColor} fullWidth onclick={() => onClose(false)}>
         {cancelText}
       </Button>
     {/if}
diff --git a/web/src/lib/components/shared-components/dialog/dialog.ts b/web/src/lib/components/shared-components/dialog/dialog.ts
index 8efff58da0..69a64aad21 100644
--- a/web/src/lib/components/shared-components/dialog/dialog.ts
+++ b/web/src/lib/components/shared-components/dialog/dialog.ts
@@ -1,8 +1,7 @@
 import { writable } from 'svelte/store';
 
 type DialogActions = {
-  onConfirm: () => void;
-  onCancel: () => void;
+  onClose: (confirmed: boolean) => void;
 };
 
 type DialogOptions = {
@@ -24,13 +23,9 @@ function createDialogWrapper() {
     return new Promise<boolean>((resolve) => {
       const newDialog: Dialog = {
         ...options,
-        onConfirm: () => {
+        onClose: (confirmed) => {
           dialog.set(undefined);
-          resolve(true);
-        },
-        onCancel: () => {
-          dialog.set(undefined);
-          resolve(false);
+          resolve(confirmed);
         },
       };
 
diff --git a/web/src/routes/admin/jobs-status/+page.svelte b/web/src/routes/admin/jobs-status/+page.svelte
index 07757614e5..af7f3d11af 100644
--- a/web/src/routes/admin/jobs-status/+page.svelte
+++ b/web/src/routes/admin/jobs-status/+page.svelte
@@ -102,8 +102,7 @@
     confirmColor="primary"
     title={$t('admin.create_job')}
     disabled={!selectedJob}
-    onConfirm={handleCreate}
-    onCancel={handleCancel}
+    onClose={(confirmed) => (confirmed ? handleCreate() : handleCancel())}
   >
     {#snippet promptSnippet()}
       <form {onsubmit} autocomplete="off" id="create-tag-form" class="w-full">
diff --git a/web/src/routes/admin/user-management/+page.svelte b/web/src/routes/admin/user-management/+page.svelte
index a25799588a..8b96c6c922 100644
--- a/web/src/routes/admin/user-management/+page.svelte
+++ b/web/src/routes/admin/user-management/+page.svelte
@@ -152,8 +152,7 @@
         <ConfirmDialog
           title={$t('password_reset_success')}
           confirmText={$t('done')}
-          onConfirm={() => (shouldShowPasswordResetSuccess = false)}
-          onCancel={() => (shouldShowPasswordResetSuccess = false)}
+          onClose={() => (shouldShowPasswordResetSuccess = false)}
           hideCancelButton={true}
           confirmColor="success"
         >