|
|
@@ -28,6 +28,9 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|
|
|
const config = await getConfig();
|
|
|
|
const config = await getConfig();
|
|
|
|
gameState.apiUrl = config.apiUrl;
|
|
|
|
gameState.apiUrl = config.apiUrl;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Check for existing session on load
|
|
|
|
|
|
|
|
restoreAuthSession();
|
|
|
|
|
|
|
|
|
|
|
|
document.getElementById('new-game-btn').addEventListener('click', showNewGameModal);
|
|
|
|
document.getElementById('new-game-btn').addEventListener('click', showNewGameModal);
|
|
|
|
document.getElementById('undo-btn').addEventListener('click', undoMoves);
|
|
|
|
document.getElementById('undo-btn').addEventListener('click', undoMoves);
|
|
|
|
document.getElementById('start-game-btn').addEventListener('click', startNewGame);
|
|
|
|
document.getElementById('start-game-btn').addEventListener('click', startNewGame);
|
|
|
@@ -55,8 +58,6 @@ document.getElementById('register-submit-btn').addEventListener('click', handleR
|
|
|
|
document.getElementById('auth-cancel-btn').addEventListener('click', hideAuthModal);
|
|
|
|
document.getElementById('auth-cancel-btn').addEventListener('click', hideAuthModal);
|
|
|
|
document.getElementById('auth-cancel-btn-2').addEventListener('click', hideAuthModal);
|
|
|
|
document.getElementById('auth-cancel-btn-2').addEventListener('click', hideAuthModal);
|
|
|
|
|
|
|
|
|
|
|
|
// Check for existing session on load
|
|
|
|
|
|
|
|
restoreAuthSession();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Auth functions
|
|
|
|
// Auth functions
|
|
|
|
function restoreAuthSession() {
|
|
|
|
function restoreAuthSession() {
|
|
|
@@ -117,6 +118,8 @@ function handleAuthClick() {
|
|
|
|
function showAuthModal() {
|
|
|
|
function showAuthModal() {
|
|
|
|
document.getElementById('auth-modal-overlay').classList.add('show');
|
|
|
|
document.getElementById('auth-modal-overlay').classList.add('show');
|
|
|
|
document.getElementById('login-identifier').focus();
|
|
|
|
document.getElementById('login-identifier').focus();
|
|
|
|
|
|
|
|
// Remove first to prevent duplicate registrations
|
|
|
|
|
|
|
|
document.removeEventListener('keydown', handleAuthModalKeydown);
|
|
|
|
document.addEventListener('keydown', handleAuthModalKeydown);
|
|
|
|
document.addEventListener('keydown', handleAuthModalKeydown);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@@ -161,6 +164,9 @@ async function handleLogin() {
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const submitBtn = document.getElementById('login-submit-btn');
|
|
|
|
|
|
|
|
submitBtn.disabled = true;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
const response = await fetch(`${gameState.apiUrl}/api/v1/auth/login`, {
|
|
|
|
const response = await fetch(`${gameState.apiUrl}/api/v1/auth/login`, {
|
|
|
|
method: 'POST',
|
|
|
|
method: 'POST',
|
|
|
@@ -170,7 +176,7 @@ async function handleLogin() {
|
|
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
if (!response.ok) {
|
|
|
|
const err = await response.json();
|
|
|
|
const err = await response.json();
|
|
|
|
flashErrorMessage(err.error || 'Login failed');
|
|
|
|
flashErrorMessage(err.error || 'Login failed', 3000);
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@@ -183,13 +189,15 @@ async function handleLogin() {
|
|
|
|
hideAuthModal();
|
|
|
|
hideAuthModal();
|
|
|
|
} catch (error) {
|
|
|
|
} catch (error) {
|
|
|
|
flashErrorMessage('Connection failed');
|
|
|
|
flashErrorMessage('Connection failed');
|
|
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
|
|
submitBtn.disabled = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function handleRegister() {
|
|
|
|
async function handleRegister() {
|
|
|
|
const username = document.getElementById('register-username').value.trim();
|
|
|
|
const username = document.getElementById('register-username').value.trim();
|
|
|
|
const email = document.getElementById('register-email').value.trim();
|
|
|
|
const email = document.getElementById('register-email').value.trim();
|
|
|
|
const password = document.getElementById('register-password').value;
|
|
|
|
const password = document.getElementById('register-password').value; // intentionally not trimmed for passwords
|
|
|
|
|
|
|
|
|
|
|
|
if (!username || !password) {
|
|
|
|
if (!username || !password) {
|
|
|
|
flashErrorMessage('Username and password required');
|
|
|
|
flashErrorMessage('Username and password required');
|
|
|
@@ -201,6 +209,17 @@ async function handleRegister() {
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Match server-side requirement: at least one letter AND one digit
|
|
|
|
|
|
|
|
const hasLetter = /[a-zA-Z]/.test(password);
|
|
|
|
|
|
|
|
const hasNumber = /[0-9]/.test(password);
|
|
|
|
|
|
|
|
if (!hasLetter || !hasNumber) {
|
|
|
|
|
|
|
|
flashErrorMessage('Password needs a letter and number');
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const submitBtn = document.getElementById('register-submit-btn');
|
|
|
|
|
|
|
|
submitBtn.disabled = true;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
const body = { username, password };
|
|
|
|
const body = { username, password };
|
|
|
|
if (email) body.email = email;
|
|
|
|
if (email) body.email = email;
|
|
|
@@ -213,7 +232,7 @@ async function handleRegister() {
|
|
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
if (!response.ok) {
|
|
|
|
const err = await response.json();
|
|
|
|
const err = await response.json();
|
|
|
|
flashErrorMessage(err.details || err.error || 'Registration failed');
|
|
|
|
flashErrorMessage(err.details || err.error || 'Registration failed', 3000);
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@@ -226,6 +245,8 @@ async function handleRegister() {
|
|
|
|
hideAuthModal();
|
|
|
|
hideAuthModal();
|
|
|
|
} catch (error) {
|
|
|
|
} catch (error) {
|
|
|
|
flashErrorMessage('Connection failed');
|
|
|
|
flashErrorMessage('Connection failed');
|
|
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
|
|
submitBtn.disabled = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@@ -252,17 +273,7 @@ function authFetch(url, options = {}) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function getConfig() {
|
|
|
|
async function getConfig() {
|
|
|
|
try {
|
|
|
|
return { apiUrl: '/chess' };
|
|
|
|
const response = await fetch('/config');
|
|
|
|
|
|
|
|
const contentType = response.headers.get('content-type') || '';
|
|
|
|
|
|
|
|
if (!response.ok || !contentType.includes('application/json')) {
|
|
|
|
|
|
|
|
throw new Error(`unexpected response: ${response.status} ${contentType}`);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return await response.json();
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
|
|
console.error('Failed to get config:', error);
|
|
|
|
|
|
|
|
return { apiUrl: 'http://localhost:8080' };
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function startHealthCheck() {
|
|
|
|
function startHealthCheck() {
|
|
|
@@ -972,18 +983,17 @@ function handleApiError(action, error, response = null) {
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function flashErrorMessage(message) {
|
|
|
|
function flashErrorMessage(message, duration = 1500) {
|
|
|
|
const overlay = document.getElementById('error-flash-overlay');
|
|
|
|
const overlay = document.getElementById('error-flash-overlay');
|
|
|
|
const messageEl = document.getElementById('error-flash-message');
|
|
|
|
const messageEl = document.getElementById('error-flash-message');
|
|
|
|
|
|
|
|
|
|
|
|
// Set message text
|
|
|
|
|
|
|
|
messageEl.textContent = message;
|
|
|
|
messageEl.textContent = message;
|
|
|
|
|
|
|
|
|
|
|
|
// Show overlay
|
|
|
|
|
|
|
|
overlay.classList.add('show');
|
|
|
|
overlay.classList.add('show');
|
|
|
|
|
|
|
|
|
|
|
|
// Auto-hide after animation completes
|
|
|
|
// Clear any pending timeout to avoid premature hide on rapid calls
|
|
|
|
setTimeout(() => {
|
|
|
|
if (overlay._flashTimeout) clearTimeout(overlay._flashTimeout);
|
|
|
|
|
|
|
|
overlay._flashTimeout = setTimeout(() => {
|
|
|
|
overlay.classList.remove('show');
|
|
|
|
overlay.classList.remove('show');
|
|
|
|
}, 1500);
|
|
|
|
overlay._flashTimeout = null;
|
|
|
|
|
|
|
|
}, duration);
|
|
|
|
}
|
|
|
|
}
|