import { showToast } from "./toast.js"; import { showSection } from './nav.js'; // Utility function to get cookie value by name function getCookie(name) { const value = `; ${document.cookie}`; const parts = value.split(`; ${name}=`); if (parts.length === 2) return parts.pop().split(';').shift(); return null; } // dashboard.js โ toggle guest vs. user dashboard and reposition streams link // Global state let isLoggingOut = false; async function handleLogout(event) { console.log('[LOGOUT] Logout initiated'); // Prevent multiple simultaneous logout attempts if (isLoggingOut) { console.log('[LOGOUT] Logout already in progress'); return; } isLoggingOut = true; // Prevent default button behavior if (event) { event.preventDefault(); event.stopPropagation(); } try { // Get auth token before we clear it const authToken = localStorage.getItem('authToken'); // 1. Clear all client-side state first (most important) console.log('[LOGOUT] Clearing all client-side state'); // Clear localStorage and sessionStorage const storageKeys = [ 'uid', 'uid_time', 'confirmed_uid', 'last_page', 'isAuthenticated', 'authToken', 'user', 'token', 'sessionid', 'sessionId' ]; storageKeys.forEach(key => { localStorage.removeItem(key); sessionStorage.removeItem(key); }); // Get all current cookies for debugging const allCookies = document.cookie.split(';'); console.log('[LOGOUT] Current cookies before clearing:', allCookies); // Clear ALL cookies (aggressive approach) allCookies.forEach(cookie => { const [name] = cookie.trim().split('='); if (name) { const cookieName = name.trim(); console.log(`[LOGOUT] Clearing cookie: ${cookieName}`); // Try multiple clearing strategies to ensure cookies are removed const clearStrategies = [ `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`, `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=${window.location.hostname};`, `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=.${window.location.hostname};`, `${cookieName}=; max-age=0; path=/;`, `${cookieName}=; max-age=0; path=/; domain=${window.location.hostname};` ]; clearStrategies.forEach(strategy => { document.cookie = strategy; }); } }); // Verify cookies are cleared const remainingCookies = document.cookie.split(';').filter(c => c.trim()); console.log('[LOGOUT] Remaining cookies after clearing:', remainingCookies); // Update UI state document.body.classList.remove('authenticated', 'logged-in'); document.body.classList.add('guest'); // 2. Try to invalidate server session (non-blocking) if (authToken) { try { console.log('[LOGOUT] Attempting to invalidate server session'); const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 2000); const response = await fetch('/api/logout', { method: 'POST', credentials: 'include', signal: controller.signal, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${authToken}` }, }); clearTimeout(timeoutId); console.log('[LOGOUT] Server session invalidation completed'); } catch (error) { console.warn('[LOGOUT] Server session invalidation failed (non-critical):', error); } } // 3. Final redirect console.log('[LOGOUT] Redirecting to home page'); window.location.href = '/?logout=' + Date.now(); } catch (error) { console.error('[LOGOUT] Unexpected error during logout:', error); if (window.showToast) { showToast('Logout failed. Please try again.'); } // Even if there's an error, force redirect to clear state window.location.href = '/?logout=error-' + Date.now(); } finally { isLoggingOut = false; } } // Delete account function async function handleDeleteAccount() { try { const uid = localStorage.getItem('uid'); if (!uid) { showToast('No user session found. Please log in again.'); return; } // Show confirmation dialog const confirmed = confirm('โ ๏ธ WARNING: This will permanently delete your account and all your data. This action cannot be undone.\n\nAre you sure you want to delete your account?'); if (!confirmed) { return; // User cancelled the deletion } // Show loading state const deleteButton = document.getElementById('delete-account-button'); const originalText = deleteButton.textContent; deleteButton.disabled = true; deleteButton.textContent = 'Deleting...'; // Call the delete account endpoint const response = await fetch(`/api/delete-account`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ uid }), }); const result = await response.json(); if (response.ok) { showToast('Account deleted successfully'); // Use comprehensive logout logic to clear all cookies and storage console.log('๐งน Account deleted - clearing all authentication data...'); // Clear all authentication-related data from localStorage const keysToRemove = [ 'uid', 'uid_time', 'confirmed_uid', 'last_page', 'isAuthenticated', 'authToken', 'user', 'token', 'sessionid' ]; keysToRemove.forEach(key => { if (localStorage.getItem(key)) { console.log(`Removing localStorage key: ${key}`); localStorage.removeItem(key); } }); // Clear sessionStorage completely sessionStorage.clear(); console.log('Cleared sessionStorage'); // Clear all cookies using multiple strategies const clearCookie = (cookieName) => { const clearStrategies = [ `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`, `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=${window.location.hostname};`, `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=.${window.location.hostname};`, `${cookieName}=; max-age=0; path=/;`, `${cookieName}=; max-age=0; path=/; domain=${window.location.hostname};` ]; clearStrategies.forEach(strategy => { document.cookie = strategy; }); console.log(`Cleared cookie: ${cookieName}`); }; // Clear all cookies by setting them to expire in the past document.cookie.split(';').forEach(cookie => { const [name] = cookie.trim().split('='); if (name) { clearCookie(name.trim()); } }); // Also specifically clear known authentication cookies const authCookies = ['authToken', 'isAuthenticated', 'sessionId', 'uid', 'token']; authCookies.forEach(clearCookie); // Log remaining cookies for verification console.log('Remaining cookies after deletion cleanup:', document.cookie); // Update UI state document.body.classList.remove('authenticated'); document.body.classList.add('guest'); // Redirect to home page setTimeout(() => { window.location.href = '/'; }, 1000); } else { throw new Error(result.detail || 'Failed to delete account'); } } catch (error) { console.error('Delete account failed:', error); showToast(`Failed to delete account: ${error.message}`); // Reset button state const deleteButton = document.getElementById('delete-account-button'); if (deleteButton) { deleteButton.disabled = false; deleteButton.textContent = '๐๏ธ Delete Account'; } } } // Debug function to check element visibility and styles function debugElementVisibility(elementId) { const el = document.getElementById(elementId); if (!el) { console.error(`[DEBUG] Element ${elementId} not found`); return {}; } const style = window.getComputedStyle(el); return { id: elementId, exists: true, display: style.display, visibility: style.visibility, opacity: style.opacity, hidden: el.hidden, classList: Array.from(el.classList), parentDisplay: el.parentElement ? window.getComputedStyle(el.parentElement).display : 'no-parent', parentVisibility: el.parentElement ? window.getComputedStyle(el.parentElement).visibility : 'no-parent', rect: el.getBoundingClientRect() }; } /** * Initialize the dashboard and handle authentication state */ async function initDashboard() { console.log('[DASHBOARD] Initializing dashboard...'); try { const guestDashboard = document.getElementById('guest-dashboard'); const userDashboard = document.getElementById('user-dashboard'); const userUpload = document.getElementById('user-upload-area'); const logoutButton = document.getElementById('logout-button'); const deleteAccountButton = document.getElementById('delete-account-button'); const fileList = document.getElementById('file-list'); if (logoutButton) { logoutButton.addEventListener('click', handleLogout); } if (deleteAccountButton) { deleteAccountButton.addEventListener('click', (e) => { e.preventDefault(); handleDeleteAccount(); }); } const isAuthenticated = (document.cookie.includes('isAuthenticated=true') || localStorage.getItem('isAuthenticated') === 'true'); if (isAuthenticated) { document.body.classList.add('authenticated'); document.body.classList.remove('guest-mode'); if (userDashboard) userDashboard.style.display = 'block'; if (userUpload) userUpload.style.display = 'block'; if (guestDashboard) guestDashboard.style.display = 'none'; const uid = getCookie('uid') || localStorage.getItem('uid'); if (uid && window.fetchAndDisplayFiles) { await window.fetchAndDisplayFiles(uid); } } else { document.body.classList.remove('authenticated'); document.body.classList.add('guest-mode'); if (guestDashboard) guestDashboard.style.display = 'block'; if (userDashboard) userDashboard.style.display = 'none'; if (userUpload) userUpload.style.display = 'none'; if (fileList) { fileList.innerHTML = `
`; } } } catch (e) { console.error('Dashboard initialization failed:', e); const guestDashboard = document.getElementById('guest-dashboard'); const userDashboard = document.getElementById('user-dashboard'); if (userDashboard) userDashboard.style.display = 'none'; if (guestDashboard) guestDashboard.style.display = 'block'; document.body.classList.remove('authenticated'); } } // Delete file function is defined below with more complete implementation // Helper function to format file size function formatFileSize(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } // Function to fetch and display user's uploaded files async function fetchAndDisplayFiles(uid) { const fileList = document.getElementById('file-list'); if (!fileList) { console.error('[FILES] File list element not found'); return; } console.log(`[FILES] Fetching files for user: ${uid}`); fileList.innerHTML = ' '; // Prepare headers with auth token if available const authToken = localStorage.getItem('authToken'); const headers = { 'Accept': 'application/json', 'Content-Type': 'application/json' }; if (authToken) { headers['Authorization'] = `Bearer ${authToken}`; } console.log('[FILES] Making request to /me with headers:', headers); try { // The backend should handle authentication via session cookies // We include the auth token in headers if available, but don't rely on it for auth console.log(`[FILES] Making request to /me/${uid} with credentials...`); const response = await fetch(`/me/${uid}`, { method: 'GET', credentials: 'include', // Important: include cookies for session auth headers: headers }); console.log('[FILES] Response status:', response.status); console.log('[FILES] Response headers:', Object.fromEntries([...response.headers.entries()])); // Get response as text first to handle potential JSON parsing errors const responseText = await response.text(); console.log('[FILES] Raw response text:', responseText); // Parse the JSON response let responseData = {}; if (responseText && responseText.trim() !== '') { try { responseData = JSON.parse(responseText); console.log('[FILES] Successfully parsed JSON response:', responseData); } catch (e) { console.error('[FILES] Failed to parse JSON response. Response text:', responseText); console.error('[FILES] Error details:', e); // If we have a non-JSON response but the status is 200, try to handle it if (response.ok) { console.warn('[FILES] Non-JSON response with 200 status, treating as empty response'); } else { throw new Error(`Invalid JSON response from server: ${e.message}`); } } } else { console.log('[FILES] Empty response received, using empty object'); } // Note: Authentication is handled by the parent component // We'll just handle the response status without clearing auth state if (response.ok) { // Check if the response has the expected format if (!responseData || !Array.isArray(responseData.files)) { console.error('[FILES] Invalid response format, expected {files: [...]}:', responseData); fileList.innerHTML = '