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>
This commit is contained in:
@@ -5,8 +5,8 @@ import Link from 'next/link'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { useLocale } from 'next-intl'
|
||||
import { useAccount, useSignMessage } from 'wagmi'
|
||||
import type { BotTrade, TrumpPost } from '@/types'
|
||||
import { getTrades, getPosts } from '@/lib/api'
|
||||
import type { BotTrade } from '@/types'
|
||||
import { getTrades, getUserPublic } from '@/lib/api'
|
||||
import { useDashboardStore } from '@/store/dashboard'
|
||||
import { getCachedViewEnvelope, getOrCreateViewEnvelope, type SignedEnvelope } from '@/lib/signedRequest'
|
||||
import TradeTable from '@/components/trades/TradeTable'
|
||||
@@ -18,13 +18,13 @@ export default function TradesPageClient() {
|
||||
const isZh = false // i18n shelved — Chinese branches kept as dead code for future revival; see messages/zh.json
|
||||
const { address, isConnected } = useAccount()
|
||||
const { signMessageAsync } = useSignMessage()
|
||||
const { isSubscribed, hlApiKeySet } = useDashboardStore()
|
||||
const { isSubscribed, hlApiKeySet, paperMode,
|
||||
setSubscribed, setHlApiKeySet, setPaperMode, setBotReadiness } = useDashboardStore()
|
||||
const pathname = usePathname()
|
||||
const locale = pathname.split('/')[1] || 'en'
|
||||
|
||||
const [mounted, setMounted] = useState(false)
|
||||
const [trades, setTrades] = useState<BotTrade[]>([])
|
||||
const [posts, setPosts] = useState<TrumpPost[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [loadErr, setLoadErr] = useState('')
|
||||
const [needsUnlock, setNeedsUnlock] = useState(false)
|
||||
@@ -37,41 +37,77 @@ export default function TradesPageClient() {
|
||||
return () => { aliveRef.current = false }
|
||||
}, [])
|
||||
|
||||
// genRef guards loadAll against stale-closure wallet-switch races.
|
||||
// `snapAddr !== address` inside an async function compares two copies of
|
||||
// the same closure-captured value — it's always equal even when the wallet
|
||||
// has changed, so the check is a no-op. genRef is a mutable ref that every
|
||||
// closure can read, so `gen !== genRef.current` correctly detects staleness.
|
||||
const genRef = useRef(0)
|
||||
useEffect(() => { genRef.current++ }, [address])
|
||||
|
||||
// B41: TradesPageClient can be the entry point (direct navigation to /trades)
|
||||
// without DashboardClient or BotConfigPanel ever running. In that case the
|
||||
// Zustand store has isSubscribed=false (initial default), causing the
|
||||
// "Bot not configured" banner to appear for valid subscribers.
|
||||
// Fix: fetch /user/{wallet}/public here too — it's lightweight, unauthenticated,
|
||||
// and idempotent with the same fetch DashboardClient does.
|
||||
useEffect(() => {
|
||||
if (!address || !isConnected) return
|
||||
let cancelled = false
|
||||
const snap = address
|
||||
getUserPublic(snap.toLowerCase())
|
||||
.then(u => {
|
||||
if (cancelled || snap !== address) return
|
||||
setSubscribed(u.active)
|
||||
setHlApiKeySet(u.hl_api_key_set)
|
||||
setBotReadiness(u.hl_api_key_set ? 'saved' : 'unknown')
|
||||
setPaperMode(!!u.paper_mode)
|
||||
})
|
||||
.catch(() => {})
|
||||
return () => { cancelled = true }
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [address, isConnected])
|
||||
|
||||
// Load public posts always; load private trades only if we have a view
|
||||
// envelope. `forcedEnv` lets the in-page Unlock button pass a freshly-minted
|
||||
// one so the user never has to detour through the Settings page first.
|
||||
async function loadAll(forcedEnv?: SignedEnvelope) {
|
||||
if (!address || !isConnected) {
|
||||
setTrades([]); setPosts([]); setLoading(false); setLoadErr(''); setNeedsUnlock(false)
|
||||
setTrades([]); setLoading(false); setLoadErr(''); setNeedsUnlock(false)
|
||||
return
|
||||
}
|
||||
const gen = genRef.current
|
||||
const snapAddr = address // for API calls only — NOT for stale detection
|
||||
setLoading(true)
|
||||
const env = forcedEnv
|
||||
?? getCachedViewEnvelope('view_trades', address)
|
||||
?? getCachedViewEnvelope('view_user', address)
|
||||
?? getCachedViewEnvelope('view_trades', snapAddr)
|
||||
?? getCachedViewEnvelope('view_user', snapAddr)
|
||||
let failed = false
|
||||
try {
|
||||
const [t, p] = await Promise.all([
|
||||
env
|
||||
? getTrades(address, env, 100, 1).catch(e => {
|
||||
failed = true
|
||||
setLoadErr(e instanceof Error ? e.message : 'Failed to load trades')
|
||||
return [] as BotTrade[]
|
||||
})
|
||||
: Promise.resolve([] as BotTrade[]),
|
||||
getPosts(500, 1).catch(() => [] as TrumpPost[]),
|
||||
])
|
||||
if (!aliveRef.current) return
|
||||
const t = env
|
||||
? await getTrades(snapAddr, env, 500, 1).catch(e => {
|
||||
failed = true
|
||||
setLoadErr(e instanceof Error ? e.message : 'Failed to load trades')
|
||||
return [] as BotTrade[]
|
||||
})
|
||||
: []
|
||||
// Use genRef — NOT `snapAddr !== address` (stale closure: both are the
|
||||
// same closed-over value and the comparison is always false).
|
||||
if (!aliveRef.current || gen !== genRef.current) return
|
||||
setTrades(t)
|
||||
setPosts(p)
|
||||
setNeedsUnlock(!env)
|
||||
if (env && !failed) setLoadErr('')
|
||||
} finally {
|
||||
if (aliveRef.current) setLoading(false)
|
||||
if (aliveRef.current && gen === genRef.current) setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// B27/B35: clear stale previous-wallet data BEFORE the async fetch so the
|
||||
// UI never briefly shows another wallet's private trades.
|
||||
setTrades([])
|
||||
setNeedsUnlock(false)
|
||||
setLoadErr('')
|
||||
// On navigation we only use a cached envelope — never auto-popup the
|
||||
// wallet. If none is cached the user unlocks explicitly via the button.
|
||||
void loadAll()
|
||||
@@ -84,8 +120,12 @@ export default function TradesPageClient() {
|
||||
setUnlocking(true)
|
||||
setLoadErr('')
|
||||
try {
|
||||
// view_user is a superset accepted by /trades, /positions/open,
|
||||
// /positions/today — one signature unlocks everything on this page.
|
||||
// Previously view_trades was used here, but that left OpenPositions
|
||||
// locked (it requires view_positions or view_user).
|
||||
const env = await getOrCreateViewEnvelope({
|
||||
action: 'view_trades', wallet: address, signMessageAsync,
|
||||
action: 'view_user', wallet: address, signMessageAsync,
|
||||
})
|
||||
await loadAll(env)
|
||||
} catch (e) {
|
||||
@@ -97,7 +137,14 @@ export default function TradesPageClient() {
|
||||
}
|
||||
}
|
||||
|
||||
const needsSetup = mounted && isConnected && (!isSubscribed || !hlApiKeySet)
|
||||
// B40: paper users have no HL key but the bot IS configured for them — don't
|
||||
// show "Bot not configured" just because hlApiKeySet is false.
|
||||
// B41: on cold start (navigating directly to /trades), isSubscribed starts
|
||||
// false in the store until DashboardClient or BotConfigPanel fetches
|
||||
// /user/public. Guard with !loading so the banner only appears once
|
||||
// the page has confirmed the wallet is genuinely unconfigured.
|
||||
const needsSetup = mounted && isConnected && !loading &&
|
||||
(!isSubscribed || (!hlApiKeySet && !paperMode))
|
||||
|
||||
return (
|
||||
<div className="page">
|
||||
@@ -105,8 +152,7 @@ export default function TradesPageClient() {
|
||||
<div>
|
||||
<h1 className="page-title">{isZh ? '交易执行' : 'Trades'}</h1>
|
||||
<PageHint>
|
||||
What the bot actually did with your money — currently-open positions
|
||||
on top, closed-trade history with realized P&L below.
|
||||
Open positions above · closed trade history with realized P&L below.
|
||||
</PageHint>
|
||||
</div>
|
||||
</div>
|
||||
@@ -163,7 +209,7 @@ export default function TradesPageClient() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<TradeTable trades={trades} posts={posts} loading={loading} />
|
||||
<TradeTable trades={trades} loading={loading} locked={needsUnlock} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user