Files
Charon/frontend/src/components/Layout.tsx

102 lines
3.9 KiB
TypeScript

import { ReactNode, useState } from 'react'
import { Link, useLocation } from 'react-router-dom'
import { ThemeToggle } from './ThemeToggle'
import { Button } from './ui/Button'
import { useAuth } from '../context/AuthContext'
interface LayoutProps {
children: ReactNode
}
export default function Layout({ children }: LayoutProps) {
const location = useLocation()
const [sidebarOpen, setSidebarOpen] = useState(false)
const { logout } = useAuth()
const navigation = [
{ name: 'Dashboard', path: '/', icon: '📊' },
{ name: 'Proxy Hosts', path: '/proxy-hosts', icon: '🌐' },
{ name: 'Remote Servers', path: '/remote-servers', icon: '🖥️' },
{ name: 'Certificates', path: '/certificates', icon: '🔒' },
{ name: 'Import Caddyfile', path: '/import', icon: '📥' },
{ name: 'Settings', path: '/settings', icon: '⚙️' },
]
return (
<div className="min-h-screen bg-gray-50 dark:bg-dark-bg flex transition-colors duration-200">
{/* Mobile Header */}
<div className="lg:hidden fixed top-0 left-0 right-0 h-16 bg-white dark:bg-dark-sidebar border-b border-gray-200 dark:border-gray-800 flex items-center justify-between px-4 z-40">
<h1 className="text-lg font-bold text-gray-900 dark:text-white">CPM+</h1>
<div className="flex items-center gap-2">
<ThemeToggle />
<Button variant="ghost" size="sm" onClick={() => setSidebarOpen(!sidebarOpen)}>
{sidebarOpen ? '✕' : '☰'}
</Button>
</div>
</div>
{/* Sidebar */}
<aside className={`
fixed lg:static inset-y-0 left-0 z-30 w-64 transform transition-transform duration-200 ease-in-out
bg-white dark:bg-dark-sidebar border-r border-gray-200 dark:border-gray-800 flex flex-col
${sidebarOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0'}
`}>
<div className="p-6 hidden lg:flex items-center justify-between">
<h1 className="text-xl font-bold text-gray-900 dark:text-white">CPM+</h1>
<ThemeToggle />
</div>
<nav className="flex-1 px-4 space-y-1 mt-16 lg:mt-0">
{navigation.map((item) => {
const isActive = location.pathname === item.path
return (
<Link
key={item.path}
to={item.path}
onClick={() => setSidebarOpen(false)}
className={`flex items-center gap-3 px-4 py-3 rounded-lg text-sm font-medium transition-colors ${
isActive
? 'bg-blue-100 text-blue-700 dark:bg-blue-active dark:text-white'
: 'text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-white'
}`}
>
<span className="text-lg">{item.icon}</span>
{item.name}
</Link>
)
})}
<button
onClick={() => {
setSidebarOpen(false)
logout()
}}
className="w-full flex items-center gap-3 px-4 py-3 rounded-lg text-sm font-medium transition-colors text-gray-600 dark:text-gray-400 hover:bg-red-50 dark:hover:bg-red-900/20 hover:text-red-600 dark:hover:text-red-400"
>
<span className="text-lg">🚪</span>
Logout
</button>
</nav>
<div className="p-4 border-t border-gray-200 dark:border-gray-800">
<div className="text-xs text-gray-500 dark:text-gray-500">
Version 0.1.0
</div>
</div>
</aside>
{/* Overlay for mobile */}
{sidebarOpen && (
<div
className="fixed inset-0 bg-black/50 z-20 lg:hidden"
onClick={() => setSidebarOpen(false)}
/>
)}
{/* Main content */}
<main className="flex-1 overflow-auto pt-16 lg:pt-0">
{children}
</main>
</div>
)
}