From 62267f3d3e0386932c7d4df33ee4ce208419fa5b Mon Sep 17 00:00:00 2001 From: Till JS Date: Thu, 23 Apr 2026 23:29:03 +0200 Subject: [PATCH] feat(wardrobe): upload feedback + success confirmation on face-ref banner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The face-ref banner vanished silently the moment the Dexie write landed — the user had to open /profile/me-images to verify the upload actually worked. Reported as "musste dann in profil reinklicken um es zu sehen". Three phases now: prompt → uploading → success. - uploading: "Wird hochgeladen…" label on the zone + a small pill with SpinnerGap in the top-right corner. Zone is disabled so drops don't queue a second upload. - success: banner swaps to a confirmation card with the newly-saved thumbnail, a CheckCircle tick, and the next-step nudge ("Perfekt — als nächstes lädst du unten dein erstes Kleidungsstück hoch"). The border switches from dashed to solid with a soft primary tint so the state change is unmistakable. Fades out after 2.5s (or when the user hits "Schließen") at which point the face$ live-query has already flipped `face` non-null, so the banner stays unmounted. - Banner uses svelte/transition fade on mount/unmount for graceful entry/exit instead of popping in and out. The .spinner class is nested under .face-banner :global(.spinner) because it travels through the Phosphor component — Svelte's scoped CSS can't reach child components without :global(). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/lib/modules/wardrobe/ListView.svelte | 173 +++++++++++++++--- 1 file changed, 143 insertions(+), 30 deletions(-) diff --git a/apps/mana/apps/web/src/lib/modules/wardrobe/ListView.svelte b/apps/mana/apps/web/src/lib/modules/wardrobe/ListView.svelte index 50bd6aa45..7bb982dba 100644 --- a/apps/mana/apps/web/src/lib/modules/wardrobe/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/wardrobe/ListView.svelte @@ -12,7 +12,8 @@ the same way picture/ListView does. -->
@@ -67,32 +104,82 @@ {/each} - {#if !face$.loading && !face} -
-
- -
-

Lade ein Gesichtsbild hoch

-

- Wir brauchen dich auf Bild, damit Try-On Kleidung an dir visualisieren kann. Das Bild - bleibt lokal und wird nur für deine eigenen Generierungen genutzt. -

+ {#if showBanner} +
+ {#if uploadPhase === 'success'} +
+ {#if uploadedPreviewUrl} + + {:else} + + + + {/if} +
+

+ + Gesichtsbild gespeichert +

+

+ Perfekt — als nächstes lädst du unten dein erstes Kleidungsstück hoch. +

+
+
-
- - {#if faceUploadError} - {/if} @@ -148,6 +235,32 @@ flex: 1; min-height: 0; } + .face-banner { + border-color: hsl(var(--color-border)); + background: hsl(var(--color-background) / 0.5); + transition: + background-color 0.25s, + border-color 0.25s; + } + .face-banner-success { + border-style: solid; + border-color: hsl(var(--color-primary) / 0.4); + background: hsl(var(--color-primary) / 0.06); + } + /* The spinner class travels through Phosphor's , + which is a child component, so scoped CSS needs :global() to reach + the rendered . Nested under .face-banner keeps it local. */ + .face-banner :global(.spinner) { + animation: wardrobe-spin 0.9s linear infinite; + } + @keyframes wardrobe-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } + } @container (min-width: 640px) { .wardrobe-root { padding: 0.75rem 1rem 1rem;