fix(frontend): surface upload errors, push ban/lock, route guards (WS8)

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) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-27 16:16:01 +02:00
parent cf428725b9
commit 5bd008591b
9 changed files with 169 additions and 10 deletions

View File

@@ -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,
}))
}