From d1ac8a6ea90b22a25130c40883ba87b52dc0bec6 Mon Sep 17 00:00:00 2001 From: Till JS Date: Thu, 23 Apr 2026 22:58:47 +0200 Subject: [PATCH] =?UTF-8?q?feat(onboarding):=20M3=20=E2=80=94=20Screen=202?= =?UTF-8?q?=20(Look=20=E2=80=94=20theme=20mode=20+=20variant)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - onboarding-flow.svelte.ts: tiny ephemeral store that bridges freshly-typed values between screens (needed because authStore.user is JWT-derived and won't reflect PATCH /me/profile until next token mint — Screen 2's greeting would otherwise show the stale empty name) - name screen now writes into the flow store on submit and on skip - /onboarding/look/+page.svelte: * "Hi {name}, wähle deinen Look" greeting — falls back to JWT name, email local-part, or "dir" * Hell/Dunkel/System mode toggle * 8 theme variants (lume/nature/stone/ocean/sunset/midnight/rose/ lavender), live preview with gradient, instant-apply on click * Back button to Screen 1, Next to /onboarding/templates No server write here — `theme.setVariant` / `theme.setMode` already sync via userSettings into mana-auth. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/lib/stores/onboarding-flow.svelte.ts | 39 ++ .../routes/(app)/onboarding/look/+page.svelte | 334 ++++++++++++++++++ .../routes/(app)/onboarding/name/+page.svelte | 10 +- 3 files changed, 380 insertions(+), 3 deletions(-) create mode 100644 apps/mana/apps/web/src/lib/stores/onboarding-flow.svelte.ts create mode 100644 apps/mana/apps/web/src/routes/(app)/onboarding/look/+page.svelte diff --git a/apps/mana/apps/web/src/lib/stores/onboarding-flow.svelte.ts b/apps/mana/apps/web/src/lib/stores/onboarding-flow.svelte.ts new file mode 100644 index 000000000..4648b0696 --- /dev/null +++ b/apps/mana/apps/web/src/lib/stores/onboarding-flow.svelte.ts @@ -0,0 +1,39 @@ +/** + * Ephemeral state for the three-screen onboarding flow — holds values + * a later screen needs from an earlier screen (the freshly-typed name + * for Screen 2's greeting, the multi-selected template ids for the + * Screen 3 finish handler). + * + * Deliberately module-local and non-persistent: + * - The canonical source of truth is `authStore.user` (name) and the + * scene Dexie table (apps). This store only bridges screens inside + * a single session. + * - We can't mutate `authStore.user.name` after PATCH /me/profile + * because `user` is minted from the JWT; the in-memory value only + * catches up on the next token refresh. This store lets Screen 2 + * greet the user immediately with what they just typed. + * - Reset on flow completion so a `markComplete → reset → revisit` + * from settings starts fresh. + */ + +let pendingName = $state(null); +let selectedTemplateIds = $state([]); + +export const onboardingFlow = { + get pendingName() { + return pendingName; + }, + get selectedTemplateIds() { + return selectedTemplateIds; + }, + setPendingName(value: string) { + pendingName = value.trim() || null; + }, + setSelectedTemplateIds(ids: string[]) { + selectedTemplateIds = ids; + }, + reset() { + pendingName = null; + selectedTemplateIds = []; + }, +}; diff --git a/apps/mana/apps/web/src/routes/(app)/onboarding/look/+page.svelte b/apps/mana/apps/web/src/routes/(app)/onboarding/look/+page.svelte new file mode 100644 index 000000000..c6d9020c8 --- /dev/null +++ b/apps/mana/apps/web/src/routes/(app)/onboarding/look/+page.svelte @@ -0,0 +1,334 @@ + + + +
+
+

Hi {displayName}, wähle deinen Look

+

+ Das Theme gilt sofort und für alle Module. Du kannst es jederzeit in Einstellungen wechseln. +

+
+ +
+ +
+ {#each modes as m (m.id)} + {@const isActive = theme.mode === m.id} + + {/each} +
+
+ +
+ +
+ {#each allVariants as variant (variant)} + {@const def = THEME_DEFINITIONS[variant]} + {@const isActive = theme.variant === variant} + + {/each} +
+
+ +
+ + +
+
+ + diff --git a/apps/mana/apps/web/src/routes/(app)/onboarding/name/+page.svelte b/apps/mana/apps/web/src/routes/(app)/onboarding/name/+page.svelte index 4626b4287..ba8bda267 100644 --- a/apps/mana/apps/web/src/routes/(app)/onboarding/name/+page.svelte +++ b/apps/mana/apps/web/src/routes/(app)/onboarding/name/+page.svelte @@ -8,6 +8,7 @@ import { goto } from '$app/navigation'; import { browser } from '$app/environment'; import { authStore } from '$lib/stores/auth.svelte'; + import { onboardingFlow } from '$lib/stores/onboarding-flow.svelte'; import { ArrowRight } from '@mana/shared-icons'; function getAuthUrl(): string { @@ -19,9 +20,10 @@ return import.meta.env.DEV ? 'http://localhost:3001' : ''; } - // Prefill: existing name (returning user revisiting) → email local-part - // → empty. Trimmed so whitespace-only values don't count as "filled". - let name = $state((authStore.user?.name ?? '').trim()); + // Prefill: last value entered in this flow (back-navigation) → existing + // `user.name` (returning user revisiting) → empty. Trimmed so + // whitespace-only values don't count as "filled". + let name = $state((onboardingFlow.pendingName ?? authStore.user?.name ?? '').trim()); let saving = $state(false); let error = $state(null); @@ -48,6 +50,7 @@ error = null; try { await saveName(trimmed); + onboardingFlow.setPendingName(trimmed); await goto('/onboarding/look'); } catch (err) { console.error('[onboarding/name] save failed:', err); @@ -70,6 +73,7 @@ } finally { saving = false; } + onboardingFlow.setPendingName(fallback); await goto('/onboarding/look'); }