Files
trumpsignal-frontend/app/[locale]/archive/ArchivePageClient.tsx
T
k 4c3c8c6f87 KOL count: 29 → 25 across marketing/SEO copy
Backend KOL_FEEDS trimmed from 29 to 25 (dead feeds removed).
Sync all hardcoded count mentions:
- layout.tsx JSON-LD, page.tsx (metric + comparison + copy)
- kol/page.tsx, KolPageClient.tsx ("and 26 more" → "and 22 more")
- glossary/page.tsx, opengraph-image.tsx
- public/llms.txt, llms-full.txt
- drop removed KOLs (Dragonfly Capital, Nic Carter) from named lists

Bundles other in-flight frontend work already in the working tree.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 22:55:27 +08:00

152 lines
5.2 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useState, useEffect } from 'react'
import type { TrumpPost } from '@/types'
import { getPosts, getPostsPage, type PostListResponse } from '@/lib/api'
import { hasCached, swrFetch } from '@/lib/cache'
import PostRow from '@/components/dashboard/PostCards'
import PageHint from '@/components/ui/PageHint'
import Pagination from '@/components/ui/Pagination'
import { buildArchiveFallbackResponse } from '@/lib/postPage'
const ARCHIVE_PAGE_SIZE = 30
interface ArchivePageClientProps {
initialData?: PostListResponse | null
}
export default function ArchivePageClient({ initialData = null }: ArchivePageClientProps) {
const [posts, setPosts] = useState<TrumpPost[]>(initialData?.items ?? [])
const [totalPosts, setTotalPosts] = useState(initialData?.total ?? 0)
const [sourceCounts, setSourceCounts] = useState(initialData?.source_counts ?? [])
const [loading, setLoading] = useState(initialData === null)
const [loadErr, setLoadErr] = useState('')
const [src, setSrc] = useState<string>('all')
const [archivePage, setArchivePage] = useState(1)
useEffect(() => {
const key = `archive-page-${archivePage}-src-${src}`
setLoadErr('')
setLoading(posts.length === 0 && !hasCached(key))
swrFetch(
key,
3 * 60_000,
() => getPostsPage(
ARCHIVE_PAGE_SIZE,
archivePage,
undefined,
{
archiveOnly: true,
sourceIn: src === 'all' ? undefined : [src],
},
),
fresh => {
setPosts(fresh.items)
setTotalPosts(fresh.total)
setSourceCounts(fresh.source_counts)
},
)
.then(r => {
setPosts(r.items)
setTotalPosts(r.total)
setSourceCounts(r.source_counts)
})
.catch(async e => {
const detail = e instanceof Error ? e.message : 'Failed to load archive'
if (!detail.includes('404')) {
setLoadErr(detail)
return
}
try {
const legacyPosts = await swrFetch(
'archive-legacy-500',
3 * 60_000,
() => getPosts(500, 1),
)
const fallback = buildArchiveFallbackResponse(legacyPosts, archivePage, ARCHIVE_PAGE_SIZE, src)
setPosts(fallback.items)
setTotalPosts(fallback.total)
setSourceCounts(fallback.source_counts)
setLoadErr('')
} catch (legacyErr) {
setLoadErr(legacyErr instanceof Error ? legacyErr.message : detail)
}
})
.finally(() => setLoading(false))
}, [archivePage, posts.length, src])
const archiveTotalPages = Math.max(1, Math.ceil(totalPosts / ARCHIVE_PAGE_SIZE))
const archiveSafePage = Math.min(archivePage, archiveTotalPages)
const allSourcesCount = sourceCounts.reduce((sum, item) => sum + item.count, 0)
const selectedCount = src === 'all'
? totalPosts
: sourceCounts.find(s => s.source === src)?.count ?? totalPosts
const sourceTabs: [string, number][] = [
['all', allSourcesCount],
...sourceCounts.map(item => [item.source, item.count] as [string, number]),
]
return (
<div className="page">
<div className="page-head">
<div>
<h1 className="page-title">Archive</h1>
<PageHint count={`${selectedCount} legacy posts`}>
Signals from retired scanner experiments
(rsi_reversal, sma_reclaim, breakout, test/phase1). Read-only
the bot no longer acts on any of these.
</PageHint>
</div>
</div>
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginBottom: 12 }}>
{sourceTabs.map(([s, n]) => (
<button
key={s}
onClick={() => { setSrc(s); setArchivePage(1) }}
style={{
padding: '4px 10px', borderRadius: 6, border: '1px solid var(--line)',
background: src === s ? 'var(--ink)' : 'transparent',
color: src === s ? 'var(--bg)' : 'var(--ink-3)',
fontSize: 11, cursor: 'pointer', fontWeight: src === s ? 600 : 400,
}}
>
{s} <span style={{ opacity: 0.6 }}>{n}</span>
</button>
))}
</div>
{loading && <div style={{ textAlign: 'center', padding: 60, color: 'var(--ink-3)' }}>Loading</div>}
{!loading && loadErr && (
<div className="card" style={{ padding: 24, textAlign: 'center', color: 'var(--down)' }}>
{`Couldn't load archive — ${loadErr}`}
<div style={{ marginTop: 10 }}>
<button className="btn ghost" style={{ fontSize: 12, padding: '6px 14px' }}
onClick={() => location.reload()}>Retry</button>
</div>
</div>
)}
{!loading && !loadErr && totalPosts === 0 && (
<div className="card" style={{ padding: 48, textAlign: 'center', color: 'var(--ink-3)' }}>
No archived signals.
</div>
)}
{!loading && posts.length > 0 && (
<>
<div className="post-stream">
{posts.map(p => <PostRow key={p.id} post={p} />)}
</div>
<Pagination
page={archiveSafePage}
total={archiveTotalPages}
count={totalPosts}
pageSize={ARCHIVE_PAGE_SIZE}
onChange={setArchivePage}
/>
</>
)}
</div>
)
}