feat: initial microservices project structure
Services: - api: FastAPI gateway with JWT auth, async endpoints, WebSocket - worker-gpu: CUDA sentence-transformers, FAISS IVFFlat, Ollama LLM - worker-indexer: Winnowing+MinHash plagiarism detection, PDF/DOCX extraction - worker-notifier: SMTP email notifications - worker-gost: GOST 7.1-2003 and GOST R 7.0.5-2008 formatting Infrastructure: - docker-compose.yml (production) + docker-compose.dev.yml (hot reload) - Nginx reverse proxy + WebSocket support - PostgreSQL 16 with Alembic migrations - Elasticsearch 8 with Russian/English analyzers - MinIO, RabbitMQ, Redis, Ollama Frontend: - React 18 + Vite + TypeScript + TailwindCSS + Zustand + React Query v5 - 9 pages: Home, Search, Cabinet, Task, Check, Bibliography, Pricing, Login, Register Scripts: - Parser stubs: OpenAlex, КиберЛенинка, arXiv (Phase 0 - to be filled) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
149
services/frontend/src/components/Layout.tsx
Normal file
149
services/frontend/src/components/Layout.tsx
Normal file
@@ -0,0 +1,149 @@
|
||||
import React from 'react';
|
||||
import { Link, NavLink, useNavigate } from 'react-router-dom';
|
||||
import { GraduationCap, Search, BookOpen, Upload, LayoutDashboard, LogOut, User, BookMarked } from 'lucide-react';
|
||||
import { clsx } from 'clsx';
|
||||
import { useAuthStore } from '../store/auth';
|
||||
|
||||
interface LayoutProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function Layout({ children }: LayoutProps) {
|
||||
const { isAuthenticated, user, logout } = useAuthStore();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleLogout = () => {
|
||||
logout();
|
||||
navigate('/');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
{/* Навигация */}
|
||||
<nav className="bg-white border-b border-gray-100 sticky top-0 z-50">
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex items-center justify-between h-16">
|
||||
{/* Логотип */}
|
||||
<Link
|
||||
to="/"
|
||||
className="flex items-center gap-2.5 text-gray-900 hover:text-brand-600 transition-colors"
|
||||
>
|
||||
<div className="p-1.5 bg-brand-600 rounded-lg">
|
||||
<GraduationCap className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<span className="font-semibold text-base hidden sm:block">Академический помощник</span>
|
||||
<span className="font-semibold text-base sm:hidden">АкадПомощник</span>
|
||||
</Link>
|
||||
|
||||
{/* Центральная навигация */}
|
||||
<div className="flex items-center gap-1">
|
||||
<NavLink
|
||||
to="/search"
|
||||
className={({ isActive }) =>
|
||||
clsx(
|
||||
'flex items-center gap-1.5 px-3 py-2 rounded-lg text-sm font-medium transition-colors',
|
||||
isActive
|
||||
? 'bg-brand-50 text-brand-700'
|
||||
: 'text-gray-600 hover:bg-gray-100'
|
||||
)
|
||||
}
|
||||
>
|
||||
<Search className="w-4 h-4" />
|
||||
<span className="hidden md:block">Поиск</span>
|
||||
</NavLink>
|
||||
|
||||
<NavLink
|
||||
to="/check"
|
||||
className={({ isActive }) =>
|
||||
clsx(
|
||||
'flex items-center gap-1.5 px-3 py-2 rounded-lg text-sm font-medium transition-colors',
|
||||
isActive
|
||||
? 'bg-brand-50 text-brand-700'
|
||||
: 'text-gray-600 hover:bg-gray-100'
|
||||
)
|
||||
}
|
||||
>
|
||||
<Upload className="w-4 h-4" />
|
||||
<span className="hidden md:block">Плагиат</span>
|
||||
</NavLink>
|
||||
|
||||
<NavLink
|
||||
to="/bibliography"
|
||||
className={({ isActive }) =>
|
||||
clsx(
|
||||
'flex items-center gap-1.5 px-3 py-2 rounded-lg text-sm font-medium transition-colors',
|
||||
isActive
|
||||
? 'bg-brand-50 text-brand-700'
|
||||
: 'text-gray-600 hover:bg-gray-100'
|
||||
)
|
||||
}
|
||||
>
|
||||
<BookOpen className="w-4 h-4" />
|
||||
<span className="hidden md:block">Библиография</span>
|
||||
</NavLink>
|
||||
</div>
|
||||
|
||||
{/* Авторизация */}
|
||||
<div className="flex items-center gap-2">
|
||||
{isAuthenticated ? (
|
||||
<>
|
||||
<NavLink
|
||||
to="/cabinet"
|
||||
className={({ isActive }) =>
|
||||
clsx(
|
||||
'flex items-center gap-1.5 px-3 py-2 rounded-lg text-sm font-medium transition-colors',
|
||||
isActive
|
||||
? 'bg-brand-50 text-brand-700'
|
||||
: 'text-gray-600 hover:bg-gray-100'
|
||||
)
|
||||
}
|
||||
>
|
||||
<LayoutDashboard className="w-4 h-4" />
|
||||
<span className="hidden md:block">Кабинет</span>
|
||||
</NavLink>
|
||||
<div className="flex items-center gap-2 pl-2 border-l border-gray-100">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<div className="w-8 h-8 bg-brand-100 rounded-full flex items-center justify-center">
|
||||
<span className="text-xs font-semibold text-brand-700">
|
||||
{user?.name?.[0]?.toUpperCase() || 'U'}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-sm text-gray-600 hidden lg:block">{user?.name}</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
className="p-1.5 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-lg transition-colors"
|
||||
title="Выйти"
|
||||
>
|
||||
<LogOut className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Link
|
||||
to="/login"
|
||||
className="px-4 py-2 text-sm font-medium text-gray-600 hover:text-gray-900 transition-colors"
|
||||
>
|
||||
Войти
|
||||
</Link>
|
||||
<Link
|
||||
to="/register"
|
||||
className="px-4 py-2 text-sm font-medium bg-brand-600 text-white rounded-lg hover:bg-brand-700 transition-colors"
|
||||
>
|
||||
Регистрация
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{/* Основной контент */}
|
||||
<main className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user