diff --git a/apps/manacore/apps/web/src/lib/api/base-client.ts b/apps/manacore/apps/web/src/lib/api/base-client.ts index ffa7b0076..686b47990 100644 --- a/apps/manacore/apps/web/src/lib/api/base-client.ts +++ b/apps/manacore/apps/web/src/lib/api/base-client.ts @@ -54,6 +54,23 @@ function isNetworkError(error: Error): boolean { ); } +/** + * Human-readable HTTP status messages + */ +function httpStatusMessage(status: number): string { + const messages: Record = { + 400: 'Ungultige Anfrage — bitte Eingaben prufen', + 404: 'Nicht gefunden', + 409: 'Konflikt — Daten wurden zwischenzeitlich geandert', + 422: 'Eingaben konnten nicht verarbeitet werden', + 429: 'Zu viele Anfragen — bitte kurz warten', + 500: 'Interner Server-Fehler', + 502: 'Service vorubergehend nicht erreichbar', + 503: 'Service wird gewartet', + }; + return messages[status] || `Fehler (HTTP ${status})`; +} + /** * Fetch with authentication and retry logic * @@ -86,23 +103,27 @@ export async function fetchWithRetry( if (!response.ok) { // Don't retry on auth errors - if (response.status === 401 || response.status === 403) { + if (response.status === 401) { return { data: null, - error: `Authentication failed (${response.status})`, + error: 'Sitzung abgelaufen — bitte neu anmelden', + }; + } + if (response.status === 403) { + return { + data: null, + error: 'Keine Berechtigung fur diese Aktion', }; } // Don't retry on client errors (except rate limiting) if (response.status >= 400 && response.status < 500 && response.status !== 429) { - const errorBody = await response.json().catch(() => ({ message: 'Request failed' })); - return { - data: null, - error: errorBody.message || `HTTP ${response.status}`, - }; + const errorBody = await response.json().catch(() => ({ message: '' })); + const msg = errorBody.message || httpStatusMessage(response.status); + return { data: null, error: msg }; } - throw new Error(`HTTP ${response.status}`); + throw new Error(`Server-Fehler (${response.status})`); } const data = await response.json(); diff --git a/apps/manacore/apps/web/src/lib/components/Breadcrumbs.svelte b/apps/manacore/apps/web/src/lib/components/Breadcrumbs.svelte new file mode 100644 index 000000000..46036b315 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/components/Breadcrumbs.svelte @@ -0,0 +1,51 @@ + + + + + diff --git a/apps/manacore/apps/web/src/lib/components/KeyboardShortcutsModal.svelte b/apps/manacore/apps/web/src/lib/components/KeyboardShortcutsModal.svelte new file mode 100644 index 000000000..d28ece76b --- /dev/null +++ b/apps/manacore/apps/web/src/lib/components/KeyboardShortcutsModal.svelte @@ -0,0 +1,130 @@ + + + + +{#if open} + + + +{/if} + + diff --git a/apps/manacore/apps/web/src/lib/components/SessionWarning.svelte b/apps/manacore/apps/web/src/lib/components/SessionWarning.svelte new file mode 100644 index 000000000..8b1212b7d --- /dev/null +++ b/apps/manacore/apps/web/src/lib/components/SessionWarning.svelte @@ -0,0 +1,116 @@ + + +{#if showWarning} + +{/if} + + diff --git a/apps/manacore/apps/web/src/routes/(app)/+layout.svelte b/apps/manacore/apps/web/src/routes/(app)/+layout.svelte index 23fb3b07a..85dc3fa4f 100644 --- a/apps/manacore/apps/web/src/routes/(app)/+layout.svelte +++ b/apps/manacore/apps/web/src/routes/(app)/+layout.svelte @@ -3,6 +3,8 @@ import { page } from '$app/stores'; import type { Snippet } from 'svelte'; import { onMount } from 'svelte'; + import KeyboardShortcutsModal from '$lib/components/KeyboardShortcutsModal.svelte'; + import SessionWarning from '$lib/components/SessionWarning.svelte'; import { locale } from 'svelte-i18n'; import { PillNavigation } from '@manacore/shared-ui'; import type { PillNavItem, PillDropdownItem } from '@manacore/shared-ui'; @@ -30,6 +32,7 @@ let loading = $state(true); let isCollapsed = $state(false); + let showShortcuts = $state(false); // Get theme state let isDark = $derived(theme.isDark); @@ -106,6 +109,13 @@ return; } + // ? key opens keyboard shortcuts + if (event.key === '?' && !event.ctrlKey && !event.metaKey) { + event.preventDefault(); + showShortcuts = !showShortcuts; + return; + } + if ((event.ctrlKey || event.metaKey) && !event.shiftKey && !event.altKey) { const num = parseInt(event.key); if (num >= 1 && num <= navRoutes.length) { @@ -254,5 +264,11 @@ {@render children()} + + + + + + (showShortcuts = false)} /> {/if} diff --git a/apps/manacore/apps/web/src/routes/(app)/admin/users/+page.svelte b/apps/manacore/apps/web/src/routes/(app)/admin/users/+page.svelte index 63b320bdf..3ea2cc296 100644 --- a/apps/manacore/apps/web/src/routes/(app)/admin/users/+page.svelte +++ b/apps/manacore/apps/web/src/routes/(app)/admin/users/+page.svelte @@ -15,6 +15,8 @@ let loading = $state(true); let error = $state(null); let searchQuery = $state(''); + let currentPage = $state(1); + const pageSize = 20; let filteredUsers = $derived( searchQuery @@ -26,6 +28,17 @@ : users ); + let totalPages = $derived(Math.max(1, Math.ceil(filteredUsers.length / pageSize))); + let paginatedUsers = $derived( + filteredUsers.slice((currentPage - 1) * pageSize, currentPage * pageSize) + ); + + // Reset to page 1 when search changes + $effect(() => { + searchQuery; + currentPage = 1; + }); + onMount(async () => { try { // TODO: Replace with actual API call @@ -112,7 +125,34 @@ - + + + + {#if totalPages > 1} +
+ + Seite {currentPage} von {totalPages} + +
+ + +
+
+ {/if} {#if error}
-
- - - - - -
-

Meine Daten

-

- Ubersicht uber alle deine gespeicherten Daten (GDPR/DSGVO) -

-
+
+ +

Meine Daten

+

+ Ubersicht uber alle deine gespeicherten Daten (GDPR/DSGVO) +

{#if userData}