103 lines
2.7 KiB
TypeScript
103 lines
2.7 KiB
TypeScript
'use client'
|
|
|
|
import { useEffect, useState } from 'react'
|
|
import { getNewsFeed, formatPublishedDate, type NewsItem } from '@/lib/newsApi'
|
|
import { SentimentalityBar } from './SentimentalityBar'
|
|
|
|
interface NewsHeadlinesProps {
|
|
keyword?: string // Symbol or topic to fetch news for
|
|
onAverageSentiment?: (avg: number | null) => void // Callback with average sentiment score
|
|
}
|
|
|
|
export function NewsHeadlines({ keyword, onAverageSentiment }: NewsHeadlinesProps) {
|
|
const [items, setItems] = useState<NewsItem[]>([])
|
|
const [loading, setLoading] = useState(true)
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
useEffect(() => {
|
|
if (!keyword) {
|
|
setLoading(false)
|
|
setError(null)
|
|
setItems([])
|
|
onAverageSentiment?.(null)
|
|
return
|
|
}
|
|
|
|
setLoading(true)
|
|
setError(null)
|
|
setItems([])
|
|
|
|
getNewsFeed(keyword)
|
|
.then((newsItems) => {
|
|
setItems(newsItems)
|
|
if (newsItems.length > 0) {
|
|
const avg = newsItems.reduce((sum, item) => sum + item.score, 0) / newsItems.length
|
|
onAverageSentiment?.(Math.round(avg))
|
|
} else {
|
|
onAverageSentiment?.(null)
|
|
}
|
|
})
|
|
.catch((err) => {
|
|
setError(err.message)
|
|
onAverageSentiment?.(null)
|
|
})
|
|
.finally(() => setLoading(false))
|
|
}, [keyword, onAverageSentiment])
|
|
|
|
if (!keyword) {
|
|
return (
|
|
<div className="text-xs text-muted-foreground text-center py-4">
|
|
No keyword selected
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="text-xs text-muted-foreground text-center py-4">
|
|
Loading news...
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<div className="text-xs text-negative text-center py-4">
|
|
{error}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (!items || items.length === 0) {
|
|
return (
|
|
<div className="text-xs text-muted-foreground text-center py-4">
|
|
No news found for "{keyword}"
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="divide-y divide-border">
|
|
{items.map((item, idx) => (
|
|
<a
|
|
key={idx}
|
|
href={item.url}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="block py-3 first:pt-0 last:pb-0 hover:bg-secondary/50 -mx-1 px-1 transition-colors cursor-pointer"
|
|
>
|
|
<div className="text-sm font-medium text-foreground line-clamp-2">
|
|
{item.title}
|
|
</div>
|
|
<div className="text-xs text-muted-foreground mt-1">
|
|
{item.publisher} • {formatPublishedDate(item.publishedAt)}
|
|
</div>
|
|
<div className="text-xs mt-1.5">
|
|
<SentimentalityBar sentiment={Math.round(item.score)} />
|
|
</div>
|
|
</a>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|