L1DraftsPage is a Phase 1 placeholder (AI drafts arrive in Phase 2). L1TicketsPage replaces the stub with a status-filterable internal-tickets queue. L1CoverageBanner renders inside L1RouteGuard so every /l1/* page shows it for engineer-coverers (hidden for native L1). SeatCounterWidget + /api/seats.ts surface engineer + L1 seat usage from the /accounts/me/ seats endpoint (T9). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
34 lines
1.1 KiB
TypeScript
34 lines
1.1 KiB
TypeScript
import { useEffect, useState } from 'react'
|
|
import { seatsApi, type SeatUsage } from '@/api/seats'
|
|
|
|
interface RowProps { label: string; check: SeatUsage['engineer'] }
|
|
|
|
function SeatRow({ label, check }: RowProps) {
|
|
const overLimit = check.limit !== null && check.current > check.limit
|
|
const limitText = check.limit === null ? '∞' : check.limit
|
|
return (
|
|
<div className={overLimit ? 'text-warning' : ''}>
|
|
<p className="text-xs uppercase tracking-wider text-muted-foreground mb-1">{label}</p>
|
|
<p className="text-lg font-mono">{check.current} / {limitText}</p>
|
|
{overLimit && <p className="text-xs">Over limit (grandfathered)</p>}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function SeatCounterWidget() {
|
|
const [usage, setUsage] = useState<SeatUsage | null>(null)
|
|
|
|
useEffect(() => {
|
|
seatsApi.getUsage().then(setUsage).catch(() => setUsage(null))
|
|
}, [])
|
|
|
|
if (!usage) return null
|
|
|
|
return (
|
|
<div className="rounded-lg border border-default bg-card p-4 grid grid-cols-2 gap-4">
|
|
<SeatRow label="Engineer seats" check={usage.engineer} />
|
|
<SeatRow label="L1 seats" check={usage.l1_tech} />
|
|
</div>
|
|
)
|
|
}
|