mirror of
https://github.com/immich-app/immich.git
synced 2025-05-24 01:12:58 -04:00
fix(web): handle deleted user on details page (#18264)
This commit is contained in:
parent
989d9dbe51
commit
3fdc1df89c
@ -1867,6 +1867,7 @@
|
|||||||
"use_current_connection": "use current connection",
|
"use_current_connection": "use current connection",
|
||||||
"use_custom_date_range": "Use custom date range instead",
|
"use_custom_date_range": "Use custom date range instead",
|
||||||
"user": "User",
|
"user": "User",
|
||||||
|
"user_has_been_deleted": "This user has been deleted.",
|
||||||
"user_id": "User ID",
|
"user_id": "User ID",
|
||||||
"user_liked": "{user} liked {type, select, photo {this photo} video {this video} asset {this asset} other {it}}",
|
"user_liked": "{user} liked {type, select, photo {this photo} video {this video} asset {this asset} other {it}}",
|
||||||
"created_at": "Created",
|
"created_at": "Created",
|
||||||
|
@ -4,12 +4,12 @@
|
|||||||
import ConfirmModal from '$lib/modals/ConfirmModal.svelte';
|
import ConfirmModal from '$lib/modals/ConfirmModal.svelte';
|
||||||
import { serverConfig } from '$lib/stores/server-config.store';
|
import { serverConfig } from '$lib/stores/server-config.store';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { deleteUserAdmin, type UserResponseDto } from '@immich/sdk';
|
import { deleteUserAdmin, type UserAdminResponseDto, type UserResponseDto } from '@immich/sdk';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
user: UserResponseDto;
|
user: UserResponseDto;
|
||||||
onClose: (confirmed?: true) => void;
|
onClose: (user?: UserAdminResponseDto) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { user, onClose }: Props = $props();
|
let { user, onClose }: Props = $props();
|
||||||
@ -20,14 +20,12 @@
|
|||||||
|
|
||||||
const handleDeleteUser = async () => {
|
const handleDeleteUser = async () => {
|
||||||
try {
|
try {
|
||||||
const { deletedAt } = await deleteUserAdmin({
|
const result = await deleteUserAdmin({
|
||||||
id: user.id,
|
id: user.id,
|
||||||
userAdminDeleteDto: { force: forceDelete },
|
userAdminDeleteDto: { force: forceDelete },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (deletedAt !== undefined) {
|
onClose(result);
|
||||||
onClose(true);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, $t('errors.unable_to_delete_user'));
|
handleError(error, $t('errors.unable_to_delete_user'));
|
||||||
}
|
}
|
||||||
|
@ -1,34 +1,30 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import FormatMessage from '$lib/components/i18n/format-message.svelte';
|
import FormatMessage from '$lib/components/i18n/format-message.svelte';
|
||||||
import ConfirmModal from '$lib/modals/ConfirmModal.svelte';
|
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { restoreUserAdmin, type UserResponseDto } from '@immich/sdk';
|
import { restoreUserAdmin, type UserAdminResponseDto, type UserResponseDto } from '@immich/sdk';
|
||||||
|
import { Button, Modal, ModalBody, ModalFooter } from '@immich/ui';
|
||||||
|
import { mdiDeleteRestore } from '@mdi/js';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
user: UserResponseDto;
|
user: UserResponseDto;
|
||||||
onClose: (confirmed?: true) => void;
|
onClose: (user?: UserAdminResponseDto) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { user, onClose }: Props = $props();
|
let { user, onClose }: Props = $props();
|
||||||
|
|
||||||
const handleRestoreUser = async () => {
|
const handleRestoreUser = async () => {
|
||||||
try {
|
try {
|
||||||
await restoreUserAdmin({ id: user.id });
|
const result = await restoreUserAdmin({ id: user.id });
|
||||||
onClose(true);
|
onClose(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, $t('errors.unable_to_restore_user'));
|
handleError(error, $t('errors.unable_to_restore_user'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ConfirmModal
|
<Modal title={$t('restore_user')} {onClose} icon={mdiDeleteRestore} size="small" class="bg-light text-dark">
|
||||||
title={$t('restore_user')}
|
<ModalBody>
|
||||||
confirmText={$t('continue')}
|
|
||||||
confirmColor="success"
|
|
||||||
onClose={(confirmed) => (confirmed ? handleRestoreUser() : onClose())}
|
|
||||||
>
|
|
||||||
{#snippet promptSnippet()}
|
|
||||||
<p>
|
<p>
|
||||||
<FormatMessage key="admin.user_restore_description" values={{ user: user.name }}>
|
<FormatMessage key="admin.user_restore_description" values={{ user: user.name }}>
|
||||||
{#snippet children({ message })}
|
{#snippet children({ message })}
|
||||||
@ -36,5 +32,16 @@
|
|||||||
{/snippet}
|
{/snippet}
|
||||||
</FormatMessage>
|
</FormatMessage>
|
||||||
</p>
|
</p>
|
||||||
{/snippet}
|
</ModalBody>
|
||||||
</ConfirmModal>
|
|
||||||
|
<ModalFooter>
|
||||||
|
<div class="flex gap-3 w-full">
|
||||||
|
<Button shape="round" color="secondary" fullWidth onclick={() => onClose()}>
|
||||||
|
{$t('cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button shape="round" color="primary" fullWidth onclick={() => handleRestoreUser()}>
|
||||||
|
{$t('restore')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</ModalFooter>
|
||||||
|
</Modal>
|
||||||
|
@ -18,8 +18,8 @@
|
|||||||
import { websocketEvents } from '$lib/stores/websocket';
|
import { websocketEvents } from '$lib/stores/websocket';
|
||||||
import { getByteUnitString } from '$lib/utils/byte-units';
|
import { getByteUnitString } from '$lib/utils/byte-units';
|
||||||
import { UserStatus, searchUsersAdmin, type UserAdminResponseDto } from '@immich/sdk';
|
import { UserStatus, searchUsersAdmin, type UserAdminResponseDto } from '@immich/sdk';
|
||||||
import { Button, IconButton, Link } from '@immich/ui';
|
import { Button, HStack, IconButton, Link, Text } from '@immich/ui';
|
||||||
import { mdiDeleteRestore, mdiInfinity, mdiPencilOutline, mdiTrashCanOutline } from '@mdi/js';
|
import { mdiDeleteRestore, mdiInfinity, mdiPencilOutline, mdiPlusBoxOutline, mdiTrashCanOutline } from '@mdi/js';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
@ -86,6 +86,13 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<UserPageLayout title={data.meta.title} admin>
|
<UserPageLayout title={data.meta.title} admin>
|
||||||
|
{#snippet buttons()}
|
||||||
|
<HStack gap={1}>
|
||||||
|
<Button leadingIcon={mdiPlusBoxOutline} onclick={handleCreate} size="small" variant="ghost" color="secondary">
|
||||||
|
<Text class="hidden md:block">{$t('create_user')}</Text>
|
||||||
|
</Button>
|
||||||
|
</HStack>
|
||||||
|
{/snippet}
|
||||||
<section id="setting-content" class="flex place-content-center sm:mx-4">
|
<section id="setting-content" class="flex place-content-center sm:mx-4">
|
||||||
<section class="w-full pb-28 lg:w-[850px]">
|
<section class="w-full pb-28 lg:w-[850px]">
|
||||||
<table class="my-5 w-full text-start">
|
<table class="my-5 w-full text-start">
|
||||||
@ -163,7 +170,6 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<Button shape="round" size="small" onclick={handleCreate}>{$t('create_user')}</Button>
|
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
</UserPageLayout>
|
</UserPageLayout>
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
|
||||||
import StatsCard from '$lib/components/admin-page/server-stats/stats-card.svelte';
|
import StatsCard from '$lib/components/admin-page/server-stats/stats-card.svelte';
|
||||||
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
||||||
import {
|
import {
|
||||||
@ -7,17 +6,18 @@
|
|||||||
NotificationType,
|
NotificationType,
|
||||||
} from '$lib/components/shared-components/notification/notification';
|
} from '$lib/components/shared-components/notification/notification';
|
||||||
import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
|
import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
|
||||||
import { modalManager } from '$lib/managers/modal-manager.svelte';
|
import { modalManager } from '$lib/managers/modal-manager.svelte';
|
||||||
import PasswordResetSuccessModal from '$lib/modals/PasswordResetSuccessModal.svelte';
|
import PasswordResetSuccessModal from '$lib/modals/PasswordResetSuccessModal.svelte';
|
||||||
import UserDeleteConfirmModal from '$lib/modals/UserDeleteConfirmModal.svelte';
|
import UserDeleteConfirmModal from '$lib/modals/UserDeleteConfirmModal.svelte';
|
||||||
import UserEditModal from '$lib/modals/UserEditModal.svelte';
|
import UserEditModal from '$lib/modals/UserEditModal.svelte';
|
||||||
|
import UserRestoreConfirmModal from '$lib/modals/UserRestoreConfirmModal.svelte';
|
||||||
import { locale } from '$lib/stores/preferences.store';
|
import { locale } from '$lib/stores/preferences.store';
|
||||||
import { user as authUser } from '$lib/stores/user.store';
|
import { user as authUser } from '$lib/stores/user.store';
|
||||||
import { getBytesWithUnit } from '$lib/utils/byte-units';
|
import { getBytesWithUnit } from '$lib/utils/byte-units';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { updateUserAdmin } from '@immich/sdk';
|
import { updateUserAdmin } from '@immich/sdk';
|
||||||
import {
|
import {
|
||||||
|
Alert,
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
CardBody,
|
CardBody,
|
||||||
@ -40,6 +40,7 @@
|
|||||||
mdiChartPie,
|
mdiChartPie,
|
||||||
mdiChartPieOutline,
|
mdiChartPieOutline,
|
||||||
mdiCheckCircle,
|
mdiCheckCircle,
|
||||||
|
mdiDeleteRestore,
|
||||||
mdiFeatureSearchOutline,
|
mdiFeatureSearchOutline,
|
||||||
mdiLockSmart,
|
mdiLockSmart,
|
||||||
mdiOnepassword,
|
mdiOnepassword,
|
||||||
@ -80,7 +81,14 @@
|
|||||||
const handleDelete = async () => {
|
const handleDelete = async () => {
|
||||||
const result = await modalManager.show(UserDeleteConfirmModal, { user });
|
const result = await modalManager.show(UserDeleteConfirmModal, { user });
|
||||||
if (result) {
|
if (result) {
|
||||||
await goto(AppRoute.ADMIN_USERS);
|
user = result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRestore = async () => {
|
||||||
|
const result = await modalManager.show(UserRestoreConfirmModal, { user });
|
||||||
|
if (result) {
|
||||||
|
user = result;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -191,19 +199,36 @@
|
|||||||
>
|
>
|
||||||
<Text class="hidden md:block">{$t('edit_user')}</Text>
|
<Text class="hidden md:block">{$t('edit_user')}</Text>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
{#if user.deletedAt}
|
||||||
color="danger"
|
<Button
|
||||||
size="small"
|
color="primary"
|
||||||
variant="ghost"
|
size="small"
|
||||||
leadingIcon={mdiTrashCanOutline}
|
variant="ghost"
|
||||||
onclick={() => handleDelete()}
|
leadingIcon={mdiDeleteRestore}
|
||||||
>
|
class="ms-1"
|
||||||
<Text class="hidden md:block">{$t('delete_user')}</Text>
|
onclick={() => handleRestore()}
|
||||||
</Button>
|
>
|
||||||
|
<Text class="hidden md:block">{$t('restore_user')}</Text>
|
||||||
|
</Button>
|
||||||
|
{:else}
|
||||||
|
<Button
|
||||||
|
color="danger"
|
||||||
|
size="small"
|
||||||
|
variant="ghost"
|
||||||
|
leadingIcon={mdiTrashCanOutline}
|
||||||
|
onclick={() => handleDelete()}
|
||||||
|
>
|
||||||
|
<Text class="hidden md:block">{$t('delete_user')}</Text>
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
</HStack>
|
</HStack>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
<div>
|
<div>
|
||||||
<Container size="large" center>
|
<Container size="large" center>
|
||||||
|
{#if user.deletedAt}
|
||||||
|
<Alert color="danger" class="my-4" title={$t('user_has_been_deleted')} icon={mdiTrashCanOutline} />
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div class="grid gap-4 grod-cols-1 lg:grid-cols-2 w-full">
|
<div class="grid gap-4 grod-cols-1 lg:grid-cols-2 w-full">
|
||||||
<div class="col-span-full flex gap-4 items-center my-4">
|
<div class="col-span-full flex gap-4 items-center my-4">
|
||||||
<UserAvatar {user} size="md" />
|
<UserAvatar {user} size="md" />
|
||||||
|
Loading…
x
Reference in New Issue
Block a user