fix: resolve mobile navigation visibility for authenticated users
- Add fix-nav.js to handle navigation state - Update mobile.css with more specific selectors - Modify dashboard.js to ensure proper auth state - Update index.html to include the new fix script - Ensure guest navigation stays hidden during client-side navigation
This commit is contained in:
@ -13,8 +13,12 @@ function getCookie(name) {
|
|||||||
let isLoggingOut = false;
|
let isLoggingOut = false;
|
||||||
|
|
||||||
async function handleLogout(event) {
|
async function handleLogout(event) {
|
||||||
|
console.log('[LOGOUT] Logout initiated');
|
||||||
// Prevent multiple simultaneous logout attempts
|
// Prevent multiple simultaneous logout attempts
|
||||||
if (isLoggingOut) return;
|
if (isLoggingOut) {
|
||||||
|
console.log('[LOGOUT] Logout already in progress');
|
||||||
|
return;
|
||||||
|
}
|
||||||
isLoggingOut = true;
|
isLoggingOut = true;
|
||||||
|
|
||||||
// Prevent default button behavior
|
// Prevent default button behavior
|
||||||
@ -23,44 +27,145 @@ async function handleLogout(event) {
|
|||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get auth token before we clear it
|
||||||
|
const authToken = localStorage.getItem('authToken');
|
||||||
|
|
||||||
|
// 1. First try to invalidate the server session (but don't block on it)
|
||||||
|
if (authToken) {
|
||||||
try {
|
try {
|
||||||
console.log('[LOGOUT] Starting logout process');
|
// We'll use a timeout to prevent hanging on the server request
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 2000);
|
||||||
|
|
||||||
// Clear user data from localStorage
|
try {
|
||||||
localStorage.removeItem('uid');
|
await fetch('/api/logout', {
|
||||||
localStorage.removeItem('uid_time');
|
method: 'POST',
|
||||||
localStorage.removeItem('confirmed_uid');
|
credentials: 'include',
|
||||||
localStorage.removeItem('last_page');
|
signal: controller.signal,
|
||||||
|
headers: {
|
||||||
// Clear session cookie with SameSite attribute to match how it was set
|
'Content-Type': 'application/json',
|
||||||
const isLocalhost = window.location.hostname === 'localhost';
|
'Authorization': `Bearer ${authToken}`
|
||||||
const secureFlag = !isLocalhost ? '; Secure' : '';
|
},
|
||||||
document.cookie = `sessionid=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT; SameSite=Lax${secureFlag};`;
|
});
|
||||||
|
clearTimeout(timeoutId);
|
||||||
// Update UI state immediately
|
} catch (error) {
|
||||||
const userDashboard = document.getElementById('user-dashboard');
|
clearTimeout(timeoutId);
|
||||||
const guestDashboard = document.getElementById('guest-dashboard');
|
// Silently handle any errors during server logout
|
||||||
const logoutButton = document.getElementById('logout-button');
|
}
|
||||||
const deleteAccountButton = document.getElementById('delete-account-button');
|
} catch (error) {
|
||||||
|
// Silently handle any unexpected errors
|
||||||
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
|
// 2. Clear all client-side state
|
||||||
if (window.showOnly) {
|
function clearClientState() {
|
||||||
window.showOnly('register-page');
|
console.log('[LOGOUT] Clearing client state');
|
||||||
|
|
||||||
|
// Clear all authentication-related data from localStorage
|
||||||
|
const keysToRemove = [
|
||||||
|
'uid', 'uid_time', 'confirmed_uid', 'last_page',
|
||||||
|
'isAuthenticated', 'authToken', 'user', 'token', 'sessionid'
|
||||||
|
];
|
||||||
|
|
||||||
|
keysToRemove.forEach(key => {
|
||||||
|
localStorage.removeItem(key);
|
||||||
|
sessionStorage.removeItem(key);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get current cookies for debugging
|
||||||
|
const cookies = document.cookie.split(';');
|
||||||
|
console.log('[LOGOUT] Current cookies before clearing:', cookies);
|
||||||
|
|
||||||
|
// Function to clear a cookie by name
|
||||||
|
const clearCookie = (name) => {
|
||||||
|
console.log(`[LOGOUT] Attempting to clear cookie: ${name}`);
|
||||||
|
const baseOptions = 'Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT; SameSite=Lax';
|
||||||
|
// Try with current domain
|
||||||
|
document.cookie = `${name}=; ${baseOptions}`;
|
||||||
|
// Try with domain
|
||||||
|
document.cookie = `${name}=; ${baseOptions}; domain=${window.location.hostname}`;
|
||||||
|
// Try with leading dot for subdomains
|
||||||
|
document.cookie = `${name}=; ${baseOptions}; domain=.${window.location.hostname}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Clear all authentication-related cookies
|
||||||
|
const authCookies = [
|
||||||
|
'uid', 'authToken', 'isAuthenticated', 'sessionid', 'session_id',
|
||||||
|
'token', 'remember_token', 'auth', 'authentication'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Clear specific auth cookies
|
||||||
|
authCookies.forEach(clearCookie);
|
||||||
|
|
||||||
|
// Also clear any existing cookies that match our patterns
|
||||||
|
cookies.forEach(cookie => {
|
||||||
|
const [name] = cookie.trim().split('=');
|
||||||
|
if (name && authCookies.some(authName => name.trim() === authName)) {
|
||||||
|
clearCookie(name.trim());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('[LOGOUT] Cookies after clearing:', document.cookie);
|
||||||
|
|
||||||
|
// Update UI state
|
||||||
|
document.body.classList.remove('authenticated');
|
||||||
|
document.body.classList.add('guest');
|
||||||
|
|
||||||
|
// Force a hard reload to ensure all state is reset
|
||||||
|
setTimeout(() => {
|
||||||
|
// Clear all storage again before redirecting
|
||||||
|
keysToRemove.forEach(key => {
|
||||||
|
localStorage.removeItem(key);
|
||||||
|
sessionStorage.removeItem(key);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Redirect to home with a cache-busting parameter
|
||||||
|
window.location.href = '/?logout=' + Date.now();
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Clear client state immediately to prevent any race conditions
|
||||||
|
clearClientState();
|
||||||
|
|
||||||
|
// 2. Try to invalidate the server session (but don't block on it)
|
||||||
|
console.log('[LOGOUT] Auth token exists:', !!authToken);
|
||||||
|
if (authToken) {
|
||||||
|
try {
|
||||||
|
console.log('[LOGOUT] Attempting to invalidate server session');
|
||||||
|
|
||||||
|
const response = await fetch('/api/logout', {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'include',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${authToken}`
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok && response.status !== 401) {
|
||||||
|
console.warn(`[LOGOUT] Server returned ${response.status} during logout`);
|
||||||
|
// Don't throw - we've already cleared client state
|
||||||
} else {
|
} else {
|
||||||
// Fallback to URL change if showOnly isn't available
|
console.log('[LOGOUT] Server session invalidated successfully');
|
||||||
window.location.href = '/#register-page';
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[LOGOUT] Error during server session invalidation (non-critical):', error);
|
||||||
|
// Continue with logout process
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Update navigation if the function exists
|
||||||
|
if (typeof injectNavigation === 'function') {
|
||||||
|
injectNavigation(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[LOGOUT] Logout completed');
|
console.log('[LOGOUT] Logout completed');
|
||||||
@ -72,6 +177,11 @@ async function handleLogout(event) {
|
|||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
isLoggingOut = false;
|
isLoggingOut = false;
|
||||||
|
|
||||||
|
// 4. Redirect to home page after a short delay to ensure state is cleared
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = '/';
|
||||||
|
}, 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,6 +304,15 @@ async function initDashboard() {
|
|||||||
const hasAuthToken = localStorage.getItem('authToken') !== null;
|
const hasAuthToken = localStorage.getItem('authToken') !== null;
|
||||||
const isAuthenticated = hasAuthCookie || hasUidCookie || hasLocalStorageAuth || hasAuthToken;
|
const isAuthenticated = hasAuthCookie || hasUidCookie || hasLocalStorageAuth || hasAuthToken;
|
||||||
|
|
||||||
|
// Ensure body class reflects authentication state
|
||||||
|
if (isAuthenticated) {
|
||||||
|
document.body.classList.add('authenticated');
|
||||||
|
document.body.classList.remove('guest-mode');
|
||||||
|
} else {
|
||||||
|
document.body.classList.remove('authenticated');
|
||||||
|
document.body.classList.add('guest-mode');
|
||||||
|
}
|
||||||
|
|
||||||
// Debug authentication state
|
// Debug authentication state
|
||||||
console.log('[AUTH] Authentication state:', {
|
console.log('[AUTH] Authentication state:', {
|
||||||
hasAuthCookie,
|
hasAuthCookie,
|
||||||
@ -485,52 +604,16 @@ async function initDashboard() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to delete a file - only define if not already defined
|
// Delete file function is defined below with more complete implementation
|
||||||
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 {
|
// Helper function to format file size
|
||||||
console.log(`[FILES] Deleting file: ${fileName} for user: ${uid}`);
|
function formatFileSize(bytes) {
|
||||||
|
if (bytes === 0) return '0 Bytes';
|
||||||
const response = await fetch(`/delete-file/${uid}/${encodeURIComponent(fileName)}`, {
|
const k = 1024;
|
||||||
method: 'DELETE',
|
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||||
credentials: 'include',
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
headers: {
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||||
'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
|
// Function to fetch and display user's uploaded files
|
||||||
async function fetchAndDisplayFiles(uid) {
|
async function fetchAndDisplayFiles(uid) {
|
||||||
@ -560,8 +643,8 @@ async function fetchAndDisplayFiles(uid) {
|
|||||||
try {
|
try {
|
||||||
// The backend should handle authentication via session cookies
|
// 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
|
// 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...');
|
console.log(`[FILES] Making request to /me/${uid} with credentials...`);
|
||||||
const response = await fetch('/me', {
|
const response = await fetch(`/me/${uid}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
credentials: 'include', // Important: include cookies for session auth
|
credentials: 'include', // Important: include cookies for session auth
|
||||||
headers: headers
|
headers: headers
|
||||||
@ -617,14 +700,29 @@ async function fetchAndDisplayFiles(uid) {
|
|||||||
// Clear the loading message
|
// Clear the loading message
|
||||||
fileList.innerHTML = '';
|
fileList.innerHTML = '';
|
||||||
|
|
||||||
|
// Track displayed files to prevent duplicates using stored filenames as unique identifiers
|
||||||
|
const displayedFiles = new Set();
|
||||||
|
|
||||||
// Add each file to the list
|
// Add each file to the list
|
||||||
files.forEach(fileName => {
|
files.forEach(file => {
|
||||||
const fileExt = fileName.split('.').pop().toLowerCase();
|
// Get the stored filename (with UUID) - this is our unique identifier
|
||||||
const fileUrl = `/data/${uid}/${encodeURIComponent(fileName)}`;
|
const storedFileName = file.stored_name || file.name || file;
|
||||||
const fileSize = 'N/A'; // We don't have size info in the current API response
|
|
||||||
|
// Skip if we've already displayed this file
|
||||||
|
if (displayedFiles.has(storedFileName)) {
|
||||||
|
console.log(`[FILES] Skipping duplicate file with stored name: ${storedFileName}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
displayedFiles.add(storedFileName);
|
||||||
|
|
||||||
|
const fileExt = storedFileName.split('.').pop().toLowerCase();
|
||||||
|
const fileUrl = `/data/${uid}/${encodeURIComponent(storedFileName)}`;
|
||||||
|
const fileSize = file.size ? formatFileSize(file.size) : 'N/A';
|
||||||
|
|
||||||
const listItem = document.createElement('li');
|
const listItem = document.createElement('li');
|
||||||
listItem.className = 'file-item';
|
listItem.className = 'file-item';
|
||||||
|
listItem.setAttribute('data-uid', uid);
|
||||||
|
|
||||||
// Create file icon based on file extension
|
// Create file icon based on file extension
|
||||||
let fileIcon = '📄'; // Default icon
|
let fileIcon = '📄'; // Default icon
|
||||||
@ -636,11 +734,14 @@ async function fetchAndDisplayFiles(uid) {
|
|||||||
fileIcon = '📄';
|
fileIcon = '📄';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use original_name if available, otherwise use the stored filename for display
|
||||||
|
const displayName = file.original_name || storedFileName;
|
||||||
|
|
||||||
listItem.innerHTML = `
|
listItem.innerHTML = `
|
||||||
<div class="file-info">
|
<div class="file-info">
|
||||||
<span class="file-icon">${fileIcon}</span>
|
<span class="file-icon">${fileIcon}</span>
|
||||||
<a href="${fileUrl}" class="file-name" target="_blank" rel="noopener noreferrer">
|
<a href="${fileUrl}" class="file-name" target="_blank" rel="noopener noreferrer">
|
||||||
${fileName}
|
${displayName}
|
||||||
</a>
|
</a>
|
||||||
<span class="file-size">${fileSize}</span>
|
<span class="file-size">${fileSize}</span>
|
||||||
</div>
|
</div>
|
||||||
@ -649,18 +750,15 @@ async function fetchAndDisplayFiles(uid) {
|
|||||||
<span class="button-icon">⬇️</span>
|
<span class="button-icon">⬇️</span>
|
||||||
<span class="button-text">Download</span>
|
<span class="button-text">Download</span>
|
||||||
</a>
|
</a>
|
||||||
<button class="delete-button" data-filename="${fileName}">
|
<button class="delete-file" data-filename="${storedFileName}" data-original-name="${displayName}">
|
||||||
<span class="button-icon">🗑️</span>
|
<span class="button-icon">🗑️</span>
|
||||||
<span class="button-text">Delete</span>
|
<span class="button-text">Delete</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Add delete button handler
|
// Delete button handler will be handled by event delegation
|
||||||
const deleteButton = listItem.querySelector('.delete-button');
|
// No need to add individual event listeners here
|
||||||
if (deleteButton) {
|
|
||||||
deleteButton.addEventListener('click', () => deleteFile(uid, fileName, listItem));
|
|
||||||
}
|
|
||||||
|
|
||||||
fileList.appendChild(listItem);
|
fileList.appendChild(listItem);
|
||||||
});
|
});
|
||||||
@ -693,31 +791,75 @@ async function fetchAndDisplayFiles(uid) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Function to handle file deletion
|
// Function to handle file deletion
|
||||||
async function deleteFile(fileId, uid) {
|
async function deleteFile(uid, fileName, listItem, displayName = '') {
|
||||||
if (!confirm('Are you sure you want to delete this file?')) {
|
const fileToDelete = displayName || fileName;
|
||||||
|
if (!confirm(`Are you sure you want to delete "${fileToDelete}"?`)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show loading state
|
||||||
|
if (listItem) {
|
||||||
|
listItem.style.opacity = '0.6';
|
||||||
|
listItem.style.pointerEvents = 'none';
|
||||||
|
const deleteButton = listItem.querySelector('.delete-file');
|
||||||
|
if (deleteButton) {
|
||||||
|
deleteButton.disabled = true;
|
||||||
|
deleteButton.innerHTML = '<span class="button-icon">⏳</span><span class="button-text">Deleting...</span>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/files/${fileId}`, {
|
if (!uid) {
|
||||||
|
throw new Error('User not authenticated. Please log in again.');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[DELETE] Attempting to delete file: ${fileName} for user: ${uid}`);
|
||||||
|
const authToken = localStorage.getItem('authToken');
|
||||||
|
const headers = { 'Content-Type': 'application/json' };
|
||||||
|
|
||||||
|
if (authToken) {
|
||||||
|
headers['Authorization'] = `Bearer ${authToken}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the provided UID in the URL
|
||||||
|
const response = await fetch(`/uploads/${uid}/${encodeURIComponent(fileName)}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: {
|
headers: headers,
|
||||||
'Content-Type': 'application/json',
|
credentials: 'include'
|
||||||
},
|
|
||||||
credentials: 'same-origin'
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(errorData.detail || `HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh the file list
|
// Remove the file from the UI immediately
|
||||||
fetchAndDisplayFiles(uid);
|
if (listItem && listItem.parentNode) {
|
||||||
showToast('File deleted successfully');
|
listItem.parentNode.removeChild(listItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
showToast(`Successfully deleted "${fileToDelete}"`, 'success');
|
||||||
|
|
||||||
|
// If the file list is now empty, show a 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) {
|
} catch (error) {
|
||||||
console.error('[FILES] Error deleting file:', error);
|
console.error('[DELETE] Error deleting file:', error);
|
||||||
showToast('Error deleting file', 'error');
|
showToast(`Error deleting "${fileToDelete}": ${error.message}`, 'error');
|
||||||
|
|
||||||
|
// Reset the button state if there was an error
|
||||||
|
if (listItem) {
|
||||||
|
listItem.style.opacity = '';
|
||||||
|
listItem.style.pointerEvents = '';
|
||||||
|
const deleteButton = listItem.querySelector('.delete-file');
|
||||||
|
if (deleteButton) {
|
||||||
|
deleteButton.disabled = false;
|
||||||
|
deleteButton.innerHTML = '🗑️';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -775,7 +917,6 @@ function initFileUpload() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
showToast('File uploaded successfully!');
|
|
||||||
|
|
||||||
// Refresh file list
|
// Refresh file list
|
||||||
if (window.fetchAndDisplayFiles) {
|
if (window.fetchAndDisplayFiles) {
|
||||||
@ -834,8 +975,33 @@ function initFileUpload() {
|
|||||||
// Main initialization when the DOM is fully loaded
|
// Main initialization when the DOM is fully loaded
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
// Initialize dashboard components
|
// Initialize dashboard components
|
||||||
initDashboard();
|
initDashboard(); // initFileUpload is called from within initDashboard
|
||||||
initFileUpload();
|
|
||||||
|
// Add event delegation for delete buttons
|
||||||
|
document.addEventListener('click', (e) => {
|
||||||
|
const deleteButton = e.target.closest('.delete-file');
|
||||||
|
if (!deleteButton) return;
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const listItem = deleteButton.closest('.file-item');
|
||||||
|
if (!listItem) return;
|
||||||
|
|
||||||
|
// Get UID from localStorage
|
||||||
|
const uid = localStorage.getItem('uid') || localStorage.getItem('confirmed_uid');
|
||||||
|
if (!uid) {
|
||||||
|
showToast('You need to be logged in to delete files', 'error');
|
||||||
|
console.error('[DELETE] No UID found in localStorage');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileName = deleteButton.getAttribute('data-filename');
|
||||||
|
const displayName = deleteButton.getAttribute('data-original-name') || fileName;
|
||||||
|
|
||||||
|
// Pass the UID to deleteFile
|
||||||
|
deleteFile(uid, fileName, listItem, displayName);
|
||||||
|
});
|
||||||
|
|
||||||
// Make fetchAndDisplayFiles available globally
|
// Make fetchAndDisplayFiles available globally
|
||||||
window.fetchAndDisplayFiles = fetchAndDisplayFiles;
|
window.fetchAndDisplayFiles = fetchAndDisplayFiles;
|
||||||
@ -874,7 +1040,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
showToast('', 'success');
|
showToast('Check your email for a magic login link!', 'success');
|
||||||
// Clear the form on success
|
// Clear the form on success
|
||||||
regForm.reset();
|
regForm.reset();
|
||||||
} else {
|
} else {
|
||||||
|
134
static/fix-nav.js
Normal file
134
static/fix-nav.js
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
// Force hide guest navigation for authenticated users
|
||||||
|
function fixMobileNavigation() {
|
||||||
|
console.log('[FIX-NAV] Running navigation fix...');
|
||||||
|
|
||||||
|
// Check if user is authenticated
|
||||||
|
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;
|
||||||
|
|
||||||
|
console.log('[FIX-NAV] Authentication state:', {
|
||||||
|
isAuthenticated,
|
||||||
|
hasAuthCookie,
|
||||||
|
hasUidCookie,
|
||||||
|
hasLocalStorageAuth,
|
||||||
|
hasAuthToken
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isAuthenticated) {
|
||||||
|
// Force hide guest navigation with !important styles
|
||||||
|
const guestNav = document.getElementById('guest-dashboard');
|
||||||
|
if (guestNav) {
|
||||||
|
console.log('[FIX-NAV] Hiding guest navigation');
|
||||||
|
guestNav.style.cssText = `
|
||||||
|
display: none !important;
|
||||||
|
visibility: hidden !important;
|
||||||
|
opacity: 0 !important;
|
||||||
|
height: 0 !important;
|
||||||
|
width: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
border: none !important;
|
||||||
|
position: absolute !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
clip: rect(0, 0, 0, 0) !important;
|
||||||
|
pointer-events: none !important;
|
||||||
|
`;
|
||||||
|
guestNav.classList.add('force-hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure user navigation is visible with !important styles
|
||||||
|
const userNav = document.getElementById('user-dashboard');
|
||||||
|
if (userNav) {
|
||||||
|
console.log('[FIX-NAV] Showing user navigation');
|
||||||
|
userNav.style.cssText = `
|
||||||
|
display: flex !important;
|
||||||
|
visibility: visible !important;
|
||||||
|
opacity: 1 !important;
|
||||||
|
height: auto !important;
|
||||||
|
position: relative !important;
|
||||||
|
clip: auto !important;
|
||||||
|
pointer-events: auto !important;
|
||||||
|
`;
|
||||||
|
userNav.classList.add('force-visible');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add authenticated class to body
|
||||||
|
document.body.classList.add('authenticated');
|
||||||
|
document.body.classList.remove('guest-mode');
|
||||||
|
|
||||||
|
// Prevent default behavior of nav links that might cause page reloads
|
||||||
|
document.querySelectorAll('a[href^="#"]').forEach(link => {
|
||||||
|
link.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const targetId = link.getAttribute('href');
|
||||||
|
if (targetId && targetId !== '#') {
|
||||||
|
// Use history API to update URL without full page reload
|
||||||
|
history.pushState(null, '', targetId);
|
||||||
|
// Dispatch a custom event that other scripts can listen for
|
||||||
|
window.dispatchEvent(new CustomEvent('hashchange'));
|
||||||
|
// Force re-apply our navigation fix
|
||||||
|
setTimeout(fixMobileNavigation, 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// User is not authenticated - ensure guest nav is visible
|
||||||
|
const guestNav = document.getElementById('guest-dashboard');
|
||||||
|
if (guestNav) {
|
||||||
|
guestNav.style.cssText = ''; // Reset any inline styles
|
||||||
|
}
|
||||||
|
document.body.classList.remove('authenticated');
|
||||||
|
document.body.classList.add('guest-mode');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run on page load
|
||||||
|
document.addEventListener('DOMContentLoaded', fixMobileNavigation);
|
||||||
|
|
||||||
|
// Also run after a short delay to catch any dynamic content
|
||||||
|
setTimeout(fixMobileNavigation, 100);
|
||||||
|
setTimeout(fixMobileNavigation, 300);
|
||||||
|
setTimeout(fixMobileNavigation, 1000);
|
||||||
|
|
||||||
|
// Listen for hash changes (navigation)
|
||||||
|
window.addEventListener('hashchange', fixMobileNavigation);
|
||||||
|
|
||||||
|
// Listen for pushState/replaceState (SPA navigation)
|
||||||
|
const originalPushState = history.pushState;
|
||||||
|
const originalReplaceState = history.replaceState;
|
||||||
|
|
||||||
|
history.pushState = function() {
|
||||||
|
originalPushState.apply(this, arguments);
|
||||||
|
setTimeout(fixMobileNavigation, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
history.replaceState = function() {
|
||||||
|
originalReplaceState.apply(this, arguments);
|
||||||
|
setTimeout(fixMobileNavigation, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run on any DOM mutations (for dynamically loaded content)
|
||||||
|
const observer = new MutationObserver((mutations) => {
|
||||||
|
let shouldFix = false;
|
||||||
|
mutations.forEach((mutation) => {
|
||||||
|
if (mutation.addedNodes.length || mutation.removedNodes.length) {
|
||||||
|
shouldFix = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (shouldFix) {
|
||||||
|
setTimeout(fixMobileNavigation, 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(document.body, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ['class', 'style', 'id']
|
||||||
|
});
|
||||||
|
|
||||||
|
// Export for debugging
|
||||||
|
window.fixMobileNavigation = fixMobileNavigation;
|
@ -72,6 +72,22 @@
|
|||||||
<li>Loading files...</li>
|
<li>Loading files...</li>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Account Deletion Section -->
|
||||||
|
<section id="account-deletion" class="article--bordered auth-only">
|
||||||
|
<h3>Account Deletion</h3>
|
||||||
|
<p>This action is irreversible and will permanently remove:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Your account information</li>
|
||||||
|
<li>All uploaded audio files</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="centered-container">
|
||||||
|
<button id="delete-account-from-privacy" class="button">
|
||||||
|
🗑️ Delete My Account
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div id="spinner" class="spinner"></div>
|
<div id="spinner" class="spinner"></div>
|
||||||
@ -81,6 +97,11 @@
|
|||||||
<section id="terms-page" class="always-visible">
|
<section id="terms-page" class="always-visible">
|
||||||
<h2>Terms of Service</h2>
|
<h2>Terms of Service</h2>
|
||||||
<article class="article--bordered">
|
<article class="article--bordered">
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<strong>Beta Testing Notice:</strong> This service is currently in public beta. As such, you may encounter bugs or unexpected behavior.
|
||||||
|
Updates to the service may cause data loss. Please report any issues or suggestions to help us improve.
|
||||||
|
</div>
|
||||||
|
|
||||||
<p>By accessing or using dicta2stream.net (the "Service"), you agree to be bound by these Terms of Service ("Terms"). If you do not agree, do not use the Service.</p>
|
<p>By accessing or using dicta2stream.net (the "Service"), you agree to be bound by these Terms of Service ("Terms"). If you do not agree, do not use the Service.</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>You must be at least 18 years old to register.</li>
|
<li>You must be at least 18 years old to register.</li>
|
||||||
@ -90,6 +111,8 @@
|
|||||||
<li>The associated email address will be banned from recreating an account.</li>
|
<li>The associated email address will be banned from recreating an account.</li>
|
||||||
<li>Uploads are limited to 100 MB and must be voice only.</li>
|
<li>Uploads are limited to 100 MB and must be voice only.</li>
|
||||||
<li>Music/singing will be rejected.</li>
|
<li>Music/singing will be rejected.</li>
|
||||||
|
<li>This is a beta service; data may be lost during updates or maintenance.</li>
|
||||||
|
<li>Please report any bugs or suggestions to help improve the service.</li>
|
||||||
</ul>
|
</ul>
|
||||||
</article>
|
</article>
|
||||||
</section>
|
</section>
|
||||||
@ -103,29 +126,10 @@
|
|||||||
<li><strong>Users</strong>: Session uses both cookies and localStorage to store UID and authentication state.</li>
|
<li><strong>Users</strong>: Session uses both cookies and localStorage to store UID and authentication state.</li>
|
||||||
<li><strong>Guests</strong>: No cookies are set. No persistent identifiers are stored.</li>
|
<li><strong>Guests</strong>: No cookies are set. No persistent identifiers are stored.</li>
|
||||||
<li>We log IP + UID only for abuse protection and quota enforcement.</li>
|
<li>We log IP + UID only for abuse protection and quota enforcement.</li>
|
||||||
<li>Uploads are scanned via Whisper+Ollama but not stored as transcripts.</li>
|
|
||||||
<li>Data is never sold.</li>
|
<li>Data is never sold.</li>
|
||||||
</ul>
|
</ul>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<!-- This section will be shown only to authenticated users -->
|
|
||||||
<div class="auth-only">
|
|
||||||
<section id="account-deletion" class="article--bordered">
|
|
||||||
<h3>Account Deletion</h3>
|
|
||||||
<p>You can delete your account and all associated data at any time. This action is irreversible and will permanently remove:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Your account information</li>
|
|
||||||
<li>All uploaded audio files</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="centered-container">
|
|
||||||
<button id="delete-account-from-privacy" class="button">
|
|
||||||
🗑️ Delete My Account
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Guest login message removed as per user request -->
|
<!-- Guest login message removed as per user request -->
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@ -175,7 +179,6 @@
|
|||||||
<p><button type="submit">Login / Create Account</button></p>
|
<p><button type="submit">Login / Create Account</button></p>
|
||||||
</form>
|
</form>
|
||||||
<p class="form-note">You'll receive a magic login link via email. No password required.</p>
|
<p class="form-note">You'll receive a magic login link via email. No password required.</p>
|
||||||
<p class="form-note session-note">Your session expires after 1 hour. Shareable links redirect to homepage.</p>
|
|
||||||
</article>
|
</article>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@ -217,5 +220,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<script type="module" src="/static/init-personal-stream.js"></script>
|
<script type="module" src="/static/init-personal-stream.js"></script>
|
||||||
|
<!-- Temporary fix for mobile navigation -->
|
||||||
|
<script src="/static/fix-nav.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -36,11 +36,53 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mobile navigation */
|
/* Mobile navigation - Enhanced with more specific selectors */
|
||||||
#user-dashboard.dashboard-nav {
|
/* Show user dashboard only when authenticated */
|
||||||
|
body.authenticated #user-dashboard.dashboard-nav,
|
||||||
|
html body.authenticated #user-dashboard.dashboard-nav,
|
||||||
|
body.authenticated #user-dashboard.dashboard-nav:not(.hidden) {
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
visibility: visible !important;
|
visibility: visible !important;
|
||||||
opacity: 1 !important;
|
opacity: 1 !important;
|
||||||
|
height: auto !important;
|
||||||
|
position: relative !important;
|
||||||
|
clip: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide guest dashboard when authenticated - with more specific selectors */
|
||||||
|
body.authenticated #guest-dashboard.dashboard-nav,
|
||||||
|
html body.authenticated #guest-dashboard.dashboard-nav,
|
||||||
|
body.authenticated #guest-dashboard.dashboard-nav:not(.visible) {
|
||||||
|
display: none !important;
|
||||||
|
visibility: hidden !important;
|
||||||
|
opacity: 0 !important;
|
||||||
|
height: 0 !important;
|
||||||
|
width: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
border: none !important;
|
||||||
|
position: absolute !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
clip: rect(0, 0, 0, 0) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show guest dashboard when not authenticated - with more specific selectors */
|
||||||
|
body:not(.authenticated) #guest-dashboard.dashboard-nav,
|
||||||
|
html body:not(.authenticated) #guest-dashboard.dashboard-nav,
|
||||||
|
body:not(.authenticated) #guest-dashboard.dashboard-nav:not(.hidden) {
|
||||||
|
display: flex !important;
|
||||||
|
visibility: visible !important;
|
||||||
|
opacity: 1 !important;
|
||||||
|
height: auto !important;
|
||||||
|
position: relative !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure user dashboard is hidden when not authenticated */
|
||||||
|
body:not(.authenticated) #user-dashboard.dashboard-nav {
|
||||||
|
display: none !important;
|
||||||
|
visibility: hidden !important;
|
||||||
|
opacity: 0 !important;
|
||||||
|
height: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-nav {
|
.dashboard-nav {
|
||||||
|
Reference in New Issue
Block a user