'use client' import { useState, useEffect } from 'react' import { useAccount } from 'wagmi' import type { TrumpPost, BotPerformance, Candle } from '@/types' import { useDashboardStore } from '@/store/dashboard' import { usePriceSocket } from '@/lib/useRealtimeData' import { getPrices, getUserPublic } from '@/lib/api' import ChartPanel from '@/components/dashboard/ChartPanel' import PostRow, { SignalPill, SourceIcon, fmtPct, timeAgo, TimeAgo, LocalDateTime } from '@/components/dashboard/PostCards' interface Props { initialPosts: TrumpPost[] initialPerformance?: BotPerformance } // ── Inline post detail panel shown in the right rail ────────────────────────── function PostDetail({ post, onClose }: { post: TrumpPost; onClose: () => void }) { const impact = post.price_impact function fmtImpactPct(v: number | null | undefined) { if (v == null || isNaN(v)) return '—' const s = Math.abs(v).toFixed(2) + '%' return v >= 0 ? '+' + s : '-' + s } return (
{/* header */}
Signal detail
{/* source + time */}
@realDonaldTrump
·
{/* post text */}

{post.text}

{/* signal + sentiment */}
{post.sentiment}
{/* AI confidence */}
AI confidence {post.ai_confidence}%
{/* AI reasoning */} {post.ai_reasoning && ( <>
AI reasoning
{post.ai_reasoning}
)} {/* Price impact grid */} {impact && ( <>
Price impact · {impact.asset}
{([['m5', '5m'], ['m15', '15m'], ['m1h', '1h']] as const).map(([key, label]) => { const v = impact[key] const correct = impact[`correct_${key}` as 'correct_m5' | 'correct_m15' | 'correct_m1h'] return (
{label}
= 0 ? 'up' : 'down'}`} style={{ fontSize: 14, fontWeight: 600 }}> {fmtImpactPct(v)}
{correct != null && (
{correct ? '✓' : '✗'}
)}
) })}
)}
) } // ── Empty state for when nothing is selected ────────────────────────────────── function SelectHint() { return (

Click any marker on the chart
or a post below to see details

) } // ── Main dashboard ───────────────────────────────────────────────────────────── export default function DashboardClient({ initialPosts, initialPerformance }: Props) { const { asset, setAsset, timeframe, setTimeframe: _setTimeframe, setLivePrice, setSubscribed, setBotReadiness, setHlApiKeySet, isSubscribed, hlApiKeySet, botReadiness } = useDashboardStore() function setTimeframe(tf: string) { _setTimeframe(tf as '5m' | '15m' | '1H' | '4H' | '1D' | '1W') } const { address, isConnected } = useAccount() const [posts, setPosts] = useState(initialPosts) const [candles, setCandles] = useState([]) const [selectedPostId, setSelectedPostId] = useState(null) // For 1D: show all posts from selected day const [selectedDayPosts, setSelectedDayPosts] = useState(null) useEffect(() => { if (!isConnected || !address) { setSubscribed(false) setHlApiKeySet(false) setBotReadiness('unknown') return } // Clear account-scoped bot state immediately when the connected wallet // changes so a previous wallet's status never leaks into the next one. setSubscribed(false) setHlApiKeySet(false) setBotReadiness('unknown') getUserPublic(address.toLowerCase()) .then((user) => { setSubscribed(user.active) setHlApiKeySet(user.hl_api_key_set) setBotReadiness(user.hl_api_key_set ? 'saved' : 'unknown') }) .catch(() => {}) }, [address, isConnected, setSubscribed, setHlApiKeySet, setBotReadiness]) usePriceSocket({ onPrice: (a, price) => setLivePrice(a, price), onNewPost: (post) => setPosts((prev) => [post as TrumpPost, ...prev].slice(0, 500)), }) useEffect(() => { setCandles([]) getPrices(asset, timeframe) .then(setCandles) .catch(() => {}) }, [asset, timeframe]) const selectedPost = posts.find(p => p.id === selectedPostId) ?? null const recentPosts = posts.slice(0, 8) const lastCandle = candles[candles.length - 1] const firstCandle = candles[0] const priceChange = lastCandle && firstCandle ? ((lastCandle.close - firstCandle.open) / firstCandle.open) * 100 : 0 const totalPosts = posts.length const todayKey = new Date().toISOString().slice(0, 10) const signalsToday = posts.filter(p => p.published_at?.slice(0, 10) === todayKey).length const actionablePosts = posts.filter(p => p.signal === 'buy' || p.signal === 'short').length const winRate = initialPerformance?.win_rate ?? 0 const netPnl = initialPerformance?.net_pnl_usd ?? 0 const hasPriceData = candles.length > 0 const hasPerformanceData = Boolean(initialPerformance) return (

Signal monitor

{actionablePosts} actionable signals · Auto-trader {botReadiness === 'ready' ? 'ready' : hlApiKeySet ? 'saved' : isSubscribed ? 'subscribed' : 'standby'} · {hasPriceData ? 'Live market context' : 'Waiting for market data'}

Live feed {totalPosts} posts tracked
{/* KPI Row */}
BTC
{lastCandle ? '$' + Math.round(lastCandle.close).toLocaleString() : '—'}
= 0 ? 'up' : 'down'}`}>{hasPriceData ? fmtPct(priceChange) : 'Feed pending'} {timeframe}
Signals today
{signalsToday}
{actionablePosts} actionable total
30d Net P&L
{netPnl >= 0 ? '+$' : '-$'}{Math.abs(netPnl).toLocaleString('en-US', { maximumFractionDigits: 0 })}
{hasPerformanceData ? 'Bot performance' : 'Performance pending'}
Win rate
{initialPerformance ? (winRate * 100).toFixed(1) + '%' : '—'}
{hasPerformanceData ? `${initialPerformance?.total_trades ?? 0} trades` : 'Waiting for trade history'}
{/* Left: Chart + signal stream */}
Price · {asset}
{lastCandle ? '$' + Math.round(lastCandle.close).toLocaleString() : '—'}
= 0 ? 'up' : 'down'}`}>{hasPriceData ? fmtPct(priceChange) : 'Feed pending'} · {timeframe}
{(['5m', '15m', '1H', '4H', '1D'] as const).map(t => ( ))}
{ setSelectedDayPosts(null) setSelectedPostId(id) }} onSelectDayPosts={(dayPosts) => { setSelectedDayPosts(dayPosts) setSelectedPostId(null) }} />
Buy signal
Short signal
Hold / filtered
Click marker to see details →
{/* Recent signals list */}

Recent signals

Showing {recentPosts.length} of {totalPosts}
{recentPosts.map(p => ( ))}
{/* Right rail */}
{/* Day-view: all posts from clicked day */} {selectedDayPosts ? (
{selectedDayPosts.length} posts · this candle
{selectedDayPosts.map(p => { const expanded = selectedPostId === p.id const impact = p.price_impact function fmtImpactPct(v: number | null | undefined) { if (v == null || isNaN(v)) return '—' const s = Math.abs(v).toFixed(2) + '%' return v >= 0 ? '+' + s : '-' + s } return (
{/* collapsed row — always visible */}
setSelectedPostId(expanded ? null : p.id)} style={{ padding:12, cursor:'pointer', background: expanded ? 'var(--bg-sunk)' : 'transparent' }}>
{p.sentiment}

{p.text}

{p.ai_confidence}% conf
{/* expanded detail */} {expanded && (
{p.ai_reasoning && ( <>
AI reasoning
{p.ai_reasoning}
)} {impact && ( <>
Price impact · {impact.asset}
{(['m5','m15','m1h'] as const).map((key) => { const label = key === 'm5' ? '5m' : key === 'm15' ? '15m' : '1h' const v = impact[key] const correct = impact[`correct_${key}` as 'correct_m5'|'correct_m15'|'correct_m1h'] return (
{label}
=0?'up':'down'}`} style={{ fontSize:13, fontWeight:600 }}> {fmtImpactPct(v)}
{correct != null && (
{correct ? '✓' : '✗'}
)}
) })}
)}
)}
) })}
) : selectedPost ? ( setSelectedPostId(null)} /> ) : ( )}
) }