131 lines
4.1 KiB
TypeScript
131 lines
4.1 KiB
TypeScript
import type { ReactNode } from 'react'
|
|
import { useNavigate, useLocation } from 'react-router-dom'
|
|
import { useAuthStore } from '@/stores/authStore'
|
|
import { Button } from '@/components/ui/button'
|
|
import {
|
|
LayoutDashboard,
|
|
FileText,
|
|
Package,
|
|
Users,
|
|
ClipboardList,
|
|
LogOut,
|
|
Menu,
|
|
X
|
|
} from 'lucide-react'
|
|
import { useState } from 'react'
|
|
|
|
interface LayoutProps {
|
|
children: ReactNode
|
|
}
|
|
|
|
const navigation = [
|
|
{ name: 'Dashboard', href: '/', icon: LayoutDashboard },
|
|
{ name: 'Cards', href: '/cards', icon: FileText },
|
|
{ name: 'Packs', href: '/packs', icon: Package },
|
|
{ name: 'Tests', href: '/tests', icon: ClipboardList },
|
|
{ name: 'Users', href: '/users', icon: Users },
|
|
]
|
|
|
|
export default function Layout({ children }: LayoutProps) {
|
|
const navigate = useNavigate()
|
|
const location = useLocation()
|
|
const { user, logout } = useAuthStore()
|
|
const [sidebarOpen, setSidebarOpen] = useState(false)
|
|
|
|
const handleLogout = () => {
|
|
logout()
|
|
navigate('/login')
|
|
}
|
|
|
|
return (
|
|
<div className="flex h-screen bg-gray-50">
|
|
{/* Mobile sidebar overlay */}
|
|
{sidebarOpen && (
|
|
<div
|
|
className="fixed inset-0 z-40 lg:hidden"
|
|
onClick={() => setSidebarOpen(false)}
|
|
>
|
|
<div className="fixed inset-0 bg-gray-600 bg-opacity-75" />
|
|
</div>
|
|
)}
|
|
|
|
{/* 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 flex-col h-full">
|
|
{/* Logo */}
|
|
<div className="flex items-center justify-between h-16 px-4 border-b border-gray-200">
|
|
<h1 className="text-xl font-bold text-gray-900">Mnemo Admin</h1>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="lg:hidden"
|
|
onClick={() => setSidebarOpen(false)}
|
|
>
|
|
<X className="h-5 w-5" />
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Navigation */}
|
|
<nav className="flex-1 px-4 py-4 space-y-2">
|
|
{navigation.map((item) => {
|
|
const Icon = item.icon
|
|
const isActive = location.pathname === item.href
|
|
return (
|
|
<Button
|
|
key={item.name}
|
|
variant={isActive ? "default" : "ghost"}
|
|
className="w-full justify-start"
|
|
onClick={() => {
|
|
navigate(item.href)
|
|
setSidebarOpen(false)
|
|
}}
|
|
>
|
|
<Icon className="mr-3 h-5 w-5" />
|
|
{item.name}
|
|
</Button>
|
|
)
|
|
})}
|
|
</nav>
|
|
|
|
{/* User info and logout */}
|
|
<div className="p-4 border-t border-gray-200">
|
|
<div className="flex items-center justify-between">
|
|
<div className="text-sm">
|
|
<p className="font-medium text-gray-900">{user?.name || 'Admin'}</p>
|
|
<p className="text-gray-500">{user?.email}</p>
|
|
</div>
|
|
<Button variant="ghost" size="sm" onClick={handleLogout}>
|
|
<LogOut className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Main content */}
|
|
<div className="flex-1 flex flex-col overflow-hidden">
|
|
{/* Top bar */}
|
|
<header className="bg-white shadow-sm border-b border-gray-200 lg:hidden">
|
|
<div className="flex items-center justify-between h-16 px-4">
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={() => setSidebarOpen(true)}
|
|
>
|
|
<Menu className="h-5 w-5" />
|
|
</Button>
|
|
<h1 className="text-lg font-semibold text-gray-900">Mnemo Cards Admin</h1>
|
|
<div className="w-10" /> {/* Spacer */}
|
|
</div>
|
|
</header>
|
|
|
|
{/* Page content */}
|
|
<main className="flex-1 overflow-auto p-6">
|
|
{children}
|
|
</main>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|