ErrorBoundary.tsx 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. "use client";
  2. import React, { Component, ErrorInfo, ReactNode } from "react";
  3. interface Props {
  4. children: ReactNode;
  5. fallback?: ReactNode;
  6. onError?: (error: Error, errorInfo: ErrorInfo) => void;
  7. }
  8. interface State {
  9. hasError: boolean;
  10. error?: Error;
  11. }
  12. export class ErrorBoundary extends Component<Props, State> {
  13. public state: State = {
  14. hasError: false,
  15. };
  16. public static getDerivedStateFromError(error: Error): State {
  17. return { hasError: true, error };
  18. }
  19. public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
  20. console.error("ErrorBoundary caught an error:", error, errorInfo);
  21. this.props.onError?.(error, errorInfo);
  22. }
  23. public render() {
  24. if (this.state.hasError) {
  25. if (this.props.fallback) {
  26. return this.props.fallback;
  27. }
  28. return (
  29. <div className="flex min-h-[400px] flex-col items-center justify-center p-8 text-center">
  30. <div className="mb-4 text-6xl">⚠️</div>
  31. <h2 className="mb-2 text-2xl font-bold text-gray-900 dark:text-white">
  32. Something went wrong
  33. </h2>
  34. <p className="mb-6 text-gray-600 dark:text-gray-400">
  35. We&apos;re sorry, but something unexpected happened. Please try
  36. refreshing the page.
  37. </p>
  38. <div className="flex gap-4">
  39. <button
  40. onClick={() => window.location.reload()}
  41. className="rounded-lg bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500"
  42. >
  43. Refresh Page
  44. </button>
  45. <button
  46. onClick={() =>
  47. this.setState({ hasError: false, error: undefined })
  48. }
  49. className="rounded-lg border border-gray-300 px-4 py-2 text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-500 dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-800"
  50. >
  51. Try Again
  52. </button>
  53. </div>
  54. {process.env.NEXT_PUBLIC_APP_ENV !== "production" && this.state.error && (
  55. <details className="mt-6 w-full max-w-2xl">
  56. <summary className="cursor-pointer text-sm text-gray-500">
  57. Error Details (Development Only)
  58. </summary>
  59. <pre className="mt-2 overflow-auto rounded bg-gray-100 p-4 text-left text-xs text-red-600 dark:bg-gray-800 dark:text-red-400">
  60. {this.state.error.stack}
  61. </pre>
  62. </details>
  63. )}
  64. </div>
  65. );
  66. }
  67. return this.props.children;
  68. }
  69. }
  70. // Specialized error boundaries for different sections
  71. export function NavbarErrorBoundary({ children }: { children: ReactNode }) {
  72. return (
  73. <ErrorBoundary
  74. fallback={
  75. <nav className="relative flex flex-col items-center justify-between gap-4 bg-neutral-50 p-4 dark:bg-neutral-900 md:flex-row lg:px-6 lg:py-4">
  76. <div className="flex w-full items-center justify-between gap-0 sm:gap-4">
  77. <div className="flex max-w-fit gap-2 xl:gap-6">
  78. <div className="h-8 w-32 animate-pulse rounded bg-neutral-200 dark:bg-neutral-700" />
  79. </div>
  80. <div className="hidden flex-1 justify-center md:flex">
  81. <div className="h-8 w-64 animate-pulse rounded bg-neutral-200 dark:bg-neutral-700" />
  82. </div>
  83. <div className="flex max-w-fit gap-2 md:gap-4">
  84. <div className="h-8 w-8 animate-pulse rounded bg-neutral-200 dark:bg-neutral-700" />
  85. <div className="h-8 w-8 animate-pulse rounded bg-neutral-200 dark:bg-neutral-700" />
  86. </div>
  87. </div>
  88. </nav>
  89. }
  90. >
  91. {children}
  92. </ErrorBoundary>
  93. );
  94. }
  95. export function CartErrorBoundary({ children }: { children: ReactNode }) {
  96. return (
  97. <ErrorBoundary
  98. fallback={
  99. <div className="flex items-center justify-center p-4">
  100. <div className="text-center">
  101. <div className="mb-2 text-2xl">🛒</div>
  102. <p className="text-sm text-gray-600 dark:text-gray-400">
  103. Cart temporarily unavailable
  104. </p>
  105. </div>
  106. </div>
  107. }
  108. >
  109. {children}
  110. </ErrorBoundary>
  111. );
  112. }
  113. export function ProductErrorBoundary({ children }: { children: ReactNode }) {
  114. return (
  115. <ErrorBoundary
  116. fallback={
  117. <div className="flex min-h-[200px] items-center justify-center rounded-lg border border-gray-200 p-8 dark:border-gray-700">
  118. <div className="text-center">
  119. <div className="mb-2 text-3xl">📦</div>
  120. <p className="text-gray-600 dark:text-gray-400">
  121. Product information temporarily unavailable
  122. </p>
  123. </div>
  124. </div>
  125. }
  126. >
  127. {children}
  128. </ErrorBoundary>
  129. );
  130. }