mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 19:41:09 +02:00
feat(shared-ui): add reusable settings components with glass styling
- Add SettingsPage, SettingsSection, SettingsCard components - Add SettingsRow, SettingsToggle for interactive elements - Add SettingsDangerZone, SettingsDangerButton for destructive actions - Apply glass morphism styling matching PillNavigation - Migrate settings pages in manacore, presi, zitare apps - Migrate archived apps: maerchenzauber, memoro, nutriphi, uload 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
3cfa6a765a
commit
7deb5b9a1e
16 changed files with 2391 additions and 1222 deletions
|
|
@ -4,6 +4,15 @@
|
|||
import { goto } from '$app/navigation';
|
||||
import { dataService } from '$lib/api';
|
||||
import { toastStore } from '$lib/stores/toast.svelte';
|
||||
import {
|
||||
SettingsPage,
|
||||
SettingsSection,
|
||||
SettingsCard,
|
||||
SettingsRow,
|
||||
SettingsToggle,
|
||||
SettingsDangerZone,
|
||||
SettingsDangerButton,
|
||||
} from '@manacore/shared-ui';
|
||||
|
||||
// Stats
|
||||
let storyCount = $state(0);
|
||||
|
|
@ -76,22 +85,19 @@
|
|||
toastStore.success('Bildmodell gespeichert');
|
||||
}
|
||||
}
|
||||
|
||||
async function handleLogout() {
|
||||
await authStore.signOut();
|
||||
goto('/login');
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Einstellungen | Märchenzauber</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="mx-auto max-w-2xl space-y-6">
|
||||
<!-- Header -->
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-800 dark:text-gray-200">Einstellungen</h1>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Verwalte dein Konto und deine Einstellungen
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Stats Section -->
|
||||
<SettingsPage title="Einstellungen" subtitle="Verwalte dein Konto und deine Einstellungen">
|
||||
<!-- Stats Section (Custom gradient) -->
|
||||
<section
|
||||
class="rounded-2xl bg-gradient-to-r from-pink-500 to-purple-600 p-6 text-white shadow-lg"
|
||||
>
|
||||
|
|
@ -136,65 +142,65 @@
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Preferences Section -->
|
||||
<section class="rounded-2xl bg-white p-6 shadow-sm dark:bg-gray-800">
|
||||
<h2 class="mb-4 text-lg font-semibold text-gray-800 dark:text-gray-200">Darstellung</h2>
|
||||
<!-- Appearance Section -->
|
||||
<SettingsSection title="Darstellung">
|
||||
{#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}
|
||||
|
||||
<div class="space-y-4">
|
||||
<!-- Dark Mode Toggle -->
|
||||
<div class="flex items-center justify-between rounded-xl bg-gray-50 p-4 dark:bg-gray-700/50">
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
class="flex h-10 w-10 items-center justify-center rounded-xl bg-gray-200 text-gray-600 dark:bg-gray-600 dark:text-gray-300"
|
||||
>
|
||||
{#if isDarkMode}
|
||||
<svg class="h-5 w-5" 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>
|
||||
{:else}
|
||||
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-medium text-gray-800 dark:text-gray-200">Dunkelmodus</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{isDarkMode ? 'Aktiviert' : 'Deaktiviert'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onclick={toggleTheme}
|
||||
class="relative h-7 w-12 rounded-full transition-colors {isDarkMode
|
||||
? 'bg-pink-500'
|
||||
: 'bg-gray-300'}"
|
||||
>
|
||||
<span
|
||||
class="absolute top-0.5 h-6 w-6 rounded-full bg-white shadow transition-all {isDarkMode
|
||||
? 'left-5.5'
|
||||
: 'left-0.5'}"
|
||||
style="left: {isDarkMode ? '1.375rem' : '0.125rem'}"
|
||||
></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<SettingsCard>
|
||||
<SettingsToggle
|
||||
label="Dunkelmodus"
|
||||
description={isDarkMode ? 'Aktiviert' : 'Deaktiviert'}
|
||||
isOn={isDarkMode}
|
||||
onToggle={toggleTheme}
|
||||
>
|
||||
{#snippet icon()}
|
||||
{#if isDarkMode}
|
||||
<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>
|
||||
{:else}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</SettingsToggle>
|
||||
</SettingsCard>
|
||||
</SettingsSection>
|
||||
|
||||
<!-- Image Model Section -->
|
||||
<section class="rounded-2xl bg-white p-6 shadow-sm dark:bg-gray-800">
|
||||
<h2 class="mb-4 text-lg font-semibold text-gray-800 dark:text-gray-200">Bildgenerierung</h2>
|
||||
<p class="mb-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
<!-- Image Model Section (Custom) -->
|
||||
<SettingsSection title="Bildgenerierung">
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
|
||||
<p class="mb-4 text-sm text-[hsl(var(--muted-foreground))]">
|
||||
Wähle das KI-Modell für die Illustration deiner Geschichten
|
||||
</p>
|
||||
|
||||
|
|
@ -205,13 +211,13 @@
|
|||
class="flex w-full items-center gap-3 rounded-xl p-4 text-left transition-all {selectedImageModel ===
|
||||
model.id
|
||||
? 'bg-pink-50 ring-2 ring-pink-500 dark:bg-pink-900/20'
|
||||
: 'bg-gray-50 hover:bg-gray-100 dark:bg-gray-700/50 dark:hover:bg-gray-700'}"
|
||||
: 'bg-[hsl(var(--muted))] hover:bg-[hsl(var(--muted)/0.8)]'}"
|
||||
>
|
||||
<div
|
||||
class="flex h-10 w-10 items-center justify-center rounded-xl {selectedImageModel ===
|
||||
model.id
|
||||
? 'bg-pink-500 text-white'
|
||||
: 'bg-gray-200 text-gray-600 dark:bg-gray-600 dark:text-gray-300'}"
|
||||
: 'bg-[hsl(var(--background))] text-[hsl(var(--muted-foreground))]'}"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
|
|
@ -223,21 +229,16 @@
|
|||
</svg>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="font-medium text-gray-800 dark:text-gray-200">{model.name}</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">{model.description}</p>
|
||||
<p class="font-medium text-[hsl(var(--foreground))]">{model.name}</p>
|
||||
<p class="text-sm text-[hsl(var(--muted-foreground))]">{model.description}</p>
|
||||
</div>
|
||||
<span
|
||||
class="rounded-full bg-gray-200 px-2 py-0.5 text-xs font-medium text-gray-600 dark:bg-gray-600 dark:text-gray-300"
|
||||
class="rounded-full bg-[hsl(var(--background))] px-2 py-0.5 text-xs font-medium text-[hsl(var(--muted-foreground))]"
|
||||
>
|
||||
{model.speed}
|
||||
</span>
|
||||
{#if selectedImageModel === model.id}
|
||||
<svg
|
||||
class="h-5 w-5 text-pink-500"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<svg class="h-5 w-5 text-pink-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
|
|
@ -249,22 +250,25 @@
|
|||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
</SettingsSection>
|
||||
|
||||
<!-- Story Settings Section -->
|
||||
<section class="rounded-2xl bg-white p-6 shadow-sm dark:bg-gray-800">
|
||||
<h2 class="mb-4 text-lg font-semibold text-gray-800 dark:text-gray-200">Geschichten</h2>
|
||||
<SettingsSection title="Geschichten">
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
|
||||
<div class="space-y-2">
|
||||
<!-- Creators -->
|
||||
<a
|
||||
href="/creators"
|
||||
class="flex items-center gap-3 rounded-xl p-3 transition-all hover:bg-gray-50 dark:hover:bg-gray-700/50"
|
||||
>
|
||||
<div
|
||||
class="flex h-10 w-10 items-center justify-center rounded-xl bg-purple-100 text-purple-500 dark:bg-purple-900/30 dark:text-purple-400"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<SettingsCard>
|
||||
<SettingsRow label="Kreative wählen" description="Autoren & Illustratoren Stil" href="/creators">
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
|
|
@ -272,25 +276,15 @@
|
|||
d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="font-medium text-gray-800 dark:text-gray-200">Kreative wählen</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Autoren & Illustratoren Stil</p>
|
||||
</div>
|
||||
<svg class="h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<!-- Templates -->
|
||||
<a
|
||||
{/snippet}
|
||||
</SettingsRow>
|
||||
<SettingsRow
|
||||
label="Story-Vorlagen"
|
||||
description="Inspiration für neue Geschichten"
|
||||
href="/templates"
|
||||
class="flex items-center gap-3 rounded-xl p-3 transition-all hover:bg-gray-50 dark:hover:bg-gray-700/50"
|
||||
>
|
||||
<div
|
||||
class="flex h-10 w-10 items-center justify-center rounded-xl bg-indigo-100 text-indigo-500 dark:bg-indigo-900/30 dark:text-indigo-400"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
|
|
@ -298,25 +292,16 @@
|
|||
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="font-medium text-gray-800 dark:text-gray-200">Story-Vorlagen</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Inspiration für neue Geschichten</p>
|
||||
</div>
|
||||
<svg class="h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<!-- Collections -->
|
||||
<a
|
||||
{/snippet}
|
||||
</SettingsRow>
|
||||
<SettingsRow
|
||||
label="Sammlungen"
|
||||
description="Geschichten organisieren"
|
||||
href="/collections"
|
||||
class="flex items-center gap-3 rounded-xl p-3 transition-all hover:bg-gray-50 dark:hover:bg-gray-700/50"
|
||||
border={false}
|
||||
>
|
||||
<div
|
||||
class="flex h-10 w-10 items-center justify-center rounded-xl bg-green-100 text-green-500 dark:bg-green-900/30 dark:text-green-400"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
|
|
@ -324,32 +309,33 @@
|
|||
d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="font-medium text-gray-800 dark:text-gray-200">Sammlungen</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Geschichten organisieren</p>
|
||||
</div>
|
||||
<svg class="h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
{/snippet}
|
||||
</SettingsRow>
|
||||
</SettingsCard>
|
||||
</SettingsSection>
|
||||
|
||||
<!-- Characters Section -->
|
||||
<section class="rounded-2xl bg-white p-6 shadow-sm dark:bg-gray-800">
|
||||
<h2 class="mb-4 text-lg font-semibold text-gray-800 dark:text-gray-200">Charaktere</h2>
|
||||
<SettingsSection title="Charaktere">
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
|
||||
<div class="space-y-2">
|
||||
<!-- Import Character -->
|
||||
<a
|
||||
<SettingsCard>
|
||||
<SettingsRow
|
||||
label="Charakter importieren"
|
||||
description="Mit Teilen-Code importieren"
|
||||
href="/characters/share"
|
||||
class="flex items-center gap-3 rounded-xl p-3 transition-all hover:bg-gray-50 dark:hover:bg-gray-700/50"
|
||||
border={false}
|
||||
>
|
||||
<div
|
||||
class="flex h-10 w-10 items-center justify-center rounded-xl bg-pink-100 text-pink-500 dark:bg-pink-900/30 dark:text-pink-400"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
|
|
@ -357,46 +343,64 @@
|
|||
d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="font-medium text-gray-800 dark:text-gray-200">Charakter importieren</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Mit Teilen-Code importieren</p>
|
||||
</div>
|
||||
<svg class="h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
{/snippet}
|
||||
</SettingsRow>
|
||||
</SettingsCard>
|
||||
</SettingsSection>
|
||||
|
||||
<!-- Account Section -->
|
||||
<section class="rounded-2xl bg-white p-6 shadow-sm dark:bg-gray-800">
|
||||
<h2 class="mb-4 text-lg font-semibold text-gray-800 dark:text-gray-200">Konto</h2>
|
||||
<SettingsSection title="Konto">
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
|
||||
<div class="space-y-4">
|
||||
<!-- Email -->
|
||||
<div class="flex items-center justify-between rounded-xl bg-gray-50 p-4 dark:bg-gray-700/50">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">E-Mail</p>
|
||||
<p class="text-gray-800 dark:text-gray-200">{authStore.user?.email || '-'}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- User ID -->
|
||||
<div class="flex items-center justify-between rounded-xl bg-gray-50 p-4 dark:bg-gray-700/50">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Benutzer-ID</p>
|
||||
<p class="font-mono text-xs text-gray-600 dark:text-gray-400">
|
||||
{authStore.user?.id || '-'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<SettingsCard>
|
||||
<SettingsRow label="E-Mail" description={authStore.user?.email || '-'}>
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
</SettingsRow>
|
||||
<SettingsRow label="Benutzer-ID" border={false}>
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M10 6H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V8a2 2 0 00-2-2h-5m-4 0V5a2 2 0 114 0v1m-4 0a2 2 0 104 0m-5 8a2 2 0 100-4 2 2 0 000 4zm0 0c1.306 0 2.417.835 2.83 2M9 14a3.001 3.001 0 00-2.83 2M15 11h3m-3 4h2"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
<span class="font-mono text-xs text-[hsl(var(--muted-foreground))]">
|
||||
{authStore.user?.id || '-'}
|
||||
</span>
|
||||
</SettingsRow>
|
||||
</SettingsCard>
|
||||
</SettingsSection>
|
||||
|
||||
<!-- Subscription Section -->
|
||||
<section class="rounded-2xl bg-white p-6 shadow-sm dark:bg-gray-800">
|
||||
<h2 class="mb-4 text-lg font-semibold text-gray-800 dark:text-gray-200">Abonnement</h2>
|
||||
<SettingsSection title="Abonnement">
|
||||
{#snippet icon()}
|
||||
<svg fill="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
|
||||
<a
|
||||
href="/subscription"
|
||||
|
|
@ -411,30 +415,38 @@
|
|||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-medium text-gray-800 dark:text-gray-200">Mana verwalten</p>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">Abonnement und Guthaben</p>
|
||||
<p class="font-medium text-[hsl(var(--foreground))]">Mana verwalten</p>
|
||||
<p class="text-sm text-[hsl(var(--muted-foreground))]">Abonnement und Guthaben</p>
|
||||
</div>
|
||||
</div>
|
||||
<svg class="h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg
|
||||
class="h-5 w-5 text-[hsl(var(--muted-foreground))]"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</a>
|
||||
</section>
|
||||
</SettingsSection>
|
||||
|
||||
<!-- Actions Section -->
|
||||
<section class="rounded-2xl bg-white p-6 shadow-sm dark:bg-gray-800">
|
||||
<h2 class="mb-4 text-lg font-semibold text-gray-800 dark:text-gray-200">Mehr</h2>
|
||||
<!-- More Section -->
|
||||
<SettingsSection title="Mehr">
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M5 12h.01M12 12h.01M19 12h.01M6 12a1 1 0 11-2 0 1 1 0 012 0zm7 0a1 1 0 11-2 0 1 1 0 012 0zm7 0a1 1 0 11-2 0 1 1 0 012 0z"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
|
||||
<div class="space-y-2">
|
||||
<!-- Feedback -->
|
||||
<a
|
||||
href="/feedback"
|
||||
class="flex items-center gap-3 rounded-xl p-3 transition-all hover:bg-gray-50 dark:hover:bg-gray-700/50"
|
||||
>
|
||||
<div
|
||||
class="flex h-10 w-10 items-center justify-center rounded-xl bg-rose-100 text-rose-500 dark:bg-rose-900/30 dark:text-rose-400"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<SettingsCard>
|
||||
<SettingsRow label="Feedback & Ideen" description="Stimme für Features ab" href="/feedback">
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
|
|
@ -442,25 +454,15 @@
|
|||
d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="font-medium text-gray-800 dark:text-gray-200">Feedback & Ideen</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Stimme für Features ab</p>
|
||||
</div>
|
||||
<svg class="h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<!-- Archive -->
|
||||
<a
|
||||
{/snippet}
|
||||
</SettingsRow>
|
||||
<SettingsRow
|
||||
label="Archiv"
|
||||
description="Archivierte Geschichten und Charaktere"
|
||||
href="/archive"
|
||||
class="flex items-center gap-3 rounded-xl p-3 transition-all hover:bg-gray-50 dark:hover:bg-gray-700/50"
|
||||
>
|
||||
<div
|
||||
class="flex h-10 w-10 items-center justify-center rounded-xl bg-gray-100 text-gray-500 dark:bg-gray-700 dark:text-gray-400"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
|
|
@ -468,27 +470,11 @@
|
|||
d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="font-medium text-gray-800 dark:text-gray-200">Archiv</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Archivierte Geschichten und Charaktere
|
||||
</p>
|
||||
</div>
|
||||
<svg class="h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<!-- Help -->
|
||||
<a
|
||||
href="/help"
|
||||
class="flex items-center gap-3 rounded-xl p-3 transition-all hover:bg-gray-50 dark:hover:bg-gray-700/50"
|
||||
>
|
||||
<div
|
||||
class="flex h-10 w-10 items-center justify-center rounded-xl bg-blue-100 text-blue-500 dark:bg-blue-900/30 dark:text-blue-400"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
{/snippet}
|
||||
</SettingsRow>
|
||||
<SettingsRow label="Hilfe" description="FAQ und Support" href="/help" border={false}>
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
|
|
@ -496,41 +482,30 @@
|
|||
d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="font-medium text-gray-800 dark:text-gray-200">Hilfe</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">FAQ und Support</p>
|
||||
</div>
|
||||
<svg class="h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</a>
|
||||
{/snippet}
|
||||
</SettingsRow>
|
||||
</SettingsCard>
|
||||
</SettingsSection>
|
||||
|
||||
<!-- Logout -->
|
||||
<button
|
||||
onclick={async () => {
|
||||
await authStore.signOut();
|
||||
goto('/login');
|
||||
}}
|
||||
class="flex w-full items-center gap-3 rounded-xl p-3 text-left transition-all hover:bg-red-50 dark:hover:bg-red-900/20"
|
||||
>
|
||||
<div
|
||||
class="flex h-10 w-10 items-center justify-center rounded-xl bg-red-100 text-red-500 dark:bg-red-900/30 dark:text-red-400"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="font-medium text-red-600 dark:text-red-400">Abmelden</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Von deinem Konto abmelden</p>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<!-- Logout -->
|
||||
<SettingsDangerZone title="Abmelden">
|
||||
<SettingsDangerButton
|
||||
label="Abmelden"
|
||||
description="Von deinem Konto abmelden"
|
||||
buttonText="Abmelden"
|
||||
onclick={handleLogout}
|
||||
border={false}
|
||||
>
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
</SettingsDangerButton>
|
||||
</SettingsDangerZone>
|
||||
</SettingsPage>
|
||||
|
|
|
|||
|
|
@ -4,9 +4,15 @@
|
|||
import { theme } from '$lib/stores/theme';
|
||||
import { settings } from '$lib/stores/settings';
|
||||
import type { ThemeMode } from '$lib/stores/theme';
|
||||
import SettingsToggle from '$lib/components/SettingsToggle.svelte';
|
||||
import SectionHeader from '$lib/components/SectionHeader.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import {
|
||||
SettingsPage,
|
||||
SettingsSection,
|
||||
SettingsCard,
|
||||
SettingsRow,
|
||||
SettingsToggle,
|
||||
SettingsDangerZone,
|
||||
SettingsDangerButton,
|
||||
} from '@manacore/shared-ui';
|
||||
|
||||
// theme is a Svelte 5 runes-based store, access properties directly
|
||||
let currentMode = $derived(theme.mode);
|
||||
|
|
@ -51,7 +57,7 @@
|
|||
}
|
||||
|
||||
function handleRateApp() {
|
||||
alert('Thank you for your interest! Rating feature coming soon for the web version. 🌟');
|
||||
alert('Thank you for your interest! Rating feature coming soon for the web version.');
|
||||
}
|
||||
|
||||
// Copy app info
|
||||
|
|
@ -78,7 +84,7 @@ User: ${currentUser?.email || 'N/A'}`;
|
|||
settings.setDeveloperMode(newMode);
|
||||
alert(
|
||||
newMode
|
||||
? '🎉 Developer Mode activated!\nAdvanced settings are now visible.'
|
||||
? 'Developer Mode activated! Advanced settings are now visible.'
|
||||
: 'Developer Mode deactivated.'
|
||||
);
|
||||
}
|
||||
|
|
@ -87,7 +93,7 @@ User: ${currentUser?.email || 'N/A'}`;
|
|||
// Delete account confirmation
|
||||
function handleDeleteAccount() {
|
||||
const confirmed = confirm(
|
||||
'⚠️ Are you sure you want to delete your account?\n\nThis action cannot be undone. All your data will be permanently deleted.'
|
||||
'Are you sure you want to delete your account?\n\nThis action cannot be undone. All your data will be permanently deleted.'
|
||||
);
|
||||
if (confirmed) {
|
||||
const doubleConfirm = confirm(
|
||||
|
|
@ -98,270 +104,406 @@ User: ${currentUser?.email || 'N/A'}`;
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleResetSettings() {
|
||||
if (confirm('Reset all settings to defaults?')) {
|
||||
settings.reset();
|
||||
alert('Settings reset to defaults!');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Settings - Memoro</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="flex h-full flex-col">
|
||||
<!-- Content Area -->
|
||||
<div class="flex-1 overflow-y-auto">
|
||||
<div class="mx-auto max-w-5xl pb-12">
|
||||
<h1 class="mb-8 text-3xl font-bold">Settings</h1>
|
||||
|
||||
<!-- Appearance Section -->
|
||||
<section class="mb-6">
|
||||
<SectionHeader title="Appearance" isFirst={true} />
|
||||
<div class="card">
|
||||
<h3 class="mb-4 text-lg font-medium">Theme Mode</h3>
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
{#each themeModes as mode}
|
||||
<button
|
||||
onclick={() => {
|
||||
setThemeMode(mode.value);
|
||||
activeMode = mode.value;
|
||||
}}
|
||||
class="flex flex-col items-center gap-3 rounded-xl border-2 p-4 transition-all bg-content-hover {activeMode ===
|
||||
mode.value
|
||||
? 'border-primary shadow-md'
|
||||
: 'border-theme'}"
|
||||
>
|
||||
<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
{#if mode.icon === 'sync'}
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
|
||||
/>
|
||||
{:else if mode.icon === 'sunny'}
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"
|
||||
/>
|
||||
{:else}
|
||||
<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"
|
||||
/>
|
||||
{/if}
|
||||
</svg>
|
||||
<span
|
||||
class="text-sm font-medium {activeMode === mode.value
|
||||
? 'text-primary'
|
||||
: 'text-theme'}"
|
||||
>
|
||||
{mode.label}
|
||||
</span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
<p class="mt-4 text-sm text-theme-secondary">
|
||||
Choose how Memoro looks. System automatically matches your device's theme.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- User Interface Elements -->
|
||||
<SectionHeader
|
||||
title="User Interface"
|
||||
collapsible={true}
|
||||
isCollapsed={!showUIElements}
|
||||
onPress={() => (showUIElements = !showUIElements)}
|
||||
/>
|
||||
{#if showUIElements}
|
||||
<div class="mb-6 space-y-4">
|
||||
<SettingsToggle
|
||||
title="Show Language Button"
|
||||
description="Display language selection button next to the recording button"
|
||||
type="toggle"
|
||||
isOn={currentSettings.showLanguageButton}
|
||||
onToggle={settings.setShowLanguageButton}
|
||||
/>
|
||||
<SettingsToggle
|
||||
title="Show Recording Instruction"
|
||||
description="Show 'Start Recording' text with arrow near the recording button"
|
||||
type="toggle"
|
||||
isOn={currentSettings.showRecordingInstruction}
|
||||
onToggle={settings.setShowRecordingInstruction}
|
||||
/>
|
||||
<SettingsToggle
|
||||
title="Show Blueprints"
|
||||
description="Display blueprint selection at the bottom of the screen"
|
||||
type="toggle"
|
||||
isOn={currentSettings.showBlueprints}
|
||||
onToggle={settings.setShowBlueprints}
|
||||
/>
|
||||
<SettingsToggle
|
||||
title="Show Mana Badge"
|
||||
description="Display Mana counter in the header"
|
||||
type="toggle"
|
||||
isOn={currentSettings.showManaBadge}
|
||||
onToggle={settings.setShowManaBadge}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Data & Privacy -->
|
||||
<SectionHeader title="Data & Privacy" />
|
||||
<div class="mb-6 space-y-4">
|
||||
<SettingsToggle
|
||||
title="Save Location"
|
||||
description="Allow the app to save your location to enable location-based features"
|
||||
type="toggle"
|
||||
isOn={currentSettings.saveLocation}
|
||||
onToggle={settings.setSaveLocation}
|
||||
<SettingsPage title="Settings" subtitle="Manage your account and preferences" maxWidth="5xl">
|
||||
<!-- Appearance Section -->
|
||||
<SettingsSection title="Appearance">
|
||||
{#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"
|
||||
/>
|
||||
<SettingsToggle
|
||||
title="Enable Analytics"
|
||||
description="Help improve Memoro by sharing anonymous usage data"
|
||||
type="toggle"
|
||||
isOn={currentSettings.enableAnalytics}
|
||||
onToggle={settings.setEnableAnalytics}
|
||||
/>
|
||||
</div>
|
||||
</svg>
|
||||
{/snippet}
|
||||
|
||||
<!-- Support -->
|
||||
<SectionHeader title="Support" />
|
||||
<div class="mb-6 space-y-4">
|
||||
<SettingsToggle
|
||||
title="Contact Support"
|
||||
description="Need help? Get in touch with our support team"
|
||||
type="button"
|
||||
onPress={handleContactSupport}
|
||||
icon="mail-outline"
|
||||
/>
|
||||
<SettingsToggle
|
||||
title="Rate App"
|
||||
description="Enjoying Memoro? Rate us in your browser's extension store"
|
||||
type="button"
|
||||
onPress={handleRateApp}
|
||||
icon="star-outline"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Advanced Settings -->
|
||||
<div class="mb-6">
|
||||
<button
|
||||
onclick={() => (showMoreSettings = !showMoreSettings)}
|
||||
class="btn-secondary w-full justify-center"
|
||||
>
|
||||
<svg
|
||||
class="mr-2 inline-block h-5 w-5 transition-transform {showMoreSettings
|
||||
? 'rotate-180'
|
||||
: ''}"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 9l-7 7-7-7"
|
||||
/>
|
||||
</svg>
|
||||
{showMoreSettings ? 'Hide' : 'Show'} Advanced Settings
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if showMoreSettings}
|
||||
<div class="card mb-6 border-2 border-theme">
|
||||
<h3 class="mb-4 text-lg font-semibold text-theme-secondary">Delete Account</h3>
|
||||
<p class="mb-4 text-sm text-theme-secondary">
|
||||
If you delete your account, all your data will be permanently deleted. This action
|
||||
cannot be undone.
|
||||
</p>
|
||||
<SettingsCard>
|
||||
<h3 class="mb-4 text-lg font-medium text-[hsl(var(--foreground))]">Theme Mode</h3>
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
{#each themeModes as mode}
|
||||
<button
|
||||
onclick={handleDeleteAccount}
|
||||
class="btn-primary w-full bg-red-600 hover:bg-red-700"
|
||||
onclick={() => {
|
||||
setThemeMode(mode.value);
|
||||
activeMode = mode.value;
|
||||
}}
|
||||
class="flex flex-col items-center gap-3 rounded-xl border-2 p-4 transition-all {activeMode ===
|
||||
mode.value
|
||||
? 'border-[hsl(var(--primary))] bg-[hsl(var(--primary)/0.1)] shadow-md'
|
||||
: 'border-[hsl(var(--border))] bg-[hsl(var(--muted))]'}"
|
||||
>
|
||||
<svg
|
||||
class="mr-2 inline-block h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
{#if mode.icon === 'sync'}
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
|
||||
/>
|
||||
{:else if mode.icon === 'sunny'}
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"
|
||||
/>
|
||||
{:else}
|
||||
<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"
|
||||
/>
|
||||
{/if}
|
||||
</svg>
|
||||
<span
|
||||
class="text-sm font-medium {activeMode === mode.value
|
||||
? 'text-[hsl(var(--primary))]'
|
||||
: 'text-[hsl(var(--foreground))]'}"
|
||||
>
|
||||
{mode.label}
|
||||
</span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
<p class="mt-4 text-sm text-[hsl(var(--muted-foreground))]">
|
||||
Choose how Memoro looks. System automatically matches your device's theme.
|
||||
</p>
|
||||
</SettingsCard>
|
||||
</SettingsSection>
|
||||
|
||||
<!-- User Interface Section (Collapsible) -->
|
||||
<SettingsSection title="User Interface">
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
|
||||
<button
|
||||
onclick={() => (showUIElements = !showUIElements)}
|
||||
class="mb-4 flex w-full items-center justify-between rounded-lg bg-[hsl(var(--muted))] p-3 text-left"
|
||||
>
|
||||
<span class="text-sm font-medium text-[hsl(var(--foreground))]">
|
||||
{showUIElements ? 'Hide' : 'Show'} UI Element Options
|
||||
</span>
|
||||
<svg
|
||||
class="h-5 w-5 transition-transform text-[hsl(var(--muted-foreground))] {showUIElements
|
||||
? 'rotate-180'
|
||||
: ''}"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{#if showUIElements}
|
||||
<SettingsCard>
|
||||
<SettingsToggle
|
||||
label="Show Language Button"
|
||||
description="Display language selection button next to the recording button"
|
||||
isOn={currentSettings.showLanguageButton}
|
||||
onToggle={settings.setShowLanguageButton}
|
||||
>
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||
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>
|
||||
Delete Account
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</SettingsToggle>
|
||||
<SettingsToggle
|
||||
label="Show Recording Instruction"
|
||||
description="Show 'Start Recording' text with arrow near the recording button"
|
||||
isOn={currentSettings.showRecordingInstruction}
|
||||
onToggle={settings.setShowRecordingInstruction}
|
||||
>
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
</SettingsToggle>
|
||||
<SettingsToggle
|
||||
label="Show Blueprints"
|
||||
description="Display blueprint selection at the bottom of the screen"
|
||||
isOn={currentSettings.showBlueprints}
|
||||
onToggle={settings.setShowBlueprints}
|
||||
>
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
</SettingsToggle>
|
||||
<SettingsToggle
|
||||
label="Show Mana Badge"
|
||||
description="Display Mana counter in the header"
|
||||
isOn={currentSettings.showManaBadge}
|
||||
onToggle={settings.setShowManaBadge}
|
||||
border={false}
|
||||
>
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
</SettingsToggle>
|
||||
</SettingsCard>
|
||||
{/if}
|
||||
</SettingsSection>
|
||||
|
||||
<!-- Developer Settings -->
|
||||
{#if currentSettings.developerMode}
|
||||
<SectionHeader title="Developer Settings" />
|
||||
<div class="mb-6 space-y-4">
|
||||
<SettingsToggle
|
||||
title="Show Debug Borders"
|
||||
description="Display borders around UI elements for development"
|
||||
type="toggle"
|
||||
isOn={currentSettings.showDebugBorders}
|
||||
onToggle={settings.setShowDebugBorders}
|
||||
/>
|
||||
<SettingsToggle
|
||||
title="Reset All Settings"
|
||||
description="Reset all settings to default values"
|
||||
type="button"
|
||||
onPress={() => {
|
||||
if (confirm('Reset all settings to defaults?')) {
|
||||
settings.reset();
|
||||
alert('Settings reset to defaults!');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<!-- Data & Privacy Section -->
|
||||
<SettingsSection title="Data & Privacy">
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
|
||||
<!-- Account Section -->
|
||||
<SectionHeader title="Account" />
|
||||
<div class="card mb-6">
|
||||
{#if currentUser}
|
||||
<div class="mb-6">
|
||||
<label class="mb-2 block text-sm font-medium text-theme-secondary">Email Address</label>
|
||||
<p class="text-lg font-medium">{currentUser.email || 'No email available'}</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<button onclick={handleLogout} class="btn-primary w-full">
|
||||
<svg
|
||||
class="mr-2 inline-block h-5 w-5"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<SettingsCard>
|
||||
<SettingsToggle
|
||||
label="Save Location"
|
||||
description="Allow the app to save your location to enable location-based features"
|
||||
isOn={currentSettings.saveLocation}
|
||||
onToggle={settings.setSaveLocation}
|
||||
>
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
|
||||
d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"
|
||||
/>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"
|
||||
/>
|
||||
</svg>
|
||||
Sign Out
|
||||
</button>
|
||||
</div>
|
||||
{/snippet}
|
||||
</SettingsToggle>
|
||||
<SettingsToggle
|
||||
label="Enable Analytics"
|
||||
description="Help improve Memoro by sharing anonymous usage data"
|
||||
isOn={currentSettings.enableAnalytics}
|
||||
onToggle={settings.setEnableAnalytics}
|
||||
border={false}
|
||||
>
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
</SettingsToggle>
|
||||
</SettingsCard>
|
||||
</SettingsSection>
|
||||
|
||||
<!-- App Information Section -->
|
||||
<SectionHeader title="App Information" />
|
||||
<div class="card mb-6 relative">
|
||||
<!-- Support Section -->
|
||||
<SettingsSection title="Support">
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M18.364 5.636l-3.536 3.536m0 5.656l3.536 3.536M9.172 9.172L5.636 5.636m3.536 9.192l-3.536 3.536M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-5 0a4 4 0 11-8 0 4 4 0 018 0z"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
|
||||
<SettingsCard>
|
||||
<SettingsRow
|
||||
label="Contact Support"
|
||||
description="Need help? Get in touch with our support team"
|
||||
onclick={handleContactSupport}
|
||||
>
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
</SettingsRow>
|
||||
<SettingsRow
|
||||
label="Rate App"
|
||||
description="Enjoying Memoro? Rate us in your browser's extension store"
|
||||
onclick={handleRateApp}
|
||||
border={false}
|
||||
>
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
</SettingsRow>
|
||||
</SettingsCard>
|
||||
</SettingsSection>
|
||||
|
||||
<!-- Account Section -->
|
||||
<SettingsSection title="Account">
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
|
||||
<SettingsCard>
|
||||
<SettingsRow label="Email Address" description={currentUser?.email || 'No email available'}>
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
</SettingsRow>
|
||||
</SettingsCard>
|
||||
</SettingsSection>
|
||||
|
||||
<!-- Developer Settings -->
|
||||
{#if currentSettings.developerMode}
|
||||
<SettingsSection title="Developer Settings">
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
|
||||
<SettingsCard>
|
||||
<SettingsToggle
|
||||
label="Show Debug Borders"
|
||||
description="Display borders around UI elements for development"
|
||||
isOn={currentSettings.showDebugBorders}
|
||||
onToggle={settings.setShowDebugBorders}
|
||||
>
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6z"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
</SettingsToggle>
|
||||
<SettingsRow
|
||||
label="Reset All Settings"
|
||||
description="Reset all settings to default values"
|
||||
onclick={handleResetSettings}
|
||||
border={false}
|
||||
>
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
</SettingsRow>
|
||||
</SettingsCard>
|
||||
</SettingsSection>
|
||||
{/if}
|
||||
|
||||
<!-- App Information Section -->
|
||||
<SettingsSection title="App Information">
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
|
||||
<SettingsCard>
|
||||
<div class="relative">
|
||||
<button
|
||||
onclick={copyAppInfo}
|
||||
class="absolute right-4 top-4 rounded-lg p-2 transition-colors bg-menu-hover"
|
||||
class="absolute right-0 top-0 rounded-lg p-2 transition-colors bg-[hsl(var(--muted))] hover:bg-[hsl(var(--muted)/0.8)]"
|
||||
title="Copy app information"
|
||||
>
|
||||
<svg
|
||||
class="h-5 w-5 text-theme-muted"
|
||||
class="h-5 w-5 text-[hsl(var(--muted-foreground))]"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
|
|
@ -374,47 +516,102 @@ User: ${currentUser?.email || 'N/A'}`;
|
|||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="space-y-3 pr-12">
|
||||
<div class="flex items-center justify-between border-b border-theme-light py-3">
|
||||
<span class="text-sm text-theme-secondary">Version</span>
|
||||
<span class="font-medium">0.1.0</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between border-b border-theme-light py-3">
|
||||
<span class="text-sm text-theme-secondary">Platform</span>
|
||||
<span class="font-medium">Web</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between border-b border-theme-light py-3">
|
||||
<span class="text-sm text-theme-secondary">Build</span>
|
||||
<span class="font-medium">Beta</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between py-3">
|
||||
<span class="text-sm text-theme-secondary">Browser</span>
|
||||
<span class="text-xs font-medium"
|
||||
>{navigator.userAgent.split(' ').slice(-2).join(' ')}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<SettingsRow label="Version">
|
||||
<span class="text-sm font-medium text-[hsl(var(--foreground))]">0.1.0</span>
|
||||
</SettingsRow>
|
||||
<SettingsRow label="Platform">
|
||||
<span class="text-sm font-medium text-[hsl(var(--foreground))]">Web</span>
|
||||
</SettingsRow>
|
||||
<SettingsRow label="Build">
|
||||
<span class="text-sm font-medium text-[hsl(var(--foreground))]">Beta</span>
|
||||
</SettingsRow>
|
||||
<SettingsRow label="Browser" border={false}>
|
||||
<span class="text-xs font-medium text-[hsl(var(--foreground))]">
|
||||
{navigator.userAgent.split(' ').slice(-2).join(' ')}
|
||||
</span>
|
||||
</SettingsRow>
|
||||
</SettingsCard>
|
||||
</SettingsSection>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="pt-8 text-center">
|
||||
<button
|
||||
onclick={handleVersionClick}
|
||||
class="text-sm text-theme-muted transition-colors hover:text-theme-secondary"
|
||||
>
|
||||
© 2025 Memoro GmbH
|
||||
</button>
|
||||
<p class="mt-1 text-xs text-theme-muted">Made with ❤️ in Germany</p>
|
||||
{#if clickCount > 0 && clickCount < 7}
|
||||
<p class="mt-2 text-xs text-theme-muted opacity-50">{7 - clickCount} more clicks...</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Advanced Settings / Danger Zone -->
|
||||
<button
|
||||
onclick={() => (showMoreSettings = !showMoreSettings)}
|
||||
class="mb-4 flex w-full items-center justify-center gap-2 rounded-lg bg-[hsl(var(--muted))] p-3"
|
||||
>
|
||||
<svg
|
||||
class="h-5 w-5 transition-transform text-[hsl(var(--muted-foreground))] {showMoreSettings
|
||||
? 'rotate-180'
|
||||
: ''}"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
<span class="text-sm font-medium text-[hsl(var(--foreground))]">
|
||||
{showMoreSettings ? 'Hide' : 'Show'} Advanced Settings
|
||||
</span>
|
||||
</button>
|
||||
|
||||
{#if showMoreSettings}
|
||||
<SettingsDangerZone title="Danger Zone">
|
||||
<SettingsDangerButton
|
||||
label="Delete Account"
|
||||
description="Permanently delete your account and all data. This action cannot be undone."
|
||||
buttonText="Delete Account"
|
||||
onclick={handleDeleteAccount}
|
||||
border={false}
|
||||
>
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
</SettingsDangerButton>
|
||||
</SettingsDangerZone>
|
||||
{/if}
|
||||
|
||||
<!-- Logout -->
|
||||
<SettingsDangerZone title="Sign Out">
|
||||
<SettingsDangerButton
|
||||
label="Sign Out"
|
||||
description="Sign out of your Memoro account"
|
||||
buttonText="Sign Out"
|
||||
onclick={handleLogout}
|
||||
border={false}
|
||||
>
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
</SettingsDangerButton>
|
||||
</SettingsDangerZone>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="pt-8 text-center">
|
||||
<button
|
||||
onclick={handleVersionClick}
|
||||
class="text-sm text-[hsl(var(--muted-foreground))] transition-colors hover:text-[hsl(var(--foreground))]"
|
||||
>
|
||||
2025 Memoro GmbH
|
||||
</button>
|
||||
<p class="mt-1 text-xs text-[hsl(var(--muted-foreground))]">Made with love in Germany</p>
|
||||
{#if clickCount > 0 && clickCount < 7}
|
||||
<p class="mt-2 text-xs text-[hsl(var(--muted-foreground))] opacity-50">
|
||||
{7 - clickCount} more clicks...
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.grid {
|
||||
display: grid;
|
||||
}
|
||||
</style>
|
||||
</SettingsPage>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,15 @@
|
|||
import { goto } from '$app/navigation';
|
||||
import { auth, user } from '$lib/stores/auth';
|
||||
import { theme } from '$lib/stores/theme';
|
||||
import {
|
||||
SettingsPage,
|
||||
SettingsSection,
|
||||
SettingsCard,
|
||||
SettingsRow,
|
||||
SettingsToggle,
|
||||
SettingsDangerZone,
|
||||
SettingsDangerButton,
|
||||
} from '@manacore/shared-ui';
|
||||
|
||||
let isDeleting = $state(false);
|
||||
let showDeleteConfirm = $state(false);
|
||||
|
|
@ -25,129 +34,215 @@
|
|||
await auth.signOut();
|
||||
goto('/login');
|
||||
}
|
||||
|
||||
function toggleDarkMode(value: boolean) {
|
||||
theme.toggleMode();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="mx-auto max-w-2xl space-y-6">
|
||||
<!-- Header -->
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">Einstellungen</h1>
|
||||
<p class="text-gray-600 dark:text-gray-400">Verwalte dein Konto und App-Einstellungen</p>
|
||||
</div>
|
||||
<SettingsPage title="Einstellungen" subtitle="Verwalte dein Konto und App-Einstellungen">
|
||||
<!-- Account Section -->
|
||||
<SettingsSection title="Konto">
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
|
||||
<!-- Account -->
|
||||
<div class="rounded-2xl bg-white p-6 shadow-lg dark:bg-gray-800">
|
||||
<h2 class="mb-4 text-lg font-semibold text-gray-900 dark:text-white">Konto</h2>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="font-medium text-gray-900 dark:text-white">E-Mail</p>
|
||||
<p class="text-gray-600 dark:text-gray-400">{$user?.email || 'Nicht angemeldet'}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="font-medium text-gray-900 dark:text-white">Benutzer-ID</p>
|
||||
<p class="font-mono text-sm text-gray-600 dark:text-gray-400">{$user?.id || '—'}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<SettingsCard>
|
||||
<SettingsRow label="E-Mail" description={$user?.email || 'Nicht angemeldet'}>
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
</SettingsRow>
|
||||
<SettingsRow label="Benutzer-ID" border={false}>
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M10 6H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V8a2 2 0 00-2-2h-5m-4 0V5a2 2 0 114 0v1m-4 0a2 2 0 104 0m-5 8a2 2 0 100-4 2 2 0 000 4zm0 0c1.306 0 2.417.835 2.83 2M9 14a3.001 3.001 0 00-2.83 2M15 11h3m-3 4h2"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
<span class="font-mono text-xs text-[hsl(var(--muted-foreground))]">{$user?.id || '—'}</span>
|
||||
</SettingsRow>
|
||||
</SettingsCard>
|
||||
</SettingsSection>
|
||||
|
||||
<!-- Appearance -->
|
||||
<div class="rounded-2xl bg-white p-6 shadow-lg dark:bg-gray-800">
|
||||
<h2 class="mb-4 text-lg font-semibold text-gray-900 dark:text-white">Erscheinungsbild</h2>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="font-medium text-gray-900 dark:text-white">Dunkles Design</p>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
Aktiviere den Dark Mode für eine augenfreundliche Ansicht
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onclick={() => theme.toggleMode()}
|
||||
class="relative h-7 w-12 rounded-full transition-colors {effectiveMode === 'dark'
|
||||
? 'bg-green-500'
|
||||
: 'bg-gray-200 dark:bg-gray-700'}"
|
||||
>
|
||||
<span
|
||||
class="absolute top-1 h-5 w-5 rounded-full bg-white shadow transition-transform {effectiveMode ===
|
||||
'dark'
|
||||
? 'translate-x-6'
|
||||
: 'translate-x-1'}"
|
||||
></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Appearance Section -->
|
||||
<SettingsSection title="Erscheinungsbild">
|
||||
{#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}
|
||||
|
||||
<!-- Data Management -->
|
||||
<div class="rounded-2xl bg-white p-6 shadow-lg dark:bg-gray-800">
|
||||
<h2 class="mb-4 text-lg font-semibold text-gray-900 dark:text-white">Datenverwaltung</h2>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="font-medium text-gray-900 dark:text-white">Daten exportieren</p>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
Exportiere alle deine Mahlzeiten und Statistiken
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onclick={() => goto('/export')}
|
||||
class="rounded-xl bg-gray-100 px-4 py-2 font-medium text-gray-700 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
|
||||
>
|
||||
<SettingsCard>
|
||||
<SettingsToggle
|
||||
label="Dunkles Design"
|
||||
description="Aktiviere den Dark Mode für eine augenfreundliche Ansicht"
|
||||
isOn={effectiveMode === 'dark'}
|
||||
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>
|
||||
</SettingsCard>
|
||||
</SettingsSection>
|
||||
|
||||
<!-- Data Management Section -->
|
||||
<SettingsSection title="Datenverwaltung">
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
|
||||
<SettingsCard>
|
||||
<SettingsRow
|
||||
label="Daten exportieren"
|
||||
description="Exportiere alle deine Mahlzeiten und Statistiken"
|
||||
onclick={() => goto('/export')}
|
||||
>
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
<span class="px-4 py-2 text-sm font-medium bg-[hsl(var(--muted))] text-[hsl(var(--foreground))] rounded-lg">
|
||||
Export
|
||||
</button>
|
||||
</div>
|
||||
</span>
|
||||
</SettingsRow>
|
||||
</SettingsCard>
|
||||
|
||||
<hr class="border-gray-200 dark:border-gray-700" />
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="font-medium text-red-600 dark:text-red-400">Alle Daten löschen</p>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
Löscht alle deine Mahlzeiten und Statistiken unwiderruflich
|
||||
</p>
|
||||
</div>
|
||||
{#if showDeleteConfirm}
|
||||
<SettingsDangerZone title="Gefahrenbereich">
|
||||
{#if showDeleteConfirm}
|
||||
<div class="px-5 py-4 flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<p class="font-medium text-[hsl(var(--foreground))]">Alle Daten löschen</p>
|
||||
<p class="text-sm text-[hsl(var(--muted-foreground))]">
|
||||
Löscht alle deine Mahlzeiten und Statistiken unwiderruflich
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
onclick={() => (showDeleteConfirm = false)}
|
||||
class="rounded-xl bg-gray-100 px-4 py-2 font-medium text-gray-700 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
|
||||
class="px-4 py-2 text-sm font-medium bg-[hsl(var(--muted))] text-[hsl(var(--foreground))] rounded-lg hover:bg-[hsl(var(--muted)/0.8)]"
|
||||
>
|
||||
Abbrechen
|
||||
</button>
|
||||
<button
|
||||
onclick={handleDeleteAllData}
|
||||
disabled={isDeleting}
|
||||
class="rounded-xl bg-red-500 px-4 py-2 font-medium text-white hover:bg-red-600 disabled:opacity-50"
|
||||
class="px-4 py-2 text-sm font-medium bg-[hsl(var(--destructive))] text-white rounded-lg hover:bg-[hsl(var(--destructive)/0.9)] disabled:opacity-50"
|
||||
>
|
||||
{isDeleting ? 'Wird gelöscht...' : 'Bestätigen'}
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<button
|
||||
onclick={handleDeleteAllData}
|
||||
class="rounded-xl bg-red-100 px-4 py-2 font-medium text-red-600 hover:bg-red-200 dark:bg-red-900/30 dark:text-red-400 dark:hover:bg-red-900/50"
|
||||
>
|
||||
Löschen
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<SettingsDangerButton
|
||||
label="Alle Daten löschen"
|
||||
description="Löscht alle deine Mahlzeiten und Statistiken unwiderruflich"
|
||||
buttonText="Löschen"
|
||||
onclick={handleDeleteAllData}
|
||||
border={false}
|
||||
>
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
</SettingsDangerButton>
|
||||
{/if}
|
||||
</SettingsDangerZone>
|
||||
</SettingsSection>
|
||||
|
||||
<!-- Logout -->
|
||||
<button
|
||||
onclick={handleLogout}
|
||||
class="w-full rounded-2xl border-2 border-red-500 bg-white py-4 font-semibold text-red-600 transition-colors hover:bg-red-50 dark:bg-gray-800 dark:hover:bg-red-900/20"
|
||||
>
|
||||
Abmelden
|
||||
</button>
|
||||
<!-- Logout Section -->
|
||||
<SettingsDangerZone title="Abmelden">
|
||||
<SettingsDangerButton
|
||||
label="Abmelden"
|
||||
description="Von deinem Konto abmelden"
|
||||
buttonText="Abmelden"
|
||||
onclick={handleLogout}
|
||||
border={false}
|
||||
>
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
</SettingsDangerButton>
|
||||
</SettingsDangerZone>
|
||||
|
||||
<!-- App Info -->
|
||||
<div class="text-center text-sm text-gray-500 dark:text-gray-400">
|
||||
<p>Nutriphi Web v0.1.0</p>
|
||||
<p>Teil des Mana Core Ökosystems</p>
|
||||
</div>
|
||||
</div>
|
||||
<SettingsSection title="Über">
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
|
||||
<SettingsCard>
|
||||
<SettingsRow label="Version" border={false}>
|
||||
<span class="text-sm text-[hsl(var(--muted-foreground))]">0.1.0</span>
|
||||
</SettingsRow>
|
||||
</SettingsCard>
|
||||
|
||||
<p class="text-center text-sm text-[hsl(var(--muted-foreground))] mt-4">
|
||||
Teil des Mana Core Ökosystems
|
||||
</p>
|
||||
</SettingsSection>
|
||||
</SettingsPage>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,14 @@
|
|||
import { page } from '$app/stores';
|
||||
import { toastMessages, notify } from '$lib/services/toast';
|
||||
import * as m from '$paraglide/messages';
|
||||
import {
|
||||
SettingsPage,
|
||||
SettingsSection,
|
||||
SettingsCard,
|
||||
SettingsRow,
|
||||
SettingsDangerZone,
|
||||
SettingsDangerButton,
|
||||
} from '@manacore/shared-ui';
|
||||
|
||||
let { data, form }: { data: PageData; form: ActionData } = $props();
|
||||
let isSubmitting = $state(false);
|
||||
|
|
@ -28,14 +36,8 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div class="min-h-screen bg-theme-background">
|
||||
<div class="mx-auto max-w-3xl px-4 py-8 sm:px-6 lg:px-8">
|
||||
<div class="mb-8">
|
||||
<h1 class="text-3xl font-bold text-theme-text dark:text-white">Settings</h1>
|
||||
<p class="mt-2 text-theme-text dark:text-theme-text">Manage your account and preferences</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-6">
|
||||
<SettingsPage title="Settings" subtitle="Manage your account and preferences" maxWidth="3xl">
|
||||
<div class="space-y-6">
|
||||
<!-- Profile Section -->
|
||||
<div class="rounded-xl bg-white p-6 shadow-xl dark:bg-theme-surface">
|
||||
<h2 class="mb-6 text-xl font-semibold text-theme-text dark:text-white">
|
||||
|
|
@ -779,5 +781,4 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SettingsPage>
|
||||
|
|
|
|||
|
|
@ -1,24 +1,34 @@
|
|||
<script lang="ts">
|
||||
import { Card, Button, Input } from '@manacore/shared-ui';
|
||||
import { Button, Input } from '@manacore/shared-ui';
|
||||
import {
|
||||
SettingsPage,
|
||||
SettingsSection,
|
||||
SettingsCard,
|
||||
SettingsRow,
|
||||
SettingsDangerZone,
|
||||
SettingsDangerButton,
|
||||
} from '@manacore/shared-ui';
|
||||
import { enhance } from '$app/forms';
|
||||
|
||||
let { data, form } = $props();
|
||||
let loading = $state(false);
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<div class="mb-8">
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">Settings</h1>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
Manage your account settings and preferences
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-6 lg:grid-cols-2">
|
||||
<!-- Profile Settings -->
|
||||
<Card>
|
||||
<h2 class="mb-4 text-lg font-semibold text-gray-900 dark:text-white">Profile Information</h2>
|
||||
<SettingsPage title="Settings" subtitle="Manage your account settings and preferences.">
|
||||
<!-- Profile Section -->
|
||||
<SettingsSection title="Profile Information">
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
|
||||
<SettingsCard>
|
||||
<form
|
||||
method="POST"
|
||||
action="?/updateProfile"
|
||||
|
|
@ -29,6 +39,7 @@
|
|||
loading = false;
|
||||
};
|
||||
}}
|
||||
class="p-5"
|
||||
>
|
||||
{#if form?.success}
|
||||
<div
|
||||
|
|
@ -50,7 +61,7 @@
|
|||
<div>
|
||||
<label
|
||||
for="email"
|
||||
class="mb-2 block text-sm font-medium text-gray-900 dark:text-gray-100"
|
||||
class="mb-2 block text-sm font-medium text-[hsl(var(--foreground))]"
|
||||
>
|
||||
Email
|
||||
</label>
|
||||
|
|
@ -59,15 +70,15 @@
|
|||
id="email"
|
||||
value={data.session?.user?.email || ''}
|
||||
disabled
|
||||
class="bg-gray-50 dark:bg-gray-900"
|
||||
class="bg-[hsl(var(--muted))]"
|
||||
/>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">Email cannot be changed</p>
|
||||
<p class="mt-1 text-xs text-[hsl(var(--muted-foreground))]">Email cannot be changed</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
for="firstName"
|
||||
class="mb-2 block text-sm font-medium text-gray-900 dark:text-gray-100"
|
||||
class="mb-2 block text-sm font-medium text-[hsl(var(--foreground))]"
|
||||
>
|
||||
First Name
|
||||
</label>
|
||||
|
|
@ -83,7 +94,7 @@
|
|||
<div>
|
||||
<label
|
||||
for="lastName"
|
||||
class="mb-2 block text-sm font-medium text-gray-900 dark:text-gray-100"
|
||||
class="mb-2 block text-sm font-medium text-[hsl(var(--foreground))]"
|
||||
>
|
||||
Last Name
|
||||
</label>
|
||||
|
|
@ -101,74 +112,116 @@
|
|||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Card>
|
||||
</SettingsCard>
|
||||
</SettingsSection>
|
||||
|
||||
<!-- Account Stats -->
|
||||
<Card>
|
||||
<h2 class="mb-4 text-lg font-semibold text-gray-900 dark:text-white">Account Information</h2>
|
||||
<!-- Account Info Section -->
|
||||
<SettingsSection title="Account Information">
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<div class="mb-1 flex items-center justify-between">
|
||||
<span class="text-sm font-medium text-gray-600 dark:text-gray-400"
|
||||
>Available Credits</span
|
||||
>
|
||||
<span class="text-2xl font-bold text-primary-600 dark:text-primary-400">
|
||||
{data.profile?.credits || 0}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<SettingsCard>
|
||||
<SettingsRow label="Available Credits">
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
<span class="text-2xl font-bold text-[hsl(var(--primary))]">
|
||||
{data.profile?.credits || 0}
|
||||
</span>
|
||||
</SettingsRow>
|
||||
|
||||
<div class="border-t border-gray-200 pt-4 dark:border-gray-700">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-sm font-medium text-gray-600 dark:text-gray-400"
|
||||
>Subscription Plan</span
|
||||
>
|
||||
<span
|
||||
class="rounded-full bg-blue-100 px-3 py-1 text-xs font-medium text-blue-800 dark:bg-blue-900/20 dark:text-blue-400"
|
||||
>
|
||||
{data.profile?.subscription_plan_id || 'Free'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<SettingsRow label="Subscription Plan">
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
<span class="rounded-full bg-blue-100 px-3 py-1 text-xs font-medium text-blue-800 dark:bg-blue-900/20 dark:text-blue-400">
|
||||
{data.profile?.subscription_plan_id || 'Free'}
|
||||
</span>
|
||||
</SettingsRow>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-sm font-medium text-gray-600 dark:text-gray-400"
|
||||
>Subscription Status</span
|
||||
>
|
||||
<span
|
||||
class="rounded-full {data.profile?.subscription_status === 'active'
|
||||
? 'bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-400'
|
||||
: 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-400'} px-3 py-1 text-xs font-medium"
|
||||
>
|
||||
{data.profile?.subscription_status || 'inactive'}
|
||||
</span>
|
||||
</div>
|
||||
<SettingsRow label="Subscription Status">
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
<span
|
||||
class="rounded-full px-3 py-1 text-xs font-medium
|
||||
{data.profile?.subscription_status === 'active'
|
||||
? 'bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-400'
|
||||
: 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-400'}"
|
||||
>
|
||||
{data.profile?.subscription_status || 'inactive'}
|
||||
</span>
|
||||
</SettingsRow>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-sm font-medium text-gray-600 dark:text-gray-400">Member Since</span>
|
||||
<span class="text-sm text-gray-900 dark:text-white">
|
||||
{data.profile?.created_at
|
||||
? new Date(data.profile.created_at).toLocaleDateString()
|
||||
: 'N/A'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<SettingsRow label="Member Since" border={false}>
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
<span class="text-sm text-[hsl(var(--foreground))]">
|
||||
{data.profile?.created_at
|
||||
? new Date(data.profile.created_at).toLocaleDateString()
|
||||
: 'N/A'}
|
||||
</span>
|
||||
</SettingsRow>
|
||||
</SettingsCard>
|
||||
</SettingsSection>
|
||||
|
||||
<!-- Danger Zone -->
|
||||
<Card class="border-red-200 dark:border-red-800">
|
||||
<h2 class="mb-4 text-lg font-semibold text-red-900 dark:text-red-400">Danger Zone</h2>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<h3 class="mb-2 font-medium text-gray-900 dark:text-white">Delete Account</h3>
|
||||
<p class="mb-4 text-sm text-gray-600 dark:text-gray-400">
|
||||
Once you delete your account, there is no going back. Please be certain.
|
||||
</p>
|
||||
<Button variant="danger" disabled>Delete Account</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Danger Zone -->
|
||||
<SettingsDangerZone title="Danger Zone">
|
||||
<SettingsDangerButton
|
||||
label="Delete Account"
|
||||
description="Once you delete your account, there is no going back. Please be certain."
|
||||
buttonText="Delete Account"
|
||||
onclick={() => {}}
|
||||
disabled
|
||||
border={false}
|
||||
>
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
</SettingsDangerButton>
|
||||
</SettingsDangerZone>
|
||||
</SettingsPage>
|
||||
|
|
|
|||
|
|
@ -2,125 +2,168 @@
|
|||
import { goto } from '$app/navigation';
|
||||
import { auth } from '$lib/stores/auth.svelte';
|
||||
import { theme } from '$lib/stores/theme';
|
||||
import { User, Mail, Shield, LogOut, Sun, Moon, Monitor } from 'lucide-svelte';
|
||||
import {
|
||||
SettingsPage,
|
||||
SettingsSection,
|
||||
SettingsCard,
|
||||
SettingsRow,
|
||||
SettingsToggle,
|
||||
SettingsDangerZone,
|
||||
SettingsDangerButton,
|
||||
} from '@manacore/shared-ui';
|
||||
|
||||
function handleLogout() {
|
||||
auth.logout();
|
||||
goto('/login');
|
||||
}
|
||||
|
||||
function setThemeMode(mode: 'light' | 'dark' | 'system') {
|
||||
theme.setMode(mode);
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Settings - Presi</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<h1 class="text-2xl font-bold text-foreground mb-8">Settings</h1>
|
||||
<SettingsPage title="Settings" subtitle="Manage your account and preferences.">
|
||||
<!-- Account Section -->
|
||||
<SettingsSection title="Account">
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Account Section -->
|
||||
<div
|
||||
class="bg-surface rounded-xl shadow-sm border border-border overflow-hidden"
|
||||
>
|
||||
<div class="p-4 border-b border-border">
|
||||
<h2 class="text-lg font-semibold text-foreground flex items-center gap-2">
|
||||
<User class="w-5 h-5 text-muted-foreground" />
|
||||
Account
|
||||
</h2>
|
||||
</div>
|
||||
<div class="p-4 space-y-4">
|
||||
<div class="flex items-center justify-between py-2">
|
||||
<div class="flex items-center gap-3">
|
||||
<Mail class="w-5 h-5 text-muted-foreground" />
|
||||
<div>
|
||||
<p class="text-sm font-medium text-foreground">Email</p>
|
||||
<p class="text-sm text-muted-foreground">{auth.user?.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between py-2">
|
||||
<div class="flex items-center gap-3">
|
||||
<Shield class="w-5 h-5 text-muted-foreground" />
|
||||
<div>
|
||||
<p class="text-sm font-medium text-foreground">User ID</p>
|
||||
<p class="text-sm text-muted-foreground font-mono">{auth.user?.id}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<SettingsCard>
|
||||
<SettingsRow label="Email" description={auth.user?.email || 'Not available'}>
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
</SettingsRow>
|
||||
<SettingsRow label="User ID" description={auth.user?.id || 'Not available'} border={false}>
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
<span class="font-mono text-xs text-[hsl(var(--muted-foreground))]">{auth.user?.id || '-'}</span>
|
||||
</SettingsRow>
|
||||
</SettingsCard>
|
||||
</SettingsSection>
|
||||
|
||||
<!-- Appearance Section -->
|
||||
<div
|
||||
class="bg-surface rounded-xl shadow-sm border border-border overflow-hidden"
|
||||
>
|
||||
<div class="p-4 border-b border-border">
|
||||
<h2 class="text-lg font-semibold text-foreground flex items-center gap-2">
|
||||
<Sun class="w-5 h-5 text-muted-foreground" />
|
||||
Appearance
|
||||
</h2>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<p class="text-sm text-muted-foreground mb-4">Choose your preferred theme</p>
|
||||
<!-- Appearance Section -->
|
||||
<SettingsSection title="Appearance">
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
|
||||
<SettingsCard>
|
||||
<div class="px-5 py-4">
|
||||
<p class="font-medium text-[hsl(var(--foreground))] mb-2">Theme</p>
|
||||
<p class="text-sm text-[hsl(var(--muted-foreground))] mb-4">
|
||||
Choose your preferred theme
|
||||
</p>
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
<button
|
||||
onclick={() => theme.setMode('light')}
|
||||
class="flex flex-col items-center gap-2 p-4 rounded-lg border-2 transition-colors {theme.mode ===
|
||||
'light'
|
||||
? 'border-primary bg-primary/10'
|
||||
: 'border-border'}"
|
||||
onclick={() => setThemeMode('light')}
|
||||
class="flex flex-col items-center gap-2 p-4 rounded-lg border-2 transition-colors
|
||||
{theme.mode === 'light'
|
||||
? 'border-[hsl(var(--primary))] bg-[hsl(var(--primary)/0.1)]'
|
||||
: 'border-[hsl(var(--border))]'}"
|
||||
>
|
||||
<Sun class="w-6 h-6 text-amber-500" />
|
||||
<span class="text-sm font-medium text-foreground">Light</span>
|
||||
<svg class="w-6 h-6 text-amber-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"
|
||||
/>
|
||||
</svg>
|
||||
<span class="text-sm font-medium text-[hsl(var(--foreground))]">Light</span>
|
||||
</button>
|
||||
<button
|
||||
onclick={() => theme.setMode('dark')}
|
||||
class="flex flex-col items-center gap-2 p-4 rounded-lg border-2 transition-colors {theme.mode ===
|
||||
'dark'
|
||||
? 'border-primary bg-primary/10'
|
||||
: 'border-border'}"
|
||||
onclick={() => setThemeMode('dark')}
|
||||
class="flex flex-col items-center gap-2 p-4 rounded-lg border-2 transition-colors
|
||||
{theme.mode === 'dark'
|
||||
? 'border-[hsl(var(--primary))] bg-[hsl(var(--primary)/0.1)]'
|
||||
: 'border-[hsl(var(--border))]'}"
|
||||
>
|
||||
<Moon class="w-6 h-6 text-indigo-500" />
|
||||
<span class="text-sm font-medium text-foreground">Dark</span>
|
||||
<svg class="w-6 h-6 text-indigo-500" 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>
|
||||
<span class="text-sm font-medium text-[hsl(var(--foreground))]">Dark</span>
|
||||
</button>
|
||||
<button
|
||||
onclick={() => theme.setMode('system')}
|
||||
class="flex flex-col items-center gap-2 p-4 rounded-lg border-2 transition-colors {theme.mode ===
|
||||
'system'
|
||||
? 'border-primary bg-primary/10'
|
||||
: 'border-border'}"
|
||||
onclick={() => setThemeMode('system')}
|
||||
class="flex flex-col items-center gap-2 p-4 rounded-lg border-2 transition-colors
|
||||
{theme.mode === 'system'
|
||||
? 'border-[hsl(var(--primary))] bg-[hsl(var(--primary)/0.1)]'
|
||||
: 'border-[hsl(var(--border))]'}"
|
||||
>
|
||||
<Monitor class="w-6 h-6 text-muted-foreground" />
|
||||
<span class="text-sm font-medium text-foreground">System</span>
|
||||
<svg class="w-6 h-6 text-[hsl(var(--muted-foreground))]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
<span class="text-sm font-medium text-[hsl(var(--foreground))]">System</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SettingsCard>
|
||||
</SettingsSection>
|
||||
|
||||
<!-- Danger Zone -->
|
||||
<div
|
||||
class="bg-surface rounded-xl shadow-sm border border-red-300 dark:border-red-900/50 overflow-hidden"
|
||||
<!-- Danger Zone -->
|
||||
<SettingsDangerZone title="Danger Zone">
|
||||
<SettingsDangerButton
|
||||
label="Sign out"
|
||||
description="Sign out of your account on this device"
|
||||
buttonText="Sign out"
|
||||
onclick={handleLogout}
|
||||
border={false}
|
||||
>
|
||||
<div class="p-4 border-b border-red-300 dark:border-red-900/50 bg-red-50 dark:bg-red-900/20">
|
||||
<h2 class="text-lg font-semibold text-red-700 dark:text-red-400">Danger Zone</h2>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-foreground">Sign out</p>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
Sign out of your account on this device
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onclick={handleLogout}
|
||||
class="flex items-center gap-2 px-4 py-2 bg-red-600 hover:bg-red-700 text-white font-medium rounded-lg transition-colors"
|
||||
>
|
||||
<LogOut class="w-4 h-4" />
|
||||
Sign out
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
</SettingsDangerButton>
|
||||
</SettingsDangerZone>
|
||||
</SettingsPage>
|
||||
|
|
|
|||
|
|
@ -4,15 +4,15 @@
|
|||
import { theme } from '$lib/stores/theme';
|
||||
import { THEME_DEFINITIONS } from '@manacore/shared-theme';
|
||||
import { ThemeColorPreview } from '@manacore/shared-theme-ui';
|
||||
import { Sparkle, Leaf, Hexagon, Waves } from '@manacore/shared-icons';
|
||||
|
||||
// Theme icon mapping
|
||||
const themeIcons = {
|
||||
sparkle: Sparkle,
|
||||
leaf: Leaf,
|
||||
hexagon: Hexagon,
|
||||
waves: Waves,
|
||||
} as const;
|
||||
import {
|
||||
SettingsPage,
|
||||
SettingsSection,
|
||||
SettingsCard,
|
||||
SettingsRow,
|
||||
SettingsToggle,
|
||||
SettingsDangerZone,
|
||||
SettingsDangerButton,
|
||||
} from '@manacore/shared-ui';
|
||||
|
||||
// Settings state
|
||||
let language = $state<'de' | 'en'>('de');
|
||||
|
|
@ -32,7 +32,7 @@
|
|||
localStorage.setItem(key, String(value));
|
||||
}
|
||||
|
||||
function toggleDarkMode() {
|
||||
function toggleDarkMode(value: boolean) {
|
||||
theme.toggleMode();
|
||||
}
|
||||
|
||||
|
|
@ -59,388 +59,191 @@
|
|||
<title>Einstellungen - Zitare</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="settings-page">
|
||||
<div class="header-container">
|
||||
<h1>Einstellungen</h1>
|
||||
</div>
|
||||
|
||||
<SettingsPage title="Einstellungen" subtitle="Passe die App an deine Vorlieben an.">
|
||||
<!-- Personal Section -->
|
||||
<section class="settings-section">
|
||||
<h2 class="section-title">Persönlich</h2>
|
||||
<SettingsSection title="Persönlich">
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
|
||||
<div class="setting-card">
|
||||
<div class="setting-header">
|
||||
<h3>Dein Name</h3>
|
||||
<SettingsCard>
|
||||
<div class="px-5 py-4">
|
||||
<label class="block">
|
||||
<span class="font-medium text-[hsl(var(--foreground))] mb-2 block">Dein Name</span>
|
||||
<input
|
||||
type="text"
|
||||
bind:value={userName}
|
||||
onblur={saveUserName}
|
||||
placeholder="Name eingeben..."
|
||||
class="w-full px-3 py-2 rounded-lg border-2 border-[hsl(var(--border))] bg-[hsl(var(--background))] text-[hsl(var(--foreground))] focus:border-[hsl(var(--primary))] focus:outline-none transition-colors"
|
||||
/>
|
||||
</label>
|
||||
<p class="text-sm text-[hsl(var(--muted-foreground))] mt-2">
|
||||
Wird als Standard-Autor für eigene Zitate verwendet
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
bind:value={userName}
|
||||
onblur={saveUserName}
|
||||
placeholder="Name eingeben..."
|
||||
class="text-input"
|
||||
/>
|
||||
<p class="setting-description">Wird als Standard-Autor für eigene Zitate verwendet</p>
|
||||
</div>
|
||||
</section>
|
||||
</SettingsCard>
|
||||
</SettingsSection>
|
||||
|
||||
<!-- Appearance Section -->
|
||||
<section class="settings-section">
|
||||
<h2 class="section-title">Aussehen</h2>
|
||||
|
||||
<!-- Dark Mode Toggle -->
|
||||
<div class="setting-card">
|
||||
<div class="setting-row">
|
||||
<div class="setting-content">
|
||||
<h3>Dark Mode</h3>
|
||||
<p class="setting-description">Dunkles Farbschema verwenden</p>
|
||||
</div>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" checked={theme.isDark} onchange={toggleDarkMode} />
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Current Theme -->
|
||||
<div class="setting-card">
|
||||
<div class="setting-row">
|
||||
<div class="setting-content">
|
||||
<h3>Aktuelles Theme</h3>
|
||||
<p class="setting-description theme-label">
|
||||
{#if THEME_DEFINITIONS[theme.variant].icon && themeIcons[THEME_DEFINITIONS[theme.variant].icon as keyof typeof themeIcons]}
|
||||
<svelte:component
|
||||
this={themeIcons[THEME_DEFINITIONS[theme.variant].icon as keyof typeof themeIcons]}
|
||||
size={16}
|
||||
weight="duotone"
|
||||
class="theme-icon"
|
||||
/>
|
||||
{/if}
|
||||
{THEME_DEFINITIONS[theme.variant].label}
|
||||
</p>
|
||||
</div>
|
||||
<button class="theme-btn" onclick={() => goto('/themes')}>
|
||||
Themes wählen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Theme Preview -->
|
||||
<div class="setting-card">
|
||||
<div class="setting-header">
|
||||
<h3>Farbvorschau</h3>
|
||||
<p class="setting-description">So sieht die App mit dem aktuellen Theme aus</p>
|
||||
</div>
|
||||
|
||||
<div class="theme-preview">
|
||||
<ThemeColorPreview
|
||||
variant={theme.variant}
|
||||
mode={theme.isDark ? 'dark' : 'light'}
|
||||
size="lg"
|
||||
<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>
|
||||
</div>
|
||||
</section>
|
||||
</SettingsCard>
|
||||
</SettingsSection>
|
||||
|
||||
<!-- Language Section -->
|
||||
<section class="settings-section">
|
||||
<h2 class="section-title">Sprache</h2>
|
||||
<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}
|
||||
|
||||
<div class="setting-card">
|
||||
<div class="setting-row">
|
||||
<div class="setting-content">
|
||||
<h3>Sprache</h3>
|
||||
<p class="setting-description">Sprache der App und Zitate</p>
|
||||
</div>
|
||||
<div class="language-toggle">
|
||||
<button
|
||||
class="lang-btn"
|
||||
class:active={language === 'de'}
|
||||
onclick={() => setLanguageSetting('de')}
|
||||
>
|
||||
DE
|
||||
</button>
|
||||
<button
|
||||
class="lang-btn"
|
||||
class:active={language === 'en'}
|
||||
onclick={() => setLanguageSetting('en')}
|
||||
>
|
||||
EN
|
||||
</button>
|
||||
<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>
|
||||
</div>
|
||||
</section>
|
||||
</SettingsCard>
|
||||
</SettingsSection>
|
||||
|
||||
<!-- About Section -->
|
||||
<section class="settings-section">
|
||||
<h2 class="section-title">Über</h2>
|
||||
<SettingsSection title="Über">
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
|
||||
<div class="setting-card">
|
||||
<div class="setting-row">
|
||||
<span>Version</span>
|
||||
<span class="setting-value">1.0.0</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<SettingsCard>
|
||||
<SettingsRow label="Version" border={false}>
|
||||
<span class="text-[hsl(var(--muted-foreground))]">1.0.0</span>
|
||||
</SettingsRow>
|
||||
</SettingsCard>
|
||||
</SettingsSection>
|
||||
|
||||
<!-- Data Section -->
|
||||
<section class="settings-section">
|
||||
<h2 class="section-title">Daten</h2>
|
||||
|
||||
<div class="setting-card danger">
|
||||
<button class="danger-btn" onclick={resetAllData}>
|
||||
<div>
|
||||
<h3 class="danger-title">Alle Daten zurücksetzen</h3>
|
||||
<p class="setting-description">Löscht Favoriten, Playlists und Einstellungen</p>
|
||||
</div>
|
||||
<span class="danger-icon">🗑️</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.settings-page {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding-bottom: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
.header-container {
|
||||
max-width: 700px;
|
||||
margin: 0 auto var(--spacing-2xl);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
margin: 0;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
max-width: 700px;
|
||||
margin: 0 auto var(--spacing-2xl);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: hsl(var(--muted-foreground));
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.setting-card {
|
||||
background: hsl(var(--card));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--spacing-lg);
|
||||
margin-bottom: var(--spacing-md);
|
||||
transition:
|
||||
transform var(--transition-base),
|
||||
box-shadow var(--transition-base);
|
||||
}
|
||||
|
||||
.setting-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.setting-header {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.setting-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.setting-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
margin: 0 0 var(--spacing-xs) 0;
|
||||
}
|
||||
|
||||
.setting-description {
|
||||
font-size: 0.875rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.setting-description.theme-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
|
||||
.setting-description.theme-label :global(.theme-icon) {
|
||||
color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
.setting-value {
|
||||
color: hsl(var(--muted-foreground));
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
/* Text Input */
|
||||
.text-input {
|
||||
width: 100%;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
border-radius: var(--radius-md);
|
||||
border: 2px solid hsl(var(--border));
|
||||
background: hsl(var(--background));
|
||||
color: hsl(var(--foreground));
|
||||
font-size: 1rem;
|
||||
margin-bottom: var(--spacing-sm);
|
||||
transition: border-color var(--transition-fast);
|
||||
}
|
||||
|
||||
.text-input:focus {
|
||||
outline: none;
|
||||
border-color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
/* Toggle Switch */
|
||||
.toggle {
|
||||
position: relative;
|
||||
width: 51px;
|
||||
height: 31px;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.toggle input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.toggle-slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: hsl(var(--border));
|
||||
transition: var(--transition-base);
|
||||
border-radius: 31px;
|
||||
}
|
||||
|
||||
.toggle-slider:before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
left: 3px;
|
||||
bottom: 3px;
|
||||
background-color: white;
|
||||
transition: var(--transition-base);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.toggle input:checked + .toggle-slider {
|
||||
background-color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
.toggle input:checked + .toggle-slider:before {
|
||||
transform: translateX(20px);
|
||||
}
|
||||
|
||||
/* Theme Button */
|
||||
.theme-btn {
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
border-radius: var(--radius-md);
|
||||
background: hsl(var(--primary));
|
||||
color: hsl(var(--primary-foreground));
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: background var(--transition-fast);
|
||||
}
|
||||
|
||||
.theme-btn:hover {
|
||||
background: hsl(var(--primary) / 0.9);
|
||||
}
|
||||
|
||||
/* Theme Preview */
|
||||
.theme-preview {
|
||||
margin-top: var(--spacing-md);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Language Toggle */
|
||||
.language-toggle {
|
||||
display: flex;
|
||||
border-radius: var(--radius-full);
|
||||
overflow: hidden;
|
||||
background: hsl(var(--card));
|
||||
border: 1px solid hsl(var(--border));
|
||||
}
|
||||
|
||||
.lang-btn {
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: hsl(var(--foreground));
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background var(--transition-fast);
|
||||
}
|
||||
|
||||
.lang-btn.active {
|
||||
background: hsl(var(--primary));
|
||||
color: hsl(var(--primary-foreground));
|
||||
}
|
||||
|
||||
/* Danger Zone */
|
||||
.setting-card.danger {
|
||||
background: hsl(var(--destructive) / 0.1);
|
||||
border-color: hsl(var(--destructive) / 0.2);
|
||||
}
|
||||
|
||||
.danger-btn {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.danger-title {
|
||||
color: hsl(var(--destructive));
|
||||
}
|
||||
|
||||
.danger-icon {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
/* Mobile Responsiveness */
|
||||
@media (max-width: 768px) {
|
||||
.header-container,
|
||||
.settings-section {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<SettingsDangerZone title="Daten">
|
||||
<SettingsDangerButton
|
||||
label="Alle Daten zurücksetzen"
|
||||
description="Löscht Favoriten, Playlists und Einstellungen"
|
||||
buttonText="Zurücksetzen"
|
||||
onclick={resetAllData}
|
||||
border={false}
|
||||
>
|
||||
{#snippet icon()}
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
</SettingsDangerButton>
|
||||
</SettingsDangerZone>
|
||||
</SettingsPage>
|
||||
|
|
|
|||
|
|
@ -47,3 +47,14 @@ export type {
|
|||
PillNavElement,
|
||||
PillNavigationProps,
|
||||
} from './navigation';
|
||||
|
||||
// Settings
|
||||
export {
|
||||
SettingsPage,
|
||||
SettingsSection,
|
||||
SettingsCard,
|
||||
SettingsRow,
|
||||
SettingsToggle,
|
||||
SettingsDangerZone,
|
||||
SettingsDangerButton,
|
||||
} from './settings';
|
||||
|
|
|
|||
107
packages/shared-ui/src/settings/SettingsCard.svelte
Normal file
107
packages/shared-ui/src/settings/SettingsCard.svelte
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
/** Card title (optional) */
|
||||
title?: string;
|
||||
/** Card description (optional) */
|
||||
description?: string;
|
||||
/** Visual variant */
|
||||
variant?: 'default' | 'danger';
|
||||
/** Additional CSS classes */
|
||||
class?: string;
|
||||
/** Content (SettingsRow components) */
|
||||
children: Snippet;
|
||||
}
|
||||
|
||||
let {
|
||||
title,
|
||||
description,
|
||||
variant = 'default',
|
||||
class: className = '',
|
||||
children,
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="settings-card settings-card--{variant} {className}">
|
||||
{#if title || description}
|
||||
<header class="settings-card__header">
|
||||
{#if title}
|
||||
<h3 class="settings-card__title">{title}</h3>
|
||||
{/if}
|
||||
{#if description}
|
||||
<p class="settings-card__description">{description}</p>
|
||||
{/if}
|
||||
</header>
|
||||
{/if}
|
||||
|
||||
<div class="settings-card__content">
|
||||
{@render children()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.settings-card {
|
||||
/* Glass effect */
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 1rem;
|
||||
overflow: hidden;
|
||||
box-shadow:
|
||||
0 4px 6px -1px rgba(0, 0, 0, 0.1),
|
||||
0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
:global(.dark) .settings-card {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
|
||||
.settings-card--danger {
|
||||
border-color: hsl(var(--destructive) / 0.3);
|
||||
background: rgba(239, 68, 68, 0.08);
|
||||
}
|
||||
|
||||
:global(.dark) .settings-card--danger {
|
||||
background: rgba(239, 68, 68, 0.12);
|
||||
border-color: rgba(239, 68, 68, 0.25);
|
||||
}
|
||||
|
||||
.settings-card__header {
|
||||
padding: 1rem 1.25rem;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
:global(.dark) .settings-card__header {
|
||||
border-bottom-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.settings-card--danger .settings-card__header {
|
||||
border-bottom-color: hsl(var(--destructive) / 0.2);
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
}
|
||||
|
||||
.settings-card__title {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.settings-card--danger .settings-card__title {
|
||||
color: hsl(var(--destructive));
|
||||
}
|
||||
|
||||
.settings-card__description {
|
||||
font-size: 0.875rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
margin: 0.25rem 0 0 0;
|
||||
}
|
||||
|
||||
.settings-card__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
180
packages/shared-ui/src/settings/SettingsDangerButton.svelte
Normal file
180
packages/shared-ui/src/settings/SettingsDangerButton.svelte
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
/** Button label */
|
||||
label: string;
|
||||
/** Optional description */
|
||||
description?: string;
|
||||
/** Optional icon (Snippet for flexibility) */
|
||||
icon?: Snippet;
|
||||
/** Click handler */
|
||||
onclick: () => void;
|
||||
/** Button text (default: label) */
|
||||
buttonText?: string;
|
||||
/** Show border at bottom */
|
||||
border?: boolean;
|
||||
/** Disabled state */
|
||||
disabled?: boolean;
|
||||
/** Additional CSS classes */
|
||||
class?: string;
|
||||
}
|
||||
|
||||
let {
|
||||
label,
|
||||
description,
|
||||
icon,
|
||||
onclick,
|
||||
buttonText,
|
||||
border = true,
|
||||
disabled = false,
|
||||
class: className = '',
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="settings-danger-button {border ? 'settings-danger-button--border' : ''} {disabled ? 'settings-danger-button--disabled' : ''} {className}"
|
||||
>
|
||||
<div class="settings-danger-button__content">
|
||||
{#if icon}
|
||||
<span class="settings-danger-button__icon">
|
||||
{@render icon()}
|
||||
</span>
|
||||
{/if}
|
||||
<div class="settings-danger-button__text">
|
||||
<span class="settings-danger-button__label">{label}</span>
|
||||
{#if description}
|
||||
<span class="settings-danger-button__description">{description}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
{onclick}
|
||||
class="settings-danger-button__button"
|
||||
{disabled}
|
||||
>
|
||||
{buttonText || label}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.settings-danger-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
padding: 1rem 1.25rem;
|
||||
}
|
||||
|
||||
.settings-danger-button--border {
|
||||
border-bottom: 1px solid rgba(239, 68, 68, 0.12);
|
||||
}
|
||||
|
||||
:global(.dark) .settings-danger-button--border {
|
||||
border-bottom-color: rgba(239, 68, 68, 0.18);
|
||||
}
|
||||
|
||||
.settings-danger-button--border:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.settings-danger-button--disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.settings-danger-button__content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.settings-danger-button__icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
border-radius: 0.625rem;
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
color: hsl(var(--destructive));
|
||||
}
|
||||
|
||||
:global(.dark) .settings-danger-button__icon {
|
||||
background: rgba(239, 68, 68, 0.15);
|
||||
}
|
||||
|
||||
.settings-danger-button__icon :global(svg) {
|
||||
width: 1.125rem;
|
||||
height: 1.125rem;
|
||||
}
|
||||
|
||||
.settings-danger-button__text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.125rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.settings-danger-button__label {
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
:global(.dark) .settings-danger-button__label {
|
||||
color: #f3f4f6;
|
||||
}
|
||||
|
||||
.settings-danger-button__description {
|
||||
font-size: 0.8125rem;
|
||||
color: #6b7280;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
:global(.dark) .settings-danger-button__description {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.settings-danger-button__button {
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: hsl(var(--destructive));
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
border: 1px solid rgba(239, 68, 68, 0.2);
|
||||
border-radius: 0.5rem;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
:global(.dark) .settings-danger-button__button {
|
||||
background: rgba(239, 68, 68, 0.15);
|
||||
border-color: rgba(239, 68, 68, 0.25);
|
||||
}
|
||||
|
||||
.settings-danger-button__button:hover:not(:disabled) {
|
||||
background: rgba(239, 68, 68, 0.2);
|
||||
border-color: rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
|
||||
:global(.dark) .settings-danger-button__button:hover:not(:disabled) {
|
||||
background: rgba(239, 68, 68, 0.25);
|
||||
border-color: rgba(239, 68, 68, 0.35);
|
||||
}
|
||||
|
||||
.settings-danger-button__button:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.settings-danger-button__button:focus-visible {
|
||||
outline: 2px solid rgba(239, 68, 68, 0.4);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
</style>
|
||||
71
packages/shared-ui/src/settings/SettingsDangerZone.svelte
Normal file
71
packages/shared-ui/src/settings/SettingsDangerZone.svelte
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
/** Section title */
|
||||
title?: string;
|
||||
/** Additional CSS classes */
|
||||
class?: string;
|
||||
/** Content (danger actions) */
|
||||
children: Snippet;
|
||||
}
|
||||
|
||||
let {
|
||||
title = 'Danger Zone',
|
||||
class: className = '',
|
||||
children,
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
<section class="settings-danger-zone {className}">
|
||||
<header class="settings-danger-zone__header">
|
||||
<h2 class="settings-danger-zone__title">{title}</h2>
|
||||
</header>
|
||||
|
||||
<div class="settings-danger-zone__content">
|
||||
{@render children()}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.settings-danger-zone {
|
||||
/* Glass effect with danger tint */
|
||||
background: rgba(239, 68, 68, 0.08);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid rgba(239, 68, 68, 0.2);
|
||||
border-radius: 1rem;
|
||||
overflow: hidden;
|
||||
box-shadow:
|
||||
0 4px 6px -1px rgba(239, 68, 68, 0.1),
|
||||
0 2px 4px -1px rgba(239, 68, 68, 0.06);
|
||||
}
|
||||
|
||||
:global(.dark) .settings-danger-zone {
|
||||
background: rgba(239, 68, 68, 0.12);
|
||||
border-color: rgba(239, 68, 68, 0.25);
|
||||
}
|
||||
|
||||
.settings-danger-zone__header {
|
||||
padding: 1rem 1.25rem;
|
||||
border-bottom: 1px solid rgba(239, 68, 68, 0.15);
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
}
|
||||
|
||||
:global(.dark) .settings-danger-zone__header {
|
||||
border-bottom-color: rgba(239, 68, 68, 0.2);
|
||||
background: rgba(239, 68, 68, 0.15);
|
||||
}
|
||||
|
||||
.settings-danger-zone__title {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--destructive));
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.settings-danger-zone__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
92
packages/shared-ui/src/settings/SettingsPage.svelte
Normal file
92
packages/shared-ui/src/settings/SettingsPage.svelte
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
/** Page title */
|
||||
title: string;
|
||||
/** Optional subtitle/description */
|
||||
subtitle?: string;
|
||||
/** Maximum width of the content */
|
||||
maxWidth?: 'sm' | 'md' | 'lg' | 'xl';
|
||||
/** Additional CSS classes */
|
||||
class?: string;
|
||||
/** Main content */
|
||||
children: Snippet;
|
||||
}
|
||||
|
||||
let {
|
||||
title,
|
||||
subtitle,
|
||||
maxWidth = 'md',
|
||||
class: className = '',
|
||||
children,
|
||||
}: Props = $props();
|
||||
|
||||
const maxWidthClasses = {
|
||||
sm: 'max-w-lg',
|
||||
md: 'max-w-2xl',
|
||||
lg: 'max-w-3xl',
|
||||
xl: 'max-w-4xl',
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="settings-page {className}">
|
||||
<div class="settings-page__container {maxWidthClasses[maxWidth]}">
|
||||
<header class="settings-page__header">
|
||||
<h1 class="settings-page__title">{title}</h1>
|
||||
{#if subtitle}
|
||||
<p class="settings-page__subtitle">{subtitle}</p>
|
||||
{/if}
|
||||
</header>
|
||||
|
||||
<div class="settings-page__content">
|
||||
{@render children()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.settings-page {
|
||||
min-height: calc(100vh - 4rem);
|
||||
padding: 2rem 1rem;
|
||||
background-color: hsl(var(--background));
|
||||
}
|
||||
|
||||
.settings-page__container {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.settings-page__header {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.settings-page__title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: hsl(var(--foreground));
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.settings-page__subtitle {
|
||||
font-size: 0.875rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
margin: 0.25rem 0 0 0;
|
||||
}
|
||||
|
||||
.settings-page__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.settings-page {
|
||||
padding: 2rem 1.5rem;
|
||||
}
|
||||
|
||||
.settings-page__title {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
240
packages/shared-ui/src/settings/SettingsRow.svelte
Normal file
240
packages/shared-ui/src/settings/SettingsRow.svelte
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
/** Row label */
|
||||
label: string;
|
||||
/** Optional description */
|
||||
description?: string;
|
||||
/** Optional icon (Snippet for flexibility) */
|
||||
icon?: Snippet;
|
||||
/** Make the entire row clickable */
|
||||
href?: string;
|
||||
/** Click handler (alternative to href) */
|
||||
onclick?: () => void;
|
||||
/** Show border at bottom */
|
||||
border?: boolean;
|
||||
/** Disabled state */
|
||||
disabled?: boolean;
|
||||
/** Additional CSS classes */
|
||||
class?: string;
|
||||
/** Control element (Toggle, Button, etc.) */
|
||||
children?: Snippet;
|
||||
}
|
||||
|
||||
let {
|
||||
label,
|
||||
description,
|
||||
icon,
|
||||
href,
|
||||
onclick,
|
||||
border = true,
|
||||
disabled = false,
|
||||
class: className = '',
|
||||
children,
|
||||
}: Props = $props();
|
||||
|
||||
const isClickable = $derived(!!href || !!onclick);
|
||||
</script>
|
||||
|
||||
{#if href}
|
||||
<a
|
||||
{href}
|
||||
class="settings-row {border ? 'settings-row--border' : ''} settings-row--clickable {disabled ? 'settings-row--disabled' : ''} {className}"
|
||||
>
|
||||
<div class="settings-row__content">
|
||||
{#if icon}
|
||||
<span class="settings-row__icon">
|
||||
{@render icon()}
|
||||
</span>
|
||||
{/if}
|
||||
<div class="settings-row__text">
|
||||
<span class="settings-row__label">{label}</span>
|
||||
{#if description}
|
||||
<span class="settings-row__description">{description}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-row__control">
|
||||
{#if children}
|
||||
{@render children()}
|
||||
{:else}
|
||||
<svg class="settings-row__chevron" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
{/if}
|
||||
</div>
|
||||
</a>
|
||||
{:else if onclick}
|
||||
<button
|
||||
type="button"
|
||||
{onclick}
|
||||
class="settings-row {border ? 'settings-row--border' : ''} settings-row--clickable {disabled ? 'settings-row--disabled' : ''} {className}"
|
||||
{disabled}
|
||||
>
|
||||
<div class="settings-row__content">
|
||||
{#if icon}
|
||||
<span class="settings-row__icon">
|
||||
{@render icon()}
|
||||
</span>
|
||||
{/if}
|
||||
<div class="settings-row__text">
|
||||
<span class="settings-row__label">{label}</span>
|
||||
{#if description}
|
||||
<span class="settings-row__description">{description}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-row__control">
|
||||
{#if children}
|
||||
{@render children()}
|
||||
{:else}
|
||||
<svg class="settings-row__chevron" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
{:else}
|
||||
<div
|
||||
class="settings-row {border ? 'settings-row--border' : ''} {disabled ? 'settings-row--disabled' : ''} {className}"
|
||||
>
|
||||
<div class="settings-row__content">
|
||||
{#if icon}
|
||||
<span class="settings-row__icon">
|
||||
{@render icon()}
|
||||
</span>
|
||||
{/if}
|
||||
<div class="settings-row__text">
|
||||
<span class="settings-row__label">{label}</span>
|
||||
{#if description}
|
||||
<span class="settings-row__description">{description}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if children}
|
||||
<div class="settings-row__control">
|
||||
{@render children()}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.settings-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
padding: 1rem 1.25rem;
|
||||
background: transparent;
|
||||
border: none;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.settings-row--border {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
:global(.dark) .settings-row--border {
|
||||
border-bottom-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.settings-row--border:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.settings-row--clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.settings-row--clickable:hover {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
:global(.dark) .settings-row--clickable:hover {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
.settings-row--disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.settings-row__content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.settings-row__icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
border-radius: 0.625rem;
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
:global(.dark) .settings-row__icon {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.settings-row__icon :global(svg) {
|
||||
width: 1.125rem;
|
||||
height: 1.125rem;
|
||||
}
|
||||
|
||||
.settings-row__text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.125rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.settings-row__label {
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
:global(.dark) .settings-row__label {
|
||||
color: #f3f4f6;
|
||||
}
|
||||
|
||||
.settings-row__description {
|
||||
font-size: 0.8125rem;
|
||||
color: #6b7280;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
:global(.dark) .settings-row__description {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.settings-row__control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.settings-row__chevron {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
:global(.dark) .settings-row__chevron {
|
||||
color: #6b7280;
|
||||
}
|
||||
</style>
|
||||
91
packages/shared-ui/src/settings/SettingsSection.svelte
Normal file
91
packages/shared-ui/src/settings/SettingsSection.svelte
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
/** Section title */
|
||||
title?: string;
|
||||
/** Optional icon (Snippet for flexibility) */
|
||||
icon?: Snippet;
|
||||
/** Additional CSS classes */
|
||||
class?: string;
|
||||
/** Content (SettingsCard components) */
|
||||
children: Snippet;
|
||||
}
|
||||
|
||||
let {
|
||||
title,
|
||||
icon,
|
||||
class: className = '',
|
||||
children,
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
<section class="settings-section {className}">
|
||||
{#if title}
|
||||
<header class="settings-section__header">
|
||||
{#if icon}
|
||||
<span class="settings-section__icon">
|
||||
{@render icon()}
|
||||
</span>
|
||||
{/if}
|
||||
<h2 class="settings-section__title">{title}</h2>
|
||||
</header>
|
||||
{/if}
|
||||
|
||||
<div class="settings-section__content">
|
||||
{@render children()}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.settings-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.settings-section__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding-left: 0.25rem;
|
||||
}
|
||||
|
||||
.settings-section__icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1.75rem;
|
||||
height: 1.75rem;
|
||||
border-radius: 0.5rem;
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
:global(.dark) .settings-section__icon {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.settings-section__icon :global(svg) {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.settings-section__title {
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
margin: 0;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
:global(.dark) .settings-section__title {
|
||||
color: #f3f4f6;
|
||||
}
|
||||
|
||||
.settings-section__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
202
packages/shared-ui/src/settings/SettingsToggle.svelte
Normal file
202
packages/shared-ui/src/settings/SettingsToggle.svelte
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
/** Row label */
|
||||
label: string;
|
||||
/** Optional description */
|
||||
description?: string;
|
||||
/** Optional icon (Snippet for flexibility) */
|
||||
icon?: Snippet;
|
||||
/** Toggle state */
|
||||
isOn: boolean;
|
||||
/** Toggle handler */
|
||||
onToggle: (value: boolean) => void;
|
||||
/** Show border at bottom */
|
||||
border?: boolean;
|
||||
/** Disabled state */
|
||||
disabled?: boolean;
|
||||
/** Additional CSS classes */
|
||||
class?: string;
|
||||
}
|
||||
|
||||
let {
|
||||
label,
|
||||
description,
|
||||
icon,
|
||||
isOn = false,
|
||||
onToggle,
|
||||
border = true,
|
||||
disabled = false,
|
||||
class: className = '',
|
||||
}: Props = $props();
|
||||
|
||||
function handleToggle() {
|
||||
if (!disabled) {
|
||||
onToggle(!isOn);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="settings-toggle {border ? 'settings-toggle--border' : ''} {disabled ? 'settings-toggle--disabled' : ''} {className}"
|
||||
>
|
||||
<div class="settings-toggle__content">
|
||||
{#if icon}
|
||||
<span class="settings-toggle__icon">
|
||||
{@render icon()}
|
||||
</span>
|
||||
{/if}
|
||||
<div class="settings-toggle__text">
|
||||
<span class="settings-toggle__label">{label}</span>
|
||||
{#if description}
|
||||
<span class="settings-toggle__description">{description}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onclick={handleToggle}
|
||||
class="settings-toggle__switch {isOn ? 'settings-toggle__switch--on' : ''}"
|
||||
role="switch"
|
||||
aria-checked={isOn}
|
||||
aria-label={label}
|
||||
{disabled}
|
||||
>
|
||||
<span class="settings-toggle__thumb"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.settings-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
padding: 1rem 1.25rem;
|
||||
}
|
||||
|
||||
.settings-toggle--border {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
:global(.dark) .settings-toggle--border {
|
||||
border-bottom-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.settings-toggle--border:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.settings-toggle--disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.settings-toggle__content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.settings-toggle__icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
border-radius: 0.625rem;
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
:global(.dark) .settings-toggle__icon {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.settings-toggle__icon :global(svg) {
|
||||
width: 1.125rem;
|
||||
height: 1.125rem;
|
||||
}
|
||||
|
||||
.settings-toggle__text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.125rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.settings-toggle__label {
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
:global(.dark) .settings-toggle__label {
|
||||
color: #f3f4f6;
|
||||
}
|
||||
|
||||
.settings-toggle__description {
|
||||
font-size: 0.8125rem;
|
||||
color: #6b7280;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
:global(.dark) .settings-toggle__description {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
/* Toggle Switch - Glass style */
|
||||
.settings-toggle__switch {
|
||||
position: relative;
|
||||
width: 3rem;
|
||||
height: 1.75rem;
|
||||
border-radius: 9999px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
background: rgba(0, 0, 0, 0.08);
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
:global(.dark) .settings-toggle__switch {
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
border-color: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.settings-toggle__switch:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.settings-toggle__switch--on {
|
||||
background-color: hsl(var(--primary));
|
||||
border-color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
.settings-toggle__thumb {
|
||||
position: absolute;
|
||||
top: 0.0625rem;
|
||||
left: 0.0625rem;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
border-radius: 9999px;
|
||||
background-color: white;
|
||||
box-shadow:
|
||||
0 2px 4px rgba(0, 0, 0, 0.15),
|
||||
0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.settings-toggle__switch--on .settings-toggle__thumb {
|
||||
transform: translateX(1.25rem);
|
||||
}
|
||||
|
||||
.settings-toggle__switch:focus-visible {
|
||||
outline: 2px solid hsl(var(--primary) / 0.4);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
</style>
|
||||
8
packages/shared-ui/src/settings/index.ts
Normal file
8
packages/shared-ui/src/settings/index.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Settings Components
|
||||
export { default as SettingsPage } from './SettingsPage.svelte';
|
||||
export { default as SettingsSection } from './SettingsSection.svelte';
|
||||
export { default as SettingsCard } from './SettingsCard.svelte';
|
||||
export { default as SettingsRow } from './SettingsRow.svelte';
|
||||
export { default as SettingsToggle } from './SettingsToggle.svelte';
|
||||
export { default as SettingsDangerZone } from './SettingsDangerZone.svelte';
|
||||
export { default as SettingsDangerButton } from './SettingsDangerButton.svelte';
|
||||
Loading…
Add table
Add a link
Reference in a new issue