diff options
| author | MohamedBassem <me@mbassem.com> | 2024-03-31 16:34:52 +0100 |
|---|---|---|
| committer | MohamedBassem <me@mbassem.com> | 2024-03-31 19:27:25 +0100 |
| commit | dc6e0eab70421749faaa9a802d66c1901f3e9b50 (patch) | |
| tree | d0f7c5623fc1ce1c11c199f794009c613d5408eb | |
| parent | c0e2bdc01366f4a8878ffb373527d786e163a19b (diff) | |
| download | karakeep-dc6e0eab70421749faaa9a802d66c1901f3e9b50.tar.zst | |
feature: Add support deleting tags
| -rw-r--r-- | apps/web/app/dashboard/lists/[listId]/page.tsx | 8 | ||||
| -rw-r--r-- | apps/web/app/dashboard/tags/[tagName]/page.tsx | 15 | ||||
| -rw-r--r-- | apps/web/components/dashboard/tags/DeleteTagButton.tsx | 59 | ||||
| -rw-r--r-- | packages/trpc/routers/tags.ts | 26 |
4 files changed, 94 insertions, 14 deletions
diff --git a/apps/web/app/dashboard/lists/[listId]/page.tsx b/apps/web/app/dashboard/lists/[listId]/page.tsx index e27e1841..4d7df133 100644 --- a/apps/web/app/dashboard/lists/[listId]/page.tsx +++ b/apps/web/app/dashboard/lists/[listId]/page.tsx @@ -1,9 +1,8 @@ -import { notFound, redirect } from "next/navigation"; +import { notFound } 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"; export default async function ListPage({ @@ -11,11 +10,6 @@ export default async function ListPage({ }: { params: { listId: string }; }) { - const session = await getServerAuthSession(); - if (!session) { - redirect("/"); - } - let list; try { list = await api.lists.get({ listId: params.listId }); diff --git a/apps/web/app/dashboard/tags/[tagName]/page.tsx b/apps/web/app/dashboard/tags/[tagName]/page.tsx index 0e9e722e..3705a6d1 100644 --- a/apps/web/app/dashboard/tags/[tagName]/page.tsx +++ b/apps/web/app/dashboard/tags/[tagName]/page.tsx @@ -1,7 +1,7 @@ -import { notFound, redirect } from "next/navigation"; +import { notFound } from "next/navigation"; import Bookmarks from "@/components/dashboard/bookmarks/Bookmarks"; +import DeleteTagButton from "@/components/dashboard/tags/DeleteTagButton"; import { api } from "@/server/api/client"; -import { getServerAuthSession } from "@/server/auth"; import { TRPCError } from "@trpc/server"; export default async function TagPage({ @@ -9,10 +9,6 @@ export default async function TagPage({ }: { params: { tagName: string }; }) { - const session = await getServerAuthSession(); - if (!session) { - redirect("/"); - } const tagName = decodeURIComponent(params.tagName); let tag; @@ -29,7 +25,12 @@ export default async function TagPage({ return ( <Bookmarks - header={<p className="text-2xl">{tagName}</p>} + header={ + <div className="flex justify-between"> + <span className="text-2xl">{tagName}</span> + <DeleteTagButton tagName={tag.name} tagId={tag.id} /> + </div> + } query={{ archived: false, tagId: tag.id }} /> ); diff --git a/apps/web/components/dashboard/tags/DeleteTagButton.tsx b/apps/web/components/dashboard/tags/DeleteTagButton.tsx new file mode 100644 index 00000000..4cff1680 --- /dev/null +++ b/apps/web/components/dashboard/tags/DeleteTagButton.tsx @@ -0,0 +1,59 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { ActionButton } from "@/components/ui/action-button"; +import ActionConfirmingDialog from "@/components/ui/action-confirming-dialog"; +import { Button } from "@/components/ui/button"; +import { toast } from "@/components/ui/use-toast"; +import { api } from "@/lib/trpc"; +import { Trash2 } from "lucide-react"; + +export default function DeleteTagButton({ + tagName, + tagId, +}: { + tagName: string; + tagId: string; +}) { + const router = useRouter(); + + const apiUtils = api.useUtils(); + + const { mutate: deleteTag, isPending } = api.tags.delete.useMutation({ + onSuccess: () => { + apiUtils.tags.list.invalidate(); + apiUtils.bookmarks.getBookmark.invalidate(); + toast({ + description: `Tag "${tagName}" has been deleted!`, + }); + router.push("/"); + }, + onError: () => { + toast({ + variant: "destructive", + description: `Something went wrong`, + }); + }, + }); + return ( + <ActionConfirmingDialog + title={`Delete ${tagName}?`} + description={`Are you sure you want to delete the tag "${tagName}"?`} + actionButton={() => ( + <ActionButton + type="button" + variant="destructive" + loading={isPending} + onClick={() => deleteTag({ tagId: tagId })} + > + Delete + </ActionButton> + )} + > + <Button className="mt-auto flex gap-2" variant="destructiveOutline"> + <Trash2 className="size-5" /> + <span className="hidden md:block">Delete Tag</span> + </Button> + </ActionConfirmingDialog> + ); +} diff --git a/packages/trpc/routers/tags.ts b/packages/trpc/routers/tags.ts index 211c3633..f6ce752d 100644 --- a/packages/trpc/routers/tags.ts +++ b/packages/trpc/routers/tags.ts @@ -104,6 +104,32 @@ export const tagsAppRouter = router({ bookmarks: res.flatMap((t) => (t.bookmarkId ? [t.bookmarkId] : [])), }; }), + delete: authedProcedure + .input( + z + .object({ + tagId: z.string(), + }) + .or( + z.object({ + tagName: z.string(), + }), + ), + ) + .use(ensureTagOwnership) + .mutation(async ({ input, ctx }) => { + const res = await ctx.db + .delete(bookmarkTags) + .where( + and( + conditionFromInput(input, ctx.user.id), + eq(bookmarkTags.userId, ctx.user.id), + ), + ); + if (res.changes == 0) { + throw new TRPCError({ code: "NOT_FOUND" }); + } + }), list: authedProcedure .output( z.object({ |
