From 7400914396eea0c9a1fb7bc59e022babc2186f42 Mon Sep 17 00:00:00 2001 From: MohamedBassem Date: Fri, 9 Feb 2024 16:47:17 +0000 Subject: [feature] Add the ability to favourite and archive bookmarks --- .../web/app/api/v1/bookmarks/[bookmarkId]/route.ts | 41 ++++++++++- .../bookmarks/components/BookmarkOptions.tsx | 84 ++++++++++++++++++++++ .../dashboard/bookmarks/components/LinkCard.tsx | 55 +------------- packages/web/app/dashboard/bookmarks/page.tsx | 2 +- packages/web/lib/api.ts | 9 +++ packages/web/lib/services/bookmarks.ts | 17 +++++ packages/web/lib/types/api/bookmarks.ts | 12 +++- 7 files changed, 162 insertions(+), 58 deletions(-) create mode 100644 packages/web/app/dashboard/bookmarks/components/BookmarkOptions.tsx (limited to 'packages/web') diff --git a/packages/web/app/api/v1/bookmarks/[bookmarkId]/route.ts b/packages/web/app/api/v1/bookmarks/[bookmarkId]/route.ts index 6adcf771..0963cf94 100644 --- a/packages/web/app/api/v1/bookmarks/[bookmarkId]/route.ts +++ b/packages/web/app/api/v1/bookmarks/[bookmarkId]/route.ts @@ -1,9 +1,44 @@ import { authOptions } from "@/lib/auth"; -import { deleteBookmark } from "@/lib/services/bookmarks"; +import { deleteBookmark, updateBookmark } from "@/lib/services/bookmarks"; +import { ZBookmark, zUpdateBookmarksRequestSchema } from "@/lib/types/api/bookmarks"; import { Prisma } from "@remember/db"; import { getServerSession } from "next-auth"; -import { NextRequest } from "next/server"; +import { NextRequest, NextResponse } from "next/server"; + +export async function PATCH( + request: NextRequest, + { params }: { params: { bookmarkId: string } }, +) { + const session = await getServerSession(authOptions); + if (!session) { + return new Response(null, { status: 401 }); + } + + const updateJson = await request.json(); + const update = zUpdateBookmarksRequestSchema.safeParse(updateJson); + if (!update.success) { + return new Response(null, { status: 400 }); + } + + try { + const bookmark: ZBookmark = await updateBookmark( + params.bookmarkId, + session.user.id, + update.data, + ); + return NextResponse.json(bookmark); + } catch (e: unknown) { + if ( + e instanceof Prisma.PrismaClientKnownRequestError && + e.code === "P2025" // RecordNotFound + ) { + return new Response(null, { status: 404 }); + } else { + throw e; + } + } +} export async function DELETE( _request: NextRequest, @@ -28,5 +63,5 @@ export async function DELETE( } } - return new Response(null, { status: 201 }); + return new Response(null, { status: 204 }); } diff --git a/packages/web/app/dashboard/bookmarks/components/BookmarkOptions.tsx b/packages/web/app/dashboard/bookmarks/components/BookmarkOptions.tsx new file mode 100644 index 00000000..15ce64c7 --- /dev/null +++ b/packages/web/app/dashboard/bookmarks/components/BookmarkOptions.tsx @@ -0,0 +1,84 @@ +"use client"; + +import { useToast } from "@/components/ui/use-toast"; +import APIClient from "@/lib/api"; +import { ZBookmark, ZUpdateBookmarksRequest } from "@/lib/types/api/bookmarks"; +import { useRouter } from "next/navigation"; +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { Archive, MoreHorizontal, Star, Trash2 } from "lucide-react"; + +export default function BookmarkOptions({ bookmark }: { bookmark: ZBookmark }) { + const { toast } = useToast(); + const router = useRouter(); + const linkId = bookmark.id; + + const unbookmarkLink = async () => { + const [_, error] = await APIClient.deleteBookmark(linkId); + + if (error) { + toast({ + variant: "destructive", + title: "Something went wrong", + description: "There was a problem with your request.", + }); + } else { + toast({ + description: "The bookmark has been deleted!", + }); + } + + router.refresh(); + }; + + const updateBookmark = async (req: ZUpdateBookmarksRequest) => { + const [_, error] = await APIClient.updateBookmark(linkId, req); + + if (error) { + toast({ + variant: "destructive", + title: "Something went wrong", + description: "There was a problem with your request.", + }); + } else { + toast({ + description: "The bookmark has been updated!", + }); + } + + router.refresh(); + }; + + return ( + + + + + + updateBookmark({ favourited: !bookmark.favourited })} + > + + {bookmark.favourited ? "Un-favourite" : "Favourite"} + + updateBookmark({ archived: !bookmark.archived })} + > + + {bookmark.archived ? "Un-archive" : "Archive"} + + + + Delete + + + + ); +} diff --git a/packages/web/app/dashboard/bookmarks/components/LinkCard.tsx b/packages/web/app/dashboard/bookmarks/components/LinkCard.tsx index aeef6bae..039cb156 100644 --- a/packages/web/app/dashboard/bookmarks/components/LinkCard.tsx +++ b/packages/web/app/dashboard/bookmarks/components/LinkCard.tsx @@ -1,63 +1,14 @@ -"use client"; - import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; import { ImageCard, ImageCardBody, ImageCardFooter, ImageCardTitle, } from "@/components/ui/imageCard"; -import { useToast } from "@/components/ui/use-toast"; -import APIClient from "@/lib/api"; import { ZBookmark } from "@/lib/types/api/bookmarks"; -import { MoreHorizontal, Trash2 } from "lucide-react"; import Link from "next/link"; -import { useRouter } from "next/navigation"; - -export function LinkOptions({ linkId }: { linkId: string }) { - const { toast } = useToast(); - const router = useRouter(); - - const unbookmarkLink = async () => { - const [_, error] = await APIClient.deleteBookmark(linkId); +import BookmarkOptions from "./BookmarkOptions"; - if (error) { - toast({ - variant: "destructive", - title: "Something went wrong", - description: "There was a problem with your request.", - }); - } else { - toast({ - description: "The link has been deleted!", - }); - } - - router.refresh(); - }; - return ( - - - - - - - - Delete - - - - ); -} export default function LinkCard({ bookmark }: { bookmark: ZBookmark }) { const link = bookmark.content; @@ -71,7 +22,7 @@ export default function LinkCard({ bookmark }: { bookmark: ZBookmark }) { image={link?.imageUrl ?? undefined} > - + {link?.title ?? parsedUrl.host} @@ -93,7 +44,7 @@ export default function LinkCard({ bookmark }: { bookmark: ZBookmark }) { {parsedUrl.host} - + diff --git a/packages/web/app/dashboard/bookmarks/page.tsx b/packages/web/app/dashboard/bookmarks/page.tsx index 3d125fad..517dc184 100644 --- a/packages/web/app/dashboard/bookmarks/page.tsx +++ b/packages/web/app/dashboard/bookmarks/page.tsx @@ -1,5 +1,5 @@ import Bookmarks from "./components/Bookmarks"; export default async function BookmarksPage() { - return ; + return ; } diff --git a/packages/web/lib/api.ts b/packages/web/lib/api.ts index f7942310..3978dcb6 100644 --- a/packages/web/lib/api.ts +++ b/packages/web/lib/api.ts @@ -3,6 +3,8 @@ import { ZodTypeAny, z } from "zod"; import { ZNewBookmarkRequest, + ZUpdateBookmarksRequest, + zBookmarkSchema, zGetBookmarksResponseSchema, } from "./types/api/bookmarks"; @@ -79,4 +81,11 @@ export default class APIClient { method: "DELETE", }); } + + static async updateBookmark(id: string, update: ZUpdateBookmarksRequest) { + return await doRequest(`/bookmarks/${id}`, zBookmarkSchema, { + method: "PATCH", + body: JSON.stringify(update), + }); + } } diff --git a/packages/web/lib/services/bookmarks.ts b/packages/web/lib/services/bookmarks.ts index 3231e4d3..7cb473f9 100644 --- a/packages/web/lib/services/bookmarks.ts +++ b/packages/web/lib/services/bookmarks.ts @@ -4,6 +4,7 @@ import { ZBookmark, ZBookmarkContent, ZGetBookmarksRequest, + ZUpdateBookmarksRequest, } from "@/lib/types/api/bookmarks"; const defaultBookmarkFields = { @@ -53,6 +54,22 @@ function toZodSchema( }; } +export async function updateBookmark( + bookmarkId: string, + userId: string, + req: ZUpdateBookmarksRequest, +) { + const bookmark = await prisma.bookmark.update({ + where: { + id: bookmarkId, + userId, + }, + data: req, + select: defaultBookmarkFields, + }); + return toZodSchema(bookmark); +} + export async function deleteBookmark(bookmarkId: string, userId: string) { await prisma.bookmark.delete({ where: { diff --git a/packages/web/lib/types/api/bookmarks.ts b/packages/web/lib/types/api/bookmarks.ts index c4aec646..9e602d73 100644 --- a/packages/web/lib/types/api/bookmarks.ts +++ b/packages/web/lib/types/api/bookmarks.ts @@ -33,8 +33,8 @@ export type ZNewBookmarkRequest = z.infer; // GET /v1/bookmarks export const zGetBookmarksRequestSchema = z.object({ - archived: z.boolean().default(false), - favourited: z.boolean().default(false), + archived: z.boolean().optional(), + favourited: z.boolean().optional(), }); export type ZGetBookmarksRequest = z.infer; @@ -42,3 +42,11 @@ export const zGetBookmarksResponseSchema = z.object({ bookmarks: z.array(zBookmarkSchema), }); export type ZGetBookmarksResponse = z.infer; + + +// PATCH /v1/bookmarks/[bookmarkId] +export const zUpdateBookmarksRequestSchema = z.object({ + archived: z.boolean().optional(), + favourited: z.boolean().optional(), +}); +export type ZUpdateBookmarksRequest = z.infer; -- cgit v1.2.3-70-g09d2