Added custom error pages, user password change, initial setup workflow with default admin user.
This commit is contained in:
222
templates/login.html
Normal file
222
templates/login.html
Normal file
@@ -0,0 +1,222 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Login - DosVault</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<style>
|
||||
:root {
|
||||
/* Default Dark Theme */
|
||||
--primary-bg: #111827;
|
||||
--secondary-bg: #1f2937;
|
||||
--tertiary-bg: #374151;
|
||||
--accent-bg: #2563eb;
|
||||
--accent-hover: #1d4ed8;
|
||||
--text-primary: #ffffff;
|
||||
--text-secondary: #9ca3af;
|
||||
--text-accent: #60a5fa;
|
||||
--border-color: #4b5563;
|
||||
--success-color: #059669;
|
||||
--warning-color: #d97706;
|
||||
--danger-color: #dc2626;
|
||||
--gradient-from: #1f2937;
|
||||
--gradient-to: #111827;
|
||||
}
|
||||
|
||||
/* Apply CSS custom properties */
|
||||
body {
|
||||
background-color: var(--primary-bg);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.bg-primary { background-color: var(--primary-bg); }
|
||||
.bg-secondary { background-color: var(--secondary-bg); }
|
||||
.bg-tertiary { background-color: var(--tertiary-bg); }
|
||||
.bg-accent { background-color: var(--accent-bg); }
|
||||
.bg-accent:hover { background-color: var(--accent-hover); }
|
||||
.bg-accent-hover { background-color: var(--accent-hover); }
|
||||
.text-primary { color: var(--text-primary); }
|
||||
.text-secondary { color: var(--text-secondary); }
|
||||
.text-accent { color: var(--text-accent); }
|
||||
.border-theme { border-color: var(--border-color); }
|
||||
.bg-gradient-theme { background: linear-gradient(to bottom right, var(--gradient-from), var(--gradient-to)); }
|
||||
|
||||
/* Fade-in animation */
|
||||
.animate-fade-in {
|
||||
animation: fadeIn 0.5s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-primary text-primary min-h-screen">
|
||||
<div class="min-h-screen flex items-center justify-center bg-gradient-theme py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div class="max-w-md w-full space-y-8 animate-fade-in">
|
||||
<div>
|
||||
<!-- DosVault Logo -->
|
||||
<div class="mx-auto h-24 w-24 flex items-center justify-center">
|
||||
<svg class="w-20 h-20 text-accent" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Vault door -->
|
||||
<circle cx="16" cy="16" r="14" fill="currentColor" opacity="0.1"/>
|
||||
<circle cx="16" cy="16" r="12" stroke="currentColor" stroke-width="2" fill="none"/>
|
||||
<circle cx="16" cy="16" r="8" stroke="currentColor" stroke-width="2" fill="none"/>
|
||||
<circle cx="16" cy="16" r="4" fill="currentColor" opacity="0.3"/>
|
||||
|
||||
<!-- DOS-style pixels -->
|
||||
<rect x="6" y="6" width="2" height="2" fill="currentColor" opacity="0.6"/>
|
||||
<rect x="10" y="6" width="2" height="2" fill="currentColor" opacity="0.8"/>
|
||||
<rect x="20" y="6" width="2" height="2" fill="currentColor" opacity="0.8"/>
|
||||
<rect x="24" y="6" width="2" height="2" fill="currentColor" opacity="0.6"/>
|
||||
<rect x="6" y="24" width="2" height="2" fill="currentColor" opacity="0.6"/>
|
||||
<rect x="24" y="24" width="2" height="2" fill="currentColor" opacity="0.6"/>
|
||||
|
||||
<!-- Handle -->
|
||||
<rect x="20" y="14" width="6" height="4" rx="2" fill="currentColor" opacity="0.7"/>
|
||||
<circle cx="24" cy="16" r="1" fill="currentColor"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h1 class="mt-6 text-center text-3xl font-extrabold text-primary">
|
||||
Welcome to DosVault
|
||||
</h1>
|
||||
<p class="mt-2 text-center text-sm text-secondary">
|
||||
Sign in to access your DOS game collection
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Login Form -->
|
||||
<form class="mt-8 space-y-6" onsubmit="handleLogin(event)">
|
||||
<div class="rounded-md shadow-sm -space-y-px">
|
||||
<div>
|
||||
<label for="username" class="sr-only">Username</label>
|
||||
<input id="username" name="username" type="text" required
|
||||
class="appearance-none rounded-none relative block w-full px-3 py-2 bg-secondary border border-theme border-b-0 rounded-t-md placeholder-secondary text-primary focus:outline-none focus:ring-accent focus:border-accent focus:z-10 sm:text-sm"
|
||||
placeholder="Username">
|
||||
</div>
|
||||
<div>
|
||||
<label for="password" class="sr-only">Password</label>
|
||||
<input id="password" name="password" type="password" required
|
||||
class="appearance-none rounded-none relative block w-full px-3 py-2 bg-secondary border border-theme rounded-b-md placeholder-secondary text-primary focus:outline-none focus:ring-accent focus:border-accent focus:z-10 sm:text-sm"
|
||||
placeholder="Password">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="login-status" class="text-sm text-center hidden"></div>
|
||||
|
||||
<div>
|
||||
<button type="submit" id="login-btn"
|
||||
class="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-accent hover:bg-accent-hover focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-accent transition-colors">
|
||||
<span class="absolute left-0 inset-y-0 flex items-center pl-3">
|
||||
<svg class="h-5 w-5 text-accent-hover group-hover:text-white" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</span>
|
||||
<span class="login-text">Sign in</span>
|
||||
<span class="login-loading hidden">Signing in...</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<a href="/" class="text-accent hover:text-accent-hover text-sm transition-colors">
|
||||
← Back to game library
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Demo Mode Notice -->
|
||||
<div class="mt-8 p-4 bg-tertiary border border-theme rounded-md">
|
||||
<div class="flex items-center">
|
||||
<svg class="w-5 h-5 text-warning-color mr-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
<div>
|
||||
<h3 class="text-sm font-medium text-primary">Demo Mode Available</h3>
|
||||
<p class="text-xs text-secondary mt-1">You can browse the game library without logging in, but downloads require authentication.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Check if there's an error message in URL params
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const errorMessage = urlParams.get('error');
|
||||
const redirectUrl = urlParams.get('redirect') || '/';
|
||||
|
||||
if (errorMessage) {
|
||||
const statusDiv = document.getElementById('login-status');
|
||||
statusDiv.textContent = decodeURIComponent(errorMessage);
|
||||
statusDiv.className = 'text-sm text-center text-red-400';
|
||||
statusDiv.classList.remove('hidden');
|
||||
}
|
||||
|
||||
async function handleLogin(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const username = document.getElementById('username').value;
|
||||
const password = document.getElementById('password').value;
|
||||
const btn = document.getElementById('login-btn');
|
||||
const status = document.getElementById('login-status');
|
||||
const loginText = btn.querySelector('.login-text');
|
||||
const loginLoading = btn.querySelector('.login-loading');
|
||||
|
||||
// Disable form and show loading
|
||||
btn.disabled = true;
|
||||
loginText.classList.add('hidden');
|
||||
loginLoading.classList.remove('hidden');
|
||||
status.textContent = '';
|
||||
status.classList.add('hidden');
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('username', username);
|
||||
formData.append('password', password);
|
||||
|
||||
try {
|
||||
const response = await fetch('/login', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
localStorage.setItem('authToken', data.access_token);
|
||||
|
||||
// Show success message briefly
|
||||
status.textContent = 'Login successful! Redirecting...';
|
||||
status.className = 'text-sm text-center text-green-400';
|
||||
status.classList.remove('hidden');
|
||||
|
||||
// Redirect after a short delay
|
||||
setTimeout(() => {
|
||||
window.location.href = redirectUrl;
|
||||
}, 1000);
|
||||
} else {
|
||||
const errorData = await response.json();
|
||||
status.textContent = errorData.detail || 'Login failed. Please check your credentials.';
|
||||
status.className = 'text-sm text-center text-red-400';
|
||||
status.classList.remove('hidden');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
status.textContent = 'Network error. Please try again.';
|
||||
status.className = 'text-sm text-center text-red-400';
|
||||
status.classList.remove('hidden');
|
||||
} finally {
|
||||
// Re-enable form
|
||||
btn.disabled = false;
|
||||
loginText.classList.remove('hidden');
|
||||
loginLoading.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user