mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-23 10:26:43 +02:00
chore: archive 25 standalone web apps, move wisekeep to apps-archived
All standalone SvelteKit web apps have been superseded by the unified ManaCore app (apps/manacore/apps/web). Moved to web-archived/ within each project to preserve history while removing from active workspace. Archived: calc, cards, chat, citycorners, contacts, context, guides, inventar, moodlit, mukke, news, nutriphi, photos, picture, planta, presi, questions, skilltree, storage, times, zitare, todo, calendar, uload, memoro Moved to apps-archived/: wisekeep (not integrated, inactive) Kept active: manacore (unified), matrix, manavoxel, arcade (separate containers) Server, landing, and package directories remain active for each project. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
373976a11b
commit
2eb1a0cd76
1994 changed files with 278 additions and 1310 deletions
13
apps-archived/wisekeep/apps/web/src/app.css
Normal file
13
apps-archived/wisekeep/apps/web/src/app.css
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
--color-primary: theme('colors.primary.600');
|
||||
--color-primary-hover: theme('colors.primary.700');
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-gray-50 text-gray-900 antialiased;
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
}
|
||||
13
apps-archived/wisekeep/apps/web/src/app.d.ts
vendored
Normal file
13
apps-archived/wisekeep/apps/web/src/app.d.ts
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// See https://kit.svelte.dev/docs/types#app
|
||||
// for information about these interfaces
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
12
apps-archived/wisekeep/apps/web/src/app.html
Normal file
12
apps-archived/wisekeep/apps/web/src/app.html
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<script lang="ts">
|
||||
let { size = 48, color = '#8b5cf6' }: { size?: number; color?: string } = $props();
|
||||
</script>
|
||||
|
||||
<svg width={size} height={size} viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="100" height="100" rx="22" fill={color} />
|
||||
<circle cx="50" cy="40" r="12" stroke="white" stroke-width="4" fill="none" />
|
||||
<line x1="50" y1="52" x2="50" y2="72" stroke="white" stroke-width="4" stroke-linecap="round" />
|
||||
<line x1="38" y1="62" x2="50" y2="72" stroke="white" stroke-width="3" stroke-linecap="round" />
|
||||
<line x1="62" y1="62" x2="50" y2="72" stroke="white" stroke-width="3" stroke-linecap="round" />
|
||||
</svg>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import { createManaAuthStore } from '@manacore/shared-auth-stores';
|
||||
|
||||
export const authStore = createManaAuthStore({
|
||||
devBackendPort: 3072,
|
||||
});
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<script lang="ts">
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
{@render children()}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { LoginPage } from '@manacore/shared-auth-ui';
|
||||
import WisekeepLogo from '$lib/components/WisekeepLogo.svelte';
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
</script>
|
||||
|
||||
<LoginPage
|
||||
appName="Wisekeep"
|
||||
logo={WisekeepLogo}
|
||||
primaryColor="#8b5cf6"
|
||||
onSignIn={(email, password) => authStore.signIn(email, password)}
|
||||
onResendVerification={(email) => authStore.resendVerificationEmail(email)}
|
||||
passkeyAvailable={authStore.isPasskeyAvailable()}
|
||||
onSignInWithPasskey={() => authStore.signInWithPasskey()}
|
||||
onVerifyTwoFactor={(code, trust) => authStore.verifyTwoFactor(code, trust)}
|
||||
onVerifyBackupCode={(code) => authStore.verifyBackupCode(code)}
|
||||
onSendMagicLink={(email) => authStore.sendMagicLink(email)}
|
||||
{goto}
|
||||
successRedirect="/transcribe"
|
||||
registerPath="/auth/register"
|
||||
/>
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { RegisterPage } from '@manacore/shared-auth-ui';
|
||||
import WisekeepLogo from '$lib/components/WisekeepLogo.svelte';
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
</script>
|
||||
|
||||
<RegisterPage
|
||||
appName="Wisekeep"
|
||||
logo={WisekeepLogo}
|
||||
primaryColor="#8b5cf6"
|
||||
onSignUp={(email, password) => authStore.signUp(email, password)}
|
||||
{goto}
|
||||
successRedirect="/transcribe"
|
||||
loginPath="/auth/login"
|
||||
/>
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { PillNavigation } from '@manacore/shared-ui';
|
||||
import type { PillNavItem } from '@manacore/shared-ui';
|
||||
import { getPillAppItems, getManaApp } from '@manacore/shared-branding';
|
||||
import { AuthGate, GuestWelcomeModal, SessionExpiredBanner } from '@manacore/shared-auth-ui';
|
||||
import { shouldShowGuestWelcome } from '@manacore/shared-auth-ui';
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { wisekeepStore } from '$lib/data/local-store';
|
||||
|
||||
let { children } = $props();
|
||||
let appItems = $derived(getPillAppItems(undefined, undefined, undefined, authStore.user?.tier));
|
||||
let userEmail = $derived(authStore.isAuthenticated ? (authStore.user?.email ?? '') : '');
|
||||
let showGuestWelcome = $state(false);
|
||||
let isCollapsed = $state(false);
|
||||
let isDark = $state(true);
|
||||
|
||||
const navItems: PillNavItem[] = [
|
||||
{ href: '/transcribe', label: 'Transkribieren', icon: 'mic' },
|
||||
{ href: '/transcripts', label: 'Bibliothek', icon: 'book' },
|
||||
{ href: '/playlists', label: 'Playlists', icon: 'list' },
|
||||
];
|
||||
|
||||
function handleCollapsedChange(collapsed: boolean) {
|
||||
isCollapsed = collapsed;
|
||||
localStorage?.setItem('wisekeep-collapsed', String(collapsed));
|
||||
}
|
||||
|
||||
function handleToggleTheme() {
|
||||
isDark = !isDark;
|
||||
document.documentElement.classList.toggle('dark', isDark);
|
||||
localStorage?.setItem('wisekeep-dark', String(isDark));
|
||||
}
|
||||
|
||||
async function handleLogout() {
|
||||
wisekeepStore.stopSync();
|
||||
await authStore.signOut();
|
||||
goto('/auth/login');
|
||||
}
|
||||
|
||||
function handleAuthReady() {
|
||||
if (authStore.isAuthenticated) wisekeepStore.startSync(() => authStore.getValidToken());
|
||||
if (!authStore.isAuthenticated && shouldShowGuestWelcome('wisekeep')) showGuestWelcome = true;
|
||||
const c = localStorage?.getItem('wisekeep-collapsed');
|
||||
if (c === 'true') isCollapsed = true;
|
||||
const d = localStorage?.getItem('wisekeep-dark');
|
||||
isDark = d !== 'false';
|
||||
document.documentElement.classList.toggle('dark', isDark);
|
||||
}
|
||||
</script>
|
||||
|
||||
<AuthGate
|
||||
{authStore}
|
||||
{goto}
|
||||
allowGuest={true}
|
||||
onReady={handleAuthReady}
|
||||
requiredTier={getManaApp('wisekeep')?.requiredTier}
|
||||
appName={getManaApp('wisekeep')?.name}
|
||||
>
|
||||
<div class="flex min-h-screen flex-col">
|
||||
<PillNavigation
|
||||
items={navItems}
|
||||
currentPath={$page.url.pathname}
|
||||
appName="Wisekeep"
|
||||
homeRoute="/transcribe"
|
||||
onLogout={handleLogout}
|
||||
onToggleTheme={handleToggleTheme}
|
||||
{isDark}
|
||||
{isCollapsed}
|
||||
onCollapsedChange={handleCollapsedChange}
|
||||
showThemeToggle={true}
|
||||
primaryColor="#8b5cf6"
|
||||
showAppSwitcher={true}
|
||||
{appItems}
|
||||
{userEmail}
|
||||
settingsHref="/settings"
|
||||
>
|
||||
{#snippet logo()}
|
||||
<span class="text-xl">🎙️</span>
|
||||
<span class="pill-label font-bold">Wisekeep</span>
|
||||
{/snippet}
|
||||
</PillNavigation>
|
||||
|
||||
<main class="main-content flex-1 transition-all duration-300 {isCollapsed ? '' : 'pt-20'}">
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
{@render children()}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<GuestWelcomeModal
|
||||
appId="wisekeep"
|
||||
visible={showGuestWelcome}
|
||||
onClose={() => (showGuestWelcome = false)}
|
||||
onLogin={() => goto('/auth/login')}
|
||||
onRegister={() => goto('/auth/register')}
|
||||
locale="de"
|
||||
/>
|
||||
{#if authStore.isAuthenticated}
|
||||
<SessionExpiredBanner locale="de" loginHref="/auth/login" />
|
||||
{/if}
|
||||
</AuthGate>
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
<script lang="ts">
|
||||
import { useLiveQuery } from '@manacore/local-store/svelte';
|
||||
import { playlistCollection } from '$lib/data/local-store';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { Trash } from '@manacore/shared-icons';
|
||||
|
||||
const playlists = useLiveQuery(() => playlistCollection.getAll({}, { sortBy: 'order' }));
|
||||
|
||||
let newName = $state('');
|
||||
let newCategory = $state('');
|
||||
|
||||
async function createPlaylist() {
|
||||
if (!newName || !newCategory) return;
|
||||
await playlistCollection.insert({
|
||||
id: crypto.randomUUID(),
|
||||
name: newName,
|
||||
category: newCategory,
|
||||
order: playlists.value?.length ?? 0,
|
||||
});
|
||||
toast.success(`Playlist "${newName}" erstellt`);
|
||||
newName = '';
|
||||
newCategory = '';
|
||||
}
|
||||
|
||||
async function deletePlaylist(id: string, name: string) {
|
||||
if (!confirm(`"${name}" löschen?`)) return;
|
||||
await playlistCollection.delete(id);
|
||||
toast.success('Gelöscht');
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="mx-auto max-w-2xl">
|
||||
<h1 class="mb-6 text-3xl font-bold">Playlists</h1>
|
||||
|
||||
<div class="mb-6 rounded-xl border border-gray-800 bg-gray-900 p-5">
|
||||
<div class="flex gap-3">
|
||||
<input
|
||||
type="text"
|
||||
bind:value={newCategory}
|
||||
placeholder="Kategorie"
|
||||
class="w-1/3 rounded-lg border border-gray-700 bg-gray-800 px-3 py-2 text-sm text-gray-100 placeholder-gray-500"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
bind:value={newName}
|
||||
placeholder="Name"
|
||||
class="flex-1 rounded-lg border border-gray-700 bg-gray-800 px-3 py-2 text-sm text-gray-100 placeholder-gray-500"
|
||||
onkeydown={(e) => e.key === 'Enter' && createPlaylist()}
|
||||
/>
|
||||
<button
|
||||
onclick={createPlaylist}
|
||||
disabled={!newName || !newCategory}
|
||||
class="rounded-lg bg-violet-600 px-4 py-2 text-sm font-medium text-white hover:bg-violet-700 disabled:opacity-50"
|
||||
>Erstellen</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if playlists.loading}
|
||||
<div class="space-y-3">
|
||||
{#each Array(2) as _}
|
||||
<div class="h-16 animate-pulse rounded-xl bg-gray-800"></div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else if !playlists.value?.length}
|
||||
<div class="rounded-xl border-2 border-dashed border-gray-700 p-12 text-center">
|
||||
<p class="text-lg font-medium text-gray-400">Keine Playlists</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="space-y-3">
|
||||
{#each playlists.value as pl (pl.id)}
|
||||
<div
|
||||
class="group flex items-center justify-between rounded-xl border border-gray-800 bg-gray-900 p-4 hover:border-gray-700"
|
||||
>
|
||||
<div>
|
||||
<span class="rounded bg-violet-900 px-2 py-0.5 text-xs text-violet-300"
|
||||
>{pl.category}</span
|
||||
>
|
||||
<span class="ml-2 font-semibold">{pl.name}</span>
|
||||
</div>
|
||||
<button
|
||||
onclick={() => deletePlaylist(pl.id, pl.name)}
|
||||
class="rounded p-1 text-gray-500 opacity-0 hover:bg-gray-800 hover:text-red-400 group-hover:opacity-100"
|
||||
>
|
||||
<Trash size={16} />
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
<script lang="ts">
|
||||
import { transcriptCollection } from '$lib/data/local-store';
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
const SERVER = import.meta.env.PUBLIC_WISEKEEP_SERVER_URL || 'http://localhost:3072';
|
||||
|
||||
let url = $state('');
|
||||
let language = $state('de');
|
||||
let transcribing = $state(false);
|
||||
|
||||
async function transcribe() {
|
||||
if (!url) return;
|
||||
transcribing = true;
|
||||
try {
|
||||
const token = authStore.isAuthenticated ? await authStore.getValidToken() : null;
|
||||
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
||||
if (token) headers['Authorization'] = `Bearer ${token}`;
|
||||
|
||||
const res = await fetch(`${SERVER}/api/v1/transcribe`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({ url, language }),
|
||||
});
|
||||
|
||||
if (!res.ok) throw new Error('Transcription failed');
|
||||
const result = await res.json();
|
||||
|
||||
await transcriptCollection.insert({
|
||||
id: result.id,
|
||||
url,
|
||||
title: result.title,
|
||||
channel: result.channel,
|
||||
duration: result.duration,
|
||||
transcript: result.transcript,
|
||||
language: result.language,
|
||||
model: result.model,
|
||||
status: 'completed',
|
||||
isArchived: false,
|
||||
});
|
||||
|
||||
toast.success(`"${result.title}" transkribiert!`);
|
||||
url = '';
|
||||
} catch (err) {
|
||||
toast.error('Transkription fehlgeschlagen. Ist der Server erreichbar?');
|
||||
}
|
||||
transcribing = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="mx-auto max-w-2xl">
|
||||
<h1 class="mb-6 text-3xl font-bold">Transkribieren</h1>
|
||||
|
||||
<div class="rounded-xl border border-gray-800 bg-gray-900 p-6">
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label for="url" class="mb-1 block text-sm font-medium text-gray-300">YouTube URL</label>
|
||||
<input
|
||||
id="url"
|
||||
type="url"
|
||||
bind:value={url}
|
||||
placeholder="https://www.youtube.com/watch?v=..."
|
||||
class="w-full rounded-lg border border-gray-700 bg-gray-800 px-4 py-3 text-gray-100 placeholder-gray-500 focus:border-violet-500 focus:outline-none"
|
||||
onkeydown={(e) => e.key === 'Enter' && transcribe()}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="lang" class="mb-1 block text-sm font-medium text-gray-300">Sprache</label>
|
||||
<select
|
||||
id="lang"
|
||||
bind:value={language}
|
||||
class="rounded-lg border border-gray-700 bg-gray-800 px-3 py-2 text-sm text-gray-100"
|
||||
>
|
||||
<option value="de">Deutsch</option>
|
||||
<option value="en">English</option>
|
||||
<option value="fr">Français</option>
|
||||
<option value="es">Español</option>
|
||||
</select>
|
||||
</div>
|
||||
<button
|
||||
onclick={transcribe}
|
||||
disabled={!url || transcribing}
|
||||
class="w-full rounded-lg bg-violet-600 py-3 font-medium text-white hover:bg-violet-700 disabled:opacity-50"
|
||||
>
|
||||
{transcribing ? 'Wird transkribiert...' : 'Transkribieren'}
|
||||
</button>
|
||||
</div>
|
||||
{#if transcribing}
|
||||
<div class="mt-4 rounded-lg bg-violet-900/20 p-4 text-center">
|
||||
<div
|
||||
class="mb-2 inline-block h-6 w-6 animate-spin rounded-full border-2 border-violet-500 border-r-transparent"
|
||||
></div>
|
||||
<p class="text-sm text-violet-300">
|
||||
Video wird heruntergeladen und transkribiert... Dies kann einige Minuten dauern.
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
<script lang="ts">
|
||||
import { useLiveQuery } from '@manacore/local-store/svelte';
|
||||
import { transcriptCollection } from '$lib/data/local-store';
|
||||
import type { LocalTranscript } from '$lib/data/local-store';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { CaretDown } from '@manacore/shared-icons';
|
||||
|
||||
const transcripts = useLiveQuery(() => transcriptCollection.getAll({ isArchived: false }));
|
||||
|
||||
let searchQuery = $state('');
|
||||
let expandedId = $state<string | null>(null);
|
||||
|
||||
let filtered = $derived.by(() => {
|
||||
const all = transcripts.value ?? [];
|
||||
if (!searchQuery) return all;
|
||||
const q = searchQuery.toLowerCase();
|
||||
return all.filter(
|
||||
(t) =>
|
||||
t.title.toLowerCase().includes(q) ||
|
||||
t.transcript.toLowerCase().includes(q) ||
|
||||
t.channel?.toLowerCase().includes(q)
|
||||
);
|
||||
});
|
||||
|
||||
async function deleteTranscript(t: LocalTranscript) {
|
||||
if (!confirm(`"${t.title}" löschen?`)) return;
|
||||
await transcriptCollection.delete(t.id);
|
||||
toast.success('Gelöscht');
|
||||
}
|
||||
|
||||
function formatDuration(seconds: number | null | undefined): string {
|
||||
if (!seconds) return '';
|
||||
const m = Math.floor(seconds / 60);
|
||||
const s = seconds % 60;
|
||||
return `${m}:${String(s).padStart(2, '0')}`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="mx-auto max-w-4xl">
|
||||
<div class="mb-6 flex items-center justify-between">
|
||||
<h1 class="text-3xl font-bold">Bibliothek</h1>
|
||||
<span class="text-sm text-gray-500">{filtered.length} Transkripte</span>
|
||||
</div>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
bind:value={searchQuery}
|
||||
placeholder="Transkripte durchsuchen..."
|
||||
class="mb-4 w-full rounded-lg border border-gray-700 bg-gray-800 px-4 py-2 text-sm text-gray-100 placeholder-gray-500 focus:border-violet-500 focus:outline-none"
|
||||
/>
|
||||
|
||||
{#if transcripts.loading}
|
||||
<div class="space-y-3">
|
||||
{#each Array(3) as _}
|
||||
<div class="h-20 animate-pulse rounded-xl bg-gray-800"></div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else if filtered.length === 0}
|
||||
<div class="rounded-xl border-2 border-dashed border-gray-700 p-12 text-center">
|
||||
<p class="text-lg font-medium text-gray-400">Keine Transkripte</p>
|
||||
<p class="mt-1 text-sm text-gray-500">Transkribiere ein Video um zu starten.</p>
|
||||
<a
|
||||
href="/transcribe"
|
||||
class="mt-4 inline-block rounded-lg bg-violet-600 px-4 py-2 text-sm text-white hover:bg-violet-700"
|
||||
>Transkribieren</a
|
||||
>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="space-y-3">
|
||||
{#each filtered as t (t.id)}
|
||||
<div
|
||||
class="rounded-xl border border-gray-800 bg-gray-900 transition-all hover:border-gray-700"
|
||||
>
|
||||
<button
|
||||
onclick={() => (expandedId = expandedId === t.id ? null : t.id)}
|
||||
class="flex w-full items-center justify-between p-4 text-left"
|
||||
>
|
||||
<div class="min-w-0 flex-1">
|
||||
<h3 class="truncate font-semibold text-gray-100">{t.title}</h3>
|
||||
<div class="mt-1 flex items-center gap-3 text-xs text-gray-500">
|
||||
{#if t.channel}<span>{t.channel}</span>{/if}
|
||||
{#if t.duration}<span>{formatDuration(t.duration)}</span>{/if}
|
||||
<span>{t.language.toUpperCase()}</span>
|
||||
</div>
|
||||
</div>
|
||||
<CaretDown
|
||||
size={20}
|
||||
class="shrink-0 text-gray-500 transition-transform {expandedId === t.id
|
||||
? 'rotate-180'
|
||||
: ''}"
|
||||
/>
|
||||
</button>
|
||||
{#if expandedId === t.id}
|
||||
<div class="border-t border-gray-800 p-4">
|
||||
<pre
|
||||
class="max-h-96 overflow-y-auto whitespace-pre-wrap text-sm text-gray-300">{t.transcript}</pre>
|
||||
<div class="mt-3 flex gap-2">
|
||||
<button
|
||||
onclick={() => {
|
||||
navigator.clipboard.writeText(t.transcript);
|
||||
toast.success('Kopiert!');
|
||||
}}
|
||||
class="rounded px-3 py-1 text-sm text-gray-400 hover:bg-gray-800">Kopieren</button
|
||||
>
|
||||
<button
|
||||
onclick={() => deleteTranscript(t)}
|
||||
class="rounded px-3 py-1 text-sm text-red-400 hover:bg-red-900/20">Löschen</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
37
apps-archived/wisekeep/apps/web/src/routes/+layout.svelte
Normal file
37
apps-archived/wisekeep/apps/web/src/routes/+layout.svelte
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<script lang="ts">
|
||||
import '../app.css';
|
||||
import { onMount } from 'svelte';
|
||||
import { Toaster } from 'svelte-sonner';
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { wisekeepStore } from '$lib/data/local-store';
|
||||
|
||||
let { children } = $props();
|
||||
let loading = $state(true);
|
||||
|
||||
onMount(async () => {
|
||||
await authStore.initialize();
|
||||
await wisekeepStore.initialize();
|
||||
loading = false;
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if loading}
|
||||
<div class="flex min-h-screen items-center justify-center bg-gray-950">
|
||||
<div
|
||||
class="inline-block h-10 w-10 animate-spin rounded-full border-4 border-solid border-violet-500 border-r-transparent"
|
||||
></div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="min-h-screen bg-gray-950 text-gray-100">
|
||||
{@render children()}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<Toaster
|
||||
position="bottom-right"
|
||||
expand={false}
|
||||
richColors
|
||||
closeButton
|
||||
duration={4000}
|
||||
visibleToasts={3}
|
||||
/>
|
||||
5
apps-archived/wisekeep/apps/web/src/routes/+page.svelte
Normal file
5
apps-archived/wisekeep/apps/web/src/routes/+page.svelte
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { onMount } from 'svelte';
|
||||
onMount(() => goto('/transcribe'));
|
||||
</script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue