- DB migration 023: tenant_subscriptions and usage_events tables - UsageMeteringMiddleware: in-memory counters, 60s flush to DB via UPSERT - FreeTierEnforcementMiddleware: 10 agents / 1,000 calls/day limits, Redis cache - UsageService: getDailyUsage and getActiveAgentCount - BillingService: Stripe checkout sessions, webhook verification, subscription status - POST /billing/checkout, POST /billing/webhook, GET /billing/usage endpoints - BILLING_ENABLED=false disables enforcement without breaking metering - Dashboard: Usage tab with Free Tier/Pro badges and metric cards - 19 unit tests passing across billing services and middleware Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
64 lines
1.9 KiB
TypeScript
64 lines
1.9 KiB
TypeScript
import * as React from 'react';
|
|
import { NavLink, Outlet } from 'react-router-dom';
|
|
import { cn } from '@/lib/utils';
|
|
import { useAuth } from '@/lib/auth';
|
|
|
|
interface NavItem {
|
|
to: string;
|
|
label: string;
|
|
}
|
|
|
|
const NAV_ITEMS: NavItem[] = [
|
|
{ to: '/dashboard/agents', label: 'Agents' },
|
|
{ to: '/dashboard/audit', label: 'Audit Log' },
|
|
{ to: '/dashboard/health', label: 'Health' },
|
|
{ to: '/dashboard/usage', label: 'Usage' },
|
|
];
|
|
|
|
/**
|
|
* Outer application shell: top navigation bar and main content area.
|
|
* Renders the active page via <Outlet />.
|
|
*/
|
|
export function AppShell(): React.JSX.Element {
|
|
const { logout } = useAuth();
|
|
|
|
return (
|
|
<div className="min-h-screen bg-slate-50">
|
|
<header className="border-b border-slate-200 bg-white shadow-sm">
|
|
<div className="mx-auto flex max-w-7xl items-center justify-between px-4 py-3">
|
|
<div className="flex items-center gap-8">
|
|
<span className="text-lg font-bold text-brand-700">SentryAgent.ai</span>
|
|
<nav className="flex gap-1">
|
|
{NAV_ITEMS.map(({ to, label }) => (
|
|
<NavLink
|
|
key={to}
|
|
to={to}
|
|
className={({ isActive }) =>
|
|
cn(
|
|
'rounded-md px-3 py-2 text-sm font-medium transition-colors',
|
|
isActive
|
|
? 'bg-brand-50 text-brand-700'
|
|
: 'text-slate-600 hover:bg-slate-100 hover:text-slate-900',
|
|
)
|
|
}
|
|
>
|
|
{label}
|
|
</NavLink>
|
|
))}
|
|
</nav>
|
|
</div>
|
|
<button
|
|
onClick={logout}
|
|
className="text-sm text-slate-500 hover:text-slate-900"
|
|
>
|
|
Sign out
|
|
</button>
|
|
</div>
|
|
</header>
|
|
<main className="mx-auto max-w-7xl px-4 py-8">
|
|
<Outlet />
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|