feat(fe): optimize error handling, state consistency, and WS feedback

- Add global SWR config with exponential backoff retry (swr-config.ts)
- Add ErrorBoundary component for React crash recovery (error-boundary.tsx)
- Standardize ErrorState onRetry on all 6 dashboard views (dashboard, media,
  messages, moderation, recordings, voice)
- Fix useAction: expose resetError + onError callback
- Refactor useSpeakers to SWR-backed state (was local useState) for
  consistent cache/revalidate semantics with other hooks
- Remove polling in useReview (15s refreshInterval); replace with
  useReviewWsSync subscribing to WS moderation_action events
- Extract hashUserId to lib/hash.ts (de-dup with ambient-canvas)
- WS context: add reconnect/error toast feedback via onStatusChange
- WS connection: expose reconnectAttemptCount getter

All typecheck + lint clean, Next 16 build passes.
This commit is contained in:
asepharyana committed 2026-08-25 13:46:15 +07:00
1 parent 796c6390ac
commit 31e303c187
17 files changed
+402 -79

No files matched your search

@@ -50,7 +50,12 @@ export function DashboardView({
initialStats?: DashboardStats;
initialActivity?: Awaited<ReturnType<typeof useActivity>>["data"];
}) {
const { data: stats, isLoading, error } = useStats(initialStats);
const {
data: stats,
isLoading,
error,
mutate: mutateStats,
} = useStats(initialStats);
const { data: activity } = useActivity(14, initialActivity as never);
const { data: reactors } = useTopReactors();
const { data: reactions } = useTopReactions();
@@ -65,7 +70,8 @@ export function DashboardView({
);
}, [stats, ambient]);
if (error && !stats) return <ErrorState error={error} />;
if (error && !stats)
return <ErrorState error={error} onRetry={() => void mutateStats()} />;
if (!stats && isLoading)
return (
<div className="space-y-5">
@@ -78,7 +84,13 @@ export function DashboardView({
</div>
</div>
);
if (!stats) return <ErrorState error={error ?? new Error("No data")} />;
if (!stats)
return (
<ErrorState
error={error ?? new Error("No data")}
onRetry={() => void mutateStats()}
/>
);
const s = stats;
const total = s.total_flagged + s.total_clean || 1;