feat(frontend): revamp UI to Linear Precision Clean Dark with GSAP micro-interactions
This commit is contained in:
1 parent
c3165ed702
commit
29f59fd91f
13 files changed
+987
-1767
No files matched your search
@@ -7,8 +7,6 @@ import {
|
||||
MessageSquare,
|
||||
Mic,
|
||||
Shield,
|
||||
ShieldAlert,
|
||||
Terminal,
|
||||
Users,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
@@ -18,7 +16,9 @@ import { AreaActivity, RadialGauge } from "@/components/charts";
|
||||
import { GlassPanel } from "@/components/primitives";
|
||||
import {
|
||||
ErrorState,
|
||||
MetricTile,
|
||||
PageTransition,
|
||||
SectionHeader,
|
||||
SkeletonHero,
|
||||
SkeletonMetricRow,
|
||||
SkeletonPanel,
|
||||
@@ -63,9 +63,9 @@ export function DashboardView({
|
||||
const { data: reactions } = useTopReactions();
|
||||
const ambient = useAmbient();
|
||||
|
||||
const hudRef = useStaggerReveal<HTMLDivElement>(".hud-tile", {
|
||||
stagger: 0.05,
|
||||
y: 16,
|
||||
const hudRef = useStaggerReveal<HTMLDivElement>(".linear-tile", {
|
||||
stagger: 0.035,
|
||||
y: 8,
|
||||
dependencies: [stats],
|
||||
});
|
||||
|
||||
@@ -82,10 +82,10 @@ export function DashboardView({
|
||||
return <ErrorState error={error} onRetry={() => void mutateStats()} />;
|
||||
if (!stats && isLoading)
|
||||
return (
|
||||
<div className="space-y-5">
|
||||
<div className="space-y-4">
|
||||
<SkeletonHero />
|
||||
<SkeletonMetricRow cols={4} />
|
||||
<SkeletonPanel rows={5} />
|
||||
<SkeletonPanel rows={4} />
|
||||
</div>
|
||||
);
|
||||
if (!stats)
|
||||
@@ -99,276 +99,155 @@ export function DashboardView({
|
||||
const s = stats;
|
||||
const total = s.total_flagged + s.total_clean || 1;
|
||||
const cleanRatio = s.total_clean / total;
|
||||
const sig = deriveSignal(s);
|
||||
|
||||
return (
|
||||
<PageTransition>
|
||||
<div ref={hudRef} className="space-y-4">
|
||||
{/* Tactical HUD Header Bar */}
|
||||
<div className="hud-tile flex flex-wrap items-center justify-between gap-3 border-b border-hairline pb-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="relative flex size-3 items-center justify-center">
|
||||
<span className="absolute inline-flex size-full animate-ping rounded-full bg-signal opacity-75" />
|
||||
<span className="relative inline-flex size-2 rounded-full bg-signal" />
|
||||
</div>
|
||||
<div className="flex items-baseline gap-2">
|
||||
<h1 className="font-mono text-xs font-semibold tracking-widest text-ink uppercase">
|
||||
GMW TELEMETRY · MATRIX_01
|
||||
</h1>
|
||||
<span className="font-mono text-[10px] text-ink-faint">
|
||||
[LIVE_MONITOR]
|
||||
</span>
|
||||
</div>
|
||||
{/* Precision Sub-Header Bar */}
|
||||
<div className="linear-tile flex flex-wrap items-center justify-between gap-3 border-b border-white/[0.06] pb-3">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<span className="h-2 w-2 rounded-full bg-[#7170ff] shadow-[0_0_8px_#7170ff]" />
|
||||
<h1 className="font-mono text-xs font-semibold tracking-wide text-[#f7f8f8] uppercase">
|
||||
Telemetry Overview · Node 01
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="inline-flex items-center gap-1.5 rounded-sm bg-surface px-2 py-0.5 font-mono text-[11px] text-ink-soft">
|
||||
<span className="text-ink-faint">SYS_STATUS:</span>
|
||||
<span className="font-bold text-signal">{sig.state}</span>
|
||||
</div>
|
||||
<div className="inline-flex items-center gap-1.5 rounded-sm bg-surface px-2 py-0.5 font-mono text-[11px] text-ink-soft">
|
||||
<span className="text-ink-faint">VER:</span>
|
||||
<span className="text-ink">v2.4-GSAP</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 font-mono text-[11px] text-[#8a8f98]">
|
||||
<span>SIGNAL:</span>
|
||||
<span className="rounded bg-white/[0.04] px-1.5 py-0.5 font-medium text-[#7170ff] border border-white/[0.06]">
|
||||
STABLE_STREAM
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Spatial HUD Grid: Live Mission Control */}
|
||||
<div className="grid gap-4 lg:grid-cols-12">
|
||||
{/* Main Telemetry & Activity Area */}
|
||||
<div className="space-y-4 lg:col-span-8">
|
||||
{/* Primary Telemetry Cluster */}
|
||||
<div className="hud-tile grid grid-cols-2 gap-2.5 sm:grid-cols-4">
|
||||
<div className="group relative overflow-hidden rounded-md border border-hairline bg-surface/50 p-3.5 backdrop-blur-md transition-colors hover:border-signal/40">
|
||||
<div className="flex items-center justify-between text-ink-faint">
|
||||
<span className="font-mono text-[10px] tracking-wider uppercase">
|
||||
Messages
|
||||
</span>
|
||||
<MessageSquare className="size-3.5 text-signal" />
|
||||
</div>
|
||||
<div className="mt-2 font-mono text-xl font-bold tracking-tight text-ink">
|
||||
{formatNumber(s.total_messages)}
|
||||
</div>
|
||||
<div className="mt-1 flex items-center gap-1 font-mono text-[10px] text-ink-faint">
|
||||
<span className="text-signal">↑ live</span> ingest
|
||||
</div>
|
||||
</div>
|
||||
{/* Primary Metric Deck */}
|
||||
<div className="linear-tile grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
|
||||
<MetricTile
|
||||
label="Total Messages"
|
||||
value={formatNumber(s.total_messages)}
|
||||
hint={`${formatNumber(s.today_messages)} today`}
|
||||
icon={<MessageSquare className="size-4" />}
|
||||
/>
|
||||
<MetricTile
|
||||
label="Clean Filter Ratio"
|
||||
value={`${Math.round(cleanRatio * 100)}%`}
|
||||
hint={`${formatNumber(s.total_clean)} verified clean`}
|
||||
tone="signal"
|
||||
icon={<Shield className="size-4" />}
|
||||
/>
|
||||
<MetricTile
|
||||
label="Voice Recordings"
|
||||
value={formatNumber(s.total_voice_recordings)}
|
||||
hint="Archived audio sessions"
|
||||
icon={<Mic className="size-4" />}
|
||||
/>
|
||||
<MetricTile
|
||||
label="Total Profiles"
|
||||
value={formatNumber(s.total_profiles)}
|
||||
hint={`${formatNumber(s.active_users_24h)} active 24h`}
|
||||
icon={<Users className="size-4" />}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="group relative overflow-hidden rounded-md border border-hairline bg-surface/50 p-3.5 backdrop-blur-md transition-colors hover:border-signal/40">
|
||||
<div className="flex items-center justify-between text-ink-faint">
|
||||
<span className="font-mono text-[10px] tracking-wider uppercase">
|
||||
Flagged
|
||||
</span>
|
||||
<ShieldAlert className="size-3.5 text-vermilion" />
|
||||
</div>
|
||||
<div className="mt-2 font-mono text-xl font-bold tracking-tight text-ink">
|
||||
{formatNumber(s.total_flagged)}
|
||||
</div>
|
||||
<div className="mt-1 font-mono text-[10px] text-ink-faint">
|
||||
<span className="text-vermilion">{s.today_flagged}</span>{" "}
|
||||
today
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="group relative overflow-hidden rounded-md border border-hairline bg-surface/50 p-3.5 backdrop-blur-md transition-colors hover:border-signal/40">
|
||||
<div className="flex items-center justify-between text-ink-faint">
|
||||
<span className="font-mono text-[10px] tracking-wider uppercase">
|
||||
Active 24h
|
||||
</span>
|
||||
<Users className="size-3.5 text-signal" />
|
||||
</div>
|
||||
<div className="mt-2 font-mono text-xl font-bold tracking-tight text-ink">
|
||||
{formatNumber(s.active_users_24h)}
|
||||
</div>
|
||||
<div className="mt-1 font-mono text-[10px] text-ink-faint">
|
||||
<span>unique accounts</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="group relative overflow-hidden rounded-md border border-hairline bg-surface/50 p-3.5 backdrop-blur-md transition-colors hover:border-signal/40">
|
||||
<div className="flex items-center justify-between text-ink-faint">
|
||||
<span className="font-mono text-[10px] tracking-wider uppercase">
|
||||
Voice Captures
|
||||
</span>
|
||||
<Mic className="size-3.5 text-signal" />
|
||||
</div>
|
||||
<div className="mt-2 font-mono text-xl font-bold tracking-tight text-ink">
|
||||
{formatNumber(s.total_voice_recordings)}
|
||||
</div>
|
||||
<div className="mt-1 font-mono text-[10px] text-ink-faint">
|
||||
<span>audio buffers</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* Dynamic Activity Area & Telemetry Gauges */}
|
||||
<div className="grid gap-3 lg:grid-cols-3">
|
||||
<GlassPanel className="linear-tile lg:col-span-2">
|
||||
<SectionHeader
|
||||
eyebrow="Activity Stream"
|
||||
title="Message Volume & Signal Density"
|
||||
/>
|
||||
<div className="mt-4">
|
||||
<AreaActivity daily={activity?.daily ?? []} />
|
||||
</div>
|
||||
</GlassPanel>
|
||||
|
||||
{/* Activity Waveform / Time Series */}
|
||||
<GlassPanel className="hud-tile rounded-md border border-hairline bg-surface/40 p-4 backdrop-blur-md">
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<div>
|
||||
<div className="font-mono text-[10px] tracking-widest text-ink-faint uppercase">
|
||||
CHRONO TELEMETRY (14D)
|
||||
</div>
|
||||
<div className="text-sm font-medium text-ink">
|
||||
Message & Activity Stream
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 font-mono text-[11px]">
|
||||
<div className="flex items-center gap-1 text-ink-soft">
|
||||
<span className="size-2 rounded-full bg-signal" />
|
||||
<span>Activity Trend</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-56 w-full">
|
||||
{activity?.daily ? (
|
||||
<AreaActivity daily={activity.daily} />
|
||||
) : (
|
||||
<div className="flex h-full items-center justify-center font-mono text-xs text-ink-faint">
|
||||
ACQUIRING SIGNAL...
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</GlassPanel>
|
||||
|
||||
{/* Quick Tactical Links */}
|
||||
<div className="hud-tile grid grid-cols-1 gap-2.5 sm:grid-cols-3">
|
||||
<Link
|
||||
href="/voice"
|
||||
className="group flex items-center justify-between rounded-md border border-hairline bg-surface/30 p-3 transition hover:border-signal/50 hover:bg-surface/60"
|
||||
>
|
||||
<div className="flex items-center gap-2.5">
|
||||
<div className="flex size-8 items-center justify-center rounded bg-canvas-2 text-signal group-hover:bg-signal group-hover:text-signal-ink">
|
||||
<AudioWaveform className="size-4" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-mono text-xs font-semibold text-ink">
|
||||
Voice Matrix
|
||||
</div>
|
||||
<div className="text-[11px] text-ink-faint">
|
||||
Realtime speaker stream
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ChevronRight className="size-4 text-ink-faint group-hover:text-signal" />
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="/messages"
|
||||
className="group flex items-center justify-between rounded-md border border-hairline bg-surface/30 p-3 transition hover:border-signal/50 hover:bg-surface/60"
|
||||
>
|
||||
<div className="flex items-center gap-2.5">
|
||||
<div className="flex size-8 items-center justify-center rounded bg-canvas-2 text-signal group-hover:bg-signal group-hover:text-signal-ink">
|
||||
<Terminal className="size-4" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-mono text-xs font-semibold text-ink">
|
||||
Chat Log Stream
|
||||
</div>
|
||||
<div className="text-[11px] text-ink-faint">
|
||||
Live telemetry feed
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ChevronRight className="size-4 text-ink-faint group-hover:text-signal" />
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="/moderation"
|
||||
className="group flex items-center justify-between rounded-md border border-hairline bg-surface/30 p-3 transition hover:border-signal/50 hover:bg-surface/60"
|
||||
>
|
||||
<div className="flex items-center gap-2.5">
|
||||
<div className="flex size-8 items-center justify-center rounded bg-canvas-2 text-signal group-hover:bg-signal group-hover:text-signal-ink">
|
||||
<Shield className="size-4" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-mono text-xs font-semibold text-ink">
|
||||
Auto-Mod Radar
|
||||
</div>
|
||||
<div className="text-[11px] text-ink-faint">
|
||||
Classification & audits
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ChevronRight className="size-4 text-ink-faint group-hover:text-signal" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Side Telemetry Panel: Health, Dials, & Nodes */}
|
||||
<div className="space-y-4 lg:col-span-4">
|
||||
{/* Safety & Moderation Integrity Gauge */}
|
||||
<GlassPanel className="hud-tile rounded-md border border-hairline bg-surface/40 p-4 backdrop-blur-md">
|
||||
<div className="font-mono text-[10px] tracking-widest text-ink-faint uppercase">
|
||||
INTEGRITY MATRIX
|
||||
</div>
|
||||
<div className="mt-1 text-sm font-medium text-ink">
|
||||
Clean Stream Ratio
|
||||
</div>
|
||||
<div className="my-4 flex items-center justify-center">
|
||||
<GlassPanel className="linear-tile flex flex-col justify-between">
|
||||
<div>
|
||||
<SectionHeader
|
||||
eyebrow="Security State"
|
||||
title="Moderation Accuracy"
|
||||
/>
|
||||
<div className="my-6 flex items-center justify-center">
|
||||
<RadialGauge
|
||||
value={Math.round(cleanRatio * 100)}
|
||||
label="Clean Ratio"
|
||||
tone={cleanRatio > 0.85 ? "signal" : "amber"}
|
||||
size={140}
|
||||
tone={cleanRatio > 0.9 ? "signal" : "amber"}
|
||||
label={`${Math.round(cleanRatio * 100)}%`}
|
||||
sublabel="CLEAN RATIO"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2 border-t border-hairline pt-3 font-mono text-xs">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-ink-faint">Clean Messages:</span>
|
||||
<span className="font-medium text-ink">
|
||||
{formatNumber(s.total_clean)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-ink-faint">Pending Flags:</span>
|
||||
<span className="font-medium text-vermilion">
|
||||
{formatNumber(s.total_flagged)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2 border-t border-white/[0.06] pt-3 text-center">
|
||||
<div>
|
||||
<div className="font-mono text-[10px] text-[#8a8f98]">CLEAN</div>
|
||||
<div className="font-sans text-sm font-semibold text-[#10b981]">
|
||||
{formatNumber(s.total_clean)}
|
||||
</div>
|
||||
</div>
|
||||
</GlassPanel>
|
||||
|
||||
{/* Top Reacted Messages / Emitters */}
|
||||
<GlassPanel className="hud-tile rounded-md border border-hairline bg-surface/40 p-4 backdrop-blur-md">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="font-mono text-[10px] tracking-widest text-ink-faint uppercase">
|
||||
FREQUENT EMITTERS
|
||||
</div>
|
||||
<div className="text-sm font-medium text-ink">
|
||||
Top Reacted Messages
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-mono text-[10px] text-[#8a8f98]">FLAGGED</div>
|
||||
<div className="font-sans text-sm font-semibold text-[#f43f5e]">
|
||||
{formatNumber(s.total_flagged)}
|
||||
</div>
|
||||
<Flame className="size-4 text-amber" />
|
||||
</div>
|
||||
</div>
|
||||
</GlassPanel>
|
||||
</div>
|
||||
|
||||
{/* Secondary Insights Deck */}
|
||||
<div className="grid gap-3 lg:grid-cols-2">
|
||||
{reactions && reactions.length > 0 && (
|
||||
<GlassPanel className="linear-tile">
|
||||
<SectionHeader
|
||||
eyebrow="Engagement"
|
||||
title="Top Reacted Messages"
|
||||
/>
|
||||
<div className="mt-3 space-y-2">
|
||||
{reactions && reactions.length > 0 ? (
|
||||
reactions.slice(0, 4).map((r) => (
|
||||
<div
|
||||
key={r.message_id}
|
||||
className="flex items-center justify-between rounded bg-surface/30 px-2.5 py-1.5 font-mono text-xs"
|
||||
>
|
||||
<span className="max-w-[150px] truncate text-ink-soft">
|
||||
{r.username ? `@${r.username}` : "anonymous"}
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-ink-faint">reacts:</span>
|
||||
<span className="font-bold text-signal">
|
||||
{r.reaction_count}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="py-4 text-center font-mono text-xs text-ink-faint">
|
||||
NO EMITTER TELEMETRY
|
||||
{reactions.slice(0, 4).map((r) => (
|
||||
<div
|
||||
key={r.message_id}
|
||||
className="flex items-center justify-between rounded-[6px] border border-white/[0.06] bg-white/[0.02] p-2.5 hover:bg-white/[0.04]"
|
||||
>
|
||||
<span className="truncate pr-2 text-xs text-[#d0d6e0]">
|
||||
{r.content || "[Media/Attachment]"}
|
||||
</span>
|
||||
<span className="font-mono text-xs font-semibold text-[#7170ff] shrink-0">
|
||||
{formatNumber(r.reaction_count)} reactions
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
))}
|
||||
</div>
|
||||
</GlassPanel>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<GlassPanel className="linear-tile flex flex-col justify-between">
|
||||
<div>
|
||||
<SectionHeader
|
||||
eyebrow="Navigation"
|
||||
title="Quick Diagnostics"
|
||||
/>
|
||||
<div className="mt-2 space-y-1.5">
|
||||
<Link
|
||||
href="/moderation"
|
||||
className="flex items-center justify-between rounded-[6px] border border-white/[0.06] bg-white/[0.02] p-2.5 text-xs text-[#d0d6e0] transition-colors hover:border-white/[0.12] hover:bg-white/[0.04] hover:text-[#f7f8f8]"
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
<Shield className="size-3.5 text-[#7170ff]" />
|
||||
Review Live Moderation Queue
|
||||
</span>
|
||||
<ChevronRight className="size-3.5 text-[#8a8f98]" />
|
||||
</Link>
|
||||
<Link
|
||||
href="/voice"
|
||||
className="flex items-center justify-between rounded-[6px] border border-white/[0.06] bg-white/[0.02] p-2.5 text-xs text-[#d0d6e0] transition-colors hover:border-white/[0.12] hover:bg-white/[0.04] hover:text-[#f7f8f8]"
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
<AudioWaveform className="size-3.5 text-[#10b981]" />
|
||||
Inspect Active Voice Stages
|
||||
</span>
|
||||
<ChevronRight className="size-3.5 text-[#8a8f98]" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</GlassPanel>
|
||||
</div>
|
||||
</div>
|
||||
</PageTransition>
|
||||
|
||||
Reference in new issue
Block a user