Files
ckeproekt/app/admin/layout.tsx

222 lines
7.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import React, { useState, useEffect } from 'react';
import Link from 'next/link';
import { usePathname, useRouter } from 'next/navigation';
import { Building, FileText, Settings, LogOut, Menu, X } from 'lucide-react';
interface AdminLayoutProps {
children: React.ReactNode;
}
export default function AdminLayout({ children }: AdminLayoutProps) {
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const pathname = usePathname();
const router = useRouter();
useEffect(() => {
// Проверяем авторизацию
const adminAuth = localStorage.getItem('adminAuth');
if (adminAuth) {
setIsAuthenticated(true);
}
setIsLoading(false);
}, []);
const handleLogin = (username: string, password: string) => {
// Простая проверка (в реальном проекте должна быть серверная авторизация)
if (username === 'admin' && password === 'admin123') {
localStorage.setItem('adminAuth', JSON.stringify({ username, role: 'admin' }));
setIsAuthenticated(true);
return true;
}
return false;
};
const handleLogout = () => {
localStorage.removeItem('adminAuth');
setIsAuthenticated(false);
router.push('/admin');
};
const navigation = [
{ name: 'Новости', href: '/admin/news', icon: FileText },
{ name: 'Настройки', href: '/admin/settings', icon: Settings },
];
if (isLoading) {
return (
<div className="min-h-screen bg-gray-100 flex items-center justify-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
</div>
);
}
if (!isAuthenticated) {
return <LoginForm onLogin={handleLogin} />;
}
return (
<div className="min-h-screen bg-gray-100">
{/* Мобильный overlay */}
{isSidebarOpen && (
<div
className="fixed inset-0 bg-black bg-opacity-50 z-40 lg:hidden"
onClick={() => setIsSidebarOpen(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 ${
isSidebarOpen ? 'translate-x-0' : '-translate-x-full'
}`}>
<div className="flex items-center justify-between h-16 px-6 border-b">
<Link href="/admin" className="flex items-center space-x-2">
<Building className="h-8 w-8 text-blue-600" />
<span className="text-xl font-bold text-gray-900">Admin Panel</span>
</Link>
<button
onClick={() => setIsSidebarOpen(false)}
className="lg:hidden p-2 rounded-md hover:bg-gray-100"
>
<X className="h-5 w-5" />
</button>
</div>
<nav className="mt-6">
{navigation.map((item) => {
const isActive = pathname.startsWith(item.href);
return (
<Link
key={item.name}
href={item.href}
className={`flex items-center px-6 py-3 text-sm font-medium transition-colors ${
isActive
? 'bg-blue-50 text-blue-700 border-r-2 border-blue-600'
: 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'
}`}
onClick={() => setIsSidebarOpen(false)}
>
<item.icon className="mr-3 h-5 w-5" />
{item.name}
</Link>
);
})}
</nav>
<div className="absolute bottom-0 w-full p-6 border-t">
<button
onClick={handleLogout}
className="flex items-center w-full px-3 py-2 text-sm font-medium text-red-600 hover:bg-red-50 rounded-md transition-colors"
>
<LogOut className="mr-3 h-5 w-5" />
Выйти
</button>
</div>
</div>
{/* Main content */}
<div className="lg:ml-64">
{/* Top bar */}
<div className="bg-white shadow-sm border-b">
<div className="flex items-center justify-between h-16 px-4 sm:px-6 lg:px-8">
<button
onClick={() => setIsSidebarOpen(true)}
className="lg:hidden p-2 rounded-md hover:bg-gray-100"
>
<Menu className="h-5 w-5" />
</button>
<div className="flex items-center space-x-4">
<span className="text-sm text-gray-500">Добро пожаловать, admin</span>
</div>
</div>
</div>
{/* Page content */}
<main className="p-4 sm:p-6 lg:p-8">
{children}
</main>
</div>
</div>
);
}
// Компонент формы входа
function LoginForm({ onLogin }: { onLogin: (username: string, password: string) => boolean }) {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
setError('');
if (!username || !password) {
setError('Заполните все поля');
return;
}
const success = onLogin(username, password);
if (!success) {
setError('Неверный логин или пароль');
}
};
return (
<div className="min-h-screen bg-gray-100 flex items-center justify-center">
<div className="max-w-md w-full bg-white rounded-lg shadow-md p-8">
<div className="text-center mb-8">
<Building className="h-12 w-12 text-blue-600 mx-auto mb-4" />
<h1 className="text-2xl font-bold text-gray-900">Административная панель</h1>
<p className="text-gray-600 mt-2">Войдите в систему управления</p>
</div>
<form onSubmit={handleSubmit} className="space-y-6">
<div>
<label htmlFor="username" className="block text-sm font-medium text-gray-700 mb-2">
Логин
</label>
<input
id="username"
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="Введите логин"
/>
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-2">
Пароль
</label>
<input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="Введите пароль"
/>
</div>
{error && (
<div className="text-red-600 text-sm">{error}</div>
)}
<button
type="submit"
className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 transition-colors"
>
Войти
</button>
</form>
</div>
</div>
);
}