Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] Add bulk edit option for bookmarks #259

Merged
merged 6 commits into from
Jul 1, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/web/app/dashboard/archive/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Bookmarks from "@/components/dashboard/bookmarks/Bookmarks";
import ChangeLayout from "@/components/dashboard/ChangeLayout";
import GlobalActions from "@/components/dashboard/GlobalActions";
import InfoTooltip from "@/components/ui/info-tooltip";

function header() {
Expand All @@ -12,7 +12,7 @@ function header() {
</InfoTooltip>
</div>
<div>
<ChangeLayout />
<GlobalActions />
</div>
</div>
);
Expand Down
6 changes: 3 additions & 3 deletions apps/web/app/dashboard/bookmarks/page.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import React from "react";
import Bookmarks from "@/components/dashboard/bookmarks/Bookmarks";
import ChangeLayout from "@/components/dashboard/ChangeLayout";
import GlobalActions from "@/components/dashboard/GlobalActions";
import { SearchInput } from "@/components/dashboard/search/SearchInput";

export default async function BookmarksPage() {
return (
<div>
<div className="flex gap-2">
<SearchInput />
<ChangeLayout />
<GlobalActions />
</div>
<div className="my-4 flex-1">
<div className="my-4">
<Bookmarks query={{ archived: false }} showEditorCard={true} />
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions apps/web/app/dashboard/favourites/page.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import Bookmarks from "@/components/dashboard/bookmarks/Bookmarks";
import ChangeLayout from "@/components/dashboard/ChangeLayout";
import GlobalActions from "@/components/dashboard/GlobalActions";

export default async function FavouritesBookmarkPage() {
return (
<Bookmarks
header={
<div className="flex items-center justify-between">
<p className="text-2xl">⭐️ Favourites</p>
<ChangeLayout />
<GlobalActions />
</div>
}
query={{ favourited: true }}
Expand Down
4 changes: 2 additions & 2 deletions apps/web/app/dashboard/search/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { Suspense, useRef } from "react";
import BookmarksGrid from "@/components/dashboard/bookmarks/BookmarksGrid";
import ChangeLayout from "@/components/dashboard/ChangeLayout";
import GlobalActions from "@/components/dashboard/GlobalActions";
import { SearchInput } from "@/components/dashboard/search/SearchInput";
import { FullPageSpinner } from "@/components/ui/full-page-spinner";
import { useBookmarkSearch } from "@/lib/hooks/bookmark-search";
Expand All @@ -17,7 +17,7 @@ function SearchComp() {
<div className="flex flex-col gap-3">
<div className="flex gap-2">
<SearchInput ref={inputRef} autoFocus={true} />
<ChangeLayout />
<GlobalActions />
</div>
{data ? (
<BookmarksGrid bookmarks={data.bookmarks} />
Expand Down
204 changes: 204 additions & 0 deletions apps/web/components/dashboard/BulkBookmarksAction.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
"use client";

import React, { useEffect, useState } from "react";
import { usePathname } from "next/navigation";
import { Button, ButtonWithTooltip } from "@/components/ui/button";
import {
Dialog,
DialogClose,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { useToast } from "@/components/ui/use-toast";
import bulkActions from "@/store/bulkBookmarksAction";
import { Archive, ArchiveRestore, Pencil, Star, Trash2, X } from "lucide-react";

import {
useDeleteBookmark,
useUpdateBookmark,
} from "@hoarder/shared-react/hooks/bookmarks";

function DeleteModal({
open,
setOpen,
deleteBookmarks,
}: {
open: boolean;
setOpen: (open: boolean) => void;
deleteBookmarks: () => void;
}) {
return (
<Dialog open={open} onOpenChange={setOpen}>
mdsaban marked this conversation as resolved.
Show resolved Hide resolved
<DialogContent>
<DialogHeader>
<DialogTitle>Delete Bookmarks</DialogTitle>
<DialogClose />
</DialogHeader>
<p>Are you sure you want to delete these bookmarks?</p>
<DialogFooter>
<Button variant="ghost" onClick={() => setOpen(false)}>
Cancel
</Button>
<Button
variant="destructive"
onClick={() => {
deleteBookmarks();
setOpen(false);
}}
>
Delete
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

export default function BulkBookmarksAction() {
const { selectedBookmarks, isBulkEditEnabled } = bulkActions();
const handleBulkEdit = bulkActions((state) => state.handleBulkEdit);
const { toast } = useToast();
const pathname = usePathname();
const [showDeleteModal, setShowDeleteModal] = useState(false);

useEffect(() => {
handleBulkEdit(false); // turn off toggle + clear selected bookmarks on mount
}, []);

const onError = () => {
toast({
variant: "destructive",
title: "Something went wrong",
mdsaban marked this conversation as resolved.
Show resolved Hide resolved
description: "There was a problem with your request.",
});
};

const deleteBookmarkMutator = useDeleteBookmark({
onSuccess: () => {
handleBulkEdit(false);
},
onError,
});

const updateBookmarkMutator = useUpdateBookmark({
onSuccess: () => {
handleBulkEdit(false);
},
onError,
});

interface UpdateBookmarkProps {
favourited?: boolean;
archived?: boolean;
}

const updateBookmarks = ({
favourited,
archived,
}: UpdateBookmarkProps): void => {
selectedBookmarks.map((item) => {
updateBookmarkMutator.mutate({
bookmarkId: item.id,
favourited: favourited ?? item.favourited,
archived: archived ?? item.archived,
});
});
toast({
description: `${selectedBookmarks.length} bookmarks have been updated!`,
});
};

const deleteBookmarks = () => {
selectedBookmarks.map((item) => {
deleteBookmarkMutator.mutate({ bookmarkId: item.id });
});
toast({
description: `${selectedBookmarks.length} bookmarks have been deleted!`,
});
};

const alreadyFavourited =
(selectedBookmarks.length &&
selectedBookmarks.every((item) => item.favourited === true)) ||
pathname.includes("favourites");
mdsaban marked this conversation as resolved.
Show resolved Hide resolved

const alreadyArchived =
(selectedBookmarks.length &&
selectedBookmarks.every((item) => item.archived === true)) ||
pathname.includes("archive");
mdsaban marked this conversation as resolved.
Show resolved Hide resolved

const actionList = [
{
name: alreadyFavourited ? "Unfavourite" : "Favourite",
icon: (
<Star
color={alreadyFavourited ? "#ebb434" : "#000"}
fill={alreadyFavourited ? "#ebb434" : "transparent"}
mdsaban marked this conversation as resolved.
Show resolved Hide resolved
size={18}
/>
),
action: () => updateBookmarks({ favourited: !alreadyFavourited }),
},
{
name: alreadyArchived ? "Un-arhcive" : "Archive",
icon: alreadyArchived ? (
<ArchiveRestore size={18} />
) : (
<Archive size={18} />
),
action: () => updateBookmarks({ archived: !alreadyArchived }),
},
{
name: "Delete",
icon: <Trash2 size={18} />,
action: () => setShowDeleteModal(true),
},
{
name: "Close bulk edit",
icon: <X size={18} />,
action: () => handleBulkEdit(false),
},
];

if (!isBulkEditEnabled) {
return (
<div>
<ButtonWithTooltip
tooltip="Bulk Edit"
delayDuration={100}
variant="ghost"
onClick={() => handleBulkEdit(true)}
>
<Pencil size={18} />
</ButtonWithTooltip>
</div>
);
}

return (
<div>
{showDeleteModal && (
<DeleteModal
open={showDeleteModal}
setOpen={setShowDeleteModal}
deleteBookmarks={deleteBookmarks}
/>
)}
<div className="flex">
{actionList.map(({ name, icon: Icon, action }) => (
<ButtonWithTooltip
mdsaban marked this conversation as resolved.
Show resolved Hide resolved
tooltip={name}
delayDuration={100}
variant="ghost"
key={name}
onClick={action}
>
{Icon}
</ButtonWithTooltip>
))}
</div>
</div>
);
}
12 changes: 8 additions & 4 deletions apps/web/components/dashboard/ChangeLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import React from "react";
import { Button } from "@/components/ui/button";
import { ButtonWithTooltip } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
Expand All @@ -20,15 +20,19 @@ const iconMap = {
list: LayoutList,
};

export default function SidebarProfileOptions() {
export default function ChangeLayout() {
const layout = useBookmarkLayout();

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">
<ButtonWithTooltip
tooltip="Change layout"
delayDuration={100}
variant="ghost"
>
{React.createElement(iconMap[layout], { size: 18 })}
</Button>
</ButtonWithTooltip>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-fit">
{Object.keys(iconMap).map((key) => (
Expand Down
22 changes: 22 additions & 0 deletions apps/web/components/dashboard/GlobalActions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"use client";

import BulkBookmarksAction from "@/components/dashboard/BulkBookmarksAction";
import ChangeLayout from "@/components/dashboard/ChangeLayout";
import { cn } from "@/lib/utils";
import bulkActions from "@/store/bulkBookmarksAction";

export default function GlobalActions() {
const { isBulkEditEnabled } = bulkActions();

return (
<div
className={cn(
"flex rounded-md border bg-background transition-all",
isBulkEditEnabled ? "w-[251px]" : "w-[102px]",
mdsaban marked this conversation as resolved.
Show resolved Hide resolved
)}
>
<ChangeLayout />
<BulkBookmarksAction />
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export default function BookmarkActionBar({
href={`/dashboard/preview/${bookmark.id}`}
className={cn(buttonVariants({ variant: "ghost" }), "px-2")}
>
<Maximize2 size="20" />
<Maximize2 size={16} />
</Link>
<BookmarkOptions bookmark={bookmark} />
</div>
Expand Down
Loading
Loading