You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
109 lines
3.8 KiB
109 lines
3.8 KiB
import { useEffect, useState } from 'react' |
|
import { useTranslation } from 'react-i18next' |
|
import { useNostr } from '@/providers/NostrProvider' |
|
import rssFeedService, { RssFeedItem as TRssFeedItem } from '@/services/rss-feed.service' |
|
import { ExtendedKind, DEFAULT_RSS_FEEDS } from '@/constants' |
|
import indexedDb from '@/services/indexed-db.service' |
|
import RssFeedItem from '../RssFeedItem' |
|
import { Loader, AlertCircle } from 'lucide-react' |
|
import logger from '@/lib/logger' |
|
|
|
export default function RssFeedList() { |
|
const { t } = useTranslation() |
|
const { pubkey } = useNostr() |
|
const [items, setItems] = useState<TRssFeedItem[]>([]) |
|
const [loading, setLoading] = useState(true) |
|
const [error, setError] = useState<string | null>(null) |
|
|
|
useEffect(() => { |
|
const loadRssFeeds = async () => { |
|
setLoading(true) |
|
setError(null) |
|
|
|
try { |
|
// Get feed URLs from event or use default |
|
let feedUrls: string[] = DEFAULT_RSS_FEEDS |
|
|
|
if (pubkey) { |
|
try { |
|
const event = await indexedDb.getReplaceableEvent(pubkey, ExtendedKind.RSS_FEED_LIST) |
|
if (event && event.content) { |
|
try { |
|
const urls = JSON.parse(event.content) as string[] |
|
if (Array.isArray(urls) && urls.length > 0) { |
|
feedUrls = urls |
|
} |
|
} catch (e) { |
|
logger.error('[RssFeedList] Failed to parse RSS feed list', { error: e }) |
|
// Use default feeds on parse error |
|
} |
|
} |
|
} catch (e) { |
|
logger.error('[RssFeedList] Failed to load RSS feed list event', { error: e }) |
|
// Use default feeds on error |
|
} |
|
} |
|
|
|
// Fetch and merge feeds (this handles errors gracefully and returns partial results) |
|
const fetchedItems = await rssFeedService.fetchMultipleFeeds(feedUrls) |
|
|
|
if (fetchedItems.length === 0) { |
|
// No items were successfully fetched, but don't show error if we tried |
|
// The fetchMultipleFeeds already logs warnings for failed feeds |
|
setError(null) // Clear any previous error |
|
} |
|
|
|
setItems(fetchedItems) |
|
} catch (err) { |
|
logger.error('[RssFeedList] Error loading RSS feeds', { error: err }) |
|
// Don't set error state - fetchMultipleFeeds handles individual feed failures gracefully |
|
// Only set error if there's a critical issue (like network completely down) |
|
if (err instanceof TypeError && err.message.includes('Failed to fetch')) { |
|
// Network error - might be temporary, don't show persistent error |
|
setError(null) |
|
} else { |
|
setError(err instanceof Error ? err.message : t('Failed to load RSS feeds')) |
|
} |
|
} finally { |
|
setLoading(false) |
|
} |
|
} |
|
|
|
loadRssFeeds() |
|
}, [pubkey, t]) |
|
|
|
if (loading) { |
|
return ( |
|
<div className="flex flex-col items-center justify-center py-12"> |
|
<Loader className="h-8 w-8 animate-spin text-muted-foreground" /> |
|
<p className="mt-4 text-sm text-muted-foreground">{t('Loading RSS feeds...')}</p> |
|
</div> |
|
) |
|
} |
|
|
|
if (error) { |
|
return ( |
|
<div className="flex flex-col items-center justify-center py-12 px-4"> |
|
<AlertCircle className="h-8 w-8 text-destructive mb-4" /> |
|
<p className="text-sm text-destructive text-center">{error}</p> |
|
</div> |
|
) |
|
} |
|
|
|
if (items.length === 0) { |
|
return ( |
|
<div className="flex flex-col items-center justify-center py-12"> |
|
<p className="text-sm text-muted-foreground">{t('No RSS feed items available')}</p> |
|
</div> |
|
) |
|
} |
|
|
|
return ( |
|
<div className="space-y-4 px-4 py-3"> |
|
{items.map((item) => ( |
|
<RssFeedItem key={`${item.feedUrl}-${item.guid}`} item={item} /> |
|
))} |
|
</div> |
|
) |
|
} |
|
|
|
|