style(dashboard): unify Macro composite into one card + dark-mode fix
for Performance accent
User flagged the macro index box on the overview page as feeling
disjointed. Reshaped it from three stacked elements (band → track →
scale) into a single bordered card so it reads as ONE component.
- JSX: wrap the three pieces in <div className="overview-macro-card">
- CSS: new .overview-macro-card with tone-coloured rim (bull/bear/neutral)
- Solid neutral-gray filled needle (was a hollow ring — looked like a
placeholder); tone-coloured background + white inner ring + double
shadow so it stands out on any gradient position
- Removed the .overview-score-fill overlay — the gradient already
encodes the spectrum; layering an opaque fill obscured it near 0
- Thinner track (14px vs 22px), tighter scale labels, smaller pill
- Added "TODAY · 8 INDICATORS" stamp next to the title — gives users
a quick anchor of what they're looking at + freshness
Plus: dark-mode override for .overview-stat-card.accent (the Performance
card). It was using a cream gradient that floated as a glaring
out-of-theme block on dark mode. Mirrored the existing .kpi.accent dark
treatment so it stays visually grouped with the rest of the dashboard.
Also includes the in-flight overview rewrite from the other AI tool
(legacy /btc redirect to /macro, middleware.ts replacing proxy.ts for
Next.js routing, refactored several dashboard panels). TypeScript clean,
production build passes.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,7 @@ import {
|
||||
getCachedViewEnvelope,
|
||||
} from '@/lib/signedRequest'
|
||||
import { walletErrorLabel } from '@/lib/walletError'
|
||||
import { getFirstReadyConnector, walletConnectErrorLabel } from '@/lib/walletConnect'
|
||||
import { confirmSign } from '@/components/wallet/SignConfirmSheet'
|
||||
|
||||
// Defaults shown to a brand-new, un-configured wallet. take_profit_pct,
|
||||
@@ -51,7 +52,7 @@ export default function BotConfigPanel() {
|
||||
const locale = useLocale()
|
||||
const isZh = false // i18n shelved — Chinese branches kept as dead code for future revival; see messages/zh.json
|
||||
const { address, isConnected } = useAccount()
|
||||
const { connect, connectors } = useConnect()
|
||||
const { connectAsync, connectors } = useConnect()
|
||||
const { signMessageAsync } = useSignMessage()
|
||||
const {
|
||||
isSubscribed, hlApiKeySet, hlApiKeyMasked, botReadiness,
|
||||
@@ -97,6 +98,7 @@ export default function BotConfigPanel() {
|
||||
// cached signature; otherwise wait for the user to click "Load settings".
|
||||
const [loadState, setLoadState] = useState<'idle'|'loading'|'loaded'|'err'>('idle')
|
||||
const [loadErr, setLoadErr] = useState('')
|
||||
const [connectErr, setConnectErr] = useState('')
|
||||
|
||||
// Apply a fetched user payload into local form state.
|
||||
function applyUserPayload(u: {
|
||||
@@ -296,10 +298,19 @@ export default function BotConfigPanel() {
|
||||
}
|
||||
}
|
||||
|
||||
const handleConnectWallet = useCallback(() => {
|
||||
const connector = connectors[0]
|
||||
if (connector) connect({ connector })
|
||||
}, [connect, connectors])
|
||||
const handleConnectWallet = useCallback(async () => {
|
||||
setConnectErr('')
|
||||
try {
|
||||
const connector = await getFirstReadyConnector(connectors)
|
||||
if (!connector) {
|
||||
setConnectErr('No wallet connector is available right now.')
|
||||
return
|
||||
}
|
||||
await connectAsync({ connector })
|
||||
} catch (err: unknown) {
|
||||
setConnectErr(walletConnectErrorLabel(err))
|
||||
}
|
||||
}, [connectAsync, connectors])
|
||||
|
||||
// ── Manual-window: arm for N hours, or clear (hours = 0) ──────────────────
|
||||
async function handleManualWindow(hours: number) {
|
||||
@@ -335,7 +346,10 @@ export default function BotConfigPanel() {
|
||||
<p style={{ color: 'var(--ink-3)', marginBottom: 16 }}>
|
||||
{isZh ? '连接钱包后即可配置机器人并查看你的交易。' : 'Connect your wallet to configure the bot and view your trades.'}
|
||||
</p>
|
||||
<button className="btn amber" onClick={handleConnectWallet}>{isZh ? '连接钱包' : 'Connect wallet'}</button>
|
||||
<button className="btn amber" onClick={() => { void handleConnectWallet() }}>{isZh ? '连接钱包' : 'Connect wallet'}</button>
|
||||
{connectErr ? (
|
||||
<p style={{ fontSize: 12, color: 'var(--down)', margin: '12px 0 0' }}>{connectErr}</p>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -370,6 +384,20 @@ export default function BotConfigPanel() {
|
||||
: 'API wallet saved. Recommended next step: keep size tiny and run your own first BTC test before treating the setup as production-ready.')
|
||||
: null
|
||||
|
||||
function ScopePill({
|
||||
label,
|
||||
tone = 'amber',
|
||||
}: {
|
||||
label: string
|
||||
tone?: 'amber' | 'up' | 'violet'
|
||||
}) {
|
||||
return (
|
||||
<span className={`chip ${tone}`} style={{ fontSize: 10, padding: '2px 8px', marginLeft: 8 }}>
|
||||
{label}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
// ── Hyperliquid key strip ─────────────────────────────────────────────────
|
||||
const hlKeyStrip = (
|
||||
<div className="card" style={{ padding: '16px 20px', marginBottom: 16, display: 'grid', gridTemplateColumns: '1fr auto', alignItems: 'center', gap: 16 }}>
|
||||
@@ -537,12 +565,12 @@ export default function BotConfigPanel() {
|
||||
})()}
|
||||
|
||||
{/* ── Main settings card ── */}
|
||||
<div className="card" style={{ padding: 0, overflow: 'hidden' }}>
|
||||
<div className="card" id="bot-config-panel" style={{ padding: 0, overflow: 'hidden' }}>
|
||||
{/* Header bar */}
|
||||
<div style={{ padding: '20px 24px', display: 'flex', alignItems: 'center', justifyContent: 'space-between', borderBottom: '1px solid var(--line)' }}>
|
||||
<div>
|
||||
<div style={{ fontSize: 16, fontWeight: 600, letterSpacing: '-0.01em' }}>Trading bot</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--ink-3)', marginTop: 2 }}>Executes on Hyperliquid when a Trump post clears your filters.</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--ink-3)', marginTop: 2 }}>Controls Trump Signal entries, Macro Vibes BTC management, and shared execution limits on Hyperliquid.</div>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
||||
{dirty && isSubscribed && <span style={{ fontSize: 12, color: 'var(--amber-ink)' }}>● Unsaved</span>}
|
||||
@@ -688,14 +716,17 @@ export default function BotConfigPanel() {
|
||||
) : (
|
||||
<div style={{ padding: '8px 24px 24px' }}>
|
||||
{/* ── EXECUTION ── */}
|
||||
<div className="section-head">
|
||||
<span className="section-head-label">Execution</span>
|
||||
<div className="section-head" id="trump-settings-panel">
|
||||
<span className="section-head-label">Trump Signal settings</span>
|
||||
<div style={{ flex: 1, height: 1, background: 'var(--line)' }} />
|
||||
</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--ink-3)', margin: '4px 0 10px' }}>
|
||||
Event-driven trade sizing, leverage, and signal threshold for Trump-triggered entries.
|
||||
</div>
|
||||
|
||||
<div className="form-row">
|
||||
<div className="form-row-label">
|
||||
Per-trade size
|
||||
Per-trade size <ScopePill label="Trump only" />
|
||||
<span className="hint">Notional in USD. margin ≈ ${(settings.position_size_usd / settings.leverage).toFixed(2)} at {settings.leverage}×</span>
|
||||
</div>
|
||||
<div className="form-row-control">
|
||||
@@ -710,7 +741,7 @@ export default function BotConfigPanel() {
|
||||
|
||||
<div className="form-row">
|
||||
<div className="form-row-label">
|
||||
Leverage <span style={{ opacity: 0.6 }}>(Trump / System 1)</span>
|
||||
Leverage <span style={{ opacity: 0.6 }}>(Trump / System 1)</span> <ScopePill label="Trump only" />
|
||||
<span className="hint">Event-driven scalp positions</span>
|
||||
</div>
|
||||
<div className="form-row-control">
|
||||
@@ -726,43 +757,71 @@ export default function BotConfigPanel() {
|
||||
{(() => {
|
||||
const aggressive = settings.sys2_mode === 'aggressive'
|
||||
return (
|
||||
<>
|
||||
<div className="section-head" id="btc-settings-panel" style={{ marginTop: 20 }}>
|
||||
<span className="section-head-label">Macro Vibes settings</span>
|
||||
<div style={{ flex: 1, height: 1, background: 'var(--line)' }} />
|
||||
</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--ink-3)', margin: '4px 0 10px' }}>
|
||||
Manage-only BTC mode, leverage, and pyramiding behavior for the Macro Vibes system.
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<div className="form-row-label">
|
||||
BTC strategy mode <span style={{ opacity: 0.6 }}>(System 2)</span>
|
||||
BTC strategy mode <span style={{ opacity: 0.6 }}>(System 2)</span> <ScopePill label="BTC only" tone="up" />
|
||||
<span className="hint">
|
||||
A separately-funded sleeve. <strong>Aggressive</strong> =
|
||||
high leverage + earlier/heavier pyramiding + wider
|
||||
trailing → explosive in a clean bull, but a normal
|
||||
25–35% correction will scale you out hard. Both modes
|
||||
stay inside the liquidation line.
|
||||
Choose how hard the BTC system presses winners. Standard is calmer. Aggressive adds more leverage and wider pyramiding.
|
||||
</span>
|
||||
</div>
|
||||
<div className="form-row-control">
|
||||
<div style={{ display: 'flex', gap: 6 }}>
|
||||
<div style={{
|
||||
display: 'inline-flex',
|
||||
gap: 6,
|
||||
padding: 4,
|
||||
borderRadius: 999,
|
||||
background: 'var(--bg-sunk)',
|
||||
border: '1px solid var(--line)',
|
||||
}}>
|
||||
<button type="button"
|
||||
onClick={() => updateSettings({ sys2_mode: 'standard' })}
|
||||
className={`btn ${!aggressive ? '' : 'ghost'}`}
|
||||
style={{ padding: '7px 14px', fontSize: 12, fontWeight: 700 }}>
|
||||
aria-pressed={!aggressive}
|
||||
style={{
|
||||
padding: '8px 16px',
|
||||
fontSize: 12,
|
||||
fontWeight: 700,
|
||||
borderRadius: 999,
|
||||
border: !aggressive ? '1px solid var(--line)' : '1px solid transparent',
|
||||
background: !aggressive ? 'var(--surface)' : 'transparent',
|
||||
color: !aggressive ? 'var(--ink)' : 'var(--ink-3)',
|
||||
boxShadow: !aggressive ? 'var(--shadow-1)' : 'none',
|
||||
cursor: 'pointer',
|
||||
}}>
|
||||
Standard
|
||||
</button>
|
||||
<button type="button"
|
||||
onClick={() => updateSettings({ sys2_mode: 'aggressive' })}
|
||||
className={`btn ${aggressive ? '' : 'ghost'}`}
|
||||
style={{ padding: '7px 14px', fontSize: 12, fontWeight: 700,
|
||||
...(aggressive ? { background: 'var(--down)', color: '#fff', border: 'none' } : {}) }}>
|
||||
aria-pressed={aggressive}
|
||||
style={{
|
||||
padding: '8px 16px',
|
||||
fontSize: 12,
|
||||
fontWeight: 700,
|
||||
borderRadius: 999,
|
||||
border: aggressive ? '1px solid var(--down)' : '1px solid transparent',
|
||||
background: aggressive ? 'var(--down)' : 'transparent',
|
||||
color: aggressive ? '#fff' : 'var(--ink-3)',
|
||||
boxShadow: aggressive ? 'var(--shadow-1)' : 'none',
|
||||
cursor: 'pointer',
|
||||
}}>
|
||||
🔥 Aggressive
|
||||
</button>
|
||||
</div>
|
||||
{aggressive && (
|
||||
<div className="hint" style={{ marginTop: 6, color: 'var(--down)' }}>
|
||||
⚠️ High-risk sleeve: default 8×, pyramids up to +1.5×
|
||||
base, gives back up to 42% off the peak. Fund this
|
||||
separately — expect frequent full scale-outs on
|
||||
corrections in exchange for explosive clean runs.
|
||||
<div className="settings-note warn" style={{ marginTop: 6 }}>
|
||||
High-risk sleeve. Uses more leverage, pyramids harder, and gives back more off the peak. Best treated as a separate risk bucket.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
})()}
|
||||
|
||||
@@ -778,12 +837,9 @@ export default function BotConfigPanel() {
|
||||
return (
|
||||
<div className="form-row">
|
||||
<div className="form-row-label">
|
||||
BTC bottom leverage <span style={{ opacity: 0.6 }}>(System 2)</span>
|
||||
BTC bottom leverage <span style={{ opacity: 0.6 }}>(System 2)</span> <ScopePill label="BTC only" tone="up" />
|
||||
<span className="hint">
|
||||
Independent of Trump. Wrong → scales OUT in 3 stages
|
||||
inside the liquidation line (<strong>never
|
||||
exchange-liquidated</strong>). Right → pyramids IN on a
|
||||
confirmed trend, stop floored at breakeven.
|
||||
Separate from Trump leverage. The bot de-risks in stages before the exchange liquidation line.
|
||||
</span>
|
||||
</div>
|
||||
<div className="form-row-control">
|
||||
@@ -793,16 +849,13 @@ export default function BotConfigPanel() {
|
||||
<div className="ticks"><span>1×</span><span>5×</span><span>10×</span></div>
|
||||
</div>
|
||||
<span className="slider-readout">{lev}×</span>
|
||||
<div className="hint" style={{ marginTop: 6, color: risky ? 'var(--down)' : 'var(--ink-3)' }}>
|
||||
At {lev}× it sheds {e1} near −{(prot * 0.6).toFixed(0)}%,
|
||||
{' '}{e2} near −{(prot * 0.8).toFixed(0)}%, fully out by
|
||||
−{prot.toFixed(0)}% (exchange would liquidate
|
||||
≈ −{liq.toFixed(0)}%).{' '}
|
||||
<div className={`settings-note ${risky ? 'warn' : ''}`} style={{ marginTop: 6 }}>
|
||||
At {lev}× it sheds {e1} near −{(prot * 0.6).toFixed(0)}%, {e2} near −{(prot * 0.8).toFixed(0)}%, and is fully out by −{prot.toFixed(0)}%.
|
||||
Exchange liquidation would be around −{liq.toFixed(0)}%.
|
||||
{' '}
|
||||
{risky
|
||||
? (aggressive
|
||||
? '🔥 Aggressive: a normal 25–35% bull correction will scale you out — you’re betting on clean explosive runs.'
|
||||
: '⚠️ Above 2× a normal 25–35% bull correction can scale you out before the top. To ride a super-bull, keep ≤2× — amplify via pyramiding, not leverage.')
|
||||
: 'Wide enough to ride normal bull corrections; amplification comes from pyramiding.'}
|
||||
? 'Above 2×, a normal bull-market correction can push you out early.'
|
||||
: 'This is wide enough to survive a normal correction; extra upside should come from pyramiding, not leverage.'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -811,7 +864,7 @@ export default function BotConfigPanel() {
|
||||
|
||||
<div className="form-row">
|
||||
<div className="form-row-label">
|
||||
Min AI confidence
|
||||
Min AI confidence <ScopePill label="Trump only" />
|
||||
<span className="hint">Skip any signal below this score (0–100)</span>
|
||||
</div>
|
||||
<div className="form-row-control">
|
||||
@@ -825,14 +878,17 @@ export default function BotConfigPanel() {
|
||||
</div>
|
||||
|
||||
{/* ── LIMITS ── */}
|
||||
<div className="section-head" style={{ marginTop: 20 }}>
|
||||
<span className="section-head-label">Limits & risk</span>
|
||||
<div className="section-head" id="global-settings-panel" style={{ marginTop: 20 }}>
|
||||
<span className="section-head-label">Global risk limits</span>
|
||||
<div style={{ flex: 1, height: 1, background: 'var(--line)' }} />
|
||||
</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--ink-3)', margin: '4px 0 10px' }}>
|
||||
Shared safety rules that cap or gate automated trading across the account.
|
||||
</div>
|
||||
|
||||
<div className="form-row">
|
||||
<div className="form-row-label">
|
||||
Daily budget <span style={{ color: 'var(--down)' }}>*</span>
|
||||
Daily budget <span style={{ color: 'var(--down)' }}>*</span> <ScopePill label="Global" tone="violet" />
|
||||
<span className="hint">Stop opening new trades once the day's total notional reaches this (UTC). Required.</span>
|
||||
</div>
|
||||
<div className="form-row-control">
|
||||
@@ -849,7 +905,7 @@ export default function BotConfigPanel() {
|
||||
|
||||
<div className="form-row">
|
||||
<div className="form-row-label">
|
||||
Take profit <span style={{ color: 'var(--down)' }}>*</span>
|
||||
Take profit <span style={{ color: 'var(--down)' }}>*</span> <ScopePill label="Trump only" />
|
||||
<span className="hint">Auto-close when unrealised gain hits target. Required.</span>
|
||||
</div>
|
||||
<div className="form-row-control">
|
||||
@@ -865,7 +921,7 @@ export default function BotConfigPanel() {
|
||||
|
||||
<div className="form-row">
|
||||
<div className="form-row-label">
|
||||
Stop loss <span style={{ color: 'var(--down)' }}>*</span>
|
||||
Stop loss <span style={{ color: 'var(--down)' }}>*</span> <ScopePill label="Trump only" />
|
||||
<span className="hint">Auto-close when drawdown hits limit. Required.</span>
|
||||
</div>
|
||||
<div className="form-row-control">
|
||||
|
||||
Reference in New Issue
Block a user