From 1c73c999ac78d2e6d3a8c68b4e17058046326f55 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sat, 12 Jul 2025 14:55:19 +0900 Subject: feat(frontend): create pages and components --- frontend/src/components/ArticleList.tsx | 137 ++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 frontend/src/components/ArticleList.tsx (limited to 'frontend/src/components/ArticleList.tsx') diff --git a/frontend/src/components/ArticleList.tsx b/frontend/src/components/ArticleList.tsx new file mode 100644 index 0000000..ee7b187 --- /dev/null +++ b/frontend/src/components/ArticleList.tsx @@ -0,0 +1,137 @@ +import { + faCheck, + faCircle, + faExternalLinkAlt, +} from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useMutation } from "urql"; +import type { + GetReadArticlesQuery, + GetUnreadArticlesQuery, +} from "../graphql/generated/graphql"; +import { + MarkArticleReadDocument, + MarkArticleUnreadDocument, +} from "../graphql/generated/graphql"; + +interface Props { + articles: NonNullable< + | GetUnreadArticlesQuery["unreadArticles"] + | GetReadArticlesQuery["readArticles"] + >; + showReadStatus?: boolean; +} + +export function ArticleList({ articles, showReadStatus = true }: Props) { + const [, markArticleRead] = useMutation(MarkArticleReadDocument); + const [, markArticleUnread] = useMutation(MarkArticleUnreadDocument); + + const handleToggleRead = async ( + articleId: string, + isCurrentlyRead: boolean, + ) => { + if (isCurrentlyRead) { + await markArticleUnread({ id: articleId }); + } else { + await markArticleRead({ id: articleId }); + } + }; + + const handleArticleClick = async (article: (typeof articles)[0]) => { + // Open article in new tab and mark as read if it's unread + window.open(article.url, "_blank"); + if (!article.isRead) { + await markArticleRead({ id: article.id }); + } + }; + + if (articles.length === 0) { + return ( +
No articles found.
+ ); + } + + // Group articles by feed + const articlesByFeed = articles.reduce( + (acc, article) => { + const feedId = article.feed.id; + if (!acc[feedId]) { + acc[feedId] = { + feed: article.feed, + articles: [], + }; + } + acc[feedId].articles.push(article); + return acc; + }, + {} as Record< + string, + { feed: { id: string; title: string }; articles: typeof articles } + >, + ); + + return ( +
+ {Object.values(articlesByFeed).map(({ feed, articles: feedArticles }) => ( +
+

+ {feed.title} + + ({feedArticles.length} article + {feedArticles.length !== 1 ? "s" : ""}) + +

+
+ {feedArticles.map((article) => ( +
+ {showReadStatus && ( + + )} +
+ +
+
+ ))} +
+
+ ))} +
+ ); +} -- cgit v1.2.3-70-g09d2