feat(ui): elegant wedding white/silver/gold redesign
Reskin the whole app via design tokens in tailwind-theme.css (remapped color ramps) plus a define-once component layer in lib/styles/components.css (.btn, .card, .input, .chip, .badge, .sheet, …). Buttons are muted/outlined gold rather than flat fills. Self-host Inter + Fraunces (woff2) under the existing font-src 'self' CSP. Restyle the shared components and the account, admin, host, join, recover and upload screens against the new tokens. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -163,9 +163,7 @@
|
||||
|
||||
<div class="mx-auto max-w-lg space-y-3 p-4">
|
||||
<!-- Profile card -->
|
||||
<div
|
||||
class="rounded-xl border border-gray-200 bg-white p-5 dark:border-gray-700 dark:bg-gray-800"
|
||||
>
|
||||
<div class="card p-5">
|
||||
<div class="flex items-center gap-4">
|
||||
<div
|
||||
class="flex h-14 w-14 shrink-0 items-center justify-center rounded-full text-xl font-bold
|
||||
@@ -195,9 +193,7 @@
|
||||
|
||||
<!-- Dashboards section (host + admin only) -->
|
||||
{#if role === 'host' || role === 'admin'}
|
||||
<div
|
||||
class="overflow-hidden rounded-xl border border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-800"
|
||||
>
|
||||
<div class="card overflow-hidden">
|
||||
<div class="border-b border-gray-100 px-5 py-3 dark:border-gray-700">
|
||||
<h2
|
||||
class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400"
|
||||
@@ -211,7 +207,7 @@
|
||||
>
|
||||
<!-- Star icon -->
|
||||
<svg
|
||||
class="h-5 w-5 text-amber-500"
|
||||
class="h-5 w-5 text-primary-500"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
@@ -270,10 +266,12 @@
|
||||
|
||||
<!-- PIN card -->
|
||||
<div
|
||||
class="rounded-xl border border-amber-200 bg-amber-50 p-5 dark:border-amber-800/60 dark:bg-amber-950/30"
|
||||
class="rounded-xl border border-primary-200 bg-primary-50 p-5 dark:border-primary-800/60 dark:bg-primary-950/30"
|
||||
>
|
||||
<h2 class="mb-1 font-semibold text-amber-900 dark:text-amber-200">Wiederherstellungs-PIN</h2>
|
||||
<p class="mb-3 text-sm text-amber-700 dark:text-amber-300/90">
|
||||
<h2 class="mb-1 font-semibold text-primary-800 dark:text-primary-200">
|
||||
Wiederherstellungs-PIN
|
||||
</h2>
|
||||
<p class="mb-3 text-sm text-primary-700 dark:text-primary-300/90">
|
||||
Du brauchst diesen PIN, um dein Konto auf einem anderen Gerät wiederherzustellen. Schreib
|
||||
ihn auf!
|
||||
</p>
|
||||
@@ -287,7 +285,7 @@
|
||||
>
|
||||
<button
|
||||
onclick={copyPin}
|
||||
class="inline-flex min-h-11 items-center rounded-md bg-amber-100 px-4 py-2 text-sm font-medium text-amber-800 transition hover:bg-amber-200 dark:bg-amber-900/40 dark:text-amber-200 dark:hover:bg-amber-900/60"
|
||||
class="inline-flex min-h-11 items-center rounded-md bg-primary-100 px-4 py-2 text-sm font-medium text-primary-800 transition hover:bg-primary-200 dark:bg-primary-900/40 dark:text-primary-200 dark:hover:bg-primary-900/60"
|
||||
>
|
||||
{pinCopied ? 'Kopiert!' : 'Kopieren'}
|
||||
</button>
|
||||
@@ -337,9 +335,7 @@
|
||||
</a>
|
||||
|
||||
<!-- Theme / Design -->
|
||||
<div
|
||||
class="overflow-hidden rounded-xl border border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-800"
|
||||
>
|
||||
<div class="card overflow-hidden">
|
||||
<div class="border-b border-gray-100 px-5 py-3 dark:border-gray-700">
|
||||
<h2 class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
||||
Design
|
||||
@@ -394,9 +390,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Data mode -->
|
||||
<div
|
||||
class="overflow-hidden rounded-xl border border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-800"
|
||||
>
|
||||
<div class="card overflow-hidden">
|
||||
<div class="border-b border-gray-100 px-5 py-3 dark:border-gray-700">
|
||||
<h2 class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
||||
Datennutzung
|
||||
@@ -439,9 +433,7 @@
|
||||
|
||||
<!-- Per-user quota widget -->
|
||||
{#if $quotaStore.enabled && $quotaStore.limit_bytes != null}
|
||||
<div
|
||||
class="rounded-xl border border-gray-200 bg-white p-5 dark:border-gray-700 dark:bg-gray-800"
|
||||
>
|
||||
<div class="card p-5">
|
||||
<h2
|
||||
class="mb-2 text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
@@ -472,9 +464,7 @@
|
||||
|
||||
<!-- Datenschutzhinweis (preformatted, plain text) -->
|
||||
{#if $privacyNote.trim()}
|
||||
<div
|
||||
class="rounded-xl border border-gray-200 bg-white p-5 dark:border-gray-700 dark:bg-gray-800"
|
||||
>
|
||||
<div class="card p-5">
|
||||
<h2
|
||||
class="mb-2 text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
@@ -486,9 +476,7 @@
|
||||
{/if}
|
||||
|
||||
<!-- Konto section -->
|
||||
<div
|
||||
class="overflow-hidden rounded-xl border border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-800"
|
||||
>
|
||||
<div class="card overflow-hidden">
|
||||
<div class="border-b border-gray-100 px-5 py-3 dark:border-gray-700">
|
||||
<h2 class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
||||
Konto
|
||||
@@ -611,16 +599,10 @@
|
||||
<p class="mb-6 text-center text-sm text-gray-500 dark:text-gray-400">
|
||||
Original-Dateien können deutlich mehr Datenvolumen verbrauchen. Am besten im WLAN aktivieren.
|
||||
</p>
|
||||
<button
|
||||
onclick={confirmOriginalMode}
|
||||
class="mb-3 w-full rounded-xl bg-blue-600 py-3 text-sm font-semibold text-white transition hover:bg-blue-700 active:bg-blue-700"
|
||||
>
|
||||
<button onclick={confirmOriginalMode} class="btn btn-primary btn-block mb-3">
|
||||
Aktivieren
|
||||
</button>
|
||||
<button
|
||||
onclick={() => (dataModeWarningOpen = false)}
|
||||
class="w-full rounded-xl border border-gray-200 py-3 text-sm font-medium text-gray-700 transition hover:bg-gray-50 active:bg-gray-100 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-800 dark:active:bg-gray-800"
|
||||
>
|
||||
<button onclick={() => (dataModeWarningOpen = false)} class="btn btn-secondary btn-block">
|
||||
Abbrechen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -453,10 +453,7 @@
|
||||
Kopieren
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
onclick={() => (pinModal = null)}
|
||||
class="w-full rounded-lg bg-blue-600 py-2 text-sm font-semibold text-white hover:bg-blue-700 active:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-400 dark:active:bg-blue-400"
|
||||
>
|
||||
<button onclick={() => (pinModal = null)} class="btn btn-primary btn-block btn-sm">
|
||||
Schließen
|
||||
</button>
|
||||
{/if}
|
||||
@@ -473,16 +470,10 @@
|
||||
Diashow und Export, und die Sitzung wird beendet. Rückgängig machbar über „Entsperren“.
|
||||
</p>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
onclick={() => (banTarget = null)}
|
||||
class="flex-1 rounded-lg border border-gray-300 py-2 text-sm text-gray-700 hover:bg-gray-50 active:bg-gray-100 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-800 dark:active:bg-gray-800"
|
||||
<button onclick={() => (banTarget = null)} class="btn btn-secondary btn-sm flex-1"
|
||||
>Abbrechen</button
|
||||
>
|
||||
<button
|
||||
onclick={confirmBan}
|
||||
disabled={banSubmitting}
|
||||
class="flex-1 rounded-lg bg-red-600 py-2 text-sm font-medium text-white hover:bg-red-700 active:bg-red-700 disabled:opacity-50 dark:bg-red-500 dark:hover:bg-red-400 dark:active:bg-red-400"
|
||||
>
|
||||
<button onclick={confirmBan} disabled={banSubmitting} class="btn btn-danger btn-sm flex-1">
|
||||
{banSubmitting ? 'Wird gesperrt…' : 'Sperren'}
|
||||
</button>
|
||||
</div>
|
||||
@@ -543,33 +534,25 @@
|
||||
<div class="space-y-3">
|
||||
{#if stats}
|
||||
<div class="grid grid-cols-2 gap-3 sm:grid-cols-4">
|
||||
<div
|
||||
class="rounded-xl bg-white border border-gray-200 p-4 text-center dark:border-gray-700 dark:bg-gray-800"
|
||||
>
|
||||
<div class="card p-4 text-center">
|
||||
<p class="text-3xl font-bold text-gray-900 dark:text-gray-100">
|
||||
{stats.user_count}
|
||||
</p>
|
||||
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">Gäste</p>
|
||||
</div>
|
||||
<div
|
||||
class="rounded-xl bg-white border border-gray-200 p-4 text-center dark:border-gray-700 dark:bg-gray-800"
|
||||
>
|
||||
<div class="card p-4 text-center">
|
||||
<p class="text-3xl font-bold text-gray-900 dark:text-gray-100">
|
||||
{stats.upload_count}
|
||||
</p>
|
||||
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">Uploads</p>
|
||||
</div>
|
||||
<div
|
||||
class="rounded-xl bg-white border border-gray-200 p-4 text-center dark:border-gray-700 dark:bg-gray-800"
|
||||
>
|
||||
<div class="card p-4 text-center">
|
||||
<p class="text-3xl font-bold text-gray-900 dark:text-gray-100">
|
||||
{stats.comment_count}
|
||||
</p>
|
||||
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">Kommentare</p>
|
||||
</div>
|
||||
<div
|
||||
class="rounded-xl bg-white border border-gray-200 p-4 text-center dark:border-gray-700 dark:bg-gray-800"
|
||||
>
|
||||
<div class="card p-4 text-center">
|
||||
<p class="text-3xl font-bold text-gray-900 dark:text-gray-100">
|
||||
{diskPct(stats)} %
|
||||
</p>
|
||||
@@ -577,9 +560,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- Disk bar -->
|
||||
<div
|
||||
class="rounded-xl border border-gray-200 bg-white p-5 dark:border-gray-700 dark:bg-gray-800"
|
||||
>
|
||||
<div class="card p-5">
|
||||
<div
|
||||
class="mb-1 flex items-center justify-between text-xs text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
@@ -611,9 +592,7 @@
|
||||
{:else if activeTab === 'config'}
|
||||
<div class="relative space-y-3 pb-20">
|
||||
{#each CONFIG_GROUPS as group (group.title)}
|
||||
<div
|
||||
class="rounded-xl border border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-800"
|
||||
>
|
||||
<div class="card">
|
||||
<div class="border-b border-gray-100 px-5 py-3 dark:border-gray-700">
|
||||
<h3
|
||||
class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400"
|
||||
@@ -652,7 +631,7 @@
|
||||
id={field.key}
|
||||
rows="6"
|
||||
bind:value={configDraft[field.key]}
|
||||
class="w-full resize-none rounded-lg border border-gray-300 bg-white px-3 py-2 font-mono text-sm text-gray-900 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-200 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100"
|
||||
class="input resize-none font-mono text-sm"
|
||||
></textarea>
|
||||
{#if field.hint}
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">{field.hint}</p>
|
||||
@@ -670,7 +649,7 @@
|
||||
min="0"
|
||||
inputmode="decimal"
|
||||
bind:value={configDraft[field.key]}
|
||||
class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-200 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100"
|
||||
class="input text-sm"
|
||||
/>
|
||||
{#if field.hint}
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">{field.hint}</p>
|
||||
@@ -686,11 +665,7 @@
|
||||
<div
|
||||
class="sticky bottom-0 -mx-4 border-t border-gray-100 bg-white px-5 py-3 dark:border-gray-800 dark:bg-gray-900 sm:mx-0 sm:rounded-b-xl"
|
||||
>
|
||||
<button
|
||||
onclick={saveConfig}
|
||||
disabled={saving}
|
||||
class="w-full rounded-xl bg-blue-600 py-3 text-sm font-semibold text-white transition hover:bg-blue-700 disabled:opacity-50 dark:bg-blue-500 dark:hover:bg-blue-400"
|
||||
>
|
||||
<button onclick={saveConfig} disabled={saving} class="btn btn-primary btn-block">
|
||||
{saving ? 'Wird gespeichert…' : 'Speichern'}
|
||||
</button>
|
||||
</div>
|
||||
@@ -700,9 +675,7 @@
|
||||
{:else if activeTab === 'export'}
|
||||
<div class="space-y-3">
|
||||
<!-- Gallery release -->
|
||||
<div
|
||||
class="rounded-xl border border-gray-200 bg-white p-5 dark:border-gray-700 dark:bg-gray-800"
|
||||
>
|
||||
<div class="card p-5">
|
||||
<h3 class="mb-3 font-semibold text-gray-900 dark:text-gray-100">Galerie</h3>
|
||||
<button
|
||||
onclick={() =>
|
||||
@@ -714,16 +687,14 @@
|
||||
tone: 'danger',
|
||||
run: releaseGallery
|
||||
})}
|
||||
class="rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white transition hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-400"
|
||||
class="btn btn-primary btn-sm"
|
||||
>
|
||||
Galerie freigeben
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Export jobs -->
|
||||
<div
|
||||
class="rounded-xl border border-gray-200 bg-white p-5 dark:border-gray-700 dark:bg-gray-800"
|
||||
>
|
||||
<div class="card p-5">
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100">Export-Jobs</h3>
|
||||
<button
|
||||
@@ -781,9 +752,7 @@
|
||||
|
||||
<!-- ── Nutzer tab ───────────────────────────────────────────────── -->
|
||||
{:else if activeTab === 'users'}
|
||||
<div
|
||||
class="overflow-hidden rounded-xl border border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-800"
|
||||
>
|
||||
<div class="card overflow-hidden">
|
||||
<!-- Search -->
|
||||
<div class="p-4">
|
||||
<div
|
||||
@@ -824,21 +793,12 @@
|
||||
>{user.display_name}</span
|
||||
>
|
||||
{#if user.role === 'host'}
|
||||
<span
|
||||
class="rounded-full bg-blue-100 px-2 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-900/40 dark:text-blue-200"
|
||||
>Host</span
|
||||
>
|
||||
<span class="badge badge-primary">Host</span>
|
||||
{:else if user.role === 'admin'}
|
||||
<span
|
||||
class="rounded-full bg-purple-100 px-2 py-0.5 text-xs font-medium text-purple-700 dark:bg-purple-900/40 dark:text-purple-200"
|
||||
>Admin</span
|
||||
>
|
||||
<span class="badge badge-gold">Admin</span>
|
||||
{/if}
|
||||
{#if user.is_banned}
|
||||
<span
|
||||
class="rounded-full bg-red-100 px-2 py-0.5 text-xs font-medium text-red-700 dark:bg-red-900/40 dark:text-red-200"
|
||||
>Gesperrt</span
|
||||
>
|
||||
<span class="badge badge-danger">Gesperrt</span>
|
||||
{/if}
|
||||
</div>
|
||||
<p class="text-xs text-gray-400 dark:text-gray-500">
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
placeholder="Passwort"
|
||||
autocomplete="current-password"
|
||||
data-testid="admin-password-input"
|
||||
class="mb-3 w-full rounded-lg border border-gray-300 bg-white px-4 py-3 text-lg text-gray-900 placeholder-gray-400 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500"
|
||||
class="input mb-3 text-lg"
|
||||
/>
|
||||
|
||||
{#if error}
|
||||
@@ -71,7 +71,7 @@
|
||||
type="submit"
|
||||
disabled={loading || !password}
|
||||
data-testid="admin-login-submit"
|
||||
class="w-full rounded-lg bg-blue-600 px-4 py-3 text-lg font-medium text-white transition hover:bg-blue-700 disabled:opacity-50 dark:bg-blue-500 dark:hover:bg-blue-400"
|
||||
class="btn btn-primary btn-lg btn-block"
|
||||
>
|
||||
{loading ? 'Wird angemeldet…' : 'Anmelden'}
|
||||
</button>
|
||||
|
||||
@@ -437,12 +437,7 @@
|
||||
Kopieren
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
onclick={() => (pinModal = null)}
|
||||
class="w-full rounded-lg bg-blue-600 py-2 text-sm font-semibold text-white hover:bg-blue-700 active:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-400 dark:active:bg-blue-400"
|
||||
>
|
||||
Schließen
|
||||
</button>
|
||||
<button onclick={() => (pinModal = null)} class="btn btn-primary btn-block"> Schließen </button>
|
||||
{/if}
|
||||
</Modal>
|
||||
|
||||
@@ -458,17 +453,10 @@
|
||||
ansehen, Keepsake herunterladen) bleibt bestehen. Rückgängig machbar über „Entsperren“.
|
||||
</p>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
onclick={() => (banTarget = null)}
|
||||
class="flex-1 rounded-lg border border-gray-300 py-2 text-sm text-gray-700 hover:bg-gray-50 active:bg-gray-100 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-800 dark:active:bg-gray-800"
|
||||
>
|
||||
<button onclick={() => (banTarget = null)} class="btn btn-secondary flex-1">
|
||||
Abbrechen
|
||||
</button>
|
||||
<button
|
||||
onclick={confirmBan}
|
||||
disabled={banSubmitting}
|
||||
class="flex-1 rounded-lg bg-red-600 py-2 text-sm font-medium text-white hover:bg-red-700 active:bg-red-700 disabled:opacity-50 dark:bg-red-500 dark:hover:bg-red-400 dark:active:bg-red-400"
|
||||
>
|
||||
<button onclick={confirmBan} disabled={banSubmitting} class="btn btn-danger flex-1">
|
||||
{banSubmitting ? 'Wird gesperrt…' : 'Sperren'}
|
||||
</button>
|
||||
</div>
|
||||
@@ -551,15 +539,13 @@
|
||||
{/if}
|
||||
|
||||
<!-- ── Statistiken ─────────────────────────────────────────────── -->
|
||||
<div
|
||||
class="overflow-hidden rounded-xl border border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-800"
|
||||
>
|
||||
<div class="card overflow-hidden">
|
||||
<button
|
||||
onclick={() => (statsOpen = !statsOpen)}
|
||||
aria-expanded={statsOpen}
|
||||
class="flex w-full items-center justify-between px-5 py-4"
|
||||
>
|
||||
<h2 class="font-semibold text-gray-900 dark:text-gray-100">Statistiken</h2>
|
||||
<h2 class="section-title">Statistiken</h2>
|
||||
<svg
|
||||
class="h-5 w-5 text-gray-400 dark:text-gray-500 transition-transform duration-200 {statsOpen
|
||||
? 'rotate-180'
|
||||
@@ -580,50 +566,50 @@
|
||||
<div
|
||||
class="grid grid-cols-2 gap-3 border-t border-gray-100 p-4 dark:border-gray-700 sm:grid-cols-4"
|
||||
>
|
||||
<div class="rounded-xl bg-gray-50 p-4 text-center dark:bg-gray-900/60">
|
||||
<div class="surface-muted p-4 text-center">
|
||||
<p class="text-2xl font-bold text-gray-900 dark:text-gray-100">{users.length}</p>
|
||||
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">Teilnehmer</p>
|
||||
</div>
|
||||
<div class="rounded-xl bg-gray-50 p-4 text-center dark:bg-gray-900/60">
|
||||
<div class="surface-muted p-4 text-center">
|
||||
<p class="text-2xl font-bold text-gray-900 dark:text-gray-100">
|
||||
{users.reduce((s, u) => s + u.upload_count, 0)}
|
||||
</p>
|
||||
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">Uploads</p>
|
||||
</div>
|
||||
<div class="rounded-xl bg-gray-50 p-4 text-center dark:bg-gray-900/60">
|
||||
<p
|
||||
class="text-2xl font-bold {event.uploads_locked
|
||||
? 'text-red-600 dark:text-red-400'
|
||||
: 'text-green-600 dark:text-green-400'}"
|
||||
<div class="surface-muted flex flex-col items-center p-4 text-center">
|
||||
<span
|
||||
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-semibold {event.uploads_locked
|
||||
? 'bg-red-100 text-red-700 dark:bg-red-950/50 dark:text-red-300'
|
||||
: 'bg-green-100 text-green-700 dark:bg-green-950/50 dark:text-green-300'}"
|
||||
>
|
||||
<span class="h-1.5 w-1.5 rounded-full bg-current"></span>
|
||||
{event.uploads_locked ? 'Gesperrt' : 'Offen'}
|
||||
</p>
|
||||
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">Uploads</p>
|
||||
</span>
|
||||
<p class="mt-1.5 text-xs text-gray-500 dark:text-gray-400">Uploads</p>
|
||||
</div>
|
||||
<div class="rounded-xl bg-gray-50 p-4 text-center dark:bg-gray-900/60">
|
||||
<p
|
||||
class="text-2xl font-bold {event.export_released
|
||||
? 'text-blue-600 dark:text-blue-400'
|
||||
: 'text-gray-400 dark:text-gray-500'}"
|
||||
<div class="surface-muted flex flex-col items-center p-4 text-center">
|
||||
<span
|
||||
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-semibold {event.export_released
|
||||
? 'bg-purple-100 text-purple-800 dark:bg-purple-950/50 dark:text-purple-200'
|
||||
: 'bg-gray-200 text-gray-600 dark:bg-gray-700 dark:text-gray-300'}"
|
||||
>
|
||||
{event.export_released ? 'Ja' : 'Nein'}
|
||||
</p>
|
||||
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">Freigegeben</p>
|
||||
<span class="h-1.5 w-1.5 rounded-full bg-current"></span>
|
||||
{event.export_released ? 'Freigegeben' : 'Noch nicht'}
|
||||
</span>
|
||||
<p class="mt-1.5 text-xs text-gray-500 dark:text-gray-400">Galerie</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Event-Einstellungen ─────────────────────────────────────── -->
|
||||
<div
|
||||
class="overflow-hidden rounded-xl border border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-800"
|
||||
>
|
||||
<div class="card overflow-hidden">
|
||||
<button
|
||||
onclick={() => (settingsOpen = !settingsOpen)}
|
||||
aria-expanded={settingsOpen}
|
||||
class="flex w-full items-center justify-between px-5 py-4"
|
||||
>
|
||||
<h2 class="font-semibold text-gray-900 dark:text-gray-100">Event-Einstellungen</h2>
|
||||
<h2 class="section-title">Event-Einstellungen</h2>
|
||||
<svg
|
||||
class="h-5 w-5 text-gray-400 dark:text-gray-500 transition-transform duration-200 {settingsOpen
|
||||
? 'rotate-180'
|
||||
@@ -644,10 +630,10 @@
|
||||
<div class="flex flex-wrap gap-3 border-t border-gray-100 p-5 dark:border-gray-700">
|
||||
<button
|
||||
onclick={toggleEventLock}
|
||||
class="rounded-lg px-4 py-2 text-sm font-medium transition
|
||||
class="rounded-lg border px-4 py-2 text-sm font-medium transition
|
||||
{event.uploads_locked
|
||||
? 'bg-green-600 text-white hover:bg-green-700 dark:bg-green-500 dark:hover:bg-green-400'
|
||||
: 'bg-amber-500 text-white hover:bg-amber-600 dark:bg-amber-500 dark:hover:bg-amber-400'}"
|
||||
? 'border-green-600 bg-green-600 text-white hover:bg-green-700 dark:border-green-500 dark:bg-green-500 dark:hover:bg-green-400'
|
||||
: 'border-gray-300 bg-white text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700'}"
|
||||
>
|
||||
{event.uploads_locked ? 'Uploads wieder öffnen' : 'Uploads sperren'}
|
||||
</button>
|
||||
@@ -662,10 +648,7 @@
|
||||
run: releaseGallery
|
||||
})}
|
||||
disabled={event.export_released}
|
||||
class="rounded-lg px-4 py-2 text-sm font-medium transition
|
||||
{event.export_released
|
||||
? 'cursor-default bg-gray-100 text-gray-400 dark:bg-gray-800 dark:text-gray-500'
|
||||
: 'bg-blue-600 text-white hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-400'}"
|
||||
class="btn btn-primary btn-sm"
|
||||
>
|
||||
{event.export_released ? 'Galerie bereits freigegeben' : 'Galerie freigeben'}
|
||||
</button>
|
||||
@@ -751,15 +734,13 @@
|
||||
</div>
|
||||
|
||||
<!-- ── Nutzerverwaltung ───────────────────────────────────────── -->
|
||||
<div
|
||||
class="overflow-hidden rounded-xl border border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-800"
|
||||
>
|
||||
<div class="card overflow-hidden">
|
||||
<button
|
||||
onclick={() => (usersOpen = !usersOpen)}
|
||||
aria-expanded={usersOpen}
|
||||
class="flex w-full items-center justify-between px-5 py-4"
|
||||
>
|
||||
<h2 class="font-semibold text-gray-900 dark:text-gray-100">Nutzerverwaltung</h2>
|
||||
<h2 class="section-title">Nutzerverwaltung</h2>
|
||||
<svg
|
||||
class="h-5 w-5 text-gray-400 dark:text-gray-500 transition-transform duration-200 {usersOpen
|
||||
? 'rotate-180'
|
||||
@@ -818,21 +799,12 @@
|
||||
>{user.display_name}</span
|
||||
>
|
||||
{#if user.role === 'host'}
|
||||
<span
|
||||
class="rounded-full bg-blue-100 px-2 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-900/40 dark:text-blue-200"
|
||||
>Host</span
|
||||
>
|
||||
<span class="badge badge-primary">Host</span>
|
||||
{:else if user.role === 'admin'}
|
||||
<span
|
||||
class="rounded-full bg-purple-100 px-2 py-0.5 text-xs font-medium text-purple-700 dark:bg-purple-900/40 dark:text-purple-200"
|
||||
>Admin</span
|
||||
>
|
||||
<span class="badge badge-gold">Admin</span>
|
||||
{/if}
|
||||
{#if user.is_banned}
|
||||
<span
|
||||
class="rounded-full bg-red-100 px-2 py-0.5 text-xs font-medium text-red-700 dark:bg-red-900/40 dark:text-red-200"
|
||||
>Gesperrt</span
|
||||
>
|
||||
<span class="badge badge-danger">Gesperrt</span>
|
||||
{/if}
|
||||
</div>
|
||||
<p class="text-xs text-gray-400 dark:text-gray-500">
|
||||
|
||||
@@ -139,142 +139,184 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex min-h-screen items-center justify-center bg-gray-50 px-4 dark:bg-gray-950">
|
||||
<div class="w-full max-w-sm">
|
||||
{#if nameTaken}
|
||||
<!-- Name-taken state: sign in with PIN or choose a different name -->
|
||||
<div
|
||||
class="relative flex min-h-screen flex-col items-center justify-center overflow-hidden bg-gradient-to-b from-primary-50 via-gray-50 to-gray-50 px-4 py-10 dark:from-gray-900 dark:via-gray-950 dark:to-gray-950"
|
||||
>
|
||||
<!-- Soft celebratory glow behind the card — a champagne-gold and a silver wash
|
||||
for the classic warm/cool wedding tension, kept subtle. -->
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="pointer-events-none absolute -top-20 h-72 w-72 rounded-full bg-primary-200/30 blur-3xl dark:bg-primary-900/25"
|
||||
></div>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="pointer-events-none absolute -bottom-24 right-0 h-72 w-72 rounded-full bg-gray-200/50 blur-3xl dark:bg-gray-800/40"
|
||||
></div>
|
||||
|
||||
<div class="relative w-full max-w-sm">
|
||||
<!-- Brand -->
|
||||
<div class="mb-7 flex flex-col items-center text-center">
|
||||
<div
|
||||
class="mb-5 rounded-lg border border-amber-200 bg-amber-50 p-4 dark:border-amber-800/60 dark:bg-amber-950/30"
|
||||
class="mb-3 flex h-16 w-16 items-center justify-center rounded-2xl bg-blue-600 text-white shadow-lg shadow-primary-600/30"
|
||||
>
|
||||
<p class="font-semibold text-amber-900 dark:text-amber-200">
|
||||
„{takenName}" ist bereits vergeben.
|
||||
</p>
|
||||
<p class="mt-1 text-sm text-amber-800 dark:text-amber-300/90">
|
||||
Wähle einen anderen Namen, z. B. einen Spitznamen oder füge deinen Nachnamen hinzu („{takenName}
|
||||
M." oder „{takenName} aus Berlin").
|
||||
</p>
|
||||
<svg class="h-8 w-8" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path
|
||||
d="M4 8.5A2.5 2.5 0 0 1 6.5 6h1.2c.5 0 .95-.28 1.17-.72l.42-.85A1.5 1.5 0 0 1 10.9 3.6h2.2c.57 0 1.09.32 1.34.83l.42.85c.22.44.67.72 1.17.72h1.2A2.5 2.5 0 0 1 20 8.5v7A2.5 2.5 0 0 1 17.5 18h-11A2.5 2.5 0 0 1 4 15.5v-7Z"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.6"
|
||||
/>
|
||||
<circle cx="12" cy="12" r="3.2" stroke="currentColor" stroke-width="1.6" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<p class="mb-3 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Falls du das bist, melde dich mit deinem PIN an:
|
||||
</p>
|
||||
|
||||
<form
|
||||
onsubmit={(e) => {
|
||||
e.preventDefault();
|
||||
handleInlineRecover();
|
||||
}}
|
||||
<span
|
||||
class="font-display text-2xl font-semibold tracking-tight text-gray-900 dark:text-gray-100"
|
||||
>EventSnap</span
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value={recoveryPin}
|
||||
oninput={onRecoveryPinInput}
|
||||
placeholder="4-stelliger PIN"
|
||||
maxlength={4}
|
||||
inputmode="numeric"
|
||||
pattern="[0-9]*"
|
||||
data-testid="recovery-pin-input"
|
||||
class="mb-3 w-full rounded-lg border border-gray-300 bg-white px-4 py-3 text-center text-2xl font-mono tracking-widest text-gray-900 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if recoveryError}
|
||||
<p class="mb-3 text-sm text-red-600 dark:text-red-400" data-testid="recovery-error">
|
||||
{recoveryError}
|
||||
<!-- Invitation card -->
|
||||
<div class="card p-6 shadow-xl">
|
||||
{#if nameTaken}
|
||||
<!-- Name-taken state: sign in with PIN or choose a different name -->
|
||||
<div
|
||||
class="mb-5 rounded-lg border border-amber-200 bg-amber-50 p-4 dark:border-amber-800/60 dark:bg-amber-950/30"
|
||||
>
|
||||
<p class="font-semibold text-amber-900 dark:text-amber-200">
|
||||
„{takenName}" ist bereits vergeben.
|
||||
</p>
|
||||
{/if}
|
||||
<p class="mt-1 text-sm text-amber-800 dark:text-amber-300/90">
|
||||
Wähle einen anderen Namen, z. B. einen Spitznamen oder füge deinen Nachnamen hinzu
|
||||
(„{takenName}
|
||||
M." oder „{takenName} aus Berlin").
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p class="mb-3 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Falls du das bist, melde dich mit deinem PIN an:
|
||||
</p>
|
||||
|
||||
<form
|
||||
onsubmit={(e) => {
|
||||
e.preventDefault();
|
||||
handleInlineRecover();
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value={recoveryPin}
|
||||
oninput={onRecoveryPinInput}
|
||||
placeholder="4-stelliger PIN"
|
||||
maxlength={4}
|
||||
inputmode="numeric"
|
||||
pattern="[0-9]*"
|
||||
data-testid="recovery-pin-input"
|
||||
class="input mb-3 text-center font-mono text-2xl tracking-widest"
|
||||
/>
|
||||
|
||||
{#if recoveryError}
|
||||
<p class="mb-3 text-sm text-red-600 dark:text-red-400" data-testid="recovery-error">
|
||||
{recoveryError}
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={recoveryLoading || recoveryPin.length < 4}
|
||||
data-testid="recovery-submit"
|
||||
class="btn btn-primary btn-block mb-3"
|
||||
>
|
||||
{recoveryLoading ? 'Wird angemeldet...' : 'Anmelden'}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={recoveryLoading || recoveryPin.length < 4}
|
||||
data-testid="recovery-submit"
|
||||
class="mb-3 w-full rounded-lg bg-blue-600 px-4 py-3 font-medium text-white transition hover:bg-blue-700 disabled:opacity-50 dark:bg-blue-500 dark:hover:bg-blue-400"
|
||||
onclick={tryDifferentName}
|
||||
data-testid="try-different-name"
|
||||
class="btn btn-secondary btn-block"
|
||||
>
|
||||
{recoveryLoading ? 'Wird angemeldet...' : 'Anmelden'}
|
||||
Anderen Namen wählen
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<button
|
||||
onclick={tryDifferentName}
|
||||
data-testid="try-different-name"
|
||||
class="w-full rounded-lg border border-gray-300 px-4 py-3 font-medium text-gray-700 transition hover:bg-gray-50 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-800"
|
||||
>
|
||||
Anderen Namen wählen
|
||||
</button>
|
||||
|
||||
<!-- Forgot the PIN entirely — ask a host to reset it in-app. -->
|
||||
{#if pinRequestSent}
|
||||
<p
|
||||
class="mt-3 rounded-lg bg-green-50 px-4 py-3 text-center text-sm text-green-700 dark:bg-green-950/30 dark:text-green-300"
|
||||
>
|
||||
Anfrage gesendet. Bitte einen Host, deine PIN zurückzusetzen — danach kannst du dich mit
|
||||
der neuen PIN anmelden.
|
||||
</p>
|
||||
<!-- Forgot the PIN entirely — ask a host to reset it in-app. -->
|
||||
{#if pinRequestSent}
|
||||
<p
|
||||
class="mt-3 rounded-lg bg-green-50 px-4 py-3 text-center text-sm text-green-700 dark:bg-green-950/30 dark:text-green-300"
|
||||
>
|
||||
Anfrage gesendet. Bitte einen Host, deine PIN zurückzusetzen — danach kannst du dich mit
|
||||
der neuen PIN anmelden.
|
||||
</p>
|
||||
{:else}
|
||||
<button
|
||||
onclick={requestPinReset}
|
||||
disabled={pinRequestLoading}
|
||||
data-testid="request-pin-reset"
|
||||
class="mt-3 w-full text-center text-sm text-blue-600 underline decoration-dotted underline-offset-2 hover:text-blue-700 disabled:opacity-50 dark:text-blue-400"
|
||||
>
|
||||
{pinRequestLoading ? 'Wird gesendet…' : 'PIN vergessen? Host um Zurücksetzen bitten'}
|
||||
</button>
|
||||
{/if}
|
||||
{:else}
|
||||
<button
|
||||
onclick={requestPinReset}
|
||||
disabled={pinRequestLoading}
|
||||
data-testid="request-pin-reset"
|
||||
class="mt-3 w-full text-center text-sm text-blue-600 underline decoration-dotted underline-offset-2 hover:text-blue-700 disabled:opacity-50 dark:text-blue-400"
|
||||
<!-- Normal join form -->
|
||||
<p class="mb-1 text-center text-sm font-medium text-gray-500 dark:text-gray-400">
|
||||
Willkommen bei
|
||||
</p>
|
||||
{#if eventName}
|
||||
<h1
|
||||
class="mb-3 text-center text-3xl font-semibold text-gray-900 dark:text-gray-100"
|
||||
data-testid="join-event-name"
|
||||
>
|
||||
{eventName}
|
||||
</h1>
|
||||
{:else}
|
||||
<h1 class="mb-3 text-center text-3xl font-semibold text-gray-900 dark:text-gray-100">
|
||||
dem Event
|
||||
</h1>
|
||||
{/if}
|
||||
<p class="mb-6 text-center text-gray-600 dark:text-gray-400">
|
||||
Gib deinen Namen ein, um Fotos zu teilen und die Galerie zu sehen.
|
||||
</p>
|
||||
|
||||
<form
|
||||
onsubmit={(e) => {
|
||||
e.preventDefault();
|
||||
handleJoin();
|
||||
}}
|
||||
>
|
||||
{pinRequestLoading ? 'Wird gesendet…' : 'PIN vergessen? Host um Zurücksetzen bitten'}
|
||||
</button>
|
||||
{/if}
|
||||
{:else}
|
||||
<!-- Normal join form -->
|
||||
<h1 class="mb-2 text-center text-2xl font-bold text-gray-900 dark:text-gray-100">
|
||||
Willkommen!
|
||||
</h1>
|
||||
{#if eventName}
|
||||
<p
|
||||
class="mb-1 text-center text-lg font-semibold text-blue-600 dark:text-blue-400"
|
||||
data-testid="join-event-name"
|
||||
>
|
||||
{eventName}
|
||||
<input
|
||||
type="text"
|
||||
bind:value={displayName}
|
||||
placeholder="Dein Name"
|
||||
maxlength={50}
|
||||
data-testid="join-name-input"
|
||||
class="input mb-3 text-lg"
|
||||
/>
|
||||
|
||||
{#if error}
|
||||
<p class="mb-3 text-sm text-red-600 dark:text-red-400" data-testid="join-error">
|
||||
{error}
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading || !displayName.trim()}
|
||||
data-testid="join-submit"
|
||||
class="btn btn-primary btn-lg btn-block"
|
||||
>
|
||||
{loading ? 'Wird geladen...' : 'Beitreten'}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p class="mt-4 text-center text-sm">
|
||||
<a
|
||||
href="/recover"
|
||||
data-testid="link-to-recover"
|
||||
class="text-blue-600 hover:underline dark:text-blue-400"
|
||||
>Ich habe bereits einen Account</a
|
||||
>
|
||||
</p>
|
||||
{/if}
|
||||
<p class="mb-6 text-center text-gray-600 dark:text-gray-400">
|
||||
Gib deinen Namen ein, um dem Event beizutreten.
|
||||
</p>
|
||||
|
||||
<form
|
||||
onsubmit={(e) => {
|
||||
e.preventDefault();
|
||||
handleJoin();
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
bind:value={displayName}
|
||||
placeholder="Dein Name"
|
||||
maxlength={50}
|
||||
data-testid="join-name-input"
|
||||
class="mb-3 w-full rounded-lg border border-gray-300 bg-white px-4 py-3 text-lg text-gray-900 placeholder-gray-400 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500"
|
||||
/>
|
||||
|
||||
{#if error}
|
||||
<p class="mb-3 text-sm text-red-600 dark:text-red-400" data-testid="join-error">
|
||||
{error}
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading || !displayName.trim()}
|
||||
data-testid="join-submit"
|
||||
class="w-full rounded-lg bg-blue-600 px-4 py-3 text-lg font-medium text-white transition hover:bg-blue-700 disabled:opacity-50 dark:bg-blue-500 dark:hover:bg-blue-400"
|
||||
>
|
||||
{loading ? 'Wird geladen...' : 'Beitreten'}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p class="mt-4 text-center text-sm">
|
||||
<a
|
||||
href="/recover"
|
||||
data-testid="link-to-recover"
|
||||
class="text-blue-600 hover:underline dark:text-blue-400">Ich habe bereits einen Account</a
|
||||
>
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -284,7 +326,7 @@
|
||||
data-testid="pin-modal"
|
||||
>
|
||||
<div
|
||||
class="w-full max-w-sm rounded-xl bg-white p-6 shadow-lg dark:bg-gray-900"
|
||||
class="card w-full max-w-sm p-6 shadow-xl"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="pin-modal-title"
|
||||
@@ -298,18 +340,12 @@
|
||||
wiederherzustellen.
|
||||
</p>
|
||||
|
||||
<div
|
||||
class="mb-4 flex items-center justify-center gap-3 rounded-lg bg-gray-100 p-4 dark:bg-gray-800"
|
||||
>
|
||||
<div class="surface-muted mb-4 flex items-center justify-center gap-3 p-4">
|
||||
<span
|
||||
class="text-4xl font-mono font-bold tracking-widest text-gray-900 dark:text-gray-100"
|
||||
class="font-mono text-4xl font-bold tracking-widest text-gray-900 dark:text-gray-100"
|
||||
data-testid="pin-display">{pin}</span
|
||||
>
|
||||
<button
|
||||
onclick={copyPin}
|
||||
data-testid="pin-copy"
|
||||
class="min-h-11 min-w-11 rounded-md bg-gray-200 px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-300 active:bg-gray-300 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600 dark:active:bg-gray-600"
|
||||
>
|
||||
<button onclick={copyPin} data-testid="pin-copy" class="btn btn-secondary btn-sm min-h-11">
|
||||
{copied ? 'Kopiert!' : 'Kopieren'}
|
||||
</button>
|
||||
</div>
|
||||
@@ -317,14 +353,14 @@
|
||||
<button
|
||||
onclick={goToFeed}
|
||||
data-testid="continue-to-feed"
|
||||
class="mb-2 w-full rounded-lg bg-blue-600 px-4 py-3 font-medium text-white transition hover:bg-blue-700 active:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-400 dark:active:bg-blue-400"
|
||||
class="btn btn-primary btn-block mb-2"
|
||||
>
|
||||
Weiter zur Galerie
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={closePinModal}
|
||||
class="w-full rounded-lg py-2 text-sm text-gray-500 hover:text-gray-700 active:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 dark:active:text-gray-200"
|
||||
class="btn btn-ghost btn-block text-sm text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
Schließen
|
||||
</button>
|
||||
|
||||
@@ -132,7 +132,7 @@
|
||||
placeholder="Dein Name"
|
||||
maxlength={50}
|
||||
data-testid="recover-name-input"
|
||||
class="mb-3 w-full rounded-lg border border-gray-300 bg-white px-4 py-3 text-lg text-gray-900 placeholder-gray-400 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500"
|
||||
class="input mb-3 text-lg"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
@@ -143,7 +143,7 @@
|
||||
inputmode="numeric"
|
||||
pattern="[0-9]*"
|
||||
data-testid="recover-pin-input"
|
||||
class="mb-3 w-full rounded-lg border border-gray-300 bg-white px-4 py-3 text-center text-2xl font-mono tracking-widest text-gray-900 placeholder-gray-400 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500"
|
||||
class="input mb-3 text-center text-2xl font-mono tracking-widest"
|
||||
/>
|
||||
|
||||
{#if error}
|
||||
@@ -156,7 +156,7 @@
|
||||
type="submit"
|
||||
disabled={loading || !displayName.trim() || pin.length < 4}
|
||||
data-testid="recover-submit"
|
||||
class="w-full rounded-lg bg-blue-600 px-4 py-3 text-lg font-medium text-white transition hover:bg-blue-700 disabled:opacity-50 dark:bg-blue-500 dark:hover:bg-blue-400"
|
||||
class="btn btn-primary btn-lg btn-block"
|
||||
>
|
||||
{loading ? 'Wird geladen...' : 'Wiederherstellen'}
|
||||
</button>
|
||||
|
||||
@@ -151,8 +151,7 @@
|
||||
onclick={handleSubmit}
|
||||
disabled={stagedFiles.length === 0 || submitting}
|
||||
data-testid="upload-submit-header"
|
||||
class="rounded-lg bg-blue-600 px-4 py-1.5 text-sm font-semibold text-white transition
|
||||
hover:bg-blue-700 disabled:opacity-40 dark:bg-blue-500 dark:hover:bg-blue-400"
|
||||
class="btn btn-primary btn-sm"
|
||||
>
|
||||
{submitting ? 'Wird hochgeladen…' : 'Hochladen'}
|
||||
</button>
|
||||
@@ -199,31 +198,30 @@
|
||||
{:else}
|
||||
<!-- No files: prompt to go back and pick some -->
|
||||
<div class="flex flex-1 flex-col items-center justify-center gap-4 p-8 text-center">
|
||||
<svg
|
||||
class="h-16 w-16 text-gray-200 dark:text-gray-700"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="1"
|
||||
<div
|
||||
class="flex h-20 w-20 items-center justify-center rounded-2xl bg-primary-50 dark:bg-primary-900/20"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909M9 9.75h.008v.008H9V9.75zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zM6 20.25h12A2.25 2.25 0 0020.25 18V6A2.25 2.25 0 0018 3.75H6A2.25 2.25 0 003.75 6v12A2.25 2.25 0 006 20.25z"
|
||||
/>
|
||||
</svg>
|
||||
<svg
|
||||
class="h-9 w-9 text-primary-500 dark:text-primary-500"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909M9 9.75h.008v.008H9V9.75zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zM6 20.25h12A2.25 2.25 0 0020.25 18V6A2.25 2.25 0 0018 3.75H6A2.25 2.25 0 003.75 6v12A2.25 2.25 0 006 20.25z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-medium text-gray-500 dark:text-gray-400">Keine Dateien ausgewählt</p>
|
||||
<p class="mt-1 text-sm text-gray-400 dark:text-gray-500">
|
||||
Geh zurück und tippe auf den Plus-Button.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onclick={cancel}
|
||||
class="rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-800"
|
||||
>
|
||||
Zurück
|
||||
</button>
|
||||
<button onclick={cancel} class="btn btn-secondary btn-sm"> Zurück </button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -236,9 +234,7 @@
|
||||
data-testid="upload-caption"
|
||||
placeholder="Beschreibung hinzufügen… (#hashtags möglich)"
|
||||
rows="4"
|
||||
class="w-full resize-none rounded-xl border border-gray-200 bg-gray-50 px-4 py-3 text-sm text-gray-900
|
||||
placeholder-gray-400 focus:border-blue-400 focus:bg-white focus:outline-none focus:ring-1 focus:ring-blue-200
|
||||
dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-500 dark:focus:bg-gray-800"
|
||||
class="input resize-none text-sm"
|
||||
></textarea>
|
||||
<div class="mt-1 text-xs text-gray-500 text-right dark:text-gray-400">
|
||||
{caption.length} / {MAX_CAPTION_LENGTH}
|
||||
@@ -316,8 +312,7 @@
|
||||
onclick={handleSubmit}
|
||||
disabled={stagedFiles.length === 0 || submitting}
|
||||
data-testid="upload-submit"
|
||||
class="flex w-full items-center justify-center gap-2 rounded-xl bg-blue-600 py-3.5 text-sm font-semibold
|
||||
text-white transition hover:bg-blue-700 active:scale-[0.98] disabled:opacity-40 dark:bg-blue-500 dark:hover:bg-blue-400"
|
||||
class="btn btn-primary btn-block"
|
||||
>
|
||||
{#if submitting}
|
||||
<svg class="h-4 w-4 animate-spin" fill="none" viewBox="0 0 24 24">
|
||||
|
||||
Reference in New Issue
Block a user