From af9359bbdf229ecddcbd34b36d8e49a1ed02a16f Mon Sep 17 00:00:00 2001 From: restitux Date: Thu, 16 Apr 2026 02:37:25 +0000 Subject: [PATCH] frontend: add login page and auth guard Add authentication flow to the frontend: - authStore with token management (localStorage persistence) - Login page with username/password form at /login - Layout-level auth guard that redirects to /login when no valid session exists, validates token on load via GET /api/auth/me - Top navigation bar showing username and admin link when applicable Co-Authored-By: Claude Opus 4.6 --- frontend/src/routes/+layout.svelte | 105 ++++++++--- frontend/src/routes/login/+page.svelte | 166 ++++++++++++++++++ .../src/routes/stores/authStore.svelte.ts | 43 +++++ 3 files changed, 288 insertions(+), 26 deletions(-) create mode 100644 frontend/src/routes/login/+page.svelte create mode 100644 frontend/src/routes/stores/authStore.svelte.ts diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte index e499baa..a6c88b2 100644 --- a/frontend/src/routes/+layout.svelte +++ b/frontend/src/routes/+layout.svelte @@ -1,22 +1,67 @@
- + {#if authChecked} + {#if userProfile && page.url.pathname !== '/login'} + + {/if} -
- {@render children()} -
- - +
+ {@render children()} +
+ {/if}
diff --git a/frontend/src/routes/login/+page.svelte b/frontend/src/routes/login/+page.svelte new file mode 100644 index 0000000..76582b7 --- /dev/null +++ b/frontend/src/routes/login/+page.svelte @@ -0,0 +1,166 @@ + + + + Login + + + + + diff --git a/frontend/src/routes/stores/authStore.svelte.ts b/frontend/src/routes/stores/authStore.svelte.ts new file mode 100644 index 0000000..041654c --- /dev/null +++ b/frontend/src/routes/stores/authStore.svelte.ts @@ -0,0 +1,43 @@ +import { goto } from '$app/navigation'; + +interface AuthState { + token: string | null; +} + +function loadToken(): string | null { + if (typeof window === 'undefined') return null; + return localStorage.getItem('auth_token'); +} + +export const authStore: AuthState = $state({ + token: loadToken() +}); + +export function getToken(): string | null { + return authStore.token; +} + +export function setToken(token: string) { + authStore.token = token; + localStorage.setItem('auth_token', token); +} + +export function clearToken() { + authStore.token = null; + localStorage.removeItem('auth_token'); +} + +export function isAuthenticated(): boolean { + return authStore.token !== null; +} + +export function requireAuth() { + if (!isAuthenticated()) { + goto('/login'); + } +} + +export function handleUnauthorized() { + clearToken(); + goto('/login'); +}