diff options
| author | Fuwn <[email protected]> | 2026-02-08 02:18:55 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-02-08 02:18:55 -0800 |
| commit | 64b5eb44a055bca5042163d19d7a330abc84c58a (patch) | |
| tree | 8f9cad1ce6fdcdde511b71b45c639b5aa4b6ab91 /apps/web/app/reader/settings/_components/subscriptions-settings.tsx | |
| parent | chore: update schema dump with authenticated feed trigger changes and new fun... (diff) | |
| download | asa.news-64b5eb44a055bca5042163d19d7a330abc84c58a.tar.xz asa.news-64b5eb44a055bca5042163d19d7a330abc84c58a.zip | |
feat: add feed management features and fix subscribe_to_feed bugs
- Fix subscribe_to_feed overload ambiguity by dropping old 4-param version
- Fix vault permission error by using vault.create_secret instead of direct INSERT
- Add duplicate subscription check with clear error message
- Add unmute confirmation dialog matching unsubscribe pattern
- Add feed button in subscriptions settings page
- Add inline rename for feeds, folders, and custom feeds from reader header
- Add drag and drop feeds between folders in sidebar
- Add credential management UI (add/update) for pro/developer tier
- Add add_feed_credentials RPC to convert public feeds to authenticated
- Enable pgsodium extension for vault crypto operations
Diffstat (limited to 'apps/web/app/reader/settings/_components/subscriptions-settings.tsx')
| -rw-r--r-- | apps/web/app/reader/settings/_components/subscriptions-settings.tsx | 101 |
1 files changed, 100 insertions, 1 deletions
diff --git a/apps/web/app/reader/settings/_components/subscriptions-settings.tsx b/apps/web/app/reader/settings/_components/subscriptions-settings.tsx index e22a005..1e9245d 100644 --- a/apps/web/app/reader/settings/_components/subscriptions-settings.tsx +++ b/apps/web/app/reader/settings/_components/subscriptions-settings.tsx @@ -5,11 +5,14 @@ import { useSubscriptions } from "@/lib/queries/use-subscriptions" import { useUpdateSubscriptionTitle, useUpdateFeedUrl, + useUpdateFeedCredentials, + useAddFeedCredentials, useMoveSubscriptionToFolder, useUnsubscribe, useRequestFeedRefresh, } from "@/lib/queries/use-subscription-mutations" import { useUserProfile } from "@/lib/queries/use-user-profile" +import { useUserInterfaceStore } from "@/lib/stores/user-interface-store" import { TIER_LIMITS } from "@asa-news/shared" import type { Subscription } from "@/lib/types/subscription" @@ -43,8 +46,13 @@ function SubscriptionRow({ const [isEditingUrl, setIsEditingUrl] = useState(false) const [editedUrl, setEditedUrl] = useState(subscription.feedUrl) const [showUnsubscribeConfirm, setShowUnsubscribeConfirm] = useState(false) + const [isEditingCredentials, setIsEditingCredentials] = useState(false) + const [editedAuthType, setEditedAuthType] = useState("bearer") + const [editedCredential, setEditedCredential] = useState("") const updateTitle = useUpdateSubscriptionTitle() const updateFeedUrl = useUpdateFeedUrl() + const updateCredentials = useUpdateFeedCredentials() + const addCredentials = useAddFeedCredentials() const moveToFolder = useMoveSubscriptionToFolder() const unsubscribe = useUnsubscribe() const requestRefresh = useRequestFeedRefresh() @@ -72,6 +80,27 @@ function SubscriptionRow({ setIsEditingUrl(false) } + function handleSaveCredentials() { + const trimmedCredential = editedCredential.trim() + if (!trimmedCredential) return + + if (subscription.feedVisibility === "authenticated") { + updateCredentials.mutate({ + feedIdentifier: subscription.feedIdentifier, + authenticationType: editedAuthType, + credential: trimmedCredential, + }) + } else { + addCredentials.mutate({ + subscriptionIdentifier: subscription.subscriptionIdentifier, + authenticationType: editedAuthType, + credential: trimmedCredential, + }) + } + setIsEditingCredentials(false) + setEditedCredential("") + } + function handleFolderChange(folderIdentifier: string) { const sourceFolder = folderOptions.find( (folder) => folder.identifier === subscription.folderIdentifier @@ -197,7 +226,59 @@ function SubscriptionRow({ )} </div> </div> - <div className="flex items-center gap-2"> + {isEditingCredentials ? ( + <div className="flex items-center gap-2"> + <select + value={editedAuthType} + onChange={(event) => setEditedAuthType(event.target.value)} + className="border border-border bg-background-primary px-2 py-1 text-text-secondary outline-none" + > + <option value="bearer">bearer token</option> + <option value="basic">basic (user:pass)</option> + <option value="query_param">query parameter</option> + </select> + <input + type="password" + value={editedCredential} + onChange={(event) => setEditedCredential(event.target.value)} + placeholder={ + editedAuthType === "query_param" + ? "api_key=your_token" + : editedAuthType === "basic" + ? "username:password" + : "your token" + } + className="min-w-0 flex-1 border border-border bg-background-primary px-2 py-1 text-text-primary outline-none placeholder:text-text-dim focus:border-text-dim" + onKeyDown={(event) => { + if (event.key === "Enter" && editedCredential.trim()) { + handleSaveCredentials() + } + if (event.key === "Escape") { + setIsEditingCredentials(false) + setEditedCredential("") + } + }} + autoFocus + /> + <button + onClick={handleSaveCredentials} + disabled={!editedCredential.trim()} + className="px-2 py-1 text-text-secondary transition-colors hover:text-text-primary disabled:opacity-50" + > + save + </button> + <button + onClick={() => { + setIsEditingCredentials(false) + setEditedCredential("") + }} + className="px-2 py-1 text-text-secondary transition-colors hover:text-text-primary" + > + cancel + </button> + </div> + ) : null} + <div className="flex flex-wrap items-center gap-2"> <select value={subscription.folderIdentifier ?? ""} onChange={(event) => handleFolderChange(event.target.value)} @@ -224,6 +305,14 @@ function SubscriptionRow({ refresh </button> )} + {(userProfile?.tier === "pro" || userProfile?.tier === "developer") && !isEditingCredentials && ( + <button + onClick={() => setIsEditingCredentials(true)} + className="px-2 py-1 text-text-secondary transition-colors hover:text-text-primary" + > + {subscription.feedVisibility === "authenticated" ? "update credentials" : "add credentials"} + </button> + )} {showUnsubscribeConfirm ? ( <div className="flex items-center gap-1"> <span className="text-text-dim">confirm?</span> @@ -262,6 +351,9 @@ function SubscriptionRow({ export function SubscriptionsSettings() { const { data: subscriptionsData, isLoading } = useSubscriptions() const { data: userProfile } = useUserProfile() + const setAddFeedDialogOpen = useUserInterfaceStore( + (state) => state.setAddFeedDialogOpen + ) const [searchQuery, setSearchQuery] = useState("") const [folderFilter, setFolderFilter] = useState<string>("all") @@ -325,6 +417,13 @@ export function SubscriptionsSettings() { <span className="text-text-dim"> {filteredSubscriptions.length} / {TIER_LIMITS[userProfile?.tier ?? "free"].maximumFeeds} </span> + <button + type="button" + onClick={() => setAddFeedDialogOpen(true)} + className="border border-border bg-background-tertiary px-3 py-1.5 text-text-primary transition-colors hover:bg-border" + > + + add feed + </button> </div> <div> {filteredSubscriptions.map((subscription) => ( |