All files / src/components/shared/ErrorPage index.tsx

0% Statements 0/197
100% Branches 0/0
0% Functions 0/1
0% Lines 0/197

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198                                                                                                                                                                                                                                                                                                                                                                                                           
'use client';

import Link from 'next/link';
import { Button } from '@/components/ui/Button';
import { Icon } from '@/components/ui/icons';
import { cn } from '@/lib/core';

export type ErrorVariant = 'warning' | 'error' | 'not-found' | 'unauthorized' | 'server';

export interface ErrorPageProps {
  /** Title displayed at the top of the error page */
  title?: string;
  /** Description message explaining the error */
  message?: string;
  /** Error code or digest for reference */
  errorCode?: string;
  /** Visual variant of the error icon */
  variant?: ErrorVariant;
  /** Whether to show the retry button */
  showRetry?: boolean;
  /** Whether to show the home button */
  showHome?: boolean;
  /** Whether to show the back button */
  showBack?: boolean;
  /** Whether to show the support link */
  showSupport?: boolean;
  /** Callback for the retry button */
  onRetry?: () => void;
  /** Additional className for the container */
  className?: string;
}

const variantConfig: Record<
  ErrorVariant,
  { icon: 'alert-triangle' | 'x-circle' | 'sad-face' | 'lock-closed' | 'alert-circle'; color: string }
> = {
  warning: {
    icon: 'alert-triangle',
    color: 'text-yellow-500 dark:text-yellow-400',
  },
  error: {
    icon: 'x-circle',
    color: 'text-red-500 dark:text-red-400',
  },
  'not-found': {
    icon: 'sad-face',
    color: 'text-gray-400 dark:text-gray-500',
  },
  unauthorized: {
    icon: 'lock-closed',
    color: 'text-orange-500 dark:text-orange-400',
  },
  server: {
    icon: 'alert-circle',
    color: 'text-red-600 dark:text-red-500',
  },
};

/**
 * ErrorPage - Shared error page component
 *
 * A reusable, accessible error page component that displays user-friendly
 * error messages with recovery actions. Used across route-level error boundaries.
 *
 * Features:
 * - Multiple visual variants (warning, error, not-found, unauthorized, server)
 * - Configurable recovery actions (retry, home, back)
 * - Optional support link
 * - Dark mode support
 * - Accessible (ARIA labels, focus management)
 *
 * @example
 * ```tsx
 * <ErrorPage
 *   title="Unable to load products"
 *   message="We couldn't load the products. Please try again."
 *   variant="error"
 *   showRetry
 *   showHome
 *   onRetry={() => reset()}
 * />
 * ```
 */
export function ErrorPage({
  title = 'Something went wrong',
  message = 'We encountered an unexpected error. Please try again.',
  errorCode,
  variant = 'warning',
  showRetry = true,
  showHome = true,
  showBack = false,
  showSupport = true,
  onRetry,
  className,
}: ErrorPageProps) {
  const config = variantConfig[variant];

  const handleBack = () => {
    if (typeof window !== 'undefined') {
      window.history.back();
    }
  };

  const handleRetry = () => {
    if (onRetry) {
      onRetry();
    } else if (typeof window !== 'undefined') {
      window.location.reload();
    }
  };

  return (
    <div
      className={cn(
        'min-h-[50vh] flex items-center justify-center px-4 py-12',
        className
      )}
      role="alert"
      aria-live="assertive"
    >
      <div className="text-center max-w-md">
        {/* Icon */}
        <div className="mb-6 flex justify-center">
          <div className={cn('w-16 h-16', config.color)}>
            <Icon name={config.icon} className="w-full h-full" aria-hidden="true" />
          </div>
        </div>

        {/* Title */}
        <h1 className="text-2xl font-bold text-gray-900 dark:text-white mb-3">
          {title}
        </h1>

        {/* Message */}
        <p className="text-gray-600 dark:text-gray-400 mb-6 leading-relaxed">
          {message}
        </p>

        {/* Error Code */}
        {errorCode && (
          <p className="text-sm text-gray-500 dark:text-gray-500 mb-6 font-mono">
            Error code: {errorCode}
          </p>
        )}

        {/* Action Buttons */}
        <div className="flex flex-col sm:flex-row gap-3 justify-center">
          {showRetry && (
            <Button
              onClick={handleRetry}
              variant="primary"
              leftIcon={<Icon name="refresh" className="w-4 h-4" />}
            >
              Try Again
            </Button>
          )}

          {showBack && (
            <Button
              onClick={handleBack}
              variant="outline"
              leftIcon={<Icon name="arrow-left" className="w-4 h-4" />}
            >
              Go Back
            </Button>
          )}

          {showHome && (
            <Link href="/" passHref>
              <Button
                variant="outline"
                leftIcon={<Icon name="home" className="w-4 h-4" />}
              >
                Home
              </Button>
            </Link>
          )}
        </div>

        {/* Support Link */}
        {showSupport && (
          <p className="mt-8 text-sm text-gray-500 dark:text-gray-400">
            Need help?{' '}
            <Link
              href="/support"
              className="text-blue hover:text-blue-dark dark:text-blue-400 dark:hover:text-blue-300 underline underline-offset-2"
            >
              Contact support
            </Link>
          </p>
        )}
      </div>
    </div>
  );
}

export default ErrorPage;