aboutsummaryrefslogtreecommitdiffstats
path: root/apps/web/components/dashboard/BulkBookmarksAction.tsx
diff options
context:
space:
mode:
authorMd Saban <45597394+mdsaban@users.noreply.github.com>2024-07-02 03:51:23 +0530
committerGitHub <noreply@github.com>2024-07-01 23:21:23 +0100
commitd193d9bf89e8a88bf70b673ea5e438d73cf40c0c (patch)
tree56d260c0624b74bda054f8865c3bd2e166f49e8d /apps/web/components/dashboard/BulkBookmarksAction.tsx
parentbf92fa3386be331871963f99ec5c813186a388b3 (diff)
downloadkarakeep-d193d9bf89e8a88bf70b673ea5e438d73cf40c0c.tar.zst
feat: Add bulk edit option for bookmarks. Fixes #84 (#259)
* feat: add bulk edit option for bookmarks * fix: resolve comments * fix: resolve comments * fix: resolve comments * fix: resolve comments * rename bulk action store, simplify the bulk action toolbar --------- Co-authored-by: MohamedBassem <me@mbassem.com>
Diffstat (limited to 'apps/web/components/dashboard/BulkBookmarksAction.tsx')
-rw-r--r--apps/web/components/dashboard/BulkBookmarksAction.tsx171
1 files changed, 171 insertions, 0 deletions
diff --git a/apps/web/components/dashboard/BulkBookmarksAction.tsx b/apps/web/components/dashboard/BulkBookmarksAction.tsx
new file mode 100644
index 00000000..b78071ee
--- /dev/null
+++ b/apps/web/components/dashboard/BulkBookmarksAction.tsx
@@ -0,0 +1,171 @@
+"use client";
+
+import React, { useEffect, useState } from "react";
+import {
+ ActionButton,
+ ActionButtonWithTooltip,
+} from "@/components/ui/action-button";
+import ActionConfirmingDialog from "@/components/ui/action-confirming-dialog";
+import { useToast } from "@/components/ui/use-toast";
+import useBulkActionsStore from "@/lib/bulkActions";
+import { Pencil, Trash2, X } from "lucide-react";
+
+import {
+ useDeleteBookmark,
+ useUpdateBookmark,
+} from "@hoarder/shared-react/hooks/bookmarks";
+
+import { ArchivedActionIcon, FavouritedActionIcon } from "./bookmarks/icons";
+
+export default function BulkBookmarksAction() {
+ const { selectedBookmarks, isBulkEditEnabled } = useBulkActionsStore();
+ const setIsBulkEditEnabled = useBulkActionsStore(
+ (state) => state.setIsBulkEditEnabled,
+ );
+ const { toast } = useToast();
+ const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
+
+ useEffect(() => {
+ setIsBulkEditEnabled(false); // turn off toggle + clear selected bookmarks on mount
+ }, []);
+
+ const onError = () => {
+ toast({
+ variant: "destructive",
+ title: "Something went wrong",
+ description: "There was a problem with your request.",
+ });
+ };
+
+ const deleteBookmarkMutator = useDeleteBookmark({
+ onSuccess: () => {
+ setIsBulkEditEnabled(false);
+ },
+ onError,
+ });
+
+ const updateBookmarkMutator = useUpdateBookmark({
+ onSuccess: () => {
+ setIsBulkEditEnabled(false);
+ },
+ onError,
+ });
+
+ interface UpdateBookmarkProps {
+ favourited?: boolean;
+ archived?: boolean;
+ }
+
+ const updateBookmarks = async ({
+ favourited,
+ archived,
+ }: UpdateBookmarkProps) => {
+ await Promise.all(
+ selectedBookmarks.map((item) =>
+ updateBookmarkMutator.mutateAsync({
+ bookmarkId: item.id,
+ favourited,
+ archived,
+ }),
+ ),
+ );
+ toast({
+ description: `${selectedBookmarks.length} bookmarks have been updated!`,
+ });
+ };
+
+ const deleteBookmarks = async () => {
+ await Promise.all(
+ selectedBookmarks.map((item) =>
+ deleteBookmarkMutator.mutateAsync({ bookmarkId: item.id }),
+ ),
+ );
+ toast({
+ description: `${selectedBookmarks.length} bookmarks have been deleted!`,
+ });
+ };
+
+ const alreadyFavourited =
+ selectedBookmarks.length &&
+ selectedBookmarks.every((item) => item.favourited === true);
+
+ const alreadyArchived =
+ selectedBookmarks.length &&
+ selectedBookmarks.every((item) => item.archived === true);
+
+ const actionList = [
+ {
+ name: alreadyFavourited ? "Unfavourite" : "Favourite",
+ icon: <FavouritedActionIcon favourited={!!alreadyFavourited} size={18} />,
+ action: () => updateBookmarks({ favourited: !alreadyFavourited }),
+ isPending: updateBookmarkMutator.isPending,
+ hidden: !isBulkEditEnabled,
+ },
+ {
+ name: alreadyArchived ? "Un-arhcive" : "Archive",
+ icon: <ArchivedActionIcon size={18} archived={!!alreadyArchived} />,
+ action: () => updateBookmarks({ archived: !alreadyArchived }),
+ isPending: updateBookmarkMutator.isPending,
+ hidden: !isBulkEditEnabled,
+ },
+ {
+ name: "Delete",
+ icon: <Trash2 size={18} color="red" />,
+ action: () => setIsDeleteDialogOpen(true),
+ hidden: !isBulkEditEnabled,
+ },
+ {
+ name: "Close bulk edit",
+ icon: <X size={18} />,
+ action: () => setIsBulkEditEnabled(false),
+ alwaysEnable: true,
+ hidden: !isBulkEditEnabled,
+ },
+ {
+ name: "Bulk Edit",
+ icon: <Pencil size={18} />,
+ action: () => setIsBulkEditEnabled(true),
+ alwaysEnable: true,
+ hidden: isBulkEditEnabled,
+ },
+ ];
+
+ return (
+ <div>
+ <ActionConfirmingDialog
+ open={isDeleteDialogOpen}
+ setOpen={setIsDeleteDialogOpen}
+ title={"Delete Bookmarks"}
+ description={<p>Are you sure you want to delete these bookmarks?</p>}
+ actionButton={() => (
+ <ActionButton
+ type="button"
+ variant="destructive"
+ loading={deleteBookmarkMutator.isPending}
+ onClick={() => deleteBookmarks()}
+ >
+ Delete
+ </ActionButton>
+ )}
+ />
+ <div className="flex">
+ {actionList.map(
+ ({ name, icon: Icon, action, isPending, hidden, alwaysEnable }) => (
+ <ActionButtonWithTooltip
+ className={hidden ? "hidden" : "block"}
+ tooltip={name}
+ disabled={!selectedBookmarks.length && !alwaysEnable}
+ delayDuration={100}
+ loading={!!isPending}
+ variant="ghost"
+ key={name}
+ onClick={action}
+ >
+ {Icon}
+ </ActionButtonWithTooltip>
+ ),
+ )}
+ </div>
+ </div>
+ );
+}