2026-06-24 12:34:14 +02:00
|
|
|
<script lang="ts">
|
|
|
|
|
import ArrowLeftIcon from '@lucide/svelte/icons/arrow-left';
|
|
|
|
|
import HomeIcon from '@lucide/svelte/icons/home';
|
|
|
|
|
import LockIcon from '@lucide/svelte/icons/lock';
|
|
|
|
|
import RefreshCwIcon from '@lucide/svelte/icons/refresh-cw';
|
|
|
|
|
import SearchXIcon from '@lucide/svelte/icons/search-x';
|
|
|
|
|
import TriangleAlertIcon from '@lucide/svelte/icons/triangle-alert';
|
|
|
|
|
import { goto, invalidateAll } from '$app/navigation';
|
|
|
|
|
import { resolve } from '$app/paths';
|
|
|
|
|
import { page } from '$app/state';
|
|
|
|
|
import PageMeta from '$lib/components/seo/page-meta.svelte';
|
|
|
|
|
import { Button } from '$lib/components/ui/button';
|
|
|
|
|
import * as Empty from '$lib/components/ui/empty';
|
|
|
|
|
import { m } from '$lib/paraglide/messages';
|
|
|
|
|
|
|
|
|
|
const status = $derived(page.status);
|
|
|
|
|
const message = $derived(page.error?.message ?? '');
|
|
|
|
|
|
|
|
|
|
const kind = $derived(
|
|
|
|
|
status === 404 ? 'not_found' : status === 401 || status === 403 ? 'unauthorized' : 'generic'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const title = $derived(
|
|
|
|
|
kind === 'not_found'
|
|
|
|
|
? m.error_not_found_title()
|
|
|
|
|
: kind === 'unauthorized'
|
|
|
|
|
? m.error_unauthorized_title()
|
|
|
|
|
: m.error_generic_title()
|
|
|
|
|
);
|
|
|
|
|
const description = $derived(
|
|
|
|
|
kind === 'not_found'
|
|
|
|
|
? m.error_not_found_description()
|
|
|
|
|
: kind === 'unauthorized'
|
|
|
|
|
? m.error_unauthorized_description()
|
|
|
|
|
: m.error_generic_description()
|
|
|
|
|
);
|
|
|
|
|
const Icon = $derived(
|
|
|
|
|
kind === 'not_found' ? SearchXIcon : kind === 'unauthorized' ? LockIcon : TriangleAlertIcon
|
|
|
|
|
);
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<PageMeta {title} {description} noIndex />
|
2026-06-25 23:11:32 +02:00
|
|
|
<div class="m-auto flex w-full max-w-2xl flex-col gap-4 p-6">
|
2026-06-24 12:34:14 +02:00
|
|
|
<Empty.Root class="border">
|
|
|
|
|
<Empty.Header>
|
|
|
|
|
<Empty.Media variant="icon" class="size-12">
|
|
|
|
|
<Icon class="size-6" />
|
|
|
|
|
</Empty.Media>
|
|
|
|
|
<Empty.Title>{title}</Empty.Title>
|
|
|
|
|
<Empty.Description>{description}</Empty.Description>
|
|
|
|
|
</Empty.Header>
|
|
|
|
|
<Empty.Content class="gap-3">
|
|
|
|
|
<div class="text-muted-foreground text-xs font-mono">
|
|
|
|
|
{m.error_code_label({ status })}
|
|
|
|
|
</div>
|
|
|
|
|
{#if message}
|
|
|
|
|
<details class="w-full max-w-lg text-left">
|
|
|
|
|
<summary class="text-muted-foreground cursor-pointer text-xs">
|
|
|
|
|
{m.error_details()}
|
|
|
|
|
</summary>
|
|
|
|
|
<pre
|
|
|
|
|
class="bg-muted text-foreground mt-2 max-h-64 overflow-auto rounded-md border p-3 text-xs whitespace-pre-wrap">{message}</pre>
|
|
|
|
|
</details>
|
|
|
|
|
{/if}
|
|
|
|
|
<div class="flex flex-wrap items-center justify-center gap-2">
|
|
|
|
|
<Button variant="outline" onclick={() => invalidateAll()}>
|
|
|
|
|
<RefreshCwIcon class="size-4" />
|
|
|
|
|
{m.error_action_retry()}
|
|
|
|
|
</Button>
|
|
|
|
|
<Button variant="outline" onclick={() => history.back()}>
|
|
|
|
|
<ArrowLeftIcon class="size-4" />
|
|
|
|
|
{m.error_action_back()}
|
|
|
|
|
</Button>
|
|
|
|
|
<Button onclick={() => goto(resolve('/'))}>
|
|
|
|
|
<HomeIcon class="size-4" />
|
|
|
|
|
{m.error_action_home()}
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</Empty.Content>
|
|
|
|
|
</Empty.Root>
|
|
|
|
|
</div>
|