fix(settings): unify global settings across web apps

- Add desktopPosition prop to Clock layout for nav sync
- Remove duplicate local theme/locale controls from Clock, Calendar, Todo, Zitare
- Move GlobalSettingsSection to proper position in settings pages
- Remove weekStartsOn from Calendar (now handled by GlobalSettingsSection)

All web apps now consistently use GlobalSettingsSection for:
- Navigation (desktopPosition, sidebarCollapsed)
- Theme (mode, colorScheme)
- Language (locale)
- General (startPage, weekStartsOn, soundsEnabled)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Till-JS 2025-12-07 16:28:00 +01:00
parent 9ece591bb5
commit 7b8335a3fb
5 changed files with 26 additions and 462 deletions

View file

@ -1,17 +1,12 @@
<script lang="ts">
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
import { _ } from 'svelte-i18n';
import { authStore } from '$lib/stores/auth.svelte';
import { theme } from '$lib/stores/theme';
import { userSettings } from '$lib/stores/user-settings.svelte';
import { settingsStore } from '$lib/stores/settings.svelte';
import type { WeekStartDay, TimeFormat, AllDayDisplayMode } from '$lib/stores/settings.svelte';
import type { TimeFormat, AllDayDisplayMode } from '$lib/stores/settings.svelte';
import { calendarsStore } from '$lib/stores/calendars.svelte';
import { toast } from '$lib/stores/toast';
import { setLocale, supportedLocales } from '$lib/i18n';
import type { SupportedLocale } from '$lib/i18n';
import { THEME_DEFINITIONS } from '@manacore/shared-theme';
import { GlobalSettingsSection } from '@manacore/shared-ui';
import type { CalendarViewType, Calendar } from '@calendar/shared';
@ -21,9 +16,6 @@
let newCalendarName = $state('');
let newCalendarColor = $state('#3b82f6');
// Get current locale from svelte-i18n
import { locale } from 'svelte-i18n';
onMount(async () => {
if (!authStore.isAuthenticated) {
goto('/login');
@ -79,22 +71,10 @@
editingCalendar = null;
}
function handleThemeChange(mode: 'light' | 'dark' | 'system') {
theme.setMode(mode);
}
function handleLocaleChange(newLocale: SupportedLocale) {
setLocale(newLocale);
}
function handleViewChange(view: CalendarViewType) {
settingsStore.set('defaultView', view);
}
function handleWeekStartChange(day: WeekStartDay) {
settingsStore.set('weekStartsOn', day);
}
function handleTimeFormatChange(format: TimeFormat) {
settingsStore.set('timeFormat', format);
}
@ -107,15 +87,6 @@
settingsStore.set('defaultReminder', minutes);
}
// Language labels
const localeLabels: Record<SupportedLocale, string> = {
de: 'Deutsch',
en: 'English',
fr: 'Français',
es: 'Español',
it: 'Italiano',
};
// View labels
const viewLabels: Record<CalendarViewType, string> = {
day: 'Tag',
@ -255,95 +226,14 @@
</div>
</section>
<!-- Sprache -->
<section class="settings-section card">
<h2>Sprache</h2>
<div class="setting-item">
<div class="setting-info">
<span class="setting-label">Anzeigesprache</span>
<span class="setting-description">Sprache für die Benutzeroberfläche</span>
</div>
<div class="locale-options">
{#each supportedLocales as loc}
<button
class="locale-option"
class:active={$locale === loc}
onclick={() => handleLocaleChange(loc)}
>
{localeLabels[loc]}
</button>
{/each}
</div>
</div>
</section>
<!-- Erscheinungsbild -->
<section class="settings-section card">
<h2>Erscheinungsbild</h2>
<div class="setting-item">
<div class="setting-info">
<span class="setting-label">Design-Modus</span>
<span class="setting-description">Wählen Sie zwischen hell, dunkel oder automatisch</span>
</div>
<div class="theme-options">
<button
class="theme-option"
class:active={theme.mode === 'light'}
onclick={() => handleThemeChange('light')}
>
<svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="5"></circle>
<path
d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"
></path>
</svg>
Hell
</button>
<button
class="theme-option"
class:active={theme.mode === 'dark'}
onclick={() => handleThemeChange('dark')}
>
<svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
</svg>
Dunkel
</button>
<button
class="theme-option"
class:active={theme.mode === 'system'}
onclick={() => handleThemeChange('system')}
>
<svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect>
<line x1="8" y1="21" x2="16" y2="21"></line>
<line x1="12" y1="17" x2="12" y2="21"></line>
</svg>
System
</button>
</div>
</div>
<div class="setting-item">
<div class="setting-info">
<span class="setting-label">Farbschema</span>
<span class="setting-description">Wählen Sie ein Farbschema für die App</span>
</div>
<div class="variant-grid">
{#each theme.variants as variant}
<button
class="variant-option"
class:active={theme.variant === variant}
onclick={() => theme.setVariant(variant)}
>
<span class="variant-icon">{THEME_DEFINITIONS[variant].icon}</span>
<span class="variant-label">{THEME_DEFINITIONS[variant].label}</span>
</button>
{/each}
</div>
</div>
<!-- Global App Settings (synced across all apps) -->
<section class="settings-section">
<GlobalSettingsSection
{userSettings}
appId="calendar"
title="App-Einstellungen"
description="Diese Einstellungen werden mit allen Mana Apps synchronisiert"
/>
</section>
<!-- Kalender-Ansicht -->
@ -366,29 +256,6 @@
</select>
</div>
<div class="setting-item">
<div class="setting-info">
<span class="setting-label">Wochenstart</span>
<span class="setting-description">Erster Tag der Woche</span>
</div>
<div class="button-group">
<button
class="group-button"
class:active={settingsStore.weekStartsOn === 1}
onclick={() => handleWeekStartChange(1)}
>
Montag
</button>
<button
class="group-button"
class:active={settingsStore.weekStartsOn === 0}
onclick={() => handleWeekStartChange(0)}
>
Sonntag
</button>
</div>
</div>
<div class="setting-item">
<div class="setting-info">
<span class="setting-label">Zeitformat</span>
@ -566,11 +433,6 @@
</div>
</section>
<!-- Global App Settings -->
<section class="settings-section">
<GlobalSettingsSection {userSettings} />
</section>
<!-- Konto -->
<section class="settings-section card">
<h2>Konto</h2>

View file

@ -168,6 +168,7 @@
currentPath={$page.url.pathname}
appName="Clock"
homeRoute="/"
desktopPosition={userSettings.nav.desktopPosition}
onToggleTheme={handleToggleTheme}
{isDark}
{isSidebarMode}

View file

@ -1,9 +1,6 @@
<script lang="ts">
import { _ } from 'svelte-i18n';
import { theme } from '$lib/stores/theme.svelte';
import { userSettings } from '$lib/stores/user-settings.svelte';
import { setLocale, supportedLocales } from '$lib/i18n';
import { THEME_DEFINITIONS } from '@manacore/shared-theme';
import { GlobalSettingsSection } from '@manacore/shared-ui';
// Settings state
@ -24,14 +21,6 @@
}
}
const languageNames: Record<string, string> = {
de: 'Deutsch',
en: 'English',
fr: 'Français',
es: 'Español',
it: 'Italiano',
};
// Translation function for GlobalSettingsSection
function translate(key: string): string {
return $_?.(key) ?? key;
@ -41,80 +30,21 @@
<div class="mx-auto max-w-2xl space-y-6">
<h1 class="text-2xl font-bold text-foreground">{$_('settings.title')}</h1>
<!-- Appearance Section -->
<!-- Global Settings Section (synced across all apps) -->
<GlobalSettingsSection
{userSettings}
appId="clock"
title="App-Einstellungen"
description="Diese Einstellungen werden mit allen Mana Apps synchronisiert"
t={translate}
/>
<!-- Clock-specific Settings -->
<div class="card">
<h2 class="mb-4 text-lg font-semibold">{$_('settings.appearance')}</h2>
<h2 class="mb-4 text-lg font-semibold">{$_('settings.clockFormat')}</h2>
<!-- Theme Mode -->
<div class="mb-6">
<label class="mb-2 block text-sm font-medium">{$_('settings.darkMode')}</label>
<div class="flex gap-2">
<button
class="btn btn-sm"
class:btn-primary={theme.mode === 'light'}
class:btn-secondary={theme.mode !== 'light'}
onclick={() => theme.setMode('light')}
>
☀️ Light
</button>
<button
class="btn btn-sm"
class:btn-primary={theme.mode === 'dark'}
class:btn-secondary={theme.mode !== 'dark'}
onclick={() => theme.setMode('dark')}
>
🌙 Dark
</button>
<button
class="btn btn-sm"
class:btn-primary={theme.mode === 'system'}
class:btn-secondary={theme.mode !== 'system'}
onclick={() => theme.setMode('system')}
>
💻 System
</button>
</div>
</div>
<!-- Theme Variant -->
<div>
<label class="mb-2 block text-sm font-medium">{$_('settings.theme')}</label>
<div class="grid grid-cols-3 gap-2 sm:grid-cols-5">
{#each theme.variants as variant}
<button
class="flex flex-col items-center gap-1 rounded-lg border-2 p-3 transition-colors"
class:border-primary={theme.variant === variant}
class:border-transparent={theme.variant !== variant}
onclick={() => theme.setVariant(variant)}
>
<span class="text-xl">{THEME_DEFINITIONS[variant].icon}</span>
<span class="text-xs">{THEME_DEFINITIONS[variant].label}</span>
</button>
{/each}
</div>
</div>
</div>
<!-- General Section -->
<div class="card">
<h2 class="mb-4 text-lg font-semibold">{$_('settings.general')}</h2>
<!-- Language -->
<div class="mb-6">
<label class="mb-2 block text-sm font-medium">{$_('settings.language')}</label>
<select
class="input"
onchange={(e) => setLocale((e.target as HTMLSelectElement).value as any)}
>
{#each supportedLocales as locale}
<option value={locale}>{languageNames[locale]}</option>
{/each}
</select>
</div>
<!-- Clock Format -->
<div>
<label class="mb-2 block text-sm font-medium">{$_('settings.clockFormat')}</label>
<label class="mb-2 block text-sm font-medium">Zeitformat</label>
<div class="flex gap-2">
<button
class="btn btn-sm"
@ -167,17 +97,4 @@
Töne können für einzelne Wecker und Timer in deren Einstellungen angepasst werden.
</p>
</div>
<!-- Global Settings Section -->
<GlobalSettingsSection
{userSettings}
appId="clock"
showNavigation={false}
showTheme={false}
showLanguage={false}
showGeneral={true}
title="Globale Einstellungen"
description="Diese Einstellungen gelten für alle Mana Apps"
t={translate}
/>
</div>

View file

@ -2,16 +2,12 @@
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
import { authStore } from '$lib/stores/auth.svelte';
import { theme } from '$lib/stores/theme';
import { userSettings } from '$lib/stores/user-settings.svelte';
import { THEME_DEFINITIONS } from '@manacore/shared-theme';
import { ThemeColorPreview } from '@manacore/shared-theme-ui';
import {
SettingsPage,
SettingsSection,
SettingsCard,
SettingsRow,
SettingsToggle,
SettingsDangerZone,
SettingsDangerButton,
GlobalSettingsSection,
@ -27,10 +23,6 @@
await userSettings.load();
});
function toggleDarkMode() {
theme.toggleMode();
}
async function handleLogout() {
await authStore.signOut();
goto('/login');
@ -89,77 +81,7 @@
</SettingsCard>
</SettingsSection>
<!-- Appearance Section -->
<SettingsSection title="Aussehen">
{#snippet icon()}
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01"
/>
</svg>
{/snippet}
<SettingsCard>
<SettingsToggle
label="Dark Mode"
description="Dunkles Farbschema verwenden"
isOn={theme.isDark}
onToggle={toggleDarkMode}
>
{#snippet icon()}
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"
/>
</svg>
{/snippet}
</SettingsToggle>
<SettingsRow
label="Aktuelles Theme"
description={THEME_DEFINITIONS[theme.variant].label}
onclick={() => goto('/themes')}
>
{#snippet icon()}
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01"
/>
</svg>
{/snippet}
<span
class="px-3 py-1.5 text-sm font-medium bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))] rounded-lg"
>
Themes wählen
</span>
</SettingsRow>
<div class="px-5 py-4 border-t border-[hsl(var(--border))]">
<p class="font-medium text-[hsl(var(--foreground))] mb-2">Farbvorschau</p>
<p class="text-sm text-[hsl(var(--muted-foreground))] mb-4">
So sieht die App mit dem aktuellen Theme aus
</p>
<div class="flex justify-center">
<ThemeColorPreview
variant={theme.variant}
mode={theme.isDark ? 'dark' : 'light'}
size="lg"
/>
</div>
</div>
</SettingsCard>
</SettingsSection>
<!-- Global Settings Section -->
<!-- Global Settings Section (synced across all apps) -->
<GlobalSettingsSection {userSettings} appId="todo" />
<!-- About Section -->

View file

@ -1,53 +1,30 @@
<script lang="ts">
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
import { theme } from '$lib/stores/theme';
import { userSettings } from '$lib/stores/user-settings.svelte';
import { THEME_DEFINITIONS } from '@manacore/shared-theme';
import { ThemeColorPreview } from '@manacore/shared-theme-ui';
import {
SettingsPage,
SettingsSection,
SettingsCard,
SettingsRow,
SettingsToggle,
SettingsDangerZone,
SettingsDangerButton,
GlobalSettingsSection,
} from '@manacore/shared-ui';
// Settings state
let language = $state<'de' | 'en'>('de');
let userName = $state('');
// Load settings from localStorage on mount
onMount(async () => {
const savedLanguage = localStorage.getItem('language');
const savedUserName = localStorage.getItem('userName');
if (savedLanguage) language = savedLanguage as 'de' | 'en';
if (savedUserName) userName = savedUserName;
// Load user settings from server
await userSettings.load();
});
// Save settings to localStorage
function saveSetting(key: string, value: string | boolean) {
localStorage.setItem(key, String(value));
}
function toggleDarkMode(value: boolean) {
theme.toggleMode();
}
function setLanguageSetting(lang: 'de' | 'en') {
language = lang;
saveSetting('language', lang);
}
function saveUserName() {
saveSetting('userName', userName);
localStorage.setItem('userName', userName);
}
function resetAllData() {
@ -97,120 +74,8 @@
</SettingsCard>
</SettingsSection>
<!-- Appearance Section -->
<SettingsSection title="Aussehen">
{#snippet icon()}
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01"
/>
</svg>
{/snippet}
<SettingsCard>
<SettingsToggle
label="Dark Mode"
description="Dunkles Farbschema verwenden"
isOn={theme.isDark}
onToggle={toggleDarkMode}
>
{#snippet icon()}
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"
/>
</svg>
{/snippet}
</SettingsToggle>
<SettingsRow
label="Aktuelles Theme"
description={THEME_DEFINITIONS[theme.variant].label}
onclick={() => goto('/themes')}
>
{#snippet icon()}
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01"
/>
</svg>
{/snippet}
<span
class="px-3 py-1.5 text-sm font-medium bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))] rounded-lg"
>
Themes wählen
</span>
</SettingsRow>
<div class="px-5 py-4 border-t border-[hsl(var(--border))]">
<p class="font-medium text-[hsl(var(--foreground))] mb-2">Farbvorschau</p>
<p class="text-sm text-[hsl(var(--muted-foreground))] mb-4">
So sieht die App mit dem aktuellen Theme aus
</p>
<div class="flex justify-center">
<ThemeColorPreview
variant={theme.variant}
mode={theme.isDark ? 'dark' : 'light'}
size="lg"
/>
</div>
</div>
</SettingsCard>
</SettingsSection>
<!-- Language Section -->
<SettingsSection title="Sprache">
{#snippet icon()}
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3 5h12M9 3v2m1.048 9.5A18.022 18.022 0 016.412 9m6.088 9h7M11 21l5-10 5 10M12.751 5C11.783 10.77 8.07 15.61 3 18.129"
/>
</svg>
{/snippet}
<SettingsCard>
<div class="px-5 py-4">
<div class="flex items-center justify-between">
<div>
<p class="font-medium text-[hsl(var(--foreground))]">Sprache</p>
<p class="text-sm text-[hsl(var(--muted-foreground))]">Sprache der App und Zitate</p>
</div>
<div class="flex rounded-full overflow-hidden border border-[hsl(var(--border))]">
<button
class="px-4 py-2 text-sm font-medium transition-colors
{language === 'de'
? 'bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))]'
: 'bg-transparent text-[hsl(var(--foreground))]'}"
onclick={() => setLanguageSetting('de')}
>
DE
</button>
<button
class="px-4 py-2 text-sm font-medium transition-colors
{language === 'en'
? 'bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))]'
: 'bg-transparent text-[hsl(var(--foreground))]'}"
onclick={() => setLanguageSetting('en')}
>
EN
</button>
</div>
</div>
</div>
</SettingsCard>
</SettingsSection>
<!-- Global Settings Section (synced across all apps) -->
<GlobalSettingsSection {userSettings} appId="zitare" />
<!-- About Section -->
<SettingsSection title="Über">
@ -232,9 +97,6 @@
</SettingsCard>
</SettingsSection>
<!-- Global Settings Section -->
<GlobalSettingsSection {userSettings} />
<!-- Data Section -->
<SettingsDangerZone title="Daten">
<SettingsDangerButton