106 lines
3.9 KiB
TypeScript
106 lines
3.9 KiB
TypeScript
import { createRootRouteWithContext, Link, Outlet } from '@tanstack/react-router'
|
|
import type { QueryClient } from '@tanstack/react-query'
|
|
import { useTranslation } from 'react-i18next'
|
|
import { LanguageSwitch } from '@nstart/ui'
|
|
import { AuthBadge } from '@/auth/AuthBadge'
|
|
import { useCanMutate } from '@/auth/useCanMutate'
|
|
import { UpdateBanner } from '@/components/version/UpdateBanner'
|
|
import { VersionBadge } from '@/components/version/VersionBadge'
|
|
|
|
export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()({
|
|
component: RootLayout,
|
|
})
|
|
|
|
const LANG_OPTIONS = [
|
|
{ id: 'ru-RU', label: 'RU' },
|
|
{ id: 'en-US', label: 'EN' },
|
|
]
|
|
|
|
function RootLayout() {
|
|
const { t, i18n } = useTranslation()
|
|
// Гость (anonymous) видит только ссылку на /dictionaries и /search.
|
|
// Аудит, outbox, webhooks, drafts, reviews — admin-only страницы, требуют
|
|
// JWT (backend SecurityConfig + admin role). Скрываем из top-nav чтобы
|
|
// user не получал 401 toast при навигации.
|
|
const canMutate = useCanMutate()
|
|
return (
|
|
<div className="min-h-full flex flex-col bg-white">
|
|
<UpdateBanner />
|
|
<header className="border-b border-regolith bg-white">
|
|
<div className="max-w-6xl mx-auto px-4 sm:px-6 py-3 sm:py-4 flex items-center flex-wrap gap-3 sm:gap-6">
|
|
<Link to="/" className="font-primary text-base sm:text-lg text-ultramarain">
|
|
{t('app.title')}
|
|
</Link>
|
|
<nav className="flex gap-4 text-sm">
|
|
<Link
|
|
to="/dictionaries"
|
|
className="text-carbon hover:text-ultramarain"
|
|
activeProps={{ className: 'text-ultramarain font-medium' }}
|
|
>
|
|
{t('nav.dictionaries')}
|
|
</Link>
|
|
<Link
|
|
to="/search"
|
|
className="text-carbon hover:text-ultramarain"
|
|
activeProps={{ className: 'text-ultramarain font-medium' }}
|
|
>
|
|
{t('nav.search')}
|
|
</Link>
|
|
{canMutate && (
|
|
<>
|
|
<Link
|
|
to="/audit"
|
|
className="text-carbon hover:text-ultramarain"
|
|
activeProps={{ className: 'text-ultramarain font-medium' }}
|
|
>
|
|
{t('nav.audit')}
|
|
</Link>
|
|
<Link
|
|
to="/outbox"
|
|
className="text-carbon hover:text-ultramarain"
|
|
activeProps={{ className: 'text-ultramarain font-medium' }}
|
|
>
|
|
{t('nav.outbox')}
|
|
</Link>
|
|
<Link
|
|
to="/webhooks"
|
|
className="text-carbon hover:text-ultramarain"
|
|
activeProps={{ className: 'text-ultramarain font-medium' }}
|
|
>
|
|
{t('nav.webhooks')}
|
|
</Link>
|
|
<Link
|
|
to="/reviews"
|
|
className="text-carbon hover:text-ultramarain"
|
|
activeProps={{ className: 'text-ultramarain font-medium' }}
|
|
>
|
|
{t('nav.reviews')}
|
|
</Link>
|
|
<Link
|
|
to="/my-drafts"
|
|
className="text-carbon hover:text-ultramarain"
|
|
activeProps={{ className: 'text-ultramarain font-medium' }}
|
|
>
|
|
{t('nav.myDrafts')}
|
|
</Link>
|
|
</>
|
|
)}
|
|
</nav>
|
|
<div className="ml-auto flex items-center gap-3">
|
|
<VersionBadge />
|
|
<LanguageSwitch
|
|
value={i18n.language}
|
|
options={LANG_OPTIONS}
|
|
onChange={(id) => i18n.changeLanguage(id)}
|
|
/>
|
|
<AuthBadge />
|
|
</div>
|
|
</div>
|
|
</header>
|
|
<main className="flex-1 max-w-6xl mx-auto w-full px-4 sm:px-6 py-4 sm:py-8">
|
|
<Outlet />
|
|
</main>
|
|
</div>
|
|
)
|
|
}
|