import React, { useState, useEffect } from 'react'; import { RSSFeed, RSSArticle } from '../types'; import { Rss, Plus, Trash2, RefreshCw, ExternalLink, Loader2, ChevronDown, ChevronUp, AlertCircle, CheckCircle, Star, Filter } from 'lucide-react'; import { getRSSFeeds, saveRSSFeed, removeRSSFeed } from '../services/storageService'; import { fetchRSSFeed, refreshFeed, SUGGESTED_FEEDS, validateRSSUrl } from '../services/rssService'; interface RSSManagerProps { onArticleSelect: (url: string) => void; onClose: () => void; } type FeedStatus = 'idle' | 'validating' | 'valid' | 'invalid' | 'loading'; export const RSSManager: React.FC = ({ onArticleSelect, onClose }) => { const [feeds, setFeeds] = useState([]); const [articles, setArticles] = useState>({}); const [newFeedUrl, setNewFeedUrl] = useState(''); const [feedStatus, setFeedStatus] = useState('idle'); const [error, setError] = useState(null); const [expandedFeed, setExpandedFeed] = useState(null); const [loadingFeeds, setLoadingFeeds] = useState>(new Set()); const [showRecommendations, setShowRecommendations] = useState(true); const [filterCategory, setFilterCategory] = useState<'all' | 'news' | 'tech' | 'unread'>('all'); useEffect(() => { const loadedFeeds = getRSSFeeds(); setFeeds(loadedFeeds); // Auto-load articles for active feeds loadedFeeds.forEach(feed => { if (feed.isActive) { handleRefreshFeed(feed, true); } }); }, []); const validateUrl = async (url: string) => { if (!url.trim()) { setFeedStatus('idle'); setError(null); return; } setFeedStatus('validating'); setError(null); try { // Check if it looks like a URL if (!url.startsWith('http://') && !url.startsWith('https://')) { setError('URL must start with http:// or https://'); setFeedStatus('invalid'); return; } const isValid = await validateRSSUrl(url); if (isValid) { setFeedStatus('valid'); setError(null); } else { setFeedStatus('invalid'); setError('Not a valid RSS feed. Make sure the URL points to an RSS/Atom feed XML file.'); } } catch (e) { setFeedStatus('invalid'); setError('Could not access URL. Check the address and try again.'); } }; // Debounced validation useEffect(() => { const timer = setTimeout(() => { if (newFeedUrl && feedStatus !== 'loading') { validateUrl(newFeedUrl); } }, 800); return () => clearTimeout(timer); }, [newFeedUrl]); const handleAddFeed = async (url: string) => { if (!url.trim()) return; setFeedStatus('loading'); setError(null); try { const { feed, articles: feedArticles } = await fetchRSSFeed(url.trim()); if (feedArticles.length === 0) { setError(`Feed added but contains 0 articles. The feed might be empty or improperly formatted.`); } saveRSSFeed(feed); setFeeds(prev => [...prev, feed]); setArticles(prev => ({ ...prev, [feed.id]: feedArticles })); setNewFeedUrl(''); setFeedStatus('idle'); setExpandedFeed(feed.id); } catch (e: any) { setFeedStatus('invalid'); if (e.message?.includes('Failed to fetch')) { setError('Network error: Could not reach the feed URL. Check your connection or try a different feed.'); } else if (e.message?.includes('status')) { setError(`Server error: The feed URL returned an error. It might be down or require authentication.`); } else { setError(e.message || 'Failed to add feed. Make sure it\'s a valid RSS/Atom feed URL.'); } } }; const handleRefreshFeed = async (feed: RSSFeed, silent = false) => { if (!silent) { setLoadingFeeds(prev => new Set(prev).add(feed.id)); } try { const feedArticles = await refreshFeed(feed); setArticles(prev => ({ ...prev, [feed.id]: feedArticles })); // Update article count const updatedFeed = { ...feed, articleCount: feedArticles.length }; saveRSSFeed(updatedFeed); setFeeds(prev => prev.map(f => f.id === feed.id ? updatedFeed : f)); } catch (e) { console.error('Failed to refresh feed:', e); } finally { if (!silent) { setLoadingFeeds(prev => { const next = new Set(prev); next.delete(feed.id); return next; }); } } }; const handleRemoveFeed = (feedId: string) => { removeRSSFeed(feedId); setFeeds(prev => prev.filter(f => f.id !== feedId)); setArticles(prev => { const next = { ...prev }; delete next[feedId]; return next; }); }; const handleArticleClick = (url: string) => { onArticleSelect(url); onClose(); }; const formatDate = (dateStr?: string): string => { if (!dateStr) return ''; try { return new Date(dateStr).toLocaleDateString('en', { month: 'short', day: 'numeric' }); } catch { return ''; } }; const getStatusIcon = () => { switch (feedStatus) { case 'validating': return ; case 'valid': return ; case 'invalid': return ; default: return null; } }; // Categorize suggested feeds by the category field const categorizedSuggestions = { news: SUGGESTED_FEEDS.filter(f => f.category === 'news'), tech: SUGGESTED_FEEDS.filter(f => f.category === 'tech'), business: SUGGESTED_FEEDS.filter(f => f.category === 'business'), science: SUGGESTED_FEEDS.filter(f => f.category === 'science'), international: SUGGESTED_FEEDS.filter(f => f.category === 'international') }; return (
{/* Header */}

RSS Feed Manager

Subscribe to your favorite news sources

{/* Add Feed Input */}
setNewFeedUrl(e.target.value)} placeholder="Enter RSS feed URL (e.g., https://example.com/feed.xml)" className="w-full px-4 py-3 pr-10 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-xl text-slate-700 dark:text-slate-200 placeholder:text-slate-400" onKeyDown={(e) => e.key === 'Enter' && feedStatus === 'valid' && handleAddFeed(newFeedUrl)} />
{getStatusIcon()}
{error && (

{error}

)} {feedStatus === 'valid' && (

Valid RSS feed detected

)}
{/* Content */}
{/* My Feeds */} {feeds.length > 0 && (

My Feeds ({feeds.length})

{feeds.map((feed) => { const feedArticles = articles[feed.id] || []; return (
{/* Feed Header */}
setExpandedFeed(expandedFeed === feed.id ? null : feed.id)} >

{feed.title}

{feedArticles.length > 0 ? `${feedArticles.length} articles` : 'Click refresh to load articles'}

{expandedFeed === feed.id ? ( ) : ( )}
{/* Articles List */} {expandedFeed === feed.id && (
{feedArticles.length > 0 ? ( feedArticles.slice(0, 20).map((article, i) => (
handleArticleClick(article.url)} className="p-4 border-t border-slate-100 dark:border-slate-800 hover:bg-slate-50 dark:hover:bg-slate-800/50 cursor-pointer transition-colors group" >

{article.title}

{article.description && (

{article.description}

)} {article.pubDate && (

{formatDate(article.pubDate)}

)}
)) ) : (

No articles loaded yet

)}
)}
); })}
)} {/* Recommendations - Always Visible */}

Recommended Feeds

{showRecommendations && (
{/* News Category */}

General News

{categorizedSuggestions.news.map((suggestion) => ( ))}
{/* Technology Category */}

Technology

{categorizedSuggestions.tech.map((suggestion) => ( ))}
{/* Business & Finance Category */}

Business & Finance

{categorizedSuggestions.business.map((suggestion) => ( ))}
{/* Science Category */}

Science & Research

{categorizedSuggestions.science.map((suggestion) => ( ))}
{/* International Category */}

International News

{categorizedSuggestions.international.map((suggestion) => ( ))}
)}
); };