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/FeedList.tsx | 150 +++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 frontend/src/components/FeedList.tsx (limited to 'frontend/src/components/FeedList.tsx') diff --git a/frontend/src/components/FeedList.tsx b/frontend/src/components/FeedList.tsx new file mode 100644 index 0000000..7e46e78 --- /dev/null +++ b/frontend/src/components/FeedList.tsx @@ -0,0 +1,150 @@ +import { + faCheckDouble, + faCircle, + faTrash, +} from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useMutation, useQuery } from "urql"; +import { + GetFeedsDocument, + MarkFeedReadDocument, + MarkFeedUnreadDocument, + RemoveFeedDocument, +} from "../graphql/generated/graphql"; + +interface Props { + onFeedDeleted?: () => void; + selectedFeeds?: Set; + onSelectFeed?: (feedId: string, selected: boolean) => void; +} + +export function FeedList({ + onFeedDeleted, + selectedFeeds, + onSelectFeed, +}: Props) { + const [{ data, fetching, error }] = useQuery({ + query: GetFeedsDocument, + }); + + const [, markFeedRead] = useMutation(MarkFeedReadDocument); + const [, markFeedUnread] = useMutation(MarkFeedUnreadDocument); + const [, removeFeed] = useMutation(RemoveFeedDocument); + + const handleMarkAllRead = async (feedId: string) => { + await markFeedRead({ id: feedId }); + }; + + const handleMarkAllUnread = async (feedId: string) => { + await markFeedUnread({ id: feedId }); + }; + + const handleDeleteFeed = async (feedId: string) => { + const confirmed = window.confirm( + "Are you sure you want to delete this feed?", + ); + if (confirmed) { + await removeFeed({ id: feedId }); + onFeedDeleted?.(); + } + }; + + if (fetching) return
Loading feeds...
; + if (error) + return
Error: {error.message}
; + if (!data?.feeds || data.feeds.length === 0) { + return
No feeds added yet.
; + } + + return ( +
+ {data.feeds.map((feed) => { + const unreadCount = feed.articles.filter((a) => !a.isRead).length; + const totalCount = feed.articles.length; + + const isSelected = selectedFeeds?.has(feed.id) ?? false; + + return ( +
+
+ {selectedFeeds && onSelectFeed && ( +
+ onSelectFeed(feed.id, e.target.checked)} + className="mt-1 rounded border-gray-300 text-blue-600 focus:ring-blue-500" + /> +
+

+ {feed.title} +

+

{feed.url}

+
+ + {unreadCount} unread / {totalCount} total + + + Last fetched:{" "} + {new Date(feed.fetchedAt).toLocaleString()} + +
+
+
+ )} + {(!selectedFeeds || !onSelectFeed) && ( +
+

+ {feed.title} +

+

{feed.url}

+
+ + {unreadCount} unread / {totalCount} total + + + Last fetched: {new Date(feed.fetchedAt).toLocaleString()} + +
+
+ )} +
+ + + +
+
+
+ ); + })} +
+ ); +} -- cgit v1.2.3-70-g09d2