115 lines
5.1 KiB
TypeScript
115 lines
5.1 KiB
TypeScript
'use client';
|
|
|
|
import Link from 'next/link';
|
|
import { usePathname } from 'next/navigation';
|
|
import { Activity, BookOpenText, ChartCandlestick, Eye } from 'lucide-react';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
type AppShellProps = {
|
|
title: string;
|
|
subtitle?: string;
|
|
actions?: React.ReactNode;
|
|
children: React.ReactNode;
|
|
};
|
|
|
|
const NAV_ITEMS = [
|
|
{ href: '/', label: 'Command Center', icon: Activity },
|
|
{ href: '/filings', label: 'Filings Stream', icon: BookOpenText },
|
|
{ href: '/portfolio', label: 'Portfolio Matrix', icon: ChartCandlestick },
|
|
{ href: '/watchlist', label: 'Watchlist', icon: Eye }
|
|
];
|
|
|
|
export function AppShell({ title, subtitle, actions, children }: AppShellProps) {
|
|
const pathname = usePathname();
|
|
|
|
return (
|
|
<div className="app-surface">
|
|
<div className="ambient-grid" aria-hidden="true" />
|
|
<div className="noise-layer" aria-hidden="true" />
|
|
|
|
<div className="relative z-10 mx-auto flex min-h-screen w-full max-w-[1300px] gap-6 px-4 pb-12 pt-6 md:px-8">
|
|
<aside className="hidden w-72 shrink-0 flex-col gap-6 rounded-2xl border border-[color:var(--line-weak)] bg-[color:var(--panel)] p-5 shadow-[0_0_0_1px_rgba(0,255,180,0.06),0_20px_60px_rgba(1,4,10,0.55)] lg:flex">
|
|
<div>
|
|
<p className="terminal-caption text-xs uppercase tracking-[0.25em] text-[color:var(--terminal-muted)]">Fiscal Clone</p>
|
|
<h1 className="mt-2 text-2xl font-semibold text-[color:var(--terminal-bright)]">Neon Desk</h1>
|
|
<p className="mt-2 text-sm text-[color:var(--terminal-muted)]">
|
|
Financial intelligence cockpit with durable AI workflows.
|
|
</p>
|
|
</div>
|
|
|
|
<nav className="space-y-2">
|
|
{NAV_ITEMS.map((item) => {
|
|
const Icon = item.icon;
|
|
const isActive = pathname === item.href;
|
|
|
|
return (
|
|
<Link
|
|
key={item.href}
|
|
href={item.href}
|
|
className={cn(
|
|
'flex items-center gap-3 rounded-xl border px-3 py-2 text-sm transition-all duration-200',
|
|
isActive
|
|
? 'border-[color:var(--line-strong)] bg-[color:var(--panel-bright)] text-[color:var(--terminal-bright)] shadow-[0_0_18px_rgba(0,255,180,0.16)]'
|
|
: 'border-transparent text-[color:var(--terminal-muted)] hover:border-[color:var(--line-weak)] hover:bg-[color:var(--panel-soft)] hover:text-[color:var(--terminal-bright)]'
|
|
)}
|
|
>
|
|
<Icon className="size-4" />
|
|
{item.label}
|
|
</Link>
|
|
);
|
|
})}
|
|
</nav>
|
|
|
|
<div className="mt-auto rounded-xl border border-[color:var(--line-weak)] bg-[color:var(--panel-soft)] p-3">
|
|
<p className="text-xs uppercase tracking-[0.2em] text-[color:var(--terminal-muted)]">Runtime</p>
|
|
<p className="mt-1 truncate text-sm text-[color:var(--terminal-bright)]">local operator mode</p>
|
|
<p className="mt-2 text-xs text-[color:var(--terminal-muted)]">
|
|
OpenClaw and market data are driven by environment configuration and live API tasks.
|
|
</p>
|
|
</div>
|
|
</aside>
|
|
|
|
<div className="flex-1">
|
|
<header className="mb-6 rounded-2xl border border-[color:var(--line-weak)] bg-[color:var(--panel)] px-6 py-5 shadow-[0_0_0_1px_rgba(0,255,180,0.05),0_14px_40px_rgba(1,4,10,0.5)]">
|
|
<div className="flex flex-wrap items-start justify-between gap-4">
|
|
<div>
|
|
<p className="terminal-caption text-xs uppercase tracking-[0.3em] text-[color:var(--terminal-muted)]">Live System</p>
|
|
<h2 className="mt-2 text-2xl font-semibold text-[color:var(--terminal-bright)] md:text-3xl">{title}</h2>
|
|
{subtitle ? (
|
|
<p className="mt-1 text-sm text-[color:var(--terminal-muted)]">{subtitle}</p>
|
|
) : null}
|
|
</div>
|
|
{actions ? <div className="flex flex-wrap items-center gap-2">{actions}</div> : null}
|
|
</div>
|
|
</header>
|
|
|
|
<nav className="mb-6 flex gap-2 overflow-x-auto rounded-xl border border-[color:var(--line-weak)] bg-[color:var(--panel)] p-2 lg:hidden">
|
|
{NAV_ITEMS.map((item) => {
|
|
const Icon = item.icon;
|
|
const isActive = pathname === item.href;
|
|
|
|
return (
|
|
<Link
|
|
key={item.href}
|
|
href={item.href}
|
|
className={cn(
|
|
'inline-flex min-w-fit items-center gap-2 rounded-lg border px-3 py-2 text-xs transition',
|
|
isActive
|
|
? 'border-[color:var(--line-strong)] bg-[color:var(--panel-bright)] text-[color:var(--terminal-bright)]'
|
|
: 'border-transparent text-[color:var(--terminal-muted)] hover:border-[color:var(--line-weak)] hover:bg-[color:var(--panel-soft)]'
|
|
)}
|
|
>
|
|
<Icon className="size-3.5" />
|
|
{item.label}
|
|
</Link>
|
|
);
|
|
})}
|
|
</nav>
|
|
|
|
<main className="space-y-6">{children}</main>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|