● Zion Boggan
repos/Drophawk/frontend/components/dashboard/StatsRow.tsx
zionboggan.com ↗
103 lines · tsx
History for this file →
1
"use client";
2
 
3
import { motion } from "framer-motion";
4
import { Zap, Radio, Eye, Timer, type LucideIcon } from "lucide-react";
5
import { Card } from "@/components/ui/card";
6
import { Skeleton } from "@/components/ui/skeleton";
7
import { CountUp } from "@/components/shared/CountUp";
8
import { staggerContainer, fadeUpItem } from "@/lib/motion";
9
import { useStats } from "@/hooks/useStats";
10
import { formatLatency } from "@/lib/utils";
11
 
12
interface Tile {
13
  key: string;
14
  label: string;
15
  icon: LucideIcon;
16
  accent: string;
17
  value: number;
18
  sub: string;
19
  render?: "latency";
20
}
21
 
22
export function StatsRow() {
23
  const { data, isLoading } = useStats();
24
 
25
  if (isLoading || !data) {
26
    return (
27
      <div className="grid grid-cols-2 gap-3 lg:grid-cols-4">
28
        {Array.from({ length: 4 }).map((_, i) => (
29
          <Skeleton key={i} className="h-[104px] rounded-[var(--radius-lg)]" />
30
        ))}
31
      </div>
32
    );
33
  }
34
 
35
  const tiles: Tile[] = [
36
    {
37
      key: "drops",
38
      label: "Drops · 24h",
39
      icon: Zap,
40
      accent: "var(--color-accent-teal)",
41
      value: data.drops_24h,
42
      sub: `${data.drops_7d.toLocaleString()} this week`,
43
    },
44
    {
45
      key: "sources",
46
      label: "Active sources",
47
      icon: Radio,
48
      accent: "var(--color-accent-green)",
49
      value: data.sources_healthy,
50
      sub: `${data.sources_total} total · ${data.sources_down} down`,
51
    },
52
    {
53
      key: "matches",
54
      label: "Watchlist hits · 24h",
55
      icon: Eye,
56
      accent: "var(--color-accent-violet)",
57
      value: data.watchlist_matches_24h,
58
      sub: `${data.events_onsale_now.toLocaleString()} on sale now`,
59
    },
60
    {
61
      key: "latency",
62
      label: "Detect latency p50",
63
      icon: Timer,
64
      accent: "var(--color-accent-amber)",
65
      value: data.detection_latency_ms_p50,
66
      sub: `p99 ${formatLatency(data.detection_latency_ms_p99)}`,
67
      render: "latency",
68
    },
69
  ];
70
 
71
  return (
72
    <motion.div
73
      variants={staggerContainer}
74
      initial="initial"
75
      animate="animate"
76
      className="grid grid-cols-2 gap-3 lg:grid-cols-4"
77
    >
78
      {tiles.map((t) => (
79
        <motion.div key={t.key} variants={fadeUpItem}>
80
          <Card className="group relative overflow-hidden p-4 transition-colors hover:border-[var(--color-border-default)]">
81
            <div
82
              className="absolute -right-6 -top-6 h-20 w-20 rounded-full opacity-[0.07] blur-2xl transition-opacity group-hover:opacity-20"
83
              style={{ background: t.accent }}
84
              aria-hidden
85
            />
86
            <div className="flex items-center justify-between">
87
              <span className="text-xs font-medium text-[var(--color-fg-tertiary)]">{t.label}</span>
88
              <t.icon className="h-4 w-4" style={{ color: t.accent }} aria-hidden />
89
            </div>
90
            <div className="mt-3 font-mono text-2xl font-semibold tracking-[var(--tracking-tight)] text-[var(--color-fg-primary)]">
91
              {t.render === "latency" ? (
92
                <span className="tabular">{formatLatency(t.value)}</span>
93
              ) : (
94
                <CountUp value={t.value} />
95
              )}
96
            </div>
97
            <div className="mt-0.5 text-xs text-[var(--color-fg-tertiary)]">{t.sub}</div>
98
          </Card>
99
        </motion.div>
100
      ))}
101
    </motion.div>
102
  );
103
}