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;
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
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>
</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>

View File

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