With DataBaseREdis

This commit is contained in:
2026-01-28 00:49:37 +03:00
parent 4b01c2d4de
commit af62de5569
10 changed files with 1309 additions and 24 deletions

View File

@ -189,6 +189,10 @@
socket = io(url, { transports: ['websocket', 'polling'], reconnection: false });
socket.on('connect', () => {
clearError();
// Отправляем имя пользователя на сервер, если авторизованы
if (currentUser && currentUser.username) {
socket.emit('setUsername', currentUser.username);
}
});
socket.on('connect_error', (e) => {
const errorMsg = e.message || 'Не удалось подключиться к серверу';
@ -2830,8 +2834,281 @@
});
}
// Авторизация
let currentUser = null;
async function checkAuth() {
try {
const response = await fetch('/api/user', { credentials: 'include' });
if (response.ok) {
const data = await response.json();
currentUser = data.user;
updateUserUI();
return true;
} else {
// Не авторизован, но не показываем модальное окно автоматически
// Пользователь может продолжить без авторизации
return false;
}
} catch (error) {
console.error('Ошибка проверки авторизации:', error);
// Не показываем модальное окно при ошибке, пользователь может продолжить
return false;
}
}
function updateUserUI() {
const userInfo = $('user-info');
const userName = $('user-name');
if (currentUser && userInfo && userName) {
userInfo.style.display = 'flex';
userName.textContent = currentUser.username;
if (currentUser.gamesPlayed !== undefined) {
userName.textContent += ` (${currentUser.gamesWon || 0}/${currentUser.gamesPlayed || 0})`;
}
}
}
function showAuthModal() {
const authOverlay = $('auth-overlay');
if (authOverlay) {
authOverlay.classList.remove('hidden');
}
}
function hideAuthModal() {
const authOverlay = $('auth-overlay');
if (authOverlay) {
authOverlay.classList.add('hidden');
}
}
function initAuth() {
// Переключение между вкладками авторизации
$all('.auth-tab').forEach(tab => {
tab.addEventListener('click', () => {
$all('.auth-tab').forEach(t => t.classList.remove('active'));
$all('.auth-panel').forEach(p => p.classList.add('hidden'));
tab.classList.add('active');
const panelId = 'auth-' + tab.dataset.authTab + '-panel';
const panel = $(panelId);
if (panel) {
panel.classList.remove('hidden');
panel.classList.add('active');
}
});
});
// Форма входа
const loginForm = $('login-form');
if (loginForm) {
loginForm.addEventListener('submit', async (e) => {
e.preventDefault();
const username = $('login-username')?.value.trim();
const password = $('login-password')?.value;
const errorEl = $('login-error');
if (!username || !password) {
if (errorEl) {
errorEl.textContent = 'Заполните все поля';
errorEl.classList.remove('hidden');
}
return;
}
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ username, password })
});
const data = await response.json();
if (response.ok && data.success) {
currentUser = data.user;
updateUserUI();
hideAuthModal();
if (errorEl) errorEl.classList.add('hidden');
} else {
if (errorEl) {
errorEl.textContent = data.error || 'Ошибка входа';
errorEl.classList.remove('hidden');
}
}
} catch (error) {
console.error('Ошибка входа:', error);
if (errorEl) {
errorEl.textContent = 'Ошибка соединения с сервером';
errorEl.classList.remove('hidden');
}
}
});
}
// Форма регистрации
const registerForm = $('register-form');
if (registerForm) {
registerForm.addEventListener('submit', async (e) => {
e.preventDefault();
const username = $('register-username')?.value.trim();
const password = $('register-password')?.value;
const email = $('register-email')?.value.trim();
const errorEl = $('register-error');
if (!username || !password) {
if (errorEl) {
errorEl.textContent = 'Заполните обязательные поля';
errorEl.classList.remove('hidden');
}
return;
}
try {
const response = await fetch('/api/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ username, password, email })
});
const data = await response.json();
if (response.ok && data.success) {
currentUser = data.user;
updateUserUI();
hideAuthModal();
if (errorEl) errorEl.classList.add('hidden');
// Отправляем имя пользователя на сервер через socket
if (socket && socket.connected && currentUser.username) {
socket.emit('setUsername', currentUser.username);
}
} else {
if (errorEl) {
errorEl.textContent = data.error || 'Ошибка регистрации';
errorEl.classList.remove('hidden');
}
}
} catch (error) {
console.error('Ошибка регистрации:', error);
if (errorEl) {
errorEl.textContent = 'Ошибка соединения с сервером';
errorEl.classList.remove('hidden');
}
}
});
}
// Кнопка закрытия авторизации
const authClose = $('auth-close');
if (authClose) {
authClose.addEventListener('click', () => {
hideAuthModal();
});
}
// Кнопка выхода
const btnLogout = $('btn-logout');
if (btnLogout) {
btnLogout.addEventListener('click', async () => {
try {
await fetch('/api/logout', { method: 'POST', credentials: 'include' });
currentUser = null;
const userInfo = $('user-info');
if (userInfo) userInfo.style.display = 'none';
showAuthModal();
} catch (error) {
console.error('Ошибка выхода:', error);
}
});
}
// Leaderboard
const btnLeaderboard = $('btn-leaderboard');
if (btnLeaderboard) {
btnLeaderboard.addEventListener('click', async () => {
const overlay = $('leaderboard-overlay');
const content = $('leaderboard-content');
if (overlay) overlay.classList.remove('hidden');
if (content) content.innerHTML = '<p class="hint" style="text-align: center; padding: 2rem;">Загрузка...</p>';
try {
const response = await fetch('/api/leaderboard', { credentials: 'include' });
const data = await response.json();
if (response.ok && data.leaderboard) {
if (data.leaderboard.length === 0) {
content.innerHTML = '<p class="hint" style="text-align: center; padding: 2rem;">Рейтинг пуст</p>';
} else {
content.innerHTML = `
<table class="leaderboard-table">
<thead>
<tr>
<th class="rank">#</th>
<th class="username">Игрок</th>
<th class="stats">Игр</th>
<th class="stats">Побед</th>
<th class="stats">% Побед</th>
<th class="stats">Урон</th>
<th class="stats">Хил</th>
</tr>
</thead>
<tbody>
${data.leaderboard.map((player, idx) => `
<tr>
<td class="rank">${idx + 1}</td>
<td class="username">${escapeHtml(player.username)}</td>
<td class="stats">${player.gamesPlayed}</td>
<td class="stats">${player.gamesWon}</td>
<td class="stats">${player.winRate}%</td>
<td class="stats">${player.totalDamage}</td>
<td class="stats">${player.totalHealing}</td>
</tr>
`).join('')}
</tbody>
</table>
`;
}
} else {
content.innerHTML = '<p class="hint" style="text-align: center; padding: 2rem; color: var(--red);">Ошибка загрузки рейтинга</p>';
}
} catch (error) {
console.error('Ошибка загрузки leaderboard:', error);
if (content) {
content.innerHTML = '<p class="hint" style="text-align: center; padding: 2rem; color: var(--red);">Ошибка соединения</p>';
}
}
if (typeof lucide !== 'undefined') {
setTimeout(() => {
try {
lucide.createIcons();
} catch (e) {
console.warn('Error updating Lucide icons:', e);
}
}, 100);
}
});
}
const leaderboardClose = $('leaderboard-close');
if (leaderboardClose) {
leaderboardClose.addEventListener('click', () => {
const overlay = $('leaderboard-overlay');
if (overlay) overlay.classList.add('hidden');
});
}
}
function init() {
initLobby();
initAuth();
// Проверяем авторизацию, но не блокируем доступ
checkAuth().catch(() => {
// Если авторизация не удалась, просто показываем модальное окно
// Пользователь может продолжить без авторизации
});
showScreen('lobby');
socket = null;
}

View File

@ -20,6 +20,55 @@
<div id="stars2"></div>
<div id="stars3"></div>
<!-- Auth Modal -->
<div id="auth-overlay" class="modal-overlay hidden">
<div class="modal auth-modal">
<div class="auth-tabs">
<button type="button" class="auth-tab active" data-auth-tab="login">Вход</button>
<button type="button" class="auth-tab" data-auth-tab="register">Регистрация</button>
</div>
<div id="auth-login-panel" class="auth-panel active">
<h2>Вход</h2>
<form id="login-form">
<label>Имя пользователя</label>
<input type="text" id="login-username" placeholder="Введите имя" required maxlength="20" />
<label style="margin-top: 1rem;">Пароль</label>
<input type="password" id="login-password" placeholder="Введите пароль" required />
<button type="submit" class="btn btn-primary" style="margin-top: 1.5rem; width: 100%;">Войти</button>
</form>
<p id="login-error" class="error hidden" style="margin-top: 1rem;"></p>
</div>
<div id="auth-register-panel" class="auth-panel hidden">
<h2>Регистрация</h2>
<form id="register-form">
<label>Имя пользователя</label>
<input type="text" id="register-username" placeholder="От 3 до 20 символов" required minlength="3" maxlength="20" />
<label style="margin-top: 1rem;">Пароль</label>
<input type="password" id="register-password" placeholder="Не менее 6 символов" required minlength="6" />
<label style="margin-top: 1rem;">Email (необязательно)</label>
<input type="email" id="register-email" placeholder="your@email.com" />
<button type="submit" class="btn btn-primary" style="margin-top: 1.5rem; width: 100%;">Зарегистрироваться</button>
</form>
<p id="register-error" class="error hidden" style="margin-top: 1rem;"></p>
</div>
<button type="button" id="auth-close" class="btn btn-ghost" style="margin-top: 1rem; width: 100%;">Продолжить без авторизации</button>
</div>
</div>
<!-- Leaderboard Modal -->
<div id="leaderboard-overlay" class="modal-overlay hidden">
<div class="modal leaderboard-modal">
<h2><i data-lucide="trophy" style="width: 24px; height: 24px; vertical-align: middle; margin-right: 0.5rem;"></i>Глобальный рейтинг</h2>
<div id="leaderboard-content" style="max-height: 500px; overflow-y: auto; margin-top: 1rem;">
<p class="hint" style="text-align: center; padding: 2rem;">Загрузка...</p>
</div>
<button type="button" id="leaderboard-close" class="btn btn-primary" style="margin-top: 1rem; width: 100%;">Закрыть</button>
</div>
</div>
<!-- Lobby -->
<section id="lobby" class="screen">
<div class="lobby-card">
@ -29,9 +78,16 @@
<span class="logo-hs">HEARTHSTONE</span>
</h1>
<p class="subtitle">PvP до 4 игроков · Игра с ИИ · Работает через Radmin VPN</p>
<button type="button" id="btn-instructions-lobby" class="btn-instructions-lobby" title="Как играть">?</button>
<button type="button" id="btn-cards-gallery" class="btn-instructions-lobby" title="Галерея карт" style="right: 3.5rem;"><i data-lucide="book-open"></i></button>
<button type="button" id="btn-settings-lobby" class="btn-settings-lobby" title="Настройки"><i data-lucide="settings"></i></button>
<div style="position: absolute; top: 1rem; right: 1rem; display: flex; gap: 0.5rem; align-items: center;">
<button type="button" id="btn-leaderboard" class="btn-instructions-lobby" title="Рейтинг"><i data-lucide="trophy"></i></button>
<button type="button" id="btn-instructions-lobby" class="btn-instructions-lobby" title="Как играть">?</button>
<button type="button" id="btn-cards-gallery" class="btn-instructions-lobby" title="Галерея карт"><i data-lucide="book-open"></i></button>
<button type="button" id="btn-settings-lobby" class="btn-settings-lobby" title="Настройки"><i data-lucide="settings"></i></button>
</div>
<div id="user-info" style="position: absolute; top: 1rem; left: 1rem; display: none; align-items: center; gap: 0.5rem;">
<span id="user-name" style="color: var(--cyan); font-weight: 600;"></span>
<button type="button" id="btn-logout" class="btn btn-ghost" style="padding: 0.3rem 0.6rem; font-size: 0.85rem;">Выйти</button>
</div>
<div class="lobby-tabs">
<button type="button" class="tab active" data-tab="host">Создать игру</button>

View File

@ -1564,6 +1564,103 @@ html, body {
animation: fadeIn 0.3s ease;
}
.modal-overlay.hidden { display: none !important; }
/* Auth Modal */
.auth-modal {
max-width: 400px;
}
.auth-tabs {
display: flex;
gap: 0.5rem;
margin-bottom: 1.5rem;
border-bottom: 2px solid rgba(212,168,75,0.2);
}
.auth-tab {
flex: 1;
padding: 0.75rem;
background: transparent;
border: none;
border-bottom: 2px solid transparent;
color: #94a3b8;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.auth-tab.active {
color: var(--cyan);
border-bottom-color: var(--cyan);
}
.auth-tab:hover {
color: var(--cyan);
}
.auth-panel {
display: none;
}
.auth-panel.active {
display: block;
}
.auth-panel label {
display: block;
margin-bottom: 0.5rem;
font-size: 0.9rem;
color: #94a3b8;
}
.auth-panel input {
width: 100%;
padding: 0.75rem;
background: rgba(0,0,0,0.3);
border: 2px solid rgba(0,180,255,0.35);
border-radius: 8px;
color: #f8fafc;
font-size: 1rem;
margin-bottom: 0.5rem;
}
.auth-panel input:focus {
outline: none;
border-color: var(--cyan);
box-shadow: 0 0 10px rgba(0,212,255,0.3);
}
/* Leaderboard Modal */
.leaderboard-modal {
max-width: 700px;
max-height: 80vh;
}
.leaderboard-table {
width: 100%;
border-collapse: collapse;
margin-top: 1rem;
}
.leaderboard-table th,
.leaderboard-table td {
padding: 0.75rem;
text-align: left;
border-bottom: 1px solid rgba(255,255,255,0.1);
}
.leaderboard-table th {
background: rgba(0,0,0,0.3);
color: var(--cyan);
font-weight: 700;
position: sticky;
top: 0;
}
.leaderboard-table tr:hover {
background: rgba(0,212,255,0.1);
}
.leaderboard-table .rank {
font-weight: 700;
color: var(--gold);
width: 50px;
text-align: center;
}
.leaderboard-table .username {
font-weight: 600;
color: var(--cyan);
}
.leaderboard-table .stats {
text-align: center;
}
.modal {
background: linear-gradient(145deg, #2d2a22 0%, #1a1814 100%);
border: 3px solid var(--gold);