
- Fixed double playback issue on stream page by properly scoping event delegation in streams-ui.js - Added init-personal-stream.js to handle UID for personal stream playback - Improved error handling and logging for audio playback - Added proper event propagation control to prevent duplicate event handling
1119 lines
38 KiB
JavaScript
1119 lines
38 KiB
JavaScript
import { showToast } from "./toast.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) {
|
|
// Prevent multiple simultaneous logout attempts
|
|
if (isLoggingOut) return;
|
|
isLoggingOut = true;
|
|
|
|
// Prevent default button behavior
|
|
if (event) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
}
|
|
|
|
try {
|
|
console.log('[LOGOUT] Starting logout process');
|
|
|
|
// Clear user data from localStorage
|
|
localStorage.removeItem('uid');
|
|
localStorage.removeItem('uid_time');
|
|
localStorage.removeItem('confirmed_uid');
|
|
localStorage.removeItem('last_page');
|
|
|
|
// Clear session cookie with SameSite attribute to match how it was set
|
|
const isLocalhost = window.location.hostname === 'localhost';
|
|
const secureFlag = !isLocalhost ? '; Secure' : '';
|
|
document.cookie = `sessionid=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT; SameSite=Lax${secureFlag};`;
|
|
|
|
// Update UI state immediately
|
|
const userDashboard = document.getElementById('user-dashboard');
|
|
const guestDashboard = document.getElementById('guest-dashboard');
|
|
const logoutButton = document.getElementById('logout-button');
|
|
const deleteAccountButton = document.getElementById('delete-account-button');
|
|
|
|
if (userDashboard) userDashboard.style.display = 'none';
|
|
if (guestDashboard) guestDashboard.style.display = 'block';
|
|
if (logoutButton) logoutButton.style.display = 'none';
|
|
if (deleteAccountButton) deleteAccountButton.style.display = 'none';
|
|
|
|
// Show success message (only once)
|
|
if (window.showToast) {
|
|
showToast('Logged out successfully');
|
|
} else {
|
|
console.log('Logged out successfully');
|
|
}
|
|
|
|
// Navigate to register page
|
|
if (window.showOnly) {
|
|
window.showOnly('register-page');
|
|
} else {
|
|
// Fallback to URL change if showOnly isn't available
|
|
window.location.href = '/#register-page';
|
|
}
|
|
|
|
console.log('[LOGOUT] Logout completed');
|
|
|
|
} catch (error) {
|
|
console.error('[LOGOUT] Logout failed:', error);
|
|
if (window.showToast) {
|
|
showToast('Logout failed. Please try again.');
|
|
}
|
|
} 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');
|
|
|
|
// Clear user data
|
|
localStorage.removeItem('uid');
|
|
localStorage.removeItem('uid_time');
|
|
localStorage.removeItem('confirmed_uid');
|
|
document.cookie = 'uid=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
|
|
|
|
// 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...');
|
|
|
|
// Get all dashboard elements
|
|
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');
|
|
|
|
// Add click event listeners for logout and delete account buttons
|
|
if (logoutButton) {
|
|
console.log('[DASHBOARD] Adding logout button handler');
|
|
logoutButton.addEventListener('click', handleLogout);
|
|
}
|
|
|
|
if (deleteAccountButton) {
|
|
console.log('[DASHBOARD] Adding delete account button handler');
|
|
deleteAccountButton.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
handleDeleteAccount();
|
|
});
|
|
}
|
|
|
|
// Check authentication state - consolidated to avoid duplicate declarations
|
|
const hasAuthCookie = document.cookie.includes('isAuthenticated=true');
|
|
const hasUidCookie = document.cookie.includes('uid=');
|
|
const hasLocalStorageAuth = localStorage.getItem('isAuthenticated') === 'true';
|
|
const hasAuthToken = localStorage.getItem('authToken') !== null;
|
|
const isAuthenticated = hasAuthCookie || hasUidCookie || hasLocalStorageAuth || hasAuthToken;
|
|
|
|
// Debug authentication state
|
|
console.log('[AUTH] Authentication state:', {
|
|
hasAuthCookie,
|
|
hasUidCookie,
|
|
hasLocalStorageAuth,
|
|
hasAuthToken,
|
|
isAuthenticated,
|
|
cookies: document.cookie,
|
|
localStorage: {
|
|
isAuthenticated: localStorage.getItem('isAuthenticated'),
|
|
uid: localStorage.getItem('uid'),
|
|
authToken: localStorage.getItem('authToken') ? 'present' : 'not present'
|
|
},
|
|
bodyClasses: document.body.className
|
|
});
|
|
|
|
// Handle authenticated user
|
|
if (isAuthenticated) {
|
|
console.log('[DASHBOARD] User is authenticated, showing user dashboard');
|
|
if (userDashboard) userDashboard.style.display = 'block';
|
|
if (userUpload) userUpload.style.display = 'block';
|
|
if (guestDashboard) guestDashboard.style.display = 'none';
|
|
|
|
// Add authenticated class to body if not present
|
|
document.body.classList.add('authenticated');
|
|
|
|
// Get UID from cookies or localStorage
|
|
let uid = getCookie('uid') || localStorage.getItem('uid');
|
|
|
|
if (!uid) {
|
|
console.warn('[DASHBOARD] No UID found in cookies or localStorage');
|
|
// Try to get UID from the URL or hash fragment
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
uid = urlParams.get('uid') || window.location.hash.substring(1);
|
|
|
|
if (uid) {
|
|
console.log(`[DASHBOARD] Using UID from URL/hash: ${uid}`);
|
|
localStorage.setItem('uid', uid);
|
|
} else {
|
|
console.error('[DASHBOARD] No UID available for file listing');
|
|
if (fileList) {
|
|
fileList.innerHTML = `
|
|
<li class="error-message">
|
|
Error: Could not determine user account. Please <a href="/#login" class="login-link">log in</a> again.
|
|
</li>`;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Initialize file listing if we have a UID
|
|
if (window.fetchAndDisplayFiles) {
|
|
console.log(`[DASHBOARD] Initializing file listing for UID: ${uid}`);
|
|
try {
|
|
await window.fetchAndDisplayFiles(uid);
|
|
} catch (error) {
|
|
console.error('[DASHBOARD] Error initializing file listing:', error);
|
|
if (fileList) {
|
|
fileList.innerHTML = `
|
|
<li class="error-message">
|
|
Error loading files: ${error.message || 'Unknown error'}.
|
|
Please <a href="/#login" class="login-link">log in</a> again.
|
|
</li>`;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// Guest view
|
|
console.log('[DASHBOARD] User not authenticated, showing guest dashboard');
|
|
if (guestDashboard) guestDashboard.style.display = 'block';
|
|
if (userDashboard) userDashboard.style.display = 'none';
|
|
if (userUpload) userUpload.style.display = 'none';
|
|
|
|
// Remove authenticated class if present
|
|
document.body.classList.remove('authenticated');
|
|
|
|
// Show login prompt
|
|
if (fileList) {
|
|
fileList.innerHTML = `
|
|
<li class="error-message">
|
|
Please <a href="/#login" class="login-link">log in</a> to view your files.
|
|
</li>`;
|
|
}
|
|
}
|
|
|
|
// Log authentication details for debugging
|
|
console.log('[DASHBOARD] Authentication details:', {
|
|
uid: getCookie('uid') || localStorage.getItem('uid'),
|
|
cookies: document.cookie,
|
|
localStorage: {
|
|
uid: localStorage.getItem('uid'),
|
|
isAuthenticated: localStorage.getItem('isAuthenticated'),
|
|
authToken: localStorage.getItem('authToken') ? 'present' : 'not present'
|
|
}
|
|
});
|
|
|
|
// If not authenticated, show guest view and return early
|
|
if (!isAuthenticated) {
|
|
console.log('[DASHBOARD] User not authenticated, showing guest dashboard');
|
|
if (guestDashboard) guestDashboard.style.display = 'block';
|
|
if (userDashboard) userDashboard.style.display = 'none';
|
|
if (userUpload) userUpload.style.display = 'none';
|
|
|
|
// Remove authenticated class if present
|
|
document.body.classList.remove('authenticated');
|
|
|
|
// Show login prompt
|
|
if (fileList) {
|
|
fileList.innerHTML = `
|
|
<li class="error-message">
|
|
Please <a href="/#login" class="login-link">log in</a> to view your files.
|
|
</li>`;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Logged-in view - show user dashboard
|
|
console.log('[DASHBOARD] User is logged in, showing user dashboard');
|
|
|
|
// Get all page elements
|
|
const mePage = document.getElementById('me-page');
|
|
|
|
// Log current display states for debugging
|
|
console.log('[DASHBOARD] Updated display states:', {
|
|
guestDashboard: guestDashboard ? window.getComputedStyle(guestDashboard).display : 'not found',
|
|
userDashboard: userDashboard ? window.getComputedStyle(userDashboard).display : 'not found',
|
|
userUpload: userUpload ? window.getComputedStyle(userUpload).display : 'not found',
|
|
logoutButton: logoutButton ? window.getComputedStyle(logoutButton).display : 'not found',
|
|
deleteAccountButton: deleteAccountButton ? window.getComputedStyle(deleteAccountButton).display : 'not found',
|
|
mePage: mePage ? window.getComputedStyle(mePage).display : 'not found'
|
|
});
|
|
|
|
// Hide guest dashboard
|
|
if (guestDashboard) {
|
|
console.log('[DASHBOARD] Hiding guest dashboard');
|
|
guestDashboard.style.display = 'none';
|
|
}
|
|
|
|
// Show user dashboard
|
|
if (userDashboard) {
|
|
console.log('[DASHBOARD] Showing user dashboard');
|
|
userDashboard.style.display = 'block';
|
|
userDashboard.style.visibility = 'visible';
|
|
userDashboard.hidden = false;
|
|
|
|
// Log final visibility state after changes
|
|
console.log('[DEBUG] Final visibility state after showing user dashboard:', {
|
|
userDashboard: debugElementVisibility('user-dashboard'),
|
|
guestDashboard: debugElementVisibility('guest-dashboard'),
|
|
computedDisplay: window.getComputedStyle(userDashboard).display,
|
|
computedVisibility: window.getComputedStyle(userDashboard).visibility
|
|
});
|
|
|
|
// Debug: Check if the element is actually in the DOM
|
|
console.log('[DASHBOARD] User dashboard parent:', userDashboard.parentElement);
|
|
console.log('[DASHBOARD] User dashboard computed display:', window.getComputedStyle(userDashboard).display);
|
|
} else {
|
|
console.error('[DASHBOARD] userDashboard element not found!');
|
|
}
|
|
|
|
// Show essential elements for logged-in users
|
|
const linksSection = document.getElementById('links');
|
|
if (linksSection) {
|
|
console.log('[DASHBOARD] Showing links section');
|
|
linksSection.style.display = 'block';
|
|
}
|
|
|
|
const showMeLink = document.getElementById('show-me');
|
|
if (showMeLink && showMeLink.parentElement) {
|
|
console.log('[DASHBOARD] Showing show-me link');
|
|
showMeLink.parentElement.style.display = 'block';
|
|
}
|
|
|
|
// Show me-page for logged-in users
|
|
if (mePage) {
|
|
console.log('[DASHBOARD] Showing me-page');
|
|
mePage.style.display = 'block';
|
|
}
|
|
|
|
try {
|
|
// Try to get UID from various sources
|
|
let uid = getCookie('uid') || localStorage.getItem('uid');
|
|
|
|
// If we have a valid UID, try to fetch user data
|
|
if (uid && uid !== 'welcome-page' && uid !== 'undefined' && uid !== 'null') {
|
|
console.log('[DASHBOARD] Found valid UID:', uid);
|
|
console.log(`[DEBUG] Fetching user data for UID: ${uid}`);
|
|
const response = await fetch(`/me/${uid}`);
|
|
if (!response.ok) {
|
|
const errorText = await response.text();
|
|
console.error(`[ERROR] Failed to fetch user data: ${response.status} ${response.statusText}`, errorText);
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
// Parse and handle the response data
|
|
const data = await response.json();
|
|
console.log('[DEBUG] User data loaded:', data);
|
|
|
|
// Ensure upload area is visible if last_page was me-page
|
|
if (userUpload && localStorage.getItem('last_page') === 'me-page') {
|
|
// userUpload visibility is now only controlled by nav.js SPA logic
|
|
}
|
|
|
|
// Remove guest warning if present
|
|
const guestMsg = document.getElementById('guest-warning-msg');
|
|
if (guestMsg && guestMsg.parentNode) guestMsg.parentNode.removeChild(guestMsg);
|
|
|
|
// Show user dashboard and logout button
|
|
if (userDashboard) userDashboard.style.display = '';
|
|
if (logoutButton) {
|
|
logoutButton.style.display = 'block';
|
|
logoutButton.onclick = handleLogout;
|
|
}
|
|
|
|
// Set audio source
|
|
const meAudio = document.getElementById('me-audio');
|
|
const username = data?.username || '';
|
|
|
|
if (meAudio) {
|
|
if (username) {
|
|
// Use username for the audio file path if available
|
|
meAudio.src = `/audio/${encodeURIComponent(username)}/stream.opus?t=${Date.now()}`;
|
|
console.log('Setting audio source to:', meAudio.src);
|
|
} else if (uid) {
|
|
// Fallback to UID if username is not available
|
|
meAudio.src = `/audio/${encodeURIComponent(uid)}/stream.opus?t=${Date.now()}`;
|
|
console.warn('Using UID fallback for audio source:', meAudio.src);
|
|
}
|
|
}
|
|
|
|
// Update quota and ensure quota meter is visible if data is available
|
|
const quotaMeter = document.getElementById('quota-meter');
|
|
const quotaBar = document.getElementById('quota-bar');
|
|
const quotaText = document.getElementById('quota-text');
|
|
|
|
if (quotaBar && data.quota !== undefined) {
|
|
quotaBar.value = data.quota;
|
|
}
|
|
|
|
if (quotaText && data.quota !== undefined) {
|
|
quotaText.textContent = `${data.quota} MB`;
|
|
}
|
|
|
|
if (quotaMeter) {
|
|
quotaMeter.hidden = false;
|
|
quotaMeter.style.display = 'block'; // Ensure it's not hidden by display:none
|
|
}
|
|
|
|
// Fetch and display the list of uploaded files if the function is available
|
|
if (window.fetchAndDisplayFiles) {
|
|
console.log('[DASHBOARD] Calling fetchAndDisplayFiles with UID:', uid);
|
|
// Ensure we have the most up-to-date UID from the response data if available
|
|
const effectiveUid = data?.uid || uid;
|
|
console.log('[DASHBOARD] Using effective UID:', effectiveUid);
|
|
window.fetchAndDisplayFiles(effectiveUid);
|
|
} else {
|
|
console.error('[DASHBOARD] fetchAndDisplayFiles function not found!');
|
|
}
|
|
} else {
|
|
// No valid UID found, ensure we're in guest mode
|
|
console.log('[DASHBOARD] No valid UID found, showing guest dashboard');
|
|
userDashboard.style.display = 'none';
|
|
guestDashboard.style.display = 'block';
|
|
userUpload.style.display = 'none';
|
|
document.body.classList.remove('authenticated');
|
|
return; // Exit early for guest users
|
|
}
|
|
|
|
// Ensure Streams link remains in nav, not moved
|
|
// (No action needed if static)
|
|
} catch (e) {
|
|
console.warn('Dashboard init error, falling back to guest mode:', e);
|
|
|
|
// Ensure guest UI is shown
|
|
userUpload.style.display = 'none';
|
|
userDashboard.style.display = 'none';
|
|
if (guestDashboard) guestDashboard.style.display = 'block';
|
|
|
|
// Update body classes
|
|
document.body.classList.remove('authenticated');
|
|
document.body.classList.add('guest-mode');
|
|
|
|
// Ensure navigation is in correct state
|
|
const registerLink = document.getElementById('guest-login');
|
|
const streamsLink = document.getElementById('guest-streams');
|
|
if (registerLink && streamsLink) {
|
|
registerLink.parentElement.insertAdjacentElement('afterend', streamsLink.parentElement);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Function to delete a file - only define if not already defined
|
|
if (typeof window.deleteFile === 'undefined') {
|
|
window.deleteFile = async function(uid, fileName, listItem) {
|
|
if (!confirm(`Are you sure you want to delete "${fileName}"? This cannot be undone.`)) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
console.log(`[FILES] Deleting file: ${fileName} for user: ${uid}`);
|
|
|
|
const response = await fetch(`/delete-file/${uid}/${encodeURIComponent(fileName)}`, {
|
|
method: 'DELETE',
|
|
credentials: 'include',
|
|
headers: {
|
|
'Accept': 'application/json'
|
|
}
|
|
});
|
|
|
|
console.log('[FILES] Delete response:', response.status, response.statusText);
|
|
|
|
if (!response.ok) {
|
|
const errorText = await response.text();
|
|
console.error('[FILES] Delete error:', errorText);
|
|
showToast(`Error deleting file: ${response.statusText}`, 'error');
|
|
return;
|
|
}
|
|
|
|
// Remove the file from the UI
|
|
if (listItem && listItem.parentNode) {
|
|
listItem.parentNode.removeChild(listItem);
|
|
}
|
|
|
|
showToast('File deleted successfully', 'success');
|
|
|
|
// If no files left, show "no files" message
|
|
const fileList = document.getElementById('file-list');
|
|
if (fileList && fileList.children.length === 0) {
|
|
fileList.innerHTML = '<li class="no-files">No files uploaded yet.</li>';
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('[FILES] Error deleting file:', error);
|
|
showToast('Error deleting file. Please try again.', 'error');
|
|
}
|
|
}; // Close the function expression
|
|
} // Close the if statement
|
|
|
|
// 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 = '<li class="loading-message">Loading your files...</li>';
|
|
|
|
// 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 with credentials...');
|
|
const response = await fetch('/me', {
|
|
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 = '<li>Error: Invalid response from server</li>';
|
|
return;
|
|
}
|
|
|
|
const files = responseData.files;
|
|
console.log('[FILES] Files array:', files);
|
|
|
|
if (files.length === 0) {
|
|
fileList.innerHTML = '<li class="no-files">No files uploaded yet.</li>';
|
|
return;
|
|
}
|
|
|
|
// Clear the loading message
|
|
fileList.innerHTML = '';
|
|
|
|
// Add each file to the list
|
|
files.forEach(fileName => {
|
|
const fileExt = fileName.split('.').pop().toLowerCase();
|
|
const fileUrl = `/data/${uid}/${encodeURIComponent(fileName)}`;
|
|
const fileSize = 'N/A'; // We don't have size info in the current API response
|
|
|
|
const listItem = document.createElement('li');
|
|
listItem.className = 'file-item';
|
|
|
|
// Create file icon based on file extension
|
|
let fileIcon = '📄'; // Default icon
|
|
if (['mp3', 'wav', 'ogg', 'm4a', 'opus'].includes(fileExt)) {
|
|
fileIcon = '🎵';
|
|
} else if (['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(fileExt)) {
|
|
fileIcon = '🖼️';
|
|
} else if (['pdf', 'doc', 'docx', 'txt'].includes(fileExt)) {
|
|
fileIcon = '📄';
|
|
}
|
|
|
|
listItem.innerHTML = `
|
|
<div class="file-info">
|
|
<span class="file-icon">${fileIcon}</span>
|
|
<a href="${fileUrl}" class="file-name" target="_blank" rel="noopener noreferrer">
|
|
${fileName}
|
|
</a>
|
|
<span class="file-size">${fileSize}</span>
|
|
</div>
|
|
<div class="file-actions">
|
|
<a href="${fileUrl}" class="download-button" download>
|
|
<span class="button-icon">⬇️</span>
|
|
<span class="button-text">Download</span>
|
|
</a>
|
|
<button class="delete-button" data-filename="${fileName}">
|
|
<span class="button-icon">🗑️</span>
|
|
<span class="button-text">Delete</span>
|
|
</button>
|
|
</div>
|
|
`;
|
|
|
|
// Add delete button handler
|
|
const deleteButton = listItem.querySelector('.delete-button');
|
|
if (deleteButton) {
|
|
deleteButton.addEventListener('click', () => deleteFile(uid, fileName, listItem));
|
|
}
|
|
|
|
fileList.appendChild(listItem);
|
|
});
|
|
} else {
|
|
// Handle non-OK responses
|
|
if (response.status === 401) {
|
|
// Parent component will handle authentication state
|
|
fileList.innerHTML = `
|
|
<li class="error-message">
|
|
Please <a href="/#login" class="login-link">log in</a> to view your files.
|
|
</li>`;
|
|
} else {
|
|
fileList.innerHTML = `
|
|
<li class="error-message">
|
|
Error loading files (${response.status}). Please try again later.
|
|
</li>`;
|
|
}
|
|
console.error('[FILES] Server error:', response.status, response.statusText);
|
|
}
|
|
} catch (error) {
|
|
console.error('[FILES] Error fetching files:', error);
|
|
const fileList = document.getElementById('file-list');
|
|
if (fileList) {
|
|
fileList.innerHTML = `
|
|
<li class="error-message">
|
|
Error loading files: ${error.message || 'Unknown error'}
|
|
</li>`;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Function to handle file deletion
|
|
async function deleteFile(fileId, uid) {
|
|
if (!confirm('Are you sure you want to delete this file?')) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`/api/files/${fileId}`, {
|
|
method: 'DELETE',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
credentials: 'same-origin'
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
// Refresh the file list
|
|
fetchAndDisplayFiles(uid);
|
|
showToast('File deleted successfully');
|
|
|
|
} catch (error) {
|
|
console.error('[FILES] Error deleting file:', error);
|
|
showToast('Error deleting file', 'error');
|
|
}
|
|
}
|
|
|
|
// Initialize file upload functionality
|
|
function initFileUpload() {
|
|
const uploadArea = document.getElementById('user-upload-area');
|
|
const fileInput = document.getElementById('fileInputUser');
|
|
|
|
if (!uploadArea || !fileInput) {
|
|
console.warn('[UPLOAD] Required elements not found for file upload');
|
|
return;
|
|
}
|
|
|
|
// Handle click on upload area
|
|
uploadArea.addEventListener('click', () => {
|
|
fileInput.click();
|
|
});
|
|
|
|
// Handle file selection
|
|
fileInput.addEventListener('change', async (e) => {
|
|
const file = e.target.files[0];
|
|
if (!file) return;
|
|
|
|
// Check file size (100MB limit)
|
|
if (file.size > 100 * 1024 * 1024) {
|
|
showToast('File is too large. Maximum size is 100MB.', 'error');
|
|
return;
|
|
}
|
|
|
|
// Show loading state
|
|
const originalText = uploadArea.innerHTML;
|
|
uploadArea.innerHTML = 'Uploading...';
|
|
|
|
try {
|
|
const formData = new FormData();
|
|
formData.append('file', file);
|
|
|
|
// Get UID from localStorage (parent UI ensures we're authenticated)
|
|
const uid = localStorage.getItem('uid');
|
|
formData.append('uid', uid);
|
|
|
|
// Proceed with the upload
|
|
const response = await fetch('/upload', {
|
|
method: 'POST',
|
|
body: formData,
|
|
credentials: 'include', // Include cookies for authentication
|
|
headers: {
|
|
'Accept': 'application/json' // Explicitly accept JSON response
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.text();
|
|
throw new Error(error || 'Upload failed');
|
|
}
|
|
|
|
const result = await response.json();
|
|
showToast('File uploaded successfully!');
|
|
|
|
// Refresh file list
|
|
if (window.fetchAndDisplayFiles) {
|
|
window.fetchAndDisplayFiles(uid);
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('[UPLOAD] Error uploading file:', error);
|
|
showToast(`Upload failed: ${error.message}`, 'error');
|
|
} finally {
|
|
// Reset file input and restore upload area text
|
|
fileInput.value = '';
|
|
uploadArea.innerHTML = originalText;
|
|
}
|
|
});
|
|
|
|
// Handle drag and drop
|
|
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
|
uploadArea.addEventListener(eventName, preventDefaults, false);
|
|
});
|
|
|
|
function preventDefaults(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
}
|
|
|
|
['dragenter', 'dragover'].forEach(eventName => {
|
|
uploadArea.addEventListener(eventName, highlight, false);
|
|
});
|
|
|
|
['dragleave', 'drop'].forEach(eventName => {
|
|
uploadArea.addEventListener(eventName, unhighlight, false);
|
|
});
|
|
|
|
function highlight() {
|
|
uploadArea.classList.add('highlight');
|
|
}
|
|
|
|
function unhighlight() {
|
|
uploadArea.classList.remove('highlight');
|
|
}
|
|
|
|
// Handle dropped files
|
|
uploadArea.addEventListener('drop', (e) => {
|
|
const dt = e.dataTransfer;
|
|
const files = dt.files;
|
|
|
|
if (files.length) {
|
|
fileInput.files = files;
|
|
const event = new Event('change');
|
|
fileInput.dispatchEvent(event);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Main initialization when the DOM is fully loaded
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
// Initialize dashboard components
|
|
initDashboard();
|
|
initFileUpload();
|
|
|
|
// Make fetchAndDisplayFiles available globally
|
|
window.fetchAndDisplayFiles = fetchAndDisplayFiles;
|
|
|
|
// Login/Register (guest)
|
|
const regForm = document.getElementById('register-form');
|
|
if (regForm) {
|
|
regForm.addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
const formData = new FormData(regForm);
|
|
const submitButton = regForm.querySelector('button[type="submit"]');
|
|
const originalButtonText = submitButton.textContent;
|
|
|
|
try {
|
|
// Disable button during submission
|
|
submitButton.disabled = true;
|
|
submitButton.textContent = 'Sending...';
|
|
|
|
const res = await fetch('/register', {
|
|
method: 'POST',
|
|
body: formData,
|
|
headers: {
|
|
'Accept': 'application/json'
|
|
}
|
|
});
|
|
|
|
let data;
|
|
const contentType = res.headers.get('content-type');
|
|
|
|
try {
|
|
if (contentType && contentType.includes('application/json')) {
|
|
data = await res.json();
|
|
} else {
|
|
const text = await res.text();
|
|
data = { detail: text };
|
|
}
|
|
|
|
if (res.ok) {
|
|
showToast('', 'success');
|
|
// Clear the form on success
|
|
regForm.reset();
|
|
} else {
|
|
showToast(`Error: ${data.detail || 'Unknown error occurred'}`, 'error');
|
|
console.error('Registration failed:', data);
|
|
}
|
|
} catch (parseError) {
|
|
console.error('Error parsing response:', parseError);
|
|
showToast('Error processing the response. Please try again.', 'error');
|
|
}
|
|
} catch (err) {
|
|
console.error('Network error:', err);
|
|
showToast('Network error. Please check your connection and try again.', 'error');
|
|
} finally {
|
|
// Re-enable button
|
|
submitButton.disabled = false;
|
|
submitButton.textContent = originalButtonText;
|
|
}
|
|
});
|
|
}
|
|
// Connect Login or Register link to register form
|
|
// Login/Register (guest)
|
|
const loginLink = document.getElementById('guest-login');
|
|
if (loginLink) {
|
|
loginLink.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
document.querySelectorAll('main > section').forEach(sec => {
|
|
sec.hidden = sec.id !== 'register-page';
|
|
});
|
|
const reg = document.getElementById('register-page');
|
|
if (reg) reg.hidden = false;
|
|
reg.scrollIntoView({behavior:'smooth'});
|
|
});
|
|
}
|
|
// Terms of Service (all dashboards)
|
|
const termsLinks = [
|
|
document.getElementById('guest-terms'),
|
|
document.getElementById('user-terms')
|
|
];
|
|
termsLinks.forEach(link => {
|
|
if (link) {
|
|
link.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
document.querySelectorAll('main > section').forEach(sec => {
|
|
sec.hidden = sec.id !== 'terms-page';
|
|
});
|
|
const terms = document.getElementById('terms-page');
|
|
if (terms) terms.hidden = false;
|
|
terms.scrollIntoView({behavior:'smooth'});
|
|
});
|
|
}
|
|
});
|
|
|
|
// Imprint (all dashboards)
|
|
const imprintLinks = [
|
|
document.getElementById('guest-imprint'),
|
|
document.getElementById('user-imprint')
|
|
];
|
|
imprintLinks.forEach(link => {
|
|
if (link) {
|
|
link.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
document.querySelectorAll('main > section').forEach(sec => {
|
|
sec.hidden = sec.id !== 'imprint-page';
|
|
});
|
|
const imprint = document.getElementById('imprint-page');
|
|
if (imprint) imprint.hidden = false;
|
|
imprint.scrollIntoView({behavior:'smooth'});
|
|
});
|
|
}
|
|
});
|
|
|
|
// Privacy Policy (all dashboards)
|
|
const privacyLinks = [
|
|
document.getElementById('guest-privacy'),
|
|
document.getElementById('user-privacy')
|
|
];
|
|
privacyLinks.forEach(link => {
|
|
if (link) {
|
|
link.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
document.querySelectorAll('main > section').forEach(sec => {
|
|
sec.hidden = sec.id !== 'privacy-page';
|
|
});
|
|
const privacy = document.getElementById('privacy-page');
|
|
if (privacy) privacy.hidden = false;
|
|
privacy.scrollIntoView({behavior:'smooth'});
|
|
});
|
|
}
|
|
});
|
|
|
|
// Back to top button functionality
|
|
const backToTop = document.getElementById('back-to-top');
|
|
if (backToTop) {
|
|
backToTop.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
});
|
|
}
|
|
|
|
// Mobile menu functionality
|
|
const menuToggle = document.getElementById('mobile-menu-toggle');
|
|
const mainNav = document.getElementById('main-navigation');
|
|
|
|
if (menuToggle && mainNav) {
|
|
// Toggle mobile menu
|
|
menuToggle.addEventListener('click', () => {
|
|
const isExpanded = menuToggle.getAttribute('aria-expanded') === 'true' || false;
|
|
menuToggle.setAttribute('aria-expanded', !isExpanded);
|
|
mainNav.setAttribute('aria-hidden', isExpanded);
|
|
|
|
// Toggle mobile menu visibility
|
|
if (isExpanded) {
|
|
mainNav.classList.remove('mobile-visible');
|
|
document.body.style.overflow = '';
|
|
} else {
|
|
mainNav.classList.add('mobile-visible');
|
|
document.body.style.overflow = 'hidden';
|
|
}
|
|
});
|
|
|
|
// Close mobile menu when clicking outside
|
|
document.addEventListener('click', (e) => {
|
|
const isClickInsideNav = mainNav.contains(e.target);
|
|
const isClickOnToggle = menuToggle === e.target || menuToggle.contains(e.target);
|
|
|
|
if (mainNav.classList.contains('mobile-visible') && !isClickInsideNav && !isClickOnToggle) {
|
|
mainNav.classList.remove('mobile-visible');
|
|
menuToggle.setAttribute('aria-expanded', 'false');
|
|
document.body.style.overflow = '';
|
|
}
|
|
});
|
|
}
|
|
|
|
// Handle navigation link clicks
|
|
const navLinks = document.querySelectorAll('nav a[href^="#"]');
|
|
navLinks.forEach(link => {
|
|
link.addEventListener('click', (e) => {
|
|
const targetId = link.getAttribute('href');
|
|
if (targetId === '#') return;
|
|
|
|
const targetElement = document.querySelector(targetId);
|
|
if (targetElement) {
|
|
e.preventDefault();
|
|
|
|
// Close mobile menu if open
|
|
if (mainNav && mainNav.classList.contains('mobile-visible')) {
|
|
mainNav.classList.remove('mobile-visible');
|
|
if (menuToggle) {
|
|
menuToggle.setAttribute('aria-expanded', 'false');
|
|
}
|
|
document.body.style.overflow = '';
|
|
}
|
|
|
|
// Smooth scroll to target
|
|
targetElement.scrollIntoView({ behavior: 'smooth' });
|
|
|
|
// Update URL without page reload
|
|
if (history.pushState) {
|
|
history.pushState(null, '', targetId);
|
|
} else {
|
|
location.hash = targetId;
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
// Helper function to handle page section navigation
|
|
const setupPageNavigation = (linkIds, pageId) => {
|
|
const links = linkIds
|
|
.map(id => document.getElementById(id))
|
|
.filter(Boolean);
|
|
|
|
links.forEach(link => {
|
|
link.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
document.querySelectorAll('main > section').forEach(sec => {
|
|
sec.hidden = sec.id !== pageId;
|
|
});
|
|
const targetPage = document.getElementById(pageId);
|
|
if (targetPage) {
|
|
targetPage.hidden = false;
|
|
targetPage.scrollIntoView({ behavior: 'smooth' });
|
|
}
|
|
});
|
|
});
|
|
};
|
|
|
|
// Setup navigation for different sections
|
|
setupPageNavigation(['guest-terms', 'user-terms'], 'terms-page');
|
|
setupPageNavigation(['guest-imprint', 'user-imprint'], 'imprint-page');
|
|
setupPageNavigation(['guest-privacy', 'user-privacy'], 'privacy-page');
|
|
|
|
// Registration form handler for guests - removed duplicate declaration
|
|
// The form submission is already handled earlier in the file
|
|
|
|
// Login link handler - removed duplicate declaration
|
|
// The login link is already handled by the setupPageNavigation function
|
|
|
|
// Handle drag and drop
|
|
const uploadArea = document.getElementById('upload-area');
|
|
if (uploadArea) {
|
|
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
|
uploadArea.addEventListener(eventName, preventDefaults, false);
|
|
});
|
|
|
|
['dragenter', 'dragover'].forEach(eventName => {
|
|
uploadArea.addEventListener(eventName, highlight, false);
|
|
});
|
|
|
|
['dragleave', 'drop'].forEach(eventName => {
|
|
uploadArea.addEventListener(eventName, unhighlight, false);
|
|
});
|
|
|
|
function preventDefaults(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
}
|
|
|
|
function highlight() {
|
|
uploadArea.classList.add('highlight');
|
|
}
|
|
|
|
function unhighlight() {
|
|
uploadArea.classList.remove('highlight');
|
|
}
|
|
|
|
// Handle dropped files
|
|
uploadArea.addEventListener('drop', (e) => {
|
|
const dt = e.dataTransfer;
|
|
const files = dt.files;
|
|
|
|
if (files.length) {
|
|
const fileInput = document.getElementById('file-input');
|
|
fileInput.files = files;
|
|
const event = new Event('change');
|
|
fileInput.dispatchEvent(event);
|
|
}
|
|
});
|
|
}
|
|
}); // End of DOMContentLoaded
|