import { useMemo } from 'react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { useZapLeaderboard } from '@/hooks/useZapLeaderboard'; import { cn } from '@/lib/utils'; import { Award, Crown, Sparkles, Trophy, Zap } from 'lucide-react'; interface BadgeRule { id: string; label: string; hint: string; Icon: typeof Zap; earned: (count: number, sats: number, rank: number | null) => boolean; } const BADGES: BadgeRule[] = [ { id: 'first', label: 'First Zap', hint: 'Send your first zap', Icon: Sparkles, earned: (c) => c >= 1 }, { id: 'regular', label: 'Regular', hint: 'Send 10 zaps', Icon: Zap, earned: (c) => c >= 10 }, { id: 'generous', label: 'Generous', hint: 'Send 10,000 sats', Icon: Award, earned: (_c, s) => s >= 10_000 }, { id: 'patron', label: 'Patron', hint: 'Send 100,000 sats', Icon: Crown, earned: (_c, s) => s >= 100_000 }, { id: 'top10', label: 'Top 10', hint: 'Reach the top 10 supporters', Icon: Trophy, earned: (_c, _s, r) => r !== null && r <= 10 }, ]; /** * Zap achievements for a pubkey, derived from the (batched) supporter * leaderboard over the last 30 days. Badges are pure recognition — nothing is * published, so this adds no events and no spam to the network. */ export function ZapBadgesCard({ pubkey, className }: { pubkey: string; className?: string }) { const since = useMemo(() => Math.floor(Date.now() / 1000) - 30 * 24 * 60 * 60, []); const { data, isLoading } = useZapLeaderboard(since); const stats = useMemo(() => { const senders = data?.senders ?? []; const idx = senders.findIndex((s) => s.pubkey === pubkey); return { count: idx >= 0 ? senders[idx].count : 0, sats: idx >= 0 ? senders[idx].sats : 0, rank: idx >= 0 ? idx + 1 : null, }; }, [data, pubkey]); // Keep profiles clean: nothing to show (or still loading) → render nothing. if (isLoading || stats.count === 0) return null; return ( Zap Badges

{stats.count.toLocaleString()} zaps ·{' '} {stats.sats.toLocaleString()} sats sent {stats.rank !== null && ( <> · rank #{stats.rank} )}

{BADGES.map((b) => { const earned = b.earned(stats.count, stats.sats, stats.rank); return (
{b.label}
); })}

Based on the last 30 days of zaps.

); }