anasraza526's picture
Clean deploy to Hugging Face
ac90985
import React, { useState, useEffect } from 'react';
import { Link, useLocation, useNavigate, Outlet } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';
import { useWebsites } from '../context/WebsiteContext';
import toast from 'react-hot-toast';
import {
LayoutDashboard,
MessageSquare,
FileText,
HelpCircle,
Settings as SettingsIcon,
LogOut,
Menu,
X,
User,
Globe,
Users,
Bell
} from 'lucide-react';
const Layout = ({ children }) => {
const { user, logout } = useAuth();
const { websites } = useWebsites();
const location = useLocation();
const navigate = useNavigate();
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const [unreadNotifications, setUnreadNotifications] = useState(0);
const hasWebsites = websites.length > 0;
useEffect(() => {
const fetchUnreadCount = async () => {
try {
const response = await api.get('/notifications/unread-count');
setUnreadNotifications(response.data.count);
} catch (error) {
console.error('Failed to fetch unread count:', error);
}
};
if (user) {
fetchUnreadCount();
const interval = setInterval(fetchUnreadCount, 60000); // Poll every minute
return () => clearInterval(interval);
}
}, [user]);
const handleLogout = () => {
logout();
navigate('/login');
};
const navItems = [
{ icon: LayoutDashboard, label: 'Dashboard', path: '/dashboard' },
{ icon: Globe, label: 'Websites', path: '/websites' },
{ icon: MessageSquare, label: 'Chats', path: '/chats' },
{ icon: Users, label: 'Users', path: '/users' },
{ icon: FileText, label: 'Content', path: '/content' },
{ icon: HelpCircle, label: 'Unanswered', path: '/unanswered-questions' },
{ icon: MessageSquare, label: 'FAQs', path: '/faqs' },
{ icon: Bell, label: 'Notifications', path: '/notifications' },
{ icon: SettingsIcon, label: 'Settings', path: '/settings' },
];
return (
<div className="min-h-screen bg-secondary-50 flex">
{/* Sidebar */}
<aside
className={`fixed inset-y-0 left-0 z-50 w-64 bg-white border-r border-secondary-200 transform transition-transform duration-300 ease-in-out lg:translate-x-0 lg:static lg:inset-0 ${isSidebarOpen ? 'translate-x-0' : '-translate-x-full'
}`}
>
<div className="h-full flex flex-col">
{/* Logo */}
<div className="h-16 flex items-center px-6 border-b border-secondary-100">
<span className="text-xl font-bold bg-gradient-to-r from-primary-600 to-primary-400 bg-clip-text text-transparent">
CustomerAgent
</span>
</div>
{/* Navigation */}
<nav className="flex-1 px-4 py-6 space-y-1 overflow-y-auto">
{navItems.map((item) => {
const Icon = item.icon;
const isActive = location.pathname === item.path;
const isAlwaysEnabled = ['Websites', 'Settings', 'Notifications'].includes(item.label);
const isDisabled = !hasWebsites && !isAlwaysEnabled;
return (
<Link
key={item.path}
to={isDisabled ? '#' : item.path}
onClick={(e) => {
if (isDisabled) {
e.preventDefault();
toast.error('Please add a website first to access this section', {
id: 'sidebar-disabled-toast'
});
}
}}
className={`flex items-center px-4 py-3 text-sm font-medium rounded-xl transition-all duration-200 ${isActive
? 'bg-primary-50 text-primary-700 shadow-sm'
: isDisabled
? 'text-secondary-300 cursor-not-allowed'
: 'text-secondary-600 hover:bg-secondary-50 hover:text-secondary-900'
}`}
>
<Icon className={`w-5 h-5 mr-3 ${isActive ? 'text-primary-600' : isDisabled ? 'text-secondary-200' : 'text-secondary-400'}`} />
{item.label}
</Link>
);
})}
</nav>
{/* User Profile */}
<div className="p-4 border-t border-secondary-100">
<div className="flex items-center p-3 rounded-xl bg-secondary-50 mb-3">
<div className="w-10 h-10 rounded-full bg-primary-100 flex items-center justify-center text-primary-600 font-semibold">
{user?.email?.[0].toUpperCase() || <User className="w-5 h-5" />}
</div>
<div className="ml-3 overflow-hidden">
<p className="text-sm font-medium text-secondary-900 truncate">{user?.email}</p>
<p className="text-xs text-secondary-500 truncate">Admin</p>
</div>
</div>
<button
onClick={handleLogout}
className="w-full flex items-center justify-center px-4 py-2 text-sm font-medium text-red-600 bg-red-50 rounded-lg hover:bg-red-100 transition-colors"
>
<LogOut className="w-4 h-4 mr-2" />
Sign Out
</button>
</div>
</div>
</aside>
{/* Mobile Overlay */}
{isSidebarOpen && (
<div
className="fixed inset-0 z-40 bg-secondary-900/50 backdrop-blur-sm lg:hidden"
onClick={() => setIsSidebarOpen(false)}
/>
)}
{/* Main Content */}
<div className="flex-1 flex flex-col min-w-0 overflow-hidden">
<header className="h-16 bg-white border-b border-secondary-200 flex items-center justify-between px-4 lg:px-8 shrink-0">
<div className="flex items-center gap-4">
<button
onClick={() => setIsSidebarOpen(!isSidebarOpen)}
className="p-2 rounded-lg text-secondary-600 hover:bg-secondary-100 lg:hidden"
>
{isSidebarOpen ? <X className="w-6 h-6" /> : <Menu className="w-6 h-6" />}
</button>
<span className="text-lg font-bold text-secondary-900 lg:hidden">CustomerAgent</span>
</div>
<div className="flex items-center gap-4">
<Link
to="/notifications"
className="p-2 bg-secondary-50 text-secondary-600 hover:bg-secondary-100 rounded-xl transition-all relative group"
title="Notifications"
>
<Bell className="w-5 h-5 group-hover:rotate-12 transition-transform" />
{unreadNotifications > 0 && (
<span className="absolute top-1.5 right-1.5 w-2.5 h-2.5 bg-red-500 border-2 border-white rounded-full animate-pulse" />
)}
</Link>
</div>
</header>
{/* Page Content */}
<main className="flex-1 overflow-y-auto p-4 lg:p-8">
<div className="max-w-7xl mx-auto">
<Outlet />
</div>
</main>
</div>
</div>
);
};
export default Layout;