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>
This commit is contained in:
k
2026-05-29 11:57:43 +08:00
parent b76de36af0
commit d50c05b120
32 changed files with 1003 additions and 224 deletions
+26 -5
View File
@@ -10,6 +10,7 @@ import type {
import { getKolPosts, getKolPost, getKolDigest, getKolChanges, getKolDivergence } from '@/lib/api'
import { swrFetch } from '@/lib/cache'
import PageHint from '@/components/ui/PageHint'
import Pagination from '@/components/ui/Pagination'
/**
* KOL feed — independent module. Lists analyzed posts from tracked KOLs
@@ -22,6 +23,7 @@ const ACTION_COLOR: Record<KolTicker['action'], string> = {
bullish: '#16a34a',
sell: '#dc2626',
bearish: '#dc2626',
reduce: '#f59e0b',
mention: '#9ca3af',
}
@@ -31,6 +33,7 @@ function actionLabel(action: KolTicker['action'], isZh: boolean) {
const labels: Record<KolTicker['action'], string> = {
buy: 'BUY',
sell: 'SELL',
reduce: 'Reduce',
bullish: 'Bullish',
bearish: 'Bearish',
mention: 'Mention',
@@ -78,6 +81,7 @@ function postActionLabel(action: string, isZh: boolean) {
const labels: Record<string, string> = {
buy: 'BUY',
sell: 'SELL',
reduce: 'Reducing',
bullish: 'Bullish post',
bearish: 'Bearish post',
}
@@ -780,12 +784,14 @@ export default function KolPage({
const locale = useLocale()
const isZh = false // i18n shelved — Chinese branches kept as dead code for future revival; see messages/zh.json
const dateLocale = isZh ? 'zh-CN' : 'en-US'
const KOL_PAGE_SIZE = 20
const [posts, setPosts] = useState<KolPostSummary[]>(initialPosts ?? [])
const [loading, setLoading] = useState(initialPosts === null)
const [err, setErr] = useState('')
const [openPost, setOpenPost] = useState<KolPostDetail | null>(null)
const [handleFilter, setHandleFilter] = useState<string>('all')
const [tickerFilter, setTickerFilter] = useState<string | null>(null)
const [kolPage, setKolPage] = useState(1)
useEffect(() => {
swrFetch(
@@ -812,6 +818,10 @@ export default function KolPage({
return out
}, [posts, handleFilter, tickerFilter])
const kolTotalPages = Math.max(1, Math.ceil(filtered.length / KOL_PAGE_SIZE))
const kolSafePage = Math.min(kolPage, kolTotalPages)
const kolPageItems = filtered.slice((kolSafePage - 1) * KOL_PAGE_SIZE, kolSafePage * KOL_PAGE_SIZE)
async function openDetail(id: number) {
try {
const detail = await getKolPost(id)
@@ -837,8 +847,10 @@ export default function KolPage({
isZh={isZh}
initialDigest={initialDigest}
activeTicker={tickerFilter}
onTickerClick={(sym) =>
setTickerFilter(prev => prev === sym ? null : sym)}
onTickerClick={(sym) => {
setTickerFilter(prev => prev === sym ? null : sym)
setKolPage(1)
}}
/>
<WalletCheckWidget
@@ -858,7 +870,7 @@ export default function KolPage({
<span style={{ color: 'var(--ink-3)' }}>{isZh ? '当前筛选标的:' : 'Filtered asset:'}</span>
<strong>{tickerFilter}</strong>
<button
onClick={() => setTickerFilter(null)}
onClick={() => { setTickerFilter(null); setKolPage(1) }}
style={{
background: 'var(--bg-sunk)', border: '1px solid var(--line)',
borderRadius: 4, padding: '2px 8px',
@@ -873,7 +885,7 @@ export default function KolPage({
{handles.map(h => (
<button
key={h}
onClick={() => setHandleFilter(h)}
onClick={() => { setHandleFilter(h); setKolPage(1) }}
className={`nav-tab ${handleFilter === h ? 'active' : ''}`}
style={{ border: 'none', cursor: 'pointer' }}
>
@@ -895,7 +907,7 @@ export default function KolPage({
: 'No data yet. Wait for the next feed ingestion run (daily 01:15 UTC).'}
</div>
)}
{filtered.map(p => (
{kolPageItems.map(p => (
<div
key={p.id}
onClick={() => openDetail(p.id)}
@@ -954,6 +966,15 @@ export default function KolPage({
<TickerChips tickers={p.tickers} isZh={isZh} />
</div>
))}
<Pagination
page={kolSafePage}
total={kolTotalPages}
count={filtered.length}
pageSize={KOL_PAGE_SIZE}
onChange={setKolPage}
scrollTop={false}
/>
</div>
)}