feat(apps): add unified Apps page and PillNavigation to all web apps

- Add AppsPage component to shared-ui for displaying all Mana apps
- Add allAppsHref prop to PillNavigation with "Alle Apps" link in dropdown
- Integrate PillNavigation in archived apps (maerchenzauber, news, uload, wisekeep)
- Add /apps route to all web apps (active and archived)
- Replace custom sidebars/headers with unified PillNavigation

Apps updated:
- Active: chat, manacore, manadeck, picture, presi, zitare
- Archived: maerchenzauber, memoro, news, nutriphi, uload, wisekeep

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Till-JS 2025-12-01 14:48:00 +01:00
parent 1f2d21e005
commit 4e4db4612c
25 changed files with 1354 additions and 538 deletions

View file

@ -5,10 +5,79 @@
import { authStore } from '$lib/stores/auth.svelte';
import { initWebSocket, cleanup, isConnected } from '$lib/stores/jobs';
import type { LayoutData } from './$types';
import { PillNavigation } from '@manacore/shared-ui';
import type { PillNavItem } from '@manacore/shared-ui';
import { getPillAppItems } from '@manacore/shared-branding';
let { children, data }: { children: any; data: LayoutData } = $props();
// App switcher items
const appItems = getPillAppItems('wisekeep');
// User email for dropdown
let userEmail = $derived(authStore.user?.email);
// Navigation items for Wisekeep
const navItems: PillNavItem[] = [
{ href: '/dashboard', label: 'Dashboard', icon: 'home' },
{ href: '/transcribe', label: 'Transcribe', icon: 'mic' },
{ href: '/transcripts', label: 'Transcripts', icon: 'document' },
{ href: '/playlists', label: 'Playlists', icon: 'list' },
{ href: '/settings', label: 'Settings', icon: 'settings' },
];
let isChecking = $state(true);
let isSidebarMode = $state(false);
let isCollapsed = $state(false);
let isDark = $state(false);
// Navigation shortcuts (Ctrl+1-5)
const navRoutes = ['/dashboard', '/transcribe', '/transcripts', '/playlists', '/settings'];
function handleKeydown(event: KeyboardEvent) {
const target = event.target as HTMLElement;
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
return;
}
if ((event.ctrlKey || event.metaKey) && !event.shiftKey && !event.altKey) {
const num = parseInt(event.key);
if (num >= 1 && num <= 5) {
event.preventDefault();
const route = navRoutes[num - 1];
if (route) {
goto(route);
}
}
}
}
function handleModeChange(isSidebar: boolean) {
isSidebarMode = isSidebar;
if (typeof localStorage !== 'undefined') {
localStorage.setItem('wisekeep-nav-sidebar', String(isSidebar));
}
}
function handleCollapsedChange(collapsed: boolean) {
isCollapsed = collapsed;
if (typeof localStorage !== 'undefined') {
localStorage.setItem('wisekeep-nav-collapsed', String(collapsed));
}
}
function handleToggleTheme() {
isDark = !isDark;
document.documentElement.classList.toggle('dark', isDark);
if (typeof localStorage !== 'undefined') {
localStorage.setItem('wisekeep-dark-mode', String(isDark));
}
}
async function handleSignOut() {
await authStore.signOut();
goto('/login');
}
// Check auth on mount and redirect if not authenticated
onMount(async () => {
@ -27,6 +96,23 @@
shouldRedirect = true;
}
// Restore nav mode from localStorage
if (typeof localStorage !== 'undefined') {
const savedSidebar = localStorage.getItem('wisekeep-nav-sidebar');
if (savedSidebar === 'true') {
isSidebarMode = true;
}
const savedCollapsed = localStorage.getItem('wisekeep-nav-collapsed');
if (savedCollapsed === 'true') {
isCollapsed = true;
}
const savedDark = localStorage.getItem('wisekeep-dark-mode');
if (savedDark === 'true') {
isDark = true;
document.documentElement.classList.add('dark');
}
}
// Always set isChecking to false
isChecking = false;
@ -38,87 +124,59 @@
// Return cleanup function
return () => cleanup();
});
async function handleSignOut() {
await authStore.signOut();
goto('/login');
}
</script>
<svelte:window onkeydown={handleKeydown} />
{#if isChecking}
<!-- Loading state while checking auth -->
<div class="min-h-screen bg-gray-50 flex items-center justify-center">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-purple-600"></div>
<div class="flex min-h-screen items-center justify-center">
<div class="text-center">
<div
class="mb-4 inline-block h-12 w-12 animate-spin rounded-full border-4 border-solid border-purple-600 border-r-transparent"
></div>
<p class="text-gray-600 dark:text-gray-400">Laden...</p>
</div>
</div>
{:else}
<div class="min-h-screen flex flex-col">
<header class="bg-white shadow-sm border-b">
<div class="max-w-7xl mx-auto px-4 py-4 flex items-center justify-between">
<a href="/dashboard" class="text-xl font-bold text-purple-600">Wisekeep</a>
<nav class="flex items-center gap-6">
<a
href="/dashboard"
class="transition-colors {$page.url.pathname === '/dashboard'
? 'text-purple-600 font-medium'
: 'text-gray-600 hover:text-gray-900'}"
>
Dashboard
</a>
<a
href="/transcribe"
class="transition-colors {$page.url.pathname === '/transcribe'
? 'text-purple-600 font-medium'
: 'text-gray-600 hover:text-gray-900'}"
>
Transcribe
</a>
<a
href="/transcripts"
class="transition-colors {$page.url.pathname === '/transcripts'
? 'text-purple-600 font-medium'
: 'text-gray-600 hover:text-gray-900'}"
>
Transcripts
</a>
<a
href="/playlists"
class="transition-colors {$page.url.pathname === '/playlists'
? 'text-purple-600 font-medium'
: 'text-gray-600 hover:text-gray-900'}"
>
Playlists
</a>
</nav>
<div class="flex items-center gap-4">
<div class="flex items-center gap-2">
<span class="w-2 h-2 rounded-full {$isConnected ? 'bg-green-500' : 'bg-red-500'}"></span>
<span class="text-sm text-gray-500">
{$isConnected ? 'Connected' : 'Disconnected'}
</span>
</div>
{#if authStore.user}
<span class="text-sm text-gray-600 hidden sm:block">
{authStore.user.email}
</span>
{/if}
<button
onclick={handleSignOut}
class="px-3 py-2 text-sm font-medium text-gray-600 hover:bg-gray-100 rounded-lg transition-colors"
>
Abmelden
</button>
</div>
</div>
</header>
<div class="flex min-h-screen flex-col">
<PillNavigation
items={navItems}
currentPath={$page.url.pathname}
appName="Wisekeep"
homeRoute="/dashboard"
onLogout={handleSignOut}
onToggleTheme={handleToggleTheme}
{isDark}
{isSidebarMode}
onModeChange={handleModeChange}
{isCollapsed}
onCollapsedChange={handleCollapsedChange}
showThemeToggle={true}
primaryColor="#9333ea"
showAppSwitcher={true}
{appItems}
{userEmail}
settingsHref="/settings"
manaHref="/subscription"
profileHref="/profile"
allAppsHref="/apps"
>
{#snippet logo()}
<span class="text-xl">🧠</span>
<span class="pill-label font-bold">Wisekeep</span>
{/snippet}
</PillNavigation>
<main class="flex-1">
{@render children()}
<main
class="main-content flex-1 transition-all duration-300 {isCollapsed
? ''
: isSidebarMode
? 'pl-[180px]'
: 'pt-20'}"
>
<div class="container mx-auto px-4 py-8">
{@render children()}
</div>
</main>
<footer class="bg-gray-100 border-t py-4">
<div class="max-w-7xl mx-auto px-4 text-center text-sm text-gray-500">
Wisekeep - AI-powered wisdom extraction from video
</div>
</footer>
</div>
{/if}

View file

@ -0,0 +1,17 @@
<script lang="ts">
import { AppsPage } from '@manacore/shared-ui';
</script>
<svelte:head>
<title>Alle Apps - Wisekeep</title>
</svelte:head>
<div class="apps-page-wrapper">
<AppsPage currentAppId="wisekeep" locale="de" title="Alle Apps" />
</div>
<style>
.apps-page-wrapper {
min-height: 100%;
}
</style>