Files
hotel777-manager/public/login.html
2026-05-04 21:09:42 +05:00

203 lines
6.0 KiB
HTML
Raw 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.
<!DOCTYPE html>
<html lang="ru" data-title="Вход в панель" data-description="Авторизация администратора гостиницы">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style.css">
<title>Вход в систему</title>
<style>
.login-page {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.login-main {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem 1rem;
}
.login-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius-xl);
padding: clamp(1.5rem, 5vw, 3rem);
width: 100%;
max-width: 420px;
box-shadow: var(--shadow-lg);
animation: fadeInUp 0.5s ease-out;
}
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.login-icon {
width: 56px;
height: 56px;
background: var(--primary-light);
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 1.5rem;
}
.login-icon svg {
width: 28px;
height: 28px;
color: var(--primary);
}
.login-title {
font-size: clamp(1.375rem, 4vw, 1.75rem);
font-weight: 700;
text-align: center;
margin-bottom: 0.375rem;
color: var(--text-primary);
letter-spacing: -0.025em;
}
.login-subtitle {
color: var(--text-muted);
text-align: center;
font-size: 0.875rem;
margin-bottom: 2rem;
}
.login-btn {
width: 100%;
padding: 0.75rem;
font-size: 0.9375rem;
font-weight: 600;
margin-top: 0.5rem;
justify-content: center;
}
.error-message {
background: var(--danger-light);
border: 1px solid #fca5a5;
color: var(--danger);
padding: 0.75rem 1rem;
border-radius: var(--radius-sm);
font-size: 0.875rem;
margin-top: 1rem;
display: none;
animation: shake 0.4s ease-in-out;
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-6px); }
75% { transform: translateX(6px); }
}
.form-group {
margin-bottom: 1rem;
}
.form-group label {
display: block;
margin-bottom: 0.375rem;
color: var(--text-secondary);
font-size: 0.875rem;
font-weight: 500;
}
.form-group input {
width: 100%;
padding: 0.75rem 0.875rem;
font-size: 0.9375rem;
}
</style>
</head>
<body>
<div class="login-page">
<header></header>
<main class="login-main">
<div class="login-card">
<div class="login-icon">
<svg fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M8.25 21v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21m0 0h4.5V3.545L12.75 2.25 5.25 3.545V21"/></svg>
</div>
<h1 class="login-title">Отель 777</h1>
<p class="login-subtitle">Панель управления</p>
<form id="loginForm">
<div class="form-group">
<label for="login">Логин</label>
<input type="text" id="login" name="login" required autocomplete="username" placeholder="Введите логин">
</div>
<div class="form-group">
<label for="password">Пароль</label>
<input type="password" id="password" name="password" required autocomplete="current-password" placeholder="Введите пароль">
</div>
<button type="submit" class="login-btn" id="loginBtn">
<span id="loginBtnText">Войти</span>
<span id="loginBtnSpinner" class="loading-spinner" style="display:none;"></span>
</button>
<p id="error" class="error-message"></p>
</form>
</div>
</main>
</div>
<script src="nav.js"></script>
<script>
document.getElementById('loginForm').addEventListener('submit', async (e) => {
e.preventDefault();
const loginBtn = document.getElementById('loginBtn');
const loginBtnText = document.getElementById('loginBtnText');
const loginBtnSpinner = document.getElementById('loginBtnSpinner');
const errorEl = document.getElementById('error');
loginBtn.disabled = true;
loginBtnText.style.display = 'none';
loginBtnSpinner.style.display = 'inline-block';
errorEl.style.display = 'none';
try {
const csrfRes = await fetch('/api/csrf-token');
const csrfData = await csrfRes.json();
const res = await fetch('/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfData.csrfToken
},
body: JSON.stringify({
login: document.getElementById('login').value,
password: document.getElementById('password').value
})
});
if (res.ok) {
const data = await res.json();
window.localStorage.setItem('csrfToken', data.csrfToken);
window.location.href = '/index.html';
} else {
const err = await res.json();
errorEl.textContent = err.error || 'Неверный логин или пароль';
errorEl.style.display = 'block';
}
} catch (err) {
errorEl.textContent = 'Ошибка соединения с сервером';
errorEl.style.display = 'block';
} finally {
loginBtn.disabled = false;
loginBtnText.style.display = 'inline';
loginBtnSpinner.style.display = 'none';
}
});
</script>
</body>
</html>