From d2c9795405be1dc60903619a6a142c53ac507ab2 Mon Sep 17 00:00:00 2001 From: Till JS Date: Fri, 10 Apr 2026 22:51:00 +0200 Subject: [PATCH] feat(sync): add sync status PillNav dropdown + onboarding step PillNavigation sync dropdown: - New cloud icon pill showing sync status (Lokal/Sync/Pausiert) - Dropdown with contextual actions: activate, top up credits, settings - Shows next charge date when active - Only visible for authenticated users Onboarding wizard: - New SyncStep between AI tier and Credits steps - Explains local-first model: data always stays local, sync is optional - Interval selection (monthly 30 / quarterly 90 / yearly 360 credits) - Activate button with balance check and error handling - Also fixed missing AiTierStep rendering in wizard template Co-Authored-By: Claude Opus 4.6 (1M context) --- .../onboarding/OnboardingWizard.svelte | 6 + .../onboarding/steps/SyncStep.svelte | 158 ++++++++++++++++++ .../apps/web/src/routes/(app)/+layout.svelte | 69 ++++++++ .../src/navigation/PillNavigation.svelte | 19 +++ 4 files changed, 252 insertions(+) create mode 100644 apps/mana/apps/web/src/lib/components/onboarding/steps/SyncStep.svelte diff --git a/apps/mana/apps/web/src/lib/components/onboarding/OnboardingWizard.svelte b/apps/mana/apps/web/src/lib/components/onboarding/OnboardingWizard.svelte index db36577c1..838098a71 100644 --- a/apps/mana/apps/web/src/lib/components/onboarding/OnboardingWizard.svelte +++ b/apps/mana/apps/web/src/lib/components/onboarding/OnboardingWizard.svelte @@ -6,6 +6,7 @@ import ProfileStep from './steps/ProfileStep.svelte'; import AppsStep from './steps/AppsStep.svelte'; import AiTierStep from './steps/AiTierStep.svelte'; + import SyncStep from './steps/SyncStep.svelte'; import CreditsStep from './steps/CreditsStep.svelte'; import CompleteStep from './steps/CompleteStep.svelte'; import { Check } from '@mana/shared-icons'; @@ -31,6 +32,7 @@ { id: 'profile', label: 'Profil', component: ProfileStep }, { id: 'apps', label: 'Apps', component: AppsStep }, { id: 'ai-tier', label: 'KI', component: AiTierStep }, + { id: 'sync', label: 'Sync', component: SyncStep }, { id: 'credits', label: 'Credits', component: CreditsStep }, { id: 'complete', label: 'Fertig', component: CompleteStep }, ]; @@ -159,6 +161,10 @@ {:else if currentStepData.id === 'apps'} + {:else if currentStepData.id === 'ai-tier'} + + {:else if currentStepData.id === 'sync'} + {:else if currentStepData.id === 'credits'} {:else if currentStepData.id === 'complete'} diff --git a/apps/mana/apps/web/src/lib/components/onboarding/steps/SyncStep.svelte b/apps/mana/apps/web/src/lib/components/onboarding/steps/SyncStep.svelte new file mode 100644 index 000000000..54f69ec26 --- /dev/null +++ b/apps/mana/apps/web/src/lib/components/onboarding/steps/SyncStep.svelte @@ -0,0 +1,158 @@ + + +
+
+
+ +
+
Cloud Sync
+

+ Synchronisiere deine Daten verschlüsselt über alle Geräte — oder nutze Mana nur lokal. +

+
+ + +
+
+ +
+
Lokal — immer verfügbar
+
+ Alle deine Daten sind lokal auf deinem Gerät gespeichert. Mana funktioniert vollständig + offline — auch ohne Cloud Sync. +
+
+
+
+ + + {#if syncBilling.active} +
+
+ +
+
Cloud Sync ist aktiv
+
+ Deine Daten werden über alle Geräte synchronisiert. +
+
+
+
+ {:else} +
+
+
+ +
+
+
Cloud Sync aktivieren
+

+ Multi-Device-Sync, automatische Backups, Ende-zu-Ende-Verschlüsselung. +

+
+
+ + +
+ {#each ['monthly', 'quarterly', 'yearly'] as const as iv} + + {/each} +
+ + {#if error} +
+ +

{error}

+
+ {/if} + + + + {#if balance !== null && balance.balance < SYNC_PRICES[selectedInterval].credits} +

+ Nicht genügend Credits ({balance.balance} verfügbar). Du kannst Sync jederzeit später in den + Einstellungen aktivieren. +

+ {/if} +
+ {/if} + +
+ +
+ Sync ist optional — du kannst diesen Schritt überspringen und Mana nur lokal nutzen. Alle + Features funktionieren auch ohne Sync. Du kannst jederzeit in den Einstellungen aktivieren. +
+
+
diff --git a/apps/mana/apps/web/src/routes/(app)/+layout.svelte b/apps/mana/apps/web/src/routes/(app)/+layout.svelte index 454e05ac8..5d4bf43f3 100644 --- a/apps/mana/apps/web/src/routes/(app)/+layout.svelte +++ b/apps/mana/apps/web/src/routes/(app)/+layout.svelte @@ -238,6 +238,72 @@ return first ? first.shortLabel.split(' (')[0] : 'KI'; }); + // ── Sync status dropdown ──────────────────────────────── + let syncStatusItems = $derived.by(() => { + const items: import('@mana/shared-ui').PillDropdownItem[] = []; + + if (syncBilling.active) { + items.push({ + id: 'sync-active', + label: 'Cloud Sync aktiv', + icon: 'cloudCheck', + active: true, + disabled: true, + }); + if (syncBilling.nextChargeAt) { + const date = new Date(syncBilling.nextChargeAt).toLocaleDateString('de-DE', { + day: '2-digit', + month: '2-digit', + year: 'numeric', + }); + items.push({ + id: 'sync-next', + label: `Nächste Abbuchung: ${date}`, + disabled: true, + }); + } + } else if (syncBilling.paused) { + items.push({ + id: 'sync-paused', + label: 'Sync pausiert — Credits aufladen', + icon: 'warning', + onClick: () => goto('/credits?tab=packages'), + }); + } else { + items.push({ + id: 'sync-inactive', + label: 'Sync aktivieren', + icon: 'cloudArrowUp', + onClick: () => goto('/settings/sync'), + }); + items.push({ + id: 'sync-info', + label: 'Nur lokal — ab 30 Credits/Monat', + disabled: true, + }); + } + + items.push({ id: 'sync-divider', label: '', divider: true }); + items.push({ + id: 'sync-settings', + label: 'Sync-Einstellungen', + icon: 'gear', + onClick: () => goto('/settings/sync'), + }); + + return items; + }); + + let currentSyncLabel = $derived( + syncBilling.loading + ? '...' + : syncBilling.active + ? 'Sync' + : syncBilling.paused + ? 'Pausiert' + : 'Lokal' + ); + // ── User / Guest awareness ────────────────────────────── let userEmail = $derived( authStore.isAuthenticated ? authStore.user?.email || $_('nav.menu') : '' @@ -750,6 +816,9 @@ showAiTierSelector={true} {aiTierItems} {currentAiTierLabel} + showSyncStatus={authStore.isAuthenticated} + {syncStatusItems} + {currentSyncLabel} {appItems} {userEmail} settingsHref="/settings" diff --git a/packages/shared-ui/src/navigation/PillNavigation.svelte b/packages/shared-ui/src/navigation/PillNavigation.svelte index 05faac2c6..f7eb2e037 100644 --- a/packages/shared-ui/src/navigation/PillNavigation.svelte +++ b/packages/shared-ui/src/navigation/PillNavigation.svelte @@ -247,6 +247,12 @@ aiTierItems?: PillDropdownItem[]; /** Current AI tier label, e.g. "Browser" or "Server" */ currentAiTierLabel?: string; + /** Show sync status dropdown */ + showSyncStatus?: boolean; + /** Sync status dropdown items */ + syncStatusItems?: PillDropdownItem[]; + /** Current sync status label */ + currentSyncLabel?: string; /** Primary color for active state (CSS custom property or hex) */ primaryColor?: string; /** Elements to prepend before nav items (tab groups, dividers, nav items) */ @@ -342,6 +348,9 @@ showAiTierSelector = false, aiTierItems = [], currentAiTierLabel = 'KI', + showSyncStatus = false, + syncStatusItems = [], + currentSyncLabel = 'Sync', themeMode = 'system', onThemeModeChange, appItems = [], @@ -670,6 +679,16 @@ /> {/if} + + {#if showSyncStatus && syncStatusItems.length > 0} + + {/if} + {#if showThemeToggle && onToggleTheme && !showThemeVariants}