feat: backend auth, admin panel, role-based routing

This commit is contained in:
Zuev
2026-02-14 02:05:37 +03:00
parent 61a5cf5cce
commit b6ff6c457a
28 changed files with 1844 additions and 10 deletions

106
frontend/script.js Normal file
View File

@@ -0,0 +1,106 @@
(() => {
'use strict';
const form = document.getElementById('login-form');
const usernameInput = document.getElementById('username');
const passwordInput = document.getElementById('password');
const usernameError = document.getElementById('username-error');
const passwordError = document.getElementById('password-error');
const formAlert = document.getElementById('form-alert');
const btnSubmit = document.getElementById('btn-submit');
const btnText = btnSubmit.querySelector('.btn-text');
const btnLoader = btnSubmit.querySelector('.btn-loader');
const togglePassword = document.getElementById('toggle-password');
togglePassword.addEventListener('click', () => {
const isPassword = passwordInput.type === 'password';
passwordInput.type = isPassword ? 'text' : 'password';
togglePassword.setAttribute('aria-label', isPassword ? 'Скрыть пароль' : 'Показать пароль');
});
usernameInput.addEventListener('input', () => clearFieldError(usernameInput, usernameError));
passwordInput.addEventListener('input', () => clearFieldError(passwordInput, passwordError));
function clearFieldError(input, errorEl) {
input.closest('.form-group').classList.remove('has-error');
errorEl.textContent = '';
}
function setFieldError(input, errorEl, message) {
input.closest('.form-group').classList.add('has-error');
errorEl.textContent = message;
}
function showAlert(message, type) {
formAlert.className = 'form-alert ' + type;
formAlert.textContent = message;
}
function hideAlert() {
formAlert.className = 'form-alert';
formAlert.textContent = '';
}
function setLoading(loading) {
btnSubmit.disabled = loading;
btnText.hidden = loading;
btnLoader.hidden = !loading;
}
function validate() {
let valid = true;
hideAlert();
if (!usernameInput.value.trim()) {
setFieldError(usernameInput, usernameError, 'Введите имя пользователя');
valid = false;
}
if (!passwordInput.value) {
setFieldError(passwordInput, passwordError, 'Введите пароль');
valid = false;
} else if (passwordInput.value.length < 4) {
setFieldError(passwordInput, passwordError, 'Минимум 4 символа');
valid = false;
}
return valid;
}
form.addEventListener('submit', async (e) => {
e.preventDefault();
if (!validate()) return;
setLoading(true);
hideAlert();
try {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: usernameInput.value.trim(),
password: passwordInput.value,
}),
});
const data = await response.json();
if (response.ok) {
showAlert('Вход выполнен успешно!', 'success');
if (data.token) localStorage.setItem('token', data.token);
if (data.role) localStorage.setItem('role', data.role);
const redirect = data.redirect || '/';
setTimeout(() => { window.location.href = redirect; }, 400);
} else {
showAlert(data.message || 'Неверное имя пользователя или пароль', 'error');
}
} catch (err) {
showAlert('Ошибка соединения с сервером', 'error');
} finally {
setLoading(false);
}
});
})();