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;
|
||||
|
||||
async function handleLogout(event) {
|
||||
console.log('[LOGOUT] Logout initiated');
|
||||
// Prevent multiple simultaneous logout attempts
|
||||
if (isLoggingOut) return;
|
||||
if (isLoggingOut) {
|
||||
console.log('[LOGOUT] Logout already in progress');
|
||||
return;
|
||||
}
|
||||
isLoggingOut = true;
|
||||
|
||||
// Prevent default button behavior
|
||||
@ -23,44 +27,145 @@ async function handleLogout(event) {
|
||||
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 {
|
||||
// We'll use a timeout to prevent hanging on the server request
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 2000);
|
||||
|
||||
try {
|
||||
await fetch('/api/logout', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
signal: controller.signal,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${authToken}`
|
||||
},
|
||||
});
|
||||
clearTimeout(timeoutId);
|
||||
} catch (error) {
|
||||
clearTimeout(timeoutId);
|
||||
// Silently handle any errors during server logout
|
||||
}
|
||||
} catch (error) {
|
||||
// Silently handle any unexpected errors
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Clear all client-side state
|
||||
function clearClientState() {
|
||||
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 {
|
||||
console.log('[LOGOUT] Starting logout process');
|
||||
// Clear client state immediately to prevent any race conditions
|
||||
clearClientState();
|
||||
|
||||
// Clear user data from localStorage
|
||||
localStorage.removeItem('uid');
|
||||
localStorage.removeItem('uid_time');
|
||||
localStorage.removeItem('confirmed_uid');
|
||||
localStorage.removeItem('last_page');
|
||||
// 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');
|
||||
|
||||
// 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};`;
|
||||
const response = await fetch('/api/logout', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${authToken}`
|
||||
},
|
||||
});
|
||||
|
||||
// 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');
|
||||
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 {
|
||||
console.log('[LOGOUT] Server session invalidated successfully');
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[LOGOUT] Error during server session invalidation (non-critical):', error);
|
||||
// Continue with logout process
|
||||
}
|
||||
}
|
||||
|
||||
// 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';
|
||||
// 3. Update navigation if the function exists
|
||||
if (typeof injectNavigation === 'function') {
|
||||
injectNavigation(false);
|
||||
}
|
||||
|
||||
console.log('[LOGOUT] Logout completed');
|
||||
@ -72,6 +177,11 @@ async function handleLogout(event) {
|
||||
}
|
||||
} finally {
|
||||
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 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
|
||||
console.log('[AUTH] Authentication state:', {
|
||||
hasAuthCookie,
|
||||
@ -485,52 +604,16 @@ async function initDashboard() {
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
// Delete file function is defined below with more complete implementation
|
||||
|
||||
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
|
||||
// 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) {
|
||||
@ -560,8 +643,8 @@ async function fetchAndDisplayFiles(uid) {
|
||||
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', {
|
||||
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
|
||||
@ -617,14 +700,29 @@ async function fetchAndDisplayFiles(uid) {
|
||||
// Clear the loading message
|
||||
fileList.innerHTML = '';
|
||||
|
||||
// Track displayed files to prevent duplicates using stored filenames as unique identifiers
|
||||
const displayedFiles = new Set();
|
||||
|
||||
// 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
|
||||
files.forEach(file => {
|
||||
// Get the stored filename (with UUID) - this is our unique identifier
|
||||
const storedFileName = file.stored_name || file.name || file;
|
||||
|
||||
// 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');
|
||||
listItem.className = 'file-item';
|
||||
listItem.setAttribute('data-uid', uid);
|
||||
|
||||
// Create file icon based on file extension
|
||||
let fileIcon = '📄'; // Default icon
|
||||
@ -636,11 +734,14 @@ async function fetchAndDisplayFiles(uid) {
|
||||
fileIcon = '📄';
|
||||
}
|
||||
|
||||
// Use original_name if available, otherwise use the stored filename for display
|
||||
const displayName = file.original_name || storedFileName;
|
||||
|
||||
listItem.innerHTML = `
|
||||
<div class="file-info">
|
||||
<span class="file-icon">${fileIcon}</span>
|
||||
<a href="${fileUrl}" class="file-name" target="_blank" rel="noopener noreferrer">
|
||||
${fileName}
|
||||
${displayName}
|
||||
</a>
|
||||
<span class="file-size">${fileSize}</span>
|
||||
</div>
|
||||
@ -649,18 +750,15 @@ async function fetchAndDisplayFiles(uid) {
|
||||
<span class="button-icon">⬇️</span>
|
||||
<span class="button-text">Download</span>
|
||||
</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-text">Delete</span>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Add delete button handler
|
||||
const deleteButton = listItem.querySelector('.delete-button');
|
||||
if (deleteButton) {
|
||||
deleteButton.addEventListener('click', () => deleteFile(uid, fileName, listItem));
|
||||
}
|
||||
// Delete button handler will be handled by event delegation
|
||||
// No need to add individual event listeners here
|
||||
|
||||
fileList.appendChild(listItem);
|
||||
});
|
||||
@ -693,31 +791,75 @@ async function fetchAndDisplayFiles(uid) {
|
||||
}
|
||||
|
||||
// Function to handle file deletion
|
||||
async function deleteFile(fileId, uid) {
|
||||
if (!confirm('Are you sure you want to delete this file?')) {
|
||||
async function deleteFile(uid, fileName, listItem, displayName = '') {
|
||||
const fileToDelete = displayName || fileName;
|
||||
if (!confirm(`Are you sure you want to delete "${fileToDelete}"?`)) {
|
||||
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 {
|
||||
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',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
credentials: 'same-origin'
|
||||
headers: headers,
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
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
|
||||
fetchAndDisplayFiles(uid);
|
||||
showToast('File deleted successfully');
|
||||
// Remove the file from the UI immediately
|
||||
if (listItem && listItem.parentNode) {
|
||||
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) {
|
||||
console.error('[FILES] Error deleting file:', error);
|
||||
showToast('Error deleting file', 'error');
|
||||
console.error('[DELETE] 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();
|
||||
showToast('File uploaded successfully!');
|
||||
|
||||
// Refresh file list
|
||||
if (window.fetchAndDisplayFiles) {
|
||||
@ -834,8 +975,33 @@ function initFileUpload() {
|
||||
// Main initialization when the DOM is fully loaded
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Initialize dashboard components
|
||||
initDashboard();
|
||||
initFileUpload();
|
||||
initDashboard(); // initFileUpload is called from within initDashboard
|
||||
|
||||
// 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
|
||||
window.fetchAndDisplayFiles = fetchAndDisplayFiles;
|
||||
@ -874,7 +1040,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
|
||||
if (res.ok) {
|
||||
showToast('', 'success');
|
||||
showToast('Check your email for a magic login link!', 'success');
|
||||
// Clear the form on success
|
||||
regForm.reset();
|
||||
} 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>
|
||||
</ul>
|
||||
</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>
|
||||
|
||||
<div id="spinner" class="spinner"></div>
|
||||
@ -81,6 +97,11 @@
|
||||
<section id="terms-page" class="always-visible">
|
||||
<h2>Terms of Service</h2>
|
||||
<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>
|
||||
<ul>
|
||||
<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>Uploads are limited to 100 MB and must be voice only.</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>
|
||||
</article>
|
||||
</section>
|
||||
@ -103,29 +126,10 @@
|
||||
<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>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>
|
||||
</ul>
|
||||
</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 -->
|
||||
</section>
|
||||
|
||||
@ -175,7 +179,6 @@
|
||||
<p><button type="submit">Login / Create Account</button></p>
|
||||
</form>
|
||||
<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>
|
||||
</section>
|
||||
|
||||
@ -217,5 +220,7 @@
|
||||
}
|
||||
</script>
|
||||
<script type="module" src="/static/init-personal-stream.js"></script>
|
||||
<!-- Temporary fix for mobile navigation -->
|
||||
<script src="/static/fix-nav.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -36,11 +36,53 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Mobile navigation */
|
||||
#user-dashboard.dashboard-nav {
|
||||
/* Mobile navigation - Enhanced with more specific selectors */
|
||||
/* 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;
|
||||
visibility: visible !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 {
|
||||
|
Reference in New Issue
Block a user