|
|
|
|
|
|
|
|
|
|
|
const API_BASE_URL = '/api'; |
|
|
|
|
|
|
|
|
let currentForm = 'login'; |
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
initializeAuth(); |
|
|
setupPasswordStrength(); |
|
|
setupFormValidation(); |
|
|
}); |
|
|
|
|
|
function initializeAuth() { |
|
|
|
|
|
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'); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
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'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
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 = []; |
|
|
|
|
|
|
|
|
if (password.length >= 8) score += 20; |
|
|
else feedback.push('At least 8 characters'); |
|
|
|
|
|
|
|
|
if (/[A-Z]/.test(password)) score += 20; |
|
|
else feedback.push('One uppercase letter'); |
|
|
|
|
|
|
|
|
if (/[a-z]/.test(password)) score += 20; |
|
|
else feedback.push('One lowercase letter'); |
|
|
|
|
|
|
|
|
if (/\d/.test(password)) score += 20; |
|
|
else feedback.push('One number'); |
|
|
|
|
|
|
|
|
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(', '); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function setupFormValidation() { |
|
|
|
|
|
const usernameInput = document.getElementById('registerUsername'); |
|
|
if (usernameInput) { |
|
|
usernameInput.addEventListener('input', validateUsername); |
|
|
usernameInput.addEventListener('blur', validateUsername); |
|
|
} |
|
|
|
|
|
|
|
|
const emailInputs = document.querySelectorAll('input[type="email"]'); |
|
|
emailInputs.forEach(input => { |
|
|
input.addEventListener('input', validateEmail); |
|
|
input.addEventListener('blur', validateEmail); |
|
|
}); |
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
|
|
|
const existingError = inputGroup.parentElement.querySelector('.error-message'); |
|
|
if (existingError) { |
|
|
existingError.remove(); |
|
|
} |
|
|
|
|
|
|
|
|
if (message && state === 'error') { |
|
|
const errorDiv = document.createElement('div'); |
|
|
errorDiv.className = 'error-message'; |
|
|
errorDiv.innerHTML = `<i class="fas fa-exclamation-circle"></i> ${message}`; |
|
|
inputGroup.parentElement.appendChild(errorDiv); |
|
|
} |
|
|
} |
|
|
|
|
|
function clearValidationState(inputGroup) { |
|
|
inputGroup.classList.remove('error', 'success'); |
|
|
const existingError = inputGroup.parentElement.querySelector('.error-message'); |
|
|
if (existingError) { |
|
|
existingError.remove(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
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) { |
|
|
|
|
|
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...'); |
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
|
|
|
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) { |
|
|
|
|
|
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...'); |
|
|
|
|
|
|
|
|
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'); |
|
|
|
|
|
|
|
|
if (result.reset_token) { |
|
|
showAlert('info', `Development: Reset token is ${result.reset_token}`); |
|
|
} |
|
|
|
|
|
|
|
|
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; |
|
|
|
|
|
|
|
|
const usernameInput = form.querySelector('#registerUsername'); |
|
|
if (!validateUsername.call(usernameInput)) { |
|
|
isValid = false; |
|
|
} |
|
|
|
|
|
|
|
|
const emailInput = form.querySelector('#registerEmail'); |
|
|
if (!validateEmail.call(emailInput)) { |
|
|
isValid = false; |
|
|
} |
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
|
|
|
|
|
|
if (!validatePasswordConfirmation()) { |
|
|
isValid = false; |
|
|
} |
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
|
|
|
|
|
|
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 = ` |
|
|
<i class="${icon}"></i> |
|
|
<span>${message}</span> |
|
|
<button class="alert-close" onclick="closeAlert(this)"> |
|
|
<i class="fas fa-times"></i> |
|
|
</button> |
|
|
`; |
|
|
|
|
|
alertContainer.appendChild(alert); |
|
|
|
|
|
|
|
|
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) { |
|
|
|
|
|
window.location.href = '/dashboard.html'; |
|
|
} else { |
|
|
|
|
|
localStorage.removeItem('auth_token'); |
|
|
localStorage.removeItem('refresh_token'); |
|
|
localStorage.removeItem('user_data'); |
|
|
} |
|
|
} catch (error) { |
|
|
console.error('Token verification error:', error); |
|
|
|
|
|
localStorage.removeItem('auth_token'); |
|
|
localStorage.removeItem('refresh_token'); |
|
|
localStorage.removeItem('user_data'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
|