aboutsummaryrefslogtreecommitdiffhomepage
path: root/frontend/src/pages/ReadArticles.tsx
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-02-13 22:01:12 +0900
committernsfisis <nsfisis@gmail.com>2026-02-13 22:01:12 +0900
commite216c3bc97994b4172d15d52b46d5f6b75f35ea4 (patch)
tree3ffbd74f4cb2d90846931c8dcbb97ec07f2b91f1 /frontend/src/pages/ReadArticles.tsx
parentc863e64c0521926e785f4aa7ecf4cf15bb9defa7 (diff)
downloadfeedaka-e216c3bc97994b4172d15d52b46d5f6b75f35ea4.tar.gz
feedaka-e216c3bc97994b4172d15d52b46d5f6b75f35ea4.tar.zst
feedaka-e216c3bc97994b4172d15d52b46d5f6b75f35ea4.zip
feat: add feed sidebar and cursor-based pagination
Add a feed sidebar to /unread and /read pages for filtering articles by feed, and replace the fixed 100-article limit with cursor-based pagination using a "Load more" button. Backend: - Add PageInfo, ArticleConnection types and pagination args to GraphQL - Replace GetUnreadArticles/GetReadArticles with parameterized queries - Add GetFeedUnreadCounts query and composite index - Add shared pagination helper in resolver Frontend: - Add FeedSidebar component with unread count badges - Add usePaginatedArticles hook for cursor-based fetching - Update ArticleList with Load more button and single-feed mode - Use ?feed=<id> query parameter for feed filtering Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'frontend/src/pages/ReadArticles.tsx')
-rw-r--r--frontend/src/pages/ReadArticles.tsx73
1 files changed, 38 insertions, 35 deletions
diff --git a/frontend/src/pages/ReadArticles.tsx b/frontend/src/pages/ReadArticles.tsx
index f90c3c9..2538446 100644
--- a/frontend/src/pages/ReadArticles.tsx
+++ b/frontend/src/pages/ReadArticles.tsx
@@ -1,45 +1,48 @@
-import { useQuery } from "urql";
-import { ArticleList } from "../components";
-import { GetReadArticlesDocument } from "../graphql/generated/graphql";
-
-const urqlContextArticle = { additionalTypenames: ["Article"] };
+import { useSearch } from "wouter";
+import { ArticleList, FeedSidebar } from "../components";
+import { usePaginatedArticles } from "../hooks/usePaginatedArticles";
export function ReadArticles() {
- const [{ data, fetching, error }] = useQuery({
- query: GetReadArticlesDocument,
- context: urqlContextArticle,
- });
-
- if (fetching) {
- return (
- <div className="py-8 text-center">
- <p className="text-sm text-stone-400">Loading read articles...</p>
- </div>
- );
- }
+ const search = useSearch();
+ const params = new URLSearchParams(search);
+ const feedId = params.get("feed");
- if (error) {
- return (
- <div className="rounded-lg bg-red-50 p-4 text-sm text-red-600">
- Error: {error.message}
- </div>
- );
- }
+ const { articles, hasNextPage, loading, loadingMore, loadMore, error } =
+ usePaginatedArticles({ isReadView: true, feedId });
return (
- <div>
- <div className="mb-6">
- <h1 className="text-xl font-semibold text-stone-900">Read</h1>
- {data?.readArticles && (
- <p className="mt-1 text-sm text-stone-400">
- {data.readArticles.length} article
- {data.readArticles.length !== 1 ? "s" : ""}
- </p>
+ <div className="flex gap-8">
+ <FeedSidebar basePath="/read" />
+ <div className="min-w-0 flex-1">
+ <div className="mb-6">
+ <h1 className="text-xl font-semibold text-stone-900">Read</h1>
+ {!loading && articles.length > 0 && (
+ <p className="mt-1 text-sm text-stone-400">
+ {articles.length}
+ {hasNextPage ? "+" : ""} article
+ {articles.length !== 1 ? "s" : ""}
+ </p>
+ )}
+ </div>
+ {loading ? (
+ <div className="py-8 text-center">
+ <p className="text-sm text-stone-400">Loading read articles...</p>
+ </div>
+ ) : error ? (
+ <div className="rounded-lg bg-red-50 p-4 text-sm text-red-600">
+ Error: {error.message}
+ </div>
+ ) : (
+ <ArticleList
+ articles={articles}
+ isReadView={true}
+ isSingleFeed={!!feedId}
+ hasNextPage={hasNextPage}
+ loadingMore={loadingMore}
+ onLoadMore={loadMore}
+ />
)}
</div>
- {data?.readArticles && (
- <ArticleList articles={data.readArticles} isReadView={true} />
- )}
</div>
);
}