// Authentication JavaScript // API Configuration const API_BASE_URL = '/api'; // DOM Elements let currentForm = 'login'; // Initialize page document.addEventListener('DOMContentLoaded', function() { initializeAuth(); setupPasswordStrength(); setupFormValidation(); }); function initializeAuth() { // Set initial form state showLogin(); } function showLogin() { hideAllForms(); document.getElementById('loginForm').style.display = 'block'; setActiveNavButton('loginNavBtn'); currentForm = 'login'; } function showRegister() { hideAllForms(); document.getElementById('registerForm').style.display = 'block'; setActiveNavButton('registerNavBtn'); currentForm = 'register'; } function showForgotPassword() { hideAllForms(); document.getElementById('forgotPasswordForm').style.display = 'block'; clearActiveNavButtons(); currentForm = 'forgot'; } function hideAllForms() { const forms = ['loginForm', 'registerForm', 'forgotPasswordForm']; forms.forEach(formId => { document.getElementById(formId).style.display = 'none'; }); } function setActiveNavButton(activeId) { clearActiveNavButtons(); document.getElementById(activeId).classList.add('active'); } function clearActiveNavButtons() { document.querySelectorAll('.nav-btn').forEach(btn => { btn.classList.remove('active'); }); } // Password visibility toggle function togglePassword(inputId) { const input = document.getElementById(inputId); const button = input.parentElement.querySelector('.password-toggle i'); if (input.type === 'password') { input.type = 'text'; button.classList.remove('fa-eye'); button.classList.add('fa-eye-slash'); } else { input.type = 'password'; button.classList.remove('fa-eye-slash'); button.classList.add('fa-eye'); } } // Password strength checker function setupPasswordStrength() { const passwordInput = document.getElementById('registerPassword'); const strengthBar = document.querySelector('.strength-fill'); const strengthText = document.querySelector('.strength-text'); if (passwordInput) { passwordInput.addEventListener('input', function() { const password = this.value; const strength = calculatePasswordStrength(password); updatePasswordStrength(strength, strengthBar, strengthText); }); } } function calculatePasswordStrength(password) { let score = 0; let feedback = []; // Length check if (password.length >= 8) score += 20; else feedback.push('At least 8 characters'); // Uppercase check if (/[A-Z]/.test(password)) score += 20; else feedback.push('One uppercase letter'); // Lowercase check if (/[a-z]/.test(password)) score += 20; else feedback.push('One lowercase letter'); // Number check if (/\d/.test(password)) score += 20; else feedback.push('One number'); // Special character check if (/[!@#$%^&*(),.?":{}|<>]/.test(password)) score += 20; else feedback.push('One special character'); return { score, feedback }; } function updatePasswordStrength(strength, strengthBar, strengthText) { const { score, feedback } = strength; strengthBar.style.width = score + '%'; if (score < 40) { strengthBar.style.background = '#e74c3c'; strengthText.textContent = 'Weak password'; strengthText.style.color = '#e74c3c'; } else if (score < 80) { strengthBar.style.background = '#f39c12'; strengthText.textContent = 'Medium password'; strengthText.style.color = '#f39c12'; } else { strengthBar.style.background = '#27ae60'; strengthText.textContent = 'Strong password'; strengthText.style.color = '#27ae60'; } if (feedback.length > 0 && score < 100) { strengthText.textContent += ' - Missing: ' + feedback.join(', '); } } // Form validation function setupFormValidation() { // Real-time validation for username const usernameInput = document.getElementById('registerUsername'); if (usernameInput) { usernameInput.addEventListener('input', validateUsername); usernameInput.addEventListener('blur', validateUsername); } // Real-time validation for email const emailInputs = document.querySelectorAll('input[type="email"]'); emailInputs.forEach(input => { input.addEventListener('input', validateEmail); input.addEventListener('blur', validateEmail); }); // Password confirmation validation const confirmPasswordInput = document.getElementById('confirmPassword'); if (confirmPasswordInput) { confirmPasswordInput.addEventListener('input', validatePasswordConfirmation); confirmPasswordInput.addEventListener('blur', validatePasswordConfirmation); } } function validateUsername() { const input = document.getElementById('registerUsername'); const value = input.value.trim(); const inputGroup = input.parentElement; clearValidationState(inputGroup); if (value.length === 0) return; if (value.length < 3 || value.length > 80) { setValidationState(inputGroup, 'error', 'Username must be 3-80 characters'); return false; } if (!/^[a-zA-Z0-9_-]+$/.test(value)) { setValidationState(inputGroup, 'error', 'Only letters, numbers, hyphens, and underscores allowed'); return false; } setValidationState(inputGroup, 'success'); return true; } function validateEmail() { const input = this; const value = input.value.trim(); const inputGroup = input.parentElement; clearValidationState(inputGroup); if (value.length === 0) return; const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; if (!emailRegex.test(value)) { setValidationState(inputGroup, 'error', 'Please enter a valid email address'); return false; } setValidationState(inputGroup, 'success'); return true; } function validatePasswordConfirmation() { const passwordInput = document.getElementById('registerPassword'); const confirmInput = document.getElementById('confirmPassword'); const inputGroup = confirmInput.parentElement; clearValidationState(inputGroup); if (confirmInput.value.length === 0) return; if (passwordInput.value !== confirmInput.value) { setValidationState(inputGroup, 'error', 'Passwords do not match'); return false; } setValidationState(inputGroup, 'success'); return true; } function setValidationState(inputGroup, state, message = '') { inputGroup.classList.remove('error', 'success'); inputGroup.classList.add(state); // Remove existing error message const existingError = inputGroup.parentElement.querySelector('.error-message'); if (existingError) { existingError.remove(); } // Add error message if provided if (message && state === 'error') { const errorDiv = document.createElement('div'); errorDiv.className = 'error-message'; errorDiv.innerHTML = ` ${message}`; inputGroup.parentElement.appendChild(errorDiv); } } function clearValidationState(inputGroup) { inputGroup.classList.remove('error', 'success'); const existingError = inputGroup.parentElement.querySelector('.error-message'); if (existingError) { existingError.remove(); } } // Form submission handlers async function handleLogin(event) { event.preventDefault(); const form = event.target; const formData = new FormData(form); const loginData = { login: formData.get('login'), password: formData.get('password') }; const button = document.getElementById('loginBtn'); setButtonLoading(button, true); try { const response = await fetch(`${API_BASE_URL}/login`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(loginData) }); const result = await response.json(); if (response.ok) { // Store tokens localStorage.setItem('auth_token', result.auth_token); localStorage.setItem('refresh_token', result.refresh_token); localStorage.setItem('user_data', JSON.stringify(result.user)); showAlert('success', 'Login successful! Redirecting to dashboard...'); // Redirect to dashboard setTimeout(() => { window.location.href = '/dashboard.html'; }, 1500); } else { showAlert('error', result.error || 'Login failed'); } } catch (error) { console.error('Login error:', error); showAlert('error', 'Network error. Please try again.'); } finally { setButtonLoading(button, false); } } async function handleRegister(event) { event.preventDefault(); const form = event.target; const formData = new FormData(form); // Validate form if (!validateRegistrationForm(form)) { showAlert('error', 'Please fix the errors in the form'); return; } const registerData = { username: formData.get('username'), email: formData.get('email'), password: formData.get('password'), subscription_type: formData.get('subscriptionType') }; const button = document.getElementById('registerBtn'); setButtonLoading(button, true); try { const response = await fetch(`${API_BASE_URL}/register`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(registerData) }); const result = await response.json(); if (response.ok) { // Store tokens localStorage.setItem('auth_token', result.auth_token); localStorage.setItem('refresh_token', result.refresh_token); localStorage.setItem('user_data', JSON.stringify(result.user)); showAlert('success', 'Account created successfully! Redirecting to dashboard...'); // Redirect to dashboard setTimeout(() => { window.location.href = '/dashboard.html'; }, 1500); } else { showAlert('error', result.error || 'Registration failed'); } } catch (error) { console.error('Registration error:', error); showAlert('error', 'Network error. Please try again.'); } finally { setButtonLoading(button, false); } } async function handleForgotPassword(event) { event.preventDefault(); const form = event.target; const formData = new FormData(form); const email = formData.get('email'); const button = document.getElementById('forgotPasswordBtn'); setButtonLoading(button, true); try { const response = await fetch(`${API_BASE_URL}/forgot-password`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ email }) }); const result = await response.json(); if (response.ok) { showAlert('success', 'Password reset instructions sent to your email'); // Show reset token for development (remove in production) if (result.reset_token) { showAlert('info', `Development: Reset token is ${result.reset_token}`); } // Switch back to login form setTimeout(() => { showLogin(); }, 3000); } else { showAlert('error', result.error || 'Failed to send reset instructions'); } } catch (error) { console.error('Forgot password error:', error); showAlert('error', 'Network error. Please try again.'); } finally { setButtonLoading(button, false); } } function validateRegistrationForm(form) { let isValid = true; // Validate username const usernameInput = form.querySelector('#registerUsername'); if (!validateUsername.call(usernameInput)) { isValid = false; } // Validate email const emailInput = form.querySelector('#registerEmail'); if (!validateEmail.call(emailInput)) { isValid = false; } // Validate password strength const passwordInput = form.querySelector('#registerPassword'); const strength = calculatePasswordStrength(passwordInput.value); if (strength.score < 80) { const inputGroup = passwordInput.parentElement; setValidationState(inputGroup, 'error', 'Password is too weak'); isValid = false; } // Validate password confirmation if (!validatePasswordConfirmation()) { isValid = false; } // Validate terms agreement const termsCheckbox = form.querySelector('#agreeTerms'); if (!termsCheckbox.checked) { showAlert('error', 'You must agree to the Terms of Service and Privacy Policy'); isValid = false; } return isValid; } // Utility functions function setButtonLoading(button, loading) { const btnText = button.querySelector('.btn-text'); const btnLoader = button.querySelector('.btn-loader'); if (loading) { button.classList.add('loading'); button.disabled = true; btnText.style.opacity = '0'; btnLoader.style.display = 'block'; } else { button.classList.remove('loading'); button.disabled = false; btnText.style.opacity = '1'; btnLoader.style.display = 'none'; } } function showAlert(type, message, duration = 5000) { const alertContainer = document.getElementById('alertContainer'); const alert = document.createElement('div'); alert.className = `alert ${type}`; const icon = getAlertIcon(type); alert.innerHTML = ` ${message} `; alertContainer.appendChild(alert); // Auto-remove alert after duration setTimeout(() => { if (alert.parentElement) { closeAlert(alert.querySelector('.alert-close')); } }, duration); } function getAlertIcon(type) { const icons = { success: 'fas fa-check-circle', error: 'fas fa-exclamation-circle', warning: 'fas fa-exclamation-triangle', info: 'fas fa-info-circle' }; return icons[type] || icons.info; } function closeAlert(button) { const alert = button.parentElement; alert.style.animation = 'slideOutRight 0.3s ease-out forwards'; setTimeout(() => { if (alert.parentElement) { alert.parentElement.removeChild(alert); } }, 300); } async function verifyTokenAndRedirect(token) { try { const response = await fetch(`${API_BASE_URL}/profile`, { headers: { 'Authorization': `Bearer ${token}` } }); if (response.ok) { // Token is valid, redirect to dashboard window.location.href = '/dashboard.html'; } else { // Token is invalid, remove from storage localStorage.removeItem('auth_token'); localStorage.removeItem('refresh_token'); localStorage.removeItem('user_data'); } } catch (error) { console.error('Token verification error:', error); // Remove invalid tokens localStorage.removeItem('auth_token'); localStorage.removeItem('refresh_token'); localStorage.removeItem('user_data'); } } // Add CSS animation for slide out const style = document.createElement('style'); style.textContent = ` @keyframes slideOutRight { from { opacity: 1; transform: translateX(0); } to { opacity: 0; transform: translateX(100%); } } `; document.head.appendChild(style);