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:
@@ -0,0 +1,56 @@
|
||||
"use client";
|
||||
|
||||
import { Component, type ReactNode } from "react";
|
||||
import { ErrorState } from "./states";
|
||||
|
||||
/**
|
||||
* React error boundary — catches render/exception crashes in child trees
|
||||
* (e.g. third-party lib throwing on unexpected payload shape) and surfaces
|
||||
* a consistent ErrorState instead of unmounting the whole app shell.
|
||||
*
|
||||
* Usage: wrap leaf views in <ErrorBoundary><SomeView /></ErrorBoundary>.
|
||||
*/
|
||||
interface ErrorBoundaryProps {
|
||||
children: ReactNode;
|
||||
fallback?: (error: Error, reset: () => void) => ReactNode;
|
||||
onReset?: () => void;
|
||||
}
|
||||
|
||||
interface ErrorBoundaryState {
|
||||
error: Error | null;
|
||||
}
|
||||
|
||||
export class ErrorBoundary extends Component<
|
||||
ErrorBoundaryProps,
|
||||
ErrorBoundaryState
|
||||
> {
|
||||
constructor(props: ErrorBoundaryProps) {
|
||||
super(props);
|
||||
this.state = { error: null };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
||||
return { error };
|
||||
}
|
||||
|
||||
reset = () => {
|
||||
this.setState({ error: null });
|
||||
this.props.onReset?.();
|
||||
};
|
||||
|
||||
override render() {
|
||||
if (!this.state.error) return this.props.children;
|
||||
|
||||
if (this.props.fallback) {
|
||||
return this.props.fallback(this.state.error, this.reset);
|
||||
}
|
||||
|
||||
return (
|
||||
<ErrorState
|
||||
title="Something went wrong"
|
||||
error={this.state.error}
|
||||
onRetry={this.reset}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ export { GuildChannelPicker } from "./guild-picker";
|
||||
export { MarkdownLite } from "./markdown";
|
||||
export { PageTransition } from "./page-transition";
|
||||
export { MetricTile, SectionHeader } from "./section";
|
||||
export { ErrorBoundary } from "./error-boundary";
|
||||
export {
|
||||
EmptyState,
|
||||
ErrorState,
|
||||
|
||||
Reference in New Issue
Block a user