monorepo/native/desktop/maplefile/frontend/src/components/Navigation.jsx

264 lines
12 KiB
JavaScript

import { useState, useEffect, useCallback } from 'react';
import { Link, useLocation, useNavigate } from 'react-router-dom';
import './Navigation.css';
import { LogoutWithOptions, GetLocalDataSize } from '../../wailsjs/go/app/Application';
function Navigation() {
const location = useLocation();
const navigate = useNavigate();
const [showLogoutConfirm, setShowLogoutConfirm] = useState(false);
const [isLoggingOut, setIsLoggingOut] = useState(false);
const [deleteLocalData, setDeleteLocalData] = useState(true); // Default to delete for security
const [localDataSize, setLocalDataSize] = useState(0);
const isActive = (path) => {
return location.pathname === path || location.pathname.startsWith(path + '/');
};
// Format bytes to human-readable size
const formatBytes = (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];
};
const handleLogoutClick = (e) => {
e.preventDefault();
// Get local data size when opening the modal
GetLocalDataSize()
.then((size) => {
setLocalDataSize(size);
})
.catch((error) => {
console.error('Failed to get local data size:', error);
setLocalDataSize(0);
});
setShowLogoutConfirm(true);
};
const handleLogoutConfirm = () => {
setIsLoggingOut(true);
LogoutWithOptions(deleteLocalData)
.then(() => {
// Reset state before navigating
setDeleteLocalData(true);
navigate('/login');
})
.catch((error) => {
console.error('Logout failed:', error);
alert('Logout failed: ' + error.message);
setIsLoggingOut(false);
setShowLogoutConfirm(false);
});
};
const handleLogoutCancel = useCallback(() => {
if (!isLoggingOut) {
setShowLogoutConfirm(false);
setDeleteLocalData(true); // Reset to default
}
}, [isLoggingOut]);
// Handle Escape key to close modal
useEffect(() => {
const handleKeyDown = (e) => {
if (e.key === 'Escape' && showLogoutConfirm && !isLoggingOut) {
handleLogoutCancel();
}
};
if (showLogoutConfirm) {
document.addEventListener('keydown', handleKeyDown);
}
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [showLogoutConfirm, isLoggingOut, handleLogoutCancel]);
return (
<>
<nav className="navigation">
<div className="nav-header">
<h2>MapleFile</h2>
</div>
<ul className="nav-menu">
<li className={isActive('/dashboard') ? 'active' : ''}>
<Link to="/dashboard">Dashboard</Link>
</li>
<li className={isActive('/file-manager') ? 'active' : ''}>
<Link to="/file-manager">File Manager</Link>
</li>
<li className={isActive('/search') ? 'active' : ''}>
<Link to="/search">Search</Link>
</li>
<li className={isActive('/tags/search') ? 'active' : ''}>
<Link to="/tags/search">Search by Tags</Link>
</li>
<li className={isActive('/me') ? 'active' : ''}>
<Link to="/me">Profile</Link>
</li>
<li>
<a href="#" onClick={handleLogoutClick}>Logout</a>
</li>
</ul>
</nav>
{/* Logout Confirmation Modal */}
{showLogoutConfirm && (
<div
onClick={handleLogoutCancel}
style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 1000,
}}
>
<div
onClick={(e) => e.stopPropagation()}
style={{
backgroundColor: 'white',
borderRadius: '8px',
padding: '30px',
maxWidth: '500px',
width: '90%',
boxShadow: '0 4px 20px rgba(0, 0, 0, 0.3)',
}}>
<h3 style={{ margin: '0 0 15px 0', color: '#2c3e50', textAlign: 'center' }}>
Sign Out
</h3>
<p style={{ margin: '0 0 20px 0', color: '#666', textAlign: 'center' }}>
You are about to sign out. You'll need to log in again next time.
</p>
{/* Security Warning */}
<div style={{
backgroundColor: '#fef3c7',
border: '1px solid #f59e0b',
borderRadius: '8px',
padding: '16px',
marginBottom: '20px',
}}>
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '12px' }}>
<svg style={{ width: '20px', height: '20px', color: '#d97706', flexShrink: 0, marginTop: '2px' }} fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div>
<p style={{ fontWeight: '600', color: '#d97706', margin: '0 0 4px 0', fontSize: '14px' }}>
Security Notice
</p>
<p style={{ color: '#666', margin: 0, fontSize: '13px', lineHeight: '1.5' }}>
For your security, we recommend deleting locally saved data when signing out. This includes your cached files and metadata{localDataSize > 0 ? ` (${formatBytes(localDataSize)})` : ''}. If you keep local data, anyone with access to this device may be able to view your files when you log in again.
</p>
</div>
</div>
</div>
{/* Data deletion options */}
<div style={{ marginBottom: '25px' }}>
<label style={{
display: 'flex',
alignItems: 'flex-start',
gap: '12px',
cursor: 'pointer',
marginBottom: '12px',
padding: '10px',
borderRadius: '6px',
backgroundColor: deleteLocalData ? '#fef2f2' : 'transparent',
border: deleteLocalData ? '1px solid #fca5a5' : '1px solid transparent',
}}>
<input
type="radio"
name="deleteLocalData"
checked={deleteLocalData === true}
onChange={() => setDeleteLocalData(true)}
style={{ marginTop: '4px' }}
/>
<div>
<span style={{ fontWeight: '500', color: '#2c3e50', fontSize: '14px' }}>
Delete all local data (Recommended)
</span>
<p style={{ color: '#666', margin: '4px 0 0 0', fontSize: '12px' }}>
All cached files and metadata will be removed. You'll need to re-download files from the cloud next time.
</p>
</div>
</label>
<label style={{
display: 'flex',
alignItems: 'flex-start',
gap: '12px',
cursor: 'pointer',
padding: '10px',
borderRadius: '6px',
backgroundColor: !deleteLocalData ? '#eff6ff' : 'transparent',
border: !deleteLocalData ? '1px solid #93c5fd' : '1px solid transparent',
}}>
<input
type="radio"
name="deleteLocalData"
checked={deleteLocalData === false}
onChange={() => setDeleteLocalData(false)}
style={{ marginTop: '4px' }}
/>
<div>
<span style={{ fontWeight: '500', color: '#2c3e50', fontSize: '14px' }}>
Keep local data for faster login
</span>
<p style={{ color: '#666', margin: '4px 0 0 0', fontSize: '12px' }}>
Your cached files will be preserved. Only use this on trusted personal devices.
</p>
</div>
</label>
</div>
<div style={{ display: 'flex', gap: '10px', justifyContent: 'flex-end' }}>
<button
onClick={handleLogoutCancel}
disabled={isLoggingOut}
style={{
padding: '10px 25px',
border: '1px solid #ddd',
borderRadius: '5px',
backgroundColor: '#f5f5f5',
color: '#333',
cursor: isLoggingOut ? 'not-allowed' : 'pointer',
fontSize: '14px',
}}
>
Cancel
</button>
<button
onClick={handleLogoutConfirm}
disabled={isLoggingOut}
style={{
padding: '10px 25px',
border: 'none',
borderRadius: '5px',
backgroundColor: '#3b82f6',
color: 'white',
cursor: isLoggingOut ? 'not-allowed' : 'pointer',
fontSize: '14px',
opacity: isLoggingOut ? 0.7 : 1,
}}
>
{isLoggingOut ? 'Signing out...' : 'Sign Out'}
</button>
</div>
</div>
</div>
)}
</>
);
}
export default Navigation;