pingql/apps/web/src/dashboard/index.html

156 lines
7.0 KiB
HTML

<!DOCTYPE html>
<html lang="en" class="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PingQL — Sign In</title>
<link rel="stylesheet" href="/dashboard/tailwind.css">
<link rel="stylesheet" href="/dashboard/app.css">
</head>
<body class="bg-[#0a0a0a] text-gray-100 min-h-screen flex items-center justify-center p-4">
<div class="w-full max-w-md">
<div class="text-center mb-8">
<h1 class="text-3xl font-bold tracking-tight">Ping<span class="text-blue-400">QL</span></h1>
<p class="text-gray-500 text-sm mt-2">Uptime monitoring for developers</p>
</div>
<div class="bg-gray-900 rounded-xl p-6 border border-gray-800" style="box-shadow:0 0 40px rgba(59,130,246,0.08)">
<!-- Sign in form — works with or without JS -->
<div id="screen-login">
<form id="login-form" action="/account/login" method="POST">
<input type="hidden" name="_form" value="1">
<label class="block text-xs text-gray-500 uppercase tracking-wider mb-2">Account Key</label>
<input id="key-input" name="key" type="text" placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" autocomplete="off" spellcheck="false"
class="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-3 text-gray-100 placeholder-gray-600 focus:outline-none focus:border-blue-500 text-sm font-mono tracking-wider"
maxlength="36">
<button type="submit"
class="w-full mt-3 bg-blue-600 hover:bg-blue-500 text-white font-medium py-3 rounded-lg transition-colors">
Sign In
</button>
<div id="login-error" class="text-red-400 text-sm mt-3 text-center hidden"></div>
</form>
<div class="mt-6 pt-5 border-t border-gray-800 text-center">
<p class="text-gray-500 text-sm mb-3">No account?</p>
<button id="register-btn"
class="w-full bg-gray-800 hover:bg-gray-700 border border-gray-700 text-gray-300 font-medium py-3 rounded-lg transition-colors">
Create Account
</button>
</div>
</div>
<!-- Post-registration: show new key -->
<div id="screen-new-account" class="hidden">
<div class="flex items-center gap-3 mb-5">
<div class="w-8 h-8 rounded-full bg-green-500/20 flex items-center justify-center text-green-400 text-lg"></div>
<div>
<p class="font-semibold text-white">Account created</p>
<p class="text-xs text-gray-500">Save your key — it's how you access your account</p>
</div>
</div>
<label class="block text-xs text-gray-500 uppercase tracking-wider mb-2">Your Account Key</label>
<div class="flex gap-2 mb-5">
<div id="new-key-display"
class="flex-1 bg-gray-800 border border-gray-700 rounded-lg px-4 py-3 text-blue-400 text-sm font-mono select-all break-all"></div>
<button id="copy-key-btn"
class="px-4 bg-gray-800 hover:bg-gray-700 border border-gray-700 rounded-lg text-gray-400 hover:text-white transition-colors text-sm">
Copy
</button>
</div>
<div class="pt-5 border-t border-gray-800">
<label class="block text-xs text-gray-500 uppercase tracking-wider mb-1">Email <span class="text-gray-600 normal-case">(optional — recovery only)</span></label>
<input id="email-input" type="email" placeholder="you@example.com"
class="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-3 text-gray-100 placeholder-gray-600 focus:outline-none focus:border-blue-500 text-sm mb-3">
<div class="flex gap-2">
<button id="save-email-btn"
class="flex-1 bg-blue-600 hover:bg-blue-500 text-white font-medium py-2.5 rounded-lg transition-colors text-sm">
Save & Continue
</button>
<button id="skip-email-btn"
class="px-4 bg-gray-800 hover:bg-gray-700 border border-gray-700 text-gray-400 rounded-lg transition-colors text-sm">
Skip
</button>
</div>
<div id="email-error" class="text-red-400 text-xs mt-2 hidden"></div>
</div>
</div>
</div>
</div>
<script>
const API = '';
let newKey = null;
const keyInput = document.getElementById('key-input');
// JS-enhanced login (overrides form POST for better UX)
document.getElementById('login-form').addEventListener('submit', async (e) => {
e.preventDefault();
const key = keyInput.value.trim();
if (key.length < 10) return showError('Enter a valid account key');
try {
const res = await fetch('/account/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ key }),
});
if (!res.ok) return showError('Invalid account key');
window.location.href = '/dashboard/home';
} catch { showError('Connection error'); }
});
// Register
document.getElementById('register-btn').addEventListener('click', async () => {
try {
const res = await fetch('/account/register', {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({}),
});
const data = await res.json();
if (!res.ok || !data.key) return showError(data.error || 'Failed');
newKey = data.key;
// Set cookie via login endpoint
await fetch('/account/login', {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ key: newKey }),
});
document.getElementById('screen-login').classList.add('hidden');
document.getElementById('screen-new-account').classList.remove('hidden');
document.getElementById('new-key-display').textContent = newKey;
} catch { showError('Connection error'); }
});
document.getElementById('copy-key-btn').addEventListener('click', () => {
navigator.clipboard.writeText(newKey).then(() => {
const btn = document.getElementById('copy-key-btn');
btn.textContent = 'Copied!'; btn.classList.add('text-green-400');
setTimeout(() => { btn.textContent = 'Copy'; btn.classList.remove('text-green-400'); }, 2000);
});
});
document.getElementById('save-email-btn').addEventListener('click', async () => {
const email = document.getElementById('email-input').value.trim();
if (!email) { document.getElementById('skip-email-btn').click(); return; }
try {
await fetch('/account/email', {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email }),
});
} catch {}
window.location.href = '/dashboard/home';
});
document.getElementById('skip-email-btn').addEventListener('click', () => {
window.location.href = '/dashboard/home';
});
function showError(msg) {
const el = document.getElementById('login-error');
el.textContent = msg; el.classList.remove('hidden');
}
</script>
</body>
</html>