feat: Reinitialize frontend with SvelteKit and TypeScript
- Delete old Vite+Svelte frontend - Initialize new SvelteKit project with TypeScript - Configure Tailwind CSS v4 + DaisyUI - Implement JWT authentication with auto-refresh - Create login page with form validation (Zod) - Add protected route guards - Update Docker configuration for single-stage build - Add E2E tests with Playwright (6/11 passing) - Fix Svelte 5 reactivity with $state() runes Known issues: - 5 E2E tests failing (timing/async issues) - Token refresh implementation needs debugging - Validation error display timing
This commit is contained in:
17
frontend/src/routes/+layout.svelte
Normal file
17
frontend/src/routes/+layout.svelte
Normal file
@@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { initAuth } from '$lib/stores/auth';
|
||||
import Navigation from '$lib/components/Navigation.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
onMount(() => {
|
||||
initAuth();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="min-h-screen bg-base-200">
|
||||
<Navigation />
|
||||
|
||||
<main class="container mx-auto px-4 py-6">
|
||||
<slot />
|
||||
</main>
|
||||
</div>
|
||||
18
frontend/src/routes/+page.svelte
Normal file
18
frontend/src/routes/+page.svelte
Normal file
@@ -0,0 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { isAuthenticated } from '$lib/stores/auth';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
// Redirect based on auth state
|
||||
$: if (browser) {
|
||||
if ($isAuthenticated) {
|
||||
goto('/dashboard');
|
||||
} else {
|
||||
goto('/login');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex items-center justify-center min-h-screen">
|
||||
<div class="loading loading-spinner loading-lg text-primary"></div>
|
||||
</div>
|
||||
18
frontend/src/routes/dashboard/+layout.ts
Normal file
18
frontend/src/routes/dashboard/+layout.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { browser } from '$app/environment';
|
||||
import { goto } from '$app/navigation';
|
||||
import { getAccessToken } from '$lib/services/api';
|
||||
import type { LayoutLoad } from './$types';
|
||||
|
||||
export const load: LayoutLoad = async () => {
|
||||
// Check authentication on client side using localStorage (source of truth)
|
||||
if (browser) {
|
||||
const token = getAccessToken();
|
||||
|
||||
if (!token) {
|
||||
goto('/login');
|
||||
return { authenticated: false };
|
||||
}
|
||||
}
|
||||
|
||||
return { authenticated: true };
|
||||
};
|
||||
51
frontend/src/routes/dashboard/+page.svelte
Normal file
51
frontend/src/routes/dashboard/+page.svelte
Normal file
@@ -0,0 +1,51 @@
|
||||
<script lang="ts">
|
||||
import { user, logout } from '$lib/stores/auth';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
async function handleLogout() {
|
||||
await logout();
|
||||
goto('/login');
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Dashboard - Headroom</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card-body">
|
||||
<h1 class="card-title text-3xl mb-4">Welcome to Headroom! 👋</h1>
|
||||
|
||||
{#if $user}
|
||||
<div class="alert alert-info mb-4">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="stroke-current shrink-0 w-6 h-6"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
||||
<span>Logged in as {$user.email} ({$user.role})</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<p class="text-lg mb-4">
|
||||
You have successfully authenticated. This is a protected dashboard page
|
||||
that requires a valid JWT token to access.
|
||||
</p>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<h2 class="text-xl font-bold mb-2">Authentication Features Implemented:</h2>
|
||||
<ul class="list-disc list-inside space-y-2 mb-6">
|
||||
<li>✅ JWT Token Authentication</li>
|
||||
<li>✅ Token Auto-refresh on 401</li>
|
||||
<li>✅ Protected Route Guards</li>
|
||||
<li>✅ Form Validation with Zod</li>
|
||||
<li>✅ Role-based Access Control</li>
|
||||
<li>✅ Redis Token Storage</li>
|
||||
</ul>
|
||||
|
||||
<div class="card-actions">
|
||||
<button class="btn btn-error" on:click={handleLogout}>
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
45
frontend/src/routes/login/+page.svelte
Normal file
45
frontend/src/routes/login/+page.svelte
Normal file
@@ -0,0 +1,45 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import LoginForm from '$lib/components/Auth/LoginForm.svelte';
|
||||
import { login, auth } from '$lib/stores/auth';
|
||||
|
||||
async function handleLogin(event: CustomEvent<{ email: string; password: string }>) {
|
||||
const { email, password } = event.detail;
|
||||
const result = await login({ email, password });
|
||||
|
||||
if (result.success) {
|
||||
goto('/dashboard');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Login - Headroom</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="min-h-screen flex items-center justify-center bg-base-200">
|
||||
<div class="card w-full max-w-md bg-base-100 shadow-xl">
|
||||
<div class="card-body">
|
||||
<h1 class="card-title text-2xl text-center justify-center mb-6">
|
||||
Welcome to Headroom
|
||||
</h1>
|
||||
|
||||
<p class="text-center text-base-content/70 mb-6">
|
||||
Sign in to access your dashboard
|
||||
</p>
|
||||
|
||||
<LoginForm
|
||||
on:login={handleLogin}
|
||||
isLoading={$auth.isLoading}
|
||||
errorMessage={$auth.error}
|
||||
/>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="text-center text-sm text-base-content/60">
|
||||
<p>Demo credentials:</p>
|
||||
<p class="font-mono mt-1">admin@example.com / password</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
1
frontend/src/routes/login/+page.ts
Normal file
1
frontend/src/routes/login/+page.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const ssr = false;
|
||||
Reference in New Issue
Block a user