diff options
| author | MohamedBassem <me@mbassem.com> | 2024-09-01 17:44:09 +0000 |
|---|---|---|
| committer | MohamedBassem <me@mbassem.com> | 2024-09-01 17:44:27 +0000 |
| commit | ddc7e5dbfc31b3e5b189c61883e1cc737fefd2ee (patch) | |
| tree | bba661440260b9615ebdf7b00eb2da8a537ce317 /apps/web | |
| parent | dbdbd4902796a1cb6a3a382ccbbf8338954e7431 (diff) | |
| download | karakeep-ddc7e5dbfc31b3e5b189c61883e1cc737fefd2ee.tar.zst | |
feature(web): Allow adding to lists in bulk actions. #368
Diffstat (limited to 'apps/web')
| -rw-r--r-- | apps/web/components/dashboard/BulkBookmarksAction.tsx | 16 | ||||
| -rw-r--r-- | apps/web/components/dashboard/bookmarks/BulkManageListsModal.tsx | 138 |
2 files changed, 153 insertions, 1 deletions
diff --git a/apps/web/components/dashboard/BulkBookmarksAction.tsx b/apps/web/components/dashboard/BulkBookmarksAction.tsx index bed1bda8..5dd31fa5 100644 --- a/apps/web/components/dashboard/BulkBookmarksAction.tsx +++ b/apps/web/components/dashboard/BulkBookmarksAction.tsx @@ -8,13 +8,14 @@ import { 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 { List, Pencil, Trash2, X } from "lucide-react"; import { useDeleteBookmark, useUpdateBookmark, } from "@hoarder/shared-react/hooks/bookmarks"; +import BulkManageListsModal from "./bookmarks/BulkManageListsModal"; import { ArchivedActionIcon, FavouritedActionIcon } from "./bookmarks/icons"; export default function BulkBookmarksAction() { @@ -24,6 +25,7 @@ export default function BulkBookmarksAction() { ); const { toast } = useToast(); const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); + const [manageListsModal, setManageListsModalOpen] = useState(false); useEffect(() => { setIsBulkEditEnabled(false); // turn off toggle + clear selected bookmarks on mount @@ -96,6 +98,13 @@ export default function BulkBookmarksAction() { const actionList = [ { + name: "Add to List", + icon: <List size={18} />, + action: () => setManageListsModalOpen(true), + isPending: false, + hidden: !isBulkEditEnabled, + }, + { name: alreadyFavourited ? "Unfavourite" : "Favourite", icon: <FavouritedActionIcon favourited={!!alreadyFavourited} size={18} />, action: () => updateBookmarks({ favourited: !alreadyFavourited }), @@ -149,6 +158,11 @@ export default function BulkBookmarksAction() { </ActionButton> )} /> + <BulkManageListsModal + bookmarkIds={selectedBookmarks.map((b) => b.id)} + open={manageListsModal} + setOpen={setManageListsModalOpen} + /> <div className="flex"> {actionList.map( ({ name, icon: Icon, action, isPending, hidden, alwaysEnable }) => ( diff --git a/apps/web/components/dashboard/bookmarks/BulkManageListsModal.tsx b/apps/web/components/dashboard/bookmarks/BulkManageListsModal.tsx new file mode 100644 index 00000000..9c1f05d2 --- /dev/null +++ b/apps/web/components/dashboard/bookmarks/BulkManageListsModal.tsx @@ -0,0 +1,138 @@ +import { ActionButton } from "@/components/ui/action-button"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogClose, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { + Form, + FormControl, + FormField, + FormItem, + FormMessage, +} from "@/components/ui/form"; +import { toast } from "@/components/ui/use-toast"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; + +import { useAddBookmarkToList } from "@hoarder/shared-react/hooks/lists"; + +import { BookmarkListSelector } from "../lists/BookmarkListSelector"; + +export default function BulkManageListsModal({ + bookmarkIds, + open, + setOpen, +}: { + bookmarkIds: string[]; + open: boolean; + setOpen: (open: boolean) => void; +}) { + const formSchema = z.object({ + listId: z.string({ + required_error: "Please select a list", + }), + }); + const form = useForm<z.infer<typeof formSchema>>({ + resolver: zodResolver(formSchema), + defaultValues: { + listId: undefined, + }, + }); + + const { mutateAsync: addToList, isPending: isAddingToListPending } = + useAddBookmarkToList({ + onSettled: () => { + form.resetField("listId"); + }, + onError: (e) => { + if (e.data?.code == "BAD_REQUEST") { + toast({ + variant: "destructive", + description: e.message, + }); + } else { + toast({ + variant: "destructive", + title: "Something went wrong", + }); + } + }, + }); + + const onSubmit = async (value: z.infer<typeof formSchema>) => { + const results = await Promise.allSettled( + bookmarkIds.map((bookmarkId) => + addToList({ + bookmarkId, + listId: value.listId, + }), + ), + ); + + const successes = results.filter((r) => r.status == "fulfilled").length; + if (successes > 0) { + toast({ + description: `${successes} bookmarks have been added to the list!`, + }); + } + + setOpen(false); + }; + + return ( + <Dialog open={open} onOpenChange={setOpen}> + <DialogContent> + <Form {...form}> + <form + className="flex w-full flex-col gap-4" + onSubmit={form.handleSubmit(onSubmit)} + > + <DialogHeader> + <DialogTitle> + Add {bookmarkIds.length} bookmarks to List + </DialogTitle> + </DialogHeader> + + <FormField + control={form.control} + name="listId" + render={({ field }) => { + return ( + <FormItem> + <FormControl> + <BookmarkListSelector + value={field.value} + onChange={field.onChange} + /> + </FormControl> + <FormMessage /> + </FormItem> + ); + }} + /> + <DialogFooter className="sm:justify-end"> + <DialogClose asChild> + <Button type="button" variant="secondary"> + Close + </Button> + </DialogClose> + <ActionButton + type="submit" + loading={isAddingToListPending} + disabled={isAddingToListPending} + > + Add + </ActionButton> + </DialogFooter> + </form> + </Form> + </DialogContent> + </Dialog> + ); +} |
