"use client"; /** * TopNav — top-level навигация на landing (gendsgn.ru/). * * Айтемы фильтруются через `isPathAllowed(allowed, deny, href)` по scope * текущего юзера из `/api/v1/me`. Источник правды — `auth/roles.yaml`: * - admin: видит всё * - pilot: «Админ» (`/admin/**`) скрыт, остальное видно * - 401 (dev без Caddy): пропускаем фильтрацию, показываем всё * - 403 (юзер не в roles) — этот компонент не дойдёт до рендера: RouteGuard * раньше перехватит и покажет NoAccessScreen на весь экран. * * Кнопка «Запросить пилот» — public CTA, не фильтруется. */ import Link from "next/link"; import { HTTPError } from "@/lib/api"; import { isPathAllowed } from "@/lib/isPathAllowed"; import { useMe } from "@/lib/useMe"; interface NavItem { href: string; label: string; } const NAV_ITEMS: NavItem[] = [ { href: "/analytics", label: "Аналитика" }, { href: "/site-finder", label: "Site Finder" }, { href: "/trade-in/", label: "Trade-In" }, { href: "/concept", label: "Концепция" }, { href: "/admin/scrape/all", label: "Админ" }, ]; interface TopNavProps { /** Slot справа от ссылок — обычно «Запросить пилот» CTA. */ rightSlot?: React.ReactNode; } export function TopNav({ rightSlot }: TopNavProps) { const { data, error } = useMe(); // Dev (без Caddy) — 401, показываем все айтемы. В проде Caddy basic_auth // всегда ставит header → 401 невозможен. // Если /me ещё грузится или вернул ошибку без scope — показываем nav без // фильтрации (RouteGuard уже перехватит 403 на весь экран, так что в // реальности этот fallback срабатывает только в dev/loading). const isDev401 = error instanceof HTTPError && error.status === 401; const items = data && !isDev401 ? NAV_ITEMS.filter((item) => isPathAllowed(data.allowed_paths, data.deny_paths, item.href), ) : NAV_ITEMS; return ( ); }