fix(ui): make the dashboards agree with each other and with what the code does

Host and admin implement the same four operations with independently written copy, and
admin was stale or wrong in every case. Its release button was always enabled and always
read "Galerie freigeben", so a second tap returned a 409; it showed no release state, no
keepsake progress, no failure reason, no rebuild, and never refreshed after releasing.
It now matches the host page.

Both dashboards subscribed to SSE and never opened the connection — `onSseEvent` only
registers a handler. Every subscription was inert, so the keepsake progress bar sat
frozen after a release and PIN requests appeared only on a manual refresh. It happened
to work when arriving straight from /feed, which connects, and /feed disconnects on
destroy, so navigating to the dashboard killed it again.

The unban confirm named neither of the two things a host most needs to know: unbanning
also restores ALL of that guest's previously hidden photos to the gallery, diashow and
export, and it retires and rebuilds a released keepsake, during which every guest's
download is briefly unavailable. The ban modal warns that uploads vanish; nothing said
they come back. Both now do, gated on the gallery actually being released.

"Event verlassen" implied the account was being deleted, then the dialog said the guest
could log back in. It calls `DELETE /session` — this device only, nothing deleted — so
it is "Abmelden" now. Gallery release now states it locks uploads and is reversible; PIN
reset states the guest is signed out on all devices.

The keepsake download failed silently: nothing inspected the iframe result and the
ticket POST always succeeded, so an over-limit tap did nothing at all. It now surfaces
the (newly visible) 429 and confirms the download started. `/export` rendered "Export
noch nicht verfügbar / Schau nach der Veranstaltung noch einmal vorbei" when the status
request had merely FAILED — telling a guest to come back after an event that already
happened. Both dashboards' error states gained a retry, which a host on a PWA with no
URL bar otherwise has no way to reach.

Modals were centred with no max-height, so on a short viewport the join PIN dialog
clipped equally top and bottom — potentially putting "Weiter zur Galerie" off-screen at
the moment a first-time guest must proceed. The ten moderation buttons were ~28px tall
side by side, on the screen where a mis-tap bans the wrong guest; they are 44px now.
Six German quotation marks paired the opening „ with an ASCII straight quote.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Fabian Hamm (Privat)
2026-08-03 18:36:57 +02:00
parent 89ca819529
commit 2b1500e624
9 changed files with 465 additions and 54 deletions

View File

@@ -5,8 +5,7 @@
import { focusTrap } from '$lib/actions/focus-trap';
import { scrollLock } from '$lib/actions/scroll-lock';
import { vibrate } from '$lib/haptics';
const GUIDE_SEEN_KEY = 'eventsnap_guide_seen';
import { hasSeenGuide, markGuideSeen } from '$lib/onboarding';
type Step =
| { kind: 'text'; icon: string; title: string; body: string }
@@ -31,7 +30,7 @@
kind: 'text',
icon: '⬆️',
title: 'Fotos & Videos hochladen',
body: 'Tippe auf den Plus-Button unten in der Mitte, um Fotos aus deiner Galerie zu wählen oder direkt mit der Kamera aufzunehmen. Mehrere Dateien auf einmal sind kein Problem!'
body: 'Tippe auf den Kamera-Button unten in der Mitte, um Fotos aus deiner Galerie zu wählen oder direkt mit der Kamera aufzunehmen. Mehrere Dateien auf einmal sind kein Problem!'
},
{
kind: 'text',
@@ -55,8 +54,8 @@
icon: '🔑',
title: 'Deinen PIN merken!',
body:
'Du hast beim Registrieren einen 4-stelligen PIN erhalten. Speichere ihn — du brauchst ihn, um dein Konto auf einem anderen Gerät wiederherzustellen. Er ist immer unter „Mein Konto" zu finden.' +
(hasPrivacyNote ? ' Den Datenschutzhinweis findest du ebenfalls unter „Mein Konto".' : '')
'Du hast beim Registrieren einen 4-stelligen PIN erhalten. Speichere ihn — du brauchst ihn, um dein Konto auf einem anderen Gerät wiederherzustellen. Er ist immer unter „Mein Konto zu finden.' +
(hasPrivacyNote ? ' Den Datenschutzhinweis findest du ebenfalls unter „Mein Konto.' : '')
}
]);
@@ -74,7 +73,7 @@
{ value: 'dark', label: 'Dunkel', hint: 'Dunkler Hintergrund', icon: '🌙' }
];
if (browser && !localStorage.getItem(GUIDE_SEEN_KEY)) {
if (browser && !hasSeenGuide()) {
visible = true;
}
@@ -87,7 +86,7 @@
}
function dismiss() {
if (browser) localStorage.setItem(GUIDE_SEEN_KEY, '1');
markGuideSeen();
vibrate([0, 8, 60, 8]);
visible = false;
}
@@ -100,8 +99,12 @@
{#if visible}
<!-- Backdrop -->
<div class="fixed inset-0 z-50 flex items-end justify-center bg-black/60 sm:items-center">
<!-- Same viewport guard as Modal/ConfirmSheet: the theme step (three option rows) is the
tallest thing a first-time guest sees, and this dialog's only exits are its own
"Weiter"/"Überspringen" buttons at the very bottom. Clipping them strands the guest
on step one of a modal they cannot dismiss. -->
<div
class="w-full max-w-sm rounded-t-3xl bg-white p-6 shadow-2xl dark:bg-gray-900 sm:rounded-2xl"
class="max-h-[90dvh] w-full max-w-sm overflow-y-auto overscroll-contain rounded-t-3xl bg-white p-6 shadow-2xl dark:bg-gray-900 sm:rounded-2xl"
role="dialog"
aria-modal="true"
aria-labelledby="onboarding-title"
@@ -150,7 +153,7 @@
</p>
{:else}
<p class="mb-4 text-sm text-gray-600 dark:text-gray-300">
Du kannst die Wahl jederzeit unter „Mein Konto" ändern.
Du kannst die Wahl jederzeit unter „Mein Konto ändern.
</p>
<div class="space-y-2 text-left" role="radiogroup" aria-label="Design">
{#each THEME_OPTIONS as opt (opt.value)}