aboutsummaryrefslogtreecommitdiffhomepage
path: root/frontend/src/pages/Settings.tsx
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2025-07-12 14:55:19 +0900
committernsfisis <nsfisis@gmail.com>2025-07-12 16:23:37 +0900
commit1c73c999ac78d2e6d3a8c68b4e17058046326f55 (patch)
tree41a01cc3827098dfc8d108e5b9cfac1fd5e7fb7f /frontend/src/pages/Settings.tsx
parent9da56e3023af305ba7c5fd49caab60ac8bb57100 (diff)
downloadfeedaka-1c73c999ac78d2e6d3a8c68b4e17058046326f55.tar.gz
feedaka-1c73c999ac78d2e6d3a8c68b4e17058046326f55.tar.zst
feedaka-1c73c999ac78d2e6d3a8c68b4e17058046326f55.zip
feat(frontend): create pages and components
Diffstat (limited to 'frontend/src/pages/Settings.tsx')
-rw-r--r--frontend/src/pages/Settings.tsx155
1 files changed, 155 insertions, 0 deletions
diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx
new file mode 100644
index 0000000..78fc306
--- /dev/null
+++ b/frontend/src/pages/Settings.tsx
@@ -0,0 +1,155 @@
+import { useState } from "react";
+import { useMutation, useQuery } from "urql";
+import { AddFeedForm, FeedList } from "../components";
+import {
+ GetFeedsDocument,
+ MarkFeedReadDocument,
+ MarkFeedUnreadDocument,
+ RemoveFeedDocument,
+} from "../graphql/generated/graphql";
+
+export function Settings() {
+ const [{ data: feedsData }, refetchFeeds] = useQuery({
+ query: GetFeedsDocument,
+ });
+ const [, markFeedRead] = useMutation(MarkFeedReadDocument);
+ const [, markFeedUnread] = useMutation(MarkFeedUnreadDocument);
+ const [, removeFeed] = useMutation(RemoveFeedDocument);
+
+ const [selectedFeeds, setSelectedFeeds] = useState<Set<string>>(new Set());
+
+ const handleFeedAdded = () => {
+ refetchFeeds();
+ };
+
+ const handleFeedDeleted = () => {
+ refetchFeeds();
+ setSelectedFeeds(new Set());
+ };
+
+ const handleSelectFeed = (feedId: string, selected: boolean) => {
+ const newSelection = new Set(selectedFeeds);
+ if (selected) {
+ newSelection.add(feedId);
+ } else {
+ newSelection.delete(feedId);
+ }
+ setSelectedFeeds(newSelection);
+ };
+
+ const handleSelectAll = () => {
+ if (!feedsData?.feeds) return;
+ if (selectedFeeds.size === feedsData.feeds.length) {
+ setSelectedFeeds(new Set());
+ } else {
+ setSelectedFeeds(new Set(feedsData.feeds.map((feed) => feed.id)));
+ }
+ };
+
+ const handleBulkMarkRead = async () => {
+ const promises = Array.from(selectedFeeds).map((feedId) =>
+ markFeedRead({ id: feedId }),
+ );
+ await Promise.all(promises);
+ refetchFeeds();
+ };
+
+ const handleBulkMarkUnread = async () => {
+ const promises = Array.from(selectedFeeds).map((feedId) =>
+ markFeedUnread({ id: feedId }),
+ );
+ await Promise.all(promises);
+ refetchFeeds();
+ };
+
+ const handleBulkDelete = async () => {
+ const confirmed = window.confirm(
+ `Are you sure you want to delete ${selectedFeeds.size} selected feeds?`,
+ );
+ if (!confirmed) return;
+
+ const promises = Array.from(selectedFeeds).map((feedId) =>
+ removeFeed({ id: feedId }),
+ );
+ await Promise.all(promises);
+ handleFeedDeleted();
+ };
+
+ const hasFeeds = feedsData?.feeds && feedsData.feeds.length > 0;
+ const hasSelectedFeeds = selectedFeeds.size > 0;
+
+ return (
+ <div className="mx-auto max-w-4xl">
+ <h1 className="mb-6 text-2xl font-bold text-gray-900">Feed Settings</h1>
+
+ {/* Add New Feed Section */}
+ <div className="mb-8">
+ <h2 className="mb-4 text-xl font-semibold text-gray-800">
+ Add New Feed
+ </h2>
+ <AddFeedForm onFeedAdded={handleFeedAdded} />
+ </div>
+
+ {/* Manage Feeds Section */}
+ <div className="mb-8">
+ <div className="flex items-center justify-between mb-4">
+ <h2 className="text-xl font-semibold text-gray-800">Manage Feeds</h2>
+ {hasFeeds && (
+ <div className="flex items-center gap-4">
+ <label className="flex items-center gap-2 text-sm text-gray-600">
+ <input
+ type="checkbox"
+ checked={selectedFeeds.size === feedsData.feeds.length}
+ onChange={handleSelectAll}
+ className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
+ />
+ Select All ({feedsData.feeds.length} feeds)
+ </label>
+ </div>
+ )}
+ </div>
+
+ {/* Bulk Operations */}
+ {hasSelectedFeeds && (
+ <div className="mb-4 rounded-lg bg-blue-50 border border-blue-200 p-4">
+ <div className="flex items-center justify-between">
+ <span className="text-sm font-medium text-blue-900">
+ {selectedFeeds.size} feed{selectedFeeds.size > 1 ? "s" : ""}{" "}
+ selected
+ </span>
+ <div className="flex gap-2">
+ <button
+ type="button"
+ onClick={handleBulkMarkRead}
+ className="rounded px-3 py-1 text-sm font-medium text-blue-700 hover:bg-blue-100"
+ >
+ Mark All Read
+ </button>
+ <button
+ type="button"
+ onClick={handleBulkMarkUnread}
+ className="rounded px-3 py-1 text-sm font-medium text-blue-700 hover:bg-blue-100"
+ >
+ Mark All Unread
+ </button>
+ <button
+ type="button"
+ onClick={handleBulkDelete}
+ className="rounded px-3 py-1 text-sm font-medium text-red-700 hover:bg-red-100"
+ >
+ Delete Selected
+ </button>
+ </div>
+ </div>
+ </div>
+ )}
+
+ <FeedList
+ onFeedDeleted={handleFeedDeleted}
+ selectedFeeds={selectedFeeds}
+ onSelectFeed={handleSelectFeed}
+ />
+ </div>
+ </div>
+ );
+}