91 lines
2.5 KiB
Svelte
91 lines
2.5 KiB
Svelte
<script lang="ts">
|
|
import { goto } from '$app/navigation';
|
|
import { API_URL } from '$lib/api';
|
|
|
|
// Login form state
|
|
let loginUsername = $state('');
|
|
let loginPassword = $state('');
|
|
let loginError: string | null = $state(null);
|
|
let loginLoading = $state(false);
|
|
|
|
async function handleLogin(e: Event) {
|
|
e.preventDefault();
|
|
loginError = null;
|
|
loginLoading = true;
|
|
try {
|
|
const form = new FormData();
|
|
form.append('username', loginUsername);
|
|
form.append('password', loginPassword);
|
|
|
|
const response = await fetch(`${API_URL}/login`, {
|
|
method: 'POST',
|
|
body: form
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const data = await response.json();
|
|
loginError = data.detail || 'Login failed';
|
|
loginLoading = false;
|
|
return;
|
|
}
|
|
|
|
const data = await response.json();
|
|
localStorage.setItem('token', data.access_token);
|
|
goto('/').then(() => location.reload());
|
|
} catch (err) {
|
|
loginError = 'Network error - ' + err;
|
|
} finally {
|
|
loginLoading = false;
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<div class="flex flex-col items-center justify-center min-h-screen bg-gray-50">
|
|
<form
|
|
class="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4 w-full max-w-sm"
|
|
onsubmit={handleLogin}
|
|
>
|
|
<h2 class="text-2xl font-bold mb-6 text-center">Login</h2>
|
|
{#if loginError}
|
|
<div class="mb-4 text-red-600 text-sm">{loginError}</div>
|
|
{/if}
|
|
<div class="mb-4">
|
|
<label class="block text-gray-700 text-sm font-bold mb-2" for="username"> Username </label>
|
|
<input
|
|
id="username"
|
|
type="text"
|
|
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
|
|
bind:value={loginUsername}
|
|
required
|
|
autocomplete="username"
|
|
/>
|
|
</div>
|
|
<div class="mb-6">
|
|
<label class="block text-gray-700 text-sm font-bold mb-2" for="password"> Password </label>
|
|
<input
|
|
id="password"
|
|
type="password"
|
|
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline"
|
|
bind:value={loginPassword}
|
|
required
|
|
autocomplete="current-password"
|
|
/>
|
|
</div>
|
|
<div class="flex items-center justify-between">
|
|
<button
|
|
type="submit"
|
|
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
|
|
disabled={loginLoading}
|
|
>
|
|
{loginLoading ? 'Logging in...' : 'Login'}
|
|
</button>
|
|
<a
|
|
href="/register"
|
|
class="inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800 ml-4"
|
|
>
|
|
Create an account
|
|
</a>
|
|
</div>
|
|
</form>
|
|
</div>
|