diff --git a/web/src/lib/components/admin-page/delete-confirm-dialoge.svelte b/web/src/lib/components/admin-page/delete-confirm-dialoge.svelte index 5b1298c26..8f687f3b6 100644 --- a/web/src/lib/components/admin-page/delete-confirm-dialoge.svelte +++ b/web/src/lib/components/admin-page/delete-confirm-dialoge.svelte @@ -1,7 +1,7 @@ <script lang="ts"> import { api, UserResponseDto } from '@api'; import { createEventDispatcher } from 'svelte'; - import Button from '../elements/buttons/button.svelte'; + import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte'; import { handleError } from '../../utils/handle-error'; export let user: UserResponseDto; @@ -23,25 +23,14 @@ }; </script> -<div - class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray p-4 shadow-sm w-[500px] max-w-[95vw] rounded-3xl py-8 dark:text-immich-dark-fg" -> - <div - class="flex flex-col place-items-center place-content-center gap-4 px-4 text-immich-primary dark:text-immich-dark-primary" - > - <h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium"> - Confirm User Deletion - </h1> - </div> - <div> - <p class="ml-4 text-md py-5 text-center"> - {user.firstName} - {user.lastName} account and assets along will be marked to delete completely after 7 days. are - you sure you want to proceed ? - </p> - - <div class="flex w-full px-4 gap-4 mt-8"> - <Button fullwidth color="red" on:click={deleteUser}>Confirm</Button> +<ConfirmDialogue title="Delete User" confirmText="Delete" on:confirm={deleteUser} on:cancel> + <svelte:fragment slot="prompt"> + <div class="flex flex-col gap-4"> + <p> + <b>{user.firstName} {user.lastName}</b>'s account and assets will be permanently deleted + after 7 days. + </p> + <p>Are you sure you want to continue?</p> </div> - </div> -</div> + </svelte:fragment> +</ConfirmDialogue> diff --git a/web/src/lib/components/admin-page/restore-dialoge.svelte b/web/src/lib/components/admin-page/restore-dialoge.svelte index 6573e04f6..d78006413 100644 --- a/web/src/lib/components/admin-page/restore-dialoge.svelte +++ b/web/src/lib/components/admin-page/restore-dialoge.svelte @@ -1,7 +1,7 @@ <script lang="ts"> import { api, UserResponseDto } from '@api'; import { createEventDispatcher } from 'svelte'; - import Button from '../elements/buttons/button.svelte'; + import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte'; export let user: UserResponseDto; @@ -14,24 +14,14 @@ }; </script> -<div - class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray p-4 shadow-sm w-[500px] max-w-[95vw] rounded-3xl py-8 dark:text-immich-dark-fg" +<ConfirmDialogue + title="Restore User" + confirmText="Continue" + confirmColor="green" + on:confirm={restoreUser} + on:cancel > - <div - class="flex flex-col place-items-center place-content-center gap-4 px-4 text-immich-primary dark:text-immich-dark-primary" - > - <h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium"> - Restore User - </h1> - </div> - <div> - <p class="ml-4 text-md py-5 text-center"> - {user.firstName} - {user.lastName} account will restored - </p> - - <div class="flex w-full px-4 gap-4 mt-8"> - <Button color="green" fullwidth on:click={restoreUser}>Confirm</Button> - </div> - </div> -</div> + <svelte:fragment slot="prompt"> + <p><b>{user.firstName} {user.lastName}</b>'s account will be restored.</p> + </svelte:fragment> +</ConfirmDialogue> diff --git a/web/src/lib/components/admin-page/settings/confirm-disable-login.svelte b/web/src/lib/components/admin-page/settings/confirm-disable-login.svelte index 1a8d238b4..37f740d43 100644 --- a/web/src/lib/components/admin-page/settings/confirm-disable-login.svelte +++ b/web/src/lib/components/admin-page/settings/confirm-disable-login.svelte @@ -4,12 +4,9 @@ <ConfirmDialogue title="Disable Login" on:cancel on:confirm> <svelte:fragment slot="prompt"> - <div class="flex flex-col gap-4 p-3"> - <p class="text-md text-center"> - Are you sure you want to disable all login methods? Login will be completely disabled. - </p> - - <p class="text-md text-center"> + <div class="flex flex-col gap-4"> + <p>Are you sure you want to disable all login methods? Login will be completely disabled.</p> + <p> To re-enable, use a <a href="https://immich.app/docs/administration/server-commands" diff --git a/web/src/lib/components/album-page/album-viewer.svelte b/web/src/lib/components/album-page/album-viewer.svelte index 9d3933be6..4b0c28cc1 100644 --- a/web/src/lib/components/album-page/album-viewer.svelte +++ b/web/src/lib/components/album-page/album-viewer.svelte @@ -43,6 +43,7 @@ import ShareInfoModal from './share-info-modal.svelte'; import ThumbnailSelection from './thumbnail-selection.svelte'; import UserSelectionModal from './user-selection-modal.svelte'; + import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte'; import { handleError } from '../../utils/handle-error'; import { downloadArchive } from '../../utils/asset-utils'; @@ -71,6 +72,7 @@ let isShowShareInfoModal = false; let isShowAlbumOptions = false; let isShowThumbnailSelection = false; + let isShowDeleteConfirmation = false; let backUrl = '/albums'; let currentAlbumName = ''; @@ -223,21 +225,17 @@ }; const removeAlbum = async () => { - if ( - window.confirm( - `Are you sure you want to delete album ${album.albumName}? If the album is shared, other users will not be able to access it.` - ) - ) { - try { - await api.albumApi.deleteAlbum({ id: album.id }); - goto(backUrl); - } catch (e) { - console.error('Error [userDeleteMenu] ', e); - notificationController.show({ - type: NotificationType.Error, - message: 'Error deleting album, check console for more details' - }); - } + try { + await api.albumApi.deleteAlbum({ id: album.id }); + goto(backUrl); + } catch (e) { + console.error('Error [userDeleteMenu] ', e); + notificationController.show({ + type: NotificationType.Error, + message: 'Error deleting album, check console for more details' + }); + } finally { + isShowDeleteConfirmation = false; } }; @@ -348,7 +346,11 @@ on:click={() => (isShowShareUserSelection = true)} logo={ShareVariantOutline} /> - <CircleIconButton title="Remove album" on:click={removeAlbum} logo={DeleteOutline} /> + <CircleIconButton + title="Remove album" + on:click={() => (isShowDeleteConfirmation = true)} + logo={DeleteOutline} + /> {/if} {/if} @@ -515,3 +517,17 @@ on:thumbnail-selected={setAlbumThumbnailHandler} /> {/if} + +{#if isShowDeleteConfirmation} + <ConfirmDialogue + title="Delete Album" + confirmText="Delete" + on:confirm={removeAlbum} + on:cancel={() => (isShowDeleteConfirmation = false)} + > + <svelte:fragment slot="prompt"> + <p>Are you sure you want to delete the album <b>{album.albumName}</b>?</p> + <p>If this album is shared, other users will not be able to access it anymore.</p> + </svelte:fragment> + </ConfirmDialogue> +{/if} diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte index 0a073f63f..358572001 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte @@ -21,6 +21,7 @@ import DetailPanel from './detail-panel.svelte'; import PhotoViewer from './photo-viewer.svelte'; import VideoViewer from './video-viewer.svelte'; + import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte'; import { assetStore } from '$lib/stores/assets.store'; import { isShowDetail } from '$lib/stores/preferences.store'; @@ -37,6 +38,7 @@ let halfRightHover = false; let appearsInAlbums: AlbumResponseDto[] = []; let isShowAlbumPicker = false; + let isShowDeleteConfirmation = false; let addToSharedAlbum = true; let shouldPlayMotionPhoto = false; let shouldShowDownloadButton = sharedLink ? sharedLink.allowDownload : true; @@ -77,7 +79,7 @@ closeViewer(); return; case 'Delete': - deleteAsset(); + isShowDeleteConfirmation = true; return; case 'i': $isShowDetail = !$isShowDetail; @@ -116,23 +118,17 @@ const deleteAsset = async () => { try { - if ( - window.confirm( - `Caution! Are you sure you want to delete this asset? This step also deletes this asset in the album(s) to which it belongs. You can not undo this action!` - ) - ) { - const { data: deletedAssets } = await api.assetApi.deleteAsset({ - deleteAssetDto: { - ids: [asset.id] - } - }); + const { data: deletedAssets } = await api.assetApi.deleteAsset({ + deleteAssetDto: { + ids: [asset.id] + } + }); - navigateAssetForward(); + navigateAssetForward(); - for (const asset of deletedAssets) { - if (asset.status == 'SUCCESS') { - assetStore.removeAsset(asset.id); - } + for (const asset of deletedAssets) { + if (asset.status == 'SUCCESS') { + assetStore.removeAsset(asset.id); } } } catch (e) { @@ -140,7 +136,9 @@ type: NotificationType.Error, message: 'Error deleting this asset, check console for more details' }); - console.error('Error deleteSelectedAssetHandler', e); + console.error('Error deleteAsset', e); + } finally { + isShowDeleteConfirmation = false; } }; @@ -227,6 +225,17 @@ }); } }; + + const getAssetType = () => { + switch (asset.type) { + case 'IMAGE': + return 'Photo'; + case 'VIDEO': + return 'Video'; + default: + return 'Asset'; + } + }; </script> <section @@ -244,7 +253,7 @@ on:goBack={closeViewer} on:showDetail={showDetailInfoHandler} on:download={() => downloadFile(asset, publicSharedKey)} - on:delete={deleteAsset} + on:delete={() => (isShowDeleteConfirmation = true)} on:favorite={toggleFavorite} on:addToAlbum={() => openAlbumPicker(false)} on:addToSharedAlbum={() => openAlbumPicker(true)} @@ -358,6 +367,23 @@ on:close={() => (isShowAlbumPicker = false)} /> {/if} + + {#if isShowDeleteConfirmation} + <ConfirmDialogue + title="Delete {getAssetType()}" + confirmText="Delete" + on:confirm={deleteAsset} + on:cancel={() => (isShowDeleteConfirmation = false)} + > + <svelte:fragment slot="prompt"> + <p> + Are you sure you want to delete this {getAssetType().toLowerCase()}? This will also remove + it from its album(s). + </p> + <p><b>You cannot undo this action!</b></p> + </svelte:fragment> + </ConfirmDialogue> + {/if} </section> <style> diff --git a/web/src/lib/components/forms/edit-user-form.svelte b/web/src/lib/components/forms/edit-user-form.svelte index 3aedf0f28..6160901b7 100644 --- a/web/src/lib/components/forms/edit-user-form.svelte +++ b/web/src/lib/components/forms/edit-user-form.svelte @@ -7,6 +7,7 @@ NotificationType } from '../shared-components/notification/notification'; import Button from '../elements/buttons/button.svelte'; + import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte'; import { handleError } from '../../utils/handle-error'; export let user: UserResponseDto; @@ -15,6 +16,8 @@ let error: string; let success: string; + let isShowResetPasswordConfirmation = false; + const dispatch = createEventDispatcher(); const editUser = async () => { @@ -41,20 +44,18 @@ const resetPassword = async () => { try { - if (window.confirm('Do you want to reset the user password?')) { - const defaultPassword = 'password'; + const defaultPassword = 'password'; - const { status } = await api.userApi.updateUser({ - updateUserDto: { - id: user.id, - password: defaultPassword, - shouldChangePassword: true - } - }); - - if (status == 200) { - dispatch('reset-password-success'); + const { status } = await api.userApi.updateUser({ + updateUserDto: { + id: user.id, + password: defaultPassword, + shouldChangePassword: true } + }); + + if (status == 200) { + dispatch('reset-password-success'); } } catch (e) { console.error('Error reseting user password', e); @@ -62,6 +63,8 @@ message: 'Error reseting user password, check console for more details', type: NotificationType.Error }); + } finally { + isShowResetPasswordConfirmation = false; } }; </script> @@ -125,9 +128,9 @@ /> <p> - Note: To apply the Storage Label to previously uploaded assets, run the <a - href="/admin/jobs-status" - class="text-immich-primary dark:text-immich-dark-primary">Storage Migration Job</a + Note: To apply the Storage Label to previously uploaded assets, run the + <a href="/admin/jobs-status" class="text-immich-primary dark:text-immich-dark-primary"> + Storage Migration Job</a > </p> </div> @@ -157,9 +160,28 @@ {/if} <div class="flex w-full px-4 gap-4 mt-8"> {#if canResetPassword} - <Button color="light-red" fullwidth on:click={resetPassword}>Reset password</Button> + <Button + color="light-red" + fullwidth + on:click={() => (isShowResetPasswordConfirmation = true)}>Reset password</Button + > {/if} <Button type="submit" fullwidth>Confirm</Button> </div> </form> </div> + +{#if isShowResetPasswordConfirmation} + <ConfirmDialogue + title="Reset Password" + confirmText="Reset" + on:confirm={resetPassword} + on:cancel={() => (isShowResetPasswordConfirmation = false)} + > + <svelte:fragment slot="prompt"> + <p> + Are you sure you want to reset <b>{user.firstName} {user.lastName}</b>'s password? + </p> + </svelte:fragment> + </ConfirmDialogue> +{/if} diff --git a/web/src/lib/components/photos-page/actions/delete-assets.svelte b/web/src/lib/components/photos-page/actions/delete-assets.svelte index ce030b544..f22a84231 100644 --- a/web/src/lib/components/photos-page/actions/delete-assets.svelte +++ b/web/src/lib/components/photos-page/actions/delete-assets.svelte @@ -13,7 +13,7 @@ export let onAssetDelete: OnAssetDelete; const { getAssets, clearSelect } = getAssetControlContext(); - let confirm = false; + let isShowConfirmation = false; const handleDelete = async () => { try { @@ -32,24 +32,43 @@ } } - notificationController.show({ message: `Deleted ${count}`, type: NotificationType.Info }); + notificationController.show({ + message: `Deleted ${count}`, + type: NotificationType.Info + }); clearSelect(); } catch (e) { handleError(e, 'Error deleting assets'); + } finally { + isShowConfirmation = false; } }; </script> -<CircleIconButton title="Delete" logo={DeleteOutline} on:click={() => (confirm = true)} /> +<CircleIconButton + title="Delete" + logo={DeleteOutline} + on:click={() => (isShowConfirmation = true)} +/> -{#if confirm} +{#if isShowConfirmation} <ConfirmDialogue - prompt="Are you sure you want to delete {getAssets() - .size} assets? This step also deletes assets in the album(s) to which they belong. You can not undo this action!" - title="Delete assets?" + title="Delete Asset{getAssets().size > 1 ? 's' : ''}" confirmText="Delete" on:confirm={handleDelete} - on:cancel={() => (confirm = false)} - /> + on:cancel={() => (isShowConfirmation = false)} + > + <svelte:fragment slot="prompt"> + <p> + Are you sure you want to delete + {#if getAssets().size > 1} + these <b>{getAssets().size}</b> assets? This will also remove them from their album(s). + {:else} + this asset? This will also remove it from its album(s). + {/if} + </p> + <p><b>You cannot undo this action!</b></p> + </svelte:fragment> + </ConfirmDialogue> {/if} diff --git a/web/src/lib/components/photos-page/actions/remove-from-album.svelte b/web/src/lib/components/photos-page/actions/remove-from-album.svelte index ffcd3d434..33a3d011b 100644 --- a/web/src/lib/components/photos-page/actions/remove-from-album.svelte +++ b/web/src/lib/components/photos-page/actions/remove-from-album.svelte @@ -7,32 +7,60 @@ import { AlbumResponseDto, api } from '@api'; import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte'; import { getAssetControlContext } from '../asset-select-control-bar.svelte'; + import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte'; export let album: AlbumResponseDto; const { getAssets, clearSelect } = getAssetControlContext(); - const handleRemoveFromAlbum = async () => { - if (window.confirm('Do you want to remove selected assets from the album?')) { - try { - const { data } = await api.albumApi.removeAssetFromAlbum({ - id: album.id, - removeAssetsDto: { - assetIds: Array.from(getAssets()).map((a) => a.id) - } - }); + let isShowConfirmation = false; - album = data; - clearSelect(); - } catch (e) { - console.error('Error [album-viewer] [removeAssetFromAlbum]', e); - notificationController.show({ - type: NotificationType.Error, - message: 'Error removing assets from album, check console for more details' - }); - } + const removeFromAlbum = async () => { + try { + const { data } = await api.albumApi.removeAssetFromAlbum({ + id: album.id, + removeAssetsDto: { + assetIds: Array.from(getAssets()).map((a) => a.id) + } + }); + + album = data; + clearSelect(); + } catch (e) { + console.error('Error [album-viewer] [removeAssetFromAlbum]', e); + notificationController.show({ + type: NotificationType.Error, + message: 'Error removing assets from album, check console for more details' + }); + } finally { + isShowConfirmation = false; } }; </script> -<CircleIconButton title="Remove from album" on:click={handleRemoveFromAlbum} logo={DeleteOutline} /> +<CircleIconButton + title="Remove from album" + on:click={() => (isShowConfirmation = true)} + logo={DeleteOutline} +/> + +{#if isShowConfirmation} + <ConfirmDialogue + title="Remove Asset{getAssets().size > 1 ? 's' : ''}" + confirmText="Remove" + on:confirm={removeFromAlbum} + on:cancel={() => (isShowConfirmation = false)} + > + <svelte:fragment slot="prompt"> + <p> + Are you sure you want to remove + {#if getAssets().size > 1} + these <b>{getAssets().size}</b> assets + {:else} + this asset + {/if} + from the album? + </p> + </svelte:fragment> + </ConfirmDialogue> +{/if} diff --git a/web/src/lib/components/shared-components/confirm-dialogue.svelte b/web/src/lib/components/shared-components/confirm-dialogue.svelte index 76fb0e160..5f6551920 100644 --- a/web/src/lib/components/shared-components/confirm-dialogue.svelte +++ b/web/src/lib/components/shared-components/confirm-dialogue.svelte @@ -2,18 +2,29 @@ import { createEventDispatcher } from 'svelte'; import FullScreenModal from './full-screen-modal.svelte'; import Button from '../elements/buttons/button.svelte'; + import type { Color } from '$lib/components/elements/buttons/button.svelte'; export let title = 'Confirm'; export let prompt = 'Are you sure you want to do this?'; export let confirmText = 'Confirm'; + export let confirmColor: Color = 'red'; export let cancelText = 'Cancel'; + export let cancelColor: Color = 'primary'; + export let hideCancelButton = false; const dispatch = createEventDispatcher(); + + let isConfirmButtonDisabled = false; + const handleCancel = () => dispatch('cancel'); - const handleConfirm = () => dispatch('confirm'); + + const handleConfirm = () => { + isConfirmButtonDisabled = true; + dispatch('confirm'); + }; </script> -<FullScreenModal on:clickOutside={() => handleCancel()}> +<FullScreenModal on:clickOutside={handleCancel}> <div class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray p-4 shadow-sm w-[500px] max-w-[95vw] rounded-3xl py-8 dark:text-immich-dark-fg" > @@ -25,13 +36,26 @@ </h1> </div> <div> - <slot name="prompt"> - <p class="ml-4 text-md py-5 text-center">{prompt}</p> - </slot> + <div class="px-4 py-5 text-md text-center"> + <slot name="prompt"> + <p>{prompt}</p> + </slot> + </div> <div class="flex w-full px-4 gap-4 mt-4"> - <Button fullwidth on:click={() => handleCancel()}>{cancelText}</Button> - <Button color="red" fullwidth on:click={() => handleConfirm()}>{confirmText}</Button> + {#if !hideCancelButton} + <Button color={cancelColor} fullwidth on:click={handleCancel}> + {cancelText} + </Button> + {/if} + <Button + color={confirmColor} + fullwidth + on:click={handleConfirm} + disabled={isConfirmButtonDisabled} + > + {confirmText} + </Button> </div> </div> </div> diff --git a/web/src/routes/(user)/albums/+page.svelte b/web/src/routes/(user)/albums/+page.svelte index f4902b40e..59f57f613 100644 --- a/web/src/routes/(user)/albums/+page.svelte +++ b/web/src/routes/(user)/albums/+page.svelte @@ -14,6 +14,12 @@ import { onMount } from 'svelte'; import { flip } from 'svelte/animate'; import Dropdown from '$lib/components/elements/dropdown.svelte'; + import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte'; + import { + notificationController, + NotificationType + } from '$lib/components/shared-components/notification/notification'; + import type { AlbumResponseDto } from '@api'; export let data: PageData; @@ -23,14 +29,36 @@ albums: unsortedAlbums, isShowContextMenu, contextMenuPosition, + contextMenuTargetAlbum, createAlbum, deleteAlbum, - deleteSelectedContextAlbum, showAlbumContextMenu, closeAlbumContextMenu } = useAlbums({ albums: data.albums }); let albums = unsortedAlbums; + let albumToDelete: AlbumResponseDto | null; + + const setAlbumToDelete = () => { + albumToDelete = $contextMenuTargetAlbum ?? null; + closeAlbumContextMenu(); + }; + + const deleteSelectedAlbum = async () => { + if (!albumToDelete) { + return; + } + try { + await deleteAlbum(albumToDelete); + } catch { + notificationController.show({ + message: 'Error deleting album', + type: NotificationType.Error + }); + } finally { + albumToDelete = null; + } + }; const sortByDate = (a: string, b: string) => { const aDate = new Date(a); @@ -69,7 +97,6 @@ for (const album of $albums) { if (album.assetCount == 0 && album.albumName == 'Untitled') { await deleteAlbum(album); - $albums = $albums.filter((a) => a.id !== album.id); } } } catch (error) { @@ -120,7 +147,7 @@ <!-- Context Menu --> {#if $isShowContextMenu} <ContextMenu {...$contextMenuPosition} on:outclick={closeAlbumContextMenu}> - <MenuOption on:click={deleteSelectedContextAlbum}> + <MenuOption on:click={() => setAlbumToDelete()}> <span class="flex place-items-center place-content-center gap-2"> <DeleteOutline size="18" /> <p>Delete album</p> @@ -128,3 +155,17 @@ </MenuOption> </ContextMenu> {/if} + +{#if albumToDelete} + <ConfirmDialogue + title="Delete Album" + confirmText="Delete" + on:confirm={deleteSelectedAlbum} + on:cancel={() => (albumToDelete = null)} + > + <svelte:fragment slot="prompt"> + <p>Are you sure you want to delete the album <b>{albumToDelete.albumName}</b>?</p> + <p>If this album is shared, other users will not be able to access it anymore.</p> + </svelte:fragment> + </ConfirmDialogue> +{/if} diff --git a/web/src/routes/(user)/albums/__tests__/albums.bloc.spec.ts b/web/src/routes/(user)/albums/__tests__/albums.bloc.spec.ts index 8dad0c966..0ae8afe5c 100644 --- a/web/src/routes/(user)/albums/__tests__/albums.bloc.spec.ts +++ b/web/src/routes/(user)/albums/__tests__/albums.bloc.spec.ts @@ -12,10 +12,6 @@ jest.mock('@api'); const apiMock: jest.MockedObject<typeof api> = api as jest.MockedObject<typeof api>; -function mockWindowConfirm(result: boolean) { - jest.spyOn(global, 'confirm').mockReturnValueOnce(result); -} - describe('Albums BLoC', () => { let sut: ReturnType<typeof useAlbums>; const _albums = albumFactory.buildList(5); @@ -115,8 +111,6 @@ describe('Albums BLoC', () => { statusText: '' }); - mockWindowConfirm(true); - const albumToDelete = get(sut.albums)[2]; // delete third album const albumToDeleteId = albumToDelete.id; const contextMenuCoords = { x: 100, y: 150 }; @@ -125,52 +119,15 @@ describe('Albums BLoC', () => { sut.showAlbumContextMenu(contextMenuCoords, albumToDelete); expect(get(sut.contextMenuPosition)).toEqual(contextMenuCoords); expect(get(sut.isShowContextMenu)).toBe(true); + expect(get(sut.contextMenuTargetAlbum)).toEqual(albumToDelete); - await sut.deleteSelectedContextAlbum(); + await sut.deleteAlbum(albumToDelete); const updatedAlbums = get(sut.albums); expect(apiMock.albumApi.deleteAlbum).toHaveBeenCalledTimes(1); expect(apiMock.albumApi.deleteAlbum).toHaveBeenCalledWith({ id: albumToDeleteId }); expect(updatedAlbums).toHaveLength(4); expect(updatedAlbums).not.toContain(albumToDelete); - expect(get(sut.isShowContextMenu)).toBe(false); - }); - - it('shows error message when it fails deleting an album', async () => { - mockWindowConfirm(true); - - const albumToDelete = get(sut.albums)[2]; // delete third album - const contextMenuCoords = { x: 100, y: 150 }; - - apiMock.albumApi.deleteAlbum.mockRejectedValueOnce({}); - - sut.showAlbumContextMenu(contextMenuCoords, albumToDelete); - const newAlbum = await sut.deleteSelectedContextAlbum(); - const notifications = get(notificationController.notificationList); - - expect(apiMock.albumApi.deleteAlbum).toHaveBeenCalledTimes(1); - expect(newAlbum).not.toBeDefined(); - expect(notifications).toHaveLength(1); - expect(notifications[0].type).toEqual(NotificationType.Error); - }); - - it('prevents deleting an album when rejecting confirm dialog', async () => { - const albumToDelete = get(sut.albums)[2]; // delete third album - - mockWindowConfirm(false); - - sut.showAlbumContextMenu({ x: 100, y: 150 }, albumToDelete); - await sut.deleteSelectedContextAlbum(); - - expect(apiMock.albumApi.deleteAlbum).not.toHaveBeenCalled(); - }); - - it('prevents deleting an album when not previously selected', async () => { - mockWindowConfirm(true); - - await sut.deleteSelectedContextAlbum(); - - expect(apiMock.albumApi.deleteAlbum).not.toHaveBeenCalled(); }); it('closes album context menu, deselecting album', () => { diff --git a/web/src/routes/(user)/albums/albums.bloc.ts b/web/src/routes/(user)/albums/albums.bloc.ts index 537ce33f1..78cd907d5 100644 --- a/web/src/routes/(user)/albums/albums.bloc.ts +++ b/web/src/routes/(user)/albums/albums.bloc.ts @@ -24,8 +24,6 @@ export const useAlbums = (props: AlbumsProps) => { if (album.albumName === 'Untitled' && album.assetCount === 0) { setTimeout(async () => { await deleteAlbum(album); - const _albums = get(albums); - albums.set(_albums.filter((a) => a.id !== album.id)); }, 500); } } @@ -54,8 +52,13 @@ export const useAlbums = (props: AlbumsProps) => { } } - async function deleteAlbum(album: AlbumResponseDto): Promise<void> { - await api.albumApi.deleteAlbum({ id: album.id }); + async function deleteAlbum(albumToDelete: AlbumResponseDto): Promise<void> { + await api.albumApi.deleteAlbum({ id: albumToDelete.id }); + albums.set( + get(albums).filter(({ id }) => { + return id !== albumToDelete.id; + }) + ); } async function showAlbumContextMenu( @@ -74,40 +77,15 @@ export const useAlbums = (props: AlbumsProps) => { contextMenuTargetAlbum.set(undefined); } - async function deleteSelectedContextAlbum(): Promise<void> { - const albumToDelete = get(contextMenuTargetAlbum); - if (!albumToDelete) { - return; - } - if ( - window.confirm( - `Are you sure you want to delete album ${albumToDelete.albumName}? If the album is shared, other users will not be able to access it.` - ) - ) { - try { - await api.albumApi.deleteAlbum({ id: albumToDelete.id }); - const _albums = get(albums); - albums.set(_albums.filter((a) => a.id !== albumToDelete.id)); - } catch { - notificationController.show({ - message: 'Error deleting album', - type: NotificationType.Error - }); - } - } - - closeAlbumContextMenu(); - } - return { albums, isShowContextMenu, contextMenuPosition, + contextMenuTargetAlbum, loadAlbums, createAlbum, deleteAlbum, showAlbumContextMenu, - closeAlbumContextMenu, - deleteSelectedContextAlbum + closeAlbumContextMenu }; }; diff --git a/web/src/routes/(user)/sharing/sharedlinks/+page.svelte b/web/src/routes/(user)/sharing/sharedlinks/+page.svelte index aee6a9863..6cd7e2e51 100644 --- a/web/src/routes/(user)/sharing/sharedlinks/+page.svelte +++ b/web/src/routes/(user)/sharing/sharedlinks/+page.svelte @@ -98,7 +98,7 @@ {#if deleteLinkId} <ConfirmDialogue title="Delete Shared Link" - prompt="Are you want to delete this shared link?" + prompt="Are you sure you want to delete this shared link?" confirmText="Delete" on:confirm={() => handleDeleteLink()} on:cancel={() => (deleteLinkId = null)} diff --git a/web/src/routes/admin/user-management/+page.svelte b/web/src/routes/admin/user-management/+page.svelte index 5dc4b8260..aae793d47 100644 --- a/web/src/routes/admin/user-management/+page.svelte +++ b/web/src/routes/admin/user-management/+page.svelte @@ -126,23 +126,21 @@ {/if} {#if shouldShowDeleteConfirmDialog} - <FullScreenModal on:clickOutside={() => (shouldShowDeleteConfirmDialog = false)}> - <DeleteConfirmDialog - user={selectedUser} - on:user-delete-success={onUserDeleteSuccess} - on:user-delete-fail={onUserDeleteFail} - /> - </FullScreenModal> + <DeleteConfirmDialog + user={selectedUser} + on:user-delete-success={onUserDeleteSuccess} + on:user-delete-fail={onUserDeleteFail} + on:cancel={() => (shouldShowDeleteConfirmDialog = false)} + /> {/if} {#if shouldShowRestoreDialog} - <FullScreenModal on:clickOutside={() => (shouldShowRestoreDialog = false)}> - <RestoreDialogue - user={selectedUser} - on:user-restore-success={onUserRestoreSuccess} - on:user-restore-fail={onUserRestoreFail} - /> - </FullScreenModal> + <RestoreDialogue + user={selectedUser} + on:user-restore-success={onUserRestoreSuccess} + on:user-restore-fail={onUserRestoreFail} + on:cancel={() => (shouldShowRestoreDialog = false)} + /> {/if} {#if shouldShowInfoPanel}