Files
magistr/frontend/script.js

131 lines
4.0 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(() => {
'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');
// Ripple effect
btnSubmit.addEventListener('click', function(e) {
const rect = this.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const ripple = document.createElement('span');
ripple.classList.add('ripple');
ripple.style.left = `${x}px`;
ripple.style.top = `${y}px`;
this.appendChild(ripple);
setTimeout(() => {
ripple.remove();
}, 600);
});
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) {
const group = input.closest('.form-group');
group.classList.add('has-error');
errorEl.textContent = message;
// Shake animation
group.classList.remove('shake');
void group.offsetWidth; // trigger reflow
group.classList.add('shake');
}
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);
}
});
})();