fix(admin-ui): guest nav filter + suppress drafts 401

This commit is contained in:
Александр Зимин
2026-05-10 21:19:07 +00:00
parent 35c4974a2e
commit c57003cd73
23 changed files with 2625 additions and 2712 deletions
+45 -35
View File
@@ -3,6 +3,7 @@ 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'
@@ -17,6 +18,11 @@ const LANG_OPTIONS = [
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 />
@@ -33,27 +39,6 @@ function RootLayout() {
>
{t('nav.dictionaries')}
</Link>
<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="/search"
className="text-carbon hover:text-ultramarain"
@@ -61,20 +46,45 @@ function RootLayout() {
>
{t('nav.search')}
</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>
{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 />
@@ -136,8 +136,10 @@ function DictionaryDetail() {
* "На review" в каждой строке records table. Polled 30s. Bypass'нем если
* dict не approval-required (нет смысла грузить).
*/
// Гость (anonymous) — drafts polling требует JWT (401 каждые 30s в console).
// Disable если не авторизован.
const pendingDraftsQuery = useDictPendingDrafts(
detailQuery.data?.approvalRequired ? name : undefined,
canMutate && detailQuery.data?.approvalRequired ? name : undefined,
)
const pendingByKey = useMemo(() => {
const set = new Set<string>()