diff --git a/.cursor/rules/web-shadcn.mdc b/.cursor/rules/web-shadcn.mdc index 5453f98..800ab69 100644 --- a/.cursor/rules/web-shadcn.mdc +++ b/.cursor/rules/web-shadcn.mdc @@ -98,7 +98,8 @@ pnpm --filter @evobgp/web run build **WEB-21** | MUST | Data fetching — TanStack Query (`useQuery`, `useMutation`, `queryOptions`); query-key factories в `apps/web/src/queries/`. Mutations invalidate keys, не refetch вручную. *Проверка:* review `queries/*.ts`. -**WEB-22** | MUST | Legacy Svelte — в `web-legacy-svelte/` (archive). Не использовать импорты оттуда в новом коде; только как референс при миграции роутов. +**WEB-22** | NEVER | Legacy Svelte UI удалён. Не восстанавливать `web-legacy-svelte/` и не копировать Svelte-паттерны в React-код. +*Проверка:* отсутствие каталога `web-legacy-svelte/`; `pnpm --filter @evobgp/web run typecheck`. --- diff --git a/AGENTS.md b/AGENTS.md index cc9961e..f738bba 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -69,7 +69,7 @@ pnpm --filter @evobgp/web run build Все три команды должны exit 0. CI job `web` не пропускает без этого. -Стек: React 19, TanStack Router/Query, shadcn/ui (base-nova, registry `@shadcn` + `@reui`), Tailwind v4, lucide-react. Legacy Svelte — в `web-legacy-svelte/` (архив, только референс при миграции). +Стек: React 19, TanStack Router/Query, shadcn/ui (base-nova, registry `@shadcn` + `@reui`), Tailwind v4, lucide-react. Legacy Svelte UI удалён (миграция завершена). **UI design contract:** [`docs/ui-design-contract.md`](docs/ui-design-contract.md) — surface `frame`, kit `apps/web/src/components/reui-kit/`. diff --git a/apps/web/src/components/blocks/app-shell-7/components/app-header.tsx b/apps/web/src/components/blocks/app-shell-7/components/app-header.tsx deleted file mode 100644 index 3d2d858..0000000 --- a/apps/web/src/components/blocks/app-shell-7/components/app-header.tsx +++ /dev/null @@ -1,220 +0,0 @@ -"use client" - -import { useState } from "react" - -import { cn } from "@evobgp/ui/lib/utils" -import { - Breadcrumb, - BreadcrumbItem, - BreadcrumbList, - BreadcrumbSeparator, -} from "@evobgp/ui/components/breadcrumb" -import { Button } from "@evobgp/ui/components/button" -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuGroup, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from "@evobgp/ui/components/dropdown-menu" -import { SidebarTrigger } from "@evobgp/ui/components/sidebar" -import { APPS, ENVIRONMENTS, WORKSPACES } from "./data" -import { Logo } from "./logo" -import { SystemStats } from "./system-stats" -import { ChevronsUpDownIcon, CheckIcon, PlusIcon } from "lucide-react" - -// ── Org Switcher ── - -function OrgSwitcher() { - const [activeOrg, setActiveOrg] = useState(WORKSPACES[0]) - - return ( - - - } - > - - {activeOrg.name} - - - - - Organizations - - {WORKSPACES.map((ws) => ( - setActiveOrg(ws)} - className={cn(activeOrg.id === ws.id && "bg-accent")} - > - -
- {ws.name} - - {ws.tier} - -
- {activeOrg.id === ws.id && ( -
- ))} -
- - - -
-
-
- ) -} - -// ── App Switcher ── - -function AppSwitcher() { - const [activeApp, setActiveApp] = useState( - APPS.find((a) => a.isActive) ?? APPS[0] - ) - - return ( - - - } - > - {activeApp.name} - {activeApp.name.split(" ")[0]} - - - - - Applications - - {APPS.map((app) => ( - setActiveApp(app)} - className={cn(activeApp.id === app.id && "bg-accent")} - > - {app.icon} - {app.name} - {activeApp.id === app.id && ( - - ))} - - - - - - - - ) -} - -// ── Environment Switcher ── - -function EnvironmentSwitcher() { - const [activeEnv, setActiveEnv] = useState( - ENVIRONMENTS.find((e) => e.isActive) ?? ENVIRONMENTS[0] - ) - - return ( - - - } - > - {activeEnv.name} - - - - - Environments - - {ENVIRONMENTS.map((env) => ( - setActiveEnv(env)} - className={cn(activeEnv.id === env.id && "bg-accent")} - > - {env.icon} - {env.name} - {activeEnv.id === env.id && ( - - ))} - - - - - ) -} - -// ── Site Header ── - -export function AppHeader() { - return ( -
-
- {/* Mobile sidebar trigger */} - - - {/* Logo */} -
- -
- - - - - - - - / - - - - - - / - - - - - - - - {/* Right side */} -
- -
-
-
- ) -} \ No newline at end of file diff --git a/apps/web/src/components/blocks/app-shell-7/components/app-shell.tsx b/apps/web/src/components/blocks/app-shell-7/components/app-shell.tsx deleted file mode 100644 index 672e1ac..0000000 --- a/apps/web/src/components/blocks/app-shell-7/components/app-shell.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { type CSSProperties } from "react" - -import { cn } from "@evobgp/ui/lib/utils" -import { SidebarInset, SidebarProvider } from "@evobgp/ui/components/sidebar" - -import { AppHeader } from "./app-header" -import { AppSidebar } from "./app-sidebar" - -export function AppShell() { - return ( - - {/* Header */} - -
- - -
-
-
-
-
-
-
-
- -
- - ) -} \ No newline at end of file diff --git a/apps/web/src/components/blocks/app-shell-7/components/app-sidebar.tsx b/apps/web/src/components/blocks/app-shell-7/components/app-sidebar.tsx deleted file mode 100644 index 13ef614..0000000 --- a/apps/web/src/components/blocks/app-shell-7/components/app-sidebar.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { - Sidebar, - SidebarContent, - SidebarFooter, - SidebarHeader, - useSidebar, -} from "@evobgp/ui/components/sidebar" - -import { NEWS_ARTICLES } from "./data" -import { NavMain } from "./nav-main" -import { NavUser } from "./nav-user" -import { SearchForm } from "./search-form" -import { SidebarNews } from "./sidebar-news" -import { SidebarRailToggle } from "./sidebar-rail-toggle" - -// ── App Sidebar ── - -export function AppSidebar() { - const { isMobile } = useSidebar() - - return ( - - {/* Header */} - -
- -
-
- - {/* Sidebar */} - - - -
- -
-
- - {/* Footer */} - {/* customize: mt-2 keeps a fixed gap above the footer so the news card never butts against it when the viewport shrinks and the content scrolls */} - - - - - {!isMobile && } -
- ) -} \ No newline at end of file diff --git a/apps/web/src/components/blocks/app-shell-7/components/data.tsx b/apps/web/src/components/blocks/app-shell-7/components/data.tsx deleted file mode 100644 index f6b9990..0000000 --- a/apps/web/src/components/blocks/app-shell-7/components/data.tsx +++ /dev/null @@ -1,385 +0,0 @@ -import { type ComponentType, type ReactNode } from "react" -import { GlobeIcon, ShieldIcon, SmartphoneIcon, RocketIcon, FlaskConicalIcon, CodeIcon, LayoutDashboardIcon, UsersIcon, Building2Icon, ShieldCheckIcon, WebhookIcon, KeyRoundIcon, CpuIcon, MonitorIcon, MemoryStickIcon, WifiIcon } from "lucide-react" - -// ── Types ── - -export type NavChild = { - id: string - label: string - isActive?: boolean -} - -export type NavItem = { - id: string - label: string - icon: ReactNode - badge?: string | number - isActive?: boolean - children?: NavChild[] -} - -export type Workspace = { - id: string - name: string - tier: string - Logo: ComponentType<{ className?: string }> -} - -export type App = { - id: string - name: string - icon: ReactNode - isActive?: boolean -} - -export type Environment = { - id: string - name: string - icon: ReactNode - isActive?: boolean -} - -export type NewsArticle = { - href: string - title: string - summary: string - image: string -} - -export type ResourceMetric = { - id: string - label: string - unit: string - icon: ReactNode - seedRange: [number, number] - color: string - spikeThreshold: number -} - -export type Agent = { - id: string - name: string - memoryMb: number - color: string -} - -// ── Logos ── - -// Acme: indigo → violet → fuchsia diagonal sweep -function AcmeLogo({ className }: { className?: string }) { - return ( - - ) -} - -// Starter: rose → orange → amber → lime horizontal sweep -function StarterLogo({ className }: { className?: string }) { - return ( - - ) -} - -// Enterprise: sky → blue → indigo vertical sweep -function EnterpriseLogo({ className }: { className?: string }) { - return ( - - ) -} - -// ── Data ── - -export const USER = { - name: "Nick Bold", - email: "nick@reui.io", - initials: "NB", - avatar: - "https://images.unsplash.com/photo-1543299750-19d1d6297053?w=96&h=96&dpr=2&q=80", -} as const - -export const WORKSPACES: Workspace[] = [ - { id: "acme", name: "Acme Inc", tier: "Pro", Logo: AcmeLogo }, - { id: "starter", name: "Starter Kit", tier: "Free", Logo: StarterLogo }, - { - id: "enterprise", - name: "Enterprise", - tier: "Enterprise", - Logo: EnterpriseLogo, - }, -] - -export const APPS: App[] = [ - { - id: "web-app", - name: "Web App", - isActive: true, - icon: ( -