refactor(fe): replace TanStack Query with SWR + UI/data cleanup
Build & Deploy (Nix) / build-and-deploy (backend) (push) Successful in 2m56s
Build & Deploy (Nix) / build-and-deploy (discord-gateway) (push) Successful in 3m42s
Build & Deploy (Nix) / build-and-deploy (proxy) (push) Successful in 3m59s

Rombak data layer frontend:
- Hapus @tanstack/react-query (package.json, lockfile, provider di
  dashboard layout) — ganti SWR 2.4.2 + SWRConfig (revalidateOnFocus
  false, deduping 10s, no retry on 404)
- Semua hooks data ditulis ulang ke useSWR; useAction() helper baru
  pengganti useMutation dengan surface kompatibel (mutate/mutateAsync/
  isPending/error)
- useMessages + useMessagesHasMore share satu SWR key — probe cursor
  yang tadinya dobel fetch API sekarang deduped
- WS sync (messages/media/recordings) pindah dari queryClient ke
  SWR mutate dengan filter key + revalidate:false
- useMessageSearch() dipakai search-panel & search-overlay; search
  overlay backdrop div -> button (fix a11y lint)

Rapikan UI + isi data:
- Tab stats recordings: placeholder 'coming soon' diganti stat asli
  (total, ukuran, speaker unik, top speakers)
- Empty states konsisten via EmptyState (images/review/recordings),
  EmptyState terima className
- biome check --write: 0 error, 8 warning pre-existing
- Verifikasi: tsc --noEmit PASS, next build PASS (11 halaman static),
  API live dicek — semua endpoint dashboard/messages/guilds/config/
  voice/media/recordings/review balikin data
This commit is contained in:
asepharyana
2026-08-01 09:19:39 +07:00
parent 1f91f99de3
commit 01c18b2060
26 changed files with 5145 additions and 329 deletions
@@ -1,17 +1,17 @@
"use client";
import { useCallback, useEffect, useState } from "react";
import { useSearchParams, useRouter } from "next/navigation";
import { Flag, Image, Loader2, RefreshCw, Search } from "lucide-react";
import { MessageList } from "@/components/messages/message-list";
import { MessageDetailView } from "@/components/messages/message-detail-view";
import { SearchOverlay } from "@/components/messages/search-overlay";
import { extractFirstImage } from "@/components/messages/message-card";
import { renderMessageContent } from "@/lib/format";
import { SubNav } from "@/components/layout/sub-nav";
import { ErrorState, LoadingSkeleton } from "@/components/shared";
import { useRouter, useSearchParams } from "next/navigation";
import { useCallback, useEffect, useState } from "react";
import { GlassCard } from "@/components/glass/card";
import { GlassPanel } from "@/components/glass/panel";
import { SubNav } from "@/components/layout/sub-nav";
import { extractFirstImage } from "@/components/messages/message-card";
import { MessageDetailView } from "@/components/messages/message-detail-view";
import { MessageList } from "@/components/messages/message-list";
import { SearchOverlay } from "@/components/messages/search-overlay";
import { EmptyState, ErrorState, LoadingSkeleton } from "@/components/shared";
import { GuildSelector } from "@/components/shared/guild-selector";
import { Button } from "@/components/ui/button";
import {
Select,
@@ -32,10 +32,10 @@ import {
useReview,
useTextChannels,
} from "@/hooks";
import { renderMessageContent } from "@/lib/format";
import type { MessageRecord } from "@/lib/types";
import { cn } from "@/lib/utils";
import { useWebSocket } from "@/lib/ws/context";
import { GuildSelector } from "@/components/shared/guild-selector";
type MessagesTab = "all" | "images" | "review";
@@ -185,10 +185,7 @@ export default function MessagesPage() {
<div className="flex gap-4">
{/* Left pane */}
<div
className={cn(
"space-y-2",
detailId ? "w-1/2 lg:w-2/5" : "w-full",
)}
className={cn("space-y-2", detailId ? "w-1/2 lg:w-2/5" : "w-full")}
>
{tab === "all" && (
<MessageList
@@ -205,10 +202,7 @@ export default function MessagesPage() {
<ImageGrid items={images ?? []} onSelect={setDetailId} />
)}
{tab === "review" && (
<ReviewList
items={reviews ?? []}
onSelect={setDetailId}
/>
<ReviewList items={reviews ?? []} onSelect={setDetailId} />
)}
</div>
@@ -216,7 +210,10 @@ export default function MessagesPage() {
{detailId && (
<div className="sticky top-16 hidden w-1/2 self-start md:block lg:w-3/5">
{detailLoading ? (
<GlassPanel dense className="flex items-center justify-center py-12">
<GlassPanel
dense
className="flex items-center justify-center py-12"
>
<Loader2 className="size-5 animate-spin text-text-secondary/60" />
</GlassPanel>
) : detailMessage ? (
@@ -288,9 +285,12 @@ function ImageGrid({
);
})}
{items.length === 0 && (
<div className="col-span-3 py-12 text-center text-xs text-text-secondary/40">
No images
</div>
<EmptyState
icon={Image}
title="No images"
description="Messages with image attachments will show up here."
className="col-span-3"
/>
)}
</div>
);
@@ -325,9 +325,11 @@ function ReviewList({
</GlassCard>
))}
{items.length === 0 && (
<div className="py-12 text-center text-xs text-text-secondary/40">
No flagged messages
</div>
<EmptyState
icon={Flag}
title="No flagged messages"
description="Messages flagged by AI moderation will appear here for review."
/>
)}
</div>
);