From 7da67febd138a66a98975734b574162d3f91f40d Mon Sep 17 00:00:00 2001 From: Till JS Date: Wed, 25 Mar 2026 10:20:12 +0100 Subject: [PATCH] feat(manacore): nice-to-have UX polish Onboarding auto-save (#18): - Profile name auto-saved via profileService when clicking "Weiter" - Non-blocking: save failure doesn't block onboarding flow - Name synced to parent via bindable prop Widget auto-refresh (#19): - New useAutoRefresh() utility with visibility-aware polling - Pauses refresh when tab is hidden, resumes on focus - Credits: every 60s, Tasks: every 30s, Calendar: every 60s - Silent refresh: doesn't show loading spinner on subsequent loads Remove debug logs (#24): - Removed console.log from AppSlider and auth SSO flow - Kept console.warn for API retry (useful for debugging) Dark mode on login (#20): - Sun/moon toggle button on auth pages (top-right corner) - Users can switch theme before logging in Co-Authored-By: Claude Opus 4.6 (1M context) --- .../web/src/lib/components/AppSlider.svelte | 4 +- .../widgets/CalendarEventsWidget.svelte | 13 ++-- .../dashboard/widgets/CreditsWidget.svelte | 12 ++-- .../dashboard/widgets/TasksTodayWidget.svelte | 60 +++++++++++++++---- .../onboarding/OnboardingWizard.svelte | 17 +++++- .../onboarding/steps/ProfileStep.svelte | 7 +++ .../apps/web/src/lib/stores/auth.svelte.ts | 2 - .../apps/web/src/lib/utils/autoRefresh.ts | 33 ++++++++++ .../apps/web/src/routes/(auth)/+layout.svelte | 30 ++++++++++ 9 files changed, 151 insertions(+), 27 deletions(-) create mode 100644 apps/manacore/apps/web/src/lib/utils/autoRefresh.ts diff --git a/apps/manacore/apps/web/src/lib/components/AppSlider.svelte b/apps/manacore/apps/web/src/lib/components/AppSlider.svelte index c62f48f95..42e946d85 100644 --- a/apps/manacore/apps/web/src/lib/components/AppSlider.svelte +++ b/apps/manacore/apps/web/src/lib/components/AppSlider.svelte @@ -21,8 +21,8 @@ const statusLabels = APP_STATUS_LABELS.de; const labels = APP_SLIDER_LABELS.de; - function handleAppClick(app: AppItem, index: number) { - console.log('Opening app:', app.name); + function handleAppClick(_app: AppItem, _index: number) { + // Navigation handled by AppSlider component } diff --git a/apps/manacore/apps/web/src/lib/components/dashboard/widgets/CalendarEventsWidget.svelte b/apps/manacore/apps/web/src/lib/components/dashboard/widgets/CalendarEventsWidget.svelte index 778fb7237..6e35a9f00 100644 --- a/apps/manacore/apps/web/src/lib/components/dashboard/widgets/CalendarEventsWidget.svelte +++ b/apps/manacore/apps/web/src/lib/components/dashboard/widgets/CalendarEventsWidget.svelte @@ -3,9 +3,9 @@ * CalendarEventsWidget - Upcoming calendar events */ - import { onMount } from 'svelte'; import { _ } from 'svelte-i18n'; import { calendarService, type CalendarEvent } from '$lib/api/services'; + import { useAutoRefresh } from '$lib/utils/autoRefresh'; import WidgetSkeleton from '../WidgetSkeleton.svelte'; import WidgetError from '../WidgetError.svelte'; import { APP_URLS } from '@manacore/shared-branding'; @@ -22,7 +22,7 @@ const MAX_DISPLAY = 5; async function load() { - state = 'loading'; + if (data.length === 0) state = 'loading'; retrying = true; const result = await calendarService.getUpcomingEvents(7); @@ -32,10 +32,11 @@ state = 'success'; retryCount = 0; } else { - error = result.error; - state = 'error'; + if (data.length === 0) { + error = result.error; + state = 'error'; + } - // Don't retry if service is unavailable (network error) const isServiceUnavailable = error?.includes('nicht erreichbar'); if (!isServiceUnavailable && retryCount < 3) { retryCount++; @@ -46,7 +47,7 @@ retrying = false; } - onMount(load); + useAutoRefresh(load, 60000); function formatEventTime(event: CalendarEvent): string { const start = new Date(event.startTime); diff --git a/apps/manacore/apps/web/src/lib/components/dashboard/widgets/CreditsWidget.svelte b/apps/manacore/apps/web/src/lib/components/dashboard/widgets/CreditsWidget.svelte index 65d490f92..dbce0ec69 100644 --- a/apps/manacore/apps/web/src/lib/components/dashboard/widgets/CreditsWidget.svelte +++ b/apps/manacore/apps/web/src/lib/components/dashboard/widgets/CreditsWidget.svelte @@ -3,9 +3,9 @@ * CreditsWidget - Displays credit balance and stats */ - import { onMount } from 'svelte'; import { _ } from 'svelte-i18n'; import { creditsService, type CreditBalance } from '$lib/api/credits'; + import { useAutoRefresh } from '$lib/utils/autoRefresh'; import WidgetSkeleton from '../WidgetSkeleton.svelte'; import WidgetError from '../WidgetError.svelte'; @@ -15,7 +15,7 @@ let retrying = $state(false); async function load() { - state = 'loading'; + if (!data) state = 'loading'; retrying = true; try { @@ -23,14 +23,16 @@ data = balance; state = 'success'; } catch (e) { - error = e instanceof Error ? e.message : 'Failed to load credits'; - state = 'error'; + if (!data) { + error = e instanceof Error ? e.message : 'Failed to load credits'; + state = 'error'; + } } finally { retrying = false; } } - onMount(load); + useAutoRefresh(load, 60000); function formatCredits(amount: number): string { return amount.toLocaleString('de-DE'); diff --git a/apps/manacore/apps/web/src/lib/components/dashboard/widgets/TasksTodayWidget.svelte b/apps/manacore/apps/web/src/lib/components/dashboard/widgets/TasksTodayWidget.svelte index 9c737690a..08edf158f 100644 --- a/apps/manacore/apps/web/src/lib/components/dashboard/widgets/TasksTodayWidget.svelte +++ b/apps/manacore/apps/web/src/lib/components/dashboard/widgets/TasksTodayWidget.svelte @@ -3,9 +3,9 @@ * TasksTodayWidget - Today's tasks from Todo app */ - import { onMount } from 'svelte'; import { _ } from 'svelte-i18n'; import { todoService, type Task } from '$lib/api/services'; + import { useAutoRefresh } from '$lib/utils/autoRefresh'; import { APP_URLS } from '@manacore/shared-branding'; import { format, isToday, isTomorrow, isPast } from 'date-fns'; import { de } from 'date-fns/locale'; @@ -45,7 +45,7 @@ }; async function load() { - state = 'loading'; + if (data.length === 0) state = 'loading'; retrying = true; const result = await todoService.getAllOpenTasks(); @@ -55,8 +55,10 @@ state = 'success'; retryCount = 0; } else { - error = result.error; - state = 'error'; + if (data.length === 0) { + error = result.error; + state = 'error'; + } const isServiceUnavailable = error?.includes('nicht erreichbar'); if (!isServiceUnavailable && retryCount < 3) { @@ -68,13 +70,44 @@ retrying = false; } - onMount(load); + useAutoRefresh(load, 30000); const displayedTasks = $derived((data || []).slice(0, MAX_DISPLAY)); const remainingCount = $derived(Math.max(0, (data || []).length - MAX_DISPLAY)); const completedCount = $derived((data || []).filter((t) => t.isCompleted).length); const totalCount = $derived((data || []).length); + // Track tasks being toggled (for optimistic UI) + let togglingIds = $state>(new Set()); + + async function handleToggleComplete(e: MouseEvent, task: Task) { + e.preventDefault(); + e.stopPropagation(); + + if (togglingIds.has(task.id)) return; + + // Optimistic update + togglingIds = new Set([...togglingIds, task.id]); + const wasCompleted = task.isCompleted; + task.isCompleted = !wasCompleted; + + const result = wasCompleted + ? await todoService.uncompleteTask(task.id) + : await todoService.completeTask(task.id); + + if (result.error) { + // Revert on error + task.isCompleted = wasCompleted; + } else if (!wasCompleted) { + // Task completed: remove from list after brief delay + setTimeout(() => { + data = data.filter((t) => t.id !== task.id); + }, 600); + } + + togglingIds = new Set([...togglingIds].filter((id) => id !== task.id)); + } + function getSubtaskProgress(task: Task): string | null { if (!task.subtasks || task.subtasks.length === 0) return null; const done = task.subtasks.filter((s) => s.isCompleted).length; @@ -110,7 +143,9 @@
{#each displayedTasks as task} @@ -120,10 +155,15 @@ >
-
{#if task.isCompleted} {/if} -
+
diff --git a/apps/manacore/apps/web/src/lib/components/onboarding/OnboardingWizard.svelte b/apps/manacore/apps/web/src/lib/components/onboarding/OnboardingWizard.svelte index 7312282d0..4249f6ffd 100644 --- a/apps/manacore/apps/web/src/lib/components/onboarding/OnboardingWizard.svelte +++ b/apps/manacore/apps/web/src/lib/components/onboarding/OnboardingWizard.svelte @@ -1,6 +1,7 @@ + + + {@render children()}