// DOM Elements const registerSection = document.getElementById('register-section'); const loginSection = document.getElementById('login-section'); const welcomeSection = document.getElementById('welcome-section'); const registerForm = document.getElementById('register-form'); const loginForm = document.getElementById('login-form'); const registerStatus = document.getElementById('register-status'); const loginStatus = document.getElementById('login-status'); const welcomeMessage = document.getElementById('welcome-message'); const logoutButton = document.getElementById('logout-button'); const notificationsDiv = document.getElementById('notifications'); const API_URL = '/api'; // Use relative path let webSocket = null; let authToken = localStorage.getItem('authToken'); // Load token on script start let currentTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; // Setup theme change listener const themeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); themeMediaQuery.addEventListener('change', e => { currentTheme = e.matches ? 'dark' : 'light'; updateThemeNotification(); }); // --- UI Control --- function showSection(sectionId) { // Hide all sections first with a fade effect fadeOut(registerSection); fadeOut(loginSection); fadeOut(welcomeSection); // Show the requested section with a fade-in effect setTimeout(() => { registerSection.style.display = 'none'; loginSection.style.display = 'none'; welcomeSection.style.display = 'none'; const sectionToShow = document.getElementById(sectionId); if (sectionToShow) { sectionToShow.style.display = 'block'; fadeIn(sectionToShow); } else { loginSection.style.display = 'block'; fadeIn(loginSection); } }, 300); // Wait for fade out to complete } function fadeOut(element) { if (element && element.style.display !== 'none') { element.style.opacity = '1'; element.style.transition = 'opacity 300ms'; element.style.opacity = '0'; } } function fadeIn(element) { if (element) { element.style.opacity = '0'; element.style.transition = 'opacity 300ms'; setTimeout(() => { element.style.opacity = '1'; }, 50); } } function setStatus(element, message, isSuccess = false) { if (!message) { element.style.display = 'none'; return; } element.textContent = message; element.className = isSuccess ? 'status-message success' : 'status-message'; // Show with animation element.style.display = 'block'; element.style.opacity = '0'; element.style.transform = 'translateY(-10px)'; element.style.transition = 'opacity 300ms, transform 300ms'; setTimeout(() => { element.style.opacity = '1'; element.style.transform = 'translateY(0)'; }, 10); } // --- API Calls --- async function apiRequest(endpoint, method = 'GET', body = null, token = null) { const headers = { 'Content-Type': 'application/json' }; if (token) { headers['Authorization'] = `Bearer ${token}`; } const options = { method: method, headers: headers, }; if (body && method !== 'GET') { options.body = JSON.stringify(body); } try { const response = await fetch(`${API_URL}${endpoint}`, options); const data = await response.json(); // Try to parse JSON regardless of status if (!response.ok) { // Use detail from JSON if available, otherwise status text const errorMessage = data?.detail || response.statusText || `HTTP error ${response.status}`; console.error("API Error:", errorMessage); throw new Error(errorMessage); } return data; } catch (error) { console.error(`Error during API request to ${endpoint}:`, error); throw error; // Re-throw to be caught by caller } } // --- WebSocket Handling --- function connectWebSocket(token) { if (!token) { console.error("No token available for WebSocket connection."); return; } if (webSocket && webSocket.readyState === WebSocket.OPEN) { console.log("WebSocket already connected."); return; } // Construct WebSocket URL dynamically (replace http/https with ws/wss) const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = `${wsProtocol}//${window.location.host}${API_URL}/ws/${token}`; console.log("Attempting to connect WebSocket:", wsUrl); webSocket = new WebSocket(wsUrl); webSocket.onopen = (event) => { console.log("WebSocket connection opened:", event); // Clear placeholder message on successful connect if (notificationsDiv.querySelector('p em')) { notificationsDiv.innerHTML = ''; } }; webSocket.onmessage = (event) => { console.log("WebSocket message received:", event.data); try { const messageData = JSON.parse(event.data); if (messageData.type === 'new_user' && messageData.message) { addNotification(messageData.message); } } catch (error) { console.error("Error parsing WebSocket message:", error); } }; webSocket.onerror = (event) => { console.error("WebSocket error:", event); addNotification(`WebSocket error occurred. Notifications may stop.`, 'error'); }; webSocket.onclose = (event) => { console.log("WebSocket connection closed:", event); webSocket = null; // Reset WebSocket variable if (!event.wasClean) { addNotification(`WebSocket disconnected unexpectedly (Code: ${event.code}). Please refresh or log out/in.`, 'warning'); } }; } function addNotification(message, type = 'info') { const p = document.createElement('p'); p.classList.add(type); // Add icon based on notification type let icon = 'bell'; if (type === 'error') icon = 'exclamation-circle'; if (type === 'warning') icon = 'exclamation-triangle'; if (type === 'success') icon = 'check-circle'; p.innerHTML = ` ${message}`; // Add message to the top with animation p.style.opacity = '0'; p.style.transform = 'translateY(-10px)'; notificationsDiv.insertBefore(p, notificationsDiv.firstChild); // Trigger animation setTimeout(() => { p.style.transition = 'opacity 300ms, transform 300ms'; p.style.opacity = '1'; p.style.transform = 'translateY(0)'; }, 10); // Limit number of messages shown (optional) while (notificationsDiv.children.length > 10) { notificationsDiv.removeChild(notificationsDiv.lastChild); } } function disconnectWebSocket() { if (webSocket) { console.log("Closing WebSocket connection."); webSocket.close(); webSocket = null; } } // --- Authentication Logic --- async function handleLogin(email, password) { setStatus(loginStatus, "Logging in..."); try { const data = await apiRequest('/login', 'POST', { email, password }); if (data.access_token) { authToken = data.access_token; localStorage.setItem('authToken', authToken); // Store token setStatus(loginStatus, ""); // Clear status await showWelcomePage(); // Fetch user info and show welcome } else { throw new Error("Login failed: No token received."); } } catch (error) { setStatus(loginStatus, `Login failed: ${error.message}`); } } async function handleRegister(email, password) { setStatus(registerStatus, "Registering..."); try { const data = await apiRequest('/register', 'POST', { email, password }); setStatus(registerStatus, `Registration successful for ${data.email}! Please log in.`, true); registerForm.reset(); // Clear form showSection('login-section'); // Switch to login } catch (error) { setStatus(registerStatus, `Registration failed: ${error.message}`); } } // Function to show current theme notification function updateThemeNotification() { if (welcomeSection.style.display !== 'none') { addNotification(`Theme switched to ${currentTheme} mode based on your system settings`, 'info'); } } async function showWelcomePage() { if (!authToken) { showSection('login-section'); return; } try { // Fetch user details using the token const user = await apiRequest('/users/me', 'GET', null, authToken); welcomeMessage.innerHTML = ` Welcome back, ${user.email}!`; showSection('welcome-section'); connectWebSocket(authToken); // Connect WS after successful login and user fetch // Add a welcome notification addNotification(`You are now logged in as ${user.email}`, 'success'); // Add theme notification setTimeout(() => { addNotification(`Currently using ${currentTheme} theme based on your system settings`, 'info'); }, 1000); } catch (error) { // If fetching user fails (e.g., invalid/expired token), logout console.error("Failed to fetch user details:", error); handleLogout(); } } function handleLogout() { authToken = null; localStorage.removeItem('authToken'); disconnectWebSocket(); setStatus(loginStatus, ""); // Clear any previous login errors showSection('login-section'); } // --- Form Validation --- function validatePassword(password, confirmPassword) { if (password.length < 8) { return "Password must be at least 8 characters."; } if (password !== confirmPassword) { return "Passwords do not match."; } return null; // No error } // --- Event Listeners --- registerForm.addEventListener('submit', (e) => { e.preventDefault(); const email = document.getElementById('reg-email').value; const password = document.getElementById('reg-password').value; const confirmPassword = document.getElementById('reg-confirm-password').value; const passwordError = validatePassword(password, confirmPassword); if (passwordError) { setStatus(registerStatus, passwordError); return; } handleRegister(email, password); }); loginForm.addEventListener('submit', (e) => { e.preventDefault(); const email = document.getElementById('login-email').value; const password = document.getElementById('login-password').value; handleLogin(email, password); }); logoutButton.addEventListener('click', () => { // Add a confirmation dialog if (confirm('Are you sure you want to log out?')) { handleLogout(); } }); // --- Initial Page Load Logic --- document.addEventListener('DOMContentLoaded', () => { if (authToken) { console.log("Token found, attempting to show welcome page."); showWelcomePage(); // Try to fetch user info and show welcome } else { console.log("No token found, showing login page."); showSection('login-section'); // Show login if no token } });