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:
oib
2025-07-20 09:24:51 +02:00
parent c5412b07ac
commit da28b205e5
4 changed files with 479 additions and 132 deletions

View File

@ -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 {
// 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 { try {
console.log('[LOGOUT] Starting logout process'); // Clear client state immediately to prevent any race conditions
clearClientState();
// Clear user data from localStorage // 2. Try to invalidate the server session (but don't block on it)
localStorage.removeItem('uid'); console.log('[LOGOUT] Auth token exists:', !!authToken);
localStorage.removeItem('uid_time'); if (authToken) {
localStorage.removeItem('confirmed_uid'); try {
localStorage.removeItem('last_page'); console.log('[LOGOUT] Attempting to invalidate server session');
// Clear session cookie with SameSite attribute to match how it was set const response = await fetch('/api/logout', {
const isLocalhost = window.location.hostname === 'localhost'; method: 'POST',
const secureFlag = !isLocalhost ? '; Secure' : ''; credentials: 'include',
document.cookie = `sessionid=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT; SameSite=Lax${secureFlag};`; headers: {
'Content-Type': 'application/json',
// Update UI state immediately 'Authorization': `Bearer ${authToken}`
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 (!response.ok && response.status !== 401) {
console.warn(`[LOGOUT] Server returned ${response.status} during logout`);
if (userDashboard) userDashboard.style.display = 'none'; // Don't throw - we've already cleared client state
if (guestDashboard) guestDashboard.style.display = 'block'; } else {
if (logoutButton) logoutButton.style.display = 'none'; console.log('[LOGOUT] Server session invalidated successfully');
if (deleteAccountButton) deleteAccountButton.style.display = 'none'; }
} catch (error) {
// Show success message (only once) console.warn('[LOGOUT] Error during server session invalidation (non-critical):', error);
if (window.showToast) { // Continue with logout process
showToast('Logged out successfully'); }
} else {
console.log('Logged out successfully');
} }
// Navigate to register page // 3. Update navigation if the function exists
if (window.showOnly) { if (typeof injectNavigation === 'function') {
window.showOnly('register-page'); injectNavigation(false);
} else {
// Fallback to URL change if showOnly isn't available
window.location.href = '/#register-page';
} }
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
View 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;

View File

@ -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>

View File

@ -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 {