phase 3.1

This commit is contained in:
Danilo Reyes
2025-11-01 23:33:52 -06:00
parent da4892cc30
commit a95a4c091a
25 changed files with 1214 additions and 27 deletions

View File

@@ -0,0 +1,35 @@
/**
* SvelteKit server hooks for route protection
*/
import type { Handle } from '@sveltejs/kit';
// Protected routes that require authentication
const protectedRoutes = ['/boards', '/library', '/settings'];
export const handle: Handle = async ({ event, resolve }) => {
const { url, cookies } = event;
const pathname = url.pathname;
// Check if route requires authentication
const requiresAuth = protectedRoutes.some(route => pathname.startsWith(route));
if (requiresAuth) {
// Check for auth token in cookies (or you could check localStorage via client-side)
const authToken = cookies.get('auth_token');
if (!authToken) {
// Redirect to login if not authenticated
return new Response(null, {
status: 302,
headers: {
location: `/login?redirect=${encodeURIComponent(pathname)}`
}
});
}
}
const response = await resolve(event);
return response;
};

View File

@@ -0,0 +1,114 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { authApi } from '$lib/api/auth';
import type { ApiError } from '$lib/api/client';
import LoginForm from '$lib/components/auth/LoginForm.svelte';
import { authStore } from '$lib/stores/auth';
import { onMount } from 'svelte';
let error: string = '';
let isLoading = false;
onMount(() => {
// Redirect if already authenticated
authStore.subscribe(state => {
if (state.isAuthenticated) {
goto('/boards');
}
});
});
async function handleLogin(event: CustomEvent<{ email: string; password: string }>) {
const { email, password } = event.detail;
error = '';
isLoading = true;
try {
const response = await authApi.login({ email, password });
authStore.login(response.user, response.access_token);
goto('/boards');
} catch (err) {
const apiError = err as ApiError;
error = apiError.error || 'Login failed. Please try again.';
} finally {
isLoading = false;
}
}
</script>
<div class="login-page">
<div class="login-container">
<h1>Login to Reference Board Viewer</h1>
{#if error}
<div class="error-message" role="alert">
{error}
</div>
{/if}
<LoginForm on:submit={handleLogin} {isLoading} />
<div class="register-link">
<p>Don't have an account? <a href="/register">Register here</a></p>
</div>
</div>
</div>
<style>
.login-page {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.login-container {
background: white;
padding: 2.5rem;
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
width: 100%;
max-width: 420px;
}
h1 {
margin: 0 0 1.5rem 0;
font-size: 1.75rem;
font-weight: 600;
color: #1a202c;
text-align: center;
}
.error-message {
padding: 1rem;
margin-bottom: 1.5rem;
background-color: #fee;
border: 1px solid #fcc;
border-radius: 6px;
color: #c33;
font-size: 0.95rem;
}
.register-link {
margin-top: 1.5rem;
text-align: center;
}
.register-link p {
margin: 0;
color: #666;
font-size: 0.95rem;
}
.register-link a {
color: #667eea;
text-decoration: none;
font-weight: 500;
}
.register-link a:hover {
text-decoration: underline;
}
</style>

View File

@@ -0,0 +1,143 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { authApi } from '$lib/api/auth';
import type { ApiError } from '$lib/api/client';
import RegisterForm from '$lib/components/auth/RegisterForm.svelte';
import { authStore } from '$lib/stores/auth';
import { onMount } from 'svelte';
let error: string = '';
let success: string = '';
let isLoading = false;
onMount(() => {
// Redirect if already authenticated
authStore.subscribe(state => {
if (state.isAuthenticated) {
goto('/boards');
}
});
});
async function handleRegister(event: CustomEvent<{ email: string; password: string }>) {
const { email, password } = event.detail;
error = '';
success = '';
isLoading = true;
try {
await authApi.register({ email, password });
success = 'Registration successful! Redirecting to login...';
// Auto-login after successful registration
setTimeout(async () => {
try {
const response = await authApi.login({ email, password });
authStore.login(response.user, response.access_token);
goto('/boards');
} catch (loginErr) {
// If auto-login fails, just redirect to login page
goto('/login');
}
}, 1500);
} catch (err) {
const apiError = err as ApiError;
error = apiError.error || apiError.detail || 'Registration failed. Please try again.';
} finally {
isLoading = false;
}
}
</script>
<div class="register-page">
<div class="register-container">
<h1>Create Your Account</h1>
{#if error}
<div class="error-message" role="alert">
{error}
</div>
{/if}
{#if success}
<div class="success-message" role="status">
{success}
</div>
{/if}
<RegisterForm on:submit={handleRegister} {isLoading} />
<div class="login-link">
<p>Already have an account? <a href="/login">Login here</a></p>
</div>
</div>
</div>
<style>
.register-page {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.register-container {
background: white;
padding: 2.5rem;
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
width: 100%;
max-width: 420px;
}
h1 {
margin: 0 0 1.5rem 0;
font-size: 1.75rem;
font-weight: 600;
color: #1a202c;
text-align: center;
}
.error-message {
padding: 1rem;
margin-bottom: 1.5rem;
background-color: #fee;
border: 1px solid #fcc;
border-radius: 6px;
color: #c33;
font-size: 0.95rem;
}
.success-message {
padding: 1rem;
margin-bottom: 1.5rem;
background-color: #efe;
border: 1px solid #cfc;
border-radius: 6px;
color: #3c3;
font-size: 0.95rem;
}
.login-link {
margin-top: 1.5rem;
text-align: center;
}
.login-link p {
margin: 0;
color: #666;
font-size: 0.95rem;
}
.login-link a {
color: #667eea;
text-decoration: none;
font-weight: 500;
}
.login-link a:hover {
text-decoration: underline;
}
</style>