- Local auth: registration/login with email+password (bcrypt, MariaDB) - Landing page: marketing page at / with hero, categories, advantages, CTA - Tool categories: Images (4), Code (3), Web (3), Utilities (2) - Sidebar: category dividers, user avatar with logout - Protected routes: all tools require auth, landing/login/register public - New files: lib/db.js, routes/auth.js, login.html, register.html, landing.html - Dependencies: bcrypt, mysql2
113 lines
5.6 KiB
HTML
113 lines
5.6 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ru" class="dark">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>Регистрация — WA Dev Tools</title>
|
|
<script src="/vendor/tailwind.js"></script>
|
|
<script>
|
|
tailwind.config = {
|
|
darkMode: 'class',
|
|
theme: { extend: {
|
|
fontFamily: { mono: ['JetBrains Mono', 'monospace'], sans: ['Manrope', 'sans-serif'] },
|
|
colors: { surface: { 900: '#060c18', 800: '#091020', 700: '#0d1828', 600: '#162040' }, accent: { DEFAULT: '#0054e6', dim: '#0043b8', bright: '#6b9fff' } }
|
|
}}
|
|
};
|
|
</script>
|
|
<link href="/vendor/fonts.css" rel="stylesheet">
|
|
<link href="/shared.css" rel="stylesheet">
|
|
<style>
|
|
.auth-card { max-width: 400px; width: 100%; }
|
|
.auth-input { width: 100%; background: var(--select-bg); border: 1px solid var(--surface-600); color: var(--select-color); padding: 12px 16px; border-radius: 10px; font-family: 'Manrope', sans-serif; font-size: 15px; outline: none; transition: border-color .2s; }
|
|
.auth-input:focus { border-color: var(--accent); }
|
|
.auth-btn { width: 100%; padding: 14px; background: var(--accent); color: #fff; border: none; border-radius: 10px; font-family: 'Manrope', sans-serif; font-weight: 700; font-size: 16px; cursor: pointer; transition: background .2s; }
|
|
.auth-btn:hover { background: var(--accent-dim); }
|
|
.auth-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
.auth-error { background: rgba(255,82,82,.1); border: 1px solid rgba(255,82,82,.3); color: #ff5252; padding: 10px 14px; border-radius: 8px; font-size: 13px; display: none; }
|
|
.auth-error.visible { display: block; }
|
|
.auth-link { color: var(--accent); text-decoration: none; }
|
|
.auth-link:hover { text-decoration: underline; }
|
|
</style>
|
|
</head>
|
|
<body style="display:flex;align-items:center;justify-content:center;min-height:100vh;padding:20px;">
|
|
<div class="auth-card">
|
|
<div style="text-align:center;margin-bottom:32px;">
|
|
<a href="/" style="text-decoration:none;">
|
|
<h1 style="font-size:28px;font-weight:800;color:var(--text-primary);font-family:'Manrope',sans-serif;">WA Dev <span style="color:var(--accent);">Tools</span></h1>
|
|
</a>
|
|
<p style="color:var(--text-muted);font-size:14px;margin-top:8px;">Создайте бесплатный аккаунт</p>
|
|
</div>
|
|
|
|
<div id="error" class="auth-error"></div>
|
|
|
|
<form id="regForm" style="display:flex;flex-direction:column;gap:16px;margin-top:16px;" onsubmit="return handleRegister(event)">
|
|
<div>
|
|
<label style="display:block;font-size:13px;color:var(--text-muted);margin-bottom:6px;font-family:'JetBrains Mono',monospace;">Имя</label>
|
|
<input class="auth-input" type="text" name="name" placeholder="Как вас зовут" autocomplete="name" />
|
|
</div>
|
|
<div>
|
|
<label style="display:block;font-size:13px;color:var(--text-muted);margin-bottom:6px;font-family:'JetBrains Mono',monospace;">Email</label>
|
|
<input class="auth-input" type="email" name="email" required autofocus autocomplete="email" />
|
|
</div>
|
|
<div>
|
|
<label style="display:block;font-size:13px;color:var(--text-muted);margin-bottom:6px;font-family:'JetBrains Mono',monospace;">Пароль</label>
|
|
<input class="auth-input" type="password" name="password" required minlength="6" autocomplete="new-password" />
|
|
</div>
|
|
<div>
|
|
<label style="display:block;font-size:13px;color:var(--text-muted);margin-bottom:6px;font-family:'JetBrains Mono',monospace;">Подтвердите пароль</label>
|
|
<input class="auth-input" type="password" name="password2" required minlength="6" autocomplete="new-password" />
|
|
</div>
|
|
<button class="auth-btn" type="submit" id="submitBtn">Зарегистрироваться</button>
|
|
</form>
|
|
|
|
<p style="text-align:center;margin-top:24px;font-size:14px;color:var(--text-muted);">
|
|
Уже есть аккаунт? <a href="/auth/login" class="auth-link">Войти</a>
|
|
</p>
|
|
</div>
|
|
|
|
<script>
|
|
async function handleRegister(e) {
|
|
e.preventDefault();
|
|
const form = e.target;
|
|
const btn = document.getElementById('submitBtn');
|
|
const err = document.getElementById('error');
|
|
err.classList.remove('visible');
|
|
|
|
if (form.password.value !== form.password2.value) {
|
|
err.textContent = 'Пароли не совпадают';
|
|
err.classList.add('visible');
|
|
return;
|
|
}
|
|
|
|
btn.disabled = true;
|
|
btn.textContent = 'Регистрация...';
|
|
|
|
try {
|
|
const res = await fetch('/auth/register', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
name: form.name.value,
|
|
email: form.email.value,
|
|
password: form.password.value,
|
|
password2: form.password2.value,
|
|
}),
|
|
});
|
|
const data = await res.json();
|
|
if (data.ok) {
|
|
window.location.href = data.redirect || '/dashboard';
|
|
} else {
|
|
err.textContent = data.error;
|
|
err.classList.add('visible');
|
|
}
|
|
} catch {
|
|
err.textContent = 'Ошибка сети';
|
|
err.classList.add('visible');
|
|
}
|
|
btn.disabled = false;
|
|
btn.textContent = 'Зарегистрироваться';
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|