"use client" import { useState } from "react" import { useSubscriptions } from "@/lib/queries/use-subscriptions" import { useUpdateSubscriptionTitle, useUpdateSubscriptionHiddenFromTimeline, 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" function formatRelativeTime(isoString: string | null): string { if (!isoString) return "never" const date = new Date(isoString) const now = new Date() const differenceSeconds = Math.floor((now.getTime() - date.getTime()) / 1000) if (differenceSeconds < 60) return "just now" if (differenceSeconds < 3600) return `${Math.floor(differenceSeconds / 60)}m ago` if (differenceSeconds < 86400) return `${Math.floor(differenceSeconds / 3600)}h ago` return `${Math.floor(differenceSeconds / 86400)}d ago` } function formatRefreshInterval(seconds: number): string { if (seconds < 3600) return `${Math.round(seconds / 60)} min` return `${Math.round(seconds / 3600)} hr` } function SubscriptionRow({ subscription, folderOptions, }: { subscription: Subscription folderOptions: { identifier: string; name: string }[] }) { const [isEditingTitle, setIsEditingTitle] = useState(false) const [editedTitle, setEditedTitle] = useState( subscription.customTitle ?? "" ) 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 updateHiddenFromTimeline = useUpdateSubscriptionHiddenFromTimeline() const updateFeedUrl = useUpdateFeedUrl() const updateCredentials = useUpdateFeedCredentials() const addCredentials = useAddFeedCredentials() const moveToFolder = useMoveSubscriptionToFolder() const unsubscribe = useUnsubscribe() const requestRefresh = useRequestFeedRefresh() const { data: userProfile } = useUserProfile() function handleSaveTitle() { const trimmedTitle = editedTitle.trim() updateTitle.mutate({ subscriptionIdentifier: subscription.subscriptionIdentifier, customTitle: trimmedTitle || null, }) setIsEditingTitle(false) } function handleSaveUrl() { const trimmedUrl = editedUrl.trim() if (!trimmedUrl || trimmedUrl === subscription.feedUrl) { setIsEditingUrl(false) return } updateFeedUrl.mutate({ feedIdentifier: subscription.feedIdentifier, feedUrl: trimmedUrl, }) 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 ) const targetFolder = folderOptions.find( (folder) => folder.identifier === folderIdentifier ) moveToFolder.mutate({ subscriptionIdentifier: subscription.subscriptionIdentifier, folderIdentifier: folderIdentifier || null, feedTitle: subscription.customTitle ?? subscription.feedTitle ?? undefined, sourceFolderName: sourceFolder?.name, folderName: targetFolder?.name, }) } return (
{isEditingTitle ? (
setEditedTitle(event.target.value)} placeholder={subscription.feedTitle} className="min-w-0 flex-1 border border-border bg-background-primary px-2 py-1 text-text-primary outline-none focus:border-text-dim" onKeyDown={(event) => { if (event.key === "Enter") handleSaveTitle() if (event.key === "Escape") setIsEditingTitle(false) }} autoFocus />
) : (
{subscription.customTitle ?? subscription.feedTitle} {subscription.feedVisibility === "authenticated" && ( [auth] )}
)} {isEditingUrl ? (
setEditedUrl(event.target.value)} className="min-w-0 flex-1 border border-border bg-background-primary px-2 py-1 text-text-primary outline-none focus:border-text-dim" onKeyDown={(event) => { if (event.key === "Enter") handleSaveUrl() if (event.key === "Escape") setIsEditingUrl(false) }} autoFocus />
) : (

{subscription.feedUrl}

)}
last fetched: {formatRelativeTime(subscription.lastFetchedAt)} interval: {formatRefreshInterval(subscription.fetchIntervalSeconds)} {subscription.consecutiveFailures > 0 && ( {subscription.consecutiveFailures} consecutive failure{subscription.consecutiveFailures !== 1 && "s"} )}
{subscription.lastFetchError && subscription.consecutiveFailures > 0 && (

{subscription.lastFetchError}

)}
{isEditingCredentials ? (
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 />
) : null}
{(userProfile?.tier === "pro" || userProfile?.tier === "developer") && ( )} {(userProfile?.tier === "pro" || userProfile?.tier === "developer") && !isEditingCredentials && ( )} {showUnsubscribeConfirm ? (
confirm?
) : ( )}
) } 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("all") if (isLoading) { return

loading subscriptions ...

} const subscriptions = subscriptionsData?.subscriptions ?? [] const folders = subscriptionsData?.folders ?? [] const folderOptions = folders.map((folder) => ({ identifier: folder.folderIdentifier, name: folder.name, })) if (subscriptions.length === 0) { return (

no subscriptions yet — add a feed to get started

) } const normalizedQuery = searchQuery.toLowerCase().trim() const filteredSubscriptions = subscriptions.filter((subscription) => { if (folderFilter === "ungrouped" && subscription.folderIdentifier !== null) return false if (folderFilter !== "all" && folderFilter !== "ungrouped" && subscription.folderIdentifier !== folderFilter) return false if (normalizedQuery) { const title = (subscription.customTitle ?? subscription.feedTitle ?? "").toLowerCase() const url = (subscription.feedUrl ?? "").toLowerCase() if (!title.includes(normalizedQuery) && !url.includes(normalizedQuery)) return false } return true }) return (
setSearchQuery(event.target.value)} placeholder="search subscriptions..." className="min-w-0 flex-1 border border-border bg-background-primary px-3 py-1.5 text-text-primary outline-none placeholder:text-text-dim focus:border-text-dim" /> {filteredSubscriptions.length} / {TIER_LIMITS[userProfile?.tier ?? "free"].maximumFeeds}
{filteredSubscriptions.map((subscription) => ( ))} {filteredSubscriptions.length === 0 && (

no subscriptions match your filters

)}
) }