trading/dashboard/src/components/Layout.tsx

142 lines
5.6 KiB
TypeScript

import { NavLink, Outlet, useNavigate } from 'react-router-dom';
import { usePortfolio } from '../hooks/usePortfolio';
import { useWebSocket } from '../hooks/useWebSocket';
const navItems = [
{ to: '/portfolio', label: 'Portfolio', icon: 'M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6' },
{ to: '/trades', label: 'Trades', icon: 'M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2' },
{ to: '/strategies', label: 'Strategies', icon: 'M13 10V3L4 14h7v7l9-11h-7z' },
{ to: '/news', label: 'News', icon: 'M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2' },
{ to: '/backtest', label: 'Backtest', icon: 'M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z' },
{ to: '/meet-kevin', label: 'Meet Kevin', icon: 'M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z' },
];
export function Layout() {
const navigate = useNavigate();
const { data: portfolio } = usePortfolio();
const { lastEvent } = useWebSocket();
const handleLogout = () => {
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
navigate('/login');
};
const portfolioValue = portfolio?.total_value ?? 0;
const dailyPnl = portfolio?.daily_pnl ?? 0;
const isActive = portfolio?.trading_active ?? false;
return (
<div className="flex h-screen bg-slate-900">
{/* Sidebar */}
<aside className="w-64 bg-slate-800 border-r border-slate-700 flex flex-col">
<div className="p-6 border-b border-slate-700">
<h1 className="text-xl font-bold text-white">Trading Bot</h1>
<p className="text-xs text-slate-400 mt-1">Automated Trading Dashboard</p>
</div>
<nav className="flex-1 p-4 space-y-1">
{navItems.map((item) => (
<NavLink
key={item.to}
to={item.to}
className={({ isActive }) =>
`flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium transition-colors ${
isActive
? 'bg-blue-600/20 text-blue-400'
: 'text-slate-300 hover:bg-slate-700 hover:text-white'
}`
}
>
<svg
className="w-5 h-5 flex-shrink-0"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d={item.icon}
/>
</svg>
{item.label}
</NavLink>
))}
</nav>
<div className="p-4 border-t border-slate-700">
<button
onClick={handleLogout}
className="flex items-center gap-2 w-full px-3 py-2 text-sm text-slate-400 hover:text-white hover:bg-slate-700 rounded-lg transition-colors"
>
<svg
className="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
/>
</svg>
Sign out
</button>
</div>
</aside>
{/* Main content */}
<div className="flex-1 flex flex-col overflow-hidden">
{/* Top bar */}
<header className="h-16 bg-slate-800 border-b border-slate-700 flex items-center justify-between px-6">
<div className="flex items-center gap-6">
<div>
<span className="text-sm text-slate-400">Portfolio Value</span>
<p className="text-lg font-semibold text-white">
${portfolioValue.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
</p>
</div>
<div>
<span className="text-sm text-slate-400">Daily P&L</span>
<p
className={`text-lg font-semibold ${
dailyPnl >= 0 ? 'text-green-400' : 'text-red-400'
}`}
>
{dailyPnl >= 0 ? '+' : ''}
${dailyPnl.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
</p>
</div>
</div>
<div className="flex items-center gap-3">
{lastEvent && (
<span className="text-xs text-slate-400 bg-slate-700 px-2 py-1 rounded">
{lastEvent.type}
</span>
)}
<div className="flex items-center gap-2">
<span
className={`w-2.5 h-2.5 rounded-full ${
isActive ? 'bg-green-400 animate-pulse' : 'bg-red-400'
}`}
/>
<span className="text-sm text-slate-300">
{isActive ? 'Active' : 'Paused'}
</span>
</div>
</div>
</header>
{/* Page content */}
<main className="flex-1 overflow-auto p-6">
<Outlet />
</main>
</div>
</div>
);
}