Akash4911's picture
Production Deploy: Improved robustness and logging
66b6851
Raw
History Blame Contribute Delete
14.2 kB
import React, { useState, useEffect } from 'react';
import { Link, useLocation, useNavigate } from 'react-router-dom';
import {
LayoutDashboard,
FileText,
Image as ImageIcon,
Mic2,
Video,
Settings,
Sun,
Moon,
LogOut,
Crown,
ShieldCheck,
ArrowRight,
User
} from 'lucide-react';
import { useTheme } from '../../hooks/useTheme';
import { useAuth } from '../../hooks/useAuth.tsx';
import logo from '../../assets/logo.png';
interface SidebarProps {
activeTab?: string;
isOpen?: boolean;
onClose?: () => void;
}
const Sidebar: React.FC<SidebarProps> = ({ activeTab, isOpen, onClose }) => {
const location = useLocation();
const navigate = useNavigate();
const { theme, toggleTheme } = useTheme();
const { user, logout, isAuthenticated } = useAuth();
const [showConfirm, setShowConfirm] = useState(false);
// Close on Esc
useEffect(() => {
if (!showConfirm) return;
const handler = (e: KeyboardEvent) => { if (e.key === 'Escape') setShowConfirm(false); };
window.addEventListener('keydown', handler);
return () => window.removeEventListener('keydown', handler);
}, [showConfirm]);
const handleLogout = () => {
logout();
navigate('/', { replace: true });
};
const menuItems = [
{ name: 'Dashboard', icon: LayoutDashboard, path: '/dashboard' },
{ name: 'Image lab', icon: ImageIcon, path: '/image-lab' },
{ name: 'Text lab', icon: FileText, path: '/text-lab' },
{ name: 'Audio lab', icon: Mic2, path: '/audio-lab' },
{ name: 'Video lab', icon: Video, path: '/video-lab' },
];
const currentTab = activeTab || menuItems.find(item => item.path === location.pathname)?.name || 'Image lab';
const isPaid = user?.subscription_tier === 'paid';
return (
<>
{/* ── Logout Confirmation Modal ── */}
{showConfirm && (
<div
className="fixed inset-0 z-[999] flex items-end sm:items-center justify-center"
style={{ background: 'rgba(0,0,0,0.55)', backdropFilter: 'blur(6px)' }}
onClick={() => setShowConfirm(false)}
>
<div
className="w-full sm:w-[400px] rounded-t-2xl sm:rounded-2xl border flex flex-col overflow-hidden"
style={{
background: 'var(--bg-primary)',
borderColor: 'var(--panel-border)',
boxShadow: '0 32px 80px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.04) inset',
}}
onClick={e => e.stopPropagation()}
>
{/* top red accent line */}
<div className="h-[2px] w-full" style={{ background: 'linear-gradient(90deg, #ef4444 0%, rgba(239,68,68,0) 60%)' }} />
{/* header */}
<div className="px-6 pt-6 pb-5 flex items-start gap-4">
<div className="w-10 h-10 rounded-xl flex items-center justify-center flex-shrink-0 mt-0.5"
style={{ background: 'rgba(239,68,68,0.08)', border: '1px solid rgba(239,68,68,0.15)' }}>
<LogOut size={17} style={{ color: '#ef4444' }} />
</div>
<div className="flex-1 min-w-0">
<p className="text-[14px] font-bold leading-snug" style={{ color: 'var(--text-heading)' }}>
Sign out of FakeShield
</p>
<p className="text-[12px] mt-1 leading-relaxed" style={{ color: 'var(--text-secondary)' }}>
Your session will be terminated and you will be redirected to the landing page.
</p>
</div>
</div>
{/* session info row */}
<div className="mx-6 mb-5 px-4 py-3 rounded-xl border flex items-center gap-3"
style={{ background: 'rgba(255,255,255,0.02)', borderColor: 'var(--panel-border)' }}>
<div className="w-7 h-7 rounded-lg flex items-center justify-center text-[11px] font-black text-black flex-shrink-0"
style={{ background: '#00E5CC' }}>
{(user?.name || user?.fullName || 'U').charAt(0).toUpperCase()}
</div>
<div className="flex-1 min-w-0">
<p className="text-[12px] font-semibold truncate" style={{ color: 'var(--text-heading)' }}>
{user?.name || user?.fullName || 'User Account'}
</p>
<p className="text-[10px] font-mono truncate" style={{ color: 'var(--text-secondary)' }}>
{user?.email ?? 'session active'}
</p>
</div>
<span className="text-[9px] font-black uppercase tracking-widest px-2 py-0.5 rounded-md flex-shrink-0"
style={{ background: 'rgba(0,229,204,0.08)', color: '#00E5CC', border: '1px solid rgba(0,229,204,0.15)' }}>
{user?.subscription_tier === 'paid' ? 'PRO' : 'FREE'}
</span>
</div>
{/* divider */}
<div className="mx-6" style={{ height: '1px', background: 'var(--panel-border)' }} />
{/* actions */}
<div className="px-6 py-5 flex items-center gap-3">
<button
onClick={() => setShowConfirm(false)}
className="flex-1 py-2.5 rounded-xl text-[13px] font-semibold border transition-all hover:bg-white/5 active:scale-95"
style={{ borderColor: 'var(--panel-border)', color: 'var(--text-secondary)' }}
>
Cancel
</button>
<button
onClick={handleLogout}
className="flex-1 py-2.5 rounded-xl text-[13px] font-bold transition-all hover:opacity-90 active:scale-95 flex items-center justify-center gap-2"
style={{ background: '#ef4444', color: '#fff' }}
>
<LogOut size={14} />
Sign out
</button>
</div>
{/* keyboard hint */}
<div className="px-6 pb-5 flex items-center gap-1.5">
<span className="text-[9px] px-1.5 py-0.5 rounded border font-mono" style={{ borderColor: 'var(--panel-border)', color: 'var(--text-secondary)' }}>Esc</span>
<span className="text-[10px]" style={{ color: 'var(--text-secondary)' }}>to cancel</span>
</div>
</div>
</div>
)}
{/* Mobile Overlay */}
{isOpen && (
<div
className="fixed inset-0 bg-black/60 backdrop-blur-sm z-[70] lg:hidden transition-opacity duration-300"
onClick={onClose}
/>
)}
<aside
className={`
fixed lg:static inset-y-0 left-0 w-64 flex-shrink-0 border-r flex flex-col z-[80]
transition-transform duration-300 transform
${isOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0'}
`}
style={{ background: 'var(--sidebar-bg)', borderColor: 'var(--panel-border)' }}
>
{/* Brand */}
<div className="p-8 pb-4 flex items-center justify-between">
<Link to="/dashboard" className="transition-transform hover:scale-105 active:scale-95">
<img
src={logo}
alt="FakeShield Logo"
style={{
height: '5.5rem',
width: 'auto',
filter: theme === 'dark' ? 'brightness(1.1) contrast(1.1)' : 'none'
}}
/>
</Link>
{isPaid && (
<div className="bg-gradient-to-r from-[#FFD700] to-[#FFA500] p-1.5 rounded-lg shadow-lg">
<Crown size={16} className="text-black" />
</div>
)}
</div>
{/* Subscription Status Card - Clean Light Theme */}
<div className="px-6 py-6 mt-2">
<div className="group relative p-4 rounded-2xl bg-[var(--panel-bg)] border border-[var(--panel-border)] hover:border-[#00E5CC]/50 transition-all duration-300 shadow-sm overflow-hidden">
{/* Subtle Background Accent */}
<div className="absolute -top-12 -right-12 w-24 h-24 bg-[#00E5CC]/5 rounded-full blur-2xl group-hover:bg-[#00E5CC]/10 transition-all duration-500"></div>
<div className="relative z-10">
<div className="flex items-center justify-between mb-4">
<div className="flex flex-col">
<span className="text-[9px] font-bold text-[var(--text-muted)] uppercase tracking-[0.2em] mb-0.5">Account Status</span>
<span className={`text-xs font-black tracking-wide ${isPaid ? 'text-[#00E5CC]' : 'text-[var(--text-primary)]'}`}>
{isPaid ? 'PREMIUM SHIELD' : 'STANDARD ACCESS'}
</span>
</div>
<div className={`p-1.5 rounded-lg ${isPaid ? 'bg-[#00E5CC]/10' : 'bg-[var(--btn-secondary-bg)]'}`}>
{isPaid ? <Crown size={14} className="text-[#00E5CC]" /> : <ShieldCheck size={14} className="text-slate-400" />}
</div>
</div>
{!isPaid ? (
<Link
to="/subscription"
className="flex items-center justify-center gap-2 w-full py-2.5 rounded-xl bg-[#00E5CC] hover:bg-[#00d1ba] text-black text-[11px] font-black transition-all duration-200 active:scale-95 shadow-lg shadow-[#00E5CC]/20"
>
<span>Unlock Full Access</span>
<ArrowRight size={12} className="opacity-70 group-hover:translate-x-1 transition-transform" />
</Link>
) : (
<div className="h-1 w-full bg-[var(--btn-secondary-bg)] rounded-full overflow-hidden mt-2">
<div className="h-full w-full bg-gradient-to-r from-[#00E5CC] to-indigo-500"></div>
</div>
)}
</div>
</div>
</div>
{/* Navigation */}
<nav className="flex-1 px-4 py-4 space-y-2 overflow-y-auto custom-scrollbar">
{menuItems.map((item) => {
const isActive = item.name === currentTab;
const Icon = item.icon;
return (
<Link
key={item.name}
to={item.path}
onClick={onClose}
className={`
flex items-center gap-3 px-4 py-3 rounded-xl transition-all duration-300 cursor-pointer group
${isActive
? 'bg-[#00E5CC]/10 border border-[#00E5CC]/30 text-[#00E5CC] shadow-[0_0_15px_rgba(0,229,204,0.15)]'
: 'text-[var(--text-inactive)] hover:text-[var(--text-active)] hover:bg-[var(--sidebar-hover-bg)]'
}
`}
>
<Icon className={`w-5 h-5 ${isActive ? 'text-[#00E5CC]' : 'group-hover:text-[var(--text-active)] transition-colors'}`} />
<span className="font-medium text-[15px]">{item.name}</span>
</Link>
);
})}
</nav>
{/* Footer / Settings at bottom */}
<div className="p-4 flex flex-col gap-1 border-t" style={{ borderColor: 'var(--panel-border)' }}>
<Link
to="/settings"
onClick={onClose}
className={`
flex items-center gap-3 px-4 py-3 rounded-xl transition-all duration-300 cursor-pointer group
${currentTab === 'Settings'
? 'bg-[#00E5CC]/10 border border-[#00E5CC]/30 text-[#00E5CC] shadow-[0_0_15px_rgba(0,229,204,0.15)]'
: 'text-[var(--text-inactive)] hover:text-[var(--text-active)] hover:bg-[var(--sidebar-hover-bg)]'
}
`}
>
<Settings className={`w-5 h-5 ${currentTab === 'Settings' ? 'text-[#00E5CC]' : 'group-hover:text-[var(--text-active)] transition-colors'}`} />
<span className="font-medium text-[15px]">Settings</span>
</Link>
<button
onClick={(e) => toggleTheme(e)}
className="flex items-center gap-3 px-4 py-3 rounded-xl transition-all duration-300 cursor-pointer group text-[var(--text-inactive)] hover:text-[var(--text-active)] hover:bg-[var(--sidebar-hover-bg)]"
>
{theme === 'dark' ? (
<>
<Sun className="w-5 h-5 group-hover:text-[var(--text-active)] transition-colors" />
<span className="font-medium text-[15px]">Light Mode</span>
</>
) : (
<>
<Moon className="w-5 h-5 group-hover:text-[var(--text-active)] transition-colors" />
<span className="font-medium text-[15px]">Dark Mode</span>
</>
)}
</button>
{/* Combined Profile & Logout Section */}
<div className="flex items-center gap-2 p-1 mt-2 rounded-2xl border border-transparent hover:border-[var(--panel-border)] transition-all group/container">
<Link
to="/settings"
className="flex-1 flex items-center gap-3 p-2 rounded-xl hover:bg-[var(--sidebar-hover-bg)] transition-all duration-300 group/profile"
>
<div className="w-9 h-9 rounded-xl overflow-hidden border-2 border-[#00E5CC] shadow-sm group-hover/profile:scale-105 transition-transform flex-shrink-0">
{user?.profile_pic ? (
<img
src={user.profile_pic}
alt="Profile"
className="w-full h-full object-cover"
/>
) : (
<div className="w-full h-full bg-gradient-to-br from-[#00E5CC] to-[#00bfa5] flex items-center justify-center text-white shadow-inner">
<User size={16} strokeWidth={2.5} />
</div>
)}
</div>
<div className="flex-1 min-w-0">
<p className="text-[13px] font-bold truncate" style={{ color: 'var(--text-heading)' }}>
{user?.name || user?.fullName || 'User Account'}
</p>
<p className="text-[9px] font-bold uppercase tracking-widest" style={{ color: '#00E5CC' }}>
Forensic Analyst
</p>
</div>
</Link>
<button
onClick={() => setShowConfirm(true)}
title="Logout"
className="p-2.5 rounded-xl text-red-400 hover:text-red-500 hover:bg-red-500/10 transition-all active:scale-90 flex-shrink-0"
>
<LogOut size={18} />
</button>
</div>
</div>
</aside>
</>
);
};
export default Sidebar;