Files
blackroad-os-demo/components/BeaconFeed.tsx
2025-11-24 02:06:39 -06:00

62 lines
2.1 KiB
TypeScript

'use client';
import { useEffect, useMemo, useState } from 'react';
import { connectBeacon, type BeaconMessage } from '../lib/beacon';
const LEVEL_STYLES: Record<BeaconMessage['level'], string> = {
info: 'bg-emerald-500/20 text-emerald-100',
warn: 'bg-amber-500/20 text-amber-100',
error: 'bg-red-500/20 text-red-100'
};
export default function BeaconFeed() {
const [events, setEvents] = useState<BeaconMessage[]>([]);
useEffect(() => {
const source = connectBeacon((payload) => {
setEvents((prev) => [payload, ...prev].slice(0, 20));
});
return () => source.close();
}, []);
const emptyState = useMemo(
() => (
<div className="rounded-xl border border-dashed border-white/20 p-4 text-sm text-white/70">
Waiting for Beacon events keep this tab open.
</div>
),
[]
);
return (
<div className="rounded-2xl border border-white/10 bg-white/5 p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-white/70">Live telemetry</p>
<h2 className="text-2xl font-semibold">Beacon feed</h2>
</div>
<span className="rounded-full bg-emerald-500/20 px-3 py-1 text-xs uppercase tracking-wide text-emerald-100">
SSE
</span>
</div>
<div className="mt-4 space-y-2">
{events.length === 0 && emptyState}
{events.map((event) => (
<article key={event.id} className="flex items-start gap-3 rounded-lg bg-black/30 p-3">
<div className={`mt-1 h-2 w-2 rounded-full ${LEVEL_STYLES[event.level]}`} />
<div className="flex-1 text-sm">
<header className="flex flex-wrap items-center gap-2 text-white/80">
<span className="text-xs uppercase tracking-wide text-white/60">{event.agent}</span>
<span className="text-xs text-white/50">
{new Date(event.ts).toLocaleTimeString()}
</span>
</header>
<p className="text-white/90">{event.detail}</p>
</div>
</article>
))}
</div>
</div>
);
}