diff options
| author | Adam Mathes <adam@adammathes.com> | 2026-02-13 06:55:21 -0800 |
|---|---|---|
| committer | Adam Mathes <adam@adammathes.com> | 2026-02-13 06:55:21 -0800 |
| commit | 3ba71500bc2d60a00ca81b9439305029670f4d52 (patch) | |
| tree | 5752a119effd739c62c80c2d15d4520c2e53eadf /frontend/src/components/FeedItems.tsx | |
| parent | 2c3cad528a247c771bca136466337877f76f280f (diff) | |
| download | neko-3ba71500bc2d60a00ca81b9439305029670f4d52.tar.gz neko-3ba71500bc2d60a00ca81b9439305029670f4d52.tar.bz2 neko-3ba71500bc2d60a00ca81b9439305029670f4d52.zip | |
Implement Frontend Feed Items View with tests
Diffstat (limited to 'frontend/src/components/FeedItems.tsx')
| -rw-r--r-- | frontend/src/components/FeedItems.tsx | 66 |
1 files changed, 66 insertions, 0 deletions
diff --git a/frontend/src/components/FeedItems.tsx b/frontend/src/components/FeedItems.tsx new file mode 100644 index 0000000..048bed7 --- /dev/null +++ b/frontend/src/components/FeedItems.tsx @@ -0,0 +1,66 @@ +import { useEffect, useState } from 'react'; +import { useParams } from 'react-router-dom'; +import type { Item } from '../types'; +import './FeedItems.css'; + +export default function FeedItems() { + const { feedId } = useParams<{ feedId: string }>(); + const [items, setItems] = useState<Item[]>([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + + useEffect(() => { + setLoading(true); + setError(''); + + const url = feedId + ? `/api/stream?feed_id=${feedId}` + : '/api/stream'; // Default or "all" view? For now let's assume we need a feedId or handle "all" logic later + + fetch(url) + .then((res) => { + if (!res.ok) { + throw new Error('Failed to fetch items'); + } + return res.json(); + }) + .then((data) => { + setItems(data); + setLoading(false); + }) + .catch((err) => { + setError(err.message); + setLoading(false); + }); + }, [feedId]); + + if (loading) return <div className="feed-items-loading">Loading items...</div>; + if (error) return <div className="feed-items-error">Error: {error}</div>; + + return ( + <div className="feed-items"> + <h2>Items</h2> + {/* TODO: Add Feed Title here if possible, maybe pass from location state or fetch feed details */} + {items.length === 0 ? ( + <p>No items found.</p> + ) : ( + <ul className="item-list"> + {items.map((item) => ( + <li key={item._id} className={`item ${item.read ? 'read' : 'unread'}`}> + <a href={item.url} target="_blank" rel="noopener noreferrer" className="item-title"> + {item.title || '(No Title)'} + </a> + <div className="item-meta"> + <span className="item-date">{new Date(item.publish_date).toLocaleDateString()}</span> + {item.feed_title && <span className="item-feed"> - {item.feed_title}</span>} + </div> + {item.description && ( + <div className="item-description" dangerouslySetInnerHTML={{ __html: item.description }} /> + )} + </li> + ))} + </ul> + )} + </div> + ); +} |
