From 67729c131c92a2fab6d1422db34aa000c348af07 Mon Sep 17 00:00:00 2001 From: MohamedBassem Date: Sun, 1 Sep 2024 19:15:24 +0000 Subject: feature(web): Manage tags in bulk actions --- .../dashboard/bookmarks/BookmarkTagsEditor.tsx | 48 ++++++++ .../dashboard/bookmarks/BulkTagModal.tsx | 126 +++++++++++++++++++++ .../components/dashboard/bookmarks/TagModal.tsx | 4 +- .../components/dashboard/bookmarks/TagsEditor.tsx | 60 ++++------ 4 files changed, 197 insertions(+), 41 deletions(-) create mode 100644 apps/web/components/dashboard/bookmarks/BookmarkTagsEditor.tsx create mode 100644 apps/web/components/dashboard/bookmarks/BulkTagModal.tsx (limited to 'apps/web/components/dashboard/bookmarks') diff --git a/apps/web/components/dashboard/bookmarks/BookmarkTagsEditor.tsx b/apps/web/components/dashboard/bookmarks/BookmarkTagsEditor.tsx new file mode 100644 index 00000000..d6d60d22 --- /dev/null +++ b/apps/web/components/dashboard/bookmarks/BookmarkTagsEditor.tsx @@ -0,0 +1,48 @@ +import { toast } from "@/components/ui/use-toast"; + +import type { ZBookmark } from "@hoarder/shared/types/bookmarks"; +import { useUpdateBookmarkTags } from "@hoarder/shared-react/hooks/bookmarks"; + +import { TagsEditor } from "./TagsEditor"; + +export function BookmarkTagsEditor({ bookmark }: { bookmark: ZBookmark }) { + const { mutate } = useUpdateBookmarkTags({ + onSuccess: () => { + toast({ + description: "Tags has been updated!", + }); + }, + onError: () => { + toast({ + variant: "destructive", + title: "Something went wrong", + description: "There was a problem with your request.", + }); + }, + }); + + return ( + { + mutate({ + bookmarkId: bookmark.id, + attach: [ + { + tagName, + tagId, + }, + ], + detach: [], + }); + }} + onDetach={({ tagId }) => { + mutate({ + bookmarkId: bookmark.id, + attach: [], + detach: [{ tagId }], + }); + }} + /> + ); +} diff --git a/apps/web/components/dashboard/bookmarks/BulkTagModal.tsx b/apps/web/components/dashboard/bookmarks/BulkTagModal.tsx new file mode 100644 index 00000000..3c8e75e7 --- /dev/null +++ b/apps/web/components/dashboard/bookmarks/BulkTagModal.tsx @@ -0,0 +1,126 @@ +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogClose, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { toast } from "@/components/ui/use-toast"; + +import { useUpdateBookmarkTags } from "@hoarder/shared-react/hooks/bookmarks"; +import { api } from "@hoarder/shared-react/trpc"; +import { ZBookmark } from "@hoarder/shared/types/bookmarks"; + +import { TagsEditor } from "./TagsEditor"; + +export default function BulkTagModal({ + bookmarkIds, + open, + setOpen, +}: { + bookmarkIds: string[]; + open: boolean; + setOpen: (open: boolean) => void; +}) { + const results = api.useQueries((t) => + bookmarkIds.map((id) => t.bookmarks.getBookmark({ bookmarkId: id })), + ); + + const bookmarks = results + .map((r) => r.data) + .filter((b): b is ZBookmark => !!b); + + const { mutateAsync } = useUpdateBookmarkTags({ + onError: (err) => { + if (err.data?.code == "BAD_REQUEST") { + if (err.data.zodError) { + toast({ + variant: "destructive", + description: Object.values(err.data.zodError.fieldErrors) + .flat() + .join("\n"), + }); + } else { + toast({ + variant: "destructive", + description: err.message, + }); + } + } else { + toast({ + variant: "destructive", + title: "Something went wrong", + }); + } + }, + }); + + const onAttach = async (tag: { tagName: string; tagId?: string }) => { + const results = await Promise.allSettled( + bookmarkIds.map((id) => + mutateAsync({ + bookmarkId: id, + attach: [tag], + detach: [], + }), + ), + ); + const successes = results.filter((r) => r.status == "fulfilled").length; + toast({ + description: `Tag "${tag.tagName}" has been added to ${successes} bookmarks!`, + }); + }; + + const onDetach = async ({ + tagId, + tagName, + }: { + tagId: string; + tagName: string; + }) => { + const results = await Promise.allSettled( + bookmarkIds.map((id) => + mutateAsync({ + bookmarkId: id, + attach: [], + detach: [{ tagId }], + }), + ), + ); + const successes = results.filter((r) => r.status == "fulfilled").length; + toast({ + description: `Tag "${tagName}" has been removed from ${successes} bookmarks!`, + }); + }; + + // Get all the tags that are attached to all the bookmarks + let tags = bookmarks + .flatMap((b) => b.tags) + .filter((tag) => + bookmarks.every((b) => b.tags.some((t) => tag.id == t.id)), + ); + // Filter duplicates + tags = tags.filter( + (tag, index, self) => index === self.findIndex((t) => t.id == tag.id), + ); + + return ( + + + + Edit Tags of {bookmarks.length} Bookmarks + + + + + + + + + + ); +} diff --git a/apps/web/components/dashboard/bookmarks/TagModal.tsx b/apps/web/components/dashboard/bookmarks/TagModal.tsx index 00cc40fc..c2f081be 100644 --- a/apps/web/components/dashboard/bookmarks/TagModal.tsx +++ b/apps/web/components/dashboard/bookmarks/TagModal.tsx @@ -11,7 +11,7 @@ import { import type { ZBookmark } from "@hoarder/shared/types/bookmarks"; -import { TagsEditor } from "./TagsEditor"; +import { BookmarkTagsEditor } from "./BookmarkTagsEditor"; export default function TagModal({ bookmark, @@ -28,7 +28,7 @@ export default function TagModal({ Edit Tags - +