class AuthManager { constructor() { this.apiBaseUrl = '/api'; // Backend API base URL this.init(); } init() { this.bindEvents(); this.initializePasswordValidation(); this.handleEmailVerification(); this.handlePasswordReset(); } bindEvents() { // Login form const loginForm = document.getElementById('loginForm'); if (loginForm) { loginForm.addEventListener('submit', (e) => this.handleLogin(e)); } // Registration form const registerForm = document.getElementById('registerForm'); if (registerForm) { registerForm.addEventListener('submit', (e) => this.handleRegistration(e)); } // Forgot password form const forgotPasswordForm = document.getElementById('forgotPasswordForm'); if (forgotPasswordForm) { forgotPasswordForm.addEventListener('submit', (e) => this.handleForgotPassword(e)); } // Reset password form const resetPasswordForm = document.getElementById('resetPasswordForm'); if (resetPasswordForm) { resetPasswordForm.addEventListener('submit', (e) => this.handleResetPassword(e)); } // Resend verification button const resendVerificationBtn = document.getElementById('resendVerificationBtn'); if (resendVerificationBtn) { resendVerificationBtn.addEventListener('click', (e) => this.handleResendVerification(e)); } } initializePasswordValidation() { const passwordInputs = document.querySelectorAll('input[type="password"]'); passwordInputs.forEach(input => { if (input.id === 'password' || input.id === 'newPassword') { input.addEventListener('input', (e) => this.validatePassword(e.target.value)); } }); // Confirm password validation const confirmPasswordInputs = document.querySelectorAll('#confirmPassword, #confirmNewPassword'); confirmPasswordInputs.forEach(input => { input.addEventListener('input', (e) => this.validatePasswordMatch(e.target)); }); } validatePassword(password) { const requirements = { 'req-length': password.length >= 8, 'req-uppercase': /[A-Z]/.test(password), 'req-lowercase': /[a-z]/.test(password), 'req-number': /\d/.test(password), 'req-special': /[!@#$%^&*(),.?":{}|<>]/.test(password) }; Object.entries(requirements).forEach(([reqId, isValid]) => { const reqElement = document.getElementById(reqId); if (reqElement) { reqElement.classList.toggle('valid', isValid); reqElement.classList.toggle('invalid', !isValid); } }); return Object.values(requirements).every(req => req); } validatePasswordMatch(confirmInput) { const passwordInput = document.getElementById('password') || document.getElementById('newPassword'); const isMatch = confirmInput.value === passwordInput.value; confirmInput.setCustomValidity(isMatch ? '' : 'Passwords do not match'); return isMatch; } async handleLogin(e) { e.preventDefault(); const form = e.target; const formData = new FormData(form); const loginBtn = document.getElementById('loginBtn'); this.setButtonLoading(loginBtn, true); this.hideMessages(); try { const response = await fetch(`${this.apiBaseUrl}/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ email: formData.get('email'), password: formData.get('password'), rememberMe: formData.get('rememberMe') === 'on' }), credentials: 'include' // Include cookies for session management }); const data = await response.json(); if (response.ok) { this.showSuccess('Login successful! Redirecting...'); // Store user info if needed if (data.user) { localStorage.setItem('user', JSON.stringify(data.user)); } // Redirect to main application setTimeout(() => { window.location.href = 'index.html'; }, 1500); } else { // Check if error is due to unverified email if (data.code === 'EMAIL_NOT_VERIFIED') { this.showEmailNotVerifiedError(formData.get('email')); } else { this.showError(data.error || 'Login failed. Please try again.'); } } } catch (error) { console.error('Login error:', error); this.showError('Network error. Please check your connection and try again.'); } finally { this.setButtonLoading(loginBtn, false); } } async handleRegistration(e) { e.preventDefault(); const form = e.target; const formData = new FormData(form); const registerBtn = document.getElementById('registerBtn'); // Validate password requirements const password = formData.get('password'); if (!this.validatePassword(password)) { this.showError('Please ensure your password meets all requirements.'); return; } // Validate password confirmation const confirmPassword = formData.get('confirmPassword'); if (password !== confirmPassword) { this.showError('Passwords do not match.'); return; } this.setButtonLoading(registerBtn, true); this.hideMessages(); try { const response = await fetch(`${this.apiBaseUrl}/auth/register`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ email: formData.get('email'), password: password }) }); const data = await response.json(); if (response.ok) { this.showSuccess('Account created successfully! Please check your email to verify your account.'); form.reset(); // Optionally redirect to login page after a delay setTimeout(() => { window.location.href = 'login.html'; }, 3000); } else { this.showError(data.error || 'Registration failed. Please try again.'); } } catch (error) { console.error('Registration error:', error); this.showError('Network error. Please check your connection and try again.'); } finally { this.setButtonLoading(registerBtn, false); } } async handleForgotPassword(e) { e.preventDefault(); const form = e.target; const formData = new FormData(form); const resetBtn = document.getElementById('resetBtn'); this.setButtonLoading(resetBtn, true); this.hideMessages(); try { const response = await fetch(`${this.apiBaseUrl}/auth/forgot-password`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ email: formData.get('email') }) }); const data = await response.json(); if (response.ok) { this.showSuccess('Password reset link sent! Please check your email.'); form.reset(); } else { this.showError(data.error || 'Failed to send reset link. Please try again.'); } } catch (error) { console.error('Forgot password error:', error); this.showError('Network error. Please check your connection and try again.'); } finally { this.setButtonLoading(resetBtn, false); } } async handleResetPassword(e) { e.preventDefault(); const form = e.target; const formData = new FormData(form); const updateBtn = document.getElementById('updatePasswordBtn'); // Validate password requirements const newPassword = formData.get('newPassword'); if (!this.validatePassword(newPassword)) { this.showError('Please ensure your password meets all requirements.'); return; } // Validate password confirmation const confirmPassword = formData.get('confirmNewPassword'); if (newPassword !== confirmPassword) { this.showError('Passwords do not match.'); return; } this.setButtonLoading(updateBtn, true); this.hideMessages(); try { const resetToken = this.getResetTokenFromUrl(); const response = await fetch(`${this.apiBaseUrl}/auth/reset-password`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ token: resetToken, newPassword: newPassword }) }); const data = await response.json(); if (response.ok) { this.showSuccess('Password updated successfully! Redirecting to login...'); setTimeout(() => { window.location.href = 'login.html'; }, 2000); } else { this.showError(data.error || 'Failed to update password. Please try again.'); } } catch (error) { console.error('Reset password error:', error); this.showError('Network error. Please check your connection and try again.'); } finally { this.setButtonLoading(updateBtn, false); } } async handleEmailVerification() { // Only run on verify-email.html page if (!window.location.pathname.includes('verify-email.html')) { return; } const token = this.getVerificationTokenFromUrl(); if (!token) { this.showVerificationError('Invalid verification link.'); return; } try { const response = await fetch(`${this.apiBaseUrl}/auth/verify/${token}`, { method: 'GET' }); const data = await response.json(); if (response.ok) { this.showVerificationSuccess(); } else { this.showVerificationError(data.error || 'Verification failed.'); } } catch (error) { console.error('Email verification error:', error); this.showVerificationError('Network error during verification.'); } } async handleResendVerification(e) { e.preventDefault(); const button = e.target; this.setButtonLoading(button, true); try { // Get email from URL parameters or prompt user const email = this.getEmailFromUrl() || prompt('Please enter your email address:'); if (!email) { this.showError('Email address is required.'); return; } const response = await fetch(`${this.apiBaseUrl}/auth/resend-verification`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ email }) }); const data = await response.json(); if (response.ok) { this.showResendSuccess(); } else { this.showError(data.error || 'Failed to resend verification email.'); } } catch (error) { console.error('Resend verification error:', error); this.showError('Network error. Please try again.'); } finally { this.setButtonLoading(button, false); } } handlePasswordReset() { // Only run on reset-password.html page if (!window.location.pathname.includes('reset-password.html')) { return; } const token = this.getResetTokenFromUrl(); if (token) { document.getElementById('resetToken').value = token; } else { this.showError('Invalid reset link.'); } } // Utility methods getVerificationTokenFromUrl() { const urlParams = new URLSearchParams(window.location.search); return urlParams.get('token'); } getResetTokenFromUrl() { const urlParams = new URLSearchParams(window.location.search); return urlParams.get('token'); } getEmailFromUrl() { const urlParams = new URLSearchParams(window.location.search); return urlParams.get('email'); } setButtonLoading(button, isLoading) { const btnText = button.querySelector('.btn-text'); const btnLoading = button.querySelector('.btn-loading'); if (isLoading) { btnText.style.display = 'none'; btnLoading.style.display = 'flex'; button.disabled = true; } else { btnText.style.display = 'block'; btnLoading.style.display = 'none'; button.disabled = false; } } showError(message) { const errorDiv = document.getElementById('authError'); const errorMessage = document.getElementById('errorMessage'); if (errorDiv && errorMessage) { errorMessage.textContent = message; errorDiv.style.display = 'block'; // Auto-hide after 5 seconds setTimeout(() => { errorDiv.style.display = 'none'; }, 5000); } } showSuccess(message) { const successDiv = document.getElementById('authSuccess'); const successMessage = document.getElementById('successMessage'); if (successDiv && successMessage) { successMessage.textContent = message; successDiv.style.display = 'block'; // Auto-hide after 5 seconds setTimeout(() => { successDiv.style.display = 'none'; }, 5000); } } hideMessages() { const errorDiv = document.getElementById('authError'); const successDiv = document.getElementById('authSuccess'); if (errorDiv) errorDiv.style.display = 'none'; if (successDiv) successDiv.style.display = 'none'; } showVerificationSuccess() { document.getElementById('verificationLoading').style.display = 'none'; document.getElementById('verificationError').style.display = 'none'; document.getElementById('verificationSuccess').style.display = 'block'; } showVerificationError(message) { document.getElementById('verificationLoading').style.display = 'none'; document.getElementById('verificationSuccess').style.display = 'none'; document.getElementById('verificationError').style.display = 'block'; const errorDescription = document.getElementById('errorDescription'); if (errorDescription) { errorDescription.textContent = message; } } showResendSuccess() { document.getElementById('verificationError').style.display = 'none'; document.getElementById('resendSuccess').style.display = 'block'; } // Check if user is authenticated (for protected pages) static async checkAuth() { try { const response = await fetch('/api/auth/me', { credentials: 'include' }); if (response.ok) { const user = await response.json(); return user; } else { return null; } } catch (error) { console.error('Auth check error:', error); return null; } } // Logout functionality static async logout() { try { await fetch('/api/auth/logout', { method: 'POST', credentials: 'include' }); // Clear local storage localStorage.removeItem('user'); // Redirect to login window.location.href = 'login.html'; } catch (error) { console.error('Logout error:', error); // Force redirect even if logout request fails window.location.href = 'login.html'; } } } // Initialize auth manager when DOM is loaded document.addEventListener('DOMContentLoaded', () => { new AuthManager(); }); // Export for use in other scripts window.AuthManager = AuthManager;