Skip to content

Commit 5637c0e

Browse files
committed
feat: enhance bookmarks functionality with article author details and improved pagination
1 parent 5abc27b commit 5637c0e

File tree

3 files changed

+104
-114
lines changed

3 files changed

+104
-114
lines changed

src/app/dashboard/bookmarks/page.tsx

Lines changed: 92 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
11
"use client";
22

3-
import { useInfiniteQuery } from "@tanstack/react-query";
4-
import { myBookmarks } from "@/backend/services/bookmark.action";
5-
import ArticleCard from "@/components/ArticleCard";
3+
import {
4+
myBookmarks,
5+
toggleResourceBookmark,
6+
} from "@/backend/services/bookmark.action";
7+
import { useAppConfirm } from "@/components/app-confirm";
8+
import { Button } from "@/components/ui/button";
69
import VisibilitySensor from "@/components/VisibilitySensor";
10+
import { useTranslation } from "@/i18n/use-translation";
11+
import { formattedTime } from "@/lib/utils";
12+
import { ChatBubbleIcon } from "@radix-ui/react-icons";
13+
import { useInfiniteQuery } from "@tanstack/react-query";
14+
import { RemoveFormatting, Trash } from "lucide-react";
15+
import Link from "next/link";
16+
import { useMemo } from "react";
717

818
interface BookmarkMeta {
919
totalCount: number;
@@ -18,117 +28,95 @@ interface BookmarkData {
1828
}
1929

2030
const BookmarksPage = () => {
21-
const {
22-
data,
23-
fetchNextPage,
24-
hasNextPage,
25-
isFetchingNextPage,
26-
isLoading,
27-
error,
28-
} = useInfiniteQuery<BookmarkData>({
29-
queryKey: ["bookmarks"],
30-
queryFn: async ({ pageParam = 1 }) => {
31-
const result = await myBookmarks({
32-
page: pageParam as number,
33-
limit: 10,
34-
offset: 0,
35-
});
36-
return result as BookmarkData;
37-
},
31+
const { _t } = useTranslation();
32+
const feedInfiniteQuery = useInfiniteQuery({
33+
queryKey: ["dashboard-articles"],
34+
queryFn: ({ pageParam }) =>
35+
myBookmarks({ limit: 10, page: pageParam, offset: 0 }),
36+
initialPageParam: 1,
3837
getNextPageParam: (lastPage) => {
39-
return lastPage?.meta?.hasNextPage
40-
? lastPage.meta.currentPage + 1
41-
: undefined;
38+
const _page = lastPage?.meta?.currentPage ?? 1;
39+
const _totalPages = lastPage?.meta?.totalPages ?? 1;
40+
return _page + 1 <= _totalPages ? _page + 1 : null;
4241
},
43-
initialPageParam: 1,
4442
});
4543

46-
const bookmarks = data?.pages.flatMap((page) => page?.nodes || []) || [];
47-
const totalCount = data?.pages[0]?.meta?.totalCount || 0;
48-
49-
if (isLoading) {
50-
return (
51-
<div className="space-y-6">
52-
<div className="border-b pb-4">
53-
<h1 className="text-2xl font-semibold">My Bookmarks</h1>
54-
<p className="text-muted-foreground mt-1">
55-
Articles you've saved for later
56-
</p>
57-
</div>
58-
<div className="space-y-4">
59-
{Array.from({ length: 3 }).map((_, i) => (
60-
<div key={i} className="animate-pulse">
61-
<div className="bg-muted rounded-lg h-48" />
62-
</div>
63-
))}
64-
</div>
65-
</div>
66-
);
67-
}
44+
const hasItems = useMemo(() => {
45+
const length = feedInfiniteQuery.data?.pages.flat()[0]?.nodes.length ?? 0;
46+
return length > 0;
47+
}, [feedInfiniteQuery]);
6848

69-
if (error) {
70-
return (
71-
<div className="text-center py-12">
72-
<div className="text-destructive mb-2">⚠️ Error</div>
73-
<p className="text-muted-foreground">Failed to load bookmarks</p>
74-
</div>
75-
);
76-
}
49+
const appConfirm = useAppConfirm();
50+
return (
51+
<div>
52+
<h3 className="text-xl font-semibold">{_t("Bookmarks")}</h3>
7753

78-
if (bookmarks.length === 0) {
79-
return (
80-
<div className="space-y-6">
81-
<div className="border-b pb-4">
82-
<h1 className="text-2xl font-semibold">My Bookmarks</h1>
83-
<p className="text-muted-foreground mt-1">
84-
Articles you've saved for later
85-
</p>
86-
</div>
87-
<div className="text-center py-12">
88-
<div className="text-6xl mb-4">📚</div>
89-
<h2 className="text-xl font-medium mb-2">No bookmarks yet</h2>
90-
<p className="text-muted-foreground mb-6">
91-
Start bookmarking articles to see them here
92-
</p>
93-
<a
94-
href="/"
95-
className="inline-flex items-center px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90"
96-
>
97-
Explore Articles
98-
</a>
54+
{!hasItems && (
55+
<div className=" min-h-30 border border-dashed border-muted grid place-content-center mt-4">
56+
<h3 className="text-xl">
57+
{_t("You didn't bookmark any article yet")}
58+
</h3>
9959
</div>
100-
</div>
101-
);
102-
}
60+
)}
10361

104-
return (
105-
<div className="space-y-6">
106-
<div className="border-b pb-4">
107-
<h1 className="text-2xl font-semibold">My Bookmarks</h1>
108-
<p className="text-muted-foreground mt-1">
109-
{totalCount} article{totalCount !== 1 ? "s" : ""} saved
110-
</p>
111-
</div>
62+
<div className="flex flex-col divide-y divide-dashed divide-border-color mt-2">
63+
{feedInfiniteQuery.isFetching &&
64+
Array.from({ length: 10 }).map((_, i) => (
65+
<article key={i} className=" bg-muted h-20 animate-pulse" />
66+
))}
11267

113-
<div className="space-y-6">
114-
{bookmarks.map((bookmark) => (
115-
<ArticleCard
116-
key={bookmark.id}
117-
id={bookmark.article.id}
118-
title={bookmark.article.title}
119-
excerpt=""
120-
coverImage={bookmark.article.cover_image}
121-
publishedAt={bookmark.created_at}
122-
readingTime={5}
123-
author={bookmark.article.author}
124-
handle={""}
125-
likes={0}
126-
comments={0}
127-
/>
128-
))}
129-
</div>
68+
{feedInfiniteQuery.data?.pages.map((page) => {
69+
return page?.nodes.map((bookmark) => (
70+
<article
71+
key={bookmark.id}
72+
className="flex justify-between flex-col md:flex-row py-3 space-y-2"
73+
>
74+
<div className="flex flex-col">
75+
<Link
76+
className="text-forground text-lg"
77+
href={`/@${bookmark?.article?.path}`}
78+
>
79+
{bookmark?.article?.title}
80+
</Link>
81+
{bookmark?.created_at && (
82+
<p className="text-sm text-muted-foreground">
83+
{_t("Bookmarked on")} {formattedTime(bookmark?.created_at!)}
84+
</p>
85+
)}
86+
</div>
13087

131-
{/* {hasNextPage && <VisibilitySensor loading={isFetchingNextPage} />} */}
88+
<div className="flex items-center gap-10 justify-between">
89+
<div className="flex gap-4 items-center">
90+
<div className="text-forground-muted flex items-center gap-1">
91+
<Button
92+
variant={"destructive"}
93+
size={"sm"}
94+
onClick={() =>
95+
appConfirm.show({
96+
title: _t("Sure to remove from bookmark?"),
97+
labels: { confirm: _t("Remove") },
98+
onConfirm() {
99+
toggleResourceBookmark({
100+
resource_id: bookmark.article.id,
101+
resource_type: "ARTICLE",
102+
}).finally(() => feedInfiniteQuery.refetch());
103+
},
104+
})
105+
}
106+
>
107+
<Trash className="h-4 w-4" />
108+
{_t("Remove")}
109+
</Button>
110+
</div>
111+
</div>
112+
</div>
113+
</article>
114+
));
115+
})}
116+
</div>
117+
{feedInfiniteQuery.hasNextPage && (
118+
<VisibilitySensor onLoadmore={feedInfiniteQuery.fetchNextPage} />
119+
)}
132120
</div>
133121
);
134122
};

src/backend/models/domain-models.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -136,18 +136,19 @@ export interface BookmarkArticlePresentation {
136136
resource_type: "ARTICLE" | "COMMENT";
137137
user_id: string;
138138
created_at: Date;
139-
user: {
140-
id: string;
141-
name: string;
142-
username: string;
143-
email: string;
144-
profile_photo: string;
145-
};
139+
146140
article: {
147141
id: string;
148142
title: string;
149143
path: string;
150144
cover_image?: IServerFile | null;
145+
author: {
146+
id: string;
147+
name: string;
148+
username: string;
149+
email: string;
150+
profile_photo: string;
151+
};
151152
};
152153
}
153154

src/backend/services/bookmark.action.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { pgClient } from "../persistence/clients";
77
import { BookmarkActionInput } from "./inputs/bookmark.input";
88
import { ActionException, handleActionException } from "./RepositoryException";
99
import { authID } from "./session.actions";
10+
import { BookmarkArticlePresentation } from "../models/domain-models";
1011

1112
const sql = String.raw;
1213

@@ -81,7 +82,7 @@ export async function myBookmarks(
8182
sessionUserId,
8283
resourceType,
8384
]);
84-
const totalCount = countResult?.rows[0]?.totalCount || 0;
85+
const totalCount = countResult?.rows[0]?.totalcount;
8586
const totalPages = Math.ceil(totalCount / input.limit);
8687

8788
const bookmarksQuery = sql`
@@ -116,9 +117,9 @@ export async function myBookmarks(
116117
]);
117118

118119
return {
119-
nodes: bookmarks?.rows,
120+
nodes: bookmarks?.rows as BookmarkArticlePresentation[],
120121
meta: {
121-
totalCount,
122+
totalCount: Number(totalCount),
122123
currentPage: input.page,
123124
hasNextPage: input.page < totalPages,
124125
totalPages,

0 commit comments

Comments
 (0)