CUB-122: Scaffold Control Center React frontend
All checks were successful
Dev Build / build-test (pull_request) Successful in 1m57s

This commit is contained in:
2026-05-07 20:15:30 -04:00
parent cce3e061a7
commit 8da593c450
116 changed files with 10724 additions and 6611 deletions

View File

@@ -0,0 +1,48 @@
import { Component, type ErrorInfo, type ReactNode } from 'react'
interface Props {
children: ReactNode
}
interface State {
hasError: boolean
error?: Error
}
export default class ErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = { hasError: false }
}
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error }
}
componentDidCatch(error: Error, info: ErrorInfo) {
console.error('ErrorBoundary caught:', error, info)
}
render() {
if (this.state.hasError) {
return (
<div className="min-h-screen flex items-center justify-center bg-surface-darkest p-4">
<div className="max-w-md w-full p-6 rounded-xl border border-danger/30 bg-danger/10 text-center">
<h2 className="text-xl font-bold text-danger mb-2">Something went wrong</h2>
<p className="text-on-surface-variant text-sm mb-4">
{this.state.error?.message || 'An unexpected error occurred.'}
</p>
<button
onClick={() => window.location.reload()}
className="px-4 py-2 rounded-lg bg-primary text-surface-darkest font-medium"
>
Reload Page
</button>
</div>
</div>
)
}
return this.props.children
}
}

View File

@@ -0,0 +1,110 @@
import { useState } from 'react'
import { NavLink } from 'react-router-dom'
import { Command, Activity, FolderKanban, Monitor, Settings, Menu, X } from 'lucide-react'
const navItems = [
{ to: '/', icon: Command, label: 'Hub' },
{ to: '/logs', icon: Activity, label: 'Logs' },
{ to: '/projects', icon: FolderKanban, label: 'Projects' },
{ to: '/sessions', icon: Monitor, label: 'Sessions' },
{ to: '/settings', icon: Settings, label: 'Settings' },
]
export default function Layout({ children }: { children: React.ReactNode }) {
const [expanded, setExpanded] = useState(false)
const [mobileOpen, setMobileOpen] = useState(false)
return (
<div className="flex min-h-screen bg-surface-darkest text-on-surface">
{/* Desktop Nav Rail */}
<aside
className={`hidden md:flex flex-col border-r border-surface-light transition-all duration-200 ${
expanded ? 'w-64' : 'w-18'
}`}
onMouseEnter={() => setExpanded(true)}
onMouseLeave={() => setExpanded(false)}
>
<div className="flex items-center gap-3 px-4 h-16 border-b border-surface-light">
<Command size={24} className="text-primary shrink-0" />
{expanded && <span className="font-bold text-lg whitespace-nowrap">Control Center</span>}
</div>
<nav className="flex-1 py-4 space-y-1">
{navItems.map((item) => (
<NavLink
key={item.to}
to={item.to}
className={({ isActive }) =>
`flex items-center gap-3 px-4 py-3 mx-2 rounded-lg transition-colors ${
isActive
? 'bg-primary/10 text-primary'
: 'text-on-surface-variant hover:bg-surface-light hover:text-on-surface'
}`
}
>
<item.icon size={20} className="shrink-0" />
{expanded && <span className="whitespace-nowrap">{item.label}</span>}
</NavLink>
))}
</nav>
</aside>
{/* Mobile Header + Bottom Nav */}
<div className="flex-1 flex flex-col md:ml-0">
<header className="md:hidden flex items-center justify-between h-16 px-4 border-b border-surface-light bg-surface-dark">
<div className="flex items-center gap-2">
<Command size={22} className="text-primary" />
<span className="font-bold">Control Center</span>
</div>
<button onClick={() => setMobileOpen(!mobileOpen)} className="p-2">
{mobileOpen ? <X size={22} /> : <Menu size={22} />}
</button>
</header>
{/* Mobile drawer */}
{mobileOpen && (
<div className="md:hidden fixed inset-0 z-50 bg-surface-dark/95 backdrop-blur">
<div className="flex flex-col p-4 space-y-2">
{navItems.map((item) => (
<NavLink
key={item.to}
to={item.to}
onClick={() => setMobileOpen(false)}
className={({ isActive }) =>
`flex items-center gap-3 px-4 py-3 rounded-lg ${
isActive
? 'bg-primary/10 text-primary'
: 'text-on-surface-variant'
}`
}
>
<item.icon size={20} />
{item.label}
</NavLink>
))}
</div>
</div>
)}
<main className="flex-1 p-4 md:p-6 overflow-auto">{children}</main>
{/* Mobile Bottom Nav */}
<nav className="md:hidden flex items-center justify-around h-16 border-t border-surface-light bg-surface-dark">
{navItems.slice(0, 5).map((item) => (
<NavLink
key={item.to}
to={item.to}
className={({ isActive }) =>
`flex flex-col items-center gap-1 p-2 text-xs ${
isActive ? 'text-primary' : 'text-on-surface-variant'
}`
}
>
<item.icon size={20} />
<span>{item.label}</span>
</NavLink>
))}
</nav>
</div>
</div>
)
}