352 lines
10 KiB
HTML
352 lines
10 KiB
HTML
|
|
<!DOCTYPE html>
|
||
|
|
<html lang="en">
|
||
|
|
<head>
|
||
|
|
<meta charset="UTF-8">
|
||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
|
<title>Login - General Bots</title>
|
||
|
|
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
|
||
|
|
<style>
|
||
|
|
:root {
|
||
|
|
--primary: #3b82f6;
|
||
|
|
--primary-hover: #2563eb;
|
||
|
|
--bg: #0f172a;
|
||
|
|
--surface: #1e293b;
|
||
|
|
--border: #334155;
|
||
|
|
--text: #f8fafc;
|
||
|
|
--text-secondary: #94a3b8;
|
||
|
|
--error: #ef4444;
|
||
|
|
--success: #22c55e;
|
||
|
|
}
|
||
|
|
|
||
|
|
* {
|
||
|
|
margin: 0;
|
||
|
|
padding: 0;
|
||
|
|
box-sizing: border-box;
|
||
|
|
}
|
||
|
|
|
||
|
|
body {
|
||
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||
|
|
background: var(--bg);
|
||
|
|
color: var(--text);
|
||
|
|
min-height: 100vh;
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: center;
|
||
|
|
}
|
||
|
|
|
||
|
|
.login-container {
|
||
|
|
width: 100%;
|
||
|
|
max-width: 400px;
|
||
|
|
padding: 2rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.login-header {
|
||
|
|
text-align: center;
|
||
|
|
margin-bottom: 2rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.login-logo {
|
||
|
|
width: 64px;
|
||
|
|
height: 64px;
|
||
|
|
margin: 0 auto 1rem;
|
||
|
|
background: var(--primary);
|
||
|
|
border-radius: 16px;
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: center;
|
||
|
|
font-size: 2rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.login-title {
|
||
|
|
font-size: 1.5rem;
|
||
|
|
font-weight: 600;
|
||
|
|
margin-bottom: 0.5rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.login-subtitle {
|
||
|
|
color: var(--text-secondary);
|
||
|
|
font-size: 0.875rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.login-form {
|
||
|
|
background: var(--surface);
|
||
|
|
border: 1px solid var(--border);
|
||
|
|
border-radius: 12px;
|
||
|
|
padding: 1.5rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.form-group {
|
||
|
|
margin-bottom: 1rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.form-label {
|
||
|
|
display: block;
|
||
|
|
font-size: 0.875rem;
|
||
|
|
font-weight: 500;
|
||
|
|
margin-bottom: 0.5rem;
|
||
|
|
color: var(--text-secondary);
|
||
|
|
}
|
||
|
|
|
||
|
|
.form-input {
|
||
|
|
width: 100%;
|
||
|
|
padding: 0.75rem 1rem;
|
||
|
|
background: var(--bg);
|
||
|
|
border: 1px solid var(--border);
|
||
|
|
border-radius: 8px;
|
||
|
|
color: var(--text);
|
||
|
|
font-size: 1rem;
|
||
|
|
transition: border-color 0.2s, box-shadow 0.2s;
|
||
|
|
}
|
||
|
|
|
||
|
|
.form-input:focus {
|
||
|
|
outline: none;
|
||
|
|
border-color: var(--primary);
|
||
|
|
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
|
||
|
|
}
|
||
|
|
|
||
|
|
.form-input::placeholder {
|
||
|
|
color: var(--text-secondary);
|
||
|
|
}
|
||
|
|
|
||
|
|
.form-checkbox {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 0.5rem;
|
||
|
|
font-size: 0.875rem;
|
||
|
|
color: var(--text-secondary);
|
||
|
|
}
|
||
|
|
|
||
|
|
.form-checkbox input {
|
||
|
|
width: 16px;
|
||
|
|
height: 16px;
|
||
|
|
accent-color: var(--primary);
|
||
|
|
}
|
||
|
|
|
||
|
|
.login-btn {
|
||
|
|
width: 100%;
|
||
|
|
padding: 0.75rem 1rem;
|
||
|
|
background: var(--primary);
|
||
|
|
color: white;
|
||
|
|
border: none;
|
||
|
|
border-radius: 8px;
|
||
|
|
font-size: 1rem;
|
||
|
|
font-weight: 500;
|
||
|
|
cursor: pointer;
|
||
|
|
transition: background 0.2s;
|
||
|
|
margin-top: 1rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.login-btn:hover {
|
||
|
|
background: var(--primary-hover);
|
||
|
|
}
|
||
|
|
|
||
|
|
.login-btn:disabled {
|
||
|
|
opacity: 0.6;
|
||
|
|
cursor: not-allowed;
|
||
|
|
}
|
||
|
|
|
||
|
|
.login-footer {
|
||
|
|
text-align: center;
|
||
|
|
margin-top: 1.5rem;
|
||
|
|
font-size: 0.875rem;
|
||
|
|
color: var(--text-secondary);
|
||
|
|
}
|
||
|
|
|
||
|
|
.login-footer a {
|
||
|
|
color: var(--primary);
|
||
|
|
text-decoration: none;
|
||
|
|
}
|
||
|
|
|
||
|
|
.login-footer a:hover {
|
||
|
|
text-decoration: underline;
|
||
|
|
}
|
||
|
|
|
||
|
|
.error-message {
|
||
|
|
background: rgba(239, 68, 68, 0.1);
|
||
|
|
border: 1px solid var(--error);
|
||
|
|
color: var(--error);
|
||
|
|
padding: 0.75rem 1rem;
|
||
|
|
border-radius: 8px;
|
||
|
|
margin-bottom: 1rem;
|
||
|
|
font-size: 0.875rem;
|
||
|
|
display: none;
|
||
|
|
}
|
||
|
|
|
||
|
|
.error-message.visible {
|
||
|
|
display: block;
|
||
|
|
}
|
||
|
|
|
||
|
|
.htmx-indicator {
|
||
|
|
display: none;
|
||
|
|
}
|
||
|
|
|
||
|
|
.htmx-request .htmx-indicator {
|
||
|
|
display: inline-block;
|
||
|
|
}
|
||
|
|
|
||
|
|
.htmx-request .btn-text {
|
||
|
|
display: none;
|
||
|
|
}
|
||
|
|
|
||
|
|
.spinner {
|
||
|
|
width: 20px;
|
||
|
|
height: 20px;
|
||
|
|
border: 2px solid transparent;
|
||
|
|
border-top-color: white;
|
||
|
|
border-radius: 50%;
|
||
|
|
animation: spin 0.8s linear infinite;
|
||
|
|
}
|
||
|
|
|
||
|
|
@keyframes spin {
|
||
|
|
to { transform: rotate(360deg); }
|
||
|
|
}
|
||
|
|
|
||
|
|
.divider {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
margin: 1.5rem 0;
|
||
|
|
color: var(--text-secondary);
|
||
|
|
font-size: 0.75rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.divider::before,
|
||
|
|
.divider::after {
|
||
|
|
content: '';
|
||
|
|
flex: 1;
|
||
|
|
height: 1px;
|
||
|
|
background: var(--border);
|
||
|
|
}
|
||
|
|
|
||
|
|
.divider span {
|
||
|
|
padding: 0 1rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.social-login {
|
||
|
|
display: flex;
|
||
|
|
gap: 0.75rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.social-btn {
|
||
|
|
flex: 1;
|
||
|
|
padding: 0.75rem;
|
||
|
|
background: var(--bg);
|
||
|
|
border: 1px solid var(--border);
|
||
|
|
border-radius: 8px;
|
||
|
|
color: var(--text);
|
||
|
|
font-size: 0.875rem;
|
||
|
|
cursor: pointer;
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: center;
|
||
|
|
gap: 0.5rem;
|
||
|
|
transition: border-color 0.2s, background 0.2s;
|
||
|
|
}
|
||
|
|
|
||
|
|
.social-btn:hover {
|
||
|
|
border-color: var(--primary);
|
||
|
|
background: var(--surface);
|
||
|
|
}
|
||
|
|
</style>
|
||
|
|
</head>
|
||
|
|
<body>
|
||
|
|
<div class="login-container">
|
||
|
|
<div class="login-header">
|
||
|
|
<div class="login-logo">🤖</div>
|
||
|
|
<h1 class="login-title">Welcome Back</h1>
|
||
|
|
<p class="login-subtitle">Sign in to General Bots Suite</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="login-form">
|
||
|
|
{% if let Some(error) = error %}
|
||
|
|
<div class="error-message visible">{{ error }}</div>
|
||
|
|
{% else %}
|
||
|
|
<div class="error-message" id="error-message"></div>
|
||
|
|
{% endif %}
|
||
|
|
|
||
|
|
<form hx-post="/api/auth/login"
|
||
|
|
hx-target="#error-message"
|
||
|
|
hx-swap="outerHTML"
|
||
|
|
hx-indicator=".login-btn">
|
||
|
|
|
||
|
|
<div class="form-group">
|
||
|
|
<label class="form-label" for="email">Email</label>
|
||
|
|
<input type="email"
|
||
|
|
id="email"
|
||
|
|
name="email"
|
||
|
|
class="form-input"
|
||
|
|
placeholder="you@example.com"
|
||
|
|
required
|
||
|
|
autocomplete="email">
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="form-group">
|
||
|
|
<label class="form-label" for="password">Password</label>
|
||
|
|
<input type="password"
|
||
|
|
id="password"
|
||
|
|
name="password"""
|
||
|
|
class="form-input"
|
||
|
|
placeholder="••••••••"
|
||
|
|
required
|
||
|
|
autocomplete="current-password">
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="form-group">
|
||
|
|
<label class="form-checkbox">
|
||
|
|
<input type="checkbox" name="remember" value="true">
|
||
|
|
<span>Remember me for 30 days</span>
|
||
|
|
</label>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<button type="submit" class="login-btn">
|
||
|
|
<span class="btn-text">Sign In</span>
|
||
|
|
<div class="spinner htmx-indicator"></div>
|
||
|
|
</button>
|
||
|
|
</form>
|
||
|
|
|
||
|
|
<div class="divider">
|
||
|
|
<span>or continue with</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="social-login">
|
||
|
|
<button type="button" class="social-btn"
|
||
|
|
hx-get="/api/auth/oauth/google"
|
||
|
|
hx-swap="none">
|
||
|
|
<svg width="18" height="18" viewBox="0 0 24 24">
|
||
|
|
<path fill="currentColor" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
|
||
|
|
<path fill="currentColor" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
|
||
|
|
<path fill="currentColor" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
|
||
|
|
<path fill="currentColor" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
|
||
|
|
</svg>
|
||
|
|
Google
|
||
|
|
</button>
|
||
|
|
<button type="button" class="social-btn"
|
||
|
|
hx-get="/api/auth/oauth/microsoft"
|
||
|
|
hx-swap="none">
|
||
|
|
<svg width="18" height="18" viewBox="0 0 24 24">
|
||
|
|
<path fill="currentColor" d="M11.4 24H0V12.6h11.4V24zM24 24H12.6V12.6H24V24zM11.4 11.4H0V0h11.4v11.4zm12.6 0H12.6V0H24v11.4z"/>
|
||
|
|
</svg>
|
||
|
|
Microsoft
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="login-footer">
|
||
|
|
<p>Don't have an account? <a href="/auth/register">Sign up</a></p>
|
||
|
|
<p style="margin-top: 0.5rem;"><a href="/auth/forgot-password">Forgot password?</a></p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
// Handle successful login redirect
|
||
|
|
document.body.addEventListener('htmx:afterRequest', function(event) {
|
||
|
|
if (event.detail.successful && event.detail.xhr.status === 200) {
|
||
|
|
const response = event.detail.xhr.response;
|
||
|
|
if (response && response.includes('redirect')) {
|
||
|
|
window.location.href = '/';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
</script>
|
||
|
|
</body>
|
||
|
|
</html>
|