Add complete CKE Project implementation with news management system

This commit is contained in:
albivkt
2025-07-13 01:34:11 +03:00
parent c9317555ca
commit a84810c6b9
32 changed files with 8901 additions and 811 deletions

222
app/admin/layout.tsx Normal file
View File

@ -0,0 +1,222 @@
'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>
);
}