diff --git a/src/admin/AdminBatchEditPanelClient.tsx b/src/admin/AdminBatchEditPanelClient.tsx
index f543e8e9..0df6d5f5 100644
--- a/src/admin/AdminBatchEditPanelClient.tsx
+++ b/src/admin/AdminBatchEditPanelClient.tsx
@@ -6,7 +6,6 @@ import SiteGrid from '@/components/SiteGrid';
import { useAppState } from '@/state/AppState';
import { clsx } from 'clsx/lite';
import { IoCloseSharp } from 'react-icons/io5';
-import DeleteButton from './DeleteButton';
import { useState } from 'react';
import { Tags } from '@/tag';
import { usePathname } from 'next/navigation';
@@ -14,6 +13,7 @@ import { PATH_GRID_INFERRED } from '@/site/paths';
import PhotoTagFieldset from './PhotoTagFieldset';
import { tagMultiplePhotosAction } from '@/photo/actions';
import { toastSuccess } from '@/toast';
+import DeletePhotosButton from './DeletePhotosButton';
export default function AdminBatchEditPanelClient({
uniqueTags,
@@ -96,7 +96,11 @@ export default function AdminBatchEditPanelClient({
>
Tag ...
-
+
>}
}
diff --git a/src/admin/DeletePhotosButton.tsx b/src/admin/DeletePhotosButton.tsx
new file mode 100644
index 00000000..66133f2f
--- /dev/null
+++ b/src/admin/DeletePhotosButton.tsx
@@ -0,0 +1,53 @@
+'use client';
+
+import LoaderButton from '@/components/primitives/LoaderButton';
+import { photoQuantityText } from '@/photo';
+import { deletePhotosAction } from '@/photo/actions';
+import { toastSuccess, toastWarning } from '@/toast';
+import { clsx } from 'clsx/lite';
+import { ComponentProps, useState } from 'react';
+import { BiTrash } from 'react-icons/bi';
+
+export default function DeletePhotosButton({
+ photoIds = [],
+ onDelete,
+ className,
+ ...rest
+}: {
+ photoIds?: string[]
+ onDelete?: () => void
+} & ComponentProps) {
+ const [isLoading, setIsLoading] = useState(false);
+
+ const photosText = photoQuantityText(photoIds.length, false);
+
+ return (
+ }
+ spinnerColor="text"
+ className={clsx(
+ '!text-red-500 dark:!text-red-600',
+ 'active:!bg-red-100/50 active:dark:!bg-red-950/50',
+ 'disabled:!bg-red-100/50 disabled:dark:!bg-red-950/50',
+ '!border-red-200 hover:!border-red-300',
+ 'dark:!border-red-900/75 dark:hover:!border-red-900',
+ className,
+ )}
+ isLoading={isLoading}
+ // eslint-disable-next-line max-len
+ confirmText={`Are you sure you want to delete ${photosText}? This action cannot be undone.`}
+ onClick={() => {
+ setIsLoading(true);
+ deletePhotosAction(photoIds)
+ .then(() => {
+ toastSuccess(`${photosText} deleted`);
+ onDelete?.();
+ })
+ .catch(() => toastWarning(`Failed to delete ${photosText}`))
+ .finally(() => setIsLoading(false));
+ }}
+ />
+ );
+}
diff --git a/src/components/primitives/LoaderButton.tsx b/src/components/primitives/LoaderButton.tsx
index c63683cf..e909e502 100644
--- a/src/components/primitives/LoaderButton.tsx
+++ b/src/components/primitives/LoaderButton.tsx
@@ -59,7 +59,7 @@ export default function LoaderButton(props: {
>
{(icon || isLoading) &&
@@ -67,9 +67,7 @@ export default function LoaderButton(props: {
?
: icon}
}
diff --git a/src/photo/actions.ts b/src/photo/actions.ts
index 9240bbf5..35d29a21 100644
--- a/src/photo/actions.ts
+++ b/src/photo/actions.ts
@@ -236,6 +236,17 @@ export const toggleFavoritePhotoAction = async (
}
});
+export const deletePhotosAction = async (photoIds: string[]) =>
+ runAuthenticatedAdminServerAction(async () => {
+ for (const photoId of photoIds) {
+ const photo = await getPhoto(photoId);
+ if (photo) {
+ await deletePhoto(photoId).then(() => deleteFile(photo.url));
+ }
+ }
+ revalidateAllKeysAndPaths();
+ });
+
export const deletePhotoAction = async (
photoId: string,
photoUrl: string,