aboutsummaryrefslogtreecommitdiffstats
path: root/packages/web
diff options
context:
space:
mode:
Diffstat (limited to 'packages/web')
-rw-r--r--packages/web/app/api/v1/bookmarks/[bookmarkId]/route.ts41
-rw-r--r--packages/web/app/dashboard/bookmarks/components/BookmarkOptions.tsx84
-rw-r--r--packages/web/app/dashboard/bookmarks/components/LinkCard.tsx55
-rw-r--r--packages/web/app/dashboard/bookmarks/page.tsx2
-rw-r--r--packages/web/lib/api.ts9
-rw-r--r--packages/web/lib/services/bookmarks.ts17
-rw-r--r--packages/web/lib/types/api/bookmarks.ts12
7 files changed, 162 insertions, 58 deletions
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 (
+ <DropdownMenu>
+ <DropdownMenuTrigger asChild>
+ <Button variant="ghost">
+ <MoreHorizontal />
+ </Button>
+ </DropdownMenuTrigger>
+ <DropdownMenuContent className="w-fit">
+ <DropdownMenuItem
+ onClick={() => updateBookmark({ favourited: !bookmark.favourited })}
+ >
+ <Star className="mr-2 size-4" />
+ <span>{bookmark.favourited ? "Un-favourite" : "Favourite"}</span>
+ </DropdownMenuItem>
+ <DropdownMenuItem
+ onClick={() => updateBookmark({ archived: !bookmark.archived })}
+ >
+ <Archive className="mr-2 size-4" />
+ <span>{bookmark.archived ? "Un-archive" : "Archive"}</span>
+ </DropdownMenuItem>
+ <DropdownMenuItem className="text-destructive" onClick={unbookmarkLink}>
+ <Trash2 className="mr-2 size-4" />
+ <span>Delete</span>
+ </DropdownMenuItem>
+ </DropdownMenuContent>
+ </DropdownMenu>
+ );
+}
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 (
- <DropdownMenu>
- <DropdownMenuTrigger asChild>
- <Button variant="ghost">
- <MoreHorizontal />
- </Button>
- </DropdownMenuTrigger>
- <DropdownMenuContent className="w-fit">
- <DropdownMenuItem className="text-destructive" onClick={unbookmarkLink}>
- <Trash2 className="mr-2 size-4" />
- <span>Delete</span>
- </DropdownMenuItem>
- </DropdownMenuContent>
- </DropdownMenu>
- );
-}
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}
>
<ImageCardTitle>
- <Link className="line-clamp-3" href={link.url}>
+ <Link className="line-clamp-2" href={link.url}>
{link?.title ?? parsedUrl.host}
</Link>
</ImageCardTitle>
@@ -93,7 +44,7 @@ export default function LinkCard({ bookmark }: { bookmark: ZBookmark }) {
{parsedUrl.host}
</Link>
</div>
- <LinkOptions linkId={bookmark.id} />
+ <BookmarkOptions bookmark={bookmark} />
</div>
</ImageCardFooter>
</ImageCard>
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 <Bookmarks title="Bookmarks" archived={false} favourited={false} />;
+ return <Bookmarks title="Bookmarks" archived={false} />;
}
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<typeof zNewBookmarkRequestSchema>;
// 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<typeof zGetBookmarksRequestSchema>;
@@ -42,3 +42,11 @@ export const zGetBookmarksResponseSchema = z.object({
bookmarks: z.array(zBookmarkSchema),
});
export type ZGetBookmarksResponse = z.infer<typeof zGetBookmarksResponseSchema>;
+
+
+// PATCH /v1/bookmarks/[bookmarkId]
+export const zUpdateBookmarksRequestSchema = z.object({
+ archived: z.boolean().optional(),
+ favourited: z.boolean().optional(),
+});
+export type ZUpdateBookmarksRequest = z.infer<typeof zUpdateBookmarksRequestSchema>;