Add SvelteKit frontend with Tailwind CSS (Phase 4)
Some checks failed
Validate / Hassfest (push) Has been cancelled
Some checks failed
Validate / Hassfest (push) Has been cancelled
Build a modern, calm web UI using SvelteKit 5 + Tailwind CSS v4. Pages: - Setup wizard (first-run admin account creation) - Login with JWT token management and auto-refresh - Dashboard with stats cards and recent events timeline - Servers: add/delete Immich server connections with validation - Trackers: create album trackers with album picker, event type selection, target assignment, and scan interval config - Templates: Jinja2 message template editor with live preview - Targets: Telegram and webhook notification targets with test - Users: admin-only user management (create/delete) Architecture: - Reactive auth state with Svelte 5 runes - API client with JWT auth, auto-refresh on 401 - Static adapter builds to 153KB for embedding in FastAPI - Vite proxy config for dev server -> backend API - Sidebar layout with navigation and user info Also adds Rule 2 to primary plan: perform detailed code review after completing each phase. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
90
frontend/src/routes/+layout.svelte
Normal file
90
frontend/src/routes/+layout.svelte
Normal file
@@ -0,0 +1,90 @@
|
||||
<script lang="ts">
|
||||
import '../app.css';
|
||||
import { page } from '$app/state';
|
||||
import { goto } from '$app/navigation';
|
||||
import { onMount } from 'svelte';
|
||||
import { getAuth, loadUser, logout } from '$lib/auth.svelte';
|
||||
|
||||
let { children } = $props();
|
||||
const auth = getAuth();
|
||||
|
||||
const navItems = [
|
||||
{ href: '/', label: 'Dashboard', icon: '⊞' },
|
||||
{ href: '/servers', label: 'Servers', icon: '⬡' },
|
||||
{ href: '/trackers', label: 'Trackers', icon: '◎' },
|
||||
{ href: '/templates', label: 'Templates', icon: '⎘' },
|
||||
{ href: '/targets', label: 'Targets', icon: '◇' },
|
||||
];
|
||||
|
||||
const isAuthPage = $derived(
|
||||
page.url.pathname === '/login' || page.url.pathname === '/setup'
|
||||
);
|
||||
|
||||
onMount(async () => {
|
||||
await loadUser();
|
||||
if (!auth.user && !isAuthPage) {
|
||||
goto('/login');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if isAuthPage || auth.loading}
|
||||
{@render children()}
|
||||
{:else if auth.user}
|
||||
<div class="flex h-screen">
|
||||
<!-- Sidebar -->
|
||||
<aside class="w-56 border-r border-[var(--color-border)] bg-[var(--color-card)] flex flex-col">
|
||||
<div class="p-4 border-b border-[var(--color-border)]">
|
||||
<h1 class="text-base font-semibold tracking-tight">Immich Watcher</h1>
|
||||
<p class="text-xs text-[var(--color-muted-foreground)] mt-0.5">Album notifications</p>
|
||||
</div>
|
||||
<nav class="flex-1 p-2 space-y-0.5">
|
||||
{#each navItems as item}
|
||||
<a
|
||||
href={item.href}
|
||||
class="flex items-center gap-2 px-3 py-2 rounded-md text-sm transition-colors
|
||||
{page.url.pathname === item.href
|
||||
? 'bg-[var(--color-accent)] text-[var(--color-accent-foreground)] font-medium'
|
||||
: 'text-[var(--color-muted-foreground)] hover:bg-[var(--color-accent)] hover:text-[var(--color-accent-foreground)]'}"
|
||||
>
|
||||
<span class="text-base">{item.icon}</span>
|
||||
{item.label}
|
||||
</a>
|
||||
{/each}
|
||||
{#if auth.isAdmin}
|
||||
<a
|
||||
href="/users"
|
||||
class="flex items-center gap-2 px-3 py-2 rounded-md text-sm transition-colors
|
||||
{page.url.pathname === '/users'
|
||||
? 'bg-[var(--color-accent)] text-[var(--color-accent-foreground)] font-medium'
|
||||
: 'text-[var(--color-muted-foreground)] hover:bg-[var(--color-accent)] hover:text-[var(--color-accent-foreground)]'}"
|
||||
>
|
||||
<span class="text-base">⊕</span>
|
||||
Users
|
||||
</a>
|
||||
{/if}
|
||||
</nav>
|
||||
<div class="p-3 border-t border-[var(--color-border)]">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-medium">{auth.user.username}</p>
|
||||
<p class="text-xs text-[var(--color-muted-foreground)]">{auth.user.role}</p>
|
||||
</div>
|
||||
<button
|
||||
onclick={logout}
|
||||
class="text-xs text-[var(--color-muted-foreground)] hover:text-[var(--color-foreground)] transition-colors"
|
||||
>
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main content -->
|
||||
<main class="flex-1 overflow-auto">
|
||||
<div class="max-w-5xl mx-auto p-6">
|
||||
{@render children()}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
{/if}
|
||||
Reference in New Issue
Block a user