← back to PoppyPetitions

components/ErrorBoundary.tsx

124 lines

'use client';

import { Component, type ReactNode, type ErrorInfo } from 'react';
import { AlertTriangle, RefreshCw } from 'lucide-react';

interface Props {
  children: ReactNode;
  fallback?: ReactNode;
}

interface State {
  hasError: boolean;
  error: Error | null;
}

export default class ErrorBoundary extends Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error: Error): State {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
    console.error('[ErrorBoundary] Caught error:', error, errorInfo);
  }

  handleReload = (): void => {
    this.setState({ hasError: false, error: null });
    window.location.reload();
  };

  render(): ReactNode {
    if (this.state.hasError) {
      if (this.props.fallback) {
        return this.props.fallback;
      }

      return (
        <div
          style={{
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
            minHeight: '100vh',
            backgroundColor: 'var(--color-bg)',
            padding: 24,
          }}
        >
          <div
            className="card"
            style={{
              maxWidth: 440,
              width: '100%',
              textAlign: 'center',
              padding: '40px 32px',
            }}
          >
            <AlertTriangle
              size={48}
              style={{
                color: 'var(--color-error, #ef4444)',
                margin: '0 auto 16px',
                display: 'block',
              }}
              aria-hidden="true"
            />
            <h2
              style={{
                fontSize: '1.25rem',
                fontWeight: 700,
                color: 'var(--color-text)',
                marginBottom: 8,
              }}
            >
              Something went wrong
            </h2>
            <p
              style={{
                fontSize: '0.8125rem',
                color: 'var(--color-text-muted)',
                marginBottom: 24,
                lineHeight: 1.6,
              }}
            >
              An unexpected error occurred. Please reload the page to try again.
            </p>
            {this.state.error && (
              <pre
                style={{
                  fontSize: '0.6875rem',
                  color: 'var(--color-text-muted)',
                  backgroundColor: 'var(--color-surface-el)',
                  padding: '12px',
                  borderRadius: 8,
                  marginBottom: 20,
                  overflow: 'auto',
                  maxHeight: 100,
                  textAlign: 'left',
                  border: '1px solid var(--color-border)',
                }}
              >
                {this.state.error.message}
              </pre>
            )}
            <button
              onClick={this.handleReload}
              className="btn btn-primary"
              aria-label="Reload page"
            >
              <RefreshCw size={16} aria-hidden="true" />
              Reload
            </button>
          </div>
        </div>
      );
    }

    return this.props.children;
  }
}