← back to Ken
react-dash/src/Layout.jsx
83 lines
import React, { useState, useEffect } from 'react';
import { Outlet, Link, useLocation, useNavigate } from 'react-router-dom';
import { api, checkSession, logout } from './api';
const NAV = [
{ to: '/', label: 'Overview', d: '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: '/markets', label: 'Markets', d: 'M13 7h8m0 0v8m0-8l-8 8-4-4-6 6' },
{ to: '/polymarket', label: 'Polymarket', d: 'M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9' },
{ to: '/weather', label: 'Weather', d: 'M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.001 4.001 0 003 15z' },
{ to: '/models', label: 'Models', d: 'M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z' },
{ to: '/risk', label: 'Risk', d: 'M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z' },
{ to: '/trades', label: 'Trades', d: 'M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4' },
{ to: '/bankroll', label: 'Bankroll', d: 'M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z' },
{ to: '/settings', label: 'Settings', d: 'M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.573 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.573c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z M15 12a3 3 0 11-6 0 3 3 0 016 0z' },
];
export default function Layout() {
const loc = useLocation();
const nav = useNavigate();
const [h, setH] = useState(null);
const [col, setCol] = useState(false);
const [authChecked, setAuthChecked] = useState(false);
useEffect(() => {
// Unified auth: verify session with server
checkSession().then(ok => {
if (!ok) { nav('/login'); return; }
setAuthChecked(true);
const load = () => api('/health').then(d => {
setH(d);
if (d && !d.setup_complete) nav('/setup');
}).catch(() => {});
load();
const i = setInterval(load, 20000);
return () => clearInterval(i);
});
}, []);
if (!authChecked) {
return <div className="flex items-center justify-center min-h-screen"><div className="animate-spin w-8 h-8 border-2 border-blue-500 border-t-transparent rounded-full" /></div>;
}
const cfg = h?.paper_mode;
return (
<div className="flex min-h-screen">
<aside className={`${col ? 'w-16' : 'w-56'} bg-gray-900/50 border-r border-gray-800 flex flex-col transition-all duration-200`}>
<div className="p-4 border-b border-gray-800 flex items-center gap-3">
<div className="w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0" style={{ background: 'linear-gradient(135deg,#3b82f6,#8b5cf6)' }}>
<span className="text-white font-bold text-sm">P</span>
</div>
{!col && <span className="font-bold text-sm bg-gradient-to-r from-blue-400 to-purple-400 bg-clip-text text-transparent">POLY</span>}
</div>
<nav className="flex-1 p-2 space-y-0.5">
{NAV.map(n => {
const active = loc.pathname === n.to || (n.to !== '/' && loc.pathname.startsWith(n.to));
return (
<Link key={n.to} to={n.to} className={`flex items-center gap-3 px-3 py-2 rounded-lg text-sm transition ${active ? 'bg-blue-500/15 text-blue-400 font-medium' : 'text-gray-400 hover:text-gray-200 hover:bg-gray-800/50'}`}>
<svg className="flex-shrink-0" style={{ width: 18, height: 18 }} fill="none" stroke="currentColor" strokeWidth="1.5" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" d={n.d} /></svg>
{!col && <span>{n.label}</span>}
</Link>
);
})}
</nav>
<div className="p-3 border-t border-gray-800">
{!col && h && <div className="space-y-1.5 text-[11px]">
<div className="flex justify-between"><span className="text-gray-500">Mode</span><span className={cfg ? 'text-yellow-400' : 'text-green-400'}>{cfg ? 'PAPER' : 'LIVE'}</span></div>
<div className="flex justify-between"><span className="text-gray-500">Kill Switch</span><span className={h.kill_switch ? 'text-red-400' : 'text-green-400'}>{h.kill_switch ? 'ACTIVE' : 'OFF'}</span></div>
<div className="flex justify-between"><span className="text-gray-500">DB</span><span className={h.db_connected ? 'text-green-400' : 'text-red-400'}>{h.db_connected ? 'OK' : 'DOWN'}</span></div>
</div>}
<div className="flex gap-1 mt-2">
<button onClick={() => setCol(!col)} className="flex-1 flex items-center justify-center py-1.5 text-gray-500 hover:text-gray-300">
<svg className={`w-4 h-4 transition-transform ${col ? '' : 'rotate-180'}`} fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M11 19l-7-7 7-7m8 14l-7-7 7-7" /></svg>
</button>
{!col && <button onClick={logout} className="px-2 py-1 text-xs text-gray-500 hover:text-red-400">Logout</button>}
</div>
</div>
</aside>
<main className="flex-1 overflow-auto"><div className="p-6 max-w-[1600px] mx-auto"><Outlet /></div></main>
</div>
);
}