Files
trumpsignal-frontend/app/[locale]/archive/page.tsx
T
k d50c05b120 fix: pre-launch UI hardening + KOL reduce-action type, proxy IP relay, settings redesign
Frontend half of the pre-launch audit campaign:

- types/index.ts + kol/KolPageClient.tsx: add missing 'reduce' KolAction
  (backend emits it; frontend lacked the type + color/label maps → undefined
  styling). Adds ACTION_COLOR/actionLabel/postActionLabel entries.
- proxy/[...path]/route.ts: relay real client IP (x-forwarded-for / x-real-ip)
  so the backend rate limiter buckets per-user instead of per-Next-server (BUG-02).
- Settings/BotConfigPanel redesign, paper-mode clarity, copy cleanup.
- Assorted page/display fixes, loading states, Pagination component.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 11:57:43 +08:00

122 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. 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, useMemo } from 'react'
import { useLocale } from 'next-intl'
import type { TrumpPost } from '@/types'
import { getPosts } from '@/lib/api'
import PostRow from '@/components/dashboard/PostCards'
import PageHint from '@/components/ui/PageHint'
import Pagination from '@/components/ui/Pagination'
const ARCHIVE_PAGE_SIZE = 30
const LIVE_SOURCES = new Set([
'truth',
'btc_bottom_reversal',
'funding_reversal',
'kol_divergence',
])
/**
* Archive — legacy / test signals (rsi_reversal, sma_reclaim, breakout,
* test, phase1…). NOT a live system. Kept only so old data is inspectable.
*/
export default function ArchivePage() {
const locale = useLocale()
const isZh = false // i18n shelved — Chinese branches kept as dead code for future revival; see messages/zh.json
const [posts, setPosts] = useState<TrumpPost[]>([])
const [loading, setLoading] = useState(true)
const [loadErr, setLoadErr] = useState('')
const [src, setSrc] = useState<string>('all')
const [archivePage, setArchivePage] = useState(1)
useEffect(() => {
getPosts(500, 1)
.then(p => { setPosts(p); setLoadErr('') })
.catch(e => setLoadErr(e instanceof Error ? e.message : (isZh ? '历史归档加载失败' : 'Failed to load archive')))
.finally(() => setLoading(false))
}, [isZh])
// Archive = legacy / test data only. Exclude every live signal source so
// active modules don't leak in here as users explore old experiments. Keep
// this set in sync with sources emitted by app/services/scanners/*.
const archivePosts = useMemo(
() => posts.filter(p => !LIVE_SOURCES.has(p.source || '')),
[posts],
)
const sources = useMemo(() => {
const m: Record<string, number> = {}
for (const p of archivePosts) m[p.source || '?'] = (m[p.source || '?'] || 0) + 1
return Object.entries(m).sort((a, b) => b[1] - a[1])
}, [archivePosts])
const filtered = useMemo(
() => src === 'all' ? archivePosts : archivePosts.filter(p => p.source === src),
[archivePosts, src],
)
const archiveTotalPages = Math.max(1, Math.ceil(filtered.length / ARCHIVE_PAGE_SIZE))
const archiveSafePage = Math.min(archivePage, archiveTotalPages)
const archivePageItems = filtered.slice((archiveSafePage - 1) * ARCHIVE_PAGE_SIZE, archiveSafePage * ARCHIVE_PAGE_SIZE)
return (
<div className="page">
<div className="page-head">
<div>
<h1 className="page-title">Archive</h1>
<PageHint count={`${archivePosts.length} legacy posts`}>
Historical fires from old scanner experiments
(rsi_reversal, sma_reclaim, breakout, test/phase1). Kept only for
inspection the bot doesn't act on these any more.
</PageHint>
</div>
</div>
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginBottom: 12 }}>
{[['all', archivePosts.length] as [string, number], ...sources].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)' }}>{isZh ? '' : 'Loading'}</div>}
{!loading && loadErr && (
<div className="card" style={{ padding: 24, textAlign: 'center', color: 'var(--down)' }}>
⚠️ {isZh ? `无法加载归档:${loadErr}` : `Couldnt load archive — ${loadErr}`}
<div style={{ marginTop: 10 }}>
<button className="btn ghost" style={{ fontSize: 12, padding: '6px 14px' }}
onClick={() => location.reload()}>{isZh ? '' : 'Retry'}</button>
</div>
</div>
)}
{!loading && !loadErr && filtered.length === 0 && (
<div className="card" style={{ padding: 48, textAlign: 'center', color: 'var(--ink-3)' }}>
{isZh ? '' : 'No archived signals.'}
</div>
)}
{!loading && archivePageItems.length > 0 && (
<>
<div className="post-stream">
{archivePageItems.map(p => <PostRow key={p.id} post={p} />)}
</div>
<Pagination
page={archiveSafePage}
total={archiveTotalPages}
count={filtered.length}
pageSize={ARCHIVE_PAGE_SIZE}
onChange={setArchivePage}
/>
</>
)}
</div>
)
}