Files
bootnode/console/components/lux/fleet-strip.tsx
zeekayandHanzo Dev da98c0de75 feat(console): one icon set — every glyph comes from @luxfi/ui/icons
74 files imported `lucide-react` directly, pinned ^1.26.0, while @luxfi/ui —
which this console renders alongside — resolves 1.27. Two Lucide copies from two
resolutions in one bundle is how a set stops being one set. Two more glyphs were
not from any icon library at all: the node list drew its network in/out arrows as
the literal characters "↓" and "↑", and the chain onboarding selector drew its
tick as "✓". Those are font glyphs — they take the text metrics, not the icon
box, so they sit on a different baseline and scale with the font, not `size-*`.

One source now: `@luxfi/ui/icons`, Lucide as @hanzo/gui standardises on it and
@luxfi/ui themes it. `lucide-react` is out of package.json, so per-surface drift
is no longer expressible; @luxfi/ui comes in at 7.4.3, which is where `./icons`
lives. The three text glyphs are ArrowDown / ArrowUp / Check.

All 121 distinct icon names this console uses were checked against the 6014
symbols @luxfi/ui/icons exports before the rewrite — zero missing, with a
negative control. `next build` is clean: 78 routes, Next 16.2.11 + Turbopack.

The d3 chart roots (node-globe, node-map, globe-view, donut-chart, fleet-map)
keep their <svg>: those are plots, not glyphs. The command menu's "↑↓" stays —
it is a keyboard legend inside <kbd>, and it means the keys.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-27 14:57:55 -07:00

77 lines
2.5 KiB
TypeScript

"use client"
import { useEffect, useState } from "react"
import Link from "next/link"
import { ArrowRight, Circle } from "@luxfi/ui/icons"
import {
NETWORK_LABEL,
fetchFleetOverview,
fleetStatus,
type LuxFleetOverview,
} from "@/lib/fleet"
const DOT: Record<string, string> = {
operational: "text-green-500",
degraded: "text-yellow-500",
down: "text-red-500",
}
/** Compact live fleet status strip for the landing hero. Public, read-only. */
export function FleetStrip() {
const [ov, setOv] = useState<LuxFleetOverview | null>(null)
useEffect(() => {
let alive = true
const ctrl = new AbortController()
const load = () =>
fetchFleetOverview(ctrl.signal)
.then((d) => alive && setOv(d))
.catch(() => {})
load()
const t = setInterval(load, 20_000)
return () => {
alive = false
ctrl.abort()
clearInterval(t)
}
}, [])
if (!ov) return null
const totals = ov.totals ?? {}
return (
<Link
href="/fleet"
className="group mt-8 flex flex-wrap items-center justify-center gap-x-6 gap-y-2 rounded-xl border border-zinc-800 bg-background/60 px-5 py-3 backdrop-blur transition-colors hover:border-zinc-700"
>
<span className="flex items-center gap-2 text-sm font-medium">
<span className="relative flex h-2.5 w-2.5">
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-green-500 opacity-60" />
<span className="relative inline-flex h-2.5 w-2.5 rounded-full bg-green-500" />
</span>
Live fleet
</span>
{ov.networks.map((f) => {
const s = fleetStatus(f)
return (
<span key={f.network} className="flex items-center gap-1.5 text-sm">
<Circle className={`h-2 w-2 fill-current ${DOT[s]}`} />
<span className="text-muted-foreground">{NETWORK_LABEL[f.network]}</span>
<span className="font-mono">
{f.nodes_ready}/{f.validators || f.nodes_total}
</span>
</span>
)
})}
<span className="hidden items-center gap-1.5 text-sm text-muted-foreground sm:flex">
<span className="font-mono text-foreground">{totals.total_peers ?? 0}</span> peers
</span>
<span className="flex items-center gap-1 text-sm text-primary opacity-80 group-hover:opacity-100">
View fleet
<ArrowRight className="h-3.5 w-3.5 transition-transform group-hover:translate-x-0.5" />
</span>
</Link>
)
}