From d3d3ba9d4a37ac757b4b8e8e02feae384c95f0e9 Mon Sep 17 00:00:00 2001 From: MohamedBassem Date: Wed, 20 Mar 2024 23:51:28 +0000 Subject: feature(web): Add support for removing items from lists --- apps/web/app/dashboard/lists/[listId]/page.tsx | 27 ++++++++++--------- .../dashboard/bookmarks/BookmarkOptions.tsx | 31 +++++++++++++++++++++- apps/web/lib/hooks/list-context.tsx | 21 +++++++++++++++ packages/trpc/routers/bookmarks.ts | 2 +- packages/trpc/routers/lists.ts | 31 ++++++++++++++++++++-- 5 files changed, 96 insertions(+), 16 deletions(-) create mode 100644 apps/web/lib/hooks/list-context.tsx diff --git a/apps/web/app/dashboard/lists/[listId]/page.tsx b/apps/web/app/dashboard/lists/[listId]/page.tsx index f28d94b1..bac2b5c7 100644 --- a/apps/web/app/dashboard/lists/[listId]/page.tsx +++ b/apps/web/app/dashboard/lists/[listId]/page.tsx @@ -1,6 +1,7 @@ import { notFound, redirect } from "next/navigation"; import Bookmarks from "@/components/dashboard/bookmarks/Bookmarks"; import DeleteListButton from "@/components/dashboard/lists/DeleteListButton"; +import { BookmarkListContextProvider } from "@/lib/hooks/list-context"; import { api } from "@/server/api/client"; import { getServerAuthSession } from "@/server/auth"; import { TRPCError } from "@trpc/server"; @@ -28,17 +29,19 @@ export default async function ListPage({ } return ( - - - {list.icon} {list.name} - - - - } - /> + + + + {list.icon} {list.name} + + + + } + /> + ); } diff --git a/apps/web/components/dashboard/bookmarks/BookmarkOptions.tsx b/apps/web/components/dashboard/bookmarks/BookmarkOptions.tsx index 692d7d78..249946b4 100644 --- a/apps/web/components/dashboard/bookmarks/BookmarkOptions.tsx +++ b/apps/web/components/dashboard/bookmarks/BookmarkOptions.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState } from "react"; +import { useContext, useState } from "react"; import { Button } from "@/components/ui/button"; import { DropdownMenu, @@ -10,11 +10,13 @@ import { } from "@/components/ui/dropdown-menu"; import { useToast } from "@/components/ui/use-toast"; import { useClientConfig } from "@/lib/clientConfig"; +import { BookmarkListContext } from "@/lib/hooks/list-context"; import { api } from "@/lib/trpc"; import { Archive, Link, List, + ListX, MoreHorizontal, Pencil, RotateCw, @@ -42,6 +44,8 @@ export default function BookmarkOptions({ bookmark }: { bookmark: ZBookmark }) { const [isTextEditorOpen, setTextEditorOpen] = useState(false); + const { listId } = useContext(BookmarkListContext); + const invalidateAllBookmarksCache = api.useUtils().bookmarks.getBookmarks.invalidate; @@ -92,6 +96,16 @@ export default function BookmarkOptions({ bookmark }: { bookmark: ZBookmark }) { }, }); + const removeFromListMutator = api.lists.removeFromList.useMutation({ + onSuccess: (_resp, req) => { + invalidateAllBookmarksCache({ listId: req.listId }); + toast({ + description: "The bookmark has been deleted from the list", + }); + }, + onError, + }); + return ( <> {tagModal} @@ -166,6 +180,21 @@ export default function BookmarkOptions({ bookmark }: { bookmark: ZBookmark }) { Add to List + {listId && ( + + removeFromListMutator.mutate({ + listId, + bookmarkId: bookmark.id, + }) + } + > + + Remove from List + + )} + {bookmark.content.type === "link" && ( ({ listId: undefined }); + +export function BookmarkListContextProvider({ + listId, + children, +}: { + listId: string; + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} diff --git a/packages/trpc/routers/bookmarks.ts b/packages/trpc/routers/bookmarks.ts index cd3ab17c..3a49c7fa 100644 --- a/packages/trpc/routers/bookmarks.ts +++ b/packages/trpc/routers/bookmarks.ts @@ -35,7 +35,7 @@ import { } from "../types/bookmarks"; import { ZBookmarkTags } from "../types/tags"; -const ensureBookmarkOwnership = experimental_trpcMiddleware<{ +export const ensureBookmarkOwnership = experimental_trpcMiddleware<{ ctx: Context; input: { bookmarkId: string }; }>().create(async (opts) => { diff --git a/packages/trpc/routers/lists.ts b/packages/trpc/routers/lists.ts index cbce3970..db5bb38e 100644 --- a/packages/trpc/routers/lists.ts +++ b/packages/trpc/routers/lists.ts @@ -7,8 +7,9 @@ import { bookmarkLists, bookmarksInLists } from "@hoarder/db/schema"; import { authedProcedure, Context, router } from "../index"; import { zBookmarkListSchema } from "../types/lists"; +import { ensureBookmarkOwnership } from "./bookmarks"; -const ensureListOwnership = experimental_trpcMiddleware<{ +export const ensureListOwnership = experimental_trpcMiddleware<{ ctx: Context; input: { listId: string }; }>().create(async (opts) => { @@ -106,6 +107,7 @@ export const listsAppRouter = router({ }), ) .use(ensureListOwnership) + .use(ensureBookmarkOwnership) .mutation(async ({ input, ctx }) => { try { await ctx.db.insert(bookmarksInLists).values({ @@ -117,7 +119,7 @@ export const listsAppRouter = router({ if (e.code == "SQLITE_CONSTRAINT_PRIMARYKEY") { throw new TRPCError({ code: "BAD_REQUEST", - message: "Bookmark already in the list", + message: `Bookmark ${input.bookmarkId} is already in the list ${input.listId}`, }); } } @@ -127,6 +129,31 @@ export const listsAppRouter = router({ }); } }), + removeFromList: authedProcedure + .input( + z.object({ + listId: z.string(), + bookmarkId: z.string(), + }), + ) + .use(ensureListOwnership) + .use(ensureBookmarkOwnership) + .mutation(async ({ input, ctx }) => { + const deleted = await ctx.db + .delete(bookmarksInLists) + .where( + and( + eq(bookmarksInLists.listId, input.listId), + eq(bookmarksInLists.bookmarkId, input.bookmarkId), + ), + ); + if (deleted.changes == 0) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: `Bookmark ${input.bookmarkId} is already not in list ${input.listId}`, + }); + } + }), get: authedProcedure .input( z.object({ -- cgit v1.2.3-70-g09d2