diff options
| author | Mohamed Bassem <me@mbassem.com> | 2025-06-01 20:46:41 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-06-01 20:46:41 +0100 |
| commit | ea1d0023bfee55358ebb1a96f3d06e783a219c0d (patch) | |
| tree | 5bddd451728cb7dd377574a9ea1ea591bca069c4 /apps/web/app/public | |
| parent | 3afe1e21df6dcc0483e74e0db02d9d82af32ecea (diff) | |
| download | karakeep-ea1d0023bfee55358ebb1a96f3d06e783a219c0d.tar.zst | |
feat: Add support for public lists (#1511)
* WIP: public lists
* Drop viewing modes
* Add the public endpoint for assets
* regen the openapi spec
* proper handling for different asset types
* Add num bookmarks and a no bookmark banner
* Correctly set page title
* Add a not-found page
* merge the RSS and public list endpoints
* Add e2e tests for the public endpoints
* Redesign the share list modal
* Make NEXTAUTH_SECRET not required
* propery render text bookmarks
* rebase migration
* fix public token tests
* Add more tests
Diffstat (limited to 'apps/web/app/public')
| -rw-r--r-- | apps/web/app/public/layout.tsx | 16 | ||||
| -rw-r--r-- | apps/web/app/public/lists/[listId]/not-found.tsx | 18 | ||||
| -rw-r--r-- | apps/web/app/public/lists/[listId]/page.tsx | 84 |
3 files changed, 118 insertions, 0 deletions
diff --git a/apps/web/app/public/layout.tsx b/apps/web/app/public/layout.tsx new file mode 100644 index 00000000..4203c44c --- /dev/null +++ b/apps/web/app/public/layout.tsx @@ -0,0 +1,16 @@ +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> + ); +} diff --git a/apps/web/app/public/lists/[listId]/not-found.tsx b/apps/web/app/public/lists/[listId]/not-found.tsx new file mode 100644 index 00000000..a6fd71dc --- /dev/null +++ b/apps/web/app/public/lists/[listId]/not-found.tsx @@ -0,0 +1,18 @@ +import { X } from "lucide-react"; + +export default function PublicListPageNotFound() { + return ( + <div className="mx-auto flex max-w-md flex-1 flex-col items-center justify-center px-4 py-16 text-center"> + <div className="mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-slate-100 dark:bg-slate-700"> + <X className="h-12 w-12 text-gray-300" strokeWidth={1.5} /> + </div> + <h1 className="mb-3 text-2xl font-semibold text-gray-800"> + List not found + </h1> + <p className="text-center text-gray-500"> + The list you're looking for doesn't exist or may have been + removed. + </p> + </div> + ); +} diff --git a/apps/web/app/public/lists/[listId]/page.tsx b/apps/web/app/public/lists/[listId]/page.tsx new file mode 100644 index 00000000..c0495b9f --- /dev/null +++ b/apps/web/app/public/lists/[listId]/page.tsx @@ -0,0 +1,84 @@ +import type { Metadata } from "next"; +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"; + +export async function generateMetadata({ + params, +}: { + params: { listId: string }; +}): Promise<Metadata> { + // TODO: Don't load the entire list, just create an endpoint to get the list name + try { + const resp = await api.publicBookmarks.getPublicBookmarksInList({ + listId: params.listId, + }); + return { + title: `${resp.list.name} - Karakeep`, + }; + } catch (e) { + if (e instanceof TRPCError && e.code === "NOT_FOUND") { + notFound(); + } + } + return { + title: "Karakeep", + }; +} + +export default async function PublicListPage({ + params, +}: { + params: { listId: string }; +}) { + try { + const { list, bookmarks, nextCursor } = + await api.publicBookmarks.getPublicBookmarksInList({ + 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 /> + <PublicListHeader + list={{ + id: params.listId, + numItems: list.numItems, + }} + /> + {list.numItems > 0 ? ( + <PublicBookmarkGrid + list={{ + id: params.listId, + name: list.name, + description: list.description, + icon: list.icon, + numItems: list.numItems, + }} + bookmarks={bookmarks} + nextCursor={nextCursor} + /> + ) : ( + <NoBookmarksBanner /> + )} + </div> + ); + } catch (e) { + if (e instanceof TRPCError && e.code === "NOT_FOUND") { + notFound(); + } + } +} |
