dev_arc_aws/src/components/Sidebar.tsx

148 lines
6 KiB
TypeScript
Raw Normal View History

import { useEffect, useState } from 'react'
import { useLocation, Link } from 'react-router-dom'
2026-06-18 08:14:00 -04:00
import {
LayoutGrid,
Server,
Bookmark,
Terminal,
Waypoints,
FolderOpen,
2026-06-18 08:14:00 -04:00
Settings,
ChevronLeft,
ChevronRight,
} from 'lucide-react'
import { api, type Integration } from '../lib/api'
2026-06-18 08:14:00 -04:00
interface SidebarProps {
collapsed: boolean
onToggle: () => void
}
const navItems = [
{ icon: LayoutGrid, label: 'Glance', route: '/' },
{ icon: Server, label: 'Infrastructure', route: '/infrastructure' },
{ icon: Bookmark, label: 'BookNest', route: '/booknest' },
{ icon: Terminal, label: 'Terminal', route: '/terminal' },
{ icon: Waypoints, label: 'Tunnels', route: '/tunnels' },
{ icon: FolderOpen, label: 'Files', route: '/files' },
{ icon: Settings, label: 'Settings', route: '/settings' },
2026-06-18 08:14:00 -04:00
]
export default function Sidebar({ collapsed, onToggle }: SidebarProps) {
const width = collapsed ? 64 : 200
const location = useLocation()
const [integrations, setIntegrations] = useState<Integration[] | null>(null)
useEffect(() => {
api.listIntegrations().then(({ integrations }) => setIntegrations(integrations))
}, [])
const errored = integrations?.filter((i) => i.status === 'error').length ?? 0
const statusOk = errored === 0
const statusColor = statusOk ? '#2ECC71' : '#E74C3C'
const statusLabel = integrations === null ? 'Checking…' : statusOk ? 'All Systems Operational' : `${errored} Issue${errored > 1 ? 's' : ''} Detected`
2026-06-18 08:14:00 -04:00
return (
<aside
className="fixed left-0 top-0 z-50 h-screen overflow-hidden flex flex-col py-6"
2026-06-18 08:14:00 -04:00
style={{ width: `${width}px`, backgroundColor: '#0A0B0D' }}
>
{/* Collapse Toggle — floating on the sidebar/content edge, vertically centered */}
<button
onClick={onToggle}
className="fixed top-1/2 z-50 flex items-center justify-center rounded-full cursor-pointer transition-colors"
style={{
left: `${width - 11}px`,
transform: 'translateY(-50%)',
width: '22px',
height: '22px',
border: '1px solid rgba(200,164,52,0.25)',
backgroundColor: '#15161A',
color: '#7A7D85',
boxShadow: '0 0 8px rgba(0,0,0,0.4)',
}}
onMouseEnter={(e) => { e.currentTarget.style.color = '#C8A434'; e.currentTarget.style.borderColor = 'rgba(200,164,52,0.5)' }}
onMouseLeave={(e) => { e.currentTarget.style.color = '#7A7D85'; e.currentTarget.style.borderColor = 'rgba(200,164,52,0.25)' }}
>
{collapsed ? <ChevronRight size={12} /> : <ChevronLeft size={12} />}
</button>
{/* Logo prominent, centered at top. Blend mode hides the baked-in
dark background of the source PNG so only the gold arc shows. */}
<div className="flex flex-col items-center mb-10" style={{ paddingTop: '4px' }}>
2026-06-18 08:14:00 -04:00
<img
src="/archnest-logo-clean.png"
2026-06-18 08:14:00 -04:00
alt="ArchNest"
style={{
width: collapsed ? '56px' : '168px',
height: 'auto',
filter: 'drop-shadow(0 0 10px rgba(200,164,52,0.3))',
2026-06-18 08:14:00 -04:00
}}
/>
</div>
{/* Nav Items */}
<nav className="flex-1 flex flex-col gap-2 w-full" style={{ padding: collapsed ? '0 8px' : '0 12px' }}>
2026-06-18 08:14:00 -04:00
{navItems.map((item) => {
const Icon = item.icon
const active = location.pathname === item.route
2026-06-18 08:14:00 -04:00
return (
<Link
2026-06-18 08:14:00 -04:00
key={item.label}
to={item.route}
className={`relative flex items-center no-underline transition-all duration-200 ${collapsed ? 'justify-center' : ''}`}
style={{
color: active ? '#C8A434' : '#7A7D85',
gap: collapsed ? '0' : '12px',
padding: collapsed ? '12px 0' : '12px 14px',
borderRadius: '10px',
backgroundColor: active ? 'rgba(200,164,52,0.1)' : 'transparent',
border: active ? '1px solid rgba(200,164,52,0.18)' : '1px solid transparent',
boxShadow: active ? '0 0 14px rgba(200,164,52,0.06)' : 'none',
}}
2026-06-18 08:14:00 -04:00
title={collapsed ? item.label : undefined}
onMouseEnter={(e) => { if (!active) { e.currentTarget.style.backgroundColor = 'rgba(200,164,52,0.05)'; e.currentTarget.style.color = '#C8A434' } }}
onMouseLeave={(e) => { if (!active) { e.currentTarget.style.backgroundColor = 'transparent'; e.currentTarget.style.color = '#7A7D85' } }}
2026-06-18 08:14:00 -04:00
>
{active && (
2026-06-18 08:14:00 -04:00
<div
className="absolute left-0 top-1/2 -translate-y-1/2"
style={{ width: '3px', height: '22px', backgroundColor: '#C8A434', borderRadius: '0 3px 3px 0', boxShadow: '0 0 6px rgba(200,164,52,0.5)' }}
2026-06-18 08:14:00 -04:00
/>
)}
<Icon className="h-5 w-5 shrink-0" strokeWidth={active ? 2 : 1.5} />
2026-06-18 08:14:00 -04:00
{!collapsed && (
<span className="truncate leading-tight font-medium" style={{ fontSize: '13px' }}>
2026-06-18 08:14:00 -04:00
{item.label}
</span>
)}
</Link>
2026-06-18 08:14:00 -04:00
)
})}
</nav>
{/* System Status — rounded block, fits inside nav with breathing room */}
<div style={{ width: '100%', padding: '0 12px 16px 12px' }}>
<div
style={{
border: '1px solid rgba(122, 125, 133, 0.2)',
borderRadius: '10px',
padding: '10px 12px',
backgroundColor: 'rgba(20, 21, 24, 0.5)',
}}
>
<div className="flex items-center gap-2">
<div style={{ width: '12px', height: '12px', borderRadius: '50%', border: `2px solid ${statusColor}`, flexShrink: 0 }} />
2026-06-18 08:14:00 -04:00
{!collapsed && (
<div>
<span style={{ fontSize: '9px', color: '#E8E6E0', display: 'block', lineHeight: 1.3, fontWeight: 500 }}>System Status</span>
<span style={{ fontSize: '8px', color: statusColor, display: 'block', lineHeight: 1.3 }}>{statusLabel}</span>
2026-06-18 08:14:00 -04:00
</div>
)}
</div>
</div>
</div>
</aside>
)
}