'use client' import { useState, useEffect, useMemo } from 'react' import { useLocale } from 'next-intl' import type { TrumpPost } from '@/types' import { getPosts } from '@/lib/api' import { swrFetch } from '@/lib/cache' import PostRow, { isAiScored } from '@/components/dashboard/PostCards' import SystemControl from '@/components/signals/SystemControl' import PageHint from '@/components/ui/PageHint' import InfoTip from '@/components/ui/InfoTip' import Pagination from '@/components/ui/Pagination' const PAGE_SIZE = 30 const SENTIMENTS = ['all', 'bullish', 'bearish', 'neutral'] as const type SentimentFilter = (typeof SENTIMENTS)[number] type SignalFilter = 'all' | 'actionable' | 'buy' | 'short' interface TrumpSignalPageProps { initialPosts?: TrumpPost[] | null } export default function TrumpSignalPage({ initialPosts = null }: TrumpSignalPageProps) { const locale = useLocale() const isZh = false const [posts, setPosts] = useState(initialPosts ?? []) const [loading, setLoading] = useState(initialPosts === null) const [loadErr, setLoadErr] = useState('') const [sentFilter, setSentFilter] = useState('all') const [sigFilter, setSigFilter] = useState('all') const [hideNoise, setHideNoise] = useState(false) const [page, setPage] = useState(1) useEffect(() => { swrFetch( 'posts-500', 3 * 60_000, () => getPosts(500, 1), fresh => setPosts(fresh), ) .then(p => { setPosts(p); setLoadErr('') }) .catch(e => setLoadErr(e instanceof Error ? e.message : 'Failed to load posts')) .finally(() => setLoading(false)) }, []) const trumpPosts = useMemo( () => posts.filter(p => (p.source || '') === 'truth'), [posts], ) const noiseCount = useMemo( () => trumpPosts.filter(p => !isAiScored(p)).length, [trumpPosts], ) const filtered = useMemo(() => trumpPosts.filter(p => { if (hideNoise && !isAiScored(p)) return false if (sentFilter !== 'all' && p.sentiment !== sentFilter) return false if (sigFilter === 'actionable' && p.signal !== 'buy' && p.signal !== 'short') return false if (sigFilter === 'buy' && p.signal !== 'buy') return false if (sigFilter === 'short' && p.signal !== 'short') return false return true }), [trumpPosts, sentFilter, sigFilter, hideNoise]) const totalPages = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE)) const safePage = Math.min(page, totalPages) const pageItems = filtered.slice((safePage - 1) * PAGE_SIZE, safePage * PAGE_SIZE) const sigCounts = useMemo(() => ({ all: trumpPosts.length, actionable: trumpPosts.filter(p => p.signal === 'buy' || p.signal === 'short').length, buy: trumpPosts.filter(p => p.signal === 'buy').length, short: trumpPosts.filter(p => p.signal === 'short').length, }), [trumpPosts]) const signalLabels: Record = { all: 'All', actionable: 'Actionable', buy: 'Buy', short: 'Short', } const sentimentLabels: Record = { all: 'All', bullish: 'Bullish', bearish: 'Bearish', neutral: 'Neutral', } return (

① Trump Signal

Watches Trump's Truth Social posts in real time, AI-scores each one, and only fires a trade when conviction is high enough to move the market.
Live
{/* Signal filter tabs */}
{([ { key: 'all', hint: 'Every post the scraper has captured.' }, { key: 'actionable', hint: 'Only posts the AI flagged as BUY or SHORT.' }, { key: 'buy', hint: 'Posts the AI scored as a long signal.' }, { key: 'short', hint: 'Posts the AI scored as a short signal.' }, ] as const).map(f => ( ))}
{/* Sentiment filter */}
{/* One-click collapse of off-topic (non-crypto, un-scored) posts. */} {noiseCount > 0 && ( )} Bias {SENTIMENTS.map(f => ( ))}
{loading && } {!loading && loadErr && (
⚠️ {`Couldn't load signals — ${loadErr}`}
)} {!loading && !loadErr && filtered.length === 0 && (
No Trump signals match the current filter.
)} {!loading && pageItems.length > 0 && ( <>
{pageItems.map(p => )}
)}
) } function TrumpSkeleton() { return (
{Array.from({ length: 6 }).map((_, i) => (
))}
) }