refactor(ui): tokenize operational surfaces

This commit is contained in:
root 2026-05-07 04:06:21 +00:00
parent 373a72e823
commit 7375ad66f8
5 changed files with 239 additions and 205 deletions

View File

@ -1,13 +1,45 @@
import React from 'react'; import React from 'react';
import type { BotState } from '../hooks/useWebSocket'; import type { CSSProperties } from 'react';
import { TrendingUp, Zap } from 'lucide-react'; import type { BotState } from '../hooks/useWebSocket';
import { TrendingUp, Zap } from 'lucide-react';
interface LivePulseTickerProps { interface LivePulseTickerProps {
botState: BotState; botState: BotState;
} }
export const LivePulseTicker: React.FC<LivePulseTickerProps> = ({ botState }) => { export const LivePulseTicker: React.FC<LivePulseTickerProps> = ({ botState }) => {
const symbols = Object.keys(botState.symbols); const tickerShellStyle: CSSProperties = {
height: '40px',
background: 'var(--bl-surface-overlay)',
backdropFilter: 'blur(10px)',
borderTop: '1px solid var(--bl-border-subtle)',
borderBottom: '1px solid var(--bl-border-subtle)',
display: 'flex',
alignItems: 'center',
overflow: 'hidden',
position: 'sticky',
top: '0',
zIndex: 1000,
padding: '0 24px'
};
const marketLabelStyle: CSSProperties = {
fontSize: '10px',
fontWeight: 900,
color: 'var(--bl-text-faint)',
textTransform: 'uppercase',
letterSpacing: '1px'
};
const aiShellStyle: CSSProperties = {
display: 'flex',
alignItems: 'center',
gap: '16px',
marginLeft: 'auto',
background: 'var(--bl-success-muted)',
padding: '0 16px',
height: '100%',
borderLeft: '1px solid var(--bl-success-border)',
};
const symbols = Object.keys(botState.symbols);
const volatileSymbols = symbols const volatileSymbols = symbols
.sort((a, b) => Math.abs(botState.symbols[b].change24h || 0) - Math.abs(botState.symbols[a].change24h || 0)) .sort((a, b) => Math.abs(botState.symbols[b].change24h || 0) - Math.abs(botState.symbols[a].change24h || 0))
.slice(0, 8); .slice(0, 8);
@ -18,71 +50,58 @@ export const LivePulseTicker: React.FC<LivePulseTickerProps> = ({ botState }) =>
.slice(0, 3); .slice(0, 3);
return ( return (
<div style={{ <div style={tickerShellStyle}>
height: '40px', {/* Market Ticker Section */}
background: 'rgba(10, 11, 13, 0.95)', <div style={{ display: 'flex', alignItems: 'center', gap: '8px', borderRight: '1px solid var(--bl-border-soft)', paddingRight: '16px', marginRight: '16px' }}>
backdropFilter: 'blur(10px)', <TrendingUp size={14} className="text-[var(--bl-success)]" />
borderTop: '1px solid rgba(255, 255, 255, 0.05)', <span style={marketLabelStyle}>Market Pulse</span>
borderBottom: '1px solid rgba(255, 255, 255, 0.05)', </div>
display: 'flex',
alignItems: 'center', <div className="ticker-scroll" style={{ display: 'flex', gap: '24px', flex: 1 }}>
overflow: 'hidden', {volatileSymbols.map(s => {
position: 'sticky', const change = botState.symbols[s].change24h || 0;
top: '0', return (
zIndex: 1000, <div key={s} style={{ display: 'flex', alignItems: 'center', gap: '6px', whiteSpace: 'nowrap' }}>
padding: '0 24px' <span style={{ fontSize: '11px', fontWeight: 800, color: 'var(--bl-text-quiet)' }}>{s.split('/')[0]}</span>
}}> <span style={{
{/* Market Ticker Section */} fontSize: '11px',
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', borderRight: '1px solid rgba(255,255,255,0.1)', paddingRight: '16px', marginRight: '16px' }}> fontWeight: 900,
<TrendingUp size={14} className="text-[#00ff88]" /> color: change >= 0 ? 'var(--bl-success)' : 'var(--bl-danger)',
<span style={{ fontSize: '10px', fontWeight: 900, color: '#444', textTransform: 'uppercase', letterSpacing: '1px' }}>Market Pulse</span> fontFamily: 'monospace'
</div> }}>
{change >= 0 ? '+' : ''}{change.toFixed(2)}%
<div className="ticker-scroll" style={{ display: 'flex', gap: '24px', flex: 1 }}> </span>
{volatileSymbols.map(s => { </div>
const change = botState.symbols[s].change24h || 0;
return (
<div key={s} style={{ display: 'flex', alignItems: 'center', gap: '6px', whiteSpace: 'nowrap' }}>
<span style={{ fontSize: '11px', fontWeight: 800, color: '#aaa' }}>{s.split('/')[0]}</span>
<span style={{
fontSize: '11px',
fontWeight: 900,
color: change >= 0 ? '#00ff88' : '#ff3366',
fontFamily: 'monospace'
}}>
{change >= 0 ? '+' : ''}{change.toFixed(2)}%
</span>
</div>
); );
})} })}
</div> </div>
{/* AI Highlight Section */} {/* AI Highlight Section */}
<div style={{ display: 'flex', alignItems: 'center', gap: '16px', marginLeft: 'auto', background: 'rgba(0, 255, 136, 0.03)', padding: '0 16px', height: '100%', borderLeft: '1px solid rgba(0, 255, 136, 0.1)' }}> <div style={aiShellStyle}>
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}> <div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
<Zap size={14} className="text-[#00ff88]" fill="#00ff88" style={{ filter: 'drop-shadow(0 0 4px #00ff88)' }} /> <Zap size={14} className="text-[var(--bl-success)]" fill="currentColor" style={{ filter: 'drop-shadow(0 0 4px color-mix(in oklab, var(--bl-success) 65%, transparent))' }} />
<span style={{ fontSize: '10px', fontWeight: 900, color: '#00ff88', textTransform: 'uppercase', letterSpacing: '1px' }}>AI Top Picks</span> <span style={{ fontSize: '10px', fontWeight: 900, color: 'var(--bl-success)', textTransform: 'uppercase', letterSpacing: '1px' }}>AI Top Picks</span>
</div> </div>
<div style={{ display: 'flex', gap: '12px' }}> <div style={{ display: 'flex', gap: '12px' }}>
{aiSetups.map(s => ( {aiSetups.map(s => (
<div key={s} style={{ display: 'flex', alignItems: 'center', gap: '4px' }}> <div key={s} style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
<span style={{ fontSize: '11px', fontWeight: 800, color: '#fff' }}>{s.split('/')[0]}</span> <span style={{ fontSize: '11px', fontWeight: 800, color: 'var(--foreground)' }}>{s.split('/')[0]}</span>
<span style={{ fontSize: '10px', fontWeight: 900, color: '#00ff88', opacity: 0.6 }}>{botState.symbols[s].rules['AIAnalysisRule']?.metadata?.confidence}%</span> <span style={{ fontSize: '10px', fontWeight: 900, color: 'var(--bl-success)', opacity: 0.6 }}>{botState.symbols[s].rules['AIAnalysisRule']?.metadata?.confidence}%</span>
</div> </div>
))} ))}
{aiSetups.length === 0 && <span style={{ fontSize: '10px', fontWeight: 800, color: '#444' }}>SCANNING...</span>} {aiSetups.length === 0 && <span style={{ fontSize: '10px', fontWeight: 800, color: 'var(--bl-text-faint)' }}>SCANNING...</span>}
</div> </div>
</div> </div>
<style>{` <style>{`
.ticker-scroll { .ticker-scroll {
animation: ticker-slide 30s linear infinite; animation: ticker-slide 30s linear infinite;
} }
@keyframes ticker-slide { @keyframes ticker-slide {
0% { transform: translateX(0); } 0% { transform: translateX(0); }
100% { transform: translateX(-20%); } 100% { transform: translateX(-20%); }
} }
`}</style> `}</style>
</div> </div>
); );
}; };

View File

@ -75,13 +75,13 @@ export function Login() {
</div> </div>
)} )}
{error && <div className="error-message">{error}</div>} {error && <div className="error-message">{error}</div>}
{message && <div className="success-message" style={{ {message && <div className="success-message" style={{
color: '#00ff88', color: 'var(--bl-success)',
background: 'rgba(0, 255, 136, 0.1)', background: 'var(--bl-success-muted)',
padding: '10px', padding: '10px',
borderRadius: '6px', borderRadius: '6px',
marginBottom: '20px', marginBottom: '20px',
textAlign: 'center', textAlign: 'center',
fontSize: '0.9rem' fontSize: '0.9rem'
}}>{message}</div>} }}>{message}</div>}
@ -111,77 +111,77 @@ export function Login() {
</div> </div>
<style>{` <style>{`
.login-container { .login-container {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
min-height: 100vh; min-height: 100vh;
background: #0a0b0d; background: var(--background);
color: #fff; color: var(--foreground);
} }
.login-card { .login-card {
background: #14151a; background: var(--bl-surface-strong);
border: 1px solid rgba(255, 255, 255, 0.1); border: 1px solid var(--bl-border-soft);
padding: 40px; padding: 40px;
border-radius: 16px; border-radius: 16px;
width: 100%; width: 100%;
max-width: 400px; max-width: 400px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5); box-shadow: var(--card-shadow);
} }
h1 { h1 {
margin: 0 0 8px 0; margin: 0 0 8px 0;
font-size: 1.5rem; font-size: 1.5rem;
text-align: center; text-align: center;
background: linear-gradient(90deg, #fff, #00ff88); background: linear-gradient(90deg, var(--foreground), var(--bl-success));
-webkit-background-clip: text; -webkit-background-clip: text;
background-clip: text; background-clip: text;
-webkit-text-fill-color: transparent; -webkit-text-fill-color: transparent;
} }
.subtitle { .subtitle {
text-align: center; text-align: center;
color: #929292; color: var(--bl-text-quiet);
margin-bottom: 32px; margin-bottom: 32px;
font-size: 0.9rem; font-size: 0.9rem;
} }
.form-group { .form-group {
margin-bottom: 20px; margin-bottom: 20px;
} }
label { label {
display: block; display: block;
margin-bottom: 8px; margin-bottom: 8px;
color: #ccc; color: var(--bl-text-quiet);
font-size: 0.9rem; font-size: 0.9rem;
} }
input { input {
width: 100%; width: 100%;
padding: 12px; padding: 12px;
background: rgba(255, 255, 255, 0.05); background: var(--input);
border: 1px solid rgba(255, 255, 255, 0.1); border: 1px solid var(--bl-border-soft);
border-radius: 8px; border-radius: 8px;
color: #fff; color: var(--foreground);
font-size: 1rem; font-size: 1rem;
box-sizing: border-box; box-sizing: border-box;
} }
input:focus { input:focus {
outline: none; outline: none;
border-color: #00ff88; border-color: var(--bl-success);
} }
.auth-button { .auth-button {
width: 100%; width: 100%;
padding: 12px; padding: 12px;
background: #00ff88; background: var(--bl-success);
color: #000; color: var(--primary-foreground);
border: none; border: none;
border-radius: 8px; border-radius: 8px;
font-weight: bold; font-weight: bold;
font-size: 1rem; font-size: 1rem;
cursor: pointer; cursor: pointer;
margin-top: 10px; margin-top: 10px;
@ -191,14 +191,14 @@ export function Login() {
.auth-button:disabled { .auth-button:disabled {
opacity: 0.7; opacity: 0.7;
cursor: not-allowed; cursor: not-allowed;
} }
.error-message { .error-message {
color: #ff3366; color: var(--bl-danger);
background: rgba(255, 51, 102, 0.1); background: var(--bl-danger-muted);
padding: 10px; padding: 10px;
border-radius: 6px; border-radius: 6px;
margin-bottom: 20px; margin-bottom: 20px;
font-size: 0.9rem; font-size: 0.9rem;
text-align: center; text-align: center;
} }
@ -208,19 +208,19 @@ export function Login() {
text-align: center; text-align: center;
} }
.link-button { .link-button {
background: none; background: none;
border: none; border: none;
color: #929292; color: var(--bl-text-quiet);
cursor: pointer; cursor: pointer;
font-size: 0.9rem; font-size: 0.9rem;
text-decoration: underline; text-decoration: underline;
} }
.link-button:hover { .link-button:hover {
color: #fff; color: var(--foreground);
} }
`}</style> `}</style>
</div> </div>
); );
} }

View File

@ -74,24 +74,24 @@ function CenteredMessage({ title, body }: { title: string; body?: string }) {
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
background: '#0a0b0d', background: 'var(--background)',
color: '#f5f5f5', color: 'var(--foreground)',
padding: '2rem', padding: '2rem',
}} }}
> >
<div <div
style={{ style={{
maxWidth: '32rem', maxWidth: '32rem',
border: '1px solid rgba(255,255,255,0.08)', border: '1px solid var(--bl-border-subtle)',
borderRadius: '1rem', borderRadius: '1rem',
padding: '2rem', padding: '2rem',
background: 'rgba(18, 20, 24, 0.92)', background: 'var(--bl-surface-overlay)',
boxShadow: '0 20px 60px rgba(0,0,0,0.35)', boxShadow: 'var(--card-shadow)',
}} }}
> >
<h1 style={{ margin: 0, fontSize: '1.5rem', marginBottom: '0.75rem' }}>{title}</h1> <h1 style={{ margin: 0, fontSize: '1.5rem', marginBottom: '0.75rem' }}>{title}</h1>
{body ? ( {body ? (
<p style={{ margin: 0, color: 'rgba(255,255,255,0.72)', lineHeight: 1.6 }}>{body}</p> <p style={{ margin: 0, color: 'var(--bl-text-quiet)', lineHeight: 1.6 }}>{body}</p>
) : null} ) : null}
</div> </div>
</div> </div>

View File

@ -51,6 +51,16 @@
--bl-danger-muted: color-mix(in oklab, var(--bl-danger) 12%, var(--background)); --bl-danger-muted: color-mix(in oklab, var(--bl-danger) 12%, var(--background));
--bl-info: var(--accent); --bl-info: var(--accent);
--bl-info-muted: var(--accent-soft); --bl-info-muted: var(--accent-soft);
--bl-info-strong: #3b82f6;
--bl-attention: #06b6d4;
--bl-emphasis: #a855f7;
--bl-surface-strong: color-mix(in oklab, var(--card) 88%, var(--foreground));
--bl-surface-overlay: color-mix(in oklab, var(--background) 90%, var(--foreground));
--bl-surface-highlight: color-mix(in oklab, var(--card-elevated) 86%, var(--foreground));
--bl-border-subtle: color-mix(in oklab, var(--border) 62%, transparent);
--bl-border-soft: color-mix(in oklab, var(--border-strong) 50%, transparent);
--bl-text-quiet: color-mix(in oklab, var(--muted-foreground) 82%, var(--background));
--bl-text-faint: color-mix(in oklab, var(--muted-foreground) 58%, var(--background));
--bl-focus-ring: var(--ring); --bl-focus-ring: var(--ring);
--bl-focus-ring-muted: var(--ring-soft); --bl-focus-ring-muted: var(--ring-soft);
--bl-radius-control: 0.5rem; --bl-radius-control: 0.5rem;

View File

@ -34,12 +34,12 @@ const ruleDescriptions: { [key: string]: { desc: string; category: string; icon:
}; };
const categoryColors: { [key: string]: string } = { const categoryColors: { [key: string]: string } = {
'Trend': '#3b82f6', 'Trend': 'var(--bl-info-strong)',
'Filter': '#a855f7', 'Filter': 'var(--bl-emphasis)',
'Entry': '#f59e0b', 'Entry': 'var(--bl-warning)',
'Momentum': '#06b6d4', 'Momentum': 'var(--bl-attention)',
'Risk': '#ef4444', 'Risk': 'var(--bl-danger)',
'AI': '#10b981', 'AI': 'var(--bl-success)',
}; };
export const AdminTab = ({ botState, socket }: AdminTabProps) => { export const AdminTab = ({ botState, socket }: AdminTabProps) => {
@ -147,8 +147,13 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|| hasReconciliationBacklog || hasReconciliationBacklog
); );
const systemBadgeLabel = systemCritical ? 'Critical' : systemDegraded ? 'Degraded' : 'Healthy'; const systemBadgeLabel = systemCritical ? 'Critical' : systemDegraded ? 'Degraded' : 'Healthy';
const systemBadgeDotClass = systemCritical ? 'bg-red-500' : systemDegraded ? 'bg-orange-500' : 'bg-[#00ff88]'; const systemBadgeDotClass = systemCritical ? 'bg-red-500' : systemDegraded ? 'bg-orange-500' : 'bg-[var(--bl-success)]';
const systemBadgeTextClass = systemCritical ? 'text-red-400' : systemDegraded ? 'text-orange-400' : 'text-[#00ff88]'; const systemBadgeTextClass = systemCritical ? 'text-red-400' : systemDegraded ? 'text-orange-400' : 'text-[var(--bl-success)]';
const adminPanelClass = 'bg-[var(--bl-surface-strong)] border border-[var(--bl-border-subtle)]';
const adminPanelOverlayClass = 'bg-[var(--bl-surface-overlay)] border border-[var(--bl-border-subtle)]';
const adminPanelHoverClass = 'bg-[var(--bl-surface-strong)] border border-[var(--bl-border-subtle)] hover:border-[var(--bl-border-soft)]';
const adminNavigationClass = 'bg-[var(--bl-surface-overlay)] border border-[var(--bl-border-subtle)]';
const adminActiveAccentClass = 'bg-[var(--bl-success)]/10 border border-[var(--bl-success)]/15';
const handlePauseTrading = async () => { const handlePauseTrading = async () => {
setIsControlLoading(true); setIsControlLoading(true);
@ -305,7 +310,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
{/* Health Summary Grid */} {/* Health Summary Grid */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4"> <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{/* Trading Engine Card */} {/* Trading Engine Card */}
<div className="bg-[#12131a] border border-white/[0.04] rounded-2xl p-5"> <div className={`${adminPanelClass} rounded-2xl p-5`}>
<p className="text-[9px] text-zinc-500 uppercase tracking-widest mb-1">Trading Engine</p> <p className="text-[9px] text-zinc-500 uppercase tracking-widest mb-1">Trading Engine</p>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className={`w-2 h-2 rounded-full ${getStatusColor(tradingLoopStatus)}`} /> <div className={`w-2 h-2 rounded-full ${getStatusColor(tradingLoopStatus)}`} />
@ -322,7 +327,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
</div> </div>
{/* Position Monitor Card */} {/* Position Monitor Card */}
<div className="bg-[#12131a] border border-white/[0.04] rounded-2xl p-5"> <div className={`${adminPanelClass} rounded-2xl p-5`}>
<p className="text-[9px] text-zinc-500 uppercase tracking-widest mb-1">Position Monitor</p> <p className="text-[9px] text-zinc-500 uppercase tracking-widest mb-1">Position Monitor</p>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className={`w-2 h-2 rounded-full ${getStatusColor(monitorLoopStatus)}`} /> <div className={`w-2 h-2 rounded-full ${getStatusColor(monitorLoopStatus)}`} />
@ -339,7 +344,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
</div> </div>
{/* Reconciliation Card */} {/* Reconciliation Card */}
<div className="bg-[#12131a] border border-white/[0.04] rounded-2xl p-5"> <div className={`${adminPanelClass} rounded-2xl p-5`}>
<p className="text-[9px] text-zinc-500 uppercase tracking-widest mb-1">Reconciliation</p> <p className="text-[9px] text-zinc-500 uppercase tracking-widest mb-1">Reconciliation</p>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className={`w-2 h-2 rounded-full ${getStatusColor(reconciliationLoopStatus)}`} /> <div className={`w-2 h-2 rounded-full ${getStatusColor(reconciliationLoopStatus)}`} />
@ -363,7 +368,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
</div> </div>
{/* Operational Events (Admin Error Panel) */} {/* Operational Events (Admin Error Panel) */}
<section className="bg-[#12131a] border border-white/[0.04] rounded-2xl overflow-hidden"> <section className={`${adminPanelClass} rounded-2xl overflow-hidden`}>
<div className="px-5 py-4 border-b border-white/[0.04] flex items-center justify-between"> <div className="px-5 py-4 border-b border-white/[0.04] flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<ShieldAlert size={14} className="text-orange-400" /> <ShieldAlert size={14} className="text-orange-400" />
@ -467,7 +472,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
</section> </section>
{/* Detailed Metrics */} {/* Detailed Metrics */}
<section className="bg-[#12131a] border border-white/[0.04] rounded-2xl p-5"> <section className={`${adminPanelClass} rounded-2xl p-5`}>
<div className="flex items-center gap-3 mb-6"> <div className="flex items-center gap-3 mb-6">
<Cpu size={14} className="text-blue-400" /> <Cpu size={14} className="text-blue-400" />
<h3 className="text-xs font-bold text-zinc-300 uppercase tracking-wider">Performance Telemetry</h3> <h3 className="text-xs font-bold text-zinc-300 uppercase tracking-wider">Performance Telemetry</h3>
@ -552,7 +557,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
{Object.entries(import.meta.env) {Object.entries(import.meta.env)
.filter(([key]) => key.startsWith('VITE_') || key === 'MODE') .filter(([key]) => key.startsWith('VITE_') || key === 'MODE')
.map(([key, value]) => ( .map(([key, value]) => (
<div key={key} className="flex items-center justify-between gap-4 px-4 py-3 bg-[#12131a] border border-white/[0.04] rounded-xl"> <div key={key} className={`flex items-center justify-between gap-4 px-4 py-3 ${adminPanelClass} rounded-xl`}>
<span className="text-[10px] font-bold text-zinc-500 uppercase tracking-wider truncate">{key}</span> <span className="text-[10px] font-bold text-zinc-500 uppercase tracking-wider truncate">{key}</span>
<span className="text-[10px] font-mono text-blue-400/80 bg-blue-500/[0.06] px-2.5 py-1 rounded-lg truncate max-w-[260px]"> <span className="text-[10px] font-mono text-blue-400/80 bg-blue-500/[0.06] px-2.5 py-1 rounded-lg truncate max-w-[260px]">
{String(value)} {String(value)}
@ -571,7 +576,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
{botConfig ? ( {botConfig ? (
<div className="grid grid-cols-1 md:grid-cols-2 gap-2"> <div className="grid grid-cols-1 md:grid-cols-2 gap-2">
{Object.entries(botConfig).map(([key, value]) => ( {Object.entries(botConfig).map(([key, value]) => (
<div key={key} className="flex items-center justify-between gap-4 px-4 py-3 bg-[#12131a] border border-white/[0.04] rounded-xl"> <div key={key} className={`flex items-center justify-between gap-4 px-4 py-3 ${adminPanelClass} rounded-xl`}>
<span className="text-[10px] font-bold text-zinc-500 uppercase tracking-wider truncate">{key}</span> <span className="text-[10px] font-bold text-zinc-500 uppercase tracking-wider truncate">{key}</span>
<span className="text-[10px] font-mono text-orange-400/80 bg-orange-500/[0.06] px-2.5 py-1 rounded-lg truncate max-w-[260px]"> <span className="text-[10px] font-mono text-orange-400/80 bg-orange-500/[0.06] px-2.5 py-1 rounded-lg truncate max-w-[260px]">
{Array.isArray(value) ? value.join(', ') : String(value)} {Array.isArray(value) ? value.join(', ') : String(value)}
@ -580,7 +585,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
))} ))}
</div> </div>
) : ( ) : (
<div className="flex items-center justify-center gap-3 py-12 bg-[#12131a] border border-dashed border-red-500/10 rounded-xl"> <div className={`flex items-center justify-center gap-3 py-12 ${adminPanelClass} border-dashed border-red-500/10 rounded-xl`}>
<WifiOff size={18} className="text-red-500/40" /> <WifiOff size={18} className="text-red-500/40" />
<p className="text-xs text-red-500/60 font-medium">Bot offline unable to fetch config</p> <p className="text-xs text-red-500/60 font-medium">Bot offline unable to fetch config</p>
</div> </div>
@ -596,7 +601,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
</div> </div>
<span className="text-[9px] text-zinc-600 font-mono uppercase tracking-widest">Buffer: 100 entries</span> <span className="text-[9px] text-zinc-600 font-mono uppercase tracking-widest">Buffer: 100 entries</span>
</div> </div>
<div className="bg-[#0c0d12] border border-white/[0.04] rounded-xl overflow-hidden shadow-2xl"> <div className={`${adminPanelOverlayClass} rounded-xl overflow-hidden shadow-2xl`}>
<div className="flex items-center gap-2 px-4 py-2 bg-white/[0.02] border-b border-white/[0.04]"> <div className="flex items-center gap-2 px-4 py-2 bg-white/[0.02] border-b border-white/[0.04]">
<div className="flex gap-1.5"> <div className="flex gap-1.5">
<div className="w-2.5 h-2.5 rounded-full bg-red-500/20" /> <div className="w-2.5 h-2.5 rounded-full bg-red-500/20" />
@ -631,7 +636,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
return ( return (
<div className="space-y-4"> <div className="space-y-4">
{rules.length === 0 ? ( {rules.length === 0 ? (
<div className="flex flex-col items-center justify-center py-20 bg-[#12131a] border border-dashed border-white/[0.06] rounded-xl"> <div className={`flex flex-col items-center justify-center py-20 ${adminPanelClass} border-dashed border-white/[0.06] rounded-xl`}>
<Hexagon size={28} className="text-zinc-700 mb-4" /> <Hexagon size={28} className="text-zinc-700 mb-4" />
<p className="text-sm font-semibold text-zinc-500">No Active Rules</p> <p className="text-sm font-semibold text-zinc-500">No Active Rules</p>
<p className="text-[11px] text-zinc-600 mt-1 max-w-xs text-center">Connect to the bot service to load the pipeline.</p> <p className="text-[11px] text-zinc-600 mt-1 max-w-xs text-center">Connect to the bot service to load the pipeline.</p>
@ -644,7 +649,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
{rules.length} rules execute sequentially per trading cycle {rules.length} rules execute sequentially per trading cycle
</p> </p>
<span className="flex items-center gap-1.5 text-[10px] text-zinc-500"> <span className="flex items-center gap-1.5 text-[10px] text-zinc-500">
<span className="w-1.5 h-1.5 rounded-full bg-[#00ff88] animate-pulse" /> All active <span className="w-1.5 h-1.5 rounded-full bg-[var(--bl-success)] animate-pulse" /> All active
</span> </span>
</div> </div>
@ -652,13 +657,13 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
<div className="space-y-2"> <div className="space-y-2">
{rules.map((rule, idx) => { {rules.map((rule, idx) => {
const info = ruleDescriptions[rule] || { desc: 'Technical strategy rule.', category: 'System', icon: Hexagon }; const info = ruleDescriptions[rule] || { desc: 'Technical strategy rule.', category: 'System', icon: Hexagon };
const color = categoryColors[info.category] || '#6b7280'; const color = categoryColors[info.category] || 'var(--bl-text-quiet)';
const RuleIcon = info.icon; const RuleIcon = info.icon;
const isLast = idx === rules.length - 1; const isLast = idx === rules.length - 1;
return ( return (
<React.Fragment key={rule}> <React.Fragment key={rule}>
<div className="group relative bg-[#12131a] border border-white/[0.04] rounded-xl hover:border-white/[0.08] transition-all duration-200"> <div className={`group relative ${adminPanelHoverClass} rounded-xl transition-all duration-200`}>
{/* Left accent */} {/* Left accent */}
<div <div
className="absolute left-0 top-3 bottom-3 w-[3px] rounded-full opacity-50 group-hover:opacity-100 transition-opacity" className="absolute left-0 top-3 bottom-3 w-[3px] rounded-full opacity-50 group-hover:opacity-100 transition-opacity"
@ -707,9 +712,9 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
{/* Status */} {/* Status */}
<div className="shrink-0"> <div className="shrink-0">
<span className="flex items-center gap-1.5 px-2.5 py-1 bg-[#00ff88]/[0.06] border border-[#00ff88]/10 rounded-full"> <span className="flex items-center gap-1.5 px-2.5 py-1 bg-[var(--bl-success)]/10 border border-[var(--bl-success)]/10 rounded-full">
<span className="w-1.5 h-1.5 rounded-full bg-[#00ff88]" /> <span className="w-1.5 h-1.5 rounded-full bg-[var(--bl-success)]" />
<span className="text-[9px] font-bold text-[#00ff88]/80 uppercase tracking-wider">Active</span> <span className="text-[9px] font-bold text-[var(--bl-success)]/80 uppercase tracking-wider">Active</span>
</span> </span>
</div> </div>
</div> </div>
@ -831,7 +836,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
</div> </div>
</div> </div>
<div className="bg-[#12131a] border border-white/[0.04] rounded-2xl overflow-hidden p-5 space-y-5"> <div className={`${adminPanelClass} rounded-2xl overflow-hidden p-5 space-y-5`}>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="space-y-1"> <div className="space-y-1">
<p className="text-xs font-bold text-white">State Snapshots</p> <p className="text-xs font-bold text-white">State Snapshots</p>
@ -900,7 +905,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
if (profile?.role !== 'admin') { if (profile?.role !== 'admin') {
return ( return (
<div className="p-8 text-center bg-[#12131a] border border-red-500/20 rounded-2xl"> <div className={`p-8 text-center ${adminPanelClass} border-red-500/20 rounded-2xl`}>
<XCircle className="mx-auto text-red-500 mb-4" size={48} /> <XCircle className="mx-auto text-red-500 mb-4" size={48} />
<h2 className="text-xl font-bold text-white mb-2">Access Denied</h2> <h2 className="text-xl font-bold text-white mb-2">Access Denied</h2>
<p className="text-zinc-500">You do not have administrative privileges to access this area.</p> <p className="text-zinc-500">You do not have administrative privileges to access this area.</p>
@ -913,8 +918,8 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
{/* Header */} {/* Header */}
<header className="flex items-center justify-between"> <header className="flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-[#00ff88]/10 border border-[#00ff88]/15 flex items-center justify-center"> <div className={`w-10 h-10 rounded-xl ${adminActiveAccentClass} flex items-center justify-center`}>
<ShieldCheck size={18} className="text-[#00ff88]" /> <ShieldCheck size={18} className="text-[var(--bl-success)]" />
</div> </div>
<div> <div>
<h2 className="text-lg font-bold text-white tracking-tight">Admin Panel</h2> <h2 className="text-lg font-bold text-white tracking-tight">Admin Panel</h2>
@ -923,7 +928,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
</div> </div>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="flex items-center gap-1.5 px-3 py-1.5 bg-[#12131a] border border-white/[0.04] rounded-lg"> <div className={`flex items-center gap-1.5 px-3 py-1.5 ${adminPanelClass} rounded-lg`}>
{botConfig ? ( {botConfig ? (
<> <>
<Wifi size={11} className="text-emerald-500" /> <Wifi size={11} className="text-emerald-500" />
@ -938,7 +943,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
</div> </div>
{/* System Status Badge */} {/* System Status Badge */}
<div className={`flex items-center gap-1.5 px-3 py-1.5 bg-[#12131a] border border-white/[0.04] rounded-lg`}> <div className={`flex items-center gap-1.5 px-3 py-1.5 ${adminPanelClass} rounded-lg`}>
<div className={`w-1.5 h-1.5 rounded-full ${systemBadgeDotClass}`} /> <div className={`w-1.5 h-1.5 rounded-full ${systemBadgeDotClass}`} />
<span className={`text-[10px] font-bold uppercase tracking-widest ${systemBadgeTextClass}`}> <span className={`text-[10px] font-bold uppercase tracking-widest ${systemBadgeTextClass}`}>
System: {systemBadgeLabel} System: {systemBadgeLabel}
@ -948,7 +953,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
</header> </header>
{/* Sub Navigation */} {/* Sub Navigation */}
<nav className="flex gap-1 bg-[#0e0f14] border border-white/[0.04] rounded-xl p-1"> <nav className={`flex gap-1 ${adminNavigationClass} rounded-xl p-1`}>
{subTabs.map((tab) => { {subTabs.map((tab) => {
const Icon = tab.icon; const Icon = tab.icon;
const isActive = subTab === tab.id; const isActive = subTab === tab.id;
@ -961,7 +966,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
: 'text-zinc-500 hover:text-zinc-300 hover:bg-white/[0.02]' : 'text-zinc-500 hover:text-zinc-300 hover:bg-white/[0.02]'
}`} }`}
> >
<Icon size={13} className={isActive ? 'text-[#00ff88]' : ''} /> <Icon size={13} className={isActive ? 'text-[var(--bl-success)]' : ''} />
<span>{tab.label}</span> <span>{tab.label}</span>
</button> </button>
); );