215 lines
6.9 KiB
TypeScript
215 lines
6.9 KiB
TypeScript
|
|
import { Button } from "@repo/shadcn-ui/components";
|
|||
|
|
import * as React from "react";
|
|||
|
|
import { FallbackProps } from "react-error-boundary";
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 1) Fallback simple
|
|||
|
|
*/
|
|||
|
|
export function SimpleFallback({ error, resetErrorBoundary }: FallbackProps) {
|
|||
|
|
return (
|
|||
|
|
<div role='alert' className='p-4 rounded-md border bg-red-50 text-red-700'>
|
|||
|
|
<p className='font-medium'>⚠️ Algo salió mal</p>
|
|||
|
|
<pre className='mt-2 text-sm whitespace-pre-wrap'>{error?.message}</pre>
|
|||
|
|
<Button
|
|||
|
|
onClick={resetErrorBoundary}
|
|||
|
|
className='mt-3 px-3 py-1.5 rounded-md bg-red-600 text-white'
|
|||
|
|
>
|
|||
|
|
Reintentar
|
|||
|
|
</Button>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 2) Fallback de sección (tarjeta compacta)
|
|||
|
|
*/
|
|||
|
|
export function SectionCardFallback({ error, resetErrorBoundary }: FallbackProps) {
|
|||
|
|
return (
|
|||
|
|
<div role='alert' className='rounded-2xl border shadow-sm p-5 bg-white'>
|
|||
|
|
<div className='flex items-start gap-3'>
|
|||
|
|
<div className='shrink-0 rounded-full p-2 bg-red-100'>❗</div>
|
|||
|
|
<div className='grow'>
|
|||
|
|
<h3 className='font-semibold text-gray-900'>No se pudo cargar esta sección</h3>
|
|||
|
|
<p className='mt-1 text-sm text-gray-600'>{error?.message}</p>
|
|||
|
|
<div className='mt-3 flex gap-2'>
|
|||
|
|
<Button
|
|||
|
|
onClick={resetErrorBoundary}
|
|||
|
|
className='px-3 py-1.5 rounded-md bg-gray-900 text-white'
|
|||
|
|
>
|
|||
|
|
Reintentar
|
|||
|
|
</Button>
|
|||
|
|
<Button
|
|||
|
|
onClick={() => window.location.reload()}
|
|||
|
|
className='px-3 py-1.5 rounded-md border'
|
|||
|
|
>
|
|||
|
|
Refrescar página
|
|||
|
|
</Button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 3) Fallback pantalla completa
|
|||
|
|
*/
|
|||
|
|
export function FullPageFallback({ error }: FallbackProps) {
|
|||
|
|
return (
|
|||
|
|
<div className='min-h-screen flex flex-col items-center justify-center px-6 bg-gray-50 text-center'>
|
|||
|
|
<div className='text-5xl'>😵</div>
|
|||
|
|
<h1 className='mt-4 text-2xl font-bold text-gray-900'>Ocurrió un error inesperado</h1>
|
|||
|
|
<p className='mt-2 text-gray-600'>{error?.message}</p>
|
|||
|
|
<div className='mt-6 flex flex-wrap items-center justify-center gap-3'>
|
|||
|
|
<a href='/' className='px-4 py-2 rounded-md bg-blue-600 text-white'>
|
|||
|
|
Volver al inicio
|
|||
|
|
</a>
|
|||
|
|
<Button onClick={() => window.location.reload()} className='px-4 py-2 rounded-md border'>
|
|||
|
|
Recargar
|
|||
|
|
</Button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 4) Fallback para listas
|
|||
|
|
*/
|
|||
|
|
export function ListFallback({ error, resetErrorBoundary }: FallbackProps) {
|
|||
|
|
return (
|
|||
|
|
<div role='alert' className='p-4 rounded-md border bg-amber-50'>
|
|||
|
|
<div className='flex items-start gap-3'>
|
|||
|
|
<span className='text-xl'>🗂️</span>
|
|||
|
|
<div>
|
|||
|
|
<p className='font-medium text-amber-900'>No pudimos cargar la lista.</p>
|
|||
|
|
<p className='text-sm text-amber-800 mt-1'>{error?.message}</p>
|
|||
|
|
<div className='mt-3 flex gap-2'>
|
|||
|
|
<Button
|
|||
|
|
onClick={resetErrorBoundary}
|
|||
|
|
className='px-3 py-1.5 rounded-md bg-amber-700 text-white'
|
|||
|
|
>
|
|||
|
|
Reintentar
|
|||
|
|
</Button>
|
|||
|
|
<Button
|
|||
|
|
onClick={() => window.location.reload()}
|
|||
|
|
className='px-3 py-1.5 rounded-md border'
|
|||
|
|
>
|
|||
|
|
Recargar
|
|||
|
|
</Button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 5) Fallback para formularios
|
|||
|
|
*/
|
|||
|
|
export function FormFallback({ error, resetErrorBoundary }: FallbackProps) {
|
|||
|
|
return (
|
|||
|
|
<div role='alert' className='rounded-md border p-4 bg-red-50'>
|
|||
|
|
<h4 className='font-semibold text-red-800'>No se pudo mostrar el formulario</h4>
|
|||
|
|
<p className='mt-1 text-sm text-red-700'>{error?.message}</p>
|
|||
|
|
<div className='mt-3 flex gap-2'>
|
|||
|
|
<Button
|
|||
|
|
onClick={resetErrorBoundary}
|
|||
|
|
className='px-3 py-1.5 rounded-md bg-red-600 text-white'
|
|||
|
|
>
|
|||
|
|
Reintentar
|
|||
|
|
</Button>
|
|||
|
|
<Button onClick={() => history.back()} className='px-3 py-1.5 rounded-md border'>
|
|||
|
|
Volver atrás
|
|||
|
|
</Button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 6) Fallback para problemas de red
|
|||
|
|
*/
|
|||
|
|
export function NetworkFallback({ error, resetErrorBoundary }: FallbackProps) {
|
|||
|
|
const note = navigator.onLine
|
|||
|
|
? "La conexión parece estar activa."
|
|||
|
|
: "Estás sin conexión. Revisa tu red y reintenta.";
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div role='alert' className='rounded-md border p-4 bg-blue-50 text-blue-900'>
|
|||
|
|
<div className='font-medium'>No pudimos obtener los datos</div>
|
|||
|
|
<p className='mt-1 text-sm'>{error?.message}</p>
|
|||
|
|
<p className='mt-1 text-xs opacity-80'>{note}</p>
|
|||
|
|
<div className='mt-3 flex gap-2'>
|
|||
|
|
<Button
|
|||
|
|
onClick={resetErrorBoundary}
|
|||
|
|
className='px-3 py-1.5 rounded-md bg-blue-700 text-white'
|
|||
|
|
>
|
|||
|
|
Reintentar
|
|||
|
|
</Button>
|
|||
|
|
<Button onClick={() => window.location.reload()} className='px-3 py-1.5 rounded-md border'>
|
|||
|
|
Recargar
|
|||
|
|
</Button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 7) Fallback modo desarrollador (detalle de stack)
|
|||
|
|
*/
|
|||
|
|
export function DevDetailsFallback({ error, resetErrorBoundary }: FallbackProps) {
|
|||
|
|
const [open, setOpen] = React.useState(false);
|
|||
|
|
return (
|
|||
|
|
<div role='alert' className='rounded-md border p-4 bg-gray-50'>
|
|||
|
|
<div className='flex items-center justify-between'>
|
|||
|
|
<p className='font-medium text-gray-900'>Algo falló</p>
|
|||
|
|
<div className='flex gap-2'>
|
|||
|
|
<Button
|
|||
|
|
onClick={resetErrorBoundary}
|
|||
|
|
className='px-3 py-1.5 rounded-md bg-gray-900 text-white'
|
|||
|
|
>
|
|||
|
|
Reintentar
|
|||
|
|
</Button>
|
|||
|
|
<Button onClick={() => setOpen((v) => !v)} className='px-3 py-1.5 rounded-md border'>
|
|||
|
|
{open ? "Ocultar detalles" : "Ver detalles"}
|
|||
|
|
</Button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
{open && (
|
|||
|
|
<pre className='mt-3 text-xs whitespace-pre-wrap bg-white p-3 rounded-md border overflow-auto'>
|
|||
|
|
{error?.stack || error?.message}
|
|||
|
|
</pre>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 8) Fallback con soporte (link a ayuda)
|
|||
|
|
*/
|
|||
|
|
export function SupportFallback({ error, resetErrorBoundary }: FallbackProps) {
|
|||
|
|
return (
|
|||
|
|
<div role='alert' className='rounded-md border p-4 bg-white'>
|
|||
|
|
<h4 className='font-semibold text-gray-900'>No pudimos completar la acción</h4>
|
|||
|
|
<p className='mt-1 text-gray-700'>{error?.message}</p>
|
|||
|
|
<div className='mt-3 flex flex-wrap gap-2'>
|
|||
|
|
<Button
|
|||
|
|
onClick={resetErrorBoundary}
|
|||
|
|
className='px-3 py-1.5 rounded-md bg-gray-900 text-white'
|
|||
|
|
>
|
|||
|
|
Intentar de nuevo
|
|||
|
|
</Button>
|
|||
|
|
<a href='/ayuda' className='px-3 py-1.5 rounded-md border'>
|
|||
|
|
Ir a Ayuda
|
|||
|
|
</a>
|
|||
|
|
<a
|
|||
|
|
href='mailto:soporte@tuapp.com?subject=Error%20en%20la%20aplicaci%C3%B3n'
|
|||
|
|
className='px-3 py-1.5 rounded-md border'
|
|||
|
|
>
|
|||
|
|
Contactar soporte
|
|||
|
|
</a>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|