Files
GMW/services/frontend/src/components/dashboard/reactions-section.tsx
T
asepharyana 5bbf75a65b refactor(frontend): finish shadcn→custom primitive migration (green build)
- Remove tw-animate-css import + dead src/components/ui shadcn tree
- Convert 7 orphaned components (moderation, analysis, guild-selector,
  voice/activity-timeline, shared/empty+error) to new primitives
- Add missing moderation/view.tsx; analysis uses SearchPanel directly
- globals.css now uses new signal-driven ops-console tokens
- tsc --noEmit clean, next build green (11 routes), local smoke 200
2026-08-14 10:49:44 +07:00

90 lines
3.1 KiB
TypeScript

"use client";
import { Flame, Heart, SmilePlus } from "lucide-react";
import { Avatar } from "@/components/primitives/avatar";
import { Badge } from "@/components/primitives/badge";
import { EmptyState, LoadingSkeleton } from "@/components/shared";
import { useTopReactions, useTopReactors } from "@/hooks";
export interface ReactionsSectionProps {
initialReactions?: Awaited<ReturnType<typeof useTopReactions>>["data"];
}
export function ReactionsSection() {
const { data: reactions, isLoading: reactionsLoading } = useTopReactions();
const { data: reactors, isLoading: reactorsLoading } = useTopReactors();
if (reactionsLoading || reactorsLoading) return <LoadingSkeleton count={5} />;
const topReactions = (reactions ?? []).slice(0, 6);
const topReactors = (reactors ?? []).slice(0, 6);
return (
<div className="grid gap-3 lg:grid-cols-2">
<div className="surface p-4">
<h3 className="mb-3 flex items-center gap-2 text-sm font-semibold">
<Heart className="size-4 text-[var(--color-vermilion)]" />
Top reactions
</h3>
{topReactions.length === 0 ? (
<EmptyState
icon={SmilePlus}
title="No reactions"
description="No reactions yet."
/>
) : (
<div className="flex flex-wrap gap-2">
{topReactions
.flatMap((m) =>
m.top_emojis.map((e) => ({ emoji: e.emoji, count: e.count })),
)
.reduce<{ emoji: string; count: number }[]>((acc, cur) => {
const found = acc.find((x) => x.emoji === cur.emoji);
if (found) found.count += cur.count;
else acc.push(cur);
return acc;
}, [])
.sort((a, b) => b.count - a.count)
.slice(0, 8)
.map((r) => (
<span
key={r.emoji}
className="flex items-center gap-1.5 rounded-[var(--radius-r-control)] bg-[var(--color-surface-2)] px-2.5 py-1 text-sm"
>
<span>{r.emoji}</span>
<span className="mono text-xs text-[var(--color-ink-soft)]">
{r.count}
</span>
</span>
))}
</div>
)}
</div>
<div className="surface p-4">
<h3 className="mb-3 flex items-center gap-2 text-sm font-semibold">
<Flame className="size-4 text-[var(--color-amber)]" />
Top reactors
</h3>
{topReactors.length === 0 ? (
<EmptyState
icon={SmilePlus}
title="No reactors"
description="No reactors yet."
/>
) : (
<div className="flex flex-col gap-2">
{topReactors.map((r) => (
<div key={r.user_id} className="flex items-center gap-3">
<Avatar name={r.username} size={28} />
<span className="flex-1 text-sm">{r.username}</span>
<Badge tone="signal">+{r.net_count}</Badge>
</div>
))}
</div>
)}
</div>
</div>
);
}