Initial commit
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
{% 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 %}
|
||||
Reference in New Issue
Block a user