// Dependencies: react ^18, lucide-react
import React, { useState } from 'react';
import type { LucideIcon } from 'lucide-react';
import { AlertTriangle, RefreshCw } from 'lucide-react';
function cn(...classes: (string | false | null | undefined)[]) {
return classes.filter(Boolean).join(' ');
}
type ErrorStateProps = {
icon?: LucideIcon;
title?: string;
description?: string;
primaryAction?: { label: string; onClick: () => void };
secondaryAction?: { label: string; onClick: () => void };
retrying?: boolean;
size?: 'sm' | 'md' | 'lg';
};
const sizeConfig = {
sm: {
container: 'py-8 px-6',
iconWrap: 'w-10 h-10 rounded-xl',
iconSize: 'w-5 h-5',
title: 'text-base',
description: 'text-xs max-w-[240px]',
primaryBtn: 'px-3 py-1.5 text-xs',
secondaryBtn: 'px-3 py-1.5 text-xs',
},
md: {
container: 'py-12 px-8',
iconWrap: 'w-14 h-14 rounded-2xl',
iconSize: 'w-6 h-6',
title: 'text-lg',
description: 'text-sm max-w-[300px]',
primaryBtn: 'px-4 py-2 text-sm',
secondaryBtn: 'px-4 py-2 text-sm',
},
lg: {
container: 'py-20 px-10',
iconWrap: 'w-20 h-20 rounded-3xl',
iconSize: 'w-9 h-9',
title: 'text-2xl',
description: 'text-base max-w-[380px]',
primaryBtn: 'px-5 py-2.5 text-sm',
secondaryBtn: 'px-5 py-2.5 text-sm',
},
};
export function ErrorState({
icon: Icon = AlertTriangle,
title = 'Something went wrong',
description = 'An unexpected error occurred. Please try again.',
primaryAction,
secondaryAction,
retrying = false,
size = 'md',
}: ErrorStateProps) {
const s = sizeConfig[size];
return (
<div className={cn('flex flex-col items-center text-center', s.container)}>
<div className={cn('flex items-center justify-center mb-5 bg-red-500/10 border border-red-500/20', s.iconWrap)}>
<Icon className={cn('text-red-400', s.iconSize)} />
</div>
<h3 className={cn('font-bold text-white mb-2', s.title)}>{title}</h3>
<p className={cn('text-zinc-500 leading-relaxed mb-6', s.description)}>{description}</p>
{(primaryAction || secondaryAction) && (
<div className="flex items-center gap-2 flex-wrap justify-center">
{primaryAction && (
<button
onClick={primaryAction.onClick}
disabled={retrying}
className={cn(
'flex items-center gap-2 rounded-lg bg-indigo-500 hover:bg-indigo-400 disabled:opacity-60 disabled:cursor-not-allowed text-white font-semibold transition-colors',
s.primaryBtn
)}
>
{retrying && <RefreshCw className="w-3.5 h-3.5 animate-spin" />}
{retrying ? 'Retrying…' : primaryAction.label}
</button>
)}
{secondaryAction && (
<button
onClick={secondaryAction.onClick}
disabled={retrying}
className={cn(
'rounded-lg border border-[#2a2a2a] text-zinc-400 hover:border-[#3a3a3a] hover:text-zinc-200 disabled:opacity-40 font-medium transition-colors',
s.secondaryBtn
)}
>
{secondaryAction.label}
</button>
)}
</div>
)}
</div>
);
}
// Usage — wire retrying state to a real fetch:
// function MyPage() {
// const [retrying, setRetrying] = useState(false);
// const retry = async () => {
// setRetrying(true);
// try { await fetchData(); } finally { setRetrying(false); }
// };
// return (
// <ErrorState
// title="Failed to load"
// description="Could not fetch your projects."
// retrying={retrying}
// primaryAction={{ label: 'Try again', onClick: retry }}
// secondaryAction={{ label: 'Go back', onClick: () => history.back() }}
// />
// );
// }