|
|
<script lang="ts"> |
|
|
import { onMount } from "svelte"; |
|
|
import type { Snippet } from "svelte"; |
|
|
import * as m from "$lib/paraglide/messages"; |
|
|
|
|
|
interface Props { |
|
|
children: Snippet; |
|
|
fallback?: Snippet<[Error]>; |
|
|
} |
|
|
|
|
|
let { children, fallback }: Props = $props(); |
|
|
|
|
|
let error = $state<Error | null>(null); |
|
|
let hasError = $state(false); |
|
|
|
|
|
onMount(() => { |
|
|
// Catch unhandled errors |
|
|
const handleError = (event: ErrorEvent) => { |
|
|
console.error("ErrorBoundary caught:", event.error); |
|
|
error = event.error; |
|
|
hasError = true; |
|
|
event.preventDefault(); |
|
|
}; |
|
|
|
|
|
// Catch unhandled promise rejections |
|
|
const handleRejection = (event: PromiseRejectionEvent) => { |
|
|
console.error("ErrorBoundary caught rejection:", event.reason); |
|
|
error = |
|
|
event.reason instanceof Error |
|
|
? event.reason |
|
|
: new Error(String(event.reason)); |
|
|
hasError = true; |
|
|
event.preventDefault(); |
|
|
}; |
|
|
|
|
|
window.addEventListener("error", handleError); |
|
|
window.addEventListener("unhandledrejection", handleRejection); |
|
|
|
|
|
return () => { |
|
|
window.removeEventListener("error", handleError); |
|
|
window.removeEventListener("unhandledrejection", handleRejection); |
|
|
}; |
|
|
}); |
|
|
|
|
|
function retry() { |
|
|
hasError = false; |
|
|
error = null; |
|
|
} |
|
|
|
|
|
function goHome() { |
|
|
window.location.href = "/"; |
|
|
} |
|
|
</script> |
|
|
|
|
|
{#if hasError && error} |
|
|
{#if fallback} |
|
|
{@render fallback(error)} |
|
|
{:else} |
|
|
<div |
|
|
class="min-h-screen flex items-center justify-center bg-gray-900 p-4" |
|
|
role="alert" |
|
|
aria-live="assertive" |
|
|
> |
|
|
<div class="bg-gray-800 rounded-lg p-8 max-w-md w-full text-center"> |
|
|
<div class="text-red-500 text-6xl mb-4" aria-hidden="true"> |
|
|
⚠️ |
|
|
</div> |
|
|
<h1 class="text-white text-2xl font-bold mb-2 uppercase"> |
|
|
{m.error_title?.() ?? "Something went wrong"} |
|
|
</h1> |
|
|
<p class="text-gray-400 mb-4 uppercase"> |
|
|
{m.error_description?.() ?? |
|
|
"An unexpected error occurred. Please try again."} |
|
|
</p> |
|
|
<details class="text-left mb-4"> |
|
|
<summary |
|
|
class="text-gray-500 uppercase cursor-pointer hover:text-gray-400" |
|
|
> |
|
|
{m.error_details?.() ?? "Technical details"} |
|
|
</summary> |
|
|
<pre |
|
|
class="mt-2 p-2 bg-gray-900 rounded text-red-400 text-xs overflow-auto max-h-32">{error.message} |
|
|
{error.stack}</pre> |
|
|
</details> |
|
|
<div class="flex gap-4 justify-center"> |
|
|
<button |
|
|
onclick={retry} |
|
|
class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors" |
|
|
> |
|
|
{m.error_retry?.() ?? "Try Again"} |
|
|
</button> |
|
|
<button |
|
|
onclick={goHome} |
|
|
class="px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700 transition-colors" |
|
|
> |
|
|
{m.error_go_home?.() ?? "Go Home"} |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
{/if} |
|
|
{:else} |
|
|
{@render children()} |
|
|
{/if}
|
|
|
|