Spaces:
Sleeping
Sleeping
sachin1801
help page revamp according to requirements, removed tutorial page, search filter improved on history, login with email pass created
068e060 | /** | |
| * Authentication utilities for client-side auth management | |
| * Supports navbar dropdown auth with auto-registration | |
| */ | |
| const AUTH_TOKEN_KEY = 'auth_token'; | |
| const USER_DATA_KEY = 'user_data'; | |
| const ACCESS_TOKEN_KEY = 'access_token'; | |
| /** | |
| * Get the stored auth token | |
| */ | |
| function getAuthToken() { | |
| return localStorage.getItem(AUTH_TOKEN_KEY); | |
| } | |
| /** | |
| * Get the stored user data | |
| */ | |
| function getUserData() { | |
| const data = localStorage.getItem(USER_DATA_KEY); | |
| return data ? JSON.parse(data) : null; | |
| } | |
| /** | |
| * Get the access token (for anonymous history) | |
| */ | |
| function getAccessToken() { | |
| let token = localStorage.getItem(ACCESS_TOKEN_KEY); | |
| if (!token) { | |
| token = 'user_' + Math.random().toString(36).substr(2, 9) + '_' + Date.now(); | |
| localStorage.setItem(ACCESS_TOKEN_KEY, token); | |
| } | |
| return token; | |
| } | |
| /** | |
| * Save auth data to localStorage | |
| */ | |
| function saveAuthData(token, user) { | |
| localStorage.setItem(AUTH_TOKEN_KEY, token); | |
| localStorage.setItem(USER_DATA_KEY, JSON.stringify(user)); | |
| } | |
| /** | |
| * Clear auth data from localStorage | |
| */ | |
| function clearAuthData() { | |
| localStorage.removeItem(AUTH_TOKEN_KEY); | |
| localStorage.removeItem(USER_DATA_KEY); | |
| } | |
| /** | |
| * Check if user is logged in | |
| */ | |
| function isLoggedIn() { | |
| return !!getAuthToken() && !!getUserData(); | |
| } | |
| /** | |
| * Toggle auth dropdown visibility | |
| */ | |
| function toggleAuthDropdown() { | |
| const dropdown = document.getElementById('auth-dropdown'); | |
| if (dropdown) { | |
| dropdown.classList.toggle('hidden'); | |
| // Reset form when opening | |
| if (!dropdown.classList.contains('hidden')) { | |
| resetAuthForm(); | |
| } | |
| } | |
| } | |
| /** | |
| * Toggle user dropdown visibility | |
| */ | |
| function toggleUserDropdown() { | |
| const dropdown = document.getElementById('user-dropdown'); | |
| if (dropdown) { | |
| dropdown.classList.toggle('hidden'); | |
| } | |
| } | |
| /** | |
| * Toggle mobile auth form visibility | |
| */ | |
| function toggleMobileAuthForm() { | |
| const form = document.getElementById('mobile-auth-form'); | |
| if (form) { | |
| form.classList.toggle('hidden'); | |
| if (!form.classList.contains('hidden')) { | |
| resetMobileAuthForm(); | |
| } | |
| } | |
| } | |
| /** | |
| * Reset the auth form to its initial state | |
| */ | |
| function resetAuthForm() { | |
| const email = document.getElementById('auth-email'); | |
| const password = document.getElementById('auth-password'); | |
| const message = document.getElementById('auth-message'); | |
| const btnText = document.getElementById('auth-btn-text'); | |
| const loading = document.getElementById('auth-loading'); | |
| const submitBtn = document.getElementById('auth-submit-btn'); | |
| if (email) email.value = ''; | |
| if (password) password.value = ''; | |
| if (message) { | |
| message.classList.add('hidden'); | |
| message.className = 'hidden mb-3 p-3 rounded-md text-sm'; | |
| } | |
| if (btnText) btnText.textContent = 'Continue'; | |
| if (loading) loading.classList.add('hidden'); | |
| if (submitBtn) submitBtn.disabled = false; | |
| } | |
| /** | |
| * Reset mobile auth form | |
| */ | |
| function resetMobileAuthForm() { | |
| const email = document.getElementById('mobile-auth-email'); | |
| const password = document.getElementById('mobile-auth-password'); | |
| const message = document.getElementById('mobile-auth-message'); | |
| if (email) email.value = ''; | |
| if (password) password.value = ''; | |
| if (message) { | |
| message.classList.add('hidden'); | |
| message.className = 'hidden p-3 rounded-md text-sm'; | |
| } | |
| } | |
| /** | |
| * Show message in auth dropdown | |
| */ | |
| function showAuthMessage(message, isError = false) { | |
| const messageEl = document.getElementById('auth-message'); | |
| if (messageEl) { | |
| messageEl.textContent = message; | |
| messageEl.classList.remove('hidden', 'bg-green-50', 'text-green-800', 'bg-red-50', 'text-red-800'); | |
| if (isError) { | |
| messageEl.classList.add('bg-red-50', 'text-red-800'); | |
| } else { | |
| messageEl.classList.add('bg-green-50', 'text-green-800'); | |
| } | |
| } | |
| } | |
| /** | |
| * Show message in mobile auth form | |
| */ | |
| function showMobileAuthMessage(message, isError = false) { | |
| const messageEl = document.getElementById('mobile-auth-message'); | |
| if (messageEl) { | |
| messageEl.textContent = message; | |
| messageEl.classList.remove('hidden', 'bg-green-50', 'text-green-800', 'bg-red-50', 'text-red-800'); | |
| if (isError) { | |
| messageEl.classList.add('bg-red-50', 'text-red-800'); | |
| } else { | |
| messageEl.classList.add('bg-green-50', 'text-green-800'); | |
| } | |
| } | |
| } | |
| /** | |
| * Set loading state for auth form | |
| */ | |
| function setAuthLoading(loading) { | |
| const btnText = document.getElementById('auth-btn-text'); | |
| const spinner = document.getElementById('auth-loading'); | |
| const submitBtn = document.getElementById('auth-submit-btn'); | |
| if (loading) { | |
| if (btnText) btnText.textContent = 'Please wait...'; | |
| if (spinner) spinner.classList.remove('hidden'); | |
| if (submitBtn) submitBtn.disabled = true; | |
| } else { | |
| if (btnText) btnText.textContent = 'Continue'; | |
| if (spinner) spinner.classList.add('hidden'); | |
| if (submitBtn) submitBtn.disabled = false; | |
| } | |
| } | |
| /** | |
| * Unified auth submit - handles both login and auto-registration | |
| */ | |
| async function submitAuth() { | |
| const email = document.getElementById('auth-email')?.value?.trim(); | |
| const password = document.getElementById('auth-password')?.value; | |
| if (!email || !password) { | |
| showAuthMessage('Please enter email and password', true); | |
| return; | |
| } | |
| if (password.length < 8) { | |
| showAuthMessage('Password must be at least 8 characters', true); | |
| return; | |
| } | |
| setAuthLoading(true); | |
| try { | |
| // First, try to login | |
| const loginResponse = await fetch('/api/auth/login', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ email, password }), | |
| }); | |
| const loginData = await loginResponse.json(); | |
| if (loginResponse.ok) { | |
| // Login successful | |
| saveAuthData(loginData.token, loginData.user); | |
| showAuthMessage('Welcome back!'); | |
| updateAuthUI(); | |
| // Close dropdown after a short delay | |
| setTimeout(() => { | |
| toggleAuthDropdown(); | |
| // Refresh the page to update any user-specific content | |
| window.location.reload(); | |
| }, 1000); | |
| return; | |
| } | |
| // Check if user not found (need to register) | |
| // FastAPI returns detail as object: {"detail": {"message": "...", "error_code": "..."}} | |
| const errorCode = loginData.detail?.error_code || loginData.error_code; | |
| if (errorCode === 'USER_NOT_FOUND') { | |
| // Auto-register | |
| const registerResponse = await fetch('/api/auth/register', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ email, password }), | |
| }); | |
| const registerData = await registerResponse.json(); | |
| if (registerResponse.ok) { | |
| // Registration successful | |
| saveAuthData(registerData.token, registerData.user); | |
| // Link existing access token | |
| const existingToken = localStorage.getItem(ACCESS_TOKEN_KEY); | |
| if (existingToken) { | |
| await linkAccessToken(existingToken); | |
| } | |
| showAuthMessage('Account created! Your history is linked.'); | |
| updateAuthUI(); | |
| setTimeout(() => { | |
| toggleAuthDropdown(); | |
| window.location.reload(); | |
| }, 1500); | |
| return; | |
| } else { | |
| showAuthMessage(registerData.detail || 'Registration failed', true); | |
| } | |
| } else if (errorCode === 'INVALID_PASSWORD') { | |
| // Wrong password | |
| showAuthMessage('Incorrect password', true); | |
| } else { | |
| // Other login error | |
| const errorMsg = loginData.detail?.message || loginData.detail || 'Login failed'; | |
| showAuthMessage(errorMsg, true); | |
| } | |
| } catch (error) { | |
| showAuthMessage('Network error. Please try again.', true); | |
| } finally { | |
| setAuthLoading(false); | |
| } | |
| } | |
| /** | |
| * Mobile auth submit | |
| */ | |
| async function submitMobileAuth() { | |
| const email = document.getElementById('mobile-auth-email')?.value?.trim(); | |
| const password = document.getElementById('mobile-auth-password')?.value; | |
| if (!email || !password) { | |
| showMobileAuthMessage('Please enter email and password', true); | |
| return; | |
| } | |
| if (password.length < 8) { | |
| showMobileAuthMessage('Password must be at least 8 characters', true); | |
| return; | |
| } | |
| try { | |
| // First, try to login | |
| const loginResponse = await fetch('/api/auth/login', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ email, password }), | |
| }); | |
| const loginData = await loginResponse.json(); | |
| if (loginResponse.ok) { | |
| saveAuthData(loginData.token, loginData.user); | |
| showMobileAuthMessage('Welcome back!'); | |
| updateAuthUI(); | |
| setTimeout(() => window.location.reload(), 1000); | |
| return; | |
| } | |
| const mobileErrorCode = loginData.detail?.error_code || loginData.error_code; | |
| if (mobileErrorCode === 'USER_NOT_FOUND') { | |
| const registerResponse = await fetch('/api/auth/register', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ email, password }), | |
| }); | |
| const registerData = await registerResponse.json(); | |
| if (registerResponse.ok) { | |
| saveAuthData(registerData.token, registerData.user); | |
| const existingToken = localStorage.getItem(ACCESS_TOKEN_KEY); | |
| if (existingToken) { | |
| await linkAccessToken(existingToken); | |
| } | |
| showMobileAuthMessage('Account created!'); | |
| updateAuthUI(); | |
| setTimeout(() => window.location.reload(), 1500); | |
| return; | |
| } else { | |
| showMobileAuthMessage(registerData.detail || 'Registration failed', true); | |
| } | |
| } else if (mobileErrorCode === 'INVALID_PASSWORD') { | |
| showMobileAuthMessage('Incorrect password', true); | |
| } else { | |
| const mobileErrorMsg = loginData.detail?.message || loginData.detail || 'Login failed'; | |
| showMobileAuthMessage(mobileErrorMsg, true); | |
| } | |
| } catch (error) { | |
| showMobileAuthMessage('Network error. Please try again.', true); | |
| } | |
| } | |
| /** | |
| * Logout user | |
| */ | |
| function logoutUser() { | |
| clearAuthData(); | |
| updateAuthUI(); | |
| window.location.href = '/'; | |
| } | |
| /** | |
| * Link an access token to the user account | |
| */ | |
| async function linkAccessToken(accessToken) { | |
| const authToken = getAuthToken(); | |
| if (!authToken) { | |
| return { success: false, message: 'Not logged in' }; | |
| } | |
| try { | |
| const response = await fetch(`/api/auth/link-token?token=${encodeURIComponent(authToken)}`, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({ access_token: accessToken }), | |
| }); | |
| const data = await response.json(); | |
| if (!response.ok) { | |
| return { success: false, message: data.detail || 'Failed to link token' }; | |
| } | |
| // Update stored user data | |
| if (data.user) { | |
| localStorage.setItem(USER_DATA_KEY, JSON.stringify(data.user)); | |
| } | |
| return { success: true, message: data.message }; | |
| } catch (error) { | |
| return { success: false, message: error.message || 'Network error' }; | |
| } | |
| } | |
| /** | |
| * Get current user info from server | |
| */ | |
| async function getCurrentUser() { | |
| const token = getAuthToken(); | |
| if (!token) { | |
| return null; | |
| } | |
| try { | |
| const response = await fetch(`/api/auth/me?token=${encodeURIComponent(token)}`); | |
| if (!response.ok) { | |
| // Token invalid or expired | |
| clearAuthData(); | |
| return null; | |
| } | |
| const user = await response.json(); | |
| localStorage.setItem(USER_DATA_KEY, JSON.stringify(user)); | |
| return user; | |
| } catch (error) { | |
| return null; | |
| } | |
| } | |
| /** | |
| * Update the auth UI in the navbar | |
| */ | |
| function updateAuthUI() { | |
| const user = getUserData(); | |
| // Desktop elements | |
| const loggedOut = document.getElementById('auth-logged-out'); | |
| const loggedIn = document.getElementById('auth-logged-in'); | |
| const emailDisplay = document.getElementById('user-email-display'); | |
| // Mobile elements | |
| const mobileLoggedOut = document.getElementById('mobile-auth-logged-out'); | |
| const mobileLoggedIn = document.getElementById('mobile-auth-logged-in'); | |
| const mobileEmail = document.getElementById('mobile-user-email'); | |
| if (user) { | |
| // Logged in state | |
| if (loggedOut) loggedOut.classList.add('hidden'); | |
| if (loggedIn) loggedIn.classList.remove('hidden'); | |
| if (emailDisplay) emailDisplay.textContent = user.email; | |
| if (mobileLoggedOut) mobileLoggedOut.classList.add('hidden'); | |
| if (mobileLoggedIn) mobileLoggedIn.classList.remove('hidden'); | |
| if (mobileEmail) mobileEmail.textContent = user.email; | |
| } else { | |
| // Logged out state | |
| if (loggedOut) loggedOut.classList.remove('hidden'); | |
| if (loggedIn) loggedIn.classList.add('hidden'); | |
| if (mobileLoggedOut) mobileLoggedOut.classList.remove('hidden'); | |
| if (mobileLoggedIn) mobileLoggedIn.classList.add('hidden'); | |
| } | |
| } | |
| // Close dropdowns when clicking outside | |
| document.addEventListener('click', function(event) { | |
| const authDropdown = document.getElementById('auth-dropdown'); | |
| const authContainer = document.getElementById('auth-logged-out'); | |
| const userDropdown = document.getElementById('user-dropdown'); | |
| const userContainer = document.getElementById('auth-logged-in'); | |
| // Close auth dropdown if clicked outside | |
| if (authDropdown && authContainer && !authContainer.contains(event.target)) { | |
| authDropdown.classList.add('hidden'); | |
| } | |
| // Close user dropdown if clicked outside | |
| if (userDropdown && userContainer && !userContainer.contains(event.target)) { | |
| userDropdown.classList.add('hidden'); | |
| } | |
| }); | |
| // Handle Enter key in auth form | |
| document.addEventListener('keydown', function(event) { | |
| if (event.key === 'Enter') { | |
| const authDropdown = document.getElementById('auth-dropdown'); | |
| if (authDropdown && !authDropdown.classList.contains('hidden')) { | |
| const activeElement = document.activeElement; | |
| if (activeElement && (activeElement.id === 'auth-email' || activeElement.id === 'auth-password')) { | |
| event.preventDefault(); | |
| submitAuth(); | |
| } | |
| } | |
| const mobileForm = document.getElementById('mobile-auth-form'); | |
| if (mobileForm && !mobileForm.classList.contains('hidden')) { | |
| const activeElement = document.activeElement; | |
| if (activeElement && (activeElement.id === 'mobile-auth-email' || activeElement.id === 'mobile-auth-password')) { | |
| event.preventDefault(); | |
| submitMobileAuth(); | |
| } | |
| } | |
| } | |
| }); | |
| // Initialize auth UI on page load | |
| document.addEventListener('DOMContentLoaded', function() { | |
| updateAuthUI(); | |
| }); | |