aboutsummaryrefslogtreecommitdiffstats
path: root/web/app/dashboard/bookmarks/components
diff options
context:
space:
mode:
authorMohamedBassem <me@mbassem.com>2024-02-08 02:16:51 +0000
committerMohamedBassem <me@mbassem.com>2024-02-08 02:23:29 +0000
commit2659da517aeec0fe955422dee76f7de292f1a591 (patch)
tree5150d69d2e5b70aea2cad6adefc6e2511d0d29da /web/app/dashboard/bookmarks/components
parent7344f167edae95b2edd984ec1ae0ef5359d1e028 (diff)
downloadkarakeep-2659da517aeec0fe955422dee76f7de292f1a591.tar.zst
[feature] Introduce a sidebar
Diffstat (limited to 'web/app/dashboard/bookmarks/components')
-rw-r--r--web/app/dashboard/bookmarks/components/AddLink.tsx43
-rw-r--r--web/app/dashboard/bookmarks/components/LinkCard.tsx96
-rw-r--r--web/app/dashboard/bookmarks/components/LinksGrid.tsx21
3 files changed, 160 insertions, 0 deletions
diff --git a/web/app/dashboard/bookmarks/components/AddLink.tsx b/web/app/dashboard/bookmarks/components/AddLink.tsx
new file mode 100644
index 00000000..fab4db8b
--- /dev/null
+++ b/web/app/dashboard/bookmarks/components/AddLink.tsx
@@ -0,0 +1,43 @@
+"use client";
+
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import APIClient from "@/lib/api";
+import { Plus } from "lucide-react";
+import { useRouter } from "next/navigation";
+import { useState } from "react";
+
+export default function AddLink() {
+ const router = useRouter();
+ const [link, setLink] = useState("");
+
+ const bookmarkLink = async () => {
+ const [_resp, error] = await APIClient.bookmarkLink(link);
+ if (error) {
+ // TODO: Proper error handling
+ alert(error.message);
+ return;
+ }
+ router.refresh();
+ };
+
+ return (
+ <div className="py-4 container flex w-full items-center space-x-2">
+ <Input
+ type="text"
+ placeholder="Link"
+ value={link}
+ onChange={(val) => setLink(val.target.value)}
+ onKeyUp={async (event) => {
+ if (event.key == "Enter") {
+ bookmarkLink();
+ setLink("");
+ }
+ }}
+ />
+ <Button onClick={bookmarkLink}>
+ <Plus />
+ </Button>
+ </div>
+ );
+}
diff --git a/web/app/dashboard/bookmarks/components/LinkCard.tsx b/web/app/dashboard/bookmarks/components/LinkCard.tsx
new file mode 100644
index 00000000..da59d9da
--- /dev/null
+++ b/web/app/dashboard/bookmarks/components/LinkCard.tsx
@@ -0,0 +1,96 @@
+"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 { ZBookmarkedLink } from "@/lib/types/api/links";
+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 () => {
+ let [_, error] = await APIClient.unbookmarkLink(linkId);
+
+ 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 h-4 w-4" />
+ <span>Delete</span>
+ </DropdownMenuItem>
+ </DropdownMenuContent>
+ </DropdownMenu>
+ );
+}
+
+export default function LinkCard({ link }: { link: ZBookmarkedLink }) {
+ const parsedUrl = new URL(link.url);
+
+ return (
+ <ImageCard
+ className={
+ "bg-gray-50 duration-300 ease-in border border-grey-100 hover:transition-all hover:border-blue-300"
+ }
+ image={link.details?.imageUrl ?? undefined}
+ >
+ <ImageCardTitle>
+ <Link className="line-clamp-3" href={link.url}>
+ {link.details?.title ?? parsedUrl.host}
+ </Link>
+ </ImageCardTitle>
+ <ImageCardBody className="py-2 overflow-clip">
+ {link.tags.map((t) => (
+ <Badge variant="default" className="bg-gray-300 text-gray-500" key={t.id}>
+ #{t.name}
+ </Badge>
+ ))}
+ </ImageCardBody>
+ <ImageCardFooter>
+ <div className="flex justify-between text-gray-500">
+ <div className="my-auto">
+ <Link className="line-clamp-1 hover:text-black" href={link.url}>
+ {parsedUrl.host}
+ </Link>
+ </div>
+ <LinkOptions linkId={link.id} />
+ </div>
+ </ImageCardFooter>
+ </ImageCard>
+ );
+}
diff --git a/web/app/dashboard/bookmarks/components/LinksGrid.tsx b/web/app/dashboard/bookmarks/components/LinksGrid.tsx
new file mode 100644
index 00000000..66f0d766
--- /dev/null
+++ b/web/app/dashboard/bookmarks/components/LinksGrid.tsx
@@ -0,0 +1,21 @@
+import { getServerSession } from "next-auth";
+import { redirect } from "next/navigation";
+import { authOptions } from "@/lib/auth";
+import { getLinks } from "@/lib/services/links";
+import LinkCard from "./LinkCard";
+
+export default async function LinksGrid() {
+ const session = await getServerSession(authOptions);
+ if (!session) {
+ redirect("/");
+ }
+ const links = await getLinks(session.user.id);
+
+ return (
+ <div className="container grid gap-4 grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
+ {links.map((l) => (
+ <LinkCard key={l.id} link={l} />
+ ))}
+ </div>
+ );
+}