chore(ts-migration): migrate the non-agent pages to TypeScript

Phase 9, first batch: 18 pages. Seventeen emit byte-identical JavaScript, the
eighteenth differs only by a JSDoc cast becoming a real annotation, and the
production bundle is byte-identical to be49184.

SCOPE, decided by imports. Twenty-two of the forty pages import `lib/skills`,
`lib/agents`, `ai-assistant`, `components/skills`, `ui-tree` or `ui-editor`, and
are deferred to Phase 11 with the rest of that region. `Owliver.jsx` is in THIS
batch despite the name: it imports only `krowHooks`, `krowScore`, `krowAi` and
`ui/button`, and is a profile-building conversation page rather than any part of
the agent runtime.

`DesignSystem` is the headline result. It was the single worst file in Phase 6 -
111 errors when the design system was first renamed - and it arrived here with
none, because those were never its errors: they were the primitives' inferred
props, and fixing them at the source fixed every consumer.

Three pages needed real work, and each was the author's own intent made
explicit:

  - `admin/Login` carried `useState(/** @type {{email?: string, password?: string}} */ ({}))`.
    JSDoc casts stop applying in a `.tsx` file, so that became a real type
    argument, and `validate`'s accumulator - built empty and filled per failed
    rule - needed the same shape. The login flow itself is untouched: the
    generic 401 message, the 429 branch and the `remember` field all stand.

  - `Owliver` gets `new Promise<void>`, because its `resolve()` takes no
    argument.

  - `Candidates` names the element type of a `Set` built from `any[]`, which
    otherwise infers `Set<unknown>` and makes every option a `ReactNode` error.

LINT CAUGHT A REGRESSION THE OTHER CHECKS DID NOT. The generator adds an entity
import when it sees the type NAME anywhere in the file, which for four pages
with no props at all left an import nothing used - four `unused-imports` ERRORS,
taking `npm run lint` from exit 0 to exit 1 while typecheck, tests, the bundle
and seventeen of eighteen emit comparisons all stayed green. Removed. The
generator's import rule is too eager and wants narrowing before the next batch.

Verified: tsc 21 -> 21, set-difference showing zero introduced and zero removed;
zero errors in any of the 18 pages; lint back to 0 errors and 289 warnings;
npm test 1684/1691 with the same seven failures; Owliver baseline 59/59;
production bundle byte-identical; baseline artifacts untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8
This commit is contained in:
2026-09-18 12:47:04 +05:30
parent be49184006
commit fcdaa32f4d
18 changed files with 89 additions and 20 deletions

766
src/pages/DesignSystem.tsx Normal file
View File

@@ -0,0 +1,766 @@
import React, { useState } from 'react';
import {
Activity, AlertCircle, Award, Brain, Briefcase, Calendar, CheckCircle2, Clock,
Download, Inbox, MoreHorizontal, Pencil, Plus, Star, Trash2, TrendingUp, Users,
} from 'lucide-react';
import { Bar, BarChart, CartesianGrid, Tooltip, XAxis, YAxis } from 'recharts';
import {
ActivityCard, Alert, Avatar, AvatarGroup, Badge, Breadcrumb, BreadcrumbItem,
BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button,
ChartContainer, ChartTooltip, Checkbox, ConfirmModal, DataTable, Drawer,
DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger,
EmptyState, Field, FilterBar, Grid, IconButton, InlineLoading, Input, KpiCard,
LoadingState, MetricCard, Modal, MultiSelect, PageHeader, PageSection,
Pagination, Popover, PopoverContent, PopoverTrigger, ProgressBar, ProgressRing,
ProgressSteps, RadioGroup, RadioGroupItem, ScoreMeter, SearchInput,
SectionHeader, SegmentedToggle, Select, SelectContent, SelectItem,
SelectTrigger, SelectValue, SkeletonCard, SkeletonKpi, SkeletonList,
SkeletonTable, SkeletonText, Spinner, Stack, StatusBadge, Surface,
Tabs, Textarea, Timeline, Toggle, Tooltip as DsTooltip, TooltipContent,
TooltipProvider, TooltipTrigger, toast,
} from '@/components/ds';
import { AXIS_PROPS, CHART_TONES } from '@/components/ds';
/**
* Design system reference.
*
* A live specimen sheet for every component, variant and state. It exists so
* design decisions are reviewable side by side rather than hunted across
* feature pages, and so a regression in a shared component is visible in one
* place. Not linked from the product navigation — reach it at /design-system.
*/
interface SpecProps {
title?: string;
description?: any;
children: React.ReactNode;
className?: string;
}
function Spec({ title, description, children, className = '' }: SpecProps) {
return (
<Surface variant="solid" radius="xl" padding="lg" className={className}>
<SectionHeader title={title} subtitle={description} />
<div className="space-y-4">{children}</div>
</Surface>
);
}
interface RowProps {
label?: string;
children: React.ReactNode;
}
function Row({ label = '', children }: RowProps) {
return (
<div className="flex flex-wrap items-center gap-3">
{label && <span className="text-caption text-ink-4 w-24 shrink-0">{label}</span>}
<div className="flex flex-wrap items-center gap-2">{children}</div>
</div>
);
}
const TABLE_ROWS = [
{ id: 1, name: 'Chef Antoine Dubois', role: 'Executive Chef', score: 97, status: 'hired', exp: 12 },
{ id: 2, name: 'Marcus Williams', role: 'Event Security', score: 94, status: 'hired', exp: 5 },
{ id: 3, name: 'Sofia Mendez', role: 'Event Server', score: 89, status: 'ai_screened', exp: 4 },
{ id: 4, name: 'Tanya Cruz', role: 'Event Server', score: 83, status: 'interview', exp: 3 },
{ id: 5, name: 'Jordan Blake', role: 'Bartender', score: 72, status: 'interview', exp: 3 },
{ id: 6, name: 'Sam Okafor', role: 'Bartender', score: 28, status: 'ai_screened', exp: 0 },
];
const CHART_DATA = [
{ label: '0–39', count: 1 },
{ label: '40–59', count: 0 },
{ label: '60–79', count: 3 },
{ label: '80–100', count: 5 },
];
export default function DesignSystem() {
const [tab, setTab] = useState('foundation');
const [search, setSearch] = useState('');
const [multi, setMulti] = useState(['Bartender']);
const [filters, setFilters] = useState({ stage: 'all', position: [] });
const [modalOpen, setModalOpen] = useState(false);
const [confirmOpen, setConfirmOpen] = useState(false);
const [drawerOpen, setDrawerOpen] = useState(false);
const [toggled, setToggled] = useState(true);
const [segment, setSegment] = useState('30d');
const [selected, setSelected] = useState([]);
const [step, setStep] = useState(1);
const [page, setPage] = useState(2);
const TABS = [
{ value: 'foundation', label: 'Foundation' },
{ value: 'controls', label: 'Controls', count: 12 },
{ value: 'feedback', label: 'Feedback' },
{ value: 'data', label: 'Data' },
];
return (
<Stack space="xl">
<PageHeader
title="Design System"
subtitle="Every shared component, variant and state — the foundation all pages build on."
icon={Award}
size="display"
breadcrumb={
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem><BreadcrumbLink href="/">KROW Forge</BreadcrumbLink></BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem><BreadcrumbPage>Design System</BreadcrumbPage></BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
}
actions={<Button variant="outline" size="sm"><Download />Export tokens</Button>}
>
<Tabs tabs={TABS} value={tab} onChange={setTab} layoutGroupId="ds" />
</PageHeader>
{/* ── Foundation ──────────────────────────────────────────────────── */}
{tab === 'foundation' && (
<Stack space="lg">
<Spec title="Colour" description="Brand, semantic and ink scales. All resolve to CSS variables.">
<div className="space-y-4">
<Row label="Brand">
{[
['krow-blue', '#0838E0'], ['krow-blue-dark', '#062BAF'], ['krow-yellow', '#F9E547'],
['krow-yellow-pale', '#F8E08E'], ['krow-navy', '#333F48'], ['krow-mint', '#D1E0D7'],
].map(([name, hex]) => (
<div key={name} className="text-center">
<div className="w-16 h-12 rounded-lg border border-border shadow-xs" style={{ background: hex }} />
<p className="text-[10px] text-ink-3 mt-1">{name}</p>
<p className="text-[9px] text-ink-4 font-mono">{hex}</p>
</div>
))}
</Row>
<Row label="Semantic">
{['bg-success', 'bg-warning', 'bg-destructive', 'bg-info', 'bg-neutral'].map((c) => (
<div key={c} className="text-center">
<div className={`w-16 h-12 rounded-lg border border-border ${c}`} />
<p className="text-[10px] text-ink-3 mt-1">{c.replace('bg-', '')}</p>
</div>
))}
</Row>
<Row label="Ink">
{['bg-ink-1', 'bg-ink-2', 'bg-ink-3', 'bg-ink-4'].map((c) => (
<div key={c} className="text-center">
<div className={`w-16 h-12 rounded-lg ${c}`} />
<p className="text-[10px] text-ink-3 mt-1">{c.replace('bg-', '')}</p>
</div>
))}
</Row>
</div>
</Spec>
<Spec title="Typography" description="The named scale. Pages use these instead of arbitrary pixel values.">
<div className="space-y-2">
{[
['text-display-lg', 'Display Large'], ['text-display', 'Display'],
['text-title-lg', 'Title Large'], ['text-title', 'Title'],
['text-body-lg', 'Body Large'], ['text-body', 'Body'],
['text-body-sm', 'Body Small'], ['text-caption', 'Caption'],
['text-overline uppercase', 'Overline'],
].map(([cls, label]) => (
<div key={cls} className="flex items-baseline gap-4 border-b border-border pb-2 last:border-0">
<span className="text-caption text-ink-4 font-mono w-40 shrink-0">{cls}</span>
<span className={`${cls} font-heading text-ink-1`}>{label}</span>
</div>
))}
</div>
</Spec>
<Spec title="Elevation & radius" description="A four-step shadow scale plus the brand and panel treatments.">
<Row>
{['shadow-xs', 'shadow-sm', 'shadow-md', 'shadow-lg', 'shadow-xl', 'shadow-brand'].map((s) => (
<div key={s} className={`w-28 h-20 rounded-xl bg-surface border border-border grid place-items-center ${s}`}>
<span className="text-[10px] text-ink-3 font-mono">{s.replace('shadow-', '')}</span>
</div>
))}
</Row>
<Row label="Radius">
{['rounded-sm', 'rounded-md', 'rounded-lg', 'rounded-xl', 'rounded-2xl', 'rounded-full'].map((r) => (
<div key={r} className={`w-20 h-14 bg-krow-blue-tint border border-krow-blue/20 grid place-items-center ${r}`}>
<span className="text-[9px] text-krow-blue font-mono">{r.replace('rounded-', '')}</span>
</div>
))}
</Row>
</Spec>
<Spec title="Surface" description="The one container. Every card in the app is one of these.">
<Grid cols={3}>
{['glass', 'solid', 'subtle', 'sunken', 'outline', 'panel'].map((v) => (
<Surface key={v} variant={v} radius="xl" padding="default">
<p className={`text-body-sm font-semibold ${v === 'panel' ? 'text-panel-foreground' : 'text-ink-1'}`}>{v}</p>
<p className={`text-caption mt-1 ${v === 'panel' ? 'text-panel-muted' : 'text-ink-3'}`}>
variant=&quot;{v}&quot;
</p>
</Surface>
))}
</Grid>
</Spec>
<Spec title="Layout" description="Grid presets that avoid orphan cards at every breakpoint.">
{[6, 4, 3, 2].map((cols) => (
<div key={cols}>
<p className="text-caption text-ink-4 mb-2">cols={cols}</p>
<Grid cols={cols}>
{Array.from({ length: cols }).map((_, i) => (
<div key={i} className="h-12 rounded-lg bg-krow-blue-tint border border-krow-blue/20" />
))}
</Grid>
</div>
))}
</Spec>
</Stack>
)}
{/* ── Controls ────────────────────────────────────────────────────── */}
{tab === 'controls' && (
<Stack space="lg">
<Spec title="Button" description="One implementation. Variant, size, shape and loading are props.">
<Row label="Variant">
<Button>Primary</Button>
<Button variant="navy">Navy</Button>
<Button variant="accent">Accent</Button>
<Button variant="outline">Outline</Button>
<Button variant="soft">Soft</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="destructive">Destructive</Button>
<Button variant="glass">Glass</Button>
</Row>
<Row label="Size">
<Button size="xs">XS</Button>
<Button size="sm">Small</Button>
<Button>Default</Button>
<Button size="lg">Large</Button>
<Button size="xl">XL</Button>
</Row>
<Row label="Shape">
<Button shape="pill">Pill</Button>
<Button shape="rounded">Rounded</Button>
<Button shape="square">Square</Button>
</Row>
<Row label="State">
<Button loading>Loading</Button>
<Button disabled>Disabled</Button>
<Button><Plus />With icon</Button>
<Button variant="link">Link style</Button>
</Row>
<Row label="Icon only">
<IconButton icon={Pencil} label="Edit" variant="outline" />
<IconButton icon={Trash2} label="Delete" variant="destructive-ghost" />
<IconButton icon={MoreHorizontal} label="More" variant="ghost" />
<IconButton icon={Plus} label="Add" size="sm" />
<IconButton icon={Download} label="Download" size="lg" variant="soft" />
</Row>
</Spec>
<Spec title="Text input" description="Field owns the label, hint and error wiring.">
<Grid cols={2}>
<Field label="Full name" required hint="As it appears on their ID.">
<Input placeholder="Chef Antoine Dubois" />
</Field>
<Field label="Email" error="That email is already on file.">
<Input defaultValue="antoine@" />
</Field>
<Field label="Search">
<SearchInput value={search} onChange={setSearch} placeholder="Search candidates…" />
</Field>
<Field label="Disabled">
<Input disabled placeholder="Not editable" />
</Field>
<Field label="Notes" className="sm:col-span-2">
<Textarea placeholder="Anything the hiring manager should know…" rows={3} />
</Field>
</Grid>
</Spec>
<Spec title="Selection" description="Single select, multi select, and the boolean controls.">
<Grid cols={2}>
<Field label="Role category">
<Select defaultValue="bartender">
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="bartender">Bartender</SelectItem>
<SelectItem value="server">Server</SelectItem>
<SelectItem value="chef">Chef</SelectItem>
<SelectItem value="security">Security</SelectItem>
</SelectContent>
</Select>
</Field>
<Field label="Required certifications" hint="Chips are removable inline.">
<MultiSelect
options={['Bartender', 'ServSafe', 'TIPS Certified', 'Guard Card', 'CPR / First Aid', 'Food Handler Card', 'OSHA 10', 'Forklift Operator', 'RBS Alcohol Server']}
value={multi}
onChange={setMulti}
placeholder="Select certifications"
/>
</Field>
</Grid>
<div className="grid sm:grid-cols-2 gap-6 pt-2">
<div className="space-y-3">
<Field label="Accept applications" inline hint="Candidates can apply immediately.">
<Checkbox defaultChecked />
</Field>
<Field label="Require certification upload" inline>
<Checkbox />
</Field>
<RadioGroup defaultValue="fluent" className="space-y-2 pt-2">
{['basic', 'conversational', 'fluent', 'native'].map((level) => (
<div key={level} className="flex items-center gap-2.5">
<RadioGroupItem value={level} id={`lvl-${level}`} />
<label htmlFor={`lvl-${level}`} className="text-body-sm text-ink-2 capitalize cursor-pointer">
{level}
</label>
</div>
))}
</RadioGroup>
</div>
<div className="space-y-4">
<Toggle
label="Auto-screen new applicants"
description="Score every application the moment it arrives."
checked={toggled}
onCheckedChange={setToggled}
/>
<Toggle label="Email me a daily digest" checked={false} onCheckedChange={() => {}} />
<Toggle label="Disabled setting" checked={false} onCheckedChange={() => {}} disabled />
<div className="pt-1">
<p className="text-caption text-ink-4 mb-2">Segmented</p>
<SegmentedToggle
options={[{ value: '7d', label: '7 days' }, { value: '30d', label: '30 days' }, { value: '90d', label: '90 days' }]}
value={segment}
onChange={setSegment}
ariaLabel="Date range"
/>
</div>
</div>
</div>
</Spec>
<Spec title="Navigation" description="Tabs, breadcrumbs, pagination and step progress.">
<Row label="Pill tabs">
<Tabs tabs={TABS} value={tab} onChange={setTab} layoutGroupId="ds-demo-pill" />
</Row>
<Row label="Underline">
<Tabs
variant="underline"
tabs={[{ value: 'all', label: 'All', count: 22 }, { value: 'screened', label: 'Screened', count: 14 }, { value: 'hired', label: 'Hired', count: 3 }]}
value="all"
onChange={() => {}}
layoutGroupId="ds-demo-underline"
/>
</Row>
<div className="pt-2">
<p className="text-caption text-ink-4 mb-3">Steps</p>
<ProgressSteps
steps={['Basics', 'Requirements', 'Vetting', 'Review']}
current={step}
onStepClick={setStep}
/>
<div className="flex gap-2 mt-4">
<Button size="sm" variant="outline" onClick={() => setStep(Math.max(0, step - 1))}>Back</Button>
<Button size="sm" onClick={() => setStep(Math.min(3, step + 1))}>Next</Button>
</div>
</div>
<div className="pt-2">
<Pagination page={page} pageCount={12} onPageChange={setPage} totalItems={287} pageSize={25} onPageSizeChange={() => {}} />
</div>
</Spec>
<Spec title="Overlays" description="Modal, drawer, dropdown, popover and tooltip.">
<Row>
<Button variant="outline" onClick={() => setModalOpen(true)}>Open modal</Button>
<Button variant="outline" onClick={() => setDrawerOpen(true)}>Open drawer</Button>
<Button variant="destructive" onClick={() => setConfirmOpen(true)}>Confirm dialog</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild><Button variant="outline">Dropdown</Button></DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem><Pencil className="w-4 h-4 mr-2" />Edit</DropdownMenuItem>
<DropdownMenuItem><Download className="w-4 h-4 mr-2" />Export</DropdownMenuItem>
<DropdownMenuItem className="text-destructive focus:text-destructive">
<Trash2 className="w-4 h-4 mr-2" />Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<Popover>
<PopoverTrigger asChild><Button variant="outline">Popover</Button></PopoverTrigger>
<PopoverContent className="w-64">
<p className="text-body-sm font-semibold text-ink-1">Score breakdown</p>
<p className="text-caption text-ink-3 mt-1">Weighted across five dimensions.</p>
</PopoverContent>
</Popover>
<TooltipProvider>
<DsTooltip>
<TooltipTrigger asChild><Button variant="ghost">Hover me</Button></TooltipTrigger>
<TooltipContent>Tooltips use the same radius and shadow</TooltipContent>
</DsTooltip>
</TooltipProvider>
</Row>
</Spec>
</Stack>
)}
{/* ── Feedback ────────────────────────────────────────────────────── */}
{tab === 'feedback' && (
<Stack space="lg">
<Spec title="Badge & status" description="StatusBadge maps a domain state to one canonical colour and label.">
<Row label="Badge">
{['default', 'soft', 'accent', 'navy', 'success', 'warning', 'destructive', 'info', 'neutral', 'outline'].map((v) => (
<Badge key={v} variant={v}>{v}</Badge>
))}
</Row>
<Row label="Application">
{['applied', 'ai_screened', 'shortlisted', 'interview', 'hired', 'rejected'].map((s) => (
<StatusBadge key={s} status={s} dot />
))}
</Row>
<Row label="Posting">
{['active', 'draft', 'paused', 'closed'].map((s) => <StatusBadge key={s} status={s} />)}
</Row>
<Row label="Bands">
{['elite', 'excellent', 'solid', 'building', 'new', 'no_score'].map((s) => (
<StatusBadge key={s} status={s} />
))}
</Row>
</Spec>
<Spec title="Alert" description="Persistent, inline conditions. Transient confirmations use toast.">
<Alert tone="info" title="Screening is recommendation only">
Owliver ranks and recommends. Every hiring decision stays with a human recruiter.
</Alert>
<Alert tone="success" title="9 candidates screened">All applications now carry a comparable score.</Alert>
<Alert tone="warning" title="8 applicants unscored" onDismiss={() => {}}>
Those decisions are being made without a standard.
</Alert>
<Alert tone="destructive" title="Certification expired" action={<Button size="sm" variant="destructive">Request update</Button>}>
Marcus Williams&apos; Guard Card lapsed on 12 July.
</Alert>
</Spec>
<Spec title="Toast" description="Wraps react-hot-toast so styling and duration live in one place.">
<Row>
<Button variant="outline" size="sm" onClick={() => toast.success('Candidate shortlisted')}>Success</Button>
<Button variant="outline" size="sm" onClick={() => toast.error('Screening failed — try again')}>Error</Button>
<Button variant="outline" size="sm" onClick={() => toast.info('All candidates already screened')}>Info</Button>
<Button
variant="outline"
size="sm"
onClick={() => toast.promise(
new Promise((r) => setTimeout(r, 1800)),
{ loading: 'Screening 8 candidates…', success: 'Screened 8 candidates', error: 'Screening failed' }
)}
>
Promise
</Button>
</Row>
</Spec>
<Spec title="Empty state" description="Distinguishes nothing-yet from nothing-matching.">
<Grid cols={2}>
<Surface variant="subtle" padding="none" radius="xl">
<EmptyState
icon={Inbox}
title="No candidates yet"
description="Share the application link and applicants will appear here automatically."
action={{ label: 'Copy apply link', onClick: () => toast.success('Link copied') }}
size="sm"
/>
</Surface>
<Surface variant="subtle" padding="none" radius="xl">
<EmptyState
icon={Inbox}
variant="filtered"
title="No matching candidates"
description="No one matches the current filters. Try widening the score range."
action={{ label: 'Clear filters', onClick: () => {} }}
size="sm"
/>
</Surface>
</Grid>
</Spec>
<Spec title="Loading" description="Skeletons mirror final geometry; spinners are for unknown shapes.">
<Row label="Spinner">
<Spinner size="xs" /><Spinner size="sm" /><Spinner /><Spinner size="lg" />
<InlineLoading message="Screening candidates…" />
</Row>
<Grid cols={4} className="pt-2">
<SkeletonKpi /><SkeletonKpi /><SkeletonKpi /><SkeletonKpi />
</Grid>
<Grid cols={2}>
<SkeletonCard />
<Surface variant="solid" radius="xl" padding="default"><SkeletonList rows={3} /></Surface>
</Grid>
<Surface variant="solid" radius="xl" padding="none" className="overflow-hidden">
<SkeletonTable rows={3} columns={4} />
</Surface>
<Surface variant="solid" radius="xl" padding="default">
<SkeletonText lines={3} />
</Surface>
<Row label="Region"><LoadingState message="Loading analytics…" size="sm" /></Row>
</Spec>
</Stack>
)}
{/* ── Data ────────────────────────────────────────────────────────── */}
{tab === 'data' && (
<Stack space="lg">
<PageSection title="KPI cards">
<Grid cols={6}>
<KpiCard icon={Briefcase} value={5} label="Open Positions" tone="brand" index={0} />
<KpiCard icon={Users} value={22} label="Total Candidates" tone="navy" index={1} />
<KpiCard icon={Brain} value={14} label="AI Screened" tone="brand" index={2} />
<KpiCard icon={Clock} value={8} label="Pending Screen" tone="accent" index={3} />
<KpiCard icon={TrendingUp} value={76} label="Avg AI Score" tone="navy" index={4} />
<KpiCard icon={Award} value={3} label="Hired" tone="brand" index={5} />
</Grid>
</PageSection>
<PageSection title="Metric cards">
<Grid cols={3}>
<MetricCard label="Time to hire" value="1d" delta={-38} invertTrend deltaLabel="vs. 5-day industry average" icon={Clock} tone="success" />
<MetricCard label="Quality of hire" value="94" delta={12} deltaLabel="Average score of hires" icon={Award} tone="brand" />
<MetricCard label="Cost saved" value="$725" delta={0} sub="9 screens · 4 interviews automated" icon={TrendingUp} tone="neutral" />
</Grid>
</PageSection>
<Spec title="Progress" description="Bars, rings and score meters share the same band thresholds.">
<Grid cols={2}>
<div className="space-y-4">
<ProgressBar label="Profile completion" value={62} showValue valueSuffix="%" />
<ProgressBar label="Score 97" value={97} tone="score" showValue />
<ProgressBar label="Score 72" value={72} tone="score" showValue />
<ProgressBar label="Score 28" value={28} tone="score" showValue />
</div>
<div className="space-y-3">
<p className="text-caption text-ink-4">Score meters</p>
<ScoreMeter label="Reliability" value={96} weight={25} />
<ScoreMeter label="Attendance" value={98} weight={20} />
<ScoreMeter label="Communication" value={72} weight={15} />
<ScoreMeter label="Leadership" value={41} weight={15} />
</div>
</Grid>
<Row label="Rings">
<ProgressRing value={97} caption="Score" size={88} />
<ProgressRing value={72} tone="score" size={88} />
<ProgressRing value={817} min={300} max={850} label="817" caption="Career" footnote="300-850" size={104} />
<ProgressRing value={28} tone="score" size={72} />
</Row>
</Spec>
<Spec title="Avatar" description="Photo when available, initials otherwise. Never a broken image.">
<Row label="Sizes">
{['xs', 'sm', 'default', 'lg', 'xl'].map((s) => (
<Avatar key={s} name="Chef Antoine Dubois" size={s} />
))}
</Row>
<Row label="Tones">
<Avatar name="Marcus Williams" tone="brand" />
<Avatar name="Sofia Mendez" tone="navy" />
<Avatar name="Tanya Cruz" tone="soft" />
<Avatar name="Sam Okafor" tone="neutral" />
<Avatar name="Maria Gonzalez" src="https://i.pravatar.cc/160?img=31" />
<Avatar name="Broken Image" src="https://example.invalid/nope.jpg" />
</Row>
<Row label="Status">
<Avatar name="Online Person" status="online" />
<Avatar name="Busy Person" status="busy" />
<Avatar name="Away Person" status="offline" />
</Row>
<Row label="Group">
<AvatarGroup
people={[
{ name: 'Chef Antoine Dubois' }, { name: 'Marcus Williams' },
{ name: 'Sofia Mendez' }, { name: 'Tanya Cruz' },
{ name: 'Jordan Blake' }, { name: 'Sam Okafor' },
]}
max={4}
/>
</Row>
</Spec>
<Spec title="Filter bar" description="Declarative filters with active chips and a clear-all.">
<FilterBar
search={search}
onSearchChange={setSearch}
searchPlaceholder="Search candidates…"
values={filters}
onChange={setFilters}
filters={[
{
key: 'stage', label: 'Stage', type: 'select',
options: [
{ value: 'all', label: 'All stages' }, { value: 'applied', label: 'Applied' },
{ value: 'ai_screened', label: 'AI Screened' }, { value: 'hired', label: 'Hired' },
],
},
{ key: 'position', label: 'Position', type: 'multi', options: ['Bartender', 'Event Server', 'Executive Chef', 'Security'] },
]}
meta="6 of 22 candidates"
actions={<Button size="sm"><Brain />AI Screen All</Button>}
/>
</Spec>
<PageSection title="Data table">
<DataTable
columns={[
{ key: 'name', header: 'Candidate', sortable: true, cell: (r) => (
<div className="flex items-center gap-2.5">
<Avatar name={r.name} size="sm" />
<span className="font-medium text-ink-1">{r.name}</span>
</div>
) },
{ key: 'role', header: 'Role', sortable: true, hideBelow: 'sm' },
{ key: 'exp', header: 'Experience', sortable: true, align: 'right', hideBelow: 'md', cell: (r) => `${r.exp} yr` },
{ key: 'status', header: 'Stage', cell: (r) => <StatusBadge status={r.status} size="sm" /> },
{ key: 'score', header: 'Score', sortable: true, align: 'right', cell: (r) => (
<span className="font-heading font-bold text-krow-blue tabular-nums">{r.score}</span>
) },
]}
rows={TABLE_ROWS}
defaultSort={{ key: 'score', direction: 'desc' }}
selectedIds={selected}
onSelectionChange={setSelected}
pageSize={5}
caption="Candidates by AI score"
onRowClick={(r) => toast.info(`Opened ${r.name}`)}
/>
</PageSection>
<Spec title="Chart container" description="Owns the title, height, legend, loading and empty states.">
<Grid cols={2}>
<ChartContainer
title="Candidate Scores"
subtitle="9 candidates screened"
headline={76}
headlineLabel="Avg score"
height={220}
legend={[
{ label: '0–39', color: CHART_TONES.navy, value: 1 },
{ label: '60–79', color: CHART_TONES.accentPale, value: 3 },
{ label: '80–100', color: CHART_TONES.brand, value: 5 },
]}
>
<BarChart data={CHART_DATA}>
<CartesianGrid strokeDasharray="3 3" stroke={CHART_TONES.grid} vertical={false} />
<XAxis dataKey="label" {...AXIS_PROPS} />
<YAxis {...AXIS_PROPS} allowDecimals={false} />
<Tooltip content={<ChartTooltip />} />
<Bar dataKey="count" name="Candidates" fill={CHART_TONES.brand} radius={[6, 6, 0, 0]} />
</BarChart>
</ChartContainer>
<ChartContainer title="Interview Volume" subtitle="No interviews in this window" height={220} isEmpty />
</Grid>
</Spec>
<Spec title="Timeline & activity" description="Event sequences and feed rows.">
<Grid cols={2}>
<div>
<p className="text-caption text-ink-4 mb-3">Timeline</p>
<Timeline
items={[
{ id: 1, title: 'Applied', description: 'Executive Chef – Catering', timestamp: 'Jul 24', icon: Calendar, tone: 'neutral' },
{ id: 2, title: 'AI screened', description: 'Scored 97 — Excellent Match', timestamp: 'Jul 24', icon: Brain, tone: 'brand', status: 'ai_screened' },
{ id: 3, title: 'Interviewed', description: 'Verdict: hire', timestamp: 'Jul 25', icon: CheckCircle2, tone: 'success' },
{ id: 4, title: 'Hired', description: 'Onboarding started', timestamp: 'Jul 25', icon: Award, tone: 'success', status: 'hired', current: true },
]}
/>
</div>
<div>
<p className="text-caption text-ink-4 mb-3">Activity</p>
<div className="space-y-1">
<ActivityCard actor="Alex Rivera" title="hired Chef Antoine Dubois" timestamp="Jul 25, 9:00 AM" status="hired" onClick={() => {}} />
<ActivityCard actor="Alex Rivera" title="screened 3 candidates" timestamp="Jul 24, 2:30 PM" onClick={() => {}} />
<ActivityCard icon={Activity} iconTone="soft" title="Position created" description="Banquet Captain – Hotel Events" timestamp="Jul 18" />
<ActivityCard
icon={AlertCircle}
iconTone="warning"
title="Interview flagged"
description="Three responses returned in under 8 seconds."
timestamp="Aug 3"
trailing={<Badge variant="warning" size="sm">Review</Badge>}
variant="card"
/>
</div>
</div>
</Grid>
</Spec>
</Stack>
)}
{/* Overlay instances */}
<Modal
open={modalOpen}
onOpenChange={setModalOpen}
title="Schedule interview"
description="Owliver will run the interview and score it automatically."
icon={Calendar}
footer={
<>
<Button variant="outline" onClick={() => setModalOpen(false)}>Cancel</Button>
<Button onClick={() => { setModalOpen(false); toast.success('Interview scheduled'); }}>Schedule</Button>
</>
}
>
<div className="space-y-4">
<Field label="Candidate"><Input defaultValue="Sofia Mendez" readOnly /></Field>
<Field label="Date" hint="Candidates get a link immediately."><Input type="date" defaultValue="2026-08-12" /></Field>
<Field label="Notes"><Textarea rows={3} placeholder="Anything to probe specifically…" /></Field>
</div>
</Modal>
<ConfirmModal
open={confirmOpen}
onOpenChange={setConfirmOpen}
title="Decline Sam Okafor?"
description="They will be notified and moved out of the active pipeline. This cannot be undone."
confirmLabel="Decline candidate"
icon={AlertCircle}
onConfirm={() => { setConfirmOpen(false); toast.success('Candidate declined'); }}
/>
<Drawer
open={drawerOpen}
onOpenChange={setDrawerOpen}
title="Chef Antoine Dubois"
description="Executive Chef – Catering · Scored 97"
icon={Star}
footer={
<>
<Button variant="outline" onClick={() => setDrawerOpen(false)}>Close</Button>
<Button onClick={() => { setDrawerOpen(false); toast.success('Shortlisted'); }}>Shortlist</Button>
</>
}
>
<div className="space-y-5">
<div className="flex items-center gap-3">
<Avatar name="Chef Antoine Dubois" size="xl" />
<div>
<p className="text-title font-heading text-ink-1">Chef Antoine Dubois</p>
<p className="text-body-sm text-ink-3">12 years · ServSafe · Bay Area</p>
<div className="flex gap-1.5 mt-2">
<StatusBadge status="hired" size="sm" />
<StatusBadge status="elite" size="sm" />
</div>
</div>
</div>
<div className="space-y-3">
<ScoreMeter label="Experience" value={99} />
<ScoreMeter label="Certifications" value={98} />
<ScoreMeter label="Reliability" value={96} />
<ScoreMeter label="Availability" value={94} />
</div>
</div>
</Drawer>
</Stack>
);
}