223 lines
7.5 KiB
HTML
223 lines
7.5 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Login - {{ app_name }}{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="min-h-screen flex items-center justify-center -mt-8">
|
|
<div class="w-full max-w-md">
|
|
<div class="card p-6 md:p-8">
|
|
<h2 class="text-3xl font-bold mb-2 text-center">Sign In</h2>
|
|
<p class="text-center text-slate-400 mb-6">Access your URL lists</p>
|
|
|
|
<div id="errorMsg" class="hidden mb-4 alert alert-error"></div>
|
|
<div id="successMsg" class="hidden mb-4 alert alert-success"></div>
|
|
|
|
<form id="loginForm" class="space-y-4" novalidate>
|
|
<div class="form-group">
|
|
<label for="email" class="form-label">Email Address</label>
|
|
<input
|
|
type="email"
|
|
name="email"
|
|
id="email"
|
|
required
|
|
class="form-input"
|
|
placeholder="you@example.com"
|
|
autocomplete="email"
|
|
maxlength="254"
|
|
>
|
|
<span id="emailError" class="text-xs text-red-400 mt-1 hidden"></span>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="password" class="form-label">Password</label>
|
|
<input
|
|
type="password"
|
|
name="password"
|
|
id="password"
|
|
required
|
|
class="form-input"
|
|
placeholder="••••••••"
|
|
autocomplete="current-password"
|
|
maxlength="128"
|
|
>
|
|
<span id="passwordError" class="text-xs text-red-400 mt-1 hidden"></span>
|
|
</div>
|
|
|
|
<div class="flex items-center">
|
|
<input
|
|
type="checkbox"
|
|
name="remember"
|
|
id="remember"
|
|
class="w-4 h-4 bg-slate-700 border border-slate-600 rounded cursor-pointer"
|
|
>
|
|
<label for="remember" class="ml-2 text-sm text-slate-400 cursor-pointer">
|
|
Remember me
|
|
</label>
|
|
</div>
|
|
|
|
<button
|
|
type="submit"
|
|
class="btn-primary w-full mt-6"
|
|
id="submitBtn"
|
|
>
|
|
Sign In
|
|
</button>
|
|
</form>
|
|
|
|
<div class="mt-6 text-center">
|
|
<p class="text-slate-400">
|
|
Don't have an account?
|
|
<a href="{{ url_for('register') }}" class="text-sky-400 hover:text-sky-300 font-semibold transition-colors">
|
|
Sign up
|
|
</a>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
if (isAuthenticated()) {
|
|
window.location.href = "{{ url_for('dashboard') }}";
|
|
}
|
|
});
|
|
|
|
const loginForm = document.getElementById('loginForm');
|
|
const errorMsg = document.getElementById('errorMsg');
|
|
const successMsg = document.getElementById('successMsg');
|
|
const submitBtn = document.getElementById('submitBtn');
|
|
|
|
function showError(message) {
|
|
errorMsg.textContent = message;
|
|
errorMsg.classList.remove('hidden');
|
|
successMsg.classList.add('hidden');
|
|
}
|
|
|
|
function showSuccess(message) {
|
|
successMsg.textContent = message;
|
|
successMsg.classList.remove('hidden');
|
|
errorMsg.classList.add('hidden');
|
|
}
|
|
|
|
function clearMessages() {
|
|
errorMsg.classList.add('hidden');
|
|
successMsg.classList.add('hidden');
|
|
}
|
|
|
|
function validateEmail(email) {
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
return emailRegex.test(email) && email.length <= 254;
|
|
}
|
|
|
|
function validateForm() {
|
|
clearMessages();
|
|
const email = document.getElementById('email').value.trim();
|
|
const password = document.getElementById('password').value;
|
|
const emailError = document.getElementById('emailError');
|
|
const passwordError = document.getElementById('passwordError');
|
|
|
|
let isValid = true;
|
|
|
|
// Validate email
|
|
if (!email) {
|
|
emailError.textContent = 'Email is required';
|
|
emailError.classList.remove('hidden');
|
|
isValid = false;
|
|
} else if (!validateEmail(email)) {
|
|
emailError.textContent = 'Please enter a valid email address';
|
|
emailError.classList.remove('hidden');
|
|
isValid = false;
|
|
} else {
|
|
emailError.classList.add('hidden');
|
|
}
|
|
|
|
// Validate password
|
|
if (!password) {
|
|
passwordError.textContent = 'Password is required';
|
|
passwordError.classList.remove('hidden');
|
|
isValid = false;
|
|
} else if (password.length < 8) {
|
|
passwordError.textContent = 'Password must be at least 8 characters';
|
|
passwordError.classList.remove('hidden');
|
|
isValid = false;
|
|
} else {
|
|
passwordError.classList.add('hidden');
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
|
|
loginForm.addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
|
|
// Validate form
|
|
if (!validateForm()) {
|
|
showError('Please correct the errors above');
|
|
return;
|
|
}
|
|
|
|
// Get form values
|
|
const email = document.getElementById('email').value.trim().toLowerCase();
|
|
const password = document.getElementById('password').value;
|
|
const remember = document.getElementById('remember').checked;
|
|
|
|
// Disable submit button
|
|
submitBtn.disabled = true;
|
|
submitBtn.textContent = 'Signing in...';
|
|
|
|
try {
|
|
// Use FormData for proper content-type
|
|
const formData = new FormData();
|
|
formData.append('username', email); // FastAPI-users expects 'username'
|
|
formData.append('password', password);
|
|
|
|
const response = await fetch('/api/auth/jwt/login', {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
|
|
// Store token with proper key name
|
|
localStorage.setItem('access_token', data.access_token);
|
|
|
|
// Store remember me preference
|
|
if (remember) {
|
|
localStorage.setItem('rememberMe', 'true');
|
|
} else {
|
|
localStorage.removeItem('rememberMe');
|
|
}
|
|
|
|
showSuccess('Login successful! Redirecting...');
|
|
|
|
// Redirect to dashboard
|
|
setTimeout(() => {
|
|
window.location.href = "{{ url_for('dashboard') }}";
|
|
}, 500);
|
|
} else {
|
|
const error = await response.json();
|
|
showError(error.detail || 'Login failed. Please check your credentials.');
|
|
submitBtn.disabled = false;
|
|
submitBtn.textContent = 'Sign In';
|
|
}
|
|
} catch (error) {
|
|
console.error('Login error:', error);
|
|
showError('An error occurred during login. Please try again.');
|
|
submitBtn.disabled = false;
|
|
submitBtn.textContent = 'Sign In';
|
|
}
|
|
});
|
|
|
|
// Clear inline errors on input
|
|
document.getElementById('email').addEventListener('input', () => {
|
|
document.getElementById('emailError').classList.add('hidden');
|
|
});
|
|
|
|
document.getElementById('password').addEventListener('input', () => {
|
|
document.getElementById('passwordError').classList.add('hidden');
|
|
});
|
|
</script>
|
|
{% endblock %}
|