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();
});