76 lines
2.5 KiB
TypeScript
76 lines
2.5 KiB
TypeScript
'use client'
|
||
|
||
import type { TrumpPost } from '@/types'
|
||
|
||
function fmtPct(n: number | null | undefined) {
|
||
if (n == null || isNaN(n)) return '—'
|
||
const s = n.toFixed(2) + '%'
|
||
return n >= 0 ? '+' + s : s
|
||
}
|
||
|
||
function timeAgo(iso: string) {
|
||
const diff = Date.now() - new Date(iso).getTime()
|
||
const m = Math.floor(diff / 60000)
|
||
if (m < 1) return 'just now'
|
||
if (m < 60) return m + 'm'
|
||
const h = Math.floor(m / 60)
|
||
if (h < 24) return h + 'h'
|
||
return Math.floor(h / 24) + 'd'
|
||
}
|
||
|
||
function SourceIcon({ source }: { source: string }) {
|
||
if (source === 'x') return <div className="src-ico x">𝕏</div>
|
||
return <div className="src-ico truth">T</div>
|
||
}
|
||
|
||
function SignalPill({ signal }: { signal: string | null }) {
|
||
if (!signal || signal === 'hold') return <span className="sig hold">HOLD</span>
|
||
return <span className={`sig ${signal}`}>{signal.toUpperCase()}</span>
|
||
}
|
||
|
||
interface PostRowProps {
|
||
post: TrumpPost
|
||
selected?: boolean
|
||
onClick?: () => void
|
||
}
|
||
|
||
export default function PostRow({ post, selected, onClick }: PostRowProps) {
|
||
const impact = post.price_impact
|
||
return (
|
||
<div className={`post-row ${selected ? 'selected' : ''}`} onClick={onClick}>
|
||
<SourceIcon source={post.source} />
|
||
<div className="post-body">
|
||
<div className="meta">
|
||
<span className="mono" style={{ color: 'var(--ink-2)', fontWeight: 500 }}>@realDonaldTrump</span>
|
||
<span>·</span>
|
||
<span>{timeAgo(post.published_at)} ago</span>
|
||
<span>·</span>
|
||
<span className={`chip ${post.sentiment === 'bullish' ? 'up' : post.sentiment === 'bearish' ? 'down' : 'neutral'}`} style={{ padding: '2px 8px', fontSize: 11 }}>
|
||
{post.sentiment}
|
||
</span>
|
||
</div>
|
||
<p className="text">{post.text.slice(0, 180)}{post.text.length > 180 ? '…' : ''}</p>
|
||
</div>
|
||
<div className="post-aside">
|
||
<SignalPill signal={post.signal} />
|
||
<div className="impact-mini">
|
||
{impact ? (
|
||
<>
|
||
<span className="tf">1h</span>
|
||
<span className={`delta ${(impact.m1h ?? 0) >= 0 ? 'up' : 'down'}`}>{fmtPct(impact.m1h)}</span>
|
||
</>
|
||
) : (
|
||
<span className="tf">no data</span>
|
||
)}
|
||
</div>
|
||
<div className="row gap-s" style={{ fontSize: 11, color: 'var(--ink-3)' }}>
|
||
<span>AI</span>
|
||
<span className="mono" style={{ color: 'var(--ink-2)', fontWeight: 500 }}>{post.ai_confidence}%</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export { SignalPill, SourceIcon, fmtPct, timeAgo }
|