264 lines
12 KiB
JavaScript
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;
|