diff options
| author | Mohamed Bassem <me@mbassem.com> | 2025-06-07 17:03:02 +0000 |
|---|---|---|
| committer | Mohamed Bassem <me@mbassem.com> | 2025-06-07 18:19:06 +0000 |
| commit | 3a0f5fa0bbc907e81a0ddf2f8728a36761499928 (patch) | |
| tree | 603dc4a282f963bd1b9cc9786b8b35ce13499458 | |
| parent | bc65a73872cf0707d2433c289d1f04423325ed95 (diff) | |
| download | karakeep-3a0f5fa0bbc907e81a0ddf2f8728a36761499928.tar.zst | |
feat(ui): Improve the look of the public bookmarks page
| -rw-r--r-- | apps/web/app/public/layout.tsx | 9 | ||||
| -rw-r--r-- | apps/web/app/public/lists/[listId]/page.tsx | 18 | ||||
| -rw-r--r-- | apps/web/components/public/lists/PublicListHeader.tsx | 64 | ||||
| -rw-r--r-- | packages/api/routes/rss.ts | 17 |
4 files changed, 77 insertions, 31 deletions
diff --git a/apps/web/app/public/layout.tsx b/apps/web/app/public/layout.tsx index 4203c44c..b4628e11 100644 --- a/apps/web/app/public/layout.tsx +++ b/apps/web/app/public/layout.tsx @@ -1,16 +1,11 @@ -import KarakeepLogo from "@/components/KarakeepIcon"; - export default function PublicLayout({ children, }: { children: React.ReactNode; }) { return ( - <div className="h-screen flex-col overflow-y-auto bg-muted"> - <header className="sticky left-0 right-0 top-0 z-50 flex h-16 items-center justify-between overflow-x-auto overflow-y-hidden bg-background p-4 shadow"> - <KarakeepLogo height={38} /> - </header> - <main className="container mx-3 mt-3 flex-1">{children}</main> + <div className="h-screen items-center justify-center overflow-y-auto bg-muted"> + <main className="container mt-3">{children}</main> </div> ); } diff --git a/apps/web/app/public/lists/[listId]/page.tsx b/apps/web/app/public/lists/[listId]/page.tsx index cdfc46d0..4a4ce414 100644 --- a/apps/web/app/public/lists/[listId]/page.tsx +++ b/apps/web/app/public/lists/[listId]/page.tsx @@ -3,7 +3,6 @@ import { notFound } from "next/navigation"; import NoBookmarksBanner from "@/components/dashboard/bookmarks/NoBookmarksBanner"; import PublicBookmarkGrid from "@/components/public/lists/PublicBookmarkGrid"; import PublicListHeader from "@/components/public/lists/PublicListHeader"; -import { Separator } from "@/components/ui/separator"; import { api } from "@/server/api/client"; import { TRPCError } from "@trpc/server"; @@ -50,22 +49,15 @@ export default async function PublicListPage({ listId: params.listId, }); return ( - <div className="flex flex-col gap-3"> - <div className="flex items-center gap-2"> - <span className="text-2xl"> - {list.icon} {list.name} - {list.description && ( - <span className="mx-2 text-lg text-gray-400"> - {`(${list.description})`} - </span> - )} - </span> - </div> - <Separator /> + <div className="space-y-3"> <PublicListHeader list={{ id: params.listId, + name: list.name, + description: list.description, + icon: list.icon, numItems: list.numItems, + ownerName: list.ownerName, }} /> {list.numItems > 0 ? ( diff --git a/apps/web/components/public/lists/PublicListHeader.tsx b/apps/web/components/public/lists/PublicListHeader.tsx index 1f016351..acdb845a 100644 --- a/apps/web/components/public/lists/PublicListHeader.tsx +++ b/apps/web/components/public/lists/PublicListHeader.tsx @@ -1,17 +1,71 @@ +import Link from "next/link"; +import KarakeepLogo from "@/components/KarakeepIcon"; +import { buttonVariants } from "@/components/ui/button"; +import { BookmarkIcon, RssIcon } from "lucide-react"; + export default function PublicListHeader({ list, }: { list: { id: string; + name: string; + description: string | null | undefined; + icon: string; + ownerName: string; numItems: number; }; }) { + const rssLink = `/api/v1/rss/lists/${list.id}`; return ( - <div className="flex w-full justify-between"> - <span /> - <p className="text-xs font-light uppercase text-gray-500"> - {list.numItems} bookmarks - </p> + <div className="rounded-lg border bg-gradient-to-br from-purple-50/50 via-purple-100/30 to-purple-200/40 p-6 transition-all duration-300 dark:from-purple-950/20 dark:via-purple-900/15 dark:to-purple-800/20"> + <div className="space-y-4"> + <KarakeepLogo height={38} /> + <div className="flex flex-col items-start justify-between gap-6 md:flex-row md:items-center"> + {/* Header */} + <div className="flex min-w-0 flex-1 items-start gap-3"> + <span className="text-3xl transition-transform duration-200 hover:scale-110"> + {list.icon} + </span> + <div className="min-w-0 flex-1"> + <h1 className="text-3xl font-bold leading-tight text-foreground"> + {list.name} + </h1> + {list.description && list.description.length > 0 && ( + <p className="mt-2 text-lg leading-relaxed text-muted-foreground"> + {list.description} + </p> + )} + </div> + </div> + {/* Created by */} + <div className="flex gap-3 md:justify-end"> + <div className="flex aspect-square size-10 flex-col items-center justify-center rounded-full bg-primary font-medium text-primary-foreground transition-all duration-200 hover:scale-105 hover:shadow-md"> + {list.ownerName[0]?.toUpperCase()} + </div> + <div> + <p className="text-sm text-muted-foreground">Created by</p> + <p className="font-medium text-foreground">{list.ownerName}</p> + </div> + </div> + </div> + {/* Options */} + <div className="flex items-center justify-start gap-1 md:justify-end"> + <div className="flex items-center gap-1 text-xs font-light uppercase text-gray-500 transition-colors duration-200 hover:text-gray-700 dark:hover:text-gray-300"> + <BookmarkIcon + size={12} + className="transition-transform duration-200 hover:scale-110" + /> + <span>{list.numItems} bookmarks</span> + </div> + <Link + href={rssLink} + target="_blank" + className={buttonVariants({ variant: "none", size: "icon" })} + > + <RssIcon className="size-3 text-gray-500" /> + </Link> + </div> + </div> </div> ); } diff --git a/packages/api/routes/rss.ts b/packages/api/routes/rss.ts index 88b943ad..94bfc775 100644 --- a/packages/api/routes/rss.ts +++ b/packages/api/routes/rss.ts @@ -14,7 +14,7 @@ const app = new Hono().get( zValidator( "query", z.object({ - token: z.string().min(1), + token: z.string().min(1).optional(), limit: z.coerce .number() .min(1) @@ -28,11 +28,16 @@ const app = new Hono().get( const searchParams = c.req.valid("query"); const token = searchParams.token; - const res = await List.getPublicListContents(c.var.ctx, listId, token, { - limit: searchParams.limit ?? 20, - order: "desc", - cursor: null, - }); + const res = await List.getPublicListContents( + c.var.ctx, + listId, + token ?? null, + { + limit: searchParams.limit ?? 20, + order: "desc", + cursor: null, + }, + ); const list = res.list; const rssFeed = toRSS( |
