Files
zeekay a648eaec80 refactor(dashboard): one chrome, from @luxfi/ui
The dashboard had its own header, its own user menu (components/layout/user-menu.tsx)
and its own branding table (lib/branding.ts) that mapped a hostname to a brand a
second time — a copy of what @luxfi/ui/white-label already answers, and one that
drifted (it had no bootno.de row and pointed zoo at the wrong issuer).

Both are deleted. The nav is now `AppNav` from @luxfi/ui: brand, org switcher,
links, settings and the user menu, with the links collapsing into the same gui
Sheet the switchers use instead of the old `hidden md:flex` that simply vanished
on a phone with nothing to replace it.

Identity comes from IdentityProvider (IAM: get-account / get-organizations /
get-organization-projects). Credentials stay with @hanzo/iam — one PKCE flow and
one token store per surface; the chrome borrows only signIn and signOut.

`next build` passes: 60 routes.
2026-07-27 14:42:56 -07:00

46 lines
1.9 KiB
TypeScript

// Status is a TONE, not a hue. Lux permits exactly two hues — success and error
// — and carries everything else on the achromatic ladder, so `pending` no longer
// paints itself amber. Tones resolve through @luxfi/ui/tokens.css, which means a
// white-label host retints them without a rebuild.
const TONE_CLASS = {
good: 'bg-[var(--muted)] text-[var(--color-status-good)] border-[var(--border)]',
bad: 'bg-[var(--muted)] text-[var(--color-status-bad)] border-[var(--border)]',
// Waiting on someone. The loudest neutral — was amber-400 on amber-500/10.
pending: 'bg-[var(--muted)] text-[var(--color-status-warn)] border-[var(--border)]',
// Work in flight. Full-contrast neutral — was blue-400 on blue-500/10.
active: 'bg-[var(--secondary)] text-[var(--foreground)] border-[var(--border)]',
// Nothing is happening.
inert: 'bg-[var(--muted)] text-[var(--muted-foreground)] border-[var(--border)]',
} as const
type Tone = keyof typeof TONE_CLASS
const STATUS_TONE: Record<string, Tone> = {
active: 'good', running: 'good', signed: 'good', approved: 'good',
enabled: 'good', settled: 'good', verified: 'good', finalized: 'good',
co_signed: 'good',
pending: 'pending', pending_approval: 'pending', pending_sign: 'pending',
matched: 'pending',
signing: 'active', processing: 'active', settling: 'active',
hsm_signing: 'active', confirming: 'active', broadcast: 'active',
recorded: 'active',
failed: 'bad', rejected: 'bad', cancelled: 'bad',
expired: 'inert', disabled: 'inert', paused: 'inert', inactive: 'inert',
}
interface StatusBadgeProps {
status: string
className?: string
}
export function StatusBadge({ status, className = '' }: StatusBadgeProps) {
const tone = STATUS_TONE[status.toLowerCase()] ?? 'inert'
return (
<span
className={`inline-flex items-center rounded-md border px-2 py-0.5 text-xs font-medium capitalize ${TONE_CLASS[tone]} ${className}`}
>
{status.replace(/_/g, ' ')}
</span>
)
}