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

View File

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

View File

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

View File

@ -51,6 +51,16 @@
--bl-danger-muted: color-mix(in oklab, var(--bl-danger) 12%, var(--background));
--bl-info: var(--accent);
--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-muted: var(--ring-soft);
--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 } = {
'Trend': '#3b82f6',
'Filter': '#a855f7',
'Entry': '#f59e0b',
'Momentum': '#06b6d4',
'Risk': '#ef4444',
'AI': '#10b981',
'Trend': 'var(--bl-info-strong)',
'Filter': 'var(--bl-emphasis)',
'Entry': 'var(--bl-warning)',
'Momentum': 'var(--bl-attention)',
'Risk': 'var(--bl-danger)',
'AI': 'var(--bl-success)',
};
export const AdminTab = ({ botState, socket }: AdminTabProps) => {
@ -147,8 +147,13 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|| hasReconciliationBacklog
);
const systemBadgeLabel = systemCritical ? 'Critical' : systemDegraded ? 'Degraded' : 'Healthy';
const systemBadgeDotClass = systemCritical ? 'bg-red-500' : systemDegraded ? 'bg-orange-500' : 'bg-[#00ff88]';
const systemBadgeTextClass = systemCritical ? 'text-red-400' : systemDegraded ? 'text-orange-400' : 'text-[#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-[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 () => {
setIsControlLoading(true);
@ -305,7 +310,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
{/* Health Summary Grid */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{/* 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>
<div className="flex items-center gap-2">
<div className={`w-2 h-2 rounded-full ${getStatusColor(tradingLoopStatus)}`} />
@ -322,7 +327,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
</div>
{/* 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>
<div className="flex items-center gap-2">
<div className={`w-2 h-2 rounded-full ${getStatusColor(monitorLoopStatus)}`} />
@ -339,7 +344,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
</div>
{/* 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>
<div className="flex items-center gap-2">
<div className={`w-2 h-2 rounded-full ${getStatusColor(reconciliationLoopStatus)}`} />
@ -363,7 +368,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
</div>
{/* 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="flex items-center gap-3">
<ShieldAlert size={14} className="text-orange-400" />
@ -467,7 +472,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
</section>
{/* 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">
<Cpu size={14} className="text-blue-400" />
<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)
.filter(([key]) => key.startsWith('VITE_') || key === 'MODE')
.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-mono text-blue-400/80 bg-blue-500/[0.06] px-2.5 py-1 rounded-lg truncate max-w-[260px]">
{String(value)}
@ -571,7 +576,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
{botConfig ? (
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
{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-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)}
@ -580,7 +585,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
))}
</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" />
<p className="text-xs text-red-500/60 font-medium">Bot offline unable to fetch config</p>
</div>
@ -596,7 +601,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
</div>
<span className="text-[9px] text-zinc-600 font-mono uppercase tracking-widest">Buffer: 100 entries</span>
</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 gap-1.5">
<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 (
<div className="space-y-4">
{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" />
<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>
@ -644,7 +649,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
{rules.length} rules execute sequentially per trading cycle
</p>
<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>
</div>
@ -652,13 +657,13 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
<div className="space-y-2">
{rules.map((rule, idx) => {
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 isLast = idx === rules.length - 1;
return (
<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 */}
<div
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 */}
<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="w-1.5 h-1.5 rounded-full bg-[#00ff88]" />
<span className="text-[9px] font-bold text-[#00ff88]/80 uppercase tracking-wider">Active</span>
<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-[var(--bl-success)]" />
<span className="text-[9px] font-bold text-[var(--bl-success)]/80 uppercase tracking-wider">Active</span>
</span>
</div>
</div>
@ -831,7 +836,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
</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="space-y-1">
<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') {
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} />
<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>
@ -913,8 +918,8 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
{/* Header */}
<header className="flex items-center justify-between">
<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">
<ShieldCheck size={18} className="text-[#00ff88]" />
<div className={`w-10 h-10 rounded-xl ${adminActiveAccentClass} flex items-center justify-center`}>
<ShieldCheck size={18} className="text-[var(--bl-success)]" />
</div>
<div>
<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 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 ? (
<>
<Wifi size={11} className="text-emerald-500" />
@ -938,7 +943,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
</div>
{/* 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}`} />
<span className={`text-[10px] font-bold uppercase tracking-widest ${systemBadgeTextClass}`}>
System: {systemBadgeLabel}
@ -948,7 +953,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
</header>
{/* 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) => {
const Icon = tab.icon;
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]'
}`}
>
<Icon size={13} className={isActive ? 'text-[#00ff88]' : ''} />
<Icon size={13} className={isActive ? 'text-[var(--bl-success)]' : ''} />
<span>{tab.label}</span>
</button>
);