From 5bd008591ba38c8365a80e4f0588fa4dd97f00d8 Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Sat, 27 Jun 2026 16:16:01 +0200 Subject: [PATCH] fix(frontend): surface upload errors, push ban/lock, route guards (WS8) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit H11: UploadSheet now honors the modal contract — role=dialog/aria-modal + accessible name, a focus-trap/Escape/focus-restore $effect mirroring ContextSheet, and inert + pointer-events-none when closed so its controls leave the tab order and AT tree. H12: a layout-level $effect toasts each upload-queue item the first time it transitions to 'error', so a banned/locked/over-quota guest is actually told why their photo didn't post instead of the composer silently closing. M10: api.ts now redirects to /join after clearAuth() on a 401 (guarded against loops on the auth screens), so a 401 no longer strands the user on a dead page. M11: ban and event-lock are pushed to guests. A new uploadsLocked store is seeded from /me/context (now exposes uploads_locked) and kept live by event-closed/opened SSE; the feed shows a banner and the FAB is disabled when locked. A user-banned SSE event force-logs-out the targeted user. M19: feed and export pages track a distinct loadError and render an "Erneut versuchen" retry card instead of collapsing a fetch failure into "empty" / "not released". Light-mode empty-state text bumped to gray-500 for contrast. Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/src/handlers/me.rs | 8 +++ frontend/src/lib/api.ts | 7 +++ frontend/src/lib/components/BottomNav.svelte | 6 ++- .../src/lib/components/UploadSheet.svelte | 53 +++++++++++++++++++ frontend/src/lib/event-state-store.ts | 10 ++++ frontend/src/lib/types.ts | 1 + frontend/src/routes/+layout.svelte | 41 +++++++++++++- frontend/src/routes/export/+page.svelte | 18 ++++++- frontend/src/routes/feed/+page.svelte | 35 ++++++++++-- 9 files changed, 169 insertions(+), 10 deletions(-) create mode 100644 frontend/src/lib/event-state-store.ts diff --git a/backend/src/handlers/me.rs b/backend/src/handlers/me.rs index 71c1df1..79ecd88 100644 --- a/backend/src/handlers/me.rs +++ b/backend/src/handlers/me.rs @@ -55,6 +55,9 @@ pub struct MeContextDto { pub privacy_note: String, pub quota_enabled: bool, pub storage_quota_enabled: bool, + /// Whether uploads are currently locked for the event, so the client can show + /// a banner + disable the upload affordance on load (not just via SSE). + pub uploads_locked: bool, } pub async fn get_context( @@ -68,6 +71,10 @@ pub async fn get_context( let privacy_note = config::get_str(&state.pool, "privacy_note", "").await; let quota_enabled = config::get_bool(&state.pool, "quota_enabled", true).await; let storage_quota_enabled = config::get_bool(&state.pool, "storage_quota_enabled", true).await; + let uploads_locked = crate::models::event::Event::find_by_slug(&state.pool, &state.config.event_slug) + .await? + .map(|e| e.uploads_locked_at.is_some()) + .unwrap_or(false); Ok(Json(MeContextDto { user_id: user.id, @@ -76,5 +83,6 @@ pub async fn get_context( privacy_note, quota_enabled, storage_quota_enabled, + uploads_locked, })) } diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 2ac3b4b..1d90d9c 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -1,3 +1,5 @@ +import { goto } from '$app/navigation'; +import { browser } from '$app/environment'; import { getToken, clearAuth } from './auth'; const BASE = '/api/v1'; @@ -42,6 +44,11 @@ async function request( if (!res.ok) { if (res.status === 401) { clearAuth(); + // Don't strand the user on a now-unauthenticated page with no nav — + // send them to /join. Guard against loops on the auth screens. + if (browser && !/^\/(join|recover|admin\/login)/.test(window.location.pathname)) { + void goto('/join'); + } } throw new ApiError(res.status, data.error ?? 'unknown', data.message ?? 'Fehler'); } diff --git a/frontend/src/lib/components/BottomNav.svelte b/frontend/src/lib/components/BottomNav.svelte index e92f6b5..b6389f7 100644 --- a/frontend/src/lib/components/BottomNav.svelte +++ b/frontend/src/lib/components/BottomNav.svelte @@ -3,6 +3,7 @@ import { page } from '$app/stores'; import { uploadSheetOpen, uploadBadgeCount } from '$lib/ui-store'; import { exportStatus, initExportStatus } from '$lib/export-status-store'; + import { uploadsLocked } from '$lib/event-state-store'; function isActive(path: string): boolean { return $page.url.pathname.startsWith(path); @@ -43,8 +44,9 @@