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:
@@ -179,11 +179,20 @@ export default function OpenPositions() {
|
||||
useEffect(() => { setMounted(true) }, [])
|
||||
const [closeErr, setCloseErr] = useState('')
|
||||
const [growId, setGrowId] = useState<number | null>(null)
|
||||
// Separate error slot for the Grow toggle so a transient toggle failure
|
||||
// doesn't squat in the panel-header `err` banner (which is otherwise
|
||||
// owned by the 15s poll). Auto-clears after 4s.
|
||||
const [growErr, setGrowErr] = useState('')
|
||||
useEffect(() => {
|
||||
if (!growErr) return
|
||||
const t = setTimeout(() => setGrowErr(''), 4000)
|
||||
return () => clearTimeout(t)
|
||||
}, [growErr])
|
||||
|
||||
async function toggleGrow(p: OpenPosition) {
|
||||
if (!address || growId !== null) return
|
||||
const next = !p.grow_mode
|
||||
setGrowId(p.trade_id)
|
||||
setGrowId(p.trade_id); setGrowErr('')
|
||||
try {
|
||||
const env = await signRequest({
|
||||
action: 'set_trade_grow', wallet: address,
|
||||
@@ -194,9 +203,9 @@ export default function OpenPositions() {
|
||||
x.trade_id === p.trade_id ? { ...x, grow_mode: r.grow_mode } : x))
|
||||
} catch (e) {
|
||||
if (isUserRejection(e)) {
|
||||
setErr(isZh ? 'Grow 切换已取消' : 'Grow toggle cancelled')
|
||||
setGrowErr(isZh ? 'Grow 切换已取消' : 'Grow toggle cancelled')
|
||||
} else {
|
||||
setErr(walletErrorLabel(e, isZh ? 'Grow 切换已取消' : 'Grow toggle cancelled', 90)
|
||||
setGrowErr(walletErrorLabel(e, isZh ? 'Grow 切换已取消' : 'Grow toggle cancelled', 90)
|
||||
|| (isZh ? 'Grow 切换失败' : 'grow toggle failed'))
|
||||
}
|
||||
} finally { setGrowId(null) }
|
||||
@@ -237,7 +246,12 @@ export default function OpenPositions() {
|
||||
void load()
|
||||
const id = setInterval(() => { void load() }, POLL_MS)
|
||||
return () => { cancelled = true; clearInterval(id) }
|
||||
}, [address, isConnected, signMessageAsync, isZh])
|
||||
// signMessageAsync and isZh are intentionally excluded: signMessageAsync is
|
||||
// never called inside this effect (load() uses cached envelopes only), and
|
||||
// isZh is a compile-time constant (always false). Including either would
|
||||
// restart the 15-second poll timer on every wagmi reference change.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [address, isConnected])
|
||||
|
||||
// Trigger close. Two-step: opens modal, user confirms, we sign + POST.
|
||||
async function confirmCloseTrade() {
|
||||
@@ -257,8 +271,15 @@ export default function OpenPositions() {
|
||||
setClosing(null)
|
||||
setCloseState('idle')
|
||||
} catch (e: unknown) {
|
||||
setCloseErr(walletErrorLabel(e, isZh ? '已取消' : 'Cancelled', 120))
|
||||
setCloseState('err')
|
||||
// Distinguish a user-cancelled signature (benign, close the modal) from
|
||||
// a real failure (keep modal open in 'err' state so user can retry or
|
||||
// read the error).
|
||||
if (isUserRejection(e)) {
|
||||
setClosing(null); setCloseState('idle'); setCloseErr('')
|
||||
} else {
|
||||
setCloseErr(walletErrorLabel(e, isZh ? '已取消' : 'Cancelled', 120))
|
||||
setCloseState('err')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,6 +372,9 @@ export default function OpenPositions() {
|
||||
{err && (
|
||||
<span style={{ fontSize: 11, color: 'var(--down)' }}>● {err}</span>
|
||||
)}
|
||||
{growErr && !err && (
|
||||
<span style={{ fontSize: 11, color: 'var(--down)' }}>● {growErr}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Position list (or empty state) */}
|
||||
@@ -396,7 +420,14 @@ export default function OpenPositions() {
|
||||
presses "Close now", reads the impact summary, then confirms. */}
|
||||
{closing && (
|
||||
<div
|
||||
onClick={() => closeState === 'idle' && setClosing(null)}
|
||||
onClick={() => {
|
||||
// Backdrop dismisses in both idle (never started) and err
|
||||
// (failed, user wants out) states. NOT during signing/closing —
|
||||
// the wallet popup or in-flight POST shouldn't be orphaned.
|
||||
if (closeState === 'idle' || closeState === 'err') {
|
||||
setClosing(null); setCloseState('idle'); setCloseErr('')
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
position: 'fixed', inset: 0, zIndex: 1000,
|
||||
background: 'rgba(0,0,0,0.55)',
|
||||
|
||||
Reference in New Issue
Block a user