2025-08-02 15:28:38 +02:00
|
|
|
import React from 'react';
|
|
|
|
|
import { Link, useLocation } from 'react-router-dom';
|
|
|
|
|
import {
|
|
|
|
|
Home,
|
|
|
|
|
FileText,
|
|
|
|
|
Rss,
|
|
|
|
|
Settings as SettingsIcon,
|
|
|
|
|
LogOut,
|
|
|
|
|
Menu,
|
|
|
|
|
X
|
|
|
|
|
} from 'lucide-react';
|
|
|
|
|
import { apiClient } from '../lib/api';
|
|
|
|
|
|
|
|
|
|
interface LayoutProps {
|
|
|
|
|
children: React.ReactNode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Layout: React.FC<LayoutProps> = ({ children }) => {
|
|
|
|
|
const [sidebarOpen, setSidebarOpen] = React.useState(false);
|
|
|
|
|
const location = useLocation();
|
|
|
|
|
const user = apiClient.getUser();
|
|
|
|
|
|
|
|
|
|
const navigation = [
|
|
|
|
|
{ name: 'Dashboard', href: '/dashboard', icon: Home },
|
|
|
|
|
{ name: 'Articles', href: '/articles', icon: FileText },
|
|
|
|
|
{ name: 'Feeds', href: '/feeds', icon: Rss },
|
|
|
|
|
{ name: 'Settings', href: '/settings', icon: SettingsIcon },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const handleLogout = async () => {
|
|
|
|
|
try {
|
|
|
|
|
await apiClient.logout();
|
|
|
|
|
window.location.reload();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Logout failed:', error);
|
|
|
|
|
apiClient.clearAuth();
|
|
|
|
|
window.location.reload();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-03 01:34:11 +02:00
|
|
|
const renderMobileOverlay = () => {
|
|
|
|
|
if (!sidebarOpen) return null;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
className="fixed inset-0 z-40 bg-gray-600 bg-opacity-75 lg:hidden"
|
|
|
|
|
onClick={() => setSidebarOpen(false)}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-02 15:28:38 +02:00
|
|
|
return (
|
|
|
|
|
<div className="min-h-screen bg-gray-50">
|
2025-08-03 01:34:11 +02:00
|
|
|
{renderMobileOverlay()}
|
|
|
|
|
|
|
|
|
|
{/* Mobile sidebar */}
|
|
|
|
|
<div className={`fixed inset-y-0 left-0 z-50 w-64 bg-white shadow-lg transform transition-transform duration-300 ease-in-out lg:hidden ${
|
|
|
|
|
sidebarOpen ? 'translate-x-0' : '-translate-x-full'
|
|
|
|
|
}`}>
|
2025-08-02 15:28:38 +02:00
|
|
|
<div className="flex items-center justify-between h-16 px-4 border-b border-gray-200">
|
2025-08-03 01:34:11 +02:00
|
|
|
<h1 className="text-xl font-bold text-gray-900">FFR</h1>
|
2025-08-02 15:28:38 +02:00
|
|
|
<button
|
|
|
|
|
onClick={() => setSidebarOpen(false)}
|
2025-08-03 01:34:11 +02:00
|
|
|
className="p-2 rounded-md text-gray-400 hover:text-gray-600 hover:bg-gray-100"
|
2025-08-02 15:28:38 +02:00
|
|
|
>
|
|
|
|
|
<X className="h-6 w-6" />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
2025-08-03 01:34:11 +02:00
|
|
|
<nav className="mt-5 px-2">
|
2025-08-02 15:28:38 +02:00
|
|
|
{navigation.map((item) => {
|
|
|
|
|
const Icon = item.icon;
|
2025-08-03 01:34:11 +02:00
|
|
|
const isActive = location.pathname === item.href;
|
2025-08-02 15:28:38 +02:00
|
|
|
return (
|
|
|
|
|
<Link
|
|
|
|
|
key={item.name}
|
|
|
|
|
to={item.href}
|
2025-08-03 01:34:11 +02:00
|
|
|
className={`group flex items-center px-2 py-2 text-base font-medium rounded-md mb-1 ${
|
|
|
|
|
isActive
|
|
|
|
|
? 'bg-blue-100 text-blue-700'
|
2025-08-02 15:28:38 +02:00
|
|
|
: 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'
|
2025-08-03 01:34:11 +02:00
|
|
|
}`}
|
2025-08-02 15:28:38 +02:00
|
|
|
onClick={() => setSidebarOpen(false)}
|
|
|
|
|
>
|
2025-08-03 01:34:11 +02:00
|
|
|
<Icon className="mr-4 h-6 w-6" />
|
2025-08-02 15:28:38 +02:00
|
|
|
{item.name}
|
|
|
|
|
</Link>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</nav>
|
2025-08-03 01:34:11 +02:00
|
|
|
</div>
|
2025-08-02 15:28:38 +02:00
|
|
|
|
2025-08-03 01:34:11 +02:00
|
|
|
{/* Desktop sidebar */}
|
|
|
|
|
<div className="hidden lg:fixed lg:inset-y-0 lg:flex lg:w-64 lg:flex-col">
|
|
|
|
|
<div className="flex flex-col flex-grow bg-white pt-5 pb-4 overflow-y-auto border-r border-gray-200">
|
|
|
|
|
<div className="flex items-center flex-shrink-0 px-4">
|
|
|
|
|
<h1 className="text-xl font-bold text-gray-900">FFR</h1>
|
|
|
|
|
</div>
|
|
|
|
|
<nav className="mt-5 flex-1 px-2 bg-white">
|
|
|
|
|
{navigation.map((item) => {
|
|
|
|
|
const Icon = item.icon;
|
|
|
|
|
const isActive = location.pathname === item.href;
|
|
|
|
|
return (
|
|
|
|
|
<Link
|
|
|
|
|
key={item.name}
|
|
|
|
|
to={item.href}
|
|
|
|
|
className={`group flex items-center px-2 py-2 text-sm font-medium rounded-md mb-1 ${
|
|
|
|
|
isActive
|
|
|
|
|
? 'bg-blue-100 text-blue-700'
|
|
|
|
|
: 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
<Icon className="mr-3 h-6 w-6" />
|
|
|
|
|
{item.name}
|
|
|
|
|
</Link>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</nav>
|
|
|
|
|
<div className="flex-shrink-0 p-4 border-t border-gray-200">
|
|
|
|
|
<div className="flex items-center">
|
|
|
|
|
<div className="flex-1 min-w-0">
|
|
|
|
|
<p className="text-sm font-medium text-gray-900 truncate">
|
|
|
|
|
{user?.name || 'User'}
|
|
|
|
|
</p>
|
|
|
|
|
<p className="text-sm text-gray-500 truncate">
|
|
|
|
|
{user?.email || 'user@example.com'}
|
|
|
|
|
</p>
|
2025-08-02 15:28:38 +02:00
|
|
|
</div>
|
2025-08-03 01:34:11 +02:00
|
|
|
<button
|
|
|
|
|
onClick={handleLogout}
|
|
|
|
|
className="ml-3 p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-md"
|
|
|
|
|
title="Logout"
|
|
|
|
|
>
|
|
|
|
|
<LogOut className="h-5 w-5" />
|
|
|
|
|
</button>
|
2025-08-02 15:28:38 +02:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Main content */}
|
2025-08-03 01:34:11 +02:00
|
|
|
<div className="lg:pl-64 flex flex-col flex-1">
|
|
|
|
|
<div className="sticky top-0 z-10 flex-shrink-0 flex h-16 bg-white shadow lg:hidden">
|
2025-08-02 15:28:38 +02:00
|
|
|
<button
|
|
|
|
|
onClick={() => setSidebarOpen(true)}
|
2025-08-03 01:34:11 +02:00
|
|
|
className="px-4 border-r border-gray-200 text-gray-500 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-blue-500 lg:hidden"
|
2025-08-02 15:28:38 +02:00
|
|
|
>
|
|
|
|
|
<Menu className="h-6 w-6" />
|
|
|
|
|
</button>
|
2025-08-03 01:34:11 +02:00
|
|
|
<div className="flex-1 px-4 flex justify-between items-center">
|
|
|
|
|
<h1 className="text-lg font-medium text-gray-900">FFR</h1>
|
2025-08-02 15:28:38 +02:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<main className="flex-1">
|
|
|
|
|
{children}
|
|
|
|
|
</main>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default Layout;
|