mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 21:01:08 +02:00
feat(workbench): section deep-links + migrate profile & themes to workbench
Section deep-links: /?app=settings#ai-options now switches the Settings ListView to the KI tab and scrolls to the ai-options anchor. ListView reads the URL hash on mount and maps it to a category via the existing searchIndex anchors. The AI-tier dropdown "KI-Einstellungen" link now targets /?app=settings#ai-options instead of just /?app=settings. Profile & Themes workbench consolidation — same pattern as Settings: - Delete standalone /profile and /themes routes (redundant with the workbench apps registered in app-registry) - Migrate all links: PillNavigation profileHref/themesHref, dashboard QuickActionsWidget, +layout theme-switcher "Alle Themes", and the scene context-menu "Hintergrund ändern" Credits stays as a standalone route — no workbench app registered for it. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
334c36a68e
commit
0af1dd7ec6
7 changed files with 24 additions and 198 deletions
|
|
@ -22,7 +22,7 @@
|
|||
descKey: 'dashboard.widgets.quick_actions.feedback_desc',
|
||||
},
|
||||
{
|
||||
href: '/profile',
|
||||
href: '/?app=profile',
|
||||
icon: '👤',
|
||||
labelKey: 'dashboard.widgets.quick_actions.profile',
|
||||
descKey: 'dashboard.widgets.quick_actions.profile_desc',
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ export function useAiTierItems() {
|
|||
id: 'ai-settings',
|
||||
label: 'KI-Einstellungen',
|
||||
icon: 'settings',
|
||||
onClick: () => goto('/?app=settings'),
|
||||
onClick: () => goto('/?app=settings#ai-options'),
|
||||
},
|
||||
]);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,15 @@
|
|||
credits, data). Profile and Themes live in their own workbench apps.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { tick } from 'svelte';
|
||||
import { onMount, tick } from 'svelte';
|
||||
import { APP_VERSION } from '$lib/version';
|
||||
import SettingsSidebar from '$lib/components/settings/SettingsSidebar.svelte';
|
||||
import type { CategoryId, SearchEntry } from '$lib/components/settings/searchIndex';
|
||||
import {
|
||||
categories,
|
||||
type CategoryId,
|
||||
type SearchEntry,
|
||||
} from '$lib/components/settings/searchIndex';
|
||||
|
||||
import GeneralSection from '$lib/components/settings/sections/GeneralSection.svelte';
|
||||
import AiSection from '$lib/components/settings/sections/AiSection.svelte';
|
||||
import SecuritySection from '$lib/components/settings/sections/SecuritySection.svelte';
|
||||
|
|
@ -15,6 +20,17 @@
|
|||
|
||||
let activeCategory = $state<CategoryId>('general');
|
||||
|
||||
onMount(() => {
|
||||
const hash = window.location.hash?.slice(1);
|
||||
if (!hash) return;
|
||||
const cat = categories.find((c) => c.anchors.includes(hash));
|
||||
if (cat) activeCategory = cat.id;
|
||||
void tick().then(() => {
|
||||
const el = document.getElementById(hash);
|
||||
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
});
|
||||
});
|
||||
|
||||
function jumpTo(entry: SearchEntry) {
|
||||
activeCategory = entry.category;
|
||||
void tick().then(() => {
|
||||
|
|
|
|||
|
|
@ -166,7 +166,7 @@
|
|||
id: 'all-themes',
|
||||
label: $_('nav.all_themes'),
|
||||
icon: 'palette',
|
||||
onClick: () => goto('/themes'),
|
||||
onClick: () => goto('/?app=themes'),
|
||||
active: false,
|
||||
},
|
||||
]);
|
||||
|
|
@ -975,10 +975,10 @@
|
|||
{appItems}
|
||||
{userEmail}
|
||||
manaHref="/mana"
|
||||
profileHref="/profile"
|
||||
profileHref="/?app=profile"
|
||||
spiralHref="/spiral"
|
||||
creditsHref="/credits"
|
||||
themesHref="/themes"
|
||||
themesHref="/?app=themes"
|
||||
helpHref="/help"
|
||||
allAppsHref="/apps"
|
||||
{spotlightActions}
|
||||
|
|
|
|||
|
|
@ -302,7 +302,7 @@
|
|||
id: 'wallpaper',
|
||||
label: 'Hintergrund ändern',
|
||||
icon: Image,
|
||||
action: () => goto('/themes'),
|
||||
action: () => goto('/?app=themes'),
|
||||
},
|
||||
];
|
||||
if (scenes.length > 1) {
|
||||
|
|
|
|||
|
|
@ -1,162 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { ProfilePage, PageHeader } from '@mana/shared-ui';
|
||||
import type { UserProfile, ProfileActions } from '@mana/shared-ui';
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { profileService, type UserProfile as ApiUserProfile } from '$lib/api/profile';
|
||||
import {
|
||||
EditProfileModal,
|
||||
ChangePasswordModal,
|
||||
DeleteAccountModal,
|
||||
} from '$lib/components/profile';
|
||||
|
||||
// Profile data from API
|
||||
let apiProfile = $state<ApiUserProfile | null>(null);
|
||||
let loading = $state(true);
|
||||
|
||||
// Modal states
|
||||
let showEditModal = $state(false);
|
||||
let showPasswordModal = $state(false);
|
||||
let showDeleteModal = $state(false);
|
||||
|
||||
// Toast notification
|
||||
let toastMessage = $state<string | null>(null);
|
||||
|
||||
onMount(async () => {
|
||||
await loadProfile();
|
||||
});
|
||||
|
||||
async function loadProfile() {
|
||||
try {
|
||||
apiProfile = await profileService.getProfile();
|
||||
} catch (e) {
|
||||
console.error('Failed to load profile:', e);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Map auth store user to UserProfile (use API profile when available)
|
||||
let userProfile = $derived<UserProfile>({
|
||||
id: apiProfile?.id || authStore.user?.id || '',
|
||||
email: apiProfile?.email || authStore.user?.email || '',
|
||||
displayName: apiProfile?.name || undefined,
|
||||
role: apiProfile?.role || authStore.user?.role,
|
||||
createdAt: apiProfile?.createdAt,
|
||||
});
|
||||
|
||||
// Profile actions
|
||||
const actions: ProfileActions = {
|
||||
onEditProfile: () => {
|
||||
showEditModal = true;
|
||||
},
|
||||
onChangePassword: () => {
|
||||
showPasswordModal = true;
|
||||
},
|
||||
onLogout: async () => {
|
||||
await authStore.signOut();
|
||||
goto('/login');
|
||||
},
|
||||
onDeleteAccount: () => {
|
||||
showDeleteModal = true;
|
||||
},
|
||||
};
|
||||
|
||||
function handleProfileUpdate(user: ApiUserProfile) {
|
||||
apiProfile = user;
|
||||
showToast('Profil erfolgreich aktualisiert');
|
||||
}
|
||||
|
||||
function handlePasswordChange() {
|
||||
showToast('Passwort erfolgreich geändert');
|
||||
}
|
||||
|
||||
async function handleAccountDeleted() {
|
||||
showToast('Konto wird gelöscht...');
|
||||
await authStore.signOut();
|
||||
goto('/login');
|
||||
}
|
||||
|
||||
function showToast(message: string) {
|
||||
toastMessage = message;
|
||||
setTimeout(() => {
|
||||
toastMessage = null;
|
||||
}, 3000);
|
||||
}
|
||||
</script>
|
||||
|
||||
<PageHeader title="Profil" backHref="/" sticky />
|
||||
|
||||
{#if loading}
|
||||
<div class="flex items-center justify-center py-12">
|
||||
<div
|
||||
class="h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent"
|
||||
></div>
|
||||
</div>
|
||||
{:else}
|
||||
<ProfilePage
|
||||
user={userProfile}
|
||||
appName="Mana"
|
||||
{actions}
|
||||
pageTitle="Profil"
|
||||
accountInfoTitle="Konto-Informationen"
|
||||
actionsTitle="Aktionen"
|
||||
emailLabel="E-Mail"
|
||||
nameLabel="Name"
|
||||
memberSinceLabel="Mitglied seit"
|
||||
lastLoginLabel="Letzter Login"
|
||||
roleLabel="Rolle"
|
||||
editProfileLabel="Profil bearbeiten"
|
||||
changePasswordLabel="Passwort ändern"
|
||||
logoutLabel="Abmelden"
|
||||
deleteAccountLabel="Konto löschen"
|
||||
deleteAccountWarning="Diese Aktion kann nicht rückgängig gemacht werden."
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<!-- Modals -->
|
||||
<EditProfileModal
|
||||
show={showEditModal}
|
||||
user={apiProfile}
|
||||
onClose={() => (showEditModal = false)}
|
||||
onSuccess={handleProfileUpdate}
|
||||
/>
|
||||
|
||||
<ChangePasswordModal
|
||||
show={showPasswordModal}
|
||||
onClose={() => (showPasswordModal = false)}
|
||||
onSuccess={handlePasswordChange}
|
||||
/>
|
||||
|
||||
<DeleteAccountModal
|
||||
show={showDeleteModal}
|
||||
userEmail={apiProfile?.email || authStore.user?.email || ''}
|
||||
onClose={() => (showDeleteModal = false)}
|
||||
onSuccess={handleAccountDeleted}
|
||||
/>
|
||||
|
||||
<!-- Toast Notification -->
|
||||
{#if toastMessage}
|
||||
<div
|
||||
class="fixed bottom-4 right-4 z-50 px-4 py-3 bg-green-600 text-white rounded-lg shadow-lg animate-fade-in"
|
||||
>
|
||||
{toastMessage}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
.animate-fade-in {
|
||||
animation: fade-in 0.2s ease-out;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { ThemePage } from '@mana/shared-theme-ui';
|
||||
import { PageHeader } from '@mana/shared-ui';
|
||||
import { theme } from '$lib/stores/theme';
|
||||
import { wallpaperStore } from '$lib/stores/wallpaper.svelte';
|
||||
import WallpaperPicker from '$lib/components/wallpaper/WallpaperPicker.svelte';
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Themes | Mana</title>
|
||||
</svelte:head>
|
||||
|
||||
<PageHeader title="Themes" backHref="/" sticky />
|
||||
|
||||
<ThemePage
|
||||
currentVariant={theme.variant}
|
||||
onSelectTheme={(v) => theme.setVariant(v)}
|
||||
showModeSelector={true}
|
||||
currentMode={theme.mode}
|
||||
onModeChange={(m) => theme.setMode(m)}
|
||||
showBackButton={false}
|
||||
transparent={wallpaperStore.hasWallpaper}
|
||||
>
|
||||
<section class="mt-8 pt-8 border-t border-border">
|
||||
<h2 class="text-sm font-medium text-muted-foreground mb-4">Hintergrund</h2>
|
||||
<WallpaperPicker />
|
||||
</section>
|
||||
</ThemePage>
|
||||
Loading…
Add table
Add a link
Reference in a new issue