fedi-feed-router/resources/js/components/Layout.tsx

150 lines
No EOL
4.8 KiB
TypeScript

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);
// Force logout even if API call fails
apiClient.clearAuth();
window.location.reload();
}
};
return (
<div className="min-h-screen bg-gray-50">
{/* Mobile sidebar overlay */}
{sidebarOpen && (
<div
className="fixed inset-0 z-40 bg-gray-600 bg-opacity-75 lg:hidden"
onClick={() => setSidebarOpen(false)}
/>
)}
{/* 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:translate-x-0 lg:static lg:inset-0
${sidebarOpen ? 'translate-x-0' : '-translate-x-full'}
`}>
<div className="flex items-center justify-between h-16 px-4 border-b border-gray-200">
<div className="flex items-center">
<img className="h-8 w-auto" src="/images/ffr-logo-600.png" alt="FFR" />
<span className="ml-2 text-xl font-semibold text-gray-900">FFR</span>
</div>
<button
onClick={() => setSidebarOpen(false)}
className="lg:hidden p-2 rounded-md text-gray-400 hover:text-gray-500"
>
<X className="h-6 w-6" />
</button>
</div>
<nav className="flex-1 px-4 py-4 space-y-2">
{navigation.map((item) => {
const isActive = location.pathname === item.href;
const Icon = item.icon;
return (
<Link
key={item.name}
to={item.href}
className={`
flex items-center px-3 py-2 text-sm font-medium rounded-md transition-colors
${isActive
? 'bg-blue-50 text-blue-600 border-r-2 border-blue-600'
: 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'
}
`}
onClick={() => setSidebarOpen(false)}
>
<Icon className="mr-3 h-5 w-5" />
{item.name}
</Link>
);
})}
</nav>
{/* User section */}
<div className="border-t border-gray-200 p-4">
<div className="flex items-center">
<div className="flex-shrink-0">
<div className="h-8 w-8 rounded-full bg-blue-500 flex items-center justify-center">
<span className="text-sm font-medium text-white">
{user?.name?.charAt(0)?.toUpperCase()}
</span>
</div>
</div>
<div className="ml-3 flex-1 min-w-0">
<p className="text-sm font-medium text-gray-900 truncate">
{user?.name}
</p>
<p className="text-xs text-gray-500 truncate">
{user?.email}
</p>
</div>
<button
onClick={handleLogout}
className="ml-2 p-2 text-gray-400 hover:text-gray-500 rounded-md"
title="Logout"
>
<LogOut className="h-4 w-4" />
</button>
</div>
</div>
</div>
{/* Main content */}
<div className="lg:pl-64">
{/* Top header for mobile */}
<div className="lg:hidden flex items-center justify-between h-16 px-4 bg-white border-b border-gray-200">
<button
onClick={() => setSidebarOpen(true)}
className="p-2 rounded-md text-gray-400 hover:text-gray-500"
>
<Menu className="h-6 w-6" />
</button>
<div className="flex items-center">
<img className="h-8 w-auto" src="/images/ffr-logo-600.png" alt="FFR" />
<span className="ml-2 text-xl font-semibold text-gray-900">FFR</span>
</div>
<div className="w-10" /> {/* Spacer for centering */}
</div>
{/* Page content */}
<main className="flex-1">
{children}
</main>
</div>
</div>
);
};
export default Layout;