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 && (
-
- )}
-
- ))}
-
-
-
-
- Create Organization
-
-
-
-
- )
-}
-
-// ── 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 && (
-
- )}
-
- ))}
-
-
-
-
- Create Application
-
-
-
-
- )
-}
-
-// ── 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 (
-
- )
-}
\ 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: (
-
- ),
- },
- {
- id: "admin",
- name: "Admin Panel",
- icon: (
-
- ),
- },
- {
- id: "mobile",
- name: "Mobile App",
- icon: (
-
- ),
- },
-]
-
-export const ENVIRONMENTS: Environment[] = [
- {
- id: "production",
- name: "Production",
- isActive: true,
- icon: (
-
- ),
- },
- {
- id: "staging",
- name: "Staging",
- icon: (
-
- ),
- },
- {
- id: "development",
- name: "Development",
- icon: (
-
- ),
- },
-]
-
-export const NAV_MAIN: NavItem[] = [
- {
- id: "overview",
- label: "Overview",
- isActive: true,
- icon: (
-
- ),
- },
- {
- id: "users",
- label: "Users",
- icon: (
-
- ),
- children: [
- { id: "all-users", label: "All Users" },
- { id: "invitations", label: "Invitations" },
- { id: "roles", label: "Roles", isActive: true },
- ],
- },
- {
- id: "organizations",
- label: "Organizations",
- icon: (
-
- ),
- children: [
- { id: "org-list", label: "All Organizations" },
- { id: "org-settings", label: "Settings" },
- ],
- },
- {
- id: "security",
- label: "Security",
- icon: (
-
- ),
- children: [
- { id: "attack-protection", label: "Attack Protection" },
- { id: "fraud", label: "Fraud Detection" },
- { id: "audit-log", label: "Audit Log" },
- ],
- },
- {
- id: "webhooks",
- label: "Webhooks",
- icon: (
-
- ),
- },
- {
- id: "api-keys",
- label: "API Keys",
- icon: (
-
- ),
- },
-]
-
-export const RESOURCE_METRICS: ResourceMetric[] = [
- {
- id: "cpu",
- label: "CPU",
- unit: "%",
- icon: (
-
- ),
- seedRange: [20, 65],
- color: "var(--color-blue-500)",
- spikeThreshold: 60,
- },
- {
- id: "gpu",
- label: "GPU",
- unit: "%",
- icon: (
-
- ),
- seedRange: [10, 50],
- color: "var(--color-emerald-500)",
- spikeThreshold: 45,
- },
- {
- id: "memory",
- label: "Memory",
- unit: "GB",
- icon: (
-
- ),
- seedRange: [4, 12],
- color: "var(--color-amber-500)",
- spikeThreshold: 12,
- },
- {
- id: "network",
- label: "Network",
- unit: "Mbps",
- icon: (
-
- ),
- seedRange: [5, 80],
- color: "var(--color-violet-500)",
- spikeThreshold: 70,
- },
-]
-
-export const AGENTS: Agent[] = [
- {
- id: "ingest",
- name: "Ingest Worker",
- memoryMb: 256,
- color: "var(--color-indigo-500)",
- },
- {
- id: "transform",
- name: "Transform Pipeline",
- memoryMb: 512,
- color: "var(--color-purple-500)",
- },
- {
- id: "query",
- name: "Query Engine",
- memoryMb: 384,
- color: "var(--color-sky-500)",
- },
- {
- id: "export",
- name: "Export Service",
- memoryMb: 128,
- color: "var(--color-teal-500)",
- },
-]
-
-export const NEWS_ARTICLES: NewsArticle[] = [
- {
- href: "https://pro.reui.io/changelog?v=2.0.3",
- title: "Multi-theme support is here",
- summary:
- "Switch between Vega, Nova, Maia, Lyra, and Mira themes across all components.",
- image:
- "https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?w=400&h=225&fit=crop&q=80",
- },
- {
- href: "https://pro.reui.io/changelog?v=2.0.2",
- title: "Icon library v2 released",
- summary:
- "Five icon libraries unified under a single API with automatic mapping.",
- image:
- "https://images.unsplash.com/photo-1579546929518-9e396f3cc809?w=400&h=225&fit=crop&q=80",
- },
- {
- href: "https://pro.reui.io/changelog?v=2.0.1",
- title: "Sidebar layout patterns guide",
- summary:
- "Learn best practices for building responsive sidebar layouts with collapsible navigation.",
- image:
- "https://images.unsplash.com/photo-1541701494587-cb58502866ab?w=400&h=225&fit=crop&q=80",
- },
-]
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/app-shell-7/components/logo.tsx b/apps/web/src/components/blocks/app-shell-7/components/logo.tsx
deleted file mode 100644
index cf7b844..0000000
--- a/apps/web/src/components/blocks/app-shell-7/components/logo.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import { Item, ItemMedia } from "@evobgp/ui/components/item"
-
-export function Logo() {
- return (
- -
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/app-shell-7/components/nav-main.tsx b/apps/web/src/components/blocks/app-shell-7/components/nav-main.tsx
deleted file mode 100644
index ca895a1..0000000
--- a/apps/web/src/components/blocks/app-shell-7/components/nav-main.tsx
+++ /dev/null
@@ -1,122 +0,0 @@
-"use client"
-
-import { useState } from "react"
-
-import { cn } from "@evobgp/ui/lib/utils"
-import {
- SidebarGroup,
- SidebarGroupContent,
- SidebarGroupLabel,
- SidebarMenu,
- SidebarMenuBadge,
- SidebarMenuButton,
- SidebarMenuItem,
- SidebarMenuSub,
- SidebarMenuSubButton,
- SidebarMenuSubItem,
-} from "@evobgp/ui/components/sidebar"
-import { NAV_MAIN, type NavChild, type NavItem } from "./data"
-import { ChevronRightIcon } from "lucide-react"
-
-// ── Nav Sub Items ──
-
-function NavSubItem({ child }: { child: NavChild }) {
- return (
-
- {/* Sidebar */}
- } isActive={child.isActive}>
- {child.label}
-
-
- )
-}
-
-function NavSubMenu({ id, items }: { id: string; items: NavChild[] }) {
- return (
-
- {items.map((child) => (
-
- ))}
-
- )
-}
-
-// ── Collapsible Nav Item ──
-
-export function CollapsibleNavItem({
- item,
-}: {
- item: NavItem & { children: NavChild[] }
-}) {
- const [open, setOpen] = useState(() => item.children.some((c) => c.isActive))
-
- return (
-
- {/* Sidebar */}
- setOpen((prev) => !prev)}
- aria-expanded={open}
- aria-controls={`subnav-${item.id}`}
- >
- {item.icon}
- {item.label}
-
-
-
- {open && }
-
- )
-}
-
-// ── Leaf Nav Item ──
-
-export function LeafNavItem({ item }: { item: NavItem }) {
- return (
-
- {/* Sidebar */}
- }
- >
- {item.icon}
- {item.label}
- {item.badge !== undefined && (
- {item.badge}
- )}
-
-
- )
-}
-
-// ── Nav Main ──
-
-export function NavMain() {
- return (
-
- {/* Sidebar */}
-
- Platform
-
-
-
- {NAV_MAIN.map((item) =>
- item.children ? (
-
- ) : (
-
- )
- )}
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/app-shell-7/components/nav-user.tsx b/apps/web/src/components/blocks/app-shell-7/components/nav-user.tsx
deleted file mode 100644
index 65d1ba6..0000000
--- a/apps/web/src/components/blocks/app-shell-7/components/nav-user.tsx
+++ /dev/null
@@ -1,202 +0,0 @@
-import { useEffect, useState } from "react"
-import { useTheme } from "next-themes"
-
-import { cn } from "@evobgp/ui/lib/utils"
-import {
- Avatar,
- AvatarFallback,
- AvatarImage,
-} from "@evobgp/ui/components/avatar"
-import { Button } from "@evobgp/ui/components/button"
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuGroup,
- DropdownMenuItem,
- DropdownMenuLabel,
- DropdownMenuSeparator,
- DropdownMenuShortcut,
- DropdownMenuTrigger,
-} from "@evobgp/ui/components/dropdown-menu"
-import {
- SidebarMenu,
- SidebarMenuButton,
- SidebarMenuItem,
- useSidebar,
-} from "@evobgp/ui/components/sidebar"
-import { USER } from "./data"
-import { SunIcon, MoonIcon, MonitorIcon, ChevronsUpDownIcon, UserIcon, SettingsIcon, InboxIcon, PaletteIcon, LogOutIcon } from "lucide-react"
-
-// ── Theme Toggle ──
-
-const THEMES = [
- {
- value: "light",
- label: "Light",
- icon: (
-
- ),
- },
- {
- value: "dark",
- label: "Dark",
- icon: (
-
- ),
- },
- {
- value: "system",
- label: "System",
- icon: (
-
- ),
- },
-]
-
-function ThemeSegmentedToggle() {
- const { theme, setTheme } = useTheme()
- const [mounted, setMounted] = useState(false)
-
- useEffect(() => {
- setMounted(true)
- }, [])
-
- const currentTheme = mounted ? (theme ?? "system") : "system"
-
- return (
-
- {THEMES.map(({ value, label, icon }) => {
- const isActive = currentTheme === value
- return (
-
- )
- })}
-
- )
-}
-
-// ── Nav User ──
-
-export function NavUser() {
- const { isMobile } = useSidebar()
-
- return (
-
- {/* Sidebar */}
-
-
-
- }
- >
-
-
-
- {USER.initials}
-
-
-
- {USER.name}
-
- {USER.email}
-
-
-
-
-
-
-
-
-
-
-
- {USER.initials}
-
-
-
-
- {USER.name}
-
-
- {USER.email}
-
-
-
-
-
-
-
-
-
- Profile
- ⇧⌘P
-
-
-
- Preferences
-
-
-
- Manage Accounts
-
-
-
-
-
-
- e.preventDefault()}
- >
-
- Theme
-
-
-
-
-
-
-
-
-
-
- Sign Out
- ⇧⌘Q
-
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/app-shell-7/components/search-form.tsx b/apps/web/src/components/blocks/app-shell-7/components/search-form.tsx
deleted file mode 100644
index 4b49b9f..0000000
--- a/apps/web/src/components/blocks/app-shell-7/components/search-form.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-"use client"
-
-import { useEffect, useId, useState, type ComponentProps } from "react"
-
-import { Button } from "@evobgp/ui/components/button"
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogHeader,
- DialogTitle,
-} from "@evobgp/ui/components/dialog"
-import { Input } from "@evobgp/ui/components/input"
-import { Kbd } from "@evobgp/ui/components/kbd"
-import {
- SidebarGroup,
- SidebarGroupContent,
-} from "@evobgp/ui/components/sidebar"
-import { SearchIcon } from "lucide-react"
-
-export function SearchForm({ ...props }: ComponentProps<"form">) {
- const [open, setOpen] = useState(false)
- const searchInputId = useId()
-
- useEffect(() => {
- function onKeyDown(event: KeyboardEvent) {
- if (event.key.toLowerCase() === "k" && (event.metaKey || event.ctrlKey)) {
- event.preventDefault()
- setOpen(true)
- }
- }
-
- window.addEventListener("keydown", onKeyDown)
- return () => window.removeEventListener("keydown", onKeyDown)
- }, [])
-
- return (
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/app-shell-7/components/sidebar-news.tsx b/apps/web/src/components/blocks/app-shell-7/components/sidebar-news.tsx
deleted file mode 100644
index 541c189..0000000
--- a/apps/web/src/components/blocks/app-shell-7/components/sidebar-news.tsx
+++ /dev/null
@@ -1,281 +0,0 @@
-import {
- useEffect,
- useRef,
- useState,
- type CSSProperties,
- type PointerEvent as ReactPointerEvent,
-} from "react"
-
-import { cn } from "@evobgp/ui/lib/utils"
-
-import { type NewsArticle } from "./data"
-
-// ── Constants ──
-
-const OFFSET_FACTOR = 4
-const SCALE_FACTOR = 0.03
-const OPACITY_FACTOR = 0.1
-const COMPLETED_DISPLAY_MS = 2700
-
-// ── News Card ──
-
-function NewsCard({
- title,
- summary,
- image,
- onDismiss,
- hideContent,
- href,
- active,
-}: {
- title: string
- summary: string
- image?: string
- onDismiss?: () => void
- hideContent?: boolean
- href?: string
- active?: boolean
-}) {
- const ref = useRef(null)
- const drag = useRef<{
- start: number
- delta: number
- startTime: number
- maxDelta: number
- }>({ start: 0, delta: 0, startTime: 0, maxDelta: 0 })
- const animation = useRef(undefined)
- const [dragging, setDragging] = useState(false)
-
- const onDragMove = (e: globalThis.PointerEvent) => {
- if (!ref.current) return
- const dx = e.clientX - drag.current.start
- drag.current.delta = dx
- drag.current.maxDelta = Math.max(drag.current.maxDelta, Math.abs(dx))
- ref.current.style.setProperty("--dx", dx.toString())
- }
-
- const dismiss = () => {
- if (!ref.current) return
- const cardWidth = ref.current.getBoundingClientRect().width
- const translateX = Math.sign(drag.current.delta) * cardWidth
-
- animation.current = ref.current.animate(
- { opacity: 0, transform: `translateX(${translateX}px)` },
- { duration: 150, easing: "ease-in-out", fill: "forwards" }
- )
- animation.current.onfinish = () => onDismiss?.()
- }
-
- const stopDragging = (cancelled: boolean) => {
- unbindListeners()
- if (!ref.current) return
- setDragging(false)
-
- const dx = drag.current.delta
- if (Math.abs(dx) > ref.current.clientWidth / (cancelled ? 2 : 3)) {
- dismiss()
- return
- }
-
- animation.current = ref.current.animate(
- { transform: "translateX(0)" },
- { duration: 150, easing: "ease-in-out" }
- )
- animation.current.onfinish = () =>
- ref.current?.style.setProperty("--dx", "0")
-
- drag.current = { start: 0, delta: 0, startTime: 0, maxDelta: 0 }
- }
-
- const onDragEnd = () => stopDragging(false)
- const onDragCancel = () => stopDragging(true)
-
- const onPointerDown = (e: ReactPointerEvent) => {
- if (!active || !ref.current || animation.current?.playState === "running")
- return
-
- bindListeners()
- setDragging(true)
- drag.current.start = e.clientX
- drag.current.startTime = Date.now()
- drag.current.delta = 0
- ref.current.style.setProperty("--w", ref.current.clientWidth.toString())
- }
-
- const onClick = () => {
- if (!ref.current || !href) return
- if (
- drag.current.maxDelta < ref.current.clientWidth / 10 &&
- (!drag.current.startTime || Date.now() - drag.current.startTime < 250)
- ) {
- window.open(href, "_blank", "noopener,noreferrer")
- }
- }
-
- const bindListeners = () => {
- document.addEventListener("pointermove", onDragMove)
- document.addEventListener("pointerup", onDragEnd)
- document.addEventListener("pointercancel", onDragCancel)
- }
-
- const unbindListeners = () => {
- document.removeEventListener("pointermove", onDragMove)
- document.removeEventListener("pointerup", onDragEnd)
- document.removeEventListener("pointercancel", onDragCancel)
- }
-
- return (
-
-
-
-
- {title}
-
-
- {summary}
-
-
-
- {image && (
-

- )}
-
-
-
-
- )
-}
-
-// ── Sidebar News ──
-
-export function SidebarNews({ articles }: { articles: NewsArticle[] }) {
- const [dismissedIds, setDismissedIds] = useState([])
- const cards = articles.filter(({ href }) => !dismissedIds.includes(href))
- const cardCount = cards.length
- const [showCompleted, setShowCompleted] = useState(cardCount > 0)
-
- useEffect(() => {
- let timeout: ReturnType | undefined
- if (cardCount === 0) {
- timeout = setTimeout(() => setShowCompleted(false), COMPLETED_DISPLAY_MS)
- }
- return () => clearTimeout(timeout)
- }, [cardCount])
-
- if (!cards.length && !showCompleted) return null
-
- return (
-
-
- {cards.toReversed().map(({ href, title, summary, image }, idx) => (
-
3
- ? [
- "opacity-0 sm:group-hover:translate-y-(--y) sm:group-hover:opacity-(--opacity)",
- "sm:group-has-[*[data-dragging=true]]:translate-y-(--y) sm:group-has-[*[data-dragging=true]]:opacity-(--opacity)",
- ]
- : "translate-y-(--y) opacity-(--opacity)"
- )}
- style={
- {
- "--y": `-${(cardCount - (idx + 1)) * OFFSET_FACTOR}%`,
- "--scale": 1 - (cardCount - (idx + 1)) * SCALE_FACTOR,
- "--opacity":
- cardCount - (idx + 1) >= 6
- ? 0
- : 1 - (cardCount - (idx + 1)) * OPACITY_FACTOR,
- } as CSSProperties
- }
- aria-hidden={idx !== cardCount - 1}
- {...(idx !== cardCount - 1 ? { inert: true } : {})}
- >
- 2}
- active={idx === cardCount - 1}
- onDismiss={() =>
- setDismissedIds([href, ...dismissedIds.slice(0, 50)])
- }
- />
-
- ))}
-
- {/* Invisible spacer to hold layout height */}
-
-
-
-
- {/* All-caught-up state */}
- {showCompleted && !cardCount && (
-
-
- You're all caught up!
-
-
- )}
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/app-shell-7/components/sidebar-rail-toggle.tsx b/apps/web/src/components/blocks/app-shell-7/components/sidebar-rail-toggle.tsx
deleted file mode 100644
index 416f8dd..0000000
--- a/apps/web/src/components/blocks/app-shell-7/components/sidebar-rail-toggle.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-"use client"
-
-import { cn } from "@evobgp/ui/lib/utils"
-import { useSidebar } from "@evobgp/ui/components/sidebar"
-
-export function SidebarRailToggle() {
- const { state, toggleSidebar } = useSidebar()
- const isExpanded = state === "expanded"
-
- return (
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/app-shell-7/components/system-stats.tsx b/apps/web/src/components/blocks/app-shell-7/components/system-stats.tsx
deleted file mode 100644
index b9ccb3b..0000000
--- a/apps/web/src/components/blocks/app-shell-7/components/system-stats.tsx
+++ /dev/null
@@ -1,441 +0,0 @@
-import {
- useCallback,
- useEffect,
- useId,
- useRef,
- useState,
- type CSSProperties,
-} from "react"
-
-import { cn } from "@evobgp/ui/lib/utils"
-import { Badge } from "@evobgp/ui/components/badge"
-import { Item, ItemMedia } from "@evobgp/ui/components/item"
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from "@evobgp/ui/components/popover"
-import { Progress } from "@evobgp/ui/components/progress"
-import {
- AGENTS,
- RESOURCE_METRICS,
- type Agent,
- type ResourceMetric,
-} from "./data"
-import { ActivityIcon, ChevronRightIcon } from "lucide-react"
-
-const HISTORY_LENGTH = 20
-const UPDATE_INTERVAL_MS = 1000
-const SPIKE_COLOR = "var(--color-red-500)"
-const STATS_RANDOM_SEED = 1337
-const DEMO_REFERENCE_DATE = new Date("2026-06-12T09:24:00")
-
-const TIME_FORMATTER = new Intl.DateTimeFormat("en-US", {
- hour: "2-digit",
- minute: "2-digit",
- second: "2-digit",
-})
-
-// Seeded PRNG (mulberry32) keeps the demo metrics deterministic across
-// renders and reloads while preserving the organic feel of live jitter.
-function createSeededRandom(seed: number) {
- let state = seed
-
- return function nextRandom() {
- state = (state + 0x6d2b79f5) | 0
- let t = Math.imul(state ^ (state >>> 15), state | 1)
- t = (t + Math.imul(t ^ (t >>> 7), t | 61)) ^ t
- return ((t ^ (t >>> 14)) >>> 0) / 4294967296
- }
-}
-
-const nextRandom = createSeededRandom(STATS_RANDOM_SEED)
-
-function randomInRange(min: number, max: number) {
- return Math.round((min + nextRandom() * (max - min)) * 10) / 10
-}
-
-function seedHistory(min: number, max: number): number[] {
- return Array.from({ length: HISTORY_LENGTH }, () => randomInRange(min, max))
-}
-
-function smoothPath(pts: { x: number; y: number }[]): string {
- if (pts.length === 0) return ""
- if (pts.length === 1) return `M ${pts[0].x} ${pts[0].y}`
- let d = `M ${pts[0].x} ${pts[0].y}`
- for (let i = 1; i < pts.length; i++) {
- const prev = pts[i - 1]
- const curr = pts[i]
- const cx = (prev.x + curr.x) / 2
- d += ` C ${cx} ${prev.y}, ${cx} ${curr.y}, ${curr.x} ${curr.y}`
- }
- return d
-}
-
-// ── Sparkline Area (full-width responsive) ──
-
-function SparklineArea({
- data,
- max,
- color,
- spikeThreshold,
- height = 36,
-}: {
- data: number[]
- max: number
- color: string
- spikeThreshold: number
- height?: number
-}) {
- const gradientId = useId()
- const W = 100
- const H = height
- const currentValue = data[data.length - 1] ?? 0
- const hasSpike = currentValue > spikeThreshold
- const activeColor = hasSpike ? SPIKE_COLOR : color
-
- const pts = data.map((value, i) => ({
- x: data.length < 2 ? 0 : (i / (data.length - 1)) * W,
- y: H - (Math.min(value, max) / max) * (H - 2),
- }))
-
- const linePath = smoothPath(pts)
- const areaPath = pts.length > 1 ? `${linePath} L ${W} ${H} L 0 ${H} Z` : ""
-
- return (
-
- )
-}
-
-// ── Mini Sparkline (for agent rows) ──
-
-function MiniSparkline({ data, color }: { data: number[]; color: string }) {
- const gradientId = useId()
- const W = 40
- const H = 14
- const dataMax = Math.max(...data, 1)
-
- const pts = data.map((value, i) => ({
- x: data.length < 2 ? 0 : (i / (data.length - 1)) * W,
- y: H - (value / dataMax) * (H - 1),
- }))
-
- const linePath = smoothPath(pts)
- const areaPath = pts.length > 1 ? `${linePath} L ${W} ${H} L 0 ${H} Z` : ""
-
- return (
-
- )
-}
-
-// ── Resource Card (grid cell) ──
-
-function ResourceCard({
- metric,
- history,
-}: {
- metric: ResourceMetric
- history: number[]
-}) {
- const current = history[history.length - 1] ?? 0
- const max = metric.id === "memory" ? 16 : 100
- const isHigh = current > metric.spikeThreshold
-
- return (
-
-
-
- -
-
- {metric.icon}
-
-
-
- {metric.label}
-
-
-
- {current}
-
- {metric.unit}
-
-
-
-
-
- )
-}
-
-// ── Agent Memory Row ──
-
-type AgentWithHistory = Agent & { history: number[] }
-
-function AgentMemoryRow({ agent }: { agent: AgentWithHistory }) {
- const barPercent = Math.min(100, (agent.memoryMb / 512) * 100)
-
- return (
-
-
-
- {agent.name}
-
-
-
- {agent.memoryMb}MB
-
-
- )
-}
-
-// ── Status Badge ──
-
-function StatusBadge({ spiking }: { spiking: boolean }) {
- return (
-
- {spiking ? "Alert" : "Normal"}
-
- )
-}
-
-// ── System Stats ──
-
-export function SystemStats() {
- const [histories, setHistories] = useState>(() => {
- const initial: Record = {}
- for (const m of RESOURCE_METRICS) {
- initial[m.id] = seedHistory(m.seedRange[0], m.seedRange[1])
- }
- return initial
- })
-
- const [agentData, setAgentData] = useState(() =>
- AGENTS.map((a) => ({
- ...a,
- history: Array.from({ length: 10 }, () =>
- Math.max(32, Math.round(a.memoryMb + (nextRandom() - 0.5) * 80))
- ),
- }))
- )
-
- const [showAgents, setShowAgents] = useState(true)
- const [now, setNow] = useState(DEMO_REFERENCE_DATE)
- const intervalRef = useRef>(null)
-
- // Derived - no extra state needed
- const spiking = RESOURCE_METRICS.some((m) => {
- const history = histories[m.id] ?? []
- const current = history[history.length - 1] ?? 0
- return current > m.spikeThreshold
- })
-
- const tick = useCallback(() => {
- setHistories((prev) => {
- const next: Record = {}
- for (const m of RESOURCE_METRICS) {
- const old = prev[m.id] ?? []
- const jitter = randomInRange(
- m.seedRange[0] * 0.8,
- m.seedRange[1] * 1.15
- )
- const clamped =
- m.id === "memory"
- ? Math.min(16, Math.max(0, jitter))
- : Math.min(100, Math.max(0, jitter))
- next[m.id] = [...old.slice(-(HISTORY_LENGTH - 1)), clamped]
- }
- return next
- })
-
- setAgentData((prev) =>
- prev.map((a) => {
- const newMb = Math.max(
- 32,
- Math.round(a.memoryMb + (nextRandom() - 0.5) * 20)
- )
- return {
- ...a,
- memoryMb: newMb,
- history: [...a.history.slice(-9), newMb],
- }
- })
- )
-
- setNow((prev) => new Date(prev.getTime() + UPDATE_INTERVAL_MS))
- }, [])
-
- useEffect(() => {
- intervalRef.current = setInterval(tick, UPDATE_INTERVAL_MS)
- return () => {
- if (intervalRef.current) clearInterval(intervalRef.current)
- }
- }, [tick])
-
- return (
-
-
- }
- >
- {/* Icon with beep ring on spike */}
-
-
- {spiking && (
-
- )}
-
- System
-
-
-
-
- {/* Header */}
-
-
- System Monitor
-
-
- {TIME_FORMATTER.format(now)}
-
-
-
- {/* 2×2 Resource Grid */}
-
- {RESOURCE_METRICS.map((metric, i) => (
-
= 2 && "border-border border-t"
- )}
- >
-
-
- ))}
-
-
- {/* Per-Agent Memory */}
-
-
-
- {showAgents && (
-
- {agentData.map((agent) => (
-
- ))}
-
- )}
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/app-shell-7/page.tsx b/apps/web/src/components/blocks/app-shell-7/page.tsx
deleted file mode 100644
index 4d44f0a..0000000
--- a/apps/web/src/components/blocks/app-shell-7/page.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import { AppShell } from "./components/app-shell"
-
-export function Page() {
- return
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/card-17/components/card-dot-field.tsx b/apps/web/src/components/blocks/card-17/components/card-dot-field.tsx
deleted file mode 100644
index 6fda68a..0000000
--- a/apps/web/src/components/blocks/card-17/components/card-dot-field.tsx
+++ /dev/null
@@ -1,99 +0,0 @@
-"use client"
-
-import { useEffect, useRef } from "react"
-
-import { cn } from "@evobgp/ui/lib/utils"
-
-// Deterministic per-dot value so the field reads as noise, not a flat grid.
-function grain(i: number, j: number) {
- const n = Math.sin(i * 127.1 + j * 311.7) * 43758.5453
- return n - Math.floor(n)
-}
-
-/**
- * Static dot field adapted from auth-3's WaveDots backdrop with the twinkle
- * animation removed: a dense grid of dots at a fixed per-dot brightness,
- * painted once and again on resize or theme change. The dot color resolves
- * from the canvas `color` token so it stays neutral in light and dark.
- * customize: GAP (density), DOT (size), PEAK (max brightness).
- */
-export function CardDotField({ className }: { className?: string }) {
- const canvasRef = useRef(null)
-
- useEffect(() => {
- const canvas = canvasRef.current
- if (!canvas) return
- const ctx = canvas.getContext("2d")
- if (!ctx) return
-
- const GAP = 3 // dot spacing in px (tight grid, still distinct dots)
- const DOT = 1.5 // dot side in px
- const BASE = 0.05 // dim end of a dot
- const PEAK = 0.32 // bright end of a dot (kept subtle for a card surface)
-
- const draw = () => {
- const rect = canvas.getBoundingClientRect()
- if (!rect.width || !rect.height) return
-
- // Resetting width clears the canvas and restores the identity transform,
- // so the color probe below reads a raw device pixel.
- const dpr = Math.min(window.devicePixelRatio || 1, 2)
- canvas.width = Math.round(rect.width * dpr)
- canvas.height = Math.round(rect.height * dpr)
-
- // Resolve the token to concrete sRGB by painting + reading it back, so
- // oklch never casts a color and dark mode recolors on theme change.
- ctx.fillStyle = getComputedStyle(canvas).color || "rgb(115,115,115)"
- ctx.fillRect(0, 0, 1, 1)
- const px = ctx.getImageData(0, 0, 1, 1).data
- const color = `rgb(${px[0]}, ${px[1]}, ${px[2]})`
-
- ctx.setTransform(dpr, 0, 0, dpr, 0, 0)
- ctx.clearRect(0, 0, rect.width, rect.height)
- ctx.fillStyle = color
-
- const cols = Math.ceil(rect.width / GAP) + 1
- const rows = Math.ceil(rect.height / GAP) + 1
- for (let i = 0; i < cols; i++) {
- const x = i * GAP
- for (let j = 0; j < rows; j++) {
- const q = grain(i, j)
- const amp = 0.7 + 0.6 * grain(j * 2 + 1, i * 2 + 1)
- let a = (BASE + (PEAK - BASE) * q * q) * amp
- if (a > 1) a = 1
- ctx.globalAlpha = a
- ctx.fillRect(x, j * GAP, DOT, DOT)
- }
- }
- ctx.globalAlpha = 1
- }
-
- draw()
-
- const resizeObserver = new ResizeObserver(() => draw())
- resizeObserver.observe(canvas)
-
- // Repaint when the theme class toggles so the resolved color stays correct.
- const themeObserver = new MutationObserver(() => draw())
- themeObserver.observe(document.documentElement, {
- attributes: true,
- attributeFilter: ["class", "style"],
- })
-
- return () => {
- resizeObserver.disconnect()
- themeObserver.disconnect()
- }
- }, [])
-
- return (
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/card-17/components/card-grid.tsx b/apps/web/src/components/blocks/card-17/components/card-grid.tsx
deleted file mode 100644
index 6190e76..0000000
--- a/apps/web/src/components/blocks/card-17/components/card-grid.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { Frame } from "@/components/reui/frame"
-
-import { CardItem } from "./card-item"
-import { CARDS } from "./data"
-
-export function CardGrid() {
- return (
-
- {/* Grid */}
-
- {CARDS.map((card) => (
-
- ))}
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/card-17/components/card-item.tsx b/apps/web/src/components/blocks/card-17/components/card-item.tsx
deleted file mode 100644
index 01fe6f1..0000000
--- a/apps/web/src/components/blocks/card-17/components/card-item.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import { FramePanel } from "@/components/reui/frame"
-
-import { cn } from "@evobgp/ui/lib/utils"
-import { Item, ItemMedia } from "@evobgp/ui/components/item"
-import { CardDotField } from "./card-dot-field"
-import { ICard } from "./data"
-import { ChevronRightIcon } from "lucide-react"
-
-export function CardItem({ card }: { card: ICard }) {
- return (
-
- {/* Card */}
-
-
-
-
-
- {card.icon}
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/card-17/components/data.tsx b/apps/web/src/components/blocks/card-17/components/data.tsx
deleted file mode 100644
index 476ceaf..0000000
--- a/apps/web/src/components/blocks/card-17/components/data.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import { type ReactNode } from "react"
-import { ShoppingBagIcon, TrendingUp, BarChart3Icon, Settings2Icon } from "lucide-react"
-
-export interface ICard {
- title: string
- description: string
- link: string
- icon: ReactNode
- iconBg: string
-}
-
-export const CARDS: ICard[] = [
- {
- title: "Recent Orders Overview",
- description:
- "Track and review all recent purchases, updates, and status changes in one place.",
- link: "View Orders",
- icon: (
-
- ),
- iconBg: "bg-green-600",
- },
- {
- title: "Active Opportunities Pipeline",
- description:
- "Monitor ongoing deals, check potential revenue, and update opportunity stages.",
- link: "Open Pipeline",
- icon: (
-
- ),
- iconBg: "bg-indigo-600",
- },
- {
- title: "Performance & Sales Reports",
- description:
- "Analyze weekly and monthly reports to gain deeper insights into performance trends.",
- link: "View Reports",
- icon: (
-
- ),
- iconBg: "bg-sky-600",
- },
- {
- title: "Integration Settings & Sync",
- description:
- "Manage connections with third-party tools and ensure data stays in sync.",
- link: "Manage Integrations",
- icon: (
-
- ),
- iconBg: "bg-orange-600",
- },
-]
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/card-17/page.tsx b/apps/web/src/components/blocks/card-17/page.tsx
deleted file mode 100644
index 6ffdc43..0000000
--- a/apps/web/src/components/blocks/card-17/page.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { CardGrid } from "./components/card-grid"
-
-export function Page() {
- return (
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/card-18/components/card-grid.tsx b/apps/web/src/components/blocks/card-18/components/card-grid.tsx
deleted file mode 100644
index 27705fe..0000000
--- a/apps/web/src/components/blocks/card-18/components/card-grid.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { CardItem } from "./card-item"
-import { CARDS } from "./data"
-
-export function CardGrid() {
- return (
-
- {/* Grid */}
-
- {CARDS.map((card) => (
-
- ))}
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/card-18/components/card-item.tsx b/apps/web/src/components/blocks/card-18/components/card-item.tsx
deleted file mode 100644
index bc83bf4..0000000
--- a/apps/web/src/components/blocks/card-18/components/card-item.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import {
- Frame,
- FrameHeader,
- FramePanel,
-} from "@/components/reui/frame"
-import { ICard } from "./data"
-import { LinkIcon } from "lucide-react"
-
-export function CardItem({ card }: { card: ICard }) {
- return (
-
- {/* Header */}
-
-
- {card.icon}
-
- {card.label}
-
-
-
- {/* Content */}
-
- {card.description}
-
-
- {card.link}
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/card-18/components/data.tsx b/apps/web/src/components/blocks/card-18/components/data.tsx
deleted file mode 100644
index 499f753..0000000
--- a/apps/web/src/components/blocks/card-18/components/data.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import { type ReactNode } from "react"
-import { PackageIcon, TrendingUp, MapPinIcon } from "lucide-react"
-
-export interface ICard {
- label: string
- icon: ReactNode
- description: string
- link: string
-}
-
-export const CARDS: ICard[] = [
- {
- label: "Binance",
- icon: (
-
- ),
- description:
- "Track trading volumes, liquidity shifts, and price movements for informed decisions",
- link: "https://www.binance.com/en/markets/over..",
- },
- {
- label: "Revenue",
- icon: (
-
- ),
- description:
- "Get instant insights into earnings and cash flow performance.",
- link: "https://nexo.io/earn/crypto-detailed-portfol..",
- },
- {
- label: "Shipments",
- icon: (
-
- ),
- description:
- "Stay on top of deliveries and track shipment statuses efficiently.",
- link: "https://www.educare.io/platform/analytics/e..",
- },
-]
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/card-18/page.tsx b/apps/web/src/components/blocks/card-18/page.tsx
deleted file mode 100644
index 9060419..0000000
--- a/apps/web/src/components/blocks/card-18/page.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { CardGrid } from "./components/card-grid"
-
-export function Page() {
- return (
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/chart-12/components/chart.tsx b/apps/web/src/components/blocks/chart-12/components/chart.tsx
deleted file mode 100644
index 6ded4f5..0000000
--- a/apps/web/src/components/blocks/chart-12/components/chart.tsx
+++ /dev/null
@@ -1,240 +0,0 @@
-"use client"
-
-import { useState, type CSSProperties } from "react"
-import { Cell, Pie, PieChart } from "recharts"
-
-import { cn } from "@evobgp/ui/lib/utils"
-import { Button } from "@evobgp/ui/components/button"
-import {
- Card,
- CardAction,
- CardContent,
- CardHeader,
- CardTitle,
-} from "@evobgp/ui/components/card"
-import {
- ChartContainer,
- ChartTooltip,
- ChartTooltipContent,
-} from "@evobgp/ui/components/chart"
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuGroup,
- DropdownMenuItem,
- DropdownMenuTrigger,
-} from "@evobgp/ui/components/dropdown-menu"
-import { Item, ItemMedia } from "@evobgp/ui/components/item"
-import { Separator } from "@evobgp/ui/components/separator"
-import {
- chartConfig,
- inflowRanges,
- rangeOptions,
- type InflowSource,
- type RangeKey,
-} from "./data"
-import { ChevronDownIcon } from "lucide-react"
-
-function GaugeGrid() {
- const gridPattern =
- "[background-image:linear-gradient(to_right,var(--border)_1px,transparent_1px),linear-gradient(to_bottom,var(--border)_1px,transparent_1px)] [background-size:8px_8px]"
-
- return (
-
- )
-}
-
-function RangeMenu({
- selectedRange,
- onSelectRange,
-}: {
- selectedRange: RangeKey
- onSelectRange: (range: RangeKey) => void
-}) {
- return (
-
-
- }
- >
- {inflowRanges[selectedRange].label}
-
-
-
-
- {rangeOptions.map((range) => (
- onSelectRange(range.key)}
- >
- {range.label}
-
- ))}
-
-
-
- )
-}
-
-function SourceIcon({ source }: { source: InflowSource }) {
- return (
- -
-
-
-
- {source.icon}
-
-
- )
-}
-
-function SourceMetric({ source }: { source: InflowSource }) {
- return (
-
-
-
-
{source.name}
-
{source.value}
-
-
- )
-}
-
-export function Chart() {
- const [selectedRange, setSelectedRange] = useState("week")
- const currentRange = inflowRanges[selectedRange]
-
- return (
-
- {/* Header */}
-
- Capital Inflows
-
-
-
-
-
- {/* Content */}
-
-
-
-
-
-
-
- (
- <>
- {name}
-
- $
- {Number(value).toLocaleString(undefined, {
- maximumFractionDigits: 1,
- })}
- M
-
- >
- )}
- />
- }
- />
-
- {currentRange.sources.map((source) => (
- |
- ))}
-
-
-
-
-
- Capital In
- {currentRange.total}
-
-
-
-
-
-
-
- {currentRange.sources.map((source, index) => (
-
-
- {index < currentRange.sources.length - 1 ? (
-
- ) : null}
-
- ))}
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/chart-12/components/data.tsx b/apps/web/src/components/blocks/chart-12/components/data.tsx
deleted file mode 100644
index b49c0cc..0000000
--- a/apps/web/src/components/blocks/chart-12/components/data.tsx
+++ /dev/null
@@ -1,147 +0,0 @@
-import { type ReactNode } from "react"
-
-import { type ChartConfig } from "@evobgp/ui/components/chart"
-import { ActivityIcon, SparklesIcon, CircleDollarSignIcon } from "lucide-react"
-
-export type RangeKey = "week" | "month" | "quarter"
-
-export interface InflowSource {
- id: "wavemark" | "envato" | "qbridge"
- name: string
- amount: number
- value: string
- color: string
- icon: ReactNode
-}
-
-export interface InflowRange {
- label: string
- total: string
- sources: InflowSource[]
-}
-
-export const rangeOptions: { key: RangeKey; label: string }[] = [
- { key: "week", label: "Last Week" },
- { key: "month", label: "Last Month" },
- { key: "quarter", label: "Last Quarter" },
-]
-
-const sourceIcons = {
- wavemark: (
-
- ),
- envato: (
-
- ),
- qbridge: (
-
- ),
-} satisfies Record
-
-export const inflowRanges: Record = {
- week: {
- label: "Last Week",
- total: "$12.4M",
- sources: [
- {
- id: "wavemark",
- name: "WaveMark",
- amount: 7.6,
- value: "$7.6M",
- color: "oklch(57% 0.13 184)",
- icon: sourceIcons.wavemark,
- },
- {
- id: "envato",
- name: "Envato Inc.",
- amount: 3.4,
- value: "$3.4M",
- color: "oklch(76% 0.17 84)",
- icon: sourceIcons.envato,
- },
- {
- id: "qbridge",
- name: "QBridge B.V.",
- amount: 1.2,
- value: "$1.2M",
- color: "oklch(65% 0.21 45)",
- icon: sourceIcons.qbridge,
- },
- ],
- },
- month: {
- label: "Last Month",
- total: "$47.8M",
- sources: [
- {
- id: "wavemark",
- name: "WaveMark",
- amount: 28.4,
- value: "$28.4M",
- color: "oklch(57% 0.13 184)",
- icon: sourceIcons.wavemark,
- },
- {
- id: "envato",
- name: "Envato Inc.",
- amount: 13.6,
- value: "$13.6M",
- color: "oklch(76% 0.17 84)",
- icon: sourceIcons.envato,
- },
- {
- id: "qbridge",
- name: "QBridge B.V.",
- amount: 5.8,
- value: "$5.8M",
- color: "oklch(65% 0.21 45)",
- icon: sourceIcons.qbridge,
- },
- ],
- },
- quarter: {
- label: "Last Quarter",
- total: "$138.2M",
- sources: [
- {
- id: "wavemark",
- name: "WaveMark",
- amount: 79.1,
- value: "$79.1M",
- color: "oklch(57% 0.13 184)",
- icon: sourceIcons.wavemark,
- },
- {
- id: "envato",
- name: "Envato Inc.",
- amount: 42.5,
- value: "$42.5M",
- color: "oklch(76% 0.17 84)",
- icon: sourceIcons.envato,
- },
- {
- id: "qbridge",
- name: "QBridge B.V.",
- amount: 16.6,
- value: "$16.6M",
- color: "oklch(65% 0.21 45)",
- icon: sourceIcons.qbridge,
- },
- ],
- },
-}
-
-export const chartConfig = {
- wavemark: {
- label: "WaveMark",
- color: "oklch(57% 0.13 184)",
- },
- envato: {
- label: "Envato Inc.",
- color: "oklch(76% 0.17 84)",
- },
- qbridge: {
- label: "QBridge B.V.",
- color: "oklch(65% 0.21 45)",
- },
-} satisfies ChartConfig
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/chart-12/page.tsx b/apps/web/src/components/blocks/chart-12/page.tsx
deleted file mode 100644
index e76ec67..0000000
--- a/apps/web/src/components/blocks/chart-12/page.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { Chart } from "./components/chart"
-
-export function Page() {
- return (
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/chart-13/components/chart.tsx b/apps/web/src/components/blocks/chart-13/components/chart.tsx
deleted file mode 100644
index cd9f588..0000000
--- a/apps/web/src/components/blocks/chart-13/components/chart.tsx
+++ /dev/null
@@ -1,262 +0,0 @@
-import { Frame, FramePanel } from "@/components/reui/frame"
-import { Cell, Pie, PieChart } from "recharts"
-
-import { Button } from "@evobgp/ui/components/button"
-import {
- ChartContainer,
- ChartTooltip,
- ChartTooltipContent,
-} from "@evobgp/ui/components/chart"
-import { Separator } from "@evobgp/ui/components/separator"
-import {
- Tabs,
- TabsContent,
- TabsList,
- TabsTrigger,
-} from "@evobgp/ui/components/tabs"
-import {
- Tooltip,
- TooltipContent,
- TooltipProvider,
- TooltipTrigger,
-} from "@evobgp/ui/components/tooltip"
-import {
- chartConfig,
- inflowPeriods,
- type InflowFund,
- type InflowPeriod,
-} from "./data"
-import { InfoIcon } from "lucide-react"
-
-const CHART_REVEAL_STYLE = `
-@keyframes chart-13-reveal-up {
- from {
- clip-path: inset(100% 0 0 0);
- opacity: 0.75;
- }
- to {
- clip-path: inset(0 0 0 0);
- opacity: 1;
- }
-}
-
-.chart-13-reveal-up {
- animation: chart-13-reveal-up 680ms cubic-bezier(0.22, 1, 0.36, 1) both;
-}
-
-@media (prefers-reduced-motion: reduce) {
- .chart-13-reveal-up {
- animation: none;
- clip-path: none;
- opacity: 1;
- }
-}
-`
-
-type DonutSlice =
- | InflowFund
- | {
- key: "reserve"
- name: string
- amount: string
- share: number
- color: string
- fill: string
- }
-
-function getDonutData(period: InflowPeriod) {
- const trackedShare = period.funds.reduce(
- (total, fund) => total + fund.share,
- 0
- )
- const reserveShare = Math.max(100 - trackedShare, 0)
-
- return [
- ...period.funds,
- {
- key: "reserve",
- name: "Other Sources",
- amount: "",
- share: reserveShare,
- color: "var(--muted)",
- fill: "var(--color-reserve)",
- },
- ] satisfies DonutSlice[]
-}
-
-function ChartTooltipFormatter(item: unknown) {
- const fund = item as DonutSlice
- const value = fund.key === "reserve" ? `${fund.share}%` : fund.amount
-
- return (
-
-
-
- {fund.name}
-
-
{value}
-
- )
-}
-
-function InfoTooltip() {
- return (
-
-
-
-
- }
- />
-
- Tracked capital committed during the selected period.
-
-
- )
-}
-
-function InflowDonut({ period }: { period: InflowPeriod }) {
- const chartData = getDonutData(period)
-
- return (
-
-
-
-
- ChartTooltipFormatter(item.payload)
- }
- />
- }
- />
-
- {chartData.map((item) => (
- |
- ))}
-
-
-
-
-
-
- Capital
- {period.total}
-
-
-
- )
-}
-
-function InflowLegend({ period }: { period: InflowPeriod }) {
- return (
-
- {period.funds.map((fund, index) => (
- -
-
-
-
- {fund.name}
-
-
{fund.amount}
-
- {fund.share}%
-
-
- {index < period.funds.length - 1 ? (
-
- ) : null}
-
- ))}
-
- )
-}
-
-function InflowPeriodPanel({ period }: { period: InflowPeriod }) {
- return (
-
-
-
-
- )
-}
-
-export function Chart() {
- return (
-
-
-
-
-
- {/* Header */}
-
-
-
Capital Inflows
-
-
-
-
- {inflowPeriods.map((period) => (
-
- {period.label}
-
- ))}
-
-
-
- {/* Content */}
- {inflowPeriods.map((period) => (
-
-
-
- ))}
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/chart-13/components/data.ts b/apps/web/src/components/blocks/chart-13/components/data.ts
deleted file mode 100644
index d823b27..0000000
--- a/apps/web/src/components/blocks/chart-13/components/data.ts
+++ /dev/null
@@ -1,141 +0,0 @@
-import { type ChartConfig } from "@evobgp/ui/components/chart"
-
-export type InflowFundKey = "northline" | "copper" | "bridgewell" | "reserve"
-
-export interface InflowFund {
- key: Exclude
- name: string
- amount: string
- share: number
- color: string
- fill: string
-}
-
-export interface InflowPeriod {
- value: "week" | "month" | "year"
- label: string
- total: string
- funds: InflowFund[]
-}
-
-const teal = "oklch(0.6 0.145 181.2)"
-const amber = "oklch(0.76 0.161 80.1)"
-const orange = "oklch(0.66 0.19 42.8)"
-
-export const chartConfig = {
- capital: {
- label: "Capital",
- },
- northline: {
- label: "WaveMark Capital",
- color: teal,
- },
- copper: {
- label: "Copper Market",
- color: amber,
- },
- bridgewell: {
- label: "Bridgewell Fund",
- color: orange,
- },
- reserve: {
- label: "Other Sources",
- color: "var(--muted)",
- },
-} satisfies ChartConfig
-
-export const inflowPeriods: InflowPeriod[] = [
- {
- value: "week",
- label: "Week",
- total: "$12,4M",
- funds: [
- {
- key: "northline",
- name: "WaveMark Capital",
- amount: "$7,6M",
- share: 35,
- color: teal,
- fill: "var(--color-northline)",
- },
- {
- key: "copper",
- name: "Envato Market",
- amount: "$3,4M",
- share: 34,
- color: amber,
- fill: "var(--color-copper)",
- },
- {
- key: "bridgewell",
- name: "QBridge Investment",
- amount: "$1,2M",
- share: 14,
- color: orange,
- fill: "var(--color-bridgewell)",
- },
- ],
- },
- {
- value: "month",
- label: "Month",
- total: "$48,2M",
- funds: [
- {
- key: "northline",
- name: "Northline Capital",
- amount: "$21,8M",
- share: 39,
- color: teal,
- fill: "var(--color-northline)",
- },
- {
- key: "copper",
- name: "Copper Market",
- amount: "$16,5M",
- share: 31,
- color: amber,
- fill: "var(--color-copper)",
- },
- {
- key: "bridgewell",
- name: "Bridgewell Fund",
- amount: "$6,9M",
- share: 18,
- color: orange,
- fill: "var(--color-bridgewell)",
- },
- ],
- },
- {
- value: "year",
- label: "Year",
- total: "$186,7M",
- funds: [
- {
- key: "northline",
- name: "Northline Capital",
- amount: "$72,4M",
- share: 37,
- color: teal,
- fill: "var(--color-northline)",
- },
- {
- key: "copper",
- name: "Copper Market",
- amount: "$61,2M",
- share: 33,
- color: amber,
- fill: "var(--color-copper)",
- },
- {
- key: "bridgewell",
- name: "Bridgewell Fund",
- amount: "$29,6M",
- share: 16,
- color: orange,
- fill: "var(--color-bridgewell)",
- },
- ],
- },
-]
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/chart-13/page.tsx b/apps/web/src/components/blocks/chart-13/page.tsx
deleted file mode 100644
index e76ec67..0000000
--- a/apps/web/src/components/blocks/chart-13/page.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { Chart } from "./components/chart"
-
-export function Page() {
- return (
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/chart-15/components/chart.tsx b/apps/web/src/components/blocks/chart-15/components/chart.tsx
deleted file mode 100644
index 904a633..0000000
--- a/apps/web/src/components/blocks/chart-15/components/chart.tsx
+++ /dev/null
@@ -1,154 +0,0 @@
-"use client"
-
-import { Frame, FramePanel } from "@/components/reui/frame"
-import { Area, AreaChart, ResponsiveContainer, Tooltip } from "recharts"
-import { activeUsersData, customersData, revenueData } from "./data"
-import { CircleDollarSignIcon, UserPlusIcon, TrendingUp } from "lucide-react"
-
-// ── Business metric cards ──
-
-const businessCards = [
- {
- title: "Revenue",
- period: "Last 28 days",
- value: "6.238$",
- timestamp: "",
- data: revenueData,
- color: "var(--color-emerald-500)",
- gradientId: "revenueGradient",
- icon: (
-
- ),
- },
- {
- title: "New Customers",
- period: "Last 28 days",
- value: "6.202",
- timestamp: "3h ago",
- data: customersData,
- color: "var(--color-blue-500)",
- gradientId: "customersGradient",
- icon: (
-
- ),
- },
- {
- title: "Active Users",
- period: "Last 28 days",
- value: "18.945",
- timestamp: "1h ago",
- data: activeUsersData,
- color: "var(--color-violet-500)",
- gradientId: "usersGradient",
- icon: (
-
- ),
- },
-]
-
-export function Chart() {
- return (
-
-
- {businessCards.map((card, i) => (
-
-
- {/* Header */}
-
-
- {card.icon}
-
- {card.title}
-
-
- {/* Chart */}
-
- {/* Value */}
-
-
- {card.period}
-
-
- {card.value}
-
-
-
-
-
-
-
-
-
-
-
-
-
- {
- if (active && payload && payload.length) {
- const value = payload[0].value as number
- const formatValue = (val: number) => {
- if (card.title === "Revenue") {
- return `${(val / 1000).toFixed(1)}k US$`
- }
- return `${(val / 1000).toFixed(1)}k`
- }
- return (
-
-
- {formatValue(value)}
-
-
- )
- }
- return null
- }}
- />
-
-
-
-
-
-
-
-
- ))}
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/chart-15/components/data.tsx b/apps/web/src/components/blocks/chart-15/components/data.tsx
deleted file mode 100644
index fff475e..0000000
--- a/apps/web/src/components/blocks/chart-15/components/data.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-import { ReactNode } from "react"
-
-// ── Types ──
-
-export interface MetricCard {
- title: string
- period: string
- value: string
- timestamp: string
- data: { value: number }[]
- color: string
- icon: ReactNode
- gradientId: string
-}
-
-// ── Data ──
-
-export const revenueData = [
- { value: 1000 },
- { value: 4500 },
- { value: 2000 },
- { value: 5200 },
- { value: 1500 },
- { value: 6100 },
- { value: 3000 },
- { value: 6800 },
- { value: 2000 },
- { value: 1000 },
- { value: 4000 },
- { value: 2000 },
- { value: 3000 },
- { value: 2000 },
- { value: 6238 },
-]
-
-export const customersData = [
- { value: 2000 },
- { value: 4500 },
- { value: 2000 },
- { value: 5200 },
- { value: 1500 },
- { value: 5100 },
- { value: 2500 },
- { value: 6800 },
- { value: 1800 },
- { value: 1000 },
- { value: 3000 },
- { value: 2000 },
- { value: 2700 },
- { value: 2000 },
- { value: 4238 },
-]
-
-export const activeUsersData = [
- { value: 2000 },
- { value: 3500 },
- { value: 2000 },
- { value: 5200 },
- { value: 1200 },
- { value: 4100 },
- { value: 3500 },
- { value: 5800 },
- { value: 2000 },
- { value: 800 },
- { value: 3000 },
- { value: 1000 },
- { value: 4000 },
- { value: 2000 },
- { value: 4238 },
-]
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/chart-15/page.tsx b/apps/web/src/components/blocks/chart-15/page.tsx
deleted file mode 100644
index adfecfa..0000000
--- a/apps/web/src/components/blocks/chart-15/page.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { Chart } from "./components/chart"
-
-export function Page() {
- return (
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/chart-27/components/chart.tsx b/apps/web/src/components/blocks/chart-27/components/chart.tsx
deleted file mode 100644
index f1e07ea..0000000
--- a/apps/web/src/components/blocks/chart-27/components/chart.tsx
+++ /dev/null
@@ -1,158 +0,0 @@
-"use client"
-
-import { useMemo, useState } from "react"
-import { Badge } from "@/components/reui/badge"
-import { Frame, FramePanel } from "@/components/reui/frame"
-import { LabelList, Pie, PieChart } from "recharts"
-
-import { Button } from "@evobgp/ui/components/button"
-import { ChartContainer, ChartTooltip } from "@evobgp/ui/components/chart"
-import { Item, ItemMedia } from "@evobgp/ui/components/item"
-import { Tabs, TabsList, TabsTrigger } from "@evobgp/ui/components/tabs"
-import { browserData, chartConfig, PeriodKey, PERIODS } from "./data"
-import { TrendingUp, PieChartIcon, InfoIcon } from "lucide-react"
-
-// ── Custom tooltip ──
-
-const CustomTooltip = ({
- active,
- payload,
-}: {
- active?: boolean
- payload?: {
- name: string
- color: string
- value: number
- payload: { browser: string }
- }[]
-}) => {
- if (active && payload && payload.length) {
- return (
-
-
- {payload[0].payload.browser}
-
-
-
-
- {payload[0].value.toLocaleString()}
-
-
-
- )
- }
- return null
-}
-
-export function Chart() {
- const [selectedPeriod, setSelectedPeriod] = useState("5D")
-
- const { currentData, totalVisitors } = useMemo(() => {
- const data = browserData[selectedPeriod] || []
- const total = data.reduce((sum, item) => sum + item.visitors, 0)
- return { currentData: data, totalVisitors: total }
- }, [selectedPeriod])
-
- return (
-
- {/* Content */}
-
-
-
-
Browser Usage
-
-
- +5.2%
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Total Visitors
-
-
- {totalVisitors.toLocaleString()}
-
-
-
-
-
setSelectedPeriod(value as PeriodKey)}
- className="w-full"
- >
-
- {Object.values(PERIODS).map((period) => (
-
- {period.label}
-
- ))}
-
-
-
-
-
-
- } />
-
-
- value >= 1000 ? `${(value / 1000).toFixed(1)}k` : value
- }
- />
-
-
-
-
-
-
-
- Visitor data based on unique browser signatures.
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/chart-27/components/data.ts b/apps/web/src/components/blocks/chart-27/components/data.ts
deleted file mode 100644
index af4ef80..0000000
--- a/apps/web/src/components/blocks/chart-27/components/data.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import { ChartConfig } from "@evobgp/ui/components/chart"
-
-// ── Types ──
-
-export type PeriodKey = "5D" | "2W" | "1M"
-
-// ── Config ──
-
-export const chartConfig = {
- visitors: { label: "Visitors" },
- chrome: { label: "Chrome", color: "var(--color-blue-500)" },
- safari: { label: "Safari", color: "var(--color-sky-500)" },
- firefox: { label: "Firefox", color: "var(--color-orange-500)" },
- edge: { label: "Edge", color: "var(--color-indigo-500)" },
- other: { label: "Other", color: "var(--color-slate-500)" },
-} satisfies ChartConfig
-
-export const PERIODS = {
- "5D": { key: "5D", label: "5D" },
- "2W": { key: "2W", label: "2W" },
- "1M": { key: "1M", label: "1M" },
-} as const
-
-// ── Data ──
-
-export const browserData: Record<
- PeriodKey,
- { browser: string; visitors: number; fill: string }[]
-> = {
- "5D": [
- { browser: "chrome", visitors: 275, fill: "var(--color-blue-500)" },
- { browser: "safari", visitors: 200, fill: "var(--color-sky-500)" },
- { browser: "firefox", visitors: 187, fill: "var(--color-orange-500)" },
- { browser: "edge", visitors: 173, fill: "var(--color-indigo-500)" },
- { browser: "other", visitors: 90, fill: "var(--color-slate-500)" },
- ],
- "2W": [
- { browser: "chrome", visitors: 1275, fill: "var(--color-blue-500)" },
- { browser: "safari", visitors: 800, fill: "var(--color-sky-500)" },
- { browser: "firefox", visitors: 587, fill: "var(--color-orange-500)" },
- { browser: "edge", visitors: 473, fill: "var(--color-indigo-500)" },
- { browser: "other", visitors: 290, fill: "var(--color-slate-500)" },
- ],
- "1M": [
- { browser: "chrome", visitors: 4275, fill: "var(--color-blue-500)" },
- { browser: "safari", visitors: 3200, fill: "var(--color-sky-500)" },
- { browser: "firefox", visitors: 2187, fill: "var(--color-orange-500)" },
- { browser: "edge", visitors: 1873, fill: "var(--color-indigo-500)" },
- { browser: "other", visitors: 1090, fill: "var(--color-slate-500)" },
- ],
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/chart-27/page.tsx b/apps/web/src/components/blocks/chart-27/page.tsx
deleted file mode 100644
index 11380d5..0000000
--- a/apps/web/src/components/blocks/chart-27/page.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { Chart } from "./components/chart"
-
-export function Page() {
- return (
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/dashboard-1/components/capacity-chart.tsx b/apps/web/src/components/blocks/dashboard-1/components/capacity-chart.tsx
deleted file mode 100644
index 5a350dd..0000000
--- a/apps/web/src/components/blocks/dashboard-1/components/capacity-chart.tsx
+++ /dev/null
@@ -1,416 +0,0 @@
-import { Frame, FramePanel } from "@/components/reui/frame"
-import { Cell, Pie, PieChart } from "recharts"
-
-import { cn } from "@evobgp/ui/lib/utils"
-import {
- Avatar,
- AvatarFallback,
- AvatarGroup,
- AvatarImage,
-} from "@evobgp/ui/components/avatar"
-import { Button } from "@evobgp/ui/components/button"
-import {
- ChartContainer,
- ChartTooltip,
- ChartTooltipContent,
-} from "@evobgp/ui/components/chart"
-import { Separator } from "@evobgp/ui/components/separator"
-import {
- Tabs,
- TabsContent,
- TabsList,
- TabsTrigger,
-} from "@evobgp/ui/components/tabs"
-import {
- Tooltip,
- TooltipContent,
- TooltipProvider,
- TooltipTrigger,
-} from "@evobgp/ui/components/tooltip"
-import {
- allocationMemberCount,
- allocationMembers,
- allocationPeriods,
- inflowChartConfig,
- inflowPeriods,
- SEGMENT_COUNT,
- type AllocationPeriod,
- type InflowFund,
- type InflowPeriod,
-} from "./data"
-import { InfoIcon } from "lucide-react"
-
-const segments = Array.from({ length: SEGMENT_COUNT }, (_, index) => index)
-
-const CHART_REVEAL_STYLE = `
-@keyframes dashboard-1-flow-reveal-up {
- from {
- clip-path: inset(100% 0 0 0);
- opacity: 0.75;
- }
- to {
- clip-path: inset(0 0 0 0);
- opacity: 1;
- }
-}
-
-.dashboard-1-flow-reveal-up {
- animation: dashboard-1-flow-reveal-up 680ms cubic-bezier(0.22, 1, 0.36, 1) both;
-}
-
-@media (prefers-reduced-motion: reduce) {
- .dashboard-1-flow-reveal-up {
- animation: none;
- clip-path: none;
- opacity: 1;
- }
-}
-`
-
-function AllocationMeter({ period }: { period: AllocationPeriod }) {
- return (
-
- {segments.map((segment) => (
-
- ))}
-
- )
-}
-
-function MemberStack() {
- return (
-
-
- {allocationMembers.map((member) => (
-
- {member.avatar ? (
-
- ) : null}
-
- {member.initials}
-
-
- ))}
-
-
- {allocationMemberCount} Members
-
-
- )
-}
-
-function AllocationChart() {
- return (
-
-
-
- {/* Header */}
-
-
-
Capacity Allocation
-
-
-
- }
- >
-
-
-
- Fulfillment capacity by selected period.
-
-
-
-
-
- {allocationPeriods.map((period) => (
-
- {period.label}
-
- ))}
-
-
-
- {allocationPeriods.map((period) => (
-
-
- {/* Metric */}
-
-
- {period.allocation}
-
-
- {period.delta}
-
-
- {period.comparison}
-
-
-
- {/* Chart */}
-
-
- {/* Footer */}
-
-
-
- Queued Orders:
- {" "}
-
- {period.exposure}
-
-
-
-
-
-
- ))}
-
-
-
- )
-}
-
-type DonutSlice =
- | InflowFund
- | {
- key: "reserve"
- name: string
- amount: string
- share: number
- color: string
- fill: string
- }
-
-function getDonutData(period: InflowPeriod) {
- const trackedShare = period.funds.reduce(
- (total, fund) => total + fund.share,
- 0
- )
- const reserveShare = Math.max(100 - trackedShare, 0)
-
- return [
- ...period.funds,
- {
- key: "reserve",
- name: "Reserve Capacity",
- amount: "",
- share: reserveShare,
- color: "var(--muted)",
- fill: "var(--color-reserve)",
- },
- ] satisfies DonutSlice[]
-}
-
-function ChartTooltipFormatter(item: unknown) {
- const fund = item as DonutSlice
- const value = fund.key === "reserve" ? `${fund.share}%` : fund.amount
-
- return (
-
-
-
- {fund.name}
-
-
{value}
-
- )
-}
-
-function InfoTooltip() {
- return (
-
-
-
-
- }
- />
-
- Tracked decisions entering fulfillment lanes.
-
-
- )
-}
-
-function InflowDonut({ period }: { period: InflowPeriod }) {
- const chartData = getDonutData(period)
-
- return (
-
-
-
-
- ChartTooltipFormatter(item.payload)
- }
- />
- }
- />
-
- {chartData.map((item) => (
- |
- ))}
-
-
-
-
-
-
- Flow
- {period.total}
-
-
-
- )
-}
-
-function InflowLegend({ period }: { period: InflowPeriod }) {
- return (
-
- {period.funds.map((fund, index) => (
- -
-
-
-
- {fund.name}
-
-
{fund.amount}
-
- {fund.share}%
-
-
- {index < period.funds.length - 1 ? (
-
- ) : null}
-
- ))}
-
- )
-}
-
-function InflowPeriodPanel({ period }: { period: InflowPeriod }) {
- return (
-
-
-
-
- )
-}
-
-function InflowChart() {
- return (
-
-
-
-
-
- {/* Header */}
-
-
-
Decision Flow
-
-
-
-
- {inflowPeriods.map((period) => (
-
- {period.label}
-
- ))}
-
-
-
- {/* Content */}
- {inflowPeriods.map((period) => (
-
-
-
- ))}
-
-
-
-
- )
-}
-
-export function Chart() {
- return (
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/dashboard-1/components/chart-cards.tsx b/apps/web/src/components/blocks/dashboard-1/components/chart-cards.tsx
deleted file mode 100644
index 1ade245..0000000
--- a/apps/web/src/components/blocks/dashboard-1/components/chart-cards.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import { Frame, FramePanel } from "@/components/reui/frame"
-
-import { cn } from "@evobgp/ui/lib/utils"
-import { Item, ItemMedia } from "@evobgp/ui/components/item"
-
-import { FULFILLMENT_CARDS, type FulfillmentCard } from "./data"
-
-function CardItem({ card }: { card: FulfillmentCard }) {
- return (
-
- {/* Heading */}
-
-
-
-
- {card.icon}
-
-
-
-
- {card.typeLabel}
-
-
{card.title}
-
-
-
-
- {card.metricLabel}
-
-
-
- {card.balance}
-
-
- {card.change.percent} ({card.change.amount})
-
-
-
-
- )
-}
-
-export function Chart() {
- return (
-
- {/* Grid */}
-
- {FULFILLMENT_CARDS.map((card) => (
-
- ))}
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/dashboard-1/components/commander-card.tsx b/apps/web/src/components/blocks/dashboard-1/components/commander-card.tsx
deleted file mode 100644
index bcbd09f..0000000
--- a/apps/web/src/components/blocks/dashboard-1/components/commander-card.tsx
+++ /dev/null
@@ -1,155 +0,0 @@
-import { Badge } from "@/components/reui/badge"
-import {
- Frame,
- FrameFooter,
- FramePanel,
-} from "@/components/reui/frame"
-
-import { cn } from "@evobgp/ui/lib/utils"
-import { Button } from "@evobgp/ui/components/button"
-import { Progress } from "@evobgp/ui/components/progress"
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@evobgp/ui/components/select"
-import { Separator } from "@evobgp/ui/components/separator"
-import {
- PERFORMANCE_RANGE_OPTIONS,
- SHIFT_ACTIVITY,
- SHIFT_PERFORMANCE,
- SHIFT_PIPELINE_PROGRESS,
-} from "./data"
-import { TrendingUp, TrendingDown, CircleCheckIcon } from "lucide-react"
-
-export function InvestorCard() {
- return (
-
- {/* Content */}
-
-
-
-
Shift Performance
-
-
-
-
-
-
-
-
- {SHIFT_PERFORMANCE.map((item) => (
-
-
- {item.value}
-
-
- {item.label}
-
-
- {item.trend === "positive" ? (
-
- ) : (
-
- )}
- {item.delta}
-
-
- ))}
-
-
-
-
-
-
-
- Pipeline Progress
-
-
- {SHIFT_PIPELINE_PROGRESS}%
-
-
-
-
-
-
-
-
-
- Recent Activity
-
-
- {SHIFT_ACTIVITY.map((activity) => (
- -
-
-
-
- {activity.title}
-
-
-
- {activity.status}
-
-
- ))}
-
-
-
-
- {/* Footer */}
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/dashboard-1/components/dashboard.tsx b/apps/web/src/components/blocks/dashboard-1/components/dashboard.tsx
deleted file mode 100644
index 226117f..0000000
--- a/apps/web/src/components/blocks/dashboard-1/components/dashboard.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { Chart as CapacityChart } from "./capacity-chart"
-import { Chart as ChartCards } from "./chart-cards"
-import { InvestorCard as CommanderCard } from "./commander-card"
-import { ExceptionGrid } from "./exception-grid"
-import { Navbar } from "./navbar"
-
-export function Dashboard() {
- return (
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/dashboard-1/components/data.tsx b/apps/web/src/components/blocks/dashboard-1/components/data.tsx
deleted file mode 100644
index 3ac5358..0000000
--- a/apps/web/src/components/blocks/dashboard-1/components/data.tsx
+++ /dev/null
@@ -1,676 +0,0 @@
-import { type ReactNode } from "react"
-import { type BadgeProps } from "@/components/reui/badge"
-
-import { type ChartConfig } from "@evobgp/ui/components/chart"
-import { PackageIcon, TruckIcon, TriangleAlertIcon, BotIcon } from "lucide-react"
-
-export type FulfillmentStatus = "On Time" | "At Risk" | "Delayed" | "Blocked"
-export type AutomationLevel = "Autopilot" | "Copilot" | "Manual"
-
-export interface TeamMember {
- name: string
- initials: string
- avatar: string
- role: string
-}
-
-export interface FulfillmentException {
- id: string
- reference: string
- customer: string
- email: string
- avatar: string
- initials: string
- lane: string
- facility: string
- stage: string
- promise: string
- slaMinutes: number
- automation: AutomationLevel
- owner: string
- units: number
- value: number
- risk: string
- status: FulfillmentStatus
-}
-
-export const STATUS_ORDER: FulfillmentStatus[] = [
- "On Time",
- "At Risk",
- "Delayed",
- "Blocked",
-]
-
-export const STATUS_BADGE_VARIANT: Record<
- FulfillmentStatus,
- BadgeProps["variant"]
-> = {
- "On Time": "success-outline",
- "At Risk": "warning-outline",
- Delayed: "info-outline",
- Blocked: "destructive-outline",
-}
-
-export const AUTOMATION_BADGE_VARIANT: Record<
- AutomationLevel,
- BadgeProps["variant"]
-> = {
- Autopilot: "success-light",
- Copilot: "info-light",
- Manual: "warning-light",
-}
-
-export const NAV_MEMBERS: TeamMember[] = [
- {
- name: "Maya Singh",
- initials: "MS",
- avatar:
- "https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=96&h=96&dpr=2&q=80",
- role: "Fulfillment lead",
- },
- {
- name: "Leo Martins",
- initials: "LM",
- avatar:
- "https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=96&h=96&dpr=2&q=80",
- role: "Automation owner",
- },
- {
- name: "Nora Albright",
- initials: "NA",
- avatar:
- "https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=96&h=96&dpr=2&q=80",
- role: "Capacity planner",
- },
-]
-
-export const TEAM_MEMBERS = NAV_MEMBERS.map((member) => ({
- src: member.avatar,
- initials: member.initials,
- name: member.name,
-}))
-
-export const TEAM_EXTRA_COUNT = 11
-
-export interface FulfillmentCardChange {
- positive: boolean
- percent: string
- amount: string
-}
-
-export interface FulfillmentCard {
- typeLabel: string
- title: string
- metricLabel: string
- balance: string
- change: FulfillmentCardChange
- icon: ReactNode
- iconBg: string
-}
-
-export const FULFILLMENT_CARDS: FulfillmentCard[] = [
- {
- typeLabel: "Outbound",
- title: "Orders Ready",
- metricLabel: "Ready Volume",
- balance: "18,420",
- change: {
- positive: true,
- percent: "+11.8%",
- amount: "1,946",
- },
- iconBg: "bg-neutral-950",
- icon: (
-
- ),
- },
- {
- typeLabel: "Promise",
- title: "Same-Day SLA",
- metricLabel: "Service Level",
- balance: "94.8%",
- change: {
- positive: true,
- percent: "+1.2 pts",
- amount: "shift",
- },
- iconBg: "bg-indigo-600",
- icon: (
-
- ),
- },
- {
- typeLabel: "Inventory",
- title: "Stock Risk",
- metricLabel: "Blocked SKUs",
- balance: "31",
- change: {
- positive: true,
- percent: "13 fewer",
- amount: "since 06:00",
- },
- iconBg: "bg-amber-400",
- icon: (
-
- ),
- },
- {
- typeLabel: "Policy",
- title: "AI Autopilot",
- metricLabel: "Auto Resolved",
- balance: "71.6%",
- change: {
- positive: true,
- percent: "+8.4 pts",
- amount: "policy",
- },
- iconBg: "bg-cyan-600",
- icon: (
-
- ),
- },
-]
-
-export type AllocationPeriod = {
- value: "week" | "month" | "year"
- label: string
- allocation: string
- delta: string
- comparison: string
- exposure: string
- filledSegments: number
-}
-
-export type AllocationMember = {
- name: string
- initials: string
- avatar?: string
-}
-
-export const SEGMENT_COUNT = 56
-export const allocationMemberCount = 6
-
-export const allocationPeriods: AllocationPeriod[] = [
- {
- value: "week",
- label: "Week",
- allocation: "86%",
- delta: "+5.8%",
- comparison: "vs labor plan",
- exposure: "3,840 orders",
- filledSegments: 48,
- },
- {
- value: "month",
- label: "Month",
- allocation: "79%",
- delta: "+2.4%",
- comparison: "vs prior month",
- exposure: "18 priority lanes",
- filledSegments: 44,
- },
- {
- value: "year",
- label: "Year",
- allocation: "74%",
- delta: "+9.2%",
- comparison: "automation lift",
- exposure: "6 facilities",
- filledSegments: 41,
- },
-]
-
-export const allocationMembers: AllocationMember[] = TEAM_MEMBERS.map(
- (member) => ({
- name: member.name,
- initials: member.initials,
- avatar: member.src,
- })
-)
-
-export type PerformanceTrend = "positive" | "negative"
-export type ActivityTone = "success" | "info" | "warning"
-
-export interface PerformanceMetric {
- label: string
- value: string
- trend: PerformanceTrend
- delta: string
-}
-
-export interface ShiftActivity {
- id: string
- title: string
- time: string
- status: string
- tone: ActivityTone
-}
-
-export const PERFORMANCE_RANGE_OPTIONS = [
- { label: "Today", value: "today" },
- { label: "Week", value: "week" },
- { label: "Month", value: "month" },
-]
-
-export const SHIFT_PERFORMANCE: PerformanceMetric[] = [
- {
- label: "Orders Cleared",
- value: "18.4k",
- trend: "positive",
- delta: "+11.8%",
- },
- {
- label: "SLA Recovery",
- value: "94.8%",
- trend: "positive",
- delta: "+1.2 pts",
- },
- {
- label: "Risk Exposure",
- value: "$128k",
- trend: "negative",
- delta: "-9.4%",
- },
-]
-
-export const SHIFT_PIPELINE_PROGRESS = 76
-
-export const SHIFT_ACTIVITY: ShiftActivity[] = [
- {
- id: "wave-release",
- title: "Released priority wave to dock B",
- time: "4 min ago",
- status: "Cleared",
- tone: "success",
- },
- {
- id: "carrier-reprice",
- title: "Carrier mix repriced for zone 6",
- time: "12 min ago",
- status: "Review",
- tone: "info",
- },
- {
- id: "inventory-hold",
- title: "Inventory hold isolated to 3 SKUs",
- time: "23 min ago",
- status: "Watch",
- tone: "warning",
- },
-]
-
-export type InflowFundKey = "autopilot" | "copilot" | "manual" | "reserve"
-
-export interface InflowFund {
- key: Exclude
- name: string
- amount: string
- share: number
- color: string
- fill: string
-}
-
-export interface InflowPeriod {
- value: "week" | "month" | "year"
- label: string
- total: string
- headline: string
- description: string
- delta: string
- funds: InflowFund[]
-}
-
-const inflowAutopilotColor = "oklch(0.62 0.19 149)"
-const inflowCopilotColor = "oklch(0.58 0.18 257)"
-const inflowManualColor = "oklch(0.72 0.16 78)"
-
-export const inflowChartConfig = {
- flow: {
- label: "Flow",
- },
- autopilot: {
- label: "Autopilot",
- color: inflowAutopilotColor,
- },
- copilot: {
- label: "Copilot",
- color: inflowCopilotColor,
- },
- manual: {
- label: "Manual",
- color: inflowManualColor,
- },
- reserve: {
- label: "Reserve",
- color: "oklch(0.7 0.04 260)",
- },
-} satisfies ChartConfig
-
-export const inflowPeriods: InflowPeriod[] = [
- {
- value: "week",
- label: "Week",
- total: "18.4k",
- headline: "Exception Flow",
- description: "Orders entering decision lanes",
- delta: "+6.2%",
- funds: [
- {
- key: "autopilot",
- name: "Autopilot",
- amount: "9.1k",
- share: 49.5,
- color: inflowAutopilotColor,
- fill: "var(--color-autopilot)",
- },
- {
- key: "copilot",
- name: "Copilot",
- amount: "5.2k",
- share: 28.3,
- color: inflowCopilotColor,
- fill: "var(--color-copilot)",
- },
- {
- key: "manual",
- name: "Manual",
- amount: "2.8k",
- share: 15.2,
- color: inflowManualColor,
- fill: "var(--color-manual)",
- },
- ],
- },
- {
- value: "month",
- label: "Month",
- total: "76.8k",
- headline: "Resolved Flow",
- description: "Completed decisions this month",
- delta: "+14.8%",
- funds: [
- {
- key: "autopilot",
- name: "Autopilot",
- amount: "41.6k",
- share: 54.2,
- color: inflowAutopilotColor,
- fill: "var(--color-autopilot)",
- },
- {
- key: "copilot",
- name: "Copilot",
- amount: "20.3k",
- share: 26.4,
- color: inflowCopilotColor,
- fill: "var(--color-copilot)",
- },
- {
- key: "manual",
- name: "Manual",
- amount: "9.8k",
- share: 12.8,
- color: inflowManualColor,
- fill: "var(--color-manual)",
- },
- ],
- },
- {
- value: "year",
- label: "Year",
- total: "812k",
- headline: "Network Flow",
- description: "Decisions across six facilities",
- delta: "+21.5%",
- funds: [
- {
- key: "autopilot",
- name: "Autopilot",
- amount: "428k",
- share: 52.7,
- color: inflowAutopilotColor,
- fill: "var(--color-autopilot)",
- },
- {
- key: "copilot",
- name: "Copilot",
- amount: "224k",
- share: 27.6,
- color: inflowCopilotColor,
- fill: "var(--color-copilot)",
- },
- {
- key: "manual",
- name: "Manual",
- amount: "103k",
- share: 12.7,
- color: inflowManualColor,
- fill: "var(--color-manual)",
- },
- ],
- },
-]
-
-export const FULFILLMENT_ROWS: FulfillmentException[] = [
- {
- id: "row-1001",
- reference: "NSC-84721",
- customer: "Avery Outdoor",
- email: "ops@averyoutdoor.example",
- avatar:
- "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=96&h=96&dpr=2&q=80",
- initials: "AO",
- lane: "Chicago to Austin",
- facility: "ORD-2",
- stage: "Carrier tender",
- promise: "Today 18:00",
- slaMinutes: 42,
- automation: "Copilot",
- owner: "Maya Singh",
- units: 480,
- value: 38240,
- risk: "Carrier capacity is tight after midday cutoff",
- status: "At Risk",
- },
- {
- id: "row-1002",
- reference: "NSC-84734",
- customer: "Field & Frame",
- email: "priority@fieldframe.example",
- avatar:
- "https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?w=96&h=96&dpr=2&q=80",
- initials: "FF",
- lane: "Dallas to Phoenix",
- facility: "DFW-1",
- stage: "Pick wave",
- promise: "Today 16:30",
- slaMinutes: 88,
- automation: "Autopilot",
- owner: "Leo Martins",
- units: 310,
- value: 21480,
- risk: "Wave optimized by carton density",
- status: "On Time",
- },
- {
- id: "row-1003",
- reference: "NSC-84755",
- customer: "MetroFit Labs",
- email: "ops@metrofit.example",
- avatar:
- "https://images.unsplash.com/photo-1519345182560-3f2917c472ef?w=96&h=96&dpr=2&q=80",
- initials: "ML",
- lane: "Newark to Boston",
- facility: "EWR-3",
- stage: "Inventory hold",
- promise: "Today 15:15",
- slaMinutes: -24,
- automation: "Manual",
- owner: "Nora Albright",
- units: 126,
- value: 18760,
- risk: "Lot trace requires human release",
- status: "Blocked",
- },
- {
- id: "row-1004",
- reference: "NSC-84763",
- customer: "Northline Studio",
- email: "returns@northline.example",
- avatar:
- "https://images.unsplash.com/photo-1517841905240-472988babdf9?w=96&h=96&dpr=2&q=80",
- initials: "NS",
- lane: "Los Angeles to Seattle",
- facility: "LAX-4",
- stage: "Packing",
- promise: "Today 19:45",
- slaMinutes: 114,
- automation: "Autopilot",
- owner: "Leo Martins",
- units: 840,
- value: 52210,
- risk: "Packing line is running above plan",
- status: "On Time",
- },
- {
- id: "row-1005",
- reference: "NSC-84801",
- customer: "Urban Pantry",
- email: "supply@urbanpantry.example",
- avatar:
- "https://images.unsplash.com/photo-1531427186611-ecfd6d936c79?w=96&h=96&dpr=2&q=80",
- initials: "UP",
- lane: "Atlanta to Miami",
- facility: "ATL-2",
- stage: "Cold chain",
- promise: "Today 17:00",
- slaMinutes: 9,
- automation: "Copilot",
- owner: "Maya Singh",
- units: 212,
- value: 30440,
- risk: "Reefer handoff needs confirmation",
- status: "Delayed",
- },
- {
- id: "row-1006",
- reference: "NSC-84819",
- customer: "Glow Market",
- email: "vip@glowmarket.example",
- avatar:
- "https://images.unsplash.com/photo-1489424731084-a5d8b219a5bb?w=96&h=96&dpr=2&q=80",
- initials: "GM",
- lane: "Las Vegas to Denver",
- facility: "LAS-1",
- stage: "Labeling",
- promise: "Tomorrow 09:20",
- slaMinutes: 312,
- automation: "Autopilot",
- owner: "Nora Albright",
- units: 94,
- value: 10920,
- risk: "No current risk",
- status: "On Time",
- },
- {
- id: "row-1007",
- reference: "NSC-84827",
- customer: "Ridge Supply",
- email: "buyers@ridgesupply.example",
- avatar:
- "https://images.unsplash.com/photo-1519085360753-af0119f7cbe7?w=96&h=96&dpr=2&q=80",
- initials: "RS",
- lane: "Portland to San Jose",
- facility: "PDX-1",
- stage: "Split shipment",
- promise: "Today 20:00",
- slaMinutes: 36,
- automation: "Copilot",
- owner: "Maya Singh",
- units: 176,
- value: 14680,
- risk: "Two SKUs short at primary node",
- status: "At Risk",
- },
- {
- id: "row-1008",
- reference: "NSC-84842",
- customer: "Casa Verde",
- email: "storeops@casaverde.example",
- avatar:
- "https://images.unsplash.com/photo-1544725176-7c40e5a71c5e?w=96&h=96&dpr=2&q=80",
- initials: "CV",
- lane: "Nashville to Charlotte",
- facility: "BNA-2",
- stage: "Dock queue",
- promise: "Today 14:30",
- slaMinutes: -51,
- automation: "Manual",
- owner: "Nora Albright",
- units: 265,
- value: 22750,
- risk: "Outbound door is constrained",
- status: "Delayed",
- },
- {
- id: "row-1009",
- reference: "NSC-84864",
- customer: "Beacon Cycle",
- email: "logistics@beaconcycle.example",
- avatar:
- "https://images.unsplash.com/photo-1552058544-f2b08422138a?w=96&h=96&dpr=2&q=80",
- initials: "BC",
- lane: "Columbus to Pittsburgh",
- facility: "CMH-1",
- stage: "Fraud review",
- promise: "Tomorrow 11:45",
- slaMinutes: 510,
- automation: "Manual",
- owner: "Maya Singh",
- units: 58,
- value: 8920,
- risk: "Payment review blocks release",
- status: "Blocked",
- },
- {
- id: "row-1010",
- reference: "NSC-84888",
- customer: "Aster Goods",
- email: "ops@astergoods.example",
- avatar:
- "https://images.unsplash.com/photo-1508214751196-bcfd4ca60f91?w=96&h=96&dpr=2&q=80",
- initials: "AG",
- lane: "Reno to Salt Lake City",
- facility: "RNO-1",
- stage: "Manifest",
- promise: "Today 22:15",
- slaMinutes: 177,
- automation: "Autopilot",
- owner: "Leo Martins",
- units: 390,
- value: 19340,
- risk: "Manifest is ready for carrier scan",
- status: "On Time",
- },
-]
-
-export function fulfillmentSearchBlob(row: FulfillmentException): string {
- return [
- row.reference,
- row.customer,
- row.email,
- row.lane,
- row.facility,
- row.stage,
- row.promise,
- row.automation,
- row.owner,
- row.risk,
- row.status,
- String(row.units),
- String(row.value),
- ]
- .filter(Boolean)
- .join(" ")
- .toLowerCase()
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/dashboard-1/components/exception-columns.tsx b/apps/web/src/components/blocks/dashboard-1/components/exception-columns.tsx
deleted file mode 100644
index 63d2530..0000000
--- a/apps/web/src/components/blocks/dashboard-1/components/exception-columns.tsx
+++ /dev/null
@@ -1,520 +0,0 @@
-import { memo } from "react"
-import { Badge } from "@/components/reui/badge"
-import { DataGridColumnHeader } from "@/components/reui/data-grid/data-grid-column-header"
-import {
- DataGridTableRowSelect,
- DataGridTableRowSelectAll,
-} from "@/components/reui/data-grid/data-grid-table"
-import { type ColumnDef, type Row } from "@tanstack/react-table"
-import { toast } from "sonner"
-
-import { cn } from "@evobgp/ui/lib/utils"
-import {
- Avatar,
- AvatarFallback,
- AvatarImage,
-} from "@evobgp/ui/components/avatar"
-import { Button } from "@evobgp/ui/components/button"
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuGroup,
- DropdownMenuItem,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from "@evobgp/ui/components/dropdown-menu"
-import { Item, ItemMedia } from "@evobgp/ui/components/item"
-import {
- Tooltip,
- TooltipContent,
- TooltipTrigger,
-} from "@evobgp/ui/components/tooltip"
-import {
- AUTOMATION_BADGE_VARIANT,
- STATUS_BADGE_VARIANT,
- type AutomationLevel,
- type FulfillmentException,
- type FulfillmentStatus,
-} from "./data"
-import { PackageIcon, InfoIcon, MoreHorizontalIcon, EyeIcon, BellIcon, CopyIcon, TriangleAlertIcon } from "lucide-react"
-
-const currencyCompact = new Intl.NumberFormat("en-US", {
- style: "currency",
- currency: "USD",
- maximumFractionDigits: 0,
-})
-
-const numberCompact = new Intl.NumberFormat("en-US", {
- maximumFractionDigits: 0,
-})
-
-const availabilityColor: Record = {
- "On Time": "bg-success",
- "At Risk": "bg-warning",
- Delayed: "bg-info",
- Blocked: "bg-destructive",
-}
-
-const stageProgress: Record = {
- "Carrier tender": 72,
- "Pick wave": 64,
- "Inventory hold": 28,
- Packing: 82,
- "Cold chain": 48,
- Labeling: 76,
- "Split shipment": 39,
- "Dock queue": 31,
- "Fraud review": 24,
- Manifest: 90,
-}
-
-function DotSeparator() {
- return (
-
- )
-}
-
-export const StatusBadge = memo(function StatusBadge({
- status,
-}: {
- status: FulfillmentStatus
-}) {
- return (
-
-
- {status}
-
- )
-})
-
-function AutomationBadge({ level }: { level: AutomationLevel }) {
- return {level}
-}
-
-const ReferenceCell = memo(function ReferenceCell({
- row,
-}: {
- row: Row
-}) {
- const order = row.original
-
- return (
-
- )
-})
-
-const CustomerCell = memo(function CustomerCell({
- row,
-}: {
- row: Row
-}) {
- const order = row.original
-
- return (
-
-
-
-
- {order.initials}
-
-
-
-
-
- )
-})
-
-const StageCell = memo(function StageCell({
- row,
-}: {
- row: Row
-}) {
- const order = row.original
- const progress = stageProgress[order.stage] ?? 50
-
- return (
-
-
-
} className="w-auto shrink-0 border-0 p-0">
-
-
-
-
-
- {order.stage}
-
-
-
-
-
-
-
- {progress}%
-
-
-
- )
-})
-
-const LaneCell = memo(function LaneCell({
- row,
-}: {
- row: Row
-}) {
- const order = row.original
-
- return (
-
-
- {order.lane}
-
-
- {order.facility}
-
-
- )
-})
-
-const ValueCell = memo(function ValueCell({
- row,
-}: {
- row: Row
-}) {
- const valueHint =
- row.original.value >= 30000 ? "Priority lane" : "Standard lane"
-
- return (
-
-
- {currencyCompact.format(row.original.value)}
-
-
-
- }
- >
-
-
-
-
- {valueHint}
-
- {numberCompact.format(row.original.units)} units
-
-
-
-
-
- )
-})
-
-function StateCell({ row }: { row: Row }) {
- const sla = row.original.slaMinutes
- const slaHint =
- sla < 0
- ? `${Math.abs(sla)} min overdue`
- : sla <= 45
- ? `${sla} min buffer`
- : `Due ${row.original.promise}`
-
- return (
-
-
-
- {slaHint}
-
-
- )
-}
-
-function RiskCell({ row }: { row: Row }) {
- return (
-
-
- }
- >
-
- {row.original.risk}
-
-
- {row.original.risk}
-
-
- )
-}
-
-function ActionsCell({ row }: { row: Row }) {
- const copyReference = async () => {
- await navigator.clipboard?.writeText(row.original.reference)
- toast.success("Reference copied", {
- description: row.original.reference,
- })
- }
-
- return (
-
-
- }
- >
-
-
-
-
-
- toast.info("Opening order", {
- description: row.original.reference,
- })
- }
- >
-
- View order
-
-
- toast.info("Owner notified", {
- description: row.original.owner,
- })
- }
- >
-
- Notify owner
-
-
-
- Copy reference
-
-
-
- toast.warning("Escalation staged", {
- description: "Connect this action to your incident workflow.",
- })
- }
- >
-
- Escalate
-
-
-
-
- )
-}
-
-export const columns: ColumnDef[] = [
- {
- accessorKey: "id",
- id: "id",
- header: () => ,
- cell: ({ row }) => ,
- enableSorting: false,
- size: 35,
- enableResizing: false,
- enableHiding: false,
- meta: {
- headerClassName: "ps-4!",
- cellClassName: "ps-4!",
- },
- },
- {
- accessorKey: "reference",
- id: "reference",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => ,
- size: 138,
- enableSorting: true,
- enableHiding: false,
- enableResizing: true,
- meta: {
- headerTitle: "Order",
- },
- },
- {
- accessorKey: "customer",
- id: "customer",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => ,
- size: 210,
- enableSorting: true,
- enableHiding: false,
- enableResizing: true,
- minSize: 190,
- meta: {
- headerTitle: "Customer",
- autoSize: true,
- },
- },
- {
- accessorKey: "lane",
- id: "lane",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => ,
- size: 165,
- enableSorting: true,
- enableHiding: true,
- enableResizing: true,
- meta: {
- headerTitle: "Lane",
- },
- },
- {
- accessorKey: "stage",
- id: "stage",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => ,
- size: 160,
- enableSorting: true,
- enableHiding: true,
- enableResizing: true,
- meta: {
- headerTitle: "Stage",
- },
- },
- {
- accessorKey: "automation",
- id: "automation",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => ,
- size: 112,
- enableSorting: true,
- enableHiding: true,
- enableResizing: true,
- meta: {
- headerTitle: "Automation",
- },
- },
- {
- accessorKey: "value",
- id: "value",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => ,
- size: 120,
- enableSorting: true,
- enableHiding: true,
- enableResizing: true,
- meta: {
- headerTitle: "Value",
- },
- },
- {
- accessorKey: "risk",
- id: "risk",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => ,
- size: 170,
- enableSorting: true,
- enableHiding: true,
- enableResizing: true,
- meta: {
- headerTitle: "Risk",
- },
- },
- {
- accessorKey: "status",
- id: "status",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => ,
- size: 142,
- enableSorting: true,
- enableHiding: true,
- enableResizing: true,
- meta: {
- headerTitle: "State",
- },
- },
- {
- id: "actions",
- header: "",
- cell: ({ row }) => ,
- size: 46,
- enableSorting: false,
- enableHiding: false,
- enableResizing: false,
- },
-]
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/dashboard-1/components/exception-grid.tsx b/apps/web/src/components/blocks/dashboard-1/components/exception-grid.tsx
deleted file mode 100644
index aa6e6c5..0000000
--- a/apps/web/src/components/blocks/dashboard-1/components/exception-grid.tsx
+++ /dev/null
@@ -1,369 +0,0 @@
-import { useMemo, useState } from "react"
-import { Badge } from "@/components/reui/badge"
-import { DataGrid as ReuiDataGrid } from "@/components/reui/data-grid/data-grid"
-import { DataGridPagination } from "@/components/reui/data-grid/data-grid-pagination"
-import { DataGridScrollArea } from "@/components/reui/data-grid/data-grid-scroll-area"
-import { DataGridTable } from "@/components/reui/data-grid/data-grid-table"
-import {
- Frame,
- FrameDescription,
- FrameFooter,
- FrameHeader,
- FramePanel,
- FrameTitle,
-} from "@/components/reui/frame"
-import {
- getCoreRowModel,
- getFilteredRowModel,
- getPaginationRowModel,
- getSortedRowModel,
- useReactTable,
- type PaginationState,
- type RowSelectionState,
- type SortingState,
- type VisibilityState,
-} from "@tanstack/react-table"
-import { toast } from "sonner"
-
-import { Button } from "@evobgp/ui/components/button"
-import { Checkbox } from "@evobgp/ui/components/checkbox"
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuGroup,
- DropdownMenuItem,
- DropdownMenuTrigger,
-} from "@evobgp/ui/components/dropdown-menu"
-import {
- InputGroup,
- InputGroupAddon,
- InputGroupButton,
- InputGroupInput,
-} from "@evobgp/ui/components/input-group"
-import { Label } from "@evobgp/ui/components/label"
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from "@evobgp/ui/components/popover"
-import { Separator } from "@evobgp/ui/components/separator"
-import { TooltipProvider } from "@evobgp/ui/components/tooltip"
-import {
- FULFILLMENT_ROWS,
- fulfillmentSearchBlob,
- STATUS_ORDER,
- type FulfillmentStatus,
-} from "./data"
-import { columns, StatusBadge } from "./exception-columns"
-import { SearchIcon, XIcon, FilterIcon, MoreHorizontalIcon, FileDownIcon, RefreshCwIcon, SettingsIcon, PlusIcon } from "lucide-react"
-
-interface ToolbarProps {
- searchQuery: string
- onSearchChange: (value: string) => void
- selectedStatuses: FulfillmentStatus[]
- onStatusChange: (checked: boolean, status: FulfillmentStatus) => void
- onClearFilters: () => void
- hasActiveFilters: boolean
- statusCounts: Record
-}
-
-function Toolbar({
- searchQuery,
- onSearchChange,
- selectedStatuses,
- onStatusChange,
- onClearFilters,
- hasActiveFilters,
- statusCounts,
-}: ToolbarProps) {
- return (
-
-
-
-
-
-
- onSearchChange(event.target.value)}
- />
- {searchQuery.length > 0 && (
-
- onSearchChange("")}
- >
-
-
-
- )}
-
-
-
-
-
- Status
- {selectedStatuses.length > 0 && (
-
- {selectedStatuses.length}
-
- )}
-
- }
- />
-
-
- Filter by status
-
- {STATUS_ORDER.map((status) => (
-
-
- onStatusChange(checked === true, status)
- }
- />
-
-
- ))}
-
-
-
- {hasActiveFilters && (
-
- )}
-
-
-
-
-
- Actions
-
- }
- />
-
-
-
- toast.success("Export ready", {
- description: "Exception queue export prepared.",
- })
- }
- >
-
- Export CSV
-
-
- toast.message("Queue refreshed", {
- description: "Live data would refresh through your API.",
- })
- }
- >
-
- Refresh
-
-
- toast.info("View settings", {
- description: "Column and density controls are available.",
- })
- }
- >
-
- View settings
-
-
-
-
-
- )
-}
-
-export function ExceptionGrid() {
- const [pagination, setPagination] = useState({
- pageIndex: 0,
- pageSize: 5,
- })
- const [sorting, setSorting] = useState([
- { id: "value", desc: true },
- ])
- const [searchQuery, setSearchQuery] = useState("")
- const [selectedStatuses, setSelectedStatuses] = useState(
- []
- )
- const [columnOrder, setColumnOrder] = useState(
- columns.map((column) => column.id as string)
- )
- const [columnVisibility, setColumnVisibility] = useState({
- risk: false,
- })
- const [rowSelection, setRowSelection] = useState({})
-
- const statusCounts = useMemo(
- () =>
- FULFILLMENT_ROWS.reduce(
- (acc, row) => {
- acc[row.status] = (acc[row.status] || 0) + 1
- return acc
- },
- {} as Record
- ),
- []
- )
-
- const filteredData = useMemo(() => {
- return FULFILLMENT_ROWS.filter((row) => {
- const matchesStatus =
- !selectedStatuses.length || selectedStatuses.includes(row.status)
- const matchesSearch =
- !searchQuery ||
- fulfillmentSearchBlob(row).includes(searchQuery.toLowerCase())
-
- return matchesStatus && matchesSearch
- })
- }, [searchQuery, selectedStatuses])
-
- const hasActiveFilters =
- searchQuery.trim().length > 0 || selectedStatuses.length > 0
-
- const resetToFirstPage = () => {
- setPagination((current) =>
- current.pageIndex === 0 ? current : { ...current, pageIndex: 0 }
- )
- }
-
- const handleSearchChange = (value: string) => {
- setSearchQuery(value)
- resetToFirstPage()
- }
-
- const handleStatusChange = (checked: boolean, status: FulfillmentStatus) => {
- setSelectedStatuses((current) =>
- checked ? [...current, status] : current.filter((item) => item !== status)
- )
- resetToFirstPage()
- }
-
- const handleClearFilters = () => {
- setSelectedStatuses([])
- setSearchQuery("")
- resetToFirstPage()
- }
-
- const table = useReactTable({
- columns,
- data: filteredData,
- pageCount: Math.ceil(filteredData.length / pagination.pageSize),
- getRowId: (row) => row.id,
- state: { pagination, sorting, columnOrder, columnVisibility, rowSelection },
- columnResizeMode: "onChange",
- enableRowSelection: true,
- autoResetPageIndex: false,
- onColumnOrderChange: setColumnOrder,
- onColumnVisibilityChange: setColumnVisibility,
- onPaginationChange: setPagination,
- onRowSelectionChange: setRowSelection,
- onSortingChange: setSorting,
- getCoreRowModel: getCoreRowModel(),
- getFilteredRowModel: getFilteredRowModel(),
- getPaginationRowModel: getPaginationRowModel(),
- getSortedRowModel: getSortedRowModel(),
- })
-
- return (
-
- td]:h-16",
- }}
- >
-
-
-
- Exception Queue
-
- {filteredData.length} of {FULFILLMENT_ROWS.length} fulfillment
- records
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/dashboard-1/components/navbar-actions.tsx b/apps/web/src/components/blocks/dashboard-1/components/navbar-actions.tsx
deleted file mode 100644
index 05e0e6d..0000000
--- a/apps/web/src/components/blocks/dashboard-1/components/navbar-actions.tsx
+++ /dev/null
@@ -1,246 +0,0 @@
-import { useState } from "react"
-import { format } from "date-fns"
-import { type DateRange } from "react-day-picker"
-import { toast } from "sonner"
-
-import { Button } from "@evobgp/ui/components/button"
-import { Calendar } from "@evobgp/ui/components/calendar"
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from "@evobgp/ui/components/popover"
-import { CalendarIcon, DownloadIcon } from "lucide-react"
-
-type PeriodKey = "last30" | "prev30"
-
-type ReportDateRange = {
- from: Date
- to: Date
-}
-
-type DateRangePreset = {
- id: string
- label: string
- period: PeriodKey
- range: ReportDateRange
-}
-
-const reportRange = (
- fromMonth: number,
- fromDay: number,
- toMonth: number,
- toDay: number,
- year = 2026
-): ReportDateRange => ({
- from: new Date(year, fromMonth, fromDay),
- to: new Date(year, toMonth, toDay),
-})
-
-const preset = (
- id: string,
- label: string,
- period: PeriodKey,
- range: ReportDateRange
-): DateRangePreset => ({ id, label, period, range })
-
-const LAST_30_RANGE = reportRange(4, 12, 5, 10)
-const PREVIOUS_30_RANGE = reportRange(3, 12, 4, 11)
-
-const REPORT_RANGE_PRESETS: DateRangePreset[] = [
- preset("today", "Today", "last30", reportRange(5, 10, 5, 10)),
- preset("yesterday", "Yesterday", "last30", reportRange(5, 9, 5, 9)),
- preset("last7", "Last 7 days", "last30", reportRange(5, 4, 5, 10)),
- preset("last30", "Last 30 days", "last30", LAST_30_RANGE),
- preset("monthToDate", "Month to date", "last30", reportRange(5, 1, 5, 10)),
- preset("lastMonth", "Last month", "last30", reportRange(4, 1, 4, 31)),
- preset("yearToDate", "Year to date", "last30", reportRange(0, 1, 5, 10)),
- preset("lastYear", "Last year", "prev30", reportRange(0, 1, 11, 31, 2025)),
-]
-
-const MAX_REPORT_DATE = LAST_30_RANGE.to
-
-function isSameRange(first: ReportDateRange, second: DateRange) {
- const secondFrom = second.from
- const secondTo = second.to ?? second.from
-
- return (
- Boolean(secondFrom && secondTo) &&
- first.from.getTime() === secondFrom?.getTime() &&
- first.to.getTime() === secondTo?.getTime()
- )
-}
-
-function normalizeRange(
- range: DateRange | undefined,
- fallback: ReportDateRange
-): ReportDateRange {
- if (!range?.from) return fallback
-
- const from = range.from
- const to = range.to ?? range.from
-
- return from.getTime() <= to.getTime() ? { from, to } : { from: to, to: from }
-}
-
-function formatReportRange(range: ReportDateRange) {
- return `${format(range.from, "MMM d, yyyy")} - ${format(range.to, "MMM d, yyyy")}`
-}
-
-function getPeriodForRange(range: ReportDateRange) {
- const matchingPreset = getMatchingPreset(range)
-
- if (matchingPreset) return matchingPreset.period
- return range.to.getTime() <= PREVIOUS_30_RANGE.to.getTime()
- ? "prev30"
- : "last30"
-}
-
-function getMatchingPreset(range: DateRange | undefined) {
- if (!range?.from || !range.to) return undefined
- const normalizedRange = normalizeRange(range, LAST_30_RANGE)
-
- return REPORT_RANGE_PRESETS.find((preset) =>
- isSameRange(preset.range, normalizedRange)
- )
-}
-
-function ReportDateRangePicker({
- period,
- onPeriodChange,
-}: {
- period: PeriodKey
- onPeriodChange: (value: PeriodKey) => void
-}) {
- const initialRange = period === "prev30" ? PREVIOUS_30_RANGE : LAST_30_RANGE
- const [open, setOpen] = useState(false)
- const [committedRange, setCommittedRange] =
- useState(initialRange)
- const [draftRange, setDraftRange] = useState(
- initialRange
- )
-
- const selectedPresetId = getMatchingPreset(draftRange ?? committedRange)?.id
-
- function handleOpenChange(nextOpen: boolean) {
- if (nextOpen) {
- setDraftRange(committedRange)
- }
-
- setOpen(nextOpen)
- }
-
- function handleApply() {
- const nextRange = normalizeRange(draftRange, committedRange)
-
- setCommittedRange(nextRange)
- onPeriodChange(getPeriodForRange(nextRange))
- setOpen(false)
- }
-
- return (
-
-
-
- {formatReportRange(committedRange)}
-
-
-
- }
- />
-
-
-
-
- {REPORT_RANGE_PRESETS.map((preset) => {
- const selected = selectedPresetId === preset.id
-
- return (
-
- )
- })}
-
-
-
-
-
-
-
-
-
-
-
-
-
- )
-}
-
-// Header action controls reused from the solution-agents-8 report toolbar.
-export function NavbarActions() {
- const [periodKey, setPeriodKey] = useState("last30")
-
- function handleExport() {
- toast.success("Export queued", {
- description: "Fulfillment command report is being prepared.",
- })
- }
-
- return (
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/dashboard-1/components/navbar-breadcrumb.tsx b/apps/web/src/components/blocks/dashboard-1/components/navbar-breadcrumb.tsx
deleted file mode 100644
index c69a80c..0000000
--- a/apps/web/src/components/blocks/dashboard-1/components/navbar-breadcrumb.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import {
- Breadcrumb,
- BreadcrumbItem,
- BreadcrumbLink,
- BreadcrumbList,
- BreadcrumbPage,
- BreadcrumbSeparator,
-} from "@evobgp/ui/components/breadcrumb"
-
-// Navbar breadcrumb
-
-export function NavbarBreadcrumb() {
- return (
-
-
-
- }>Home
-
-
-
-
-
- }>Operations
-
-
-
-
-
- Fulfillment
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/dashboard-1/components/navbar-presence.tsx b/apps/web/src/components/blocks/dashboard-1/components/navbar-presence.tsx
deleted file mode 100644
index 59ecbdf..0000000
--- a/apps/web/src/components/blocks/dashboard-1/components/navbar-presence.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import { useState } from "react"
-
-import {
- Avatar,
- AvatarFallback,
- AvatarGroup,
- AvatarGroupCount,
- AvatarImage,
-} from "@evobgp/ui/components/avatar"
-import { Button } from "@evobgp/ui/components/button"
-import { Input } from "@evobgp/ui/components/input"
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from "@evobgp/ui/components/popover"
-import { TEAM_EXTRA_COUNT, TEAM_MEMBERS } from "./data"
-import { UserPlusIcon } from "lucide-react"
-
-// Header presence controls with team avatars and invite action.
-
-export function NavbarPresence() {
- const [email, setEmail] = useState("")
- const [open, setOpen] = useState(false)
-
- const handleInvite = () => {
- if (!email.trim()) return
- setEmail("")
- setOpen(false)
- }
-
- return (
-
-
- {TEAM_MEMBERS.map((member, index) => (
-
-
-
- {member.initials}
-
-
- ))}
-
- +{TEAM_EXTRA_COUNT}
-
-
-
-
-
- }
- >
-
-
-
-
-
-
Invite team member
-
- setEmail(e.target.value)}
- onKeyDown={(e) => e.key === "Enter" && handleInvite()}
- />
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/dashboard-1/components/navbar.tsx b/apps/web/src/components/blocks/dashboard-1/components/navbar.tsx
deleted file mode 100644
index e8d49ab..0000000
--- a/apps/web/src/components/blocks/dashboard-1/components/navbar.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { NavbarActions } from "./navbar-actions"
-import { NavbarBreadcrumb } from "./navbar-breadcrumb"
-
-// Navbar with breadcrumb and report range actions.
-
-export function Navbar() {
- return (
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/dashboard-1/page.tsx b/apps/web/src/components/blocks/dashboard-1/page.tsx
deleted file mode 100644
index 3e1db5e..0000000
--- a/apps/web/src/components/blocks/dashboard-1/page.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { Dashboard } from "./components/dashboard"
-
-export function Page() {
- return (
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/dashboard-2/components/chart.tsx b/apps/web/src/components/blocks/dashboard-2/components/chart.tsx
deleted file mode 100644
index 88e30b2..0000000
--- a/apps/web/src/components/blocks/dashboard-2/components/chart.tsx
+++ /dev/null
@@ -1,141 +0,0 @@
-import { Area, AreaChart, ResponsiveContainer, Tooltip } from "recharts"
-
-import { Card, CardContent } from "@evobgp/ui/components/card"
-
-import { activeUsersData, customersData, revenueData } from "./data"
-
-// Business metric cards
-
-const businessCards = [
- {
- title: "Revenue",
- period: "reui.io, 28 days",
- value: "$6.2K",
- timestamp: "",
- data: revenueData,
- color: "var(--color-emerald-500)",
- gradientId: "revenueGradient",
- formatValue: (value: number) => `$${(value / 1000).toFixed(1)}K`,
- },
- {
- title: "Signups",
- period: "Last 28 days",
- value: "4,238",
- timestamp: "3h ago",
- data: customersData,
- color: "var(--color-blue-500)",
- gradientId: "customersGradient",
- formatValue: (value: number) => `${(value / 1000).toFixed(1)}K`,
- },
- {
- title: "Active Licenses",
- period: "ReUI Cloud, 28 days",
- value: "4,238",
- timestamp: "1h ago",
- data: activeUsersData,
- color: "var(--color-violet-500)",
- gradientId: "usersGradient",
- formatValue: (value: number) => `${(value / 1000).toFixed(1)}K`,
- },
-]
-
-export function Chart() {
- return (
-
-
- {businessCards.map((card) => (
-
-
- {/* Header */}
- {card.title}
-
- {/* Chart */}
-
- {/* Value */}
-
-
- {card.period}
-
-
- {card.value}
-
-
-
-
-
-
-
-
-
-
-
-
-
- {
- if (active && payload && payload.length) {
- const value = payload[0].value as number
- return (
-
-
-
- {card.title}
-
-
- {card.formatValue(value)}
-
-
-
- )
- }
- return null
- }}
- />
-
-
-
-
-
-
-
-
- ))}
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/dashboard-2/components/columns.tsx b/apps/web/src/components/blocks/dashboard-2/components/columns.tsx
deleted file mode 100644
index c75b6dd..0000000
--- a/apps/web/src/components/blocks/dashboard-2/components/columns.tsx
+++ /dev/null
@@ -1,338 +0,0 @@
-import { type ComponentProps } from "react"
-import { Badge } from "@/components/reui/badge"
-import { DataGridColumnHeader } from "@/components/reui/data-grid/data-grid-column-header"
-import { type ColumnDef } from "@tanstack/react-table"
-
-import { cn } from "@evobgp/ui/lib/utils"
-import {
- Avatar,
- AvatarFallback,
- AvatarImage,
-} from "@evobgp/ui/components/avatar"
-import { Button } from "@evobgp/ui/components/button"
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuGroup,
- DropdownMenuItem,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from "@evobgp/ui/components/dropdown-menu"
-import { type ModuleRecord, type ModuleStatus } from "./data"
-import { CalendarDaysIcon, FileTextIcon, StarIcon, MoreHorizontalIcon, CopyIcon, ArchiveIcon } from "lucide-react"
-
-export type ModuleRowAction = "open" | "favorite" | "duplicate" | "archive"
-
-const moduleStatusVariant: Record<
- ModuleStatus,
- ComponentProps["variant"]
-> = {
- Planned: "info-outline",
- Backlog: "outline",
- "In Progress": "warning-outline",
-}
-
-const moduleStatusDotClass: Record = {
- Planned: "bg-sky-500 dark:bg-sky-400",
- Backlog: "bg-muted-foreground/50",
- "In Progress": "bg-amber-500 dark:bg-amber-400",
-}
-
-function DotSeparator() {
- return (
-
- )
-}
-
-function getProgressToneClass(value: number) {
- if (value >= 75) return "text-emerald-500 dark:text-emerald-400"
- if (value >= 40) return "text-amber-500 dark:text-amber-400"
- if (value > 0) return "text-sky-500 dark:text-sky-400"
-
- return "text-muted-foreground/35"
-}
-
-function getWindowDurationLabel(module: ModuleRecord) {
- const start = new Date(module.dateStart).getTime()
- const end = new Date(module.dateEnd).getTime()
- const dayMs = 24 * 60 * 60 * 1000
- const days = Math.max(1, Math.round((end - start) / dayMs))
-
- return `${days}-day window`
-}
-
-function getCompactDateRange(module: ModuleRecord) {
- return module.dateRange.replace(/, 2026/g, "")
-}
-
-function ModuleProgress({ module }: { module: ModuleRecord }) {
- const value = module.progress
- const radius = 18
- const circumference = 2 * Math.PI * radius
- const dashOffset = circumference - (value / 100) * circumference
- const progressClassName = getProgressToneClass(value)
-
- return (
-
-
-
-
- {value}%
-
-
-
-
- {value}% ready
-
-
- {module.tasksCompleted}/{module.tasksTotal} tasks
-
-
-
- )
-}
-
-function ModuleNameCell({ module }: { module: ModuleRecord }) {
- return (
-
-
- {module.name}
-
-
-
{module.kind}
-
-
-
- {module.owner.avatar ? (
-
- ) : null}
-
- {module.owner.initials}
-
-
- {module.owner.name}
-
-
-
- {module.domain}
-
-
-
- )
-}
-
-function ModuleDateCell({ module }: { module: ModuleRecord }) {
- return (
-
-
- {getCompactDateRange(module)}
-
-
-
- {getWindowDurationLabel(module)}
-
-
- )
-}
-
-function ModuleStatusCell({ module }: { module: ModuleRecord }) {
- return (
-
-
- {module.status}
-
- )
-}
-
-function ModuleActions({
- module,
- onAction,
-}: {
- module: ModuleRecord
- onAction: (action: ModuleRowAction, module: ModuleRecord) => void
-}) {
- return (
-
-
-
-
- event.stopPropagation()}
- />
- }
- >
-
-
-
-
- onAction("duplicate", module)}>
-
- Duplicate
-
-
- onAction("archive", module)}
- >
-
- Archive
-
-
-
-
-
- )
-}
-
-export function createModuleGridColumns({
- onAction,
-}: {
- onAction: (action: ModuleRowAction, module: ModuleRecord) => void
-}): ColumnDef[] {
- return [
- {
- accessorKey: "progress",
- id: "progress",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => ,
- size: 210,
- enableSorting: true,
- enableHiding: false,
- enableResizing: false,
- meta: {
- headerTitle: "Progress",
- },
- },
- {
- accessorKey: "name",
- id: "name",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => ,
- minSize: 300,
- enableSorting: true,
- enableHiding: false,
- enableResizing: false,
- meta: {
- autoSize: true,
- headerTitle: "Module",
- },
- },
- {
- accessorKey: "dateStart",
- id: "dateStart",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => ,
- sortingFn: (rowA, rowB) =>
- new Date(rowA.original.dateStart).getTime() -
- new Date(rowB.original.dateStart).getTime(),
- size: 180,
- enableSorting: true,
- enableHiding: false,
- enableResizing: false,
- meta: {
- headerTitle: "Window",
- },
- },
- {
- accessorKey: "status",
- id: "status",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => ,
- size: 126,
- enableSorting: true,
- enableHiding: false,
- enableResizing: false,
- meta: {
- headerTitle: "Status",
- },
- },
- {
- id: "actions",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => (
-
- ),
- size: 104,
- enableSorting: false,
- enableHiding: false,
- enableResizing: false,
- meta: {
- headerTitle: "Actions",
- },
- },
- ]
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/dashboard-2/components/dashboard.tsx b/apps/web/src/components/blocks/dashboard-2/components/dashboard.tsx
deleted file mode 100644
index 3ab6773..0000000
--- a/apps/web/src/components/blocks/dashboard-2/components/dashboard.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-"use client"
-
-import { Chart } from "./chart"
-import { ModulesDataGridView } from "./data-grid-view"
-import { Navbar } from "./navbar"
-
-/**
- * ReUI operations dashboard: navbar -> metric charts -> module grid.
- * The sections are copied from reviewed donor blocks.
- * Customize: swap the records and chart series in data.tsx first.
- */
-export function Dashboard() {
- return (
-
-
-
-
-
- ReUI Operations Dashboard
-
-
- {/* Metric Charts */}
-
-
- {/* Module Grid */}
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/dashboard-2/components/data-grid-view.tsx b/apps/web/src/components/blocks/dashboard-2/components/data-grid-view.tsx
deleted file mode 100644
index 68a4ddf..0000000
--- a/apps/web/src/components/blocks/dashboard-2/components/data-grid-view.tsx
+++ /dev/null
@@ -1,380 +0,0 @@
-import { useCallback, useMemo, useState } from "react"
-import { Badge } from "@/components/reui/badge"
-import { DataGrid } from "@/components/reui/data-grid/data-grid"
-import { DataGridPagination } from "@/components/reui/data-grid/data-grid-pagination"
-import { DataGridScrollArea } from "@/components/reui/data-grid/data-grid-scroll-area"
-import {
- DataGridTable,
- DataGridTableHeader,
-} from "@/components/reui/data-grid/data-grid-table"
-import {
- getCoreRowModel,
- getPaginationRowModel,
- getSortedRowModel,
- useReactTable,
- type PaginationState,
- type SortingState,
-} from "@tanstack/react-table"
-import { toast } from "sonner"
-
-import { Button } from "@evobgp/ui/components/button"
-import {
- DropdownMenu,
- DropdownMenuCheckboxItem,
- DropdownMenuContent,
- DropdownMenuGroup,
- DropdownMenuItem,
- DropdownMenuLabel,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from "@evobgp/ui/components/dropdown-menu"
-import {
- InputGroup,
- InputGroupAddon,
- InputGroupButton,
- InputGroupInput,
-} from "@evobgp/ui/components/input-group"
-import { createModuleGridColumns, type ModuleRowAction } from "./columns"
-import {
- MODULE_RECORDS,
- MODULE_STATUS_OPTIONS,
- type ModuleRecord,
- type ModuleStatus,
-} from "./data"
-import { CircleCheckIcon, FlagIcon, ChevronRightIcon, PackageIcon, SearchIcon, XIcon, ArrowUpDownIcon, ChevronDownIcon, FilterIcon } from "lucide-react"
-
-type ModuleSort = "name" | "dateStart" | "progress" | "status"
-
-const sortLabels: Record = {
- name: "Name",
- dateStart: "Window",
- progress: "Progress",
- status: "Status",
-}
-
-const EMPTY_MODULE_MESSAGE = "No ReUI modules match the selected filters."
-
-function buildSorting(sortBy: ModuleSort): SortingState {
- return [{ id: sortBy, desc: false }]
-}
-
-function getModuleSearchBlob(module: ModuleRecord) {
- return [
- module.name,
- module.id,
- module.kind,
- module.domain,
- module.owner.name,
- module.owner.role,
- module.health,
- module.status,
- module.dateRange,
- ]
- .join(" ")
- .toLowerCase()
-}
-
-export function ModulesDataGridView() {
- const [modules, setModules] = useState(MODULE_RECORDS)
- const [searchQuery, setSearchQuery] = useState("")
- const [selectedStatuses, setSelectedStatuses] = useState([])
- const [pagination, setPagination] = useState({
- pageIndex: 0,
- pageSize: 10,
- })
- const [sortBy, setSortBy] = useState("name")
- const [sorting, setSorting] = useState(() =>
- buildSorting("name")
- )
-
- const filteredModules = useMemo(() => {
- const normalizedSearchQuery = searchQuery.trim().toLowerCase()
-
- return modules.filter((module) => {
- const matchesSearch =
- normalizedSearchQuery.length === 0 ||
- getModuleSearchBlob(module).includes(normalizedSearchQuery)
- const matchesStatus =
- selectedStatuses.length === 0 ||
- selectedStatuses.includes(module.status)
-
- return matchesSearch && matchesStatus
- })
- }, [modules, searchQuery, selectedStatuses])
-
- const activeFilterCount = selectedStatuses.length
-
- const resetPagination = useCallback(() => {
- setPagination((current) => ({
- ...current,
- pageIndex: 0,
- }))
- }, [])
-
- const handleSearchChange = useCallback(
- (value: string) => {
- setSearchQuery(value)
- resetPagination()
- },
- [resetPagination]
- )
-
- const handleStatusToggle = useCallback(
- (status: ModuleStatus, checked: boolean) => {
- setSelectedStatuses((current) => {
- if (checked) {
- return current.includes(status) ? current : [...current, status]
- }
-
- return current.filter((item) => item !== status)
- })
- resetPagination()
- },
- [resetPagination]
- )
-
- const handleSortChange = useCallback(
- (value: string) => {
- const nextSort = value as ModuleSort
- setSortBy(nextSort)
- setSorting(buildSorting(nextSort))
- resetPagination()
- },
- [resetPagination]
- )
-
- const handleModuleAction = useCallback(
- (action: ModuleRowAction, module: ModuleRecord) => {
- if (action === "favorite") {
- setModules((current) =>
- current.map((item) =>
- item.id === module.id
- ? {
- ...item,
- favorite: !item.favorite,
- }
- : item
- )
- )
- toast.success(module.favorite ? "Removed favorite" : "Module starred", {
- description: module.name,
- icon: (
-
- ),
- })
- return
- }
-
- if (action === "open") {
- toast.info("Open ReUI module", {
- description: `${module.name} (${module.kind})`,
- })
- return
- }
-
- toast.message(
- action === "duplicate" ? "Duplicate module" : "Archive module",
- {
- description: `Connect this action to your ${module.name} flow.`,
- }
- )
- },
- []
- )
-
- const handleAddModule = () => {
- toast.success("Add ReUI module", {
- description: "Open your module creation dialog.",
- icon: (
-
- ),
- })
- }
-
- const columns = useMemo(
- () => createModuleGridColumns({ onAction: handleModuleAction }),
- [handleModuleAction]
- )
-
- // eslint-disable-next-line react-hooks/incompatible-library
- const table = useReactTable({
- data: filteredModules,
- columns,
- pageCount: Math.ceil(filteredModules.length / pagination.pageSize),
- state: {
- pagination,
- sorting,
- },
- onPaginationChange: setPagination,
- onSortingChange: setSorting,
- getRowId: (row) => row.id,
- getCoreRowModel: getCoreRowModel(),
- getPaginationRowModel: getPaginationRowModel(),
- getSortedRowModel: getSortedRowModel(),
- })
-
- return (
- td]:h-16",
- }}
- >
-
-
-
-
-
ReUI
-
-
-
- Modules
-
-
-
-
-
-
-
-
- handleSearchChange(event.target.value)}
- placeholder="Search..."
- aria-label="Search modules"
- />
- {searchQuery.length > 0 ? (
-
- handleSearchChange("")}
- >
-
-
-
- ) : null}
-
-
-
-
-
- {sortLabels[sortBy]}
-
-
- }
- />
-
-
- {(["name", "dateStart", "progress", "status"] as const).map(
- (value) => (
- handleSortChange(value)}
- >
- {sortLabels[value]}
-
- )
- )}
-
-
-
-
-
-
-
- Filters
- {activeFilterCount > 0 ? (
-
- {activeFilterCount}
-
- ) : null}
-
- }
- />
-
-
- Status
- {MODULE_STATUS_OPTIONS.map((status) => (
-
- handleStatusToggle(status, checked === true)
- }
- >
- {status}
-
- ))}
-
- {activeFilterCount > 0 ? (
- <>
-
- {
- setSelectedStatuses([])
- resetPagination()
- }}
- >
- Reset filters
-
- >
- ) : null}
-
-
-
-
-
-
-
- {filteredModules.length > 0 ? (
-
-
-
- ) : (
- <>
-
-
-
-
- {EMPTY_MODULE_MESSAGE}
-
- >
- )}
-
-
- {filteredModules.length > 0 ? (
-
- ) : (
-
- 0 modules
-
- )}
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/dashboard-2/components/data.tsx b/apps/web/src/components/blocks/dashboard-2/components/data.tsx
deleted file mode 100644
index fe23604..0000000
--- a/apps/web/src/components/blocks/dashboard-2/components/data.tsx
+++ /dev/null
@@ -1,490 +0,0 @@
-export type ModuleStatus = "Planned" | "Backlog" | "In Progress"
-
-export type ModuleKind = "System" | "Feature" | "Area"
-
-export type ModuleHealth = "On Track" | "Watch" | "Blocked"
-
-export interface ModuleOwner {
- name: string
- initials: string
- role: string
- avatar?: string
-}
-
-export interface ModuleRecord {
- id: string
- name: string
- kind: ModuleKind
- owner: ModuleOwner
- domain: string
- progress: number
- tasksCompleted: number
- tasksTotal: number
- contributors: number
- blockers: number
- health: ModuleHealth
- dateStart: string
- dateEnd: string
- dateRange: string
- status: ModuleStatus
- favorite: boolean
-}
-
-export const MODULE_STATUS_OPTIONS: ModuleStatus[] = [
- "Planned",
- "Backlog",
- "In Progress",
-]
-
-const moduleOwners = {
- maya: {
- name: "Nora Vale",
- initials: "NV",
- role: "ReUI release lead",
- avatar:
- "https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=96&h=96&dpr=2&q=80",
- },
- jonah: {
- name: "Jonah Lee",
- initials: "JL",
- role: "ReUI product ops",
- avatar:
- "https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=96&h=96&dpr=2&q=80",
- },
- nina: {
- name: "Nina Santos",
- initials: "NS",
- role: "Docs owner",
- avatar:
- "https://images.unsplash.com/photo-1544005313-94ddf0286df2?w=96&h=96&dpr=2&q=80",
- },
- elijah: {
- name: "Elijah Morgan",
- initials: "EM",
- role: "License lead",
- avatar:
- "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=96&h=96&dpr=2&q=80",
- },
- priya: {
- name: "Priya Shah",
- initials: "PS",
- role: "Lifecycle PM",
- avatar:
- "https://images.unsplash.com/photo-1488426862026-3ee34a7d66df?w=96&h=96&dpr=2&q=80",
- },
- omar: {
- name: "Omar Haddad",
- initials: "OH",
- role: "Trust owner",
- avatar:
- "https://images.unsplash.com/photo-1507591064344-4c6ce005b128?w=96&h=96&dpr=2&q=80",
- },
- sofia: {
- name: "Sofia Romero",
- initials: "SR",
- role: "Content lead",
- avatar:
- "https://images.unsplash.com/photo-1517841905240-472988babdf9?w=96&h=96&dpr=2&q=80",
- },
- kenji: {
- name: "Kenji Tan",
- initials: "KT",
- role: "Platform lead",
- avatar:
- "https://images.unsplash.com/photo-1519085360753-af0119f7cbe7?w=96&h=96&dpr=2&q=80",
- },
- lena: {
- name: "Lena Wade",
- initials: "LW",
- role: "Developer tools",
- },
-} satisfies Record
-
-export const MODULE_RECORDS: ModuleRecord[] = [
- {
- id: "core-workflow",
- name: "Registry Sync",
- kind: "System",
- owner: moduleOwners.maya,
- domain: "registry.reui.io",
- progress: 25,
- tasksCompleted: 8,
- tasksTotal: 32,
- contributors: 6,
- blockers: 0,
- health: "On Track",
- dateStart: "2026-04-17",
- dateEnd: "2026-05-01",
- dateRange: "Apr 17 - May 01, 2026",
- status: "Planned",
- favorite: false,
- },
- {
- id: "onboarding-flow",
- name: "Pro Onboarding",
- kind: "Feature",
- owner: moduleOwners.jonah,
- domain: "pro.reui.io",
- progress: 0,
- tasksCompleted: 0,
- tasksTotal: 18,
- contributors: 4,
- blockers: 0,
- health: "Watch",
- dateStart: "2026-04-19",
- dateEnd: "2026-05-03",
- dateRange: "Apr 19 - May 03, 2026",
- status: "Backlog",
- favorite: false,
- },
- {
- id: "workspace-setup",
- name: "Docs Portal",
- kind: "Area",
- owner: moduleOwners.nina,
- domain: "docs.reui.io",
- progress: 0,
- tasksCompleted: 2,
- tasksTotal: 14,
- contributors: 3,
- blockers: 1,
- health: "Blocked",
- dateStart: "2026-04-21",
- dateEnd: "2026-05-05",
- dateRange: "Apr 21 - May 05, 2026",
- status: "In Progress",
- favorite: false,
- },
- {
- id: "permission-matrix",
- name: "Access Matrix",
- kind: "System",
- owner: moduleOwners.maya,
- domain: "admin.reui.io",
- progress: 42,
- tasksCompleted: 11,
- tasksTotal: 26,
- contributors: 5,
- blockers: 0,
- health: "Watch",
- dateStart: "2026-04-22",
- dateEnd: "2026-05-06",
- dateRange: "Apr 22 - May 06, 2026",
- status: "In Progress",
- favorite: true,
- },
- {
- id: "billing-rules",
- name: "License Billing",
- kind: "Feature",
- owner: moduleOwners.elijah,
- domain: "billing.reui.io",
- progress: 64,
- tasksCompleted: 21,
- tasksTotal: 33,
- contributors: 7,
- blockers: 0,
- health: "On Track",
- dateStart: "2026-04-18",
- dateEnd: "2026-05-02",
- dateRange: "Apr 18 - May 02, 2026",
- status: "In Progress",
- favorite: false,
- },
- {
- id: "notification-center",
- name: "Release Notes",
- kind: "Area",
- owner: moduleOwners.priya,
- domain: "changelog.reui.io",
- progress: 18,
- tasksCompleted: 5,
- tasksTotal: 28,
- contributors: 4,
- blockers: 2,
- health: "Blocked",
- dateStart: "2026-04-23",
- dateEnd: "2026-05-09",
- dateRange: "Apr 23 - May 09, 2026",
- status: "Backlog",
- favorite: false,
- },
- {
- id: "audit-trail",
- name: "Trust Audit",
- kind: "System",
- owner: moduleOwners.omar,
- domain: "trust.reui.io",
- progress: 76,
- tasksCompleted: 19,
- tasksTotal: 25,
- contributors: 5,
- blockers: 0,
- health: "On Track",
- dateStart: "2026-04-15",
- dateEnd: "2026-04-30",
- dateRange: "Apr 15 - Apr 30, 2026",
- status: "In Progress",
- favorite: true,
- },
- {
- id: "template-library",
- name: "Block Library",
- kind: "Feature",
- owner: moduleOwners.sofia,
- domain: "blocks.reui.io",
- progress: 33,
- tasksCompleted: 10,
- tasksTotal: 30,
- contributors: 6,
- blockers: 0,
- health: "Watch",
- dateStart: "2026-04-24",
- dateEnd: "2026-05-10",
- dateRange: "Apr 24 - May 10, 2026",
- status: "Planned",
- favorite: false,
- },
- {
- id: "integration-hub",
- name: "Integration Hub",
- kind: "Area",
- owner: moduleOwners.kenji,
- domain: "integrations.reui.io",
- progress: 58,
- tasksCompleted: 14,
- tasksTotal: 24,
- contributors: 8,
- blockers: 1,
- health: "Watch",
- dateStart: "2026-04-20",
- dateEnd: "2026-05-04",
- dateRange: "Apr 20 - May 04, 2026",
- status: "In Progress",
- favorite: false,
- },
- {
- id: "api-console",
- name: "API Console",
- kind: "Feature",
- owner: moduleOwners.lena,
- domain: "api.reui.io",
- progress: 91,
- tasksCompleted: 29,
- tasksTotal: 32,
- contributors: 4,
- blockers: 0,
- health: "On Track",
- dateStart: "2026-04-12",
- dateEnd: "2026-04-26",
- dateRange: "Apr 12 - Apr 26, 2026",
- status: "In Progress",
- favorite: true,
- },
- {
- id: "role-automation",
- name: "Role Automation",
- kind: "System",
- owner: moduleOwners.maya,
- domain: "admin.reui.io",
- progress: 12,
- tasksCompleted: 3,
- tasksTotal: 25,
- contributors: 3,
- blockers: 1,
- health: "Blocked",
- dateStart: "2026-04-25",
- dateEnd: "2026-05-12",
- dateRange: "Apr 25 - May 12, 2026",
- status: "Backlog",
- favorite: false,
- },
- {
- id: "workspace-invites",
- name: "Team Invites",
- kind: "Feature",
- owner: moduleOwners.jonah,
- domain: "teams.reui.io",
- progress: 47,
- tasksCompleted: 15,
- tasksTotal: 32,
- contributors: 5,
- blockers: 0,
- health: "Watch",
- dateStart: "2026-04-19",
- dateEnd: "2026-05-06",
- dateRange: "Apr 19 - May 06, 2026",
- status: "Planned",
- favorite: false,
- },
- {
- id: "release-checklist",
- name: "Release Checklist",
- kind: "Area",
- owner: moduleOwners.nina,
- domain: "release.reui.io",
- progress: 84,
- tasksCompleted: 26,
- tasksTotal: 31,
- contributors: 7,
- blockers: 0,
- health: "On Track",
- dateStart: "2026-04-16",
- dateEnd: "2026-05-01",
- dateRange: "Apr 16 - May 01, 2026",
- status: "In Progress",
- favorite: false,
- },
- {
- id: "reporting-digest",
- name: "Usage Digest",
- kind: "Feature",
- owner: moduleOwners.priya,
- domain: "reports.reui.io",
- progress: 5,
- tasksCompleted: 2,
- tasksTotal: 38,
- contributors: 3,
- blockers: 0,
- health: "Watch",
- dateStart: "2026-04-28",
- dateEnd: "2026-05-16",
- dateRange: "Apr 28 - May 16, 2026",
- status: "Backlog",
- favorite: false,
- },
- {
- id: "security-review",
- name: "Security Review",
- kind: "System",
- owner: moduleOwners.omar,
- domain: "trust.reui.io",
- progress: 69,
- tasksCompleted: 18,
- tasksTotal: 26,
- contributors: 6,
- blockers: 2,
- health: "Blocked",
- dateStart: "2026-04-18",
- dateEnd: "2026-05-07",
- dateRange: "Apr 18 - May 07, 2026",
- status: "In Progress",
- favorite: false,
- },
- {
- id: "help-center",
- name: "Help Center",
- kind: "Area",
- owner: moduleOwners.sofia,
- domain: "help.reui.io",
- progress: 39,
- tasksCompleted: 9,
- tasksTotal: 23,
- contributors: 4,
- blockers: 0,
- health: "On Track",
- dateStart: "2026-04-23",
- dateEnd: "2026-05-11",
- dateRange: "Apr 23 - May 11, 2026",
- status: "Planned",
- favorite: false,
- },
- {
- id: "data-retention",
- name: "Data Retention",
- kind: "System",
- owner: moduleOwners.kenji,
- domain: "privacy.reui.io",
- progress: 22,
- tasksCompleted: 7,
- tasksTotal: 32,
- contributors: 5,
- blockers: 1,
- health: "Watch",
- dateStart: "2026-04-27",
- dateEnd: "2026-05-14",
- dateRange: "Apr 27 - May 14, 2026",
- status: "Backlog",
- favorite: false,
- },
-]
-
-export const revenueData = [
- { value: 1000 },
- { value: 4500 },
- { value: 2000 },
- { value: 5200 },
- { value: 1500 },
- { value: 6100 },
- { value: 3000 },
- { value: 6800 },
- { value: 2000 },
- { value: 1000 },
- { value: 4000 },
- { value: 2000 },
- { value: 3000 },
- { value: 2000 },
- { value: 6238 },
-]
-
-export const customersData = [
- { value: 2000 },
- { value: 4500 },
- { value: 2000 },
- { value: 5200 },
- { value: 1500 },
- { value: 5100 },
- { value: 2500 },
- { value: 6800 },
- { value: 1800 },
- { value: 1000 },
- { value: 3000 },
- { value: 2000 },
- { value: 2700 },
- { value: 2000 },
- { value: 4238 },
-]
-
-export const activeUsersData = [
- { value: 2000 },
- { value: 3500 },
- { value: 2000 },
- { value: 5200 },
- { value: 1200 },
- { value: 4100 },
- { value: 3500 },
- { value: 5800 },
- { value: 2000 },
- { value: 800 },
- { value: 3000 },
- { value: 1000 },
- { value: 4000 },
- { value: 2000 },
- { value: 4238 },
-]
-
-export type TeamMember = {
- src: string
- name: string
- initials: string
-}
-
-export const TEAM_MEMBERS: TeamMember[] = [
- {
- src: "https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=96&h=96&dpr=2&q=80",
- name: "Mira Stone",
- initials: "MS",
- },
- {
- src: "https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=96&h=96&dpr=2&q=80",
- name: "Alex Johnson",
- initials: "AJ",
- },
- {
- src: "https://images.unsplash.com/photo-1519699047748-de8e457a634e?w=96&h=96&dpr=2&q=80",
- name: "Sarah Chen",
- initials: "SC",
- },
-]
-
-export const TEAM_EXTRA_COUNT = 8
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/dashboard-2/components/navbar-actions.tsx b/apps/web/src/components/blocks/dashboard-2/components/navbar-actions.tsx
deleted file mode 100644
index c65badd..0000000
--- a/apps/web/src/components/blocks/dashboard-2/components/navbar-actions.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import { toast } from "sonner"
-
-import { Button } from "@evobgp/ui/components/button"
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuGroup,
- DropdownMenuItem,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from "@evobgp/ui/components/dropdown-menu"
-import { PlusIcon, MoreHorizontalIcon, CopyIcon, Share2Icon, DownloadIcon, SettingsIcon } from "lucide-react"
-
-// Navbar actions with a primary module action and overflow menu.
-
-export function NavbarActions() {
- const handleAddModule = () => {
- toast.success("Add Module", {
- description: "Open your module creation dialog.",
- })
- }
-
- return (
-
-
-
-
-
- }
- >
-
-
-
-
-
-
-
- Copy link
-
-
-
-
- Share
-
-
-
-
- Export
-
-
-
-
-
-
- Settings
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/dashboard-2/components/navbar-breadcrumb.tsx b/apps/web/src/components/blocks/dashboard-2/components/navbar-breadcrumb.tsx
deleted file mode 100644
index 4732fbb..0000000
--- a/apps/web/src/components/blocks/dashboard-2/components/navbar-breadcrumb.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import {
- Breadcrumb,
- BreadcrumbItem,
- BreadcrumbLink,
- BreadcrumbList,
- BreadcrumbSeparator,
-} from "@evobgp/ui/components/breadcrumb"
-import { HouseIcon, LayoutDashboardIcon } from "lucide-react"
-
-// Navbar breadcrumb
-
-export function NavbarBreadcrumb() {
- return (
-
- {/* List */}
-
-
-
-
- Home
-
-
-
- /
-
-
-
-
- ReUI
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/dashboard-2/components/navbar-presence.tsx b/apps/web/src/components/blocks/dashboard-2/components/navbar-presence.tsx
deleted file mode 100644
index 799e1f3..0000000
--- a/apps/web/src/components/blocks/dashboard-2/components/navbar-presence.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-import { useState } from "react"
-
-import {
- Avatar,
- AvatarFallback,
- AvatarGroup,
- AvatarGroupCount,
- AvatarImage,
-} from "@evobgp/ui/components/avatar"
-import { Button } from "@evobgp/ui/components/button"
-import { Input } from "@evobgp/ui/components/input"
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from "@evobgp/ui/components/popover"
-import { Separator } from "@evobgp/ui/components/separator"
-import { TEAM_EXTRA_COUNT, TEAM_MEMBERS } from "./data"
-import { UserPlusIcon } from "lucide-react"
-
-// Navbar presence with team avatars and invite
-
-export function NavbarPresence() {
- const [email, setEmail] = useState("")
- const [open, setOpen] = useState(false)
-
- const handleInvite = () => {
- if (!email.trim()) return
- setEmail("")
- setOpen(false)
- }
-
- return (
-
- {/* List */}
-
- {TEAM_MEMBERS.map((member, index) => (
-
-
-
- {member.initials}
-
-
- ))}
- +{TEAM_EXTRA_COUNT}
-
-
-
-
-
- }
- >
-
- Invite
-
-
-
-
-
Invite team member
-
- setEmail(e.target.value)}
- onKeyDown={(e) => e.key === "Enter" && handleInvite()}
- />
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/dashboard-2/components/navbar.tsx b/apps/web/src/components/blocks/dashboard-2/components/navbar.tsx
deleted file mode 100644
index 065fb68..0000000
--- a/apps/web/src/components/blocks/dashboard-2/components/navbar.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { NavbarActions } from "./navbar-actions"
-import { NavbarBreadcrumb } from "./navbar-breadcrumb"
-import { NavbarPresence } from "./navbar-presence"
-
-// Navbar with breadcrumb, team presence, and actions
-
-export function Navbar() {
- return (
-
- {/* Left - breadcrumb */}
-
-
- {/* Right - team presence + actions */}
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/dashboard-2/page.tsx b/apps/web/src/components/blocks/dashboard-2/page.tsx
deleted file mode 100644
index d5980e6..0000000
--- a/apps/web/src/components/blocks/dashboard-2/page.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import { Dashboard } from "./components/dashboard"
-
-export function Page() {
- return
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/dashboard-5/components/card-dot-field.tsx b/apps/web/src/components/blocks/dashboard-5/components/card-dot-field.tsx
deleted file mode 100644
index 57c95cf..0000000
--- a/apps/web/src/components/blocks/dashboard-5/components/card-dot-field.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import { useEffect, useRef } from "react"
-
-import { cn } from "@evobgp/ui/lib/utils"
-
-// Deterministic per-dot value so the field reads as noise, not a flat grid.
-function grain(i: number, j: number) {
- const n = Math.sin(i * 127.1 + j * 311.7) * 43758.5453
- return n - Math.floor(n)
-}
-
-/**
- * Static dot field adapted from card-5's reviewed background effect.
- * The dots resolve from the current text color, so the surface stays
- * neutral and theme-aware without hard-coded colors.
- */
-export function CardDotField({ className }: { className?: string }) {
- const canvasRef = useRef(null)
-
- useEffect(() => {
- const canvas = canvasRef.current
- if (!canvas) return
- const ctx = canvas.getContext("2d")
- if (!ctx) return
-
- const GAP = 3
- const DOT = 1.5
- const BASE = 0.03
- const PEAK = 0.2
-
- const draw = () => {
- const rect = canvas.getBoundingClientRect()
- if (!rect.width || !rect.height) return
-
- const dpr = Math.min(window.devicePixelRatio || 1, 2)
- canvas.width = Math.round(rect.width * dpr)
- canvas.height = Math.round(rect.height * dpr)
-
- ctx.fillStyle = getComputedStyle(canvas).color || "rgb(115,115,115)"
- ctx.fillRect(0, 0, 1, 1)
- const px = ctx.getImageData(0, 0, 1, 1).data
- const color = `rgb(${px[0]}, ${px[1]}, ${px[2]})`
-
- ctx.setTransform(dpr, 0, 0, dpr, 0, 0)
- ctx.clearRect(0, 0, rect.width, rect.height)
- ctx.fillStyle = color
-
- const cols = Math.ceil(rect.width / GAP) + 1
- const rows = Math.ceil(rect.height / GAP) + 1
- for (let i = 0; i < cols; i++) {
- const x = i * GAP
- for (let j = 0; j < rows; j++) {
- const q = grain(i, j)
- const amp = 0.7 + 0.6 * grain(j * 2 + 1, i * 2 + 1)
- let a = (BASE + (PEAK - BASE) * q * q) * amp
- if (a > 1) a = 1
- ctx.globalAlpha = a
- ctx.fillRect(x, j * GAP, DOT, DOT)
- }
- }
- ctx.globalAlpha = 1
- }
-
- draw()
-
- const resizeObserver = new ResizeObserver(() => draw())
- resizeObserver.observe(canvas)
-
- const themeObserver = new MutationObserver(() => draw())
- themeObserver.observe(document.documentElement, {
- attributes: true,
- attributeFilter: ["class", "style"],
- })
-
- return () => {
- resizeObserver.disconnect()
- themeObserver.disconnect()
- }
- }, [])
-
- return (
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/dashboard-5/components/chart-tooltip.tsx b/apps/web/src/components/blocks/dashboard-5/components/chart-tooltip.tsx
deleted file mode 100644
index 5cb28ac..0000000
--- a/apps/web/src/components/blocks/dashboard-5/components/chart-tooltip.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import { Card, CardContent } from "@evobgp/ui/components/card"
-
-type TooltipItem = {
- value?: unknown
- name?: unknown
- color?: string
-}
-
-export function ChartTooltip({
- active,
- payload,
- label,
-}: {
- active?: boolean
- payload?: readonly TooltipItem[]
- label?: string | number
-}) {
- if (!active || !payload?.length) return null
-
- return (
-
-
-
- {label}
-
-
- {payload.map((item, index) => (
-
-
-
-
- {String(item.name)}
-
-
-
- {Number(item.value ?? 0).toLocaleString()}
-
-
- ))}
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/dashboard-5/components/data.tsx b/apps/web/src/components/blocks/dashboard-5/components/data.tsx
deleted file mode 100644
index 1e1e018..0000000
--- a/apps/web/src/components/blocks/dashboard-5/components/data.tsx
+++ /dev/null
@@ -1,200 +0,0 @@
-import { type ComponentProps } from "react"
-import { Badge } from "@/components/reui/badge"
-
-export type MetricTone = "danger" | "success" | "warning" | "info"
-
-export type MetricCard = {
- id: string
- title: string
- label: string
- value: string
- delta: string
- deltaVariant: ComponentProps["variant"]
- detail: string
- tone: MetricTone
- sparkline: number[]
-}
-
-export const metricCards: MetricCard[] = [
- {
- id: "signal-risk",
- title: "Signal Risk",
- label: "Threat Index",
- value: "Elevated",
- delta: "+12.5%",
- deltaVariant: "destructive-light",
- detail: "5 cases",
- tone: "danger",
- sparkline: [18, 21, 15, 33, 29, 35, 28, 31, 19],
- },
- {
- id: "mesh-uptime",
- title: "Mesh Uptime",
- label: "Service Health",
- value: "99.98%",
- delta: "+0.2%",
- deltaVariant: "success-light",
- detail: "58 zones",
- tone: "success",
- sparkline: [42, 40, 42, 38, 39, 41, 40, 43, 46],
- },
- {
- id: "edge-traffic",
- title: "Edge Traffic",
- label: "Scrubbed Load",
- value: "4.8 GB/s",
- delta: "-3.1%",
- deltaVariant: "warning-light",
- detail: "clean flow",
- tone: "warning",
- sparkline: [21, 27, 28, 34, 32, 35, 33, 31, 34],
- },
- {
- id: "sensor-reach",
- title: "Sensor Reach",
- label: "Global Nodes",
- value: "18,420",
- delta: "+6.8%",
- deltaVariant: "success-light",
- detail: "93 regions",
- tone: "success",
- sparkline: [25, 23, 29, 28, 26, 31, 27, 30, 29],
- },
-]
-
-export const threatVectors = [
- { name: "Bot", blocked: 34, watched: 62 },
- { name: "Phish", blocked: 30, watched: 74 },
- { name: "DDoS", blocked: 52, watched: 33 },
- { name: "Inject", blocked: 22, watched: 48 },
- { name: "Auth", blocked: 43, watched: 68 },
- { name: "Probe", blocked: 18, watched: 58 },
- { name: "Exfil", blocked: 56, watched: 35 },
- { name: "Beacon", blocked: 38, watched: 64 },
- { name: "Day0", blocked: 14, watched: 28 },
-]
-
-export const networkFlow = [
- { month: "January", api: 1820, webhook: 1640 },
- { month: "February", api: 2340, webhook: 2160 },
- { month: "March", api: 1960, webhook: 1880 },
- { month: "April", api: 2780, webhook: 2540 },
- { month: "May", api: 2100, webhook: 1920 },
- { month: "June", api: 3120, webhook: 2880 },
- { month: "July", api: 2540, webhook: 2320 },
- { month: "August", api: 3480, webhook: 3160 },
- { month: "September", api: 2860, webhook: 2580 },
- { month: "October", api: 2420, webhook: 2140 },
- { month: "November", api: 3240, webhook: 2960 },
- { month: "December", api: 2680, webhook: 2440 },
-]
-
-export const networkFlowSummary = {
- change: "+12.8%",
- year: "2026",
-}
-
-const loadSamples = [
- 72, 68, 22, 18, 61, 71, 20, 26, 31, 70, 46, 88, 39, 25, 12, 33, 18, 28, 42,
- 36, 61, 68, 74, 70, 91, 32, 82, 66, 52, 76, 48, 35, 70, 62, 57, 49, 37, 58,
- 71, 7, 11, 69, 34, 28, 40, 61, 17, 55, 64, 19, 63, 67,
-] as const
-
-export type LoadState = "nominal" | "warm" | "critical"
-
-export type LoadPoint = {
- node: string
- load: number
- state: LoadState
-}
-
-export const loadDistribution: LoadPoint[] = loadSamples.map((load, index) => ({
- node: `N${String(index).padStart(2, "0")}`,
- load,
- state: load >= 86 ? "critical" : load >= 76 ? "warm" : "nominal",
-}))
-
-const loadStateTotals = loadDistribution.reduce>(
- (totals, point) => ({
- ...totals,
- [point.state]: totals[point.state] + 1,
- }),
- {
- nominal: 0,
- warm: 0,
- critical: 0,
- }
-)
-
-export const clusterLoadSummary = {
- details: [
- {
- label: "Nodes",
- value: String(loadDistribution.length),
- variant: "secondary",
- },
- {
- label: "Warm",
- value: String(loadStateTotals.warm),
- variant: "warning-light",
- },
- {
- label: "Critical",
- value: String(loadStateTotals.critical),
- variant: "destructive-light",
- },
- ],
-} satisfies {
- details: Array<{
- label: string
- value: string
- variant: ComponentProps["variant"]
- }>
-}
-
-export const activeThreats = [
- {
- id: "00",
- label: "API Flood",
- source: "Edge WAF",
- state: "Mitigating",
- value: 4521,
- progress: 92,
- tone: "danger",
- },
- {
- id: "01",
- label: "Mail Spoof",
- source: "Mail Relay",
- state: "Reviewing",
- value: 3102,
- progress: 64,
- tone: "warning",
- },
- {
- id: "02",
- label: "Cloud Probe",
- source: "Cloud API",
- state: "Queued",
- value: 1250,
- progress: 26,
- tone: "info",
- },
- {
- id: "03",
- label: "Mesh Beacon",
- source: "Int Node",
- state: "Watching",
- value: 420,
- progress: 9,
- tone: "success",
- },
-] satisfies Array<{
- id: string
- label: string
- source: string
- state: string
- value: number
- progress: number
- tone: MetricTone
-}>
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/dashboard-5/components/load-panels.tsx b/apps/web/src/components/blocks/dashboard-5/components/load-panels.tsx
deleted file mode 100644
index 3bf8929..0000000
--- a/apps/web/src/components/blocks/dashboard-5/components/load-panels.tsx
+++ /dev/null
@@ -1,182 +0,0 @@
-import { Badge } from "@/components/reui/badge"
-import {
- Bar,
- BarChart,
- CartesianGrid,
- Cell,
- ResponsiveContainer,
- Tooltip,
- XAxis,
- YAxis,
-} from "recharts"
-
-import { cn } from "@evobgp/ui/lib/utils"
-import { Card, CardContent } from "@evobgp/ui/components/card"
-
-import { ChartTooltip } from "./chart-tooltip"
-import { activeThreats, clusterLoadSummary, loadDistribution } from "./data"
-import { PanelCorners, PanelHeading } from "./panel-heading"
-import { loadStateColor, toneStyles } from "./tone-styles"
-
-const chartGridProps = {
- vertical: false,
- stroke: "var(--border)",
- strokeDasharray: "3 3",
- strokeOpacity: 0.75,
-}
-
-export function LoadPanel() {
- return (
-
-
-
-
-
-
Cluster Load
-
-
-
- Normal
-
-
-
- Warm
-
-
-
- Critical
-
-
-
-
- {clusterLoadSummary.details.map((detail) => (
-
-
- {detail.value}
-
- {detail.label}
-
- ))}
-
-
-
-
-
-
-
-
-
- (
-
- )}
- />
-
- {loadDistribution.map((entry) => (
- |
- ))}
-
-
-
-
-
-
- )
-}
-
-export function ActiveThreatsPanel() {
- return (
-
-
-
-
-
-
- {activeThreats.map((threat) => {
- const tone = toneStyles[threat.tone]
-
- return (
-
-
-
-
-
-
- [{threat.id}] {threat.label}
-
-
-
- {threat.source}
-
- {threat.state}
-
-
-
- {threat.value.toLocaleString()}
-
-
-
-
-
- {threat.progress}%
-
-
-
- )
- })}
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/dashboard-5/components/metric-tile.tsx b/apps/web/src/components/blocks/dashboard-5/components/metric-tile.tsx
deleted file mode 100644
index 81cc71c..0000000
--- a/apps/web/src/components/blocks/dashboard-5/components/metric-tile.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-import { Badge } from "@/components/reui/badge"
-
-import { Card, CardContent } from "@evobgp/ui/components/card"
-
-import { CardDotField } from "@/components/dashboard/card-dot-field"
-import { type MetricCard } from "./data"
-import { PanelCorners } from "./panel-heading"
-import { toneStyles } from "./tone-styles"
-
-function Sparkline({
- values,
- color,
-}: {
- values: readonly number[]
- color: string
-}) {
- const min = Math.min(...values)
- const max = Math.max(...values)
- const spread = Math.max(1, max - min)
- const points = values
- .map((value, index) => {
- const x = (index / (values.length - 1)) * 72
- const y = 28 - ((value - min) / spread) * 22
- return `${x.toFixed(1)},${y.toFixed(1)}`
- })
- .join(" ")
-
- return (
-
- )
-}
-
-export function MetricTile({ metric }: { metric: MetricCard }) {
- const tone = toneStyles[metric.tone]
-
- return (
-
-
-
-
-
-
- {metric.title}
-
-
- {metric.label}
-
-
-
-
-
-
- {metric.value}
-
-
-
- {metric.delta}
-
-
- {metric.detail}
-
-
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/dashboard-5/components/navbar-actions.tsx b/apps/web/src/components/blocks/dashboard-5/components/navbar-actions.tsx
deleted file mode 100644
index fb8d776..0000000
--- a/apps/web/src/components/blocks/dashboard-5/components/navbar-actions.tsx
+++ /dev/null
@@ -1,248 +0,0 @@
-import { useState } from "react"
-import { format } from "date-fns"
-import { type DateRange } from "react-day-picker"
-import { toast } from "sonner"
-
-import { Button } from "@evobgp/ui/components/button"
-import { Calendar } from "@evobgp/ui/components/calendar"
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from "@evobgp/ui/components/popover"
-import { CalendarIcon, ShieldCheckIcon, DownloadIcon } from "lucide-react"
-
-type PeriodKey = "last30" | "prev30"
-
-type ReportDateRange = {
- from: Date
- to: Date
-}
-
-type DateRangePreset = {
- id: string
- label: string
- period: PeriodKey
- range: ReportDateRange
-}
-
-const reportRange = (
- fromMonth: number,
- fromDay: number,
- toMonth: number,
- toDay: number,
- year = 2026
-): ReportDateRange => ({
- from: new Date(year, fromMonth, fromDay),
- to: new Date(year, toMonth, toDay),
-})
-
-const preset = (
- id: string,
- label: string,
- period: PeriodKey,
- range: ReportDateRange
-): DateRangePreset => ({ id, label, period, range })
-
-const LAST_30_RANGE = reportRange(4, 12, 5, 10)
-const PREVIOUS_30_RANGE = reportRange(3, 12, 4, 11)
-
-const REPORT_RANGE_PRESETS: DateRangePreset[] = [
- preset("today", "Today", "last30", reportRange(5, 10, 5, 10)),
- preset("yesterday", "Yesterday", "last30", reportRange(5, 9, 5, 9)),
- preset("last7", "Last 7 days", "last30", reportRange(5, 4, 5, 10)),
- preset("last30", "Last 30 days", "last30", LAST_30_RANGE),
- preset("monthToDate", "Month to date", "last30", reportRange(5, 1, 5, 10)),
- preset("lastMonth", "Last month", "last30", reportRange(4, 1, 4, 31)),
- preset("yearToDate", "Year to date", "last30", reportRange(0, 1, 5, 10)),
- preset("lastYear", "Last year", "prev30", reportRange(0, 1, 11, 31, 2025)),
-]
-
-const MAX_REPORT_DATE = LAST_30_RANGE.to
-
-function isSameRange(first: ReportDateRange, second: DateRange) {
- const secondFrom = second.from
- const secondTo = second.to ?? second.from
-
- return (
- Boolean(secondFrom && secondTo) &&
- first.from.getTime() === secondFrom?.getTime() &&
- first.to.getTime() === secondTo?.getTime()
- )
-}
-
-function normalizeRange(
- range: DateRange | undefined,
- fallback: ReportDateRange
-): ReportDateRange {
- if (!range?.from) return fallback
-
- const from = range.from
- const to = range.to ?? range.from
-
- return from.getTime() <= to.getTime() ? { from, to } : { from: to, to: from }
-}
-
-function formatReportRange(range: ReportDateRange) {
- return `${format(range.from, "MMM d, yyyy")} - ${format(range.to, "MMM d, yyyy")}`
-}
-
-function getPeriodForRange(range: ReportDateRange) {
- const matchingPreset = getMatchingPreset(range)
-
- if (matchingPreset) return matchingPreset.period
- return range.to.getTime() <= PREVIOUS_30_RANGE.to.getTime()
- ? "prev30"
- : "last30"
-}
-
-function getMatchingPreset(range: DateRange | undefined) {
- if (!range?.from || !range.to) return undefined
- const normalizedRange = normalizeRange(range, LAST_30_RANGE)
-
- return REPORT_RANGE_PRESETS.find((preset) =>
- isSameRange(preset.range, normalizedRange)
- )
-}
-
-function ReportDateRangePicker({
- period,
- onPeriodChange,
-}: {
- period: PeriodKey
- onPeriodChange: (value: PeriodKey) => void
-}) {
- const initialRange = period === "prev30" ? PREVIOUS_30_RANGE : LAST_30_RANGE
- const [open, setOpen] = useState(false)
- const [committedRange, setCommittedRange] =
- useState(initialRange)
- const [draftRange, setDraftRange] = useState(
- initialRange
- )
-
- const selectedPresetId = getMatchingPreset(draftRange ?? committedRange)?.id
-
- function handleOpenChange(nextOpen: boolean) {
- if (nextOpen) {
- setDraftRange(committedRange)
- }
-
- setOpen(nextOpen)
- }
-
- function handleApply() {
- const nextRange = normalizeRange(draftRange, committedRange)
-
- setCommittedRange(nextRange)
- onPeriodChange(getPeriodForRange(nextRange))
- setOpen(false)
- }
-
- return (
-
-
-
- {formatReportRange(committedRange)}
-
-
-
- }
- />
-
-
-
-
- {REPORT_RANGE_PRESETS.map((preset) => {
- const selected = selectedPresetId === preset.id
-
- return (
-
- )
- })}
-
-
-
-
-
-
-
-
-
-
-
-
-
- )
-}
-
-export function NavbarActions() {
- const [periodKey, setPeriodKey] = useState("last30")
-
- function handleExport() {
- toast.success("Export queued", {
- description: "Security telemetry report is being prepared.",
- icon: (
-
- ),
- })
- }
-
- return (
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/dashboard-5/components/navbar-breadcrumb.tsx b/apps/web/src/components/blocks/dashboard-5/components/navbar-breadcrumb.tsx
deleted file mode 100644
index eac5435..0000000
--- a/apps/web/src/components/blocks/dashboard-5/components/navbar-breadcrumb.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import {
- Breadcrumb,
- BreadcrumbItem,
- BreadcrumbLink,
- BreadcrumbList,
- BreadcrumbPage,
- BreadcrumbSeparator,
-} from "@evobgp/ui/components/breadcrumb"
-
-export function NavbarBreadcrumb() {
- return (
-
-
-
- }>Home
-
-
-
-
-
- }>Security
-
-
-
-
-
- Telemetry
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/dashboard-5/components/navbar.tsx b/apps/web/src/components/blocks/dashboard-5/components/navbar.tsx
deleted file mode 100644
index c08b3e9..0000000
--- a/apps/web/src/components/blocks/dashboard-5/components/navbar.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { NavbarActions } from "./navbar-actions"
-import { NavbarBreadcrumb } from "./navbar-breadcrumb"
-
-export function Navbar() {
- return (
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/dashboard-5/components/network-panels.tsx b/apps/web/src/components/blocks/dashboard-5/components/network-panels.tsx
deleted file mode 100644
index 818268c..0000000
--- a/apps/web/src/components/blocks/dashboard-5/components/network-panels.tsx
+++ /dev/null
@@ -1,305 +0,0 @@
-import { Badge } from "@/components/reui/badge"
-import {
- Area,
- AreaChart,
- Bar,
- BarChart,
- CartesianGrid,
- XAxis,
- YAxis,
-} from "recharts"
-
-import { Card, CardContent } from "@evobgp/ui/components/card"
-import {
- ChartContainer,
- ChartTooltip,
- ChartTooltipContent,
- type ChartConfig,
-} from "@evobgp/ui/components/chart"
-
-import { networkFlow, networkFlowSummary, threatVectors } from "./data"
-import { PanelCorners, PanelHeading } from "./panel-heading"
-
-const threatChartConfig = {
- blocked: {
- label: "Blocked",
- color: "var(--color-blue-600)",
- },
- watched: {
- label: "Watched",
- color: "var(--color-sky-300)",
- },
-} satisfies ChartConfig
-
-const flowChartConfig = {
- api: {
- label: "API Calls",
- color: "var(--color-yellow-500)",
- },
- webhook: {
- label: "Webhooks",
- color: "var(--color-emerald-500)",
- },
-} satisfies ChartConfig
-
-const chartGridProps = {
- vertical: false,
- stroke: "var(--border)",
- strokeDasharray: "3 3",
- strokeOpacity: 0.75,
-}
-
-function getChartColor(config: ChartConfig, name: string | number) {
- return config[String(name)]?.color
-}
-
-function getChartLabel(config: ChartConfig, name: string | number) {
- return config[String(name)]?.label ?? String(name)
-}
-
-function formatTooltipItem(
- config: ChartConfig,
- value: unknown,
- name: string | number
-) {
- return (
-
-
-
-
- {getChartLabel(config, name)}
-
-
-
- {Number(value ?? 0).toLocaleString()}
-
-
- )
-}
-
-function CrosshatchPattern({
- config,
- idPrefix,
-}: {
- config: ChartConfig
- idPrefix: string
-}) {
- const entries = Object.entries(config).filter(([, value]) => value.color)
-
- return (
- <>
- {entries.map(([key, { color }]) => (
-
-
-
-
- ))}
- >
- )
-}
-
-export function ThreatVectorsPanel() {
- return (
-
-
-
-
-
-
-
-
- Blocked
-
-
-
- Watched
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- (
-
- {value}
-
- )}
- formatter={(value, name) =>
- formatTooltipItem(threatChartConfig, value, name)
- }
- />
- }
- />
-
-
-
-
-
-
- )
-}
-
-export function NetworkFlowPanel() {
- return (
-
-
-
-
-
-
- {networkFlowSummary.change}
-
-
-
-
-
-
- String(value).slice(0, 3)}
- />
-
- (
-
-
- {value} {networkFlowSummary.year}
-
-
- )}
- formatter={(value, name) =>
- formatTooltipItem(flowChartConfig, value, name)
- }
- />
- }
- />
-
-
-
-
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/dashboard-5/components/panel-heading.tsx b/apps/web/src/components/blocks/dashboard-5/components/panel-heading.tsx
deleted file mode 100644
index d7ce4a1..0000000
--- a/apps/web/src/components/blocks/dashboard-5/components/panel-heading.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-export function PanelCorners() {
- return (
- <>
-
-
- >
- )
-}
-
-export function PanelHeading({
- title,
- description,
-}: {
- title: string
- description: string
-}) {
- return (
-
-
{title}
-
{description}
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/dashboard-5/components/security-dashboard.tsx b/apps/web/src/components/blocks/dashboard-5/components/security-dashboard.tsx
deleted file mode 100644
index 9b308b2..0000000
--- a/apps/web/src/components/blocks/dashboard-5/components/security-dashboard.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import { metricCards } from "./data"
-import { ActiveThreatsPanel, LoadPanel } from "./load-panels"
-import { MetricTile } from "./metric-tile"
-import { Navbar } from "./navbar"
-import { NetworkFlowPanel, ThreatVectorsPanel } from "./network-panels"
-
-/**
- * Dense edge security dashboard inspired by a telemetry wall.
- * The main entry only owns section order; records and panel details stay
- * in focused local files so the block remains easy to adapt.
- */
-export function SecurityDashboard() {
- return (
-
-
- Edge Security Telemetry
-
-
-
-
-
- {metricCards.map((metric) => (
-
- ))}
-
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/dashboard-5/components/tone-styles.ts b/apps/web/src/components/blocks/dashboard-5/components/tone-styles.ts
deleted file mode 100644
index 154b587..0000000
--- a/apps/web/src/components/blocks/dashboard-5/components/tone-styles.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { type LoadState, type MetricTone } from "./data"
-
-export const toneStyles: Record<
- MetricTone,
- {
- dot: string
- stroke: string
- bar: string
- }
-> = {
- danger: {
- dot: "bg-red-500",
- stroke: "var(--color-red-500)",
- bar: "bg-red-500",
- },
- success: {
- dot: "bg-emerald-500",
- stroke: "var(--color-emerald-500)",
- bar: "bg-emerald-500",
- },
- warning: {
- dot: "bg-amber-500",
- stroke: "var(--color-amber-500)",
- bar: "bg-amber-500",
- },
- info: {
- dot: "bg-blue-500",
- stroke: "var(--color-blue-500)",
- bar: "bg-blue-500",
- },
-}
-
-export const loadStateColor: Record = {
- nominal: "var(--color-zinc-300)",
- warm: "var(--color-amber-500)",
- critical: "var(--color-red-500)",
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/dashboard-5/page.tsx b/apps/web/src/components/blocks/dashboard-5/page.tsx
deleted file mode 100644
index 55cf2fb..0000000
--- a/apps/web/src/components/blocks/dashboard-5/page.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import { SecurityDashboard } from "./components/security-dashboard"
-
-export function Page() {
- return (
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/data-grid-base-6/components/columns.tsx b/apps/web/src/components/blocks/data-grid-base-6/components/columns.tsx
deleted file mode 100644
index cec1ac1..0000000
--- a/apps/web/src/components/blocks/data-grid-base-6/components/columns.tsx
+++ /dev/null
@@ -1,241 +0,0 @@
-import { DataGridColumnHeader } from "@/components/reui/data-grid/data-grid-column-header"
-import { type ColumnDef } from "@tanstack/react-table"
-
-import { cn } from "@evobgp/ui/lib/utils"
-import {
- Avatar,
- AvatarFallback,
- AvatarImage,
-} from "@evobgp/ui/components/avatar"
-import { Button } from "@evobgp/ui/components/button"
-import { type MemberBillingStatus, type MemberRecord } from "./data"
-import { EyeIcon, PencilIcon, Trash2Icon } from "lucide-react"
-
-export type MemberRowAction = "view" | "edit" | "delete"
-
-const billingStatusDotClass: Record = {
- Active: "bg-emerald-500",
- "Pending invoice": "bg-sky-500",
- "Manual review": "bg-amber-500",
- "Past due": "bg-rose-500",
-}
-
-function MemberRowActions({
- member,
- onAction,
-}: {
- member: MemberRecord
- onAction: (action: MemberRowAction, member: MemberRecord) => void
-}) {
- return (
-
-
-
-
-
- )
-}
-
-function MemberCell({
- member,
- onAction,
-}: {
- member: MemberRecord
- onAction: (action: MemberRowAction, member: MemberRecord) => void
-}) {
- return (
-
-
-
- {member.initials}
-
-
-
-
{member.fullName}
-
- {member.displayName}
-
-
-
- {/* Actions */}
-
-
- )
-}
-
-function BillingStatusCell({
- billingStatus,
-}: {
- billingStatus: MemberBillingStatus
-}) {
- return (
-
-
- {billingStatus}
-
- )
-}
-
-export function createMembersGridColumns({
- onAction,
-}: {
- onAction: (action: MemberRowAction, member: MemberRecord) => void
-}): ColumnDef[] {
- return [
- {
- accessorKey: "fullName",
- id: "fullName",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => (
-
- ),
- size: 255,
- enableSorting: true,
- enableHiding: false,
- enableResizing: false,
- meta: {
- headerTitle: "Member",
- },
- },
- {
- accessorKey: "email",
- id: "email",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => (
-
- {row.original.email}
-
- ),
- size: 220,
- enableSorting: true,
- enableHiding: false,
- enableResizing: false,
- meta: {
- headerTitle: "Email",
- },
- },
- {
- accessorKey: "role",
- id: "role",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => (
- {row.original.role}
- ),
- size: 120,
- enableSorting: true,
- enableHiding: false,
- enableResizing: false,
- meta: {
- headerTitle: "Role",
- },
- },
- {
- accessorKey: "billingStatus",
- id: "billingStatus",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => (
-
- ),
- size: 150,
- enableSorting: true,
- enableHiding: false,
- enableResizing: false,
- meta: {
- headerTitle: "Billing status",
- },
- },
- {
- accessorKey: "authProvider",
- id: "authProvider",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => (
-
- {row.original.authProvider}
-
- ),
- size: 130,
- enableSorting: true,
- enableHiding: false,
- enableResizing: false,
- meta: {
- headerTitle: "Authentication",
- },
- },
- {
- accessorKey: "joinedAt",
- id: "joinedAt",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => (
-
- {row.original.joinedAt}
-
- ),
- sortingFn: (rowA, rowB) =>
- new Date(rowA.original.joinedAt).getTime() -
- new Date(rowB.original.joinedAt).getTime(),
- size: 130,
- enableSorting: true,
- enableHiding: false,
- enableResizing: false,
- meta: {
- headerTitle: "Joining date",
- },
- },
- ]
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/data-grid-base-6/components/data-grid-view.tsx b/apps/web/src/components/blocks/data-grid-base-6/components/data-grid-view.tsx
deleted file mode 100644
index 1d1696d..0000000
--- a/apps/web/src/components/blocks/data-grid-base-6/components/data-grid-view.tsx
+++ /dev/null
@@ -1,426 +0,0 @@
-import { useCallback, useMemo, useState } from "react"
-import { Badge } from "@/components/reui/badge"
-import { DataGrid } from "@/components/reui/data-grid/data-grid"
-import { DataGridPagination } from "@/components/reui/data-grid/data-grid-pagination"
-import { DataGridScrollArea } from "@/components/reui/data-grid/data-grid-scroll-area"
-import { DataGridTable } from "@/components/reui/data-grid/data-grid-table"
-import {
- getCoreRowModel,
- getPaginationRowModel,
- getSortedRowModel,
- useReactTable,
- type PaginationState,
- type SortingState,
-} from "@tanstack/react-table"
-import { toast } from "sonner"
-
-import { Button } from "@evobgp/ui/components/button"
-import {
- DropdownMenu,
- DropdownMenuCheckboxItem,
- DropdownMenuContent,
- DropdownMenuGroup,
- DropdownMenuItem,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from "@evobgp/ui/components/dropdown-menu"
-import {
- InputGroup,
- InputGroupAddon,
- InputGroupButton,
- InputGroupInput,
-} from "@evobgp/ui/components/input-group"
-import { Separator } from "@evobgp/ui/components/separator"
-import {
- Tabs,
- TabsContent,
- TabsList,
- TabsTrigger,
-} from "@evobgp/ui/components/tabs"
-import { createMembersGridColumns, type MemberRowAction } from "./columns"
-import {
- MEMBER_AUTH_PROVIDER_OPTIONS,
- MEMBER_BILLING_STATUS_OPTIONS,
- MEMBER_RECORDS,
- MEMBER_ROLE_OPTIONS,
- type FilterOption,
- type MemberAuthProvider,
- type MemberBillingStatus,
- type MemberRecord,
- type MemberRole,
-} from "./data"
-import { SearchIcon, XIcon, ChevronDownIcon } from "lucide-react"
-
-type FilterTabContentProps = {
- options: readonly FilterOption[]
- selectedValues: T[]
- onToggle: (value: T, checked: boolean) => void
-}
-
-function getMemberSearchBlob(member: MemberRecord) {
- return [
- member.fullName,
- member.displayName,
- member.email,
- member.role,
- member.billingStatus,
- member.authProvider,
- member.joinedAt,
- ]
- .join(" ")
- .toLowerCase()
-}
-
-function toggleFilterValue(
- current: T[],
- value: T,
- checked: boolean
-) {
- if (checked) {
- return current.includes(value) ? current : [...current, value]
- }
-
- return current.filter((item) => item !== value)
-}
-
-function FilterTabContent({
- options,
- selectedValues,
- onToggle,
-}: FilterTabContentProps) {
- return (
-
- {options.map((option) => (
-
- onToggle(option.value, checked === true)
- }
- >
- {option.label}
-
- ))}
-
- )
-}
-
-export function MembersDataGridView() {
- const [pagination, setPagination] = useState({
- pageIndex: 0,
- pageSize: 5,
- })
- const [sorting, setSorting] = useState([
- { id: "fullName", desc: false },
- ])
- const [searchQuery, setSearchQuery] = useState("")
- const [selectedRoles, setSelectedRoles] = useState([])
- const [selectedBillingStatuses, setSelectedBillingStatuses] = useState<
- MemberBillingStatus[]
- >([])
- const [selectedAuthProviders, setSelectedAuthProviders] = useState<
- MemberAuthProvider[]
- >([])
-
- const filteredData = useMemo(() => {
- const normalizedSearchQuery = searchQuery.trim().toLowerCase()
-
- return MEMBER_RECORDS.filter((member) => {
- const matchesSearch =
- normalizedSearchQuery.length === 0 ||
- getMemberSearchBlob(member).includes(normalizedSearchQuery)
- const matchesRole =
- selectedRoles.length === 0 || selectedRoles.includes(member.role)
- const matchesBillingStatus =
- selectedBillingStatuses.length === 0 ||
- selectedBillingStatuses.includes(member.billingStatus)
- const matchesAuthProvider =
- selectedAuthProviders.length === 0 ||
- selectedAuthProviders.includes(member.authProvider)
-
- return (
- matchesSearch &&
- matchesRole &&
- matchesBillingStatus &&
- matchesAuthProvider
- )
- })
- }, [
- searchQuery,
- selectedAuthProviders,
- selectedBillingStatuses,
- selectedRoles,
- ])
-
- const activeFilterCount =
- selectedRoles.length +
- selectedBillingStatuses.length +
- selectedAuthProviders.length
-
- const handleMemberAction = useCallback(
- (action: MemberRowAction, member: MemberRecord) => {
- if (action === "view") {
- toast.info("Open member", {
- description: `Review ${member.fullName}'s profile and recent activity.`,
- })
- return
- }
-
- if (action === "edit") {
- toast.message("Edit member", {
- description: `Open the access and profile editor for ${member.fullName}.`,
- })
- return
- }
-
- toast.message("Delete member", {
- description: `Wire this action to your member removal flow for ${member.fullName}.`,
- })
- },
- []
- )
-
- const columns = useMemo(
- () => createMembersGridColumns({ onAction: handleMemberAction }),
- [handleMemberAction]
- )
-
- const table = useReactTable({
- columns,
- data: filteredData,
- pageCount: Math.ceil(filteredData.length / pagination.pageSize),
- getRowId: (row) => row.id,
- state: {
- pagination,
- sorting,
- },
- onPaginationChange: setPagination,
- onSortingChange: setSorting,
- getCoreRowModel: getCoreRowModel(),
- getPaginationRowModel: getPaginationRowModel(),
- getSortedRowModel: getSortedRowModel(),
- })
-
- const handleSearchChange = (value: string) => {
- setSearchQuery(value)
- setPagination((current) => ({
- ...current,
- pageIndex: 0,
- }))
- }
-
- const handleRoleToggle = (value: MemberRole, checked: boolean) => {
- setSelectedRoles((current) => toggleFilterValue(current, value, checked))
- setPagination((current) => ({
- ...current,
- pageIndex: 0,
- }))
- }
-
- const handleBillingStatusToggle = (
- value: MemberBillingStatus,
- checked: boolean
- ) => {
- setSelectedBillingStatuses((current) =>
- toggleFilterValue(current, value, checked)
- )
- setPagination((current) => ({
- ...current,
- pageIndex: 0,
- }))
- }
-
- const handleAuthProviderToggle = (
- value: MemberAuthProvider,
- checked: boolean
- ) => {
- setSelectedAuthProviders((current) =>
- toggleFilterValue(current, value, checked)
- )
- setPagination((current) => ({
- ...current,
- pageIndex: 0,
- }))
- }
-
- const handleResetFilters = () => {
- setSelectedRoles([])
- setSelectedBillingStatuses([])
- setSelectedAuthProviders([])
- setPagination((current) => ({
- ...current,
- pageIndex: 0,
- }))
- }
-
- const handleImport = () => {
- toast.message("Import members", {
- description:
- "Connect this button to your CSV import, directory sync, or SCIM provisioning flow.",
- })
- }
-
- const handleAddMember = () => {
- toast.message("Add member", {
- description:
- "Open your invite drawer or provisioning dialog from this primary action.",
- })
- }
-
- return (
-
- {/* Heading */}
-
-
-
-
- Members
-
-
- {filteredData.length}
-
-
-
-
-
-
-
-
- handleSearchChange(event.target.value)}
- />
- {searchQuery.length > 0 ? (
-
- handleSearchChange("")}
- >
-
-
-
- ) : null}
-
-
-
-
- Filters
- {activeFilterCount > 0 ? (
-
- {activeFilterCount}
-
- ) : null}
-
-
- }
- />
-
-
-
-
- Role
- Billing
- Auth
-
-
-
-
-
-
-
-
-
-
-
-
- {activeFilterCount > 0 ? (
- <>
-
-
- Reset filters
-
- >
- ) : null}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {filteredData.length > 0 ? (
-
- ) : (
-
0 members
- )}
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/data-grid-base-6/components/data.tsx b/apps/web/src/components/blocks/data-grid-base-6/components/data.tsx
deleted file mode 100644
index ac2a220..0000000
--- a/apps/web/src/components/blocks/data-grid-base-6/components/data.tsx
+++ /dev/null
@@ -1,154 +0,0 @@
-export type FilterOption = {
- value: T
- label: string
-}
-
-export const MEMBER_ROLE_OPTIONS = [
- { value: "Admin", label: "Admin" },
- { value: "Finance", label: "Finance" },
- { value: "Support", label: "Support" },
- { value: "Operations", label: "Operations" },
- { value: "Product", label: "Product" },
- { value: "Security", label: "Security" },
- { value: "Growth", label: "Growth" },
-] as const satisfies readonly FilterOption[]
-
-export const MEMBER_BILLING_STATUS_OPTIONS = [
- { value: "Active", label: "Active" },
- { value: "Pending invoice", label: "Pending invoice" },
- { value: "Manual review", label: "Manual review" },
- { value: "Past due", label: "Past due" },
-] as const satisfies readonly FilterOption[]
-
-export const MEMBER_AUTH_PROVIDER_OPTIONS = [
- { value: "Google", label: "Google" },
- { value: "GitHub", label: "GitHub" },
- { value: "Okta SSO", label: "Okta SSO" },
- { value: "Passwordless", label: "Passwordless" },
-] as const satisfies readonly FilterOption[]
-
-export type MemberRole = (typeof MEMBER_ROLE_OPTIONS)[number]["value"]
-export type MemberBillingStatus =
- (typeof MEMBER_BILLING_STATUS_OPTIONS)[number]["value"]
-export type MemberAuthProvider =
- (typeof MEMBER_AUTH_PROVIDER_OPTIONS)[number]["value"]
-
-export type MemberRecord = {
- id: string
- fullName: string
- displayName: string
- email: string
- role: MemberRole
- billingStatus: MemberBillingStatus
- authProvider: MemberAuthProvider
- joinedAt: string
- avatarSrc: string
- initials: string
-}
-
-export const MEMBER_RECORDS: MemberRecord[] = [
- {
- id: "lena-torres",
- fullName: "Lena Torres",
- displayName: "lena.torres",
- email: "lena@northline.app",
- role: "Admin",
- billingStatus: "Active",
- authProvider: "Google",
- joinedAt: "Apr 17, 2026",
- avatarSrc:
- "https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=80&h=80&dpr=2&q=80",
- initials: "LT",
- },
- {
- id: "marcus-hale",
- fullName: "Marcus Hale",
- displayName: "marcus.hale",
- email: "marcus@northline.app",
- role: "Finance",
- billingStatus: "Pending invoice",
- authProvider: "Okta SSO",
- joinedAt: "Apr 12, 2026",
- avatarSrc:
- "https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=80&h=80&dpr=2&q=80",
- initials: "MH",
- },
- {
- id: "imani-brooks",
- fullName: "Imani Brooks",
- displayName: "imani.brooks",
- email: "imani@northline.app",
- role: "Support",
- billingStatus: "Active",
- authProvider: "Passwordless",
- joinedAt: "Mar 29, 2026",
- avatarSrc:
- "https://images.unsplash.com/photo-1488426862026-3ee34a7d66df?w=80&h=80&dpr=2&q=80",
- initials: "IB",
- },
- {
- id: "theo-mercer",
- fullName: "Theo Mercer",
- displayName: "theo.mercer",
- email: "theo@northline.app",
- role: "Operations",
- billingStatus: "Manual review",
- authProvider: "GitHub",
- joinedAt: "Mar 18, 2026",
- avatarSrc:
- "https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?w=80&h=80&dpr=2&q=80",
- initials: "TM",
- },
- {
- id: "noor-hadid",
- fullName: "Noor Hadid",
- displayName: "noor.hadid",
- email: "noor@northline.app",
- role: "Product",
- billingStatus: "Active",
- authProvider: "Google",
- joinedAt: "Feb 27, 2026",
- avatarSrc:
- "https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=80&h=80&dpr=2&q=80",
- initials: "NH",
- },
- {
- id: "avery-quinn",
- fullName: "Avery Quinn",
- displayName: "avery.quinn",
- email: "avery@northline.app",
- role: "Security",
- billingStatus: "Past due",
- authProvider: "Okta SSO",
- joinedAt: "Feb 10, 2026",
- avatarSrc:
- "https://images.unsplash.com/photo-1504593811423-6dd665756598?w=80&h=80&dpr=2&q=80",
- initials: "AQ",
- },
- {
- id: "sofia-lane",
- fullName: "Sofia Lane",
- displayName: "sofia.lane",
- email: "sofia@northline.app",
- role: "Finance",
- billingStatus: "Active",
- authProvider: "Google",
- joinedAt: "Jan 23, 2026",
- avatarSrc:
- "https://images.unsplash.com/photo-1544005313-94ddf0286df2?w=80&h=80&dpr=2&q=80",
- initials: "SL",
- },
- {
- id: "dev-rana",
- fullName: "Dev Rana",
- displayName: "dev.rana",
- email: "dev@northline.app",
- role: "Growth",
- billingStatus: "Pending invoice",
- authProvider: "GitHub",
- joinedAt: "Jan 11, 2026",
- avatarSrc:
- "https://images.unsplash.com/photo-1504257432389-52343af06ae3?w=80&h=80&dpr=2&q=80",
- initials: "DR",
- },
-]
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/data-grid-base-6/page.tsx b/apps/web/src/components/blocks/data-grid-base-6/page.tsx
deleted file mode 100644
index 045c84f..0000000
--- a/apps/web/src/components/blocks/data-grid-base-6/page.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { MembersDataGridView } from "./components/data-grid-view"
-
-export function Page() {
- return (
-
-
- Members data grid
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/data-grid-expansion-3/components/columns.tsx b/apps/web/src/components/blocks/data-grid-expansion-3/components/columns.tsx
deleted file mode 100644
index 144c86d..0000000
--- a/apps/web/src/components/blocks/data-grid-expansion-3/components/columns.tsx
+++ /dev/null
@@ -1,512 +0,0 @@
-import { memo, type ComponentProps } from "react"
-import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
-import { Badge } from "@/components/reui/badge"
-import { DataGridColumnHeader } from "@/components/reui/data-grid/data-grid-column-header"
-import { type ColumnDef } from "@tanstack/react-table"
-import { toast } from "sonner"
-
-import { cn } from "@evobgp/ui/lib/utils"
-import {
- Avatar,
- AvatarFallback,
- AvatarImage,
-} from "@evobgp/ui/components/avatar"
-import { Button } from "@evobgp/ui/components/button"
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuGroup,
- DropdownMenuItem,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from "@evobgp/ui/components/dropdown-menu"
-import { type IReleaseReview, type ReviewStatus, type RiskLevel } from "./data"
-import { ReleaseCheckRail } from "./release-check-rail"
-import { ChevronRightIcon, MoreHorizontalIcon, EyeIcon, CircleCheckIcon, CopyIcon, FlagIcon } from "lucide-react"
-
-export type ReviewAction = "open" | "approve" | "escalate"
-
-const riskVariant: Record["variant"]> =
- {
- Critical: "destructive-light",
- High: "warning-light",
- Medium: "info-light",
- Low: "secondary",
- }
-
-const statusConfig: Record<
- ReviewStatus,
- {
- variant: ComponentProps["variant"]
- dotClassName: string
- }
-> = {
- "Needs approval": {
- variant: "warning-outline",
- dotClassName: "bg-amber-500 dark:bg-amber-400",
- },
- Blocked: {
- variant: "destructive-outline",
- dotClassName: "bg-rose-500 dark:bg-rose-400",
- },
- Scheduled: {
- variant: "info-outline",
- dotClassName: "bg-sky-500 dark:bg-sky-400",
- },
- Ready: {
- variant: "success-outline",
- dotClassName: "bg-emerald-500 dark:bg-emerald-400",
- },
- Shipped: {
- variant: "outline",
- dotClassName: "bg-zinc-500 dark:bg-zinc-400",
- },
-}
-
-function DotSeparator() {
- return (
-
- )
-}
-
-function initials(name: string) {
- return name
- .split(" ")
- .map((part) => part[0])
- .join("")
-}
-
-function readinessPercent(review: IReleaseReview) {
- if (review.checksTotal === 0) return 100
-
- return Math.round((review.checksCompleted / review.checksTotal) * 100)
-}
-
-function readinessRingColor(percent: number) {
- if (percent >= 100) return "text-emerald-500"
- if (percent >= 50) return "text-amber-500"
- return "text-rose-500"
-}
-
-export const StatusBadge = memo(function StatusBadge({
- status,
-}: {
- status: ReviewStatus
-}) {
- return (
-
-
- {status}
-
- )
-})
-
-export const RiskBadge = memo(function RiskBadge({
- risk,
-}: {
- risk: RiskLevel
-}) {
- return {risk}
-})
-
-const ExpandReleaseButton = memo(function ExpandReleaseButton({
- expanded,
- onToggle,
-}: {
- expanded: boolean
- onToggle: () => void
-}) {
- return (
-
- )
-})
-
-const RequestCell = memo(function RequestCell({
- review,
- showChecks,
- canExpand,
- isExpanded,
- onToggleExpand,
-}: {
- review: IReleaseReview
- showChecks: boolean
- canExpand: boolean
- isExpanded: boolean
- onToggleExpand: () => void
-}) {
- return (
-
- {showChecks && canExpand ? (
-
- ) : (
-
- )}
-
-
-
- {review.title}
-
-
-
- {review.changeKey}
-
-
- {review.service.label}
-
- {review.environment}
-
-
-
- )
-})
-
-const OwnerCell = memo(function OwnerCell({
- review,
-}: {
- review: IReleaseReview
-}) {
- const owner = review.owner
-
- return (
-
-
- {owner.avatar ? (
-
- ) : null}
- {initials(owner.name)}
-
-
-
- {owner.name}
-
-
- {owner.role}
-
-
-
- )
-})
-
-const WindowCell = memo(function WindowCell({
- review,
-}: {
- review: IReleaseReview
-}) {
- return (
-
-
- {review.windowLabel}
-
-
- {review.windowDurationLabel}
-
- {review.blastRadiusLabel}
-
-
- )
-})
-
-const ApprovalsCell = memo(function ApprovalsCell({
- review,
-}: {
- review: IReleaseReview
-}) {
- const pending = review.approvalsRequired - review.approvalsApproved
- const isComplete = pending <= 0
-
- return (
-
-
- {review.approvalsApproved}/{review.approvalsRequired}
-
-
- {isComplete ? "Complete" : `${pending} pending`}
-
-
- )
-})
-
-const ReadinessCell = memo(function ReadinessCell({
- review,
-}: {
- review: IReleaseReview
-}) {
- const percent = readinessPercent(review)
- const radius = 9
- const circumference = 2 * Math.PI * radius
- const dashOffset = circumference - (percent / 100) * circumference
- const toneClassName = readinessRingColor(percent)
-
- return (
-
-
- {percent}%
-
- )
-})
-
-const UpdatedCell = memo(function UpdatedCell({
- review,
-}: {
- review: IReleaseReview
-}) {
- return (
- {review.updatedLabel}
- )
-})
-
-function ReviewActionsCell({
- review,
- onAction,
-}: {
- review: IReleaseReview
- onAction: (action: ReviewAction, review: IReleaseReview) => void
-}) {
- const { copyToClipboard } = useCopyToClipboard()
-
- return (
-
-
- }
- >
-
-
- {/* Content */}
-
-
- onAction("open", review)}>
-
- Open review
-
- onAction("approve", review)}>
-
- Approve window
-
- {
- copyToClipboard(review.changeKey)
- toast.success("Change key copied", {
- description: review.changeKey,
- })
- }}
- >
-
- Copy key
-
-
- onAction("escalate", review)}>
-
- Escalate risk
-
-
-
-
- )
-}
-
-export function createReleaseColumns({
- onReviewAction,
- showChecks,
-}: {
- onReviewAction: (action: ReviewAction, review: IReleaseReview) => void
- showChecks: boolean
-}): ColumnDef[] {
- return [
- {
- accessorFn: (review) => review.title,
- id: "request",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => (
-
- ),
- enableHiding: false,
- enableSorting: false,
- size: 332,
- minSize: 300,
- meta: {
- headerTitle: "Request",
- autoSize: true,
- headerClassName: "ps-5!",
- cellClassName: "ps-5!",
- expandedContent: (review: IReleaseReview) =>
- showChecks ? : null,
- },
- },
- {
- accessorFn: (review) => review.owner.name,
- id: "owner",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => ,
- size: 168,
- enableSorting: false,
- meta: {
- headerTitle: "Owner",
- },
- },
- {
- accessorFn: (review) => review.statusOrder,
- id: "status",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => ,
- size: 150,
- enableSorting: false,
- meta: {
- headerTitle: "Status",
- },
- },
- {
- accessorFn: (review) => review.riskValue,
- id: "risk",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => ,
- size: 116,
- meta: {
- headerTitle: "Risk",
- },
- },
- {
- accessorFn: (review) => review.windowStart,
- id: "window",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => ,
- size: 200,
- meta: {
- headerTitle: "Window",
- },
- },
- {
- accessorFn: (review) => review.approvalsApproved,
- id: "approvals",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => ,
- size: 136,
- enableSorting: false,
- meta: {
- headerTitle: "Approvals",
- },
- },
- {
- accessorFn: (review) => review.checksCompleted,
- id: "readiness",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => ,
- size: 150,
- enableSorting: false,
- meta: {
- headerTitle: "Readiness",
- },
- },
- {
- accessorFn: (review) => review.updatedAt,
- id: "updated",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => ,
- size: 120,
- meta: {
- headerTitle: "Updated",
- },
- },
- {
- id: "actions",
- header: "",
- enableSorting: false,
- enableHiding: false,
- cell: ({ row }) => (
-
- ),
- size: 56,
- meta: {
- headerClassName: "pe-5!",
- cellClassName: "pe-5!",
- },
- },
- ]
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/data-grid-expansion-3/components/data-grid-view.tsx b/apps/web/src/components/blocks/data-grid-expansion-3/components/data-grid-view.tsx
deleted file mode 100644
index 2469fd2..0000000
--- a/apps/web/src/components/blocks/data-grid-expansion-3/components/data-grid-view.tsx
+++ /dev/null
@@ -1,691 +0,0 @@
-"use client"
-
-import { useCallback, useMemo, useState } from "react"
-import {
- DataGrid,
- DataGridContainer,
- getColumnHeaderLabel,
-} from "@/components/reui/data-grid/data-grid"
-import { DataGridPagination } from "@/components/reui/data-grid/data-grid-pagination"
-import { DataGridScrollArea } from "@/components/reui/data-grid/data-grid-scroll-area"
-import { DataGridTable } from "@/components/reui/data-grid/data-grid-table"
-import {
- getCoreRowModel,
- getExpandedRowModel,
- getPaginationRowModel,
- useReactTable,
- type ExpandedState,
- type PaginationState,
- type SortingState,
- type VisibilityState,
-} from "@tanstack/react-table"
-import { toast } from "sonner"
-
-import { Button } from "@evobgp/ui/components/button"
-import {
- Field,
- FieldGroup,
- FieldLabel,
- FieldSeparator,
-} from "@evobgp/ui/components/field"
-import {
- InputGroup,
- InputGroupAddon,
- InputGroupButton,
- InputGroupInput,
-} from "@evobgp/ui/components/input-group"
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from "@evobgp/ui/components/popover"
-import {
- Select,
- SelectContent,
- SelectGroup,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@evobgp/ui/components/select"
-import { Separator } from "@evobgp/ui/components/separator"
-import { Switch } from "@evobgp/ui/components/switch"
-import {
- ToggleGroup,
- ToggleGroupItem,
-} from "@evobgp/ui/components/toggle-group"
-import { createReleaseColumns, type ReviewAction } from "./columns"
-import {
- ORDERING_OPTIONS,
- RELEASE_REVIEWS,
- REVIEW_STATUS_ORDER,
- SERVICE_OPTIONS,
- type IReleaseReview,
- type ReleaseOrdering,
- type ReviewStatus,
-} from "./data"
-import { ReleaseReviewCard } from "./release-review-card"
-import { PlusIcon, SearchIcon, XIcon, Settings2Icon } from "lucide-react"
-
-type TableDensity = "compact" | "comfortable"
-type OrderDirection = "asc" | "desc"
-type ServiceFilter = IReleaseReview["service"]["label"] | "All services"
-type StatusFilter = ReviewStatus | "All statuses"
-
-const TABLE_DENSITY_OPTIONS: { value: TableDensity; label: string }[] = [
- { value: "compact", label: "Compact" },
- { value: "comfortable", label: "Comfortable" },
-]
-
-const ORDER_DIRECTION_OPTIONS: { value: OrderDirection; label: string }[] = [
- { value: "asc", label: "Asc" },
- { value: "desc", label: "Desc" },
-]
-
-function getDefaultExpandedReviewIds(reviews: IReleaseReview[]): ExpandedState {
- const firstExpandableRow = reviews.find((review) => review.checks?.length)
-
- if (!firstExpandableRow) {
- return {}
- }
-
- return { [firstExpandableRow.id]: true }
-}
-
-function compareReviews(
- reviewA: IReleaseReview,
- reviewB: IReleaseReview,
- sorting: SortingState
-) {
- const primarySort = sorting[0]
- if (!primarySort) return 0
-
- const direction = primarySort.desc ? -1 : 1
- let baseValue = 0
-
- switch (primarySort.id) {
- case "window":
- baseValue =
- reviewA.windowStart.localeCompare(reviewB.windowStart) ||
- reviewA.changeKey.localeCompare(reviewB.changeKey)
- break
- case "updated":
- baseValue =
- reviewA.updatedAt.localeCompare(reviewB.updatedAt) ||
- reviewA.changeKey.localeCompare(reviewB.changeKey)
- break
- case "risk":
- default:
- baseValue =
- reviewA.riskValue - reviewB.riskValue ||
- reviewA.changeKey.localeCompare(reviewB.changeKey)
- break
- }
-
- return baseValue * direction
-}
-
-function sortIdToOrdering(sortId: string | undefined): ReleaseOrdering {
- if (sortId === "window") return "window"
- if (sortId === "updated") return "updated"
- return "risk"
-}
-
-function buildSorting(ordering: ReleaseOrdering, direction: OrderDirection) {
- const id =
- ordering === "window"
- ? "window"
- : ordering === "updated"
- ? "updated"
- : "risk"
-
- return [{ id, desc: direction === "desc" }]
-}
-
-function getReviewSearchBlob(review: IReleaseReview) {
- return [
- review.changeKey,
- review.title,
- review.service.label,
- review.environment,
- review.owner.name,
- review.status,
- review.risk,
- review.blastRadiusLabel,
- ]
- .join(" ")
- .toLowerCase()
-}
-
-export function ReleaseReviewGridView() {
- const [reviews, setReviews] = useState(RELEASE_REVIEWS)
- const [searchQuery, setSearchQuery] = useState("")
- const [serviceFilter, setServiceFilter] =
- useState("All services")
- const [statusFilter, setStatusFilter] = useState("All statuses")
- const [includeShipped, setIncludeShipped] = useState(false)
- const [showChecks, setShowChecks] = useState(true)
- const [tableDensity, setTableDensity] = useState("compact")
- const [pagination, setPagination] = useState({
- pageIndex: 0,
- pageSize: 5,
- })
- const [sorting, setSorting] = useState(
- buildSorting("risk", "desc")
- )
- const [expandedReviewIds, setExpandedReviewIds] = useState(
- () => getDefaultExpandedReviewIds(RELEASE_REVIEWS)
- )
- const [columnVisibility, setColumnVisibility] = useState({
- owner: true,
- risk: true,
- window: false,
- approvals: true,
- updated: false,
- })
-
- const resetPagination = useCallback(() => {
- setPagination((current) =>
- current.pageIndex === 0 ? current : { ...current, pageIndex: 0 }
- )
- }, [])
-
- const ordering = sortIdToOrdering(sorting[0]?.id)
- const direction: OrderDirection = sorting[0]?.desc ? "desc" : "asc"
- const normalizedQuery = searchQuery.trim().toLowerCase()
- const hasActiveFilters =
- normalizedQuery.length > 0 ||
- serviceFilter !== "All services" ||
- statusFilter !== "All statuses"
-
- const filteredReviews = useMemo(() => {
- return reviews.filter((review) => {
- if (!includeShipped && review.status === "Shipped") {
- return false
- }
-
- if (
- serviceFilter !== "All services" &&
- review.service.label !== serviceFilter
- ) {
- return false
- }
-
- if (statusFilter !== "All statuses" && review.status !== statusFilter) {
- return false
- }
-
- if (
- normalizedQuery.length > 0 &&
- !getReviewSearchBlob(review).includes(normalizedQuery)
- ) {
- return false
- }
-
- return true
- })
- }, [includeShipped, normalizedQuery, reviews, serviceFilter, statusFilter])
-
- const sortedReviews = useMemo(
- () => [...filteredReviews].sort((a, b) => compareReviews(a, b, sorting)),
- [filteredReviews, sorting]
- )
-
- const hiddenShippedCount = useMemo(
- () =>
- includeShipped
- ? 0
- : reviews.filter((review) => review.status === "Shipped").length,
- [includeShipped, reviews]
- )
-
- const emptyMessage = useMemo(() => {
- if (filteredReviews.length > 0) return undefined
-
- if (!includeShipped && hiddenShippedCount > 0 && !hasActiveFilters) {
- return `Only shipped reviews remain. Turn on Include shipped to show ${hiddenShippedCount} hidden ${hiddenShippedCount === 1 ? "record" : "records"}.`
- }
-
- return "No release reviews match this view."
- }, [
- filteredReviews.length,
- hasActiveFilters,
- hiddenShippedCount,
- includeShipped,
- ])
-
- const handleOrderingChange = useCallback(
- (value: ReleaseOrdering | null) => {
- if (!value) return
-
- setSorting(buildSorting(value, direction))
- resetPagination()
- },
- [direction, resetPagination]
- )
-
- const handleDirectionChange = useCallback(
- (value: string[]) => {
- const nextDirection = value[0] as OrderDirection | undefined
- if (!nextDirection) return
-
- setSorting(buildSorting(ordering, nextDirection))
- resetPagination()
- },
- [ordering, resetPagination]
- )
-
- const handleShowChecksChange = useCallback(
- (checked: boolean) => {
- setShowChecks(checked)
- setExpandedReviewIds((current) => {
- if (!checked) return {}
- return Object.keys(current).length > 0
- ? current
- : getDefaultExpandedReviewIds(sortedReviews)
- })
- },
- [sortedReviews]
- )
-
- const handleIncludeShippedChange = useCallback(
- (checked: boolean) => {
- setIncludeShipped(checked)
- resetPagination()
- },
- [resetPagination]
- )
-
- const handleClearFilters = useCallback(() => {
- setSearchQuery("")
- setServiceFilter("All services")
- setStatusFilter("All statuses")
- resetPagination()
- }, [resetPagination])
-
- const handleReviewAction = useCallback(
- (action: ReviewAction, review: IReleaseReview) => {
- if (action === "open") {
- toast.info("Open release review", {
- description: `${review.changeKey} · ${review.title}`,
- })
- return
- }
-
- if (action === "approve") {
- setReviews((current) =>
- current.map((item) =>
- item.id === review.id
- ? {
- ...item,
- status: "Ready",
- statusOrder: REVIEW_STATUS_ORDER.indexOf("Ready"),
- approvalsApproved: item.approvalsRequired,
- updatedAt: "2026-04-14T09:30:00Z",
- updatedLabel: "Just now",
- }
- : item
- )
- )
- resetPagination()
- toast.success("Window approved", {
- description: `${review.changeKey} is now ready for the scheduled window.`,
- })
- return
- }
-
- setReviews((current) =>
- current.map((item) =>
- item.id === review.id
- ? {
- ...item,
- status: "Blocked",
- statusOrder: REVIEW_STATUS_ORDER.indexOf("Blocked"),
- risk: "Critical",
- riskValue: 4,
- updatedAt: "2026-04-14T09:30:00Z",
- updatedLabel: "Just now",
- }
- : item
- )
- )
- toast.warning("Risk escalated", {
- description: `${review.changeKey} moved to the blocked queue.`,
- })
- },
- [resetPagination]
- )
-
- const columns = useMemo(
- () =>
- createReleaseColumns({
- onReviewAction: handleReviewAction,
- showChecks,
- }),
- [handleReviewAction, showChecks]
- )
-
- const table = useReactTable({
- data: sortedReviews,
- columns,
- state: {
- pagination,
- sorting,
- expanded: expandedReviewIds,
- columnVisibility,
- },
- onPaginationChange: setPagination,
- onSortingChange: setSorting,
- onExpandedChange: setExpandedReviewIds,
- onColumnVisibilityChange: setColumnVisibility,
- getRowId: (row) => row.id,
- getRowCanExpand: (row) =>
- showChecks && Boolean(row.original.checks?.length),
- paginateExpandedRows: false,
- getCoreRowModel: getCoreRowModel(),
- getExpandedRowModel: getExpandedRowModel(),
- getPaginationRowModel: getPaginationRowModel(),
- })
-
- return (
-
- {/* Card */}
-
-
- New review
-
- }
- footer={
-
-
-
- }
- >
-
-
-
-
-
-
-
- {
- setSearchQuery(event.target.value)
- resetPagination()
- }}
- />
- {searchQuery.length > 0 ? (
-
- {
- setSearchQuery("")
- resetPagination()
- }}
- >
-
-
-
- ) : null}
-
-
-
-
-
-
- {hasActiveFilters ? (
-
- ) : null}
-
-
-
-
-
-
- View Settings
-
- }
- />
-
-
-
-
-
- Ordering
-
-
-
-
-
-
- Direction
-
-
- {ORDER_DIRECTION_OPTIONS.map((option) => (
-
- {option.label}
-
- ))}
-
-
-
-
-
- Density
-
-
-
-
-
-
-
-
-
-
- Check cards
-
-
-
-
-
-
- Include shipped
-
-
-
-
-
-
-
-
-
- Columns
-
- {table
- .getAllColumns()
- .filter((column) => column.getCanHide())
- .map((column) => (
-
-
- {getColumnHeaderLabel(column)}
-
-
- column.toggleVisibility(checked)
- }
- />
-
- ))}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/data-grid-expansion-3/components/data.tsx b/apps/web/src/components/blocks/data-grid-expansion-3/components/data.tsx
deleted file mode 100644
index 90a2bfc..0000000
--- a/apps/web/src/components/blocks/data-grid-expansion-3/components/data.tsx
+++ /dev/null
@@ -1,1110 +0,0 @@
-import { type ReactNode } from "react"
-import { CreditCardIcon, ShieldCheckIcon, WebhookIcon, DatabaseIcon } from "lucide-react"
-
-export type ReviewStatus =
- | "Needs approval"
- | "Blocked"
- | "Scheduled"
- | "Ready"
- | "Shipped"
-
-export type RiskLevel = "Critical" | "High" | "Medium" | "Low"
-
-export type ReleaseOrdering = "risk" | "window" | "updated"
-
-export type ServiceKey = "checkout" | "auth" | "webhooks" | "data-sync"
-
-export type CheckStatus = "Open" | "Done" | "Flagged"
-
-export type CheckTrack =
- | "rollback"
- | "reliability"
- | "observability"
- | "handoff"
-
-export interface IOwner {
- id: string
- name: string
- initials: string
- avatar?: string
- role: string
-}
-
-export interface IServiceOption {
- value: ServiceKey
- label: string
- icon: ReactNode
-}
-
-export interface IReleaseCheck {
- id: string
- title: string
- status: CheckStatus
- reviewer: IOwner | null
- dueAt: string
- dueLabel: string
- track: CheckTrack
- confidence: number
- coverage: number
-}
-
-export interface IReleaseReview {
- id: string
- changeKey: string
- title: string
- service: IServiceOption
- environment: string
- status: ReviewStatus
- statusOrder: number
- risk: RiskLevel
- riskValue: number
- owner: IOwner
- windowStart: string
- windowLabel: string
- windowDurationLabel: string
- blastRadiusLabel: string
- updatedAt: string
- updatedLabel: string
- approvalsApproved: number
- approvalsRequired: number
- checksCompleted: number
- checksTotal: number
- checks?: IReleaseCheck[]
-}
-
-export const REVIEW_STATUS_ORDER: ReviewStatus[] = [
- "Needs approval",
- "Blocked",
- "Scheduled",
- "Ready",
- "Shipped",
-]
-
-export const RISK_ORDER: RiskLevel[] = ["Critical", "High", "Medium", "Low"]
-
-export const ORDERING_OPTIONS: {
- value: ReleaseOrdering
- label: string
-}[] = [
- { value: "risk", label: "Risk" },
- { value: "window", label: "Window" },
- { value: "updated", label: "Updated" },
-]
-
-export const OWNERS: IOwner[] = [
- {
- id: "maya",
- name: "Maya Patel",
- initials: "MP",
- avatar:
- "https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=96&h=96&dpr=2&q=80",
- role: "Release lead",
- },
- {
- id: "jonah",
- name: "Jonah Lee",
- initials: "JL",
- avatar:
- "https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=96&h=96&dpr=2&q=80",
- role: "Platform reviewer",
- },
- {
- id: "nina",
- name: "Nina Santos",
- initials: "NS",
- avatar:
- "https://images.unsplash.com/photo-1544005313-94ddf0286df2?w=96&h=96&dpr=2&q=80",
- role: "Billing reviewer",
- },
- {
- id: "omar",
- name: "Omar Haddad",
- initials: "OH",
- avatar:
- "https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?w=96&h=96&dpr=2&q=80",
- role: "Integrations owner",
- },
- {
- id: "priya",
- name: "Priya Menon",
- initials: "PM",
- avatar:
- "https://images.unsplash.com/photo-1557296387-5358ad7997bb?w=96&h=96&dpr=2&q=80",
- role: "Incident commander",
- },
- {
- id: "theo",
- name: "Theo Vincent",
- initials: "TV",
- avatar:
- "https://images.unsplash.com/photo-1507591064344-4c6ce005b128?w=96&h=96&dpr=2&q=80",
- role: "Data platform lead",
- },
-]
-
-export const SERVICE_OPTIONS: IServiceOption[] = [
- {
- value: "checkout",
- label: "Checkout",
- icon: (
-
- ),
- },
- {
- value: "auth",
- label: "Authentication",
- icon: (
-
- ),
- },
- {
- value: "webhooks",
- label: "Webhooks",
- icon: (
-
- ),
- },
- {
- value: "data-sync",
- label: "Data sync",
- icon: (
-
- ),
- },
-]
-
-function owner(id: IOwner["id"]) {
- const match = OWNERS.find((item) => item.id === id)
-
- if (!match) {
- throw new Error(`Unknown owner: ${id}`)
- }
-
- return match
-}
-
-function service(value: ServiceKey) {
- const match = SERVICE_OPTIONS.find((item) => item.value === value)
-
- if (!match) {
- throw new Error(`Unknown service: ${value}`)
- }
-
- return match
-}
-
-function releaseCheck(
- reviewKey: string,
- index: number,
- title: string,
- status: CheckStatus,
- reviewerId: IOwner["id"] | null,
- dueAt: string,
- dueLabel: string,
- track: CheckTrack,
- confidence: number,
- coverage: number
-): IReleaseCheck {
- return {
- id: `${reviewKey}-check-${index + 1}`,
- title,
- status,
- reviewer: reviewerId ? owner(reviewerId) : null,
- dueAt,
- dueLabel,
- track,
- confidence,
- coverage,
- }
-}
-
-export const RELEASE_REVIEWS: IReleaseReview[] = [
- {
- id: "1",
- changeKey: "REL-8421",
- title: "Split EU checkout traffic",
- service: service("checkout"),
- environment: "Production",
- status: "Needs approval",
- statusOrder: REVIEW_STATUS_ORDER.indexOf("Needs approval"),
- risk: "Critical",
- riskValue: 4,
- owner: owner("maya"),
- windowStart: "2026-04-18T22:00:00Z",
- windowLabel: "Apr 18 · 22:00 UTC",
- windowDurationLabel: "90 min window",
- blastRadiusLabel: "EU checkout",
- updatedAt: "2026-04-14T09:16:00Z",
- updatedLabel: "14m ago",
- approvalsApproved: 3,
- approvalsRequired: 5,
- checksCompleted: 2,
- checksTotal: 5,
- checks: [
- releaseCheck(
- "REL-8421",
- 0,
- "Replica lag watch",
- "Flagged",
- "jonah",
- "2026-04-18",
- "Due Apr 18",
- "reliability",
- 2.4,
- 42
- ),
- releaseCheck(
- "REL-8421",
- 1,
- "PSP fallback ready",
- "Open",
- "nina",
- "2026-04-18",
- "Due Apr 18",
- "rollback",
- 3.1,
- 58
- ),
- releaseCheck(
- "REL-8421",
- 2,
- "Rollback sign-off",
- "Done",
- "maya",
- "2026-04-17",
- "Done Apr 17",
- "rollback",
- 4.8,
- 96
- ),
- releaseCheck(
- "REL-8421",
- 3,
- "Fraud rule approval",
- "Open",
- "nina",
- "2026-04-18",
- "Due Apr 18",
- "handoff",
- 3.2,
- 63
- ),
- releaseCheck(
- "REL-8421",
- 4,
- "Cache bypass drill",
- "Done",
- "jonah",
- "2026-04-17",
- "Done Apr 17",
- "observability",
- 4.6,
- 91
- ),
- ],
- },
- {
- id: "2",
- changeKey: "REL-8416",
- title: "Rotate auth signing keys",
- service: service("auth"),
- environment: "Production",
- status: "Blocked",
- statusOrder: REVIEW_STATUS_ORDER.indexOf("Blocked"),
- risk: "High",
- riskValue: 3,
- owner: owner("jonah"),
- windowStart: "2026-04-18T19:30:00Z",
- windowLabel: "Apr 18 · 19:30 UTC",
- windowDurationLabel: "45 min window",
- blastRadiusLabel: "Global session auth",
- updatedAt: "2026-04-14T08:48:00Z",
- updatedLabel: "42m ago",
- approvalsApproved: 2,
- approvalsRequired: 4,
- checksCompleted: 2,
- checksTotal: 4,
- checks: [
- releaseCheck(
- "REL-8416",
- 0,
- "Android fallback bundle",
- "Flagged",
- "priya",
- "2026-04-18",
- "Due Apr 18",
- "rollback",
- 2.2,
- 37
- ),
- releaseCheck(
- "REL-8416",
- 1,
- "Old JWKS expiry",
- "Done",
- "jonah",
- "2026-04-17",
- "Done Apr 17",
- "reliability",
- 4.7,
- 95
- ),
- releaseCheck(
- "REL-8416",
- 2,
- "Failure threshold note",
- "Open",
- "maya",
- "2026-04-18",
- "Due Apr 18",
- "observability",
- 3.4,
- 59
- ),
- releaseCheck(
- "REL-8416",
- 3,
- "Revert token handoff",
- "Done",
- "priya",
- "2026-04-17",
- "Done Apr 17",
- "handoff",
- 4.5,
- 90
- ),
- ],
- },
- {
- id: "3",
- changeKey: "REL-8408",
- title: "Move invoice workers to new partitions",
- service: service("data-sync"),
- environment: "Staging",
- status: "Scheduled",
- statusOrder: REVIEW_STATUS_ORDER.indexOf("Scheduled"),
- risk: "Medium",
- riskValue: 2,
- owner: owner("theo"),
- windowStart: "2026-04-16T21:00:00Z",
- windowLabel: "Apr 16 · 21:00 UTC",
- windowDurationLabel: "60 min window",
- blastRadiusLabel: "Invoice pipeline",
- updatedAt: "2026-04-14T07:20:00Z",
- updatedLabel: "2h ago",
- approvalsApproved: 4,
- approvalsRequired: 4,
- checksCompleted: 4,
- checksTotal: 4,
- checks: [
- releaseCheck(
- "REL-8408",
- 0,
- "Autoscaling lock",
- "Done",
- "theo",
- "2026-04-15",
- "Done Apr 15",
- "reliability",
- 4.9,
- 100
- ),
- releaseCheck(
- "REL-8408",
- 1,
- "Reconciliation replay",
- "Done",
- "nina",
- "2026-04-15",
- "Done Apr 15",
- "observability",
- 4.7,
- 94
- ),
- releaseCheck(
- "REL-8408",
- 2,
- "DLQ replay path",
- "Done",
- "theo",
- "2026-04-15",
- "Done Apr 15",
- "rollback",
- 4.8,
- 96
- ),
- releaseCheck(
- "REL-8408",
- 3,
- "Staging sign-off",
- "Done",
- "maya",
- "2026-04-15",
- "Done Apr 15",
- "handoff",
- 4.9,
- 100
- ),
- ],
- },
- {
- id: "4",
- changeKey: "REL-8399",
- title: "Open LATAM webhook fanout",
- service: service("webhooks"),
- environment: "Production",
- status: "Ready",
- statusOrder: REVIEW_STATUS_ORDER.indexOf("Ready"),
- risk: "Medium",
- riskValue: 2,
- owner: owner("omar"),
- windowStart: "2026-04-17T18:00:00Z",
- windowLabel: "Apr 17 · 18:00 UTC",
- windowDurationLabel: "30 min window",
- blastRadiusLabel: "LATAM partner flows",
- updatedAt: "2026-04-14T06:14:00Z",
- updatedLabel: "3h ago",
- approvalsApproved: 5,
- approvalsRequired: 5,
- checksCompleted: 5,
- checksTotal: 5,
- checks: [
- releaseCheck(
- "REL-8399",
- 0,
- "Retry envelope pass",
- "Done",
- "omar",
- "2026-04-16",
- "Done Apr 16",
- "reliability",
- 4.9,
- 100
- ),
- releaseCheck(
- "REL-8399",
- 1,
- "Refund replay shadow",
- "Done",
- "nina",
- "2026-04-16",
- "Done Apr 16",
- "observability",
- 4.8,
- 95
- ),
- releaseCheck(
- "REL-8399",
- 2,
- "Rate limit approval",
- "Done",
- "priya",
- "2026-04-16",
- "Done Apr 16",
- "handoff",
- 4.7,
- 92
- ),
- releaseCheck(
- "REL-8399",
- 3,
- "Responder map ready",
- "Done",
- "maya",
- "2026-04-16",
- "Done Apr 16",
- "rollback",
- 4.8,
- 94
- ),
- releaseCheck(
- "REL-8399",
- 4,
- "Launch note posted",
- "Done",
- "omar",
- "2026-04-16",
- "Done Apr 16",
- "handoff",
- 4.9,
- 100
- ),
- ],
- },
- {
- id: "5",
- changeKey: "REL-8387",
- title: "Activate instant refunds",
- service: service("checkout"),
- environment: "Production",
- status: "Needs approval",
- statusOrder: REVIEW_STATUS_ORDER.indexOf("Needs approval"),
- risk: "High",
- riskValue: 3,
- owner: owner("nina"),
- windowStart: "2026-04-19T20:30:00Z",
- windowLabel: "Apr 19 · 20:30 UTC",
- windowDurationLabel: "75 min window",
- blastRadiusLabel: "Refund operations",
- updatedAt: "2026-04-14T05:34:00Z",
- updatedLabel: "4h ago",
- approvalsApproved: 1,
- approvalsRequired: 4,
- checksCompleted: 1,
- checksTotal: 4,
- checks: [
- releaseCheck(
- "REL-8387",
- 0,
- "Refund cap review",
- "Flagged",
- "priya",
- "2026-04-19",
- "Due Apr 19",
- "handoff",
- 2.8,
- 41
- ),
- releaseCheck(
- "REL-8387",
- 1,
- "Ledger replay",
- "Open",
- "theo",
- "2026-04-19",
- "Due Apr 19",
- "observability",
- 3.2,
- 56
- ),
- releaseCheck(
- "REL-8387",
- 2,
- "Dispute webhook pass",
- "Done",
- "omar",
- "2026-04-18",
- "Done Apr 18",
- "reliability",
- 4.6,
- 88
- ),
- releaseCheck(
- "REL-8387",
- 3,
- "Finance notice",
- "Open",
- "maya",
- "2026-04-19",
- "Due Apr 19",
- "handoff",
- 3.1,
- 52
- ),
- ],
- },
- {
- id: "6",
- changeKey: "REL-8379",
- title: "Reduce invite session TTL",
- service: service("auth"),
- environment: "Production",
- status: "Shipped",
- statusOrder: REVIEW_STATUS_ORDER.indexOf("Shipped"),
- risk: "Low",
- riskValue: 1,
- owner: owner("maya"),
- windowStart: "2026-04-12T17:00:00Z",
- windowLabel: "Apr 12 · 17:00 UTC",
- windowDurationLabel: "20 min window",
- blastRadiusLabel: "Invite acceptance",
- updatedAt: "2026-04-13T16:28:00Z",
- updatedLabel: "Yesterday",
- approvalsApproved: 3,
- approvalsRequired: 3,
- checksCompleted: 3,
- checksTotal: 3,
- checks: [
- releaseCheck(
- "REL-8379",
- 0,
- "Eviction pattern check",
- "Done",
- "jonah",
- "2026-04-12",
- "Done Apr 12",
- "reliability",
- 4.8,
- 100
- ),
- releaseCheck(
- "REL-8379",
- 1,
- "Invite metrics verify",
- "Done",
- "maya",
- "2026-04-12",
- "Done Apr 12",
- "observability",
- 4.7,
- 98
- ),
- releaseCheck(
- "REL-8379",
- 2,
- "Onboarding note",
- "Done",
- "maya",
- "2026-04-12",
- "Done Apr 12",
- "handoff",
- 4.9,
- 100
- ),
- ],
- },
- {
- id: "7",
- changeKey: "REL-8368",
- title: "Shift analytics cutover",
- service: service("data-sync"),
- environment: "Staging",
- status: "Scheduled",
- statusOrder: REVIEW_STATUS_ORDER.indexOf("Scheduled"),
- risk: "Medium",
- riskValue: 2,
- owner: owner("theo"),
- windowStart: "2026-04-20T23:00:00Z",
- windowLabel: "Apr 20 · 23:00 UTC",
- windowDurationLabel: "120 min window",
- blastRadiusLabel: "Analyst reporting",
- updatedAt: "2026-04-14T04:58:00Z",
- updatedLabel: "5h ago",
- approvalsApproved: 3,
- approvalsRequired: 4,
- checksCompleted: 3,
- checksTotal: 4,
- checks: [
- releaseCheck(
- "REL-8368",
- 0,
- "Late partition rebuild",
- "Done",
- "theo",
- "2026-04-19",
- "Done Apr 19",
- "reliability",
- 4.5,
- 90
- ),
- releaseCheck(
- "REL-8368",
- 1,
- "Looker explore pass",
- "Done",
- "priya",
- "2026-04-19",
- "Done Apr 19",
- "observability",
- 4.4,
- 86
- ),
- releaseCheck(
- "REL-8368",
- 2,
- "Rollback snapshot",
- "Open",
- "nina",
- "2026-04-20",
- "Due Apr 20",
- "rollback",
- 3.3,
- 62
- ),
- releaseCheck(
- "REL-8368",
- 3,
- "Data channel note",
- "Done",
- "maya",
- "2026-04-19",
- "Done Apr 19",
- "handoff",
- 4.6,
- 91
- ),
- ],
- },
- {
- id: "8",
- changeKey: "REL-8362",
- title: "Roll out regional webhook retries",
- service: service("webhooks"),
- environment: "Production",
- status: "Blocked",
- statusOrder: REVIEW_STATUS_ORDER.indexOf("Blocked"),
- risk: "Critical",
- riskValue: 4,
- owner: owner("omar"),
- windowStart: "2026-04-19T18:30:00Z",
- windowLabel: "Apr 19 · 18:30 UTC",
- windowDurationLabel: "50 min window",
- blastRadiusLabel: "Enterprise delivery",
- updatedAt: "2026-04-14T09:05:00Z",
- updatedLabel: "25m ago",
- approvalsApproved: 2,
- approvalsRequired: 5,
- checksCompleted: 2,
- checksTotal: 5,
- checks: [
- releaseCheck(
- "REL-8362",
- 0,
- "Support cap review",
- "Flagged",
- "priya",
- "2026-04-19",
- "Due Apr 19",
- "handoff",
- 2.1,
- 34
- ),
- releaseCheck(
- "REL-8362",
- 1,
- "Replay shadow test",
- "Open",
- "omar",
- "2026-04-19",
- "Due Apr 19",
- "observability",
- 3.1,
- 49
- ),
- releaseCheck(
- "REL-8362",
- 2,
- "Routing rollback sign-off",
- "Done",
- "maya",
- "2026-04-18",
- "Done Apr 18",
- "rollback",
- 4.5,
- 84
- ),
- releaseCheck(
- "REL-8362",
- 3,
- "Region pinning verify",
- "Open",
- "jonah",
- "2026-04-19",
- "Due Apr 19",
- "reliability",
- 3.0,
- 53
- ),
- releaseCheck(
- "REL-8362",
- 4,
- "Partner runbook lock",
- "Done",
- "omar",
- "2026-04-18",
- "Done Apr 18",
- "handoff",
- 4.4,
- 80
- ),
- ],
- },
- {
- id: "9",
- changeKey: "REL-8358",
- title: "Pin checkout tax cache for Canada",
- service: service("checkout"),
- environment: "Production",
- status: "Ready",
- statusOrder: REVIEW_STATUS_ORDER.indexOf("Ready"),
- risk: "Low",
- riskValue: 1,
- owner: owner("nina"),
- windowStart: "2026-04-20T16:30:00Z",
- windowLabel: "Apr 20 · 16:30 UTC",
- windowDurationLabel: "25 min window",
- blastRadiusLabel: "Canada tax quotes",
- updatedAt: "2026-04-14T03:48:00Z",
- updatedLabel: "6h ago",
- approvalsApproved: 4,
- approvalsRequired: 4,
- checksCompleted: 4,
- checksTotal: 4,
- checks: [
- releaseCheck(
- "REL-8358",
- 0,
- "Tax cache warmup",
- "Done",
- "nina",
- "2026-04-19",
- "Done Apr 19",
- "reliability",
- 4.8,
- 97
- ),
- releaseCheck(
- "REL-8358",
- 1,
- "Quote audit sample",
- "Done",
- "maya",
- "2026-04-19",
- "Done Apr 19",
- "observability",
- 4.6,
- 92
- ),
- releaseCheck(
- "REL-8358",
- 2,
- "Rollback note",
- "Done",
- "jonah",
- "2026-04-19",
- "Done Apr 19",
- "rollback",
- 4.7,
- 94
- ),
- releaseCheck(
- "REL-8358",
- 3,
- "Support heads-up",
- "Done",
- "priya",
- "2026-04-19",
- "Done Apr 19",
- "handoff",
- 4.5,
- 89
- ),
- ],
- },
- {
- id: "10",
- changeKey: "REL-8349",
- title: "Move login audit writes to stream",
- service: service("auth"),
- environment: "Staging",
- status: "Scheduled",
- statusOrder: REVIEW_STATUS_ORDER.indexOf("Scheduled"),
- risk: "Medium",
- riskValue: 2,
- owner: owner("jonah"),
- windowStart: "2026-04-21T01:00:00Z",
- windowLabel: "Apr 21 · 01:00 UTC",
- windowDurationLabel: "40 min window",
- blastRadiusLabel: "Audit ingestion",
- updatedAt: "2026-04-14T02:40:00Z",
- updatedLabel: "7h ago",
- approvalsApproved: 2,
- approvalsRequired: 3,
- checksCompleted: 2,
- checksTotal: 3,
- checks: [
- releaseCheck(
- "REL-8349",
- 0,
- "Backfill mirror test",
- "Done",
- "theo",
- "2026-04-20",
- "Done Apr 20",
- "observability",
- 4.2,
- 83
- ),
- releaseCheck(
- "REL-8349",
- 1,
- "Staging revert path",
- "Done",
- "jonah",
- "2026-04-20",
- "Done Apr 20",
- "rollback",
- 4.4,
- 87
- ),
- releaseCheck(
- "REL-8349",
- 2,
- "Security review note",
- "Open",
- "maya",
- "2026-04-20",
- "Due Apr 20",
- "handoff",
- 3.5,
- 61
- ),
- ],
- },
- {
- id: "11",
- changeKey: "REL-8341",
- title: "Gate partner retries by tenant policy",
- service: service("webhooks"),
- environment: "Production",
- status: "Needs approval",
- statusOrder: REVIEW_STATUS_ORDER.indexOf("Needs approval"),
- risk: "High",
- riskValue: 3,
- owner: owner("omar"),
- windowStart: "2026-04-21T18:45:00Z",
- windowLabel: "Apr 21 · 18:45 UTC",
- windowDurationLabel: "55 min window",
- blastRadiusLabel: "Enterprise retries",
- updatedAt: "2026-04-14T01:55:00Z",
- updatedLabel: "8h ago",
- approvalsApproved: 2,
- approvalsRequired: 5,
- checksCompleted: 2,
- checksTotal: 5,
- checks: [
- releaseCheck(
- "REL-8341",
- 0,
- "Tenant policy audit",
- "Done",
- "priya",
- "2026-04-20",
- "Done Apr 20",
- "observability",
- 4.1,
- 78
- ),
- releaseCheck(
- "REL-8341",
- 1,
- "Escalation ladder",
- "Done",
- "maya",
- "2026-04-20",
- "Done Apr 20",
- "handoff",
- 4.3,
- 85
- ),
- releaseCheck(
- "REL-8341",
- 2,
- "Fallback quota caps",
- "Open",
- "omar",
- "2026-04-21",
- "Due Apr 21",
- "rollback",
- 3.0,
- 47
- ),
- releaseCheck(
- "REL-8341",
- 3,
- "Partner retry replay",
- "Flagged",
- "jonah",
- "2026-04-21",
- "Due Apr 21",
- "reliability",
- 2.3,
- 39
- ),
- releaseCheck(
- "REL-8341",
- 4,
- "Support macro update",
- "Open",
- "priya",
- "2026-04-21",
- "Due Apr 21",
- "handoff",
- 3.2,
- 55
- ),
- ],
- },
- {
- id: "12",
- changeKey: "REL-8337",
- title: "Rebalance ledger export workers",
- service: service("data-sync"),
- environment: "Production",
- status: "Blocked",
- statusOrder: REVIEW_STATUS_ORDER.indexOf("Blocked"),
- risk: "High",
- riskValue: 3,
- owner: owner("theo"),
- windowStart: "2026-04-22T00:30:00Z",
- windowLabel: "Apr 22 · 00:30 UTC",
- windowDurationLabel: "70 min window",
- blastRadiusLabel: "Ledger exports",
- updatedAt: "2026-04-14T00:44:00Z",
- updatedLabel: "9h ago",
- approvalsApproved: 1,
- approvalsRequired: 4,
- checksCompleted: 1,
- checksTotal: 4,
- checks: [
- releaseCheck(
- "REL-8337",
- 0,
- "Replay parity run",
- "Done",
- "theo",
- "2026-04-21",
- "Done Apr 21",
- "observability",
- 4.0,
- 76
- ),
- releaseCheck(
- "REL-8337",
- 1,
- "Backpressure threshold",
- "Flagged",
- "jonah",
- "2026-04-21",
- "Due Apr 21",
- "reliability",
- 2.4,
- 36
- ),
- releaseCheck(
- "REL-8337",
- 2,
- "Rollback queue snapshot",
- "Open",
- "maya",
- "2026-04-21",
- "Due Apr 21",
- "rollback",
- 3.1,
- 48
- ),
- releaseCheck(
- "REL-8337",
- 3,
- "Finance comms",
- "Open",
- "nina",
- "2026-04-21",
- "Due Apr 21",
- "handoff",
- 3.0,
- 51
- ),
- ],
- },
-]
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/data-grid-expansion-3/components/release-check-rail.tsx b/apps/web/src/components/blocks/data-grid-expansion-3/components/release-check-rail.tsx
deleted file mode 100644
index 7249852..0000000
--- a/apps/web/src/components/blocks/data-grid-expansion-3/components/release-check-rail.tsx
+++ /dev/null
@@ -1,212 +0,0 @@
-import { type ComponentProps } from "react"
-import { Badge } from "@/components/reui/badge"
-import { Rating } from "@/components/reui/rating"
-
-import { cn } from "@evobgp/ui/lib/utils"
-import {
- Avatar,
- AvatarFallback,
- AvatarImage,
-} from "@evobgp/ui/components/avatar"
-import { Card, CardContent } from "@evobgp/ui/components/card"
-
-import {
- type CheckStatus,
- type IReleaseCheck,
- type IReleaseReview,
-} from "./data"
-
-const MAX_VISIBLE_CHECK_CARDS = 3
-
-const checkStatusConfig: Record<
- CheckStatus,
- {
- variant: ComponentProps["variant"]
- dotClassName: string
- }
-> = {
- Open: {
- variant: "invert-light",
- dotClassName: "bg-zinc-400 dark:bg-zinc-500",
- },
- Done: {
- variant: "success-light",
- dotClassName: "bg-emerald-500 dark:bg-emerald-400",
- },
- Flagged: {
- variant: "destructive-light",
- dotClassName: "bg-rose-500 dark:bg-rose-400",
- },
-}
-
-function DotSeparator() {
- return (
-
- )
-}
-
-function initials(name: string) {
- return name
- .split(" ")
- .map((part) => part[0])
- .join("")
-}
-
-function coverageRingColor(coverage: number) {
- if (coverage >= 85) return "text-emerald-500"
- if (coverage >= 60) return "text-amber-500"
- return "text-rose-500"
-}
-
-function CheckStatusBadge({ status }: { status: CheckStatus }) {
- return (
-
-
- {status}
-
- )
-}
-
-function CoverageRing({
- coverage,
- className,
-}: {
- coverage: number
- className?: string
-}) {
- const radius = 8
- const circumference = 2 * Math.PI * radius
- const dashOffset = circumference - (coverage / 100) * circumference
-
- return (
-
-
-
- {coverage}%
-
-
- )
-}
-
-function ReleaseCheckCard({ check }: { check: IReleaseCheck }) {
- return (
-
- {/* Content */}
-
-
-
-
-
-
-
- {check.title}
-
-
-
-
- {check.reviewer?.avatar ? (
-
- ) : null}
-
- {check.reviewer ? initials(check.reviewer.name) : "--"}
-
-
-
- {check.reviewer?.name ?? "Reviewer pending"}
-
-
-
-
-
-
{check.dueLabel}
-
-
-
-
-
-
-
- {check.confidence.toFixed(1)}
-
-
-
-
-
-
- )
-}
-
-export function ReleaseCheckRail({ review }: { review: IReleaseReview }) {
- const checks = review.checks ?? []
-
- if (checks.length === 0) return null
-
- const visibleChecks = checks.slice(0, MAX_VISIBLE_CHECK_CARDS)
- const hiddenCheckCount = checks.length - visibleChecks.length
- const openChecks = checks.filter((check) => check.status !== "Done").length
-
- return (
-
-
- Checklist
-
-
- {hiddenCheckCount > 0
- ? `${visibleChecks.length} of ${checks.length} items`
- : `${checks.length} items`}
-
-
- {openChecks} open
- {hiddenCheckCount > 0 ? (
- <>
-
-
- +{hiddenCheckCount} more
-
- >
- ) : null}
-
-
- {/* Cards */}
-
- {visibleChecks.map((check) => (
-
- ))}
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/data-grid-expansion-3/components/release-review-card.tsx b/apps/web/src/components/blocks/data-grid-expansion-3/components/release-review-card.tsx
deleted file mode 100644
index b4c45fe..0000000
--- a/apps/web/src/components/blocks/data-grid-expansion-3/components/release-review-card.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import { type ReactNode } from "react"
-
-import { cn } from "@evobgp/ui/lib/utils"
-import {
- Card,
- CardAction,
- CardContent,
- CardDescription,
- CardFooter,
- CardHeader,
- CardTitle,
-} from "@evobgp/ui/components/card"
-
-interface ReleaseReviewCardProps {
- title: string
- description: ReactNode
- action?: ReactNode
- children: ReactNode
- footer?: ReactNode
- className?: string
- contentClassName?: string
- footerClassName?: string
-}
-
-export function ReleaseReviewCard({
- title,
- description,
- action,
- children,
- footer,
- className,
- contentClassName,
- footerClassName,
-}: ReleaseReviewCardProps) {
- return (
-
- {/* Header */}
-
- {title}
-
- {description}
-
- {action ? (
- {action}
- ) : null}
-
-
- {/* Content */}
-
- {children}
-
-
- {footer ? (
-
- {footer}
-
- ) : null}
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/data-grid-expansion-3/page.tsx b/apps/web/src/components/blocks/data-grid-expansion-3/page.tsx
deleted file mode 100644
index 6aa55a8..0000000
--- a/apps/web/src/components/blocks/data-grid-expansion-3/page.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { ReleaseReviewGridView } from "./components/data-grid-view"
-
-export function Page() {
- return (
-
-
- Release review data grid
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/data-grid-filtering-1/components/columns.tsx b/apps/web/src/components/blocks/data-grid-filtering-1/components/columns.tsx
deleted file mode 100644
index 8abab1a..0000000
--- a/apps/web/src/components/blocks/data-grid-filtering-1/components/columns.tsx
+++ /dev/null
@@ -1,426 +0,0 @@
-import { memo, useState } from "react"
-import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
-import { Badge } from "@/components/reui/badge"
-import { DataGridColumnHeader } from "@/components/reui/data-grid/data-grid-column-header"
-import {
- DataGridTableRowSelect,
- DataGridTableRowSelectAll,
-} from "@/components/reui/data-grid/data-grid-table"
-import { Row, type ColumnDef } from "@tanstack/react-table"
-import { toast } from "sonner"
-
-import { cn } from "@evobgp/ui/lib/utils"
-import {
- AlertDialog,
- AlertDialogAction,
- AlertDialogCancel,
- AlertDialogContent,
- AlertDialogDescription,
- AlertDialogFooter,
- AlertDialogHeader,
- AlertDialogTitle,
-} from "@evobgp/ui/components/alert-dialog"
-import { Button } from "@evobgp/ui/components/button"
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuGroup,
- DropdownMenuItem,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from "@evobgp/ui/components/dropdown-menu"
-import { Item, ItemMedia } from "@evobgp/ui/components/item"
-import { Skeleton } from "@evobgp/ui/components/skeleton"
-import { ITransaction, TxChannel, TxStatus } from "./data"
-import { MoreHorizontalIcon, EyeIcon, CopyIcon, DownloadIcon, TriangleAlertIcon, ArrowRightIcon } from "lucide-react"
-
-// ── Formatters ──
-
-function formatAmount(amount: number, currency: string) {
- return new Intl.NumberFormat("en-US", {
- style: "currency",
- currency,
- minimumFractionDigits: 2,
- }).format(Math.abs(amount))
-}
-
-function formatDate(iso: string) {
- return new Intl.DateTimeFormat("en-US", {
- month: "short",
- day: "numeric",
- year: "numeric",
- }).format(new Date(iso))
-}
-
-// ── Status (aligned with data-grid-1 badge language) ──
-
-export const StatusBadge = memo(function StatusBadge({
- status,
-}: {
- status: TxStatus
-}) {
- if (status === "completed")
- return Completed
- if (status === "failed")
- return Failed
- if (status === "pending")
- return Pending
- return Cancelled
-})
-
-// ── Channel (origin of charge - replaces "method" duplicate logos) ──
-
-const channelLabel: Record = {
- checkout: "Checkout",
- api: "API",
- invoice: "Invoice",
- dashboard: "Dashboard",
-}
-
-const channelVariant: Record<
- TxChannel,
- "success-light" | "info-light" | "warning-light"
-> = {
- checkout: "success-light",
- api: "info-light",
- invoice: "warning-light",
- dashboard: "success-light",
-}
-
-export const ChannelBadge = memo(function ChannelBadge({
- channel,
-}: {
- channel: TxChannel
-}) {
- return {channelLabel[channel]}
-})
-
-// ── Actions ──
-
-export function ActionsCell({ row }: { row: Row }) {
- const { copyToClipboard } = useCopyToClipboard()
- const [disputeOpen, setDisputeOpen] = useState(false)
-
- const handleCopyRef = () => {
- copyToClipboard(row.original.reference)
- toast.success("Reference copied", { description: row.original.reference })
- }
-
- const handleDisputeConfirm = () => {
- setDisputeOpen(false)
- toast.message("Dispute opened", {
- description: `${row.original.reference}. Connect your payments API (demo).`,
- })
- }
-
- return (
- <>
-
-
- }
- >
-
-
-
-
-
- toast.info("Transaction details", {
- description: "Demo. Navigate to your detail view.",
- })
- }
- >
-
- View Details
-
-
-
- Copy Reference
-
-
- toast.success("Receipt", {
- description: "Demo. Download from your storage.",
- })
- }
- >
-
- Receipt
-
-
- setDisputeOpen(true)}
- >
-
- Dispute
-
-
-
-
-
-
-
-
- Open a dispute?
-
- This starts a dispute for{""}
-
- {row.original.reference}
-
- . In production this is irreversible until resolved. Wire to your
- payment provider.
-
-
-
- Cancel
-
- Start dispute
-
-
-
-
- >
- )
-}
-
-export const columns: ColumnDef[] = [
- {
- accessorKey: "id",
- id: "id",
- header: () => ,
- cell: ({ row }) => ,
- enableSorting: false,
- size: 35,
- enableResizing: false,
- enableHiding: false,
- meta: {
- skeleton: ,
- headerClassName:
- "[--data-grid-header-cell-ps:var(--frame-panel-header-px)]",
- cellClassName: "[--data-grid-body-cell-ps:var(--frame-panel-px)]",
- },
- },
- {
- accessorKey: "reference",
- id: "reference",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => (
-
-
-
- {row.original.reference}
-
-
-
-
- {formatDate(row.original.date)}
-
-
- ),
- size: 150,
- enableSorting: true,
- enableHiding: true,
- enableResizing: true,
- meta: {
- headerTitle: "Reference",
- skeleton: (
-
-
-
-
- ),
- },
- },
- {
- accessorKey: "description",
- id: "description",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => (
-
-
-
-
- {row.original.merchantLogo}
-
-
-
-
- {row.original.description}
-
-
- {row.original.merchant} · {row.original.category}
-
-
-
- ),
- minSize: 150,
- enableSorting: true,
- enableHiding: true,
- enableResizing: true,
- meta: {
- headerTitle: "Description",
- skeleton: (
-
- ),
- },
- },
- {
- accessorKey: "country",
- id: "region",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => (
-
-
-
}.svg`})
-
- {row.original.country}
-
-
- {row.original.timezone && (
-
- {row.original.timezone}
-
- )}
-
- ),
- size: 170,
- enableSorting: true,
- enableHiding: true,
- enableResizing: true,
- meta: {
- headerTitle: "Region",
- skeleton: (
-
- ),
- },
- },
- {
- accessorKey: "channel",
- id: "channel",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => ,
- size: 120,
- enableSorting: true,
- enableHiding: true,
- enableResizing: true,
- meta: {
- headerTitle: "Channel",
- skeleton: ,
- },
- },
- {
- accessorKey: "amount",
- id: "amount",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => {
- const { amount, currency } = row.original
- const inflow = amount > 0
- return (
-
- {inflow ? "+" : "−"}
- {formatAmount(amount, currency)}
-
- )
- },
- size: 100,
- enableSorting: true,
- enableHiding: true,
- meta: {
- headerTitle: "Amount",
- skeleton: ,
- },
- },
- {
- accessorKey: "account",
- id: "account",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => (
-
- {row.original.account}
-
- ),
- size: 160,
- enableSorting: false,
- enableHiding: true,
- enableResizing: true,
- meta: {
- headerTitle: "Account",
- skeleton: ,
- },
- },
- {
- accessorKey: "status",
- id: "status",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => ,
- size: 90,
- enableSorting: true,
- enableHiding: true,
- enableResizing: false,
- meta: {
- headerTitle: "Status",
- skeleton: ,
- },
- },
- {
- id: "actions",
- header: "",
- cell: ({ row }) => ,
- size: 50,
- enableSorting: false,
- enableHiding: false,
- enableResizing: false,
- meta: {
- skeleton: ,
- headerClassName:
- "[--data-grid-header-cell-pe:var(--frame-panel-header-px)]",
- cellClassName: "[--data-grid-body-cell-pe:var(--frame-panel-px)]",
- },
- },
-]
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/data-grid-filtering-1/components/data-grid-view.tsx b/apps/web/src/components/blocks/data-grid-filtering-1/components/data-grid-view.tsx
deleted file mode 100644
index d18ab66..0000000
--- a/apps/web/src/components/blocks/data-grid-filtering-1/components/data-grid-view.tsx
+++ /dev/null
@@ -1,597 +0,0 @@
-"use client"
-
-import { useCallback, useEffect, useRef, useState } from "react"
-import { Badge } from "@/components/reui/badge"
-import { DataGrid } from "@/components/reui/data-grid/data-grid"
-import { DataGridPagination } from "@/components/reui/data-grid/data-grid-pagination"
-import { DataGridScrollArea } from "@/components/reui/data-grid/data-grid-scroll-area"
-import { DataGridTable } from "@/components/reui/data-grid/data-grid-table"
-import {
- createFilter,
- Filters,
- type Filter,
- type FilterFieldConfig,
-} from "@/components/reui/filters"
-import {
- Frame,
- FrameDescription,
- FrameFooter,
- FrameHeader,
- FramePanel,
- FrameTitle,
-} from "@/components/reui/frame"
-import {
- ColumnSizingState,
- getCoreRowModel,
- getFilteredRowModel,
- getPaginationRowModel,
- getSortedRowModel,
- PaginationState,
- SortingState,
- useReactTable,
- type VisibilityState,
-} from "@tanstack/react-table"
-import { toast } from "sonner"
-
-import { Button } from "@evobgp/ui/components/button"
-import { Item, ItemMedia } from "@evobgp/ui/components/item"
-import { Separator } from "@evobgp/ui/components/separator"
-import { TooltipProvider } from "@evobgp/ui/components/tooltip"
-import { ChannelBadge, columns, StatusBadge } from "./columns"
-import {
- TRANSACTIONS,
- type ITransaction,
- type TxChannel,
- type TxStatus,
-} from "./data"
-import { HashIcon, FileTextIcon, Building2Icon, FolderIcon, GlobeIcon, RouteIcon, CircleDotIcon, PlusIcon, FilterIcon, FunnelXIcon } from "lucide-react"
-
-// ── Helpers ──
-
-function getActiveFilters(filters: Filter[]) {
- return filters.filter((filter) => {
- const { values } = filter
- if (!values || values.length === 0) return false
- if (
- values.every((value) => typeof value === "string" && value.trim() === "")
- )
- return false
- if (values.every((value) => value === null || value === undefined))
- return false
- if (values.every((value) => Array.isArray(value) && value.length === 0))
- return false
- return true
- })
-}
-
-/** Stable key for comparing whether applied (active) filters meaningfully changed. */
-function serializeActiveFiltersKey(active: Filter[]) {
- return JSON.stringify(
- active.map((f) => ({
- field: f.field,
- operator: f.operator,
- values: f.values,
- }))
- )
-}
-
-function filterFieldValue(item: ITransaction, field: string): unknown {
- if (field === "region") {
- return [item.country, item.timezone].filter(Boolean).join(" ")
- }
- return item[field as keyof ITransaction]
-}
-
-function applyFiltersToData(
- data: ITransaction[],
- filters: Filter[]
-): ITransaction[] {
- const active = getActiveFilters(filters)
- let result = [...data]
- active.forEach((filter) => {
- const { field, operator, values } = filter
- result = result.filter((item) => {
- const raw = filterFieldValue(item, field)
- const fieldValue = raw != null ? raw : ""
-
- switch (operator) {
- case "is":
- return values.includes(fieldValue)
- case "is_not":
- return !values.includes(fieldValue)
- case "is_any_of":
- return values.some((v) => fieldValue === v)
- case "is_not_any_of":
- return !values.some((v) => fieldValue === v)
- case "contains": {
- const tokens = values.map((v) => String(v).trim()).filter(Boolean)
- if (tokens.length === 0) return true
- return tokens.some((token) =>
- String(fieldValue).toLowerCase().includes(token.toLowerCase())
- )
- }
- case "not_contains":
- return !values.some((v) =>
- String(fieldValue).toLowerCase().includes(String(v).toLowerCase())
- )
- case "starts_with":
- return values.some((v) =>
- String(fieldValue).toLowerCase().startsWith(String(v).toLowerCase())
- )
- case "ends_with":
- return values.some((v) =>
- String(fieldValue).toLowerCase().endsWith(String(v).toLowerCase())
- )
- case "equals":
- return fieldValue === values[0]
- case "not_equals":
- return fieldValue !== values[0]
- case "greater_than":
- return Number(fieldValue) > Number(values[0])
- case "less_than":
- return Number(fieldValue) < Number(values[0])
- case "greater_than_or_equal":
- return Number(fieldValue) >= Number(values[0])
- case "less_than_or_equal":
- return Number(fieldValue) <= Number(values[0])
- case "between":
- if (values.length >= 2) {
- const min = Number(values[0])
- const max = Number(values[1])
- return Number(fieldValue) >= min && Number(fieldValue) <= max
- }
- return true
- case "not_between":
- if (values.length >= 2) {
- const min = Number(values[0])
- const max = Number(values[1])
- return Number(fieldValue) < min || Number(fieldValue) > max
- }
- return true
- case "empty":
- return fieldValue === "" || fieldValue == null
- case "not_empty":
- return fieldValue !== "" && fieldValue != null
- default:
- return true
- }
- })
- })
- return result
-}
-
-// ── Filter field config ──
-
-const CATEGORY_OPTIONS = [
- "Payments",
- "Infrastructure",
- "AI / ML",
- "Automation",
- "Design",
- "Documentation",
- "Backend",
- "Developer tools",
-]
-
-const STATUS_OPTIONS: { value: TxStatus; label: string }[] = [
- { value: "completed", label: "Completed" },
- { value: "pending", label: "Pending" },
- { value: "failed", label: "Failed" },
- { value: "cancelled", label: "Cancelled" },
-]
-
-const CHANNEL_OPTIONS: { value: TxChannel; label: string }[] = [
- { value: "checkout", label: "Checkout" },
- { value: "api", label: "API" },
- { value: "invoice", label: "Invoice" },
- { value: "dashboard", label: "Dashboard" },
-]
-
-const MERCHANT_FILTER_OPTIONS = Array.from(
- new Map(
- TRANSACTIONS.map((transaction) => [
- transaction.merchant,
- {
- value: transaction.merchant,
- label: transaction.merchant,
- icon: (
- }
- className="w-auto shrink-0 border-0 p-0 [&_svg]:size-4"
- >
-
- {transaction.merchantLogo}
-
-
- ),
- },
- ])
- ).values()
-)
-
-function renderSelectedCount(values: unknown[]) {
- if (values.length === 0) return "Select..."
- if (values.length > 1) return `${values.length} selected`
- return null
-}
-
-const filterFields: FilterFieldConfig[] = [
- {
- key: "reference",
- label: "Reference",
- icon: (
-
- ),
- type: "text",
- className: "w-44",
- placeholder: "Search...",
- },
- {
- key: "description",
- label: "Description",
- icon: (
-
- ),
- type: "text",
- className: "w-52",
- placeholder: "Search...",
- },
- {
- key: "merchant",
- label: "Merchant",
- icon: (
-
- ),
- type: "select",
- searchable: true,
- className: "w-[200px]",
- options: MERCHANT_FILTER_OPTIONS,
- customValueRenderer: (values, options) => {
- const state = renderSelectedCount(values)
- if (state) return state
-
- const option = options.find((item) => item.value === values[0])
- if (!option) return String(values[0])
-
- return (
-
- {option.icon}
- {option.label}
-
- )
- },
- },
- {
- key: "category",
- label: "Category",
- icon: (
-
- ),
- type: "select",
- searchable: true,
- className: "w-[200px]",
- options: CATEGORY_OPTIONS.map((category) => ({
- value: category,
- label: category,
- })),
- customValueRenderer: (values) => {
- const state = renderSelectedCount(values)
- if (state) return state
-
- return {String(values[0])}
- },
- },
- {
- key: "region",
- label: "Region",
- icon: (
-
- ),
- type: "text",
- className: "w-52",
- placeholder: "Search...",
- },
- {
- key: "channel",
- label: "Channel",
- icon: (
-
- ),
- type: "select",
- searchable: false,
- className: "w-[156px]",
- options: CHANNEL_OPTIONS,
- customValueRenderer: (values) => {
- const state = renderSelectedCount(values)
- if (state) return state
-
- return
- },
- },
- {
- key: "status",
- label: "Status",
- icon: (
-
- ),
- type: "select",
- searchable: false,
- className: "w-[156px]",
- options: STATUS_OPTIONS,
- customValueRenderer: (values) => {
- const state = renderSelectedCount(values)
- if (state) return state
-
- return
- },
- },
-]
-
-/** Single default row: Reference | contains | empty - inactive until user types (see getActiveFilters). */
-function createDefaultTransactionFilters(): Filter[] {
- return [createFilter("reference", "contains", [""])]
-}
-
-const DESCRIPTION_COLUMN_ID = "description"
-const DESCRIPTION_COLUMN_DEFAULT_SIZE = 300
-
-function getAutoDescriptionColumnSize(
- containerWidth: number,
- columnVisibility: VisibilityState
-) {
- const occupiedWidth = columns.reduce((total, column) => {
- if (
- !column.id ||
- column.id === DESCRIPTION_COLUMN_ID ||
- columnVisibility[column.id] === false
- ) {
- return total
- }
-
- return total + (typeof column.size === "number" ? column.size : 0)
- }, 0)
-
- return Math.max(
- DESCRIPTION_COLUMN_DEFAULT_SIZE,
- Math.round(containerWidth - occupiedWidth)
- )
-}
-
-// ── Main component ──
-
-export function DataGridView() {
- const [pagination, setPagination] = useState({
- pageIndex: 0,
- pageSize: 5,
- })
- const [sorting, setSorting] = useState([
- { id: "reference", desc: true },
- ])
- const [columnVisibility, setColumnVisibility] = useState({
- account: false,
- })
- const [columnSizing, setColumnSizing] = useState({})
- const [filters, setFilters] = useState(
- createDefaultTransactionFilters
- )
-
- const [isLoading, setIsLoading] = useState(false)
- const [filteredData, setFilteredData] = useState(TRANSACTIONS)
- const [gridWidth, setGridWidth] = useState(0)
- const isInitialLoad = useRef(true)
- const gridWidthRef = useRef(null)
- const hasManualColumnSizing = useRef(false)
- const lastAppliedActiveKey = useRef(
- serializeActiveFiltersKey(
- getActiveFilters(createDefaultTransactionFilters())
- )
- )
-
- const applyFilters = useCallback((newFilters: Filter[]) => {
- return applyFiltersToData(TRANSACTIONS, newFilters)
- }, [])
-
- const simulateAsyncFiltering = useCallback(
- async (newFilters: Filter[]) => {
- setIsLoading(true)
- await new Promise((resolve) => setTimeout(resolve, 400))
- setFilteredData(applyFilters(newFilters))
- setIsLoading(false)
- },
- [applyFilters]
- )
-
- const handleFiltersChange = useCallback(
- (newFilters: Filter[]) => {
- setFilters(newFilters)
- const newActive = getActiveFilters(newFilters)
- const nextKey = serializeActiveFiltersKey(newActive)
- if (nextKey === lastAppliedActiveKey.current) return
- lastAppliedActiveKey.current = nextKey
- setPagination((prev) => ({ ...prev, pageIndex: 0 }))
- simulateAsyncFiltering(newFilters)
- },
- [simulateAsyncFiltering]
- )
-
- useEffect(() => {
- if (isInitialLoad.current) {
- setFilteredData(applyFilters(filters))
- isInitialLoad.current = false
- }
- }, [filters, applyFilters])
-
- useEffect(() => {
- const element = gridWidthRef.current
-
- if (!element) return
-
- const syncGridWidth = () => {
- setGridWidth(element.clientWidth)
- }
-
- syncGridWidth()
-
- if (typeof ResizeObserver === "undefined") return
-
- const observer = new ResizeObserver(syncGridWidth)
- observer.observe(element)
-
- return () => {
- observer.disconnect()
- }
- }, [])
-
- useEffect(() => {
- if (gridWidth <= 0 || hasManualColumnSizing.current) return
-
- const nextDescriptionWidth = getAutoDescriptionColumnSize(
- gridWidth,
- columnVisibility
- )
-
- setColumnSizing((current) =>
- current[DESCRIPTION_COLUMN_ID] === nextDescriptionWidth
- ? current
- : {
- ...current,
- [DESCRIPTION_COLUMN_ID]: nextDescriptionWidth,
- }
- )
- }, [columnVisibility, gridWidth])
-
- const [columnOrder, setColumnOrder] = useState(
- columns.map((c) => c.id as string)
- )
-
- const handleColumnSizingChange = useCallback(
- (
- updater:
- | ColumnSizingState
- | ((old: ColumnSizingState) => ColumnSizingState)
- ) => {
- hasManualColumnSizing.current = true
- setColumnSizing(updater)
- },
- []
- )
-
- const table = useReactTable({
- columns,
- data: filteredData,
- pageCount: Math.ceil(filteredData.length / pagination.pageSize),
- getRowId: (row) => row.id,
- state: {
- pagination,
- sorting,
- columnOrder,
- columnSizing,
- columnVisibility,
- },
- columnResizeMode: "onChange",
- onColumnOrderChange: setColumnOrder,
- onColumnSizingChange: handleColumnSizingChange,
- onColumnVisibilityChange: setColumnVisibility,
- onPaginationChange: setPagination,
- onSortingChange: setSorting,
- getCoreRowModel: getCoreRowModel(),
- getFilteredRowModel: getFilteredRowModel(),
- getPaginationRowModel: getPaginationRowModel(),
- getSortedRowModel: getSortedRowModel(),
- })
-
- /** Show Clear whenever any filter row is visible (including empty placeholders). */
- const showClearButton = filters.length > 0
-
- return (
-
- {/* Table */}
-
-
-
-
-
- Transactions
-
-
- Billing ledger(refunds / payouts)
-
-
-
-
-
-
-
-
- Filters
-
- }
- />
- {showClearButton && (
-
- )}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/data-grid-filtering-1/components/data.tsx b/apps/web/src/components/blocks/data-grid-filtering-1/components/data.tsx
deleted file mode 100644
index e407f5e..0000000
--- a/apps/web/src/components/blocks/data-grid-filtering-1/components/data.tsx
+++ /dev/null
@@ -1,263 +0,0 @@
-import { type ReactNode } from "react"
-
-import { AnthropicBlack } from "@evobgp/ui/components/svgs/anthropicBlack"
-import { AnthropicWhite } from "@evobgp/ui/components/svgs/anthropicWhite"
-import { Convex } from "@evobgp/ui/components/svgs/convex"
-import { Mintlify } from "@evobgp/ui/components/svgs/mintlify"
-import { N8n } from "@evobgp/ui/components/svgs/n8n"
-import { Neon } from "@evobgp/ui/components/svgs/neon"
-import { Openai } from "@evobgp/ui/components/svgs/openai"
-import { OpenaiDark } from "@evobgp/ui/components/svgs/openaiDark"
-import { Paper } from "@evobgp/ui/components/svgs/paper"
-import { Prisma } from "@evobgp/ui/components/svgs/prisma"
-import { PrismaDark } from "@evobgp/ui/components/svgs/prismaDark"
-import { Stripe } from "@evobgp/ui/components/svgs/stripe"
-import { Supabase } from "@evobgp/ui/components/svgs/supabase"
-
-// ── Types ──
-
-export type TxType = "payment" | "refund" | "payout" | "charge" | "transfer"
-export type TxStatus = "completed" | "pending" | "failed" | "cancelled"
-/** Where the charge originated (billing SaaS pattern - replaces duplicate "method" logos). */
-export type TxChannel = "checkout" | "api" | "invoice" | "dashboard"
-
-export interface ITransaction {
- id: string
- reference: string
- date: string
- description: string
- merchant: string
- merchantLogo: ReactNode
- category: string
- type: TxType
- channel: TxChannel
- amount: number
- currency: string
- status: TxStatus
- account: string
- country: string
- flag: string
- timezone?: string
- note?: string
-}
-
-// ── Logos (one mark per vendor - @/components/ui/svgs only) ──
-
-const OPENAI_LOGO = (
- <>
-
-
-
-
-
-
- >
-)
-
-const PRISMA_LOGO = (
- <>
-
-
-
-
-
-
- >
-)
-
-const ANTHROPIC_LOGO = (
- <>
-
-
-
-
-
-
- >
-)
-
-// ── Data: 10 rows, unique merchants, inbound + outbound (refunds / payouts) ──
-
-export const TRANSACTIONS: ITransaction[] = [
- {
- id: "txn_001",
- reference: "TXN-2025-000143",
- date: "2025-06-12T09:14:00Z",
- description: "Pro plan · subscription renewal",
- merchant: "Stripe",
- merchantLogo: ,
- category: "Payments",
- type: "charge",
- channel: "checkout",
- amount: 240.0,
- currency: "USD",
- status: "completed",
- account: "•••• 4242",
- country: "United States",
- flag: "us",
- timezone: "PST (UTC−8)",
- },
- {
- id: "txn_002",
- reference: "TXN-2025-000142",
- date: "2025-06-11T16:30:00Z",
- description: "Database compute · June cycle",
- merchant: "Supabase",
- merchantLogo: ,
- category: "Infrastructure",
- type: "charge",
- channel: "dashboard",
- amount: 75.0,
- currency: "USD",
- status: "completed",
- account: "•••• 4242",
- country: "United States",
- flag: "us",
- timezone: "EST (UTC−5)",
- },
- {
- id: "txn_003",
- reference: "TXN-2025-000141",
- date: "2025-06-11T11:02:00Z",
- description: "GPT-4o usage · billing period",
- merchant: "OpenAI",
- merchantLogo: OPENAI_LOGO,
- category: "AI / ML",
- type: "charge",
- channel: "api",
- amount: 312.48,
- currency: "USD",
- status: "completed",
- account: "•••• 4242",
- country: "United States",
- flag: "us",
- timezone: "PST (UTC−8)",
- },
- {
- id: "txn_004",
- reference: "TXN-2025-000140",
- date: "2025-06-10T08:50:00Z",
- description: "Workflow credit · billing adjustment (outbound)",
- merchant: "N8n",
- merchantLogo: ,
- category: "Automation",
- type: "refund",
- channel: "dashboard",
- amount: -21.0,
- currency: "USD",
- status: "completed",
- account: "org@acme.io",
- country: "Germany",
- flag: "de",
- timezone: "CET (UTC+1)",
- },
- {
- id: "txn_005",
- reference: "TXN-2025-000139",
- date: "2025-06-09T14:15:00Z",
- description: "Canvas seats · prorated refund to customer",
- merchant: "Paper",
- merchantLogo: ,
- category: "Design",
- type: "refund",
- channel: "invoice",
- amount: -96.0,
- currency: "USD",
- status: "pending",
- account: "•••• 0011",
- country: "United Kingdom",
- flag: "gb",
- timezone: "GMT (UTC+0)",
- },
- {
- id: "txn_006",
- reference: "TXN-2025-000138",
- date: "2025-06-08T18:44:00Z",
- description: "Docs hosting · team",
- merchant: "Mintlify",
- merchantLogo: ,
- category: "Documentation",
- type: "charge",
- channel: "checkout",
- amount: 45.0,
- currency: "USD",
- status: "completed",
- account: "•••• 4242",
- country: "United States",
- flag: "us",
- timezone: "CST (UTC−6)",
- },
- {
- id: "txn_007",
- reference: "TXN-2025-000137",
- date: "2025-06-08T12:22:00Z",
- description: "Function invocations · May",
- merchant: "Convex",
- merchantLogo: ,
- category: "Backend",
- type: "charge",
- channel: "api",
- amount: 25.0,
- currency: "USD",
- status: "failed",
- account: "•••• 9871",
- country: "United States",
- flag: "us",
- timezone: "PST (UTC−8)",
- note: "Card declined, retry scheduled",
- },
- {
- id: "txn_008",
- reference: "TXN-2025-000136",
- date: "2025-06-07T10:05:00Z",
- description: "Payout to linked bank · hobby tier balance",
- merchant: "Neon",
- merchantLogo: ,
- category: "Infrastructure",
- type: "payout",
- channel: "dashboard",
- amount: -250.0,
- currency: "USD",
- status: "completed",
- account: "dev@acme.io",
- country: "Germany",
- flag: "de",
- timezone: "CET (UTC+1)",
- },
- {
- id: "txn_009",
- reference: "TXN-2025-000135",
- date: "2025-06-06T15:00:00Z",
- description: "ORM · team license",
- merchant: "Prisma",
- merchantLogo: PRISMA_LOGO,
- category: "Developer tools",
- type: "charge",
- channel: "invoice",
- amount: 60.0,
- currency: "USD",
- status: "completed",
- account: "•••• 4242",
- country: "Germany",
- flag: "de",
- timezone: "CET (UTC+1)",
- },
- {
- id: "txn_010",
- reference: "TXN-2025-000134",
- date: "2025-06-05T11:11:00Z",
- description: "Claude API · May usage",
- merchant: "Anthropic",
- merchantLogo: ANTHROPIC_LOGO,
- category: "AI / ML",
- type: "payment",
- channel: "api",
- amount: 2500.0,
- currency: "USD",
- status: "completed",
- account: "dev@acme.io",
- country: "Canada",
- flag: "ca",
- timezone: "EST (UTC−5)",
- },
-]
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/data-grid-filtering-1/page.tsx b/apps/web/src/components/blocks/data-grid-filtering-1/page.tsx
deleted file mode 100644
index 3e87c79..0000000
--- a/apps/web/src/components/blocks/data-grid-filtering-1/page.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { DataGridView } from "./components/data-grid-view"
-
-export function Page() {
- return (
-
-
- Billing ledger data grid
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/data-grid-filtering-2/components/columns.tsx b/apps/web/src/components/blocks/data-grid-filtering-2/components/columns.tsx
deleted file mode 100644
index 680817b..0000000
--- a/apps/web/src/components/blocks/data-grid-filtering-2/components/columns.tsx
+++ /dev/null
@@ -1,408 +0,0 @@
-"use client"
-
-import { Badge } from "@/components/reui/badge"
-import { DataGridColumnHeader } from "@/components/reui/data-grid/data-grid-column-header"
-import {
- DataGridTableRowSelect,
- DataGridTableRowSelectAll,
-} from "@/components/reui/data-grid/data-grid-table"
-import { Rating } from "@/components/reui/rating"
-import { type ReactNode } from "react"
-import { type ColumnDef } from "@tanstack/react-table"
-import { format, parseISO } from "date-fns"
-
-import { cn } from "@evobgp/ui/lib/utils"
-import {
- Avatar,
- AvatarFallback,
- AvatarImage,
-} from "@evobgp/ui/components/avatar"
-import { Button } from "@evobgp/ui/components/button"
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuGroup,
- DropdownMenuItem,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from "@evobgp/ui/components/dropdown-menu"
-import { Item, ItemMedia } from "@evobgp/ui/components/item"
-import { Switch } from "@evobgp/ui/components/switch"
-import {
- type AutomationKind,
- type AutomationOwnerAvailability,
- type AutomationState,
- type IAutomationRecord,
-} from "./data"
-import { GitBranchIcon, RouteIcon, SparklesIcon, MailIcon, BellRingIcon, EllipsisVerticalIcon, PencilIcon, EyeIcon, CircleCheckIcon, ArchiveIcon } from "lucide-react"
-
-export type AutomationAction = "edit" | "open" | "archive"
-
-const automationKindStyles: Record<
- AutomationKind,
- { chipClassName: string; icon: ReactNode }
-> = {
- sequence: {
- chipClassName:
- "bg-sky-50 text-sky-600 ring-sky-200 dark:bg-sky-500/15 dark:text-sky-300 dark:ring-sky-400/30",
- icon: (
-
- ),
- },
- routing: {
- chipClassName:
- "bg-violet-50 text-violet-600 ring-violet-200 dark:bg-violet-500/15 dark:text-violet-300 dark:ring-violet-400/30",
- icon: (
-
- ),
- },
- enrichment: {
- chipClassName:
- "bg-emerald-50 text-emerald-600 ring-emerald-200 dark:bg-emerald-500/15 dark:text-emerald-300 dark:ring-emerald-400/30",
- icon: (
-
- ),
- },
- digest: {
- chipClassName:
- "bg-amber-50 text-amber-600 ring-amber-200 dark:bg-amber-500/15 dark:text-amber-300 dark:ring-amber-400/30",
- icon: (
-
- ),
- },
- escalation: {
- chipClassName:
- "bg-rose-50 text-rose-600 ring-rose-200 dark:bg-rose-500/15 dark:text-rose-300 dark:ring-rose-400/30",
- icon: (
-
- ),
- },
-}
-
-const stateBadgeStyles: Record<
- AutomationState,
- { label: string; dotClassName?: string }
-> = {
- live: {
- label: "Live",
- dotClassName: "bg-emerald-500",
- },
- review: {
- label: "Needs approval",
- dotClassName: "bg-amber-500",
- },
- drafts: {
- label: "Draft",
- dotClassName: "bg-slate-400 dark:bg-slate-300",
- },
- paused: {
- label: "Paused",
- dotClassName: "bg-zinc-400 dark:bg-zinc-300",
- },
-}
-
-const updatedBucketLabel: Record = {
- today: "Today",
- "this-week": "This week",
- older: "Older",
-}
-
-const availabilityColor: Record = {
- online: "bg-green-500",
- away: "bg-yellow-400",
- busy: "bg-red-500",
- offline: "bg-gray-500",
-}
-
-function AutomationKindChip({ kind }: { kind: AutomationKind }) {
- const style = automationKindStyles[kind]
-
- return (
- }
- className={cn(
- "p-0",
- "inline-flex size-10 items-center justify-center ring-1 ring-inset",
- style.chipClassName
- )}
- aria-hidden="true"
- >
-
- {style.icon}
-
-
- )
-}
-
-function AutomationNameCell({ automation }: { automation: IAutomationRecord }) {
- return (
-
-
-
-
- {automation.title}
-
-
- {automation.runWindowLabel}
-
- {automation.audienceLabel}
-
-
-
- )
-}
-
-function OwnerCell({ automation }: { automation: IAutomationRecord }) {
- return (
-
-
-
- {automation.owner.avatar ? (
-
- ) : null}
- {automation.owner.initials}
-
-
-
-
-
- {automation.owner.name}
-
-
- {automation.owner.email}
-
-
-
- )
-}
-
-function StateCell({ automation }: { automation: IAutomationRecord }) {
- const stateStyle = stateBadgeStyles[automation.state]
-
- return (
-
-
- {stateStyle.dotClassName ? (
-
- ) : null}
- {stateStyle.label}
-
-
- )
-}
-
-function UpdatedCell({ automation }: { automation: IAutomationRecord }) {
- return (
-
-
- {format(parseISO(automation.updatedAt), "MMM d, yyyy")}
-
-
- {updatedBucketLabel[automation.updatedBucket]}
-
-
- )
-}
-
-function RatingCell({ automation }: { automation: IAutomationRecord }) {
- return
-}
-
-function AutomationActionsCell({
- automation,
- onAction,
- onToggleEnabled,
-}: {
- automation: IAutomationRecord
- onAction: (action: AutomationAction, automation: IAutomationRecord) => void
- onToggleEnabled: (automation: IAutomationRecord, nextValue: boolean) => void
-}) {
- return (
-
-
-
-
- }
- />
- {/* Content */}
-
-
- onAction("edit", automation)}>
-
- Edit
-
- onAction("open", automation)}>
-
- View Details
-
- {
- // The Switch toggles itself and its click bubbles here; skip it
- // so item-level activation (row click, Enter/Space) toggles once.
- if (
- event.target instanceof Element &&
- event.target.closest('[data-slot="switch"]')
- ) {
- return
- }
- onToggleEnabled(automation, !automation.enabled)
- }}
- className="justify-between gap-4"
- >
-
-
- Enabled
-
-
- onToggleEnabled(automation, checked)
- }
- />
-
-
- onAction("archive", automation)}
- >
-
- Archive
-
-
-
-
- )
-}
-
-export function createAutomationColumns({
- onAction,
- onToggleEnabled,
-}: {
- onAction: (action: AutomationAction, automation: IAutomationRecord) => void
- onToggleEnabled: (automation: IAutomationRecord, nextValue: boolean) => void
-}): ColumnDef[] {
- return [
- {
- id: "select",
- header: () => ,
- cell: ({ row }) => ,
- size: 30,
- enableSorting: false,
- enableResizing: false,
- enableHiding: false,
- meta: {
- headerClassName:
- "[--data-grid-header-cell-ps:var(--frame-panel-header-px)]",
- cellClassName: "[--data-grid-body-cell-ps:var(--frame-panel-px)]",
- },
- },
- {
- accessorFn: (row) => row.title,
- id: "workflow",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => ,
- size: 340,
- minSize: 200,
- enableSorting: true,
- enableHiding: false,
- meta: {
- autoSize: true,
- headerClassName: "pl-3!",
- cellClassName: "pl-3!",
- },
- },
- {
- accessorFn: (row) => row.owner.name,
- id: "owner",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => ,
- size: 175,
- enableSorting: true,
- },
- {
- accessorFn: (row) => row.rating,
- id: "rating",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => ,
- size: 150,
- enableSorting: true,
- },
- {
- accessorFn: (row) => row.state,
- id: "state",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => ,
- size: 150,
- enableSorting: true,
- },
- {
- accessorFn: (row) => parseISO(row.updatedAt).getTime(),
- id: "updatedAt",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => ,
- size: 125,
- enableSorting: true,
- },
- {
- id: "actions",
- header: () => null,
- cell: ({ row }) => (
-
- ),
- size: 56,
- enableSorting: false,
- meta: {
- headerClassName:
- "[--data-grid-header-cell-pe:var(--frame-panel-header-px)]",
- cellClassName: "[--data-grid-body-cell-pe:var(--frame-panel-px)]",
- },
- },
- ]
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/data-grid-filtering-2/components/data-grid-view.tsx b/apps/web/src/components/blocks/data-grid-filtering-2/components/data-grid-view.tsx
deleted file mode 100644
index bece769..0000000
--- a/apps/web/src/components/blocks/data-grid-filtering-2/components/data-grid-view.tsx
+++ /dev/null
@@ -1,660 +0,0 @@
-import { useCallback, useMemo, useState } from "react"
-import { Badge } from "@/components/reui/badge"
-import { DataGrid } from "@/components/reui/data-grid/data-grid"
-import { DataGridPagination } from "@/components/reui/data-grid/data-grid-pagination"
-import { DataGridScrollArea } from "@/components/reui/data-grid/data-grid-scroll-area"
-import { DataGridTable } from "@/components/reui/data-grid/data-grid-table"
-import {
- createFilter,
- Filters,
- type Filter,
- type FilterFieldConfig,
-} from "@/components/reui/filters"
-import {
- Frame,
- FrameDescription,
- FrameFooter,
- FrameHeader,
- FramePanel,
- FrameTitle,
-} from "@/components/reui/frame"
-import {
- getCoreRowModel,
- getPaginationRowModel,
- getSortedRowModel,
- useReactTable,
- type PaginationState,
- type RowSelectionState,
- type SortingState,
-} from "@tanstack/react-table"
-import { toast } from "sonner"
-
-import { cn } from "@evobgp/ui/lib/utils"
-import {
- AlertDialog,
- AlertDialogAction,
- AlertDialogCancel,
- AlertDialogContent,
- AlertDialogDescription,
- AlertDialogFooter,
- AlertDialogHeader,
- AlertDialogTitle,
-} from "@evobgp/ui/components/alert-dialog"
-import { Button } from "@evobgp/ui/components/button"
-import { Separator } from "@evobgp/ui/components/separator"
-import { Tabs, TabsList, TabsTrigger } from "@evobgp/ui/components/tabs"
-import { createAutomationColumns } from "./columns"
-import {
- AUTOMATION_TABS,
- AUTOMATIONS,
- DELIVERY_FILTER_OPTIONS,
- getAutomationTabFromState,
- OWNER_FILTER_OPTIONS,
- UPDATED_FILTER_OPTIONS,
- type AutomationState,
- type AutomationTab,
- type IAutomationRecord,
-} from "./data"
-import { SearchIcon, UsersIcon, RouteIcon, ClockIcon, PlusIcon, FilterIcon, FunnelXIcon } from "lucide-react"
-
-type ToastTone = "success" | "neutral" | "destructive"
-
-const toneStyles: Record = {
- success: { dot: "bg-emerald-500" },
- neutral: { dot: "bg-sky-500" },
- destructive: { dot: "bg-rose-500" },
-}
-
-function showAutomationToast({
- tone,
- title,
- description,
-}: {
- tone: ToastTone
- title: string
- description: string
-}) {
- toast.custom((id) => (
-
-
-
-
-
{title}
-
- {description}
-
-
-
-
-
-
-
- ))
-}
-
-function getAutomationSearchBlob(automation: IAutomationRecord) {
- return [
- automation.title,
- automation.kind,
- automation.state,
- automation.deliveryMode,
- automation.audienceLabel,
- automation.runWindowLabel,
- automation.owner.name,
- automation.owner.email,
- automation.owner.teamLabel,
- automation.approvalRequired ? "approval required" : "auto-approved",
- automation.enabled ? "enabled" : "disabled",
- ]
- .join(" ")
- .toLowerCase()
-}
-
-function getActiveFilters(filters: Filter[]) {
- return filters.filter((filter) => {
- const { values } = filter
- if (!values || values.length === 0) return false
- if (
- values.every((value) => typeof value === "string" && value.trim() === "")
- ) {
- return false
- }
- if (values.every((value) => value === null || value === undefined)) {
- return false
- }
- if (values.every((value) => Array.isArray(value) && value.length === 0)) {
- return false
- }
- return true
- })
-}
-
-function renderSelectedCount(values: unknown[]) {
- if (values.length === 0) return "Select..."
- if (values.length > 1) return `${values.length} selected`
- return null
-}
-
-function renderSingleSelectedLabel(
- values: unknown[],
- options: { value: string; label: string }[]
-) {
- const state = renderSelectedCount(values)
- if (state) return state
-
- const option = options.find((item) => item.value === values[0])
- return option?.label ?? String(values[0])
-}
-
-function filterFieldValue(
- automation: IAutomationRecord,
- field: string
-): unknown {
- switch (field) {
- case "workflow":
- return getAutomationSearchBlob(automation)
- case "ownerTeam":
- return automation.owner.team
- case "deliveryMode":
- return automation.deliveryMode
- case "updatedBucket":
- return automation.updatedBucket
- default:
- return ""
- }
-}
-
-function applyFiltersToData(
- data: IAutomationRecord[],
- filters: Filter[]
-): IAutomationRecord[] {
- const active = getActiveFilters(filters)
- let result = [...data]
-
- active.forEach((filter) => {
- const { field, operator, values } = filter
-
- result = result.filter((item) => {
- const raw = filterFieldValue(item, field)
- const fieldValue = raw != null ? raw : ""
-
- switch (operator) {
- case "is":
- return values.includes(fieldValue)
- case "is_not":
- return !values.includes(fieldValue)
- case "is_any_of":
- return values.some((value) => fieldValue === value)
- case "is_not_any_of":
- return !values.some((value) => fieldValue === value)
- case "contains": {
- const tokens = values
- .map((value) => String(value).trim())
- .filter(Boolean)
- if (tokens.length === 0) return true
- return tokens.some((token) =>
- String(fieldValue).toLowerCase().includes(token.toLowerCase())
- )
- }
- case "not_contains":
- return !values.some((value) =>
- String(fieldValue)
- .toLowerCase()
- .includes(String(value).toLowerCase())
- )
- case "starts_with":
- return values.some((value) =>
- String(fieldValue)
- .toLowerCase()
- .startsWith(String(value).toLowerCase())
- )
- case "ends_with":
- return values.some((value) =>
- String(fieldValue)
- .toLowerCase()
- .endsWith(String(value).toLowerCase())
- )
- case "empty":
- return fieldValue === "" || fieldValue == null
- case "not_empty":
- return fieldValue !== "" && fieldValue != null
- default:
- return true
- }
- })
- })
-
- return result
-}
-
-const OWNER_TEAM_FILTER_OPTIONS = OWNER_FILTER_OPTIONS.filter(
- (option) => option.value !== "everyone"
-).map((option) => ({
- value: option.value,
- label: option.label,
-}))
-
-const DELIVERY_MODE_FILTER_OPTIONS = DELIVERY_FILTER_OPTIONS.filter(
- (option) => option.value !== "any"
-).map((option) => ({
- value: option.value,
- label: option.label,
-}))
-
-const UPDATED_BUCKET_FILTER_OPTIONS = UPDATED_FILTER_OPTIONS.filter(
- (option) => option.value !== "any"
-).map((option) => ({
- value: option.value,
- label: option.label,
-}))
-
-const filterFields: FilterFieldConfig[] = [
- {
- key: "workflow",
- label: "Workflow",
- icon: (
-
- ),
- type: "text",
- className: "w-52",
- placeholder: "Search...",
- },
- {
- key: "ownerTeam",
- label: "Owner team",
- icon: (
-
- ),
- type: "select",
- searchable: false,
- className: "w-[168px]",
- options: OWNER_TEAM_FILTER_OPTIONS,
- customValueRenderer: (values) =>
- renderSingleSelectedLabel(values, OWNER_TEAM_FILTER_OPTIONS),
- },
- {
- key: "deliveryMode",
- label: "Delivery mode",
- icon: (
-
- ),
- type: "select",
- searchable: false,
- className: "w-[168px]",
- options: DELIVERY_MODE_FILTER_OPTIONS,
- customValueRenderer: (values) =>
- renderSingleSelectedLabel(values, DELIVERY_MODE_FILTER_OPTIONS),
- },
- {
- key: "updatedBucket",
- label: "Last updated",
- icon: (
-
- ),
- type: "select",
- searchable: false,
- className: "w-[160px]",
- options: UPDATED_BUCKET_FILTER_OPTIONS,
- customValueRenderer: (values) =>
- renderSingleSelectedLabel(values, UPDATED_BUCKET_FILTER_OPTIONS),
- },
-]
-
-function createDefaultAutomationFilters(): Filter[] {
- return [createFilter("workflow", "contains", [""])]
-}
-
-function getTabCounts(records: IAutomationRecord[]) {
- return {
- all: records.length,
- live: records.filter((record) => record.state === "live").length,
- review: records.filter((record) => record.state === "review").length,
- drafts: records.filter((record) => record.state === "drafts").length,
- paused: records.filter((record) => record.state === "paused").length,
- } satisfies Record
-}
-
-export function AutomationLibraryGridView() {
- const [automations, setAutomations] =
- useState(AUTOMATIONS)
- const [activeTab, setActiveTab] = useState("all")
- const [filters, setFilters] = useState(
- createDefaultAutomationFilters
- )
- const [sorting, setSorting] = useState([
- { id: "updatedAt", desc: true },
- ])
- const [rowSelection, setRowSelection] = useState({})
- const [pagination, setPagination] = useState({
- pageIndex: 0,
- pageSize: 5,
- })
- const [automationPendingArchive, setAutomationPendingArchive] =
- useState(null)
-
- const resetPagination = useCallback(() => {
- setPagination((current) =>
- current.pageIndex === 0 ? current : { ...current, pageIndex: 0 }
- )
- }, [])
-
- const filteredBaseAutomations = useMemo(() => {
- return applyFiltersToData(automations, filters)
- }, [automations, filters])
-
- const filteredAutomations = useMemo(
- () =>
- filteredBaseAutomations.filter((automation) =>
- activeTab === "all"
- ? true
- : getAutomationTabFromState(automation.state) === activeTab
- ),
- [activeTab, filteredBaseAutomations]
- )
-
- const tabCounts = useMemo(
- () => getTabCounts(filteredBaseAutomations),
- [filteredBaseAutomations]
- )
-
- const filteredLiveCount = useMemo(
- () =>
- filteredAutomations.filter((automation) => automation.state === "live")
- .length,
- [filteredAutomations]
- )
-
- const filteredReviewCount = useMemo(
- () =>
- filteredAutomations.filter((automation) => automation.state === "review")
- .length,
- [filteredAutomations]
- )
-
- const selectedCount = useMemo(
- () => Object.keys(rowSelection).length,
- [rowSelection]
- )
-
- const columns = useMemo(
- () =>
- createAutomationColumns({
- onAction: (action, automation) => {
- if (action === "archive") {
- setAutomationPendingArchive(automation)
- return
- }
-
- if (action === "edit") {
- showAutomationToast({
- tone: "neutral",
- title: "Workflow editor",
- description: `Connect "${automation.title}" to your builder, side panel, or automation step editor.`,
- })
- return
- }
-
- showAutomationToast({
- tone: "success",
- title: "Workflow details",
- description: `"${automation.title}" is ready for a detail route, run history drawer, or audit panel.`,
- })
- },
- onToggleEnabled: (automation, nextValue) => {
- const nextState: AutomationState =
- nextValue && automation.state === "paused"
- ? "live"
- : nextValue && automation.state === "drafts"
- ? "review"
- : !nextValue && automation.state === "live"
- ? "paused"
- : automation.state
-
- setAutomations((current) =>
- current.map((item) =>
- item.id === automation.id
- ? {
- ...item,
- enabled: nextValue,
- state: nextState,
- }
- : item
- )
- )
-
- showAutomationToast({
- tone: nextValue ? "success" : "neutral",
- title: nextValue ? "Workflow enabled" : "Workflow paused",
- description: nextValue
- ? `"${automation.title}" is ready to run in the ${nextState === "review" ? "review" : "live"} queue.`
- : `"${automation.title}" will stay available but will not continue running until resumed.`,
- })
- },
- }),
- []
- )
-
- const table = useReactTable({
- data: filteredAutomations,
- columns,
- getRowId: (row) => row.id,
- state: {
- sorting,
- rowSelection,
- pagination,
- },
- enableRowSelection: true,
- onSortingChange: setSorting,
- onRowSelectionChange: setRowSelection,
- onPaginationChange: setPagination,
- getCoreRowModel: getCoreRowModel(),
- getSortedRowModel: getSortedRowModel(),
- getPaginationRowModel: getPaginationRowModel(),
- })
-
- const handleClearControls = useCallback(() => {
- setFilters(createDefaultAutomationFilters())
- resetPagination()
- }, [resetPagination])
-
- const handleFiltersChange = useCallback(
- (nextFilters: Filter[]) => {
- setFilters(nextFilters)
- resetPagination()
- },
- [resetPagination]
- )
-
- const handleArchiveAutomation = useCallback(() => {
- if (!automationPendingArchive) return
-
- const automationToArchive = automationPendingArchive
-
- setAutomations((current) =>
- current.filter(
- (automation) => automation.id !== automationPendingArchive.id
- )
- )
- setRowSelection((current) => {
- const next = { ...current }
- delete next[automationPendingArchive.id]
- return next
- })
- setAutomationPendingArchive(null)
- resetPagination()
-
- showAutomationToast({
- tone: "destructive",
- title: "Workflow archived",
- description: `"${automationToArchive.title}" was removed from this automation library.`,
- })
- }, [automationPendingArchive, resetPagination])
-
- const emptyMessage =
- "No workflows match this automation slice. Switch tabs or clear the filters."
-
- return (
- <>
- {/* Table */}
-
-
-
-
-
- Automation Library
-
-
-
- {filteredAutomations.length} workflow
- {filteredAutomations.length === 1 ? "" : "s"}
-
-
- {filteredLiveCount} live
-
- {filteredReviewCount} review
- {selectedCount > 0 ? (
- <>
-
- {selectedCount} selected
- >
- ) : null}
-
-
-
-
-
-
-
-
- {
- setActiveTab(value as AutomationTab)
- resetPagination()
- }}
- >
-
- {AUTOMATION_TABS.map((tab) => (
-
- {tab.label}
-
- {tabCounts[tab.value]}
-
-
- ))}
-
-
-
-
-
-
-
-
-
- Filters
-
- }
- />
-
-
- {selectedCount > 0 ? (
-
- {selectedCount} selected
-
- ) : null}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {
- if (!open) {
- setAutomationPendingArchive(null)
- }
- }}
- >
-
-
- Archive workflow?
-
- {automationPendingArchive
- ? `Archive "${automationPendingArchive.title}" from this automation library. Run history and ownership context can stay available in your backend, but this row will disappear from the grid preview.`
- : "Archive this workflow from the automation library."}
-
-
-
- Cancel
-
- Archive
-
- }
- />
-
-
-
- >
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/data-grid-filtering-2/components/data.tsx b/apps/web/src/components/blocks/data-grid-filtering-2/components/data.tsx
deleted file mode 100644
index 2ca5e30..0000000
--- a/apps/web/src/components/blocks/data-grid-filtering-2/components/data.tsx
+++ /dev/null
@@ -1,384 +0,0 @@
-export type AutomationTab = "all" | "live" | "review" | "drafts" | "paused"
-
-export type AutomationKind =
- | "sequence"
- | "routing"
- | "enrichment"
- | "digest"
- | "escalation"
-
-export type OwnerFilter =
- | "everyone"
- | "product"
- | "engineering"
- | "operations"
- | "revenue"
- | "support"
-
-export type DeliveryFilter =
- | "any"
- | "scheduled"
- | "event-driven"
- | "manual"
- | "hybrid"
-
-export type UpdatedFilter = "any" | "today" | "this-week" | "older"
-
-export type AutomationState = Exclude
-export type AutomationOwnerAvailability = "online" | "away" | "busy" | "offline"
-
-export interface IAutomationOwner {
- id: string
- name: string
- email: string
- initials: string
- avatar?: string
- availability: AutomationOwnerAvailability
- team: Exclude
- teamLabel: string
-}
-
-export interface IAutomationRecord {
- id: string
- title: string
- kind: AutomationKind
- state: AutomationState
- rating: number
- deliveryMode: Exclude
- owner: IAutomationOwner
- updatedAt: string
- updatedBucket: Exclude
- enabled: boolean
- approvalRequired: boolean
- audienceLabel: string
- runWindowLabel: string
-}
-
-const OWNERS: Record = {
- maya: {
- id: "maya-patel",
- name: "Maya Patel",
- email: "maya@reui.io",
- initials: "MP",
- avatar:
- "https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=96&h=96&dpr=2&q=80",
- availability: "online",
- team: "product",
- teamLabel: "Product",
- },
- jonas: {
- id: "jonas-reed",
- name: "Jonas Reed",
- email: "jonas@reui.io",
- initials: "JR",
- avatar:
- "https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=96&h=96&dpr=2&q=80",
- availability: "busy",
- team: "engineering",
- teamLabel: "Engineering",
- },
- priya: {
- id: "priya-nair",
- name: "Priya Nair",
- email: "priya@reui.io",
- initials: "PN",
- avatar:
- "https://images.unsplash.com/photo-1517841905240-472988babdf9?w=96&h=96&dpr=2&q=80",
- availability: "away",
- team: "operations",
- teamLabel: "Operations",
- },
- emil: {
- id: "emil-novak",
- name: "Emil Novak",
- email: "emil@reui.io",
- initials: "EN",
- avatar:
- "https://images.unsplash.com/photo-1560250097-0b93528c311a?w=96&h=96&dpr=2&q=80",
- availability: "offline",
- team: "revenue",
- teamLabel: "Revenue",
- },
- nora: {
- id: "nora-ibrahim",
- name: "Nora Ibrahim",
- email: "nora@reui.io",
- initials: "NI",
- avatar:
- "https://images.unsplash.com/photo-1544005313-94ddf0286df2?w=96&h=96&dpr=2&q=80",
- availability: "online",
- team: "support",
- teamLabel: "Support",
- },
-}
-
-function automation(
- input: Omit & {
- owner: keyof typeof OWNERS
- }
-): IAutomationRecord {
- return {
- ...input,
- owner: OWNERS[input.owner],
- }
-}
-
-export const AUTOMATION_TABS: { value: AutomationTab; label: string }[] = [
- { value: "all", label: "All" },
- { value: "live", label: "Live" },
- { value: "review", label: "Needs Review" },
- { value: "drafts", label: "Drafts" },
- { value: "paused", label: "Paused" },
-]
-
-export const OWNER_FILTER_OPTIONS: {
- value: OwnerFilter
- label: string
-}[] = [
- { value: "everyone", label: "Owner team" },
- { value: "product", label: "Product" },
- { value: "engineering", label: "Engineering" },
- { value: "operations", label: "Operations" },
- { value: "revenue", label: "Revenue" },
- { value: "support", label: "Support" },
-]
-
-export const DELIVERY_FILTER_OPTIONS: {
- value: DeliveryFilter
- label: string
-}[] = [
- { value: "any", label: "Delivery mode" },
- { value: "scheduled", label: "Scheduled" },
- { value: "event-driven", label: "Event-driven" },
- { value: "manual", label: "Manual" },
- { value: "hybrid", label: "Hybrid" },
-]
-
-export const UPDATED_FILTER_OPTIONS: {
- value: UpdatedFilter
- label: string
-}[] = [
- { value: "any", label: "Last updated" },
- { value: "today", label: "Today" },
- { value: "this-week", label: "This week" },
- { value: "older", label: "Older" },
-]
-
-export function getAutomationTabFromState(
- state: AutomationState
-): Exclude {
- return state
-}
-
-export const AUTOMATIONS: IAutomationRecord[] = [
- automation({
- id: "renewal-touchpoint-orchestration",
- title: "Renewal touchpoint orchestration",
- kind: "sequence",
- state: "live",
- rating: 4.8,
- deliveryMode: "scheduled",
- owner: "emil",
- updatedAt: "2026-04-11",
- updatedBucket: "today",
- enabled: true,
- approvalRequired: false,
- audienceLabel: "Renewal accounts",
- runWindowLabel: "Weekdays 09:00",
- }),
- automation({
- id: "delegated-sender-review-route",
- title: "Delegated sender review route",
- kind: "routing",
- state: "review",
- rating: 4.2,
- deliveryMode: "manual",
- owner: "priya",
- updatedAt: "2026-04-11",
- updatedBucket: "today",
- enabled: false,
- approvalRequired: true,
- audienceLabel: "Delegated senders",
- runWindowLabel: "Queue-based release",
- }),
- automation({
- id: "launch-handoff-digest",
- title: "Launch handoff digest",
- kind: "digest",
- state: "live",
- rating: 4.7,
- deliveryMode: "scheduled",
- owner: "maya",
- updatedAt: "2026-04-10",
- updatedBucket: "this-week",
- enabled: true,
- approvalRequired: false,
- audienceLabel: "Launch squad",
- runWindowLabel: "Daily 08:30",
- }),
- automation({
- id: "sla-escalation-watch",
- title: "SLA escalation watch",
- kind: "escalation",
- state: "live",
- rating: 4.9,
- deliveryMode: "event-driven",
- owner: "nora",
- updatedAt: "2026-04-10",
- updatedBucket: "this-week",
- enabled: true,
- approvalRequired: false,
- audienceLabel: "Priority tickets",
- runWindowLabel: "On trigger",
- }),
- automation({
- id: "lead-enrichment-pass",
- title: "Lead enrichment pass",
- kind: "enrichment",
- state: "paused",
- rating: 3.9,
- deliveryMode: "hybrid",
- owner: "jonas",
- updatedAt: "2026-04-09",
- updatedBucket: "this-week",
- enabled: false,
- approvalRequired: false,
- audienceLabel: "Inbound pipeline",
- runWindowLabel: "Hourly batch",
- }),
- automation({
- id: "sandbox-onboarding-sequence",
- title: "Sandbox onboarding sequence",
- kind: "sequence",
- state: "drafts",
- rating: 4.1,
- deliveryMode: "scheduled",
- owner: "priya",
- updatedAt: "2026-04-08",
- updatedBucket: "this-week",
- enabled: false,
- approvalRequired: false,
- audienceLabel: "Trial workspaces",
- runWindowLabel: "Pending QA",
- }),
- automation({
- id: "partner-routing-fallback",
- title: "Partner routing fallback",
- kind: "routing",
- state: "live",
- rating: 4.4,
- deliveryMode: "hybrid",
- owner: "emil",
- updatedAt: "2026-04-07",
- updatedBucket: "this-week",
- enabled: true,
- approvalRequired: false,
- audienceLabel: "Partner renewals",
- runWindowLabel: "Live + nightly",
- }),
- automation({
- id: "weekly-adoption-digest",
- title: "Weekly adoption digest",
- kind: "digest",
- state: "paused",
- rating: 3.8,
- deliveryMode: "scheduled",
- owner: "maya",
- updatedAt: "2026-04-06",
- updatedBucket: "this-week",
- enabled: false,
- approvalRequired: false,
- audienceLabel: "Workspace champions",
- runWindowLabel: "Fridays 16:00",
- }),
- automation({
- id: "enterprise-risk-escalation",
- title: "Enterprise risk escalation",
- kind: "escalation",
- state: "review",
- rating: 4.3,
- deliveryMode: "manual",
- owner: "nora",
- updatedAt: "2026-04-05",
- updatedBucket: "older",
- enabled: false,
- approvalRequired: true,
- audienceLabel: "Enterprise accounts",
- runWindowLabel: "Manual release",
- }),
- automation({
- id: "crm-enrichment-backfill",
- title: "CRM enrichment backfill",
- kind: "enrichment",
- state: "live",
- rating: 4.6,
- deliveryMode: "scheduled",
- owner: "jonas",
- updatedAt: "2026-04-04",
- updatedBucket: "older",
- enabled: true,
- approvalRequired: false,
- audienceLabel: "Open opportunities",
- runWindowLabel: "Nightly 01:00",
- }),
- automation({
- id: "trial-conversion-follow-up",
- title: "Trial conversion follow-up",
- kind: "sequence",
- state: "drafts",
- rating: 4.0,
- deliveryMode: "scheduled",
- owner: "emil",
- updatedAt: "2026-04-03",
- updatedBucket: "older",
- enabled: false,
- approvalRequired: false,
- audienceLabel: "Product-led signups",
- runWindowLabel: "Awaiting copy",
- }),
- automation({
- id: "owner-assignment-router",
- title: "Owner assignment router",
- kind: "routing",
- state: "live",
- rating: 4.5,
- deliveryMode: "event-driven",
- owner: "priya",
- updatedAt: "2026-04-02",
- updatedBucket: "older",
- enabled: true,
- approvalRequired: false,
- audienceLabel: "Workspace requests",
- runWindowLabel: "Immediate",
- }),
- automation({
- id: "ops-exception-digest",
- title: "Ops exception digest",
- kind: "digest",
- state: "review",
- rating: 4.1,
- deliveryMode: "scheduled",
- owner: "priya",
- updatedAt: "2026-04-11",
- updatedBucket: "today",
- enabled: false,
- approvalRequired: true,
- audienceLabel: "Ops leadership",
- runWindowLabel: "Daily 18:00",
- }),
- automation({
- id: "billing-retry-escalation",
- title: "Billing retry escalation",
- kind: "escalation",
- state: "live",
- rating: 4.7,
- deliveryMode: "event-driven",
- owner: "nora",
- updatedAt: "2026-04-01",
- updatedBucket: "older",
- enabled: true,
- approvalRequired: false,
- audienceLabel: "Recovery queue",
- runWindowLabel: "On failure",
- }),
-]
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/data-grid-filtering-2/page.tsx b/apps/web/src/components/blocks/data-grid-filtering-2/page.tsx
deleted file mode 100644
index e3ffcac..0000000
--- a/apps/web/src/components/blocks/data-grid-filtering-2/page.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { AutomationLibraryGridView } from "./components/data-grid-view"
-
-export function Page() {
- return (
-
-
- Automation library data grid
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/data-grid-filtering-3/components/columns.tsx b/apps/web/src/components/blocks/data-grid-filtering-3/components/columns.tsx
deleted file mode 100644
index a9a0f3b..0000000
--- a/apps/web/src/components/blocks/data-grid-filtering-3/components/columns.tsx
+++ /dev/null
@@ -1,685 +0,0 @@
-import { memo, useMemo, useState, type ComponentProps } from "react"
-import { Badge } from "@/components/reui/badge"
-import { DataGridColumnHeader } from "@/components/reui/data-grid/data-grid-column-header"
-import {
- DataGridTableRowSelect,
- DataGridTableRowSelectAll,
-} from "@/components/reui/data-grid/data-grid-table"
-import { Row, type ColumnDef } from "@tanstack/react-table"
-
-import { cn } from "@evobgp/ui/lib/utils"
-import {
- AlertDialog,
- AlertDialogAction,
- AlertDialogCancel,
- AlertDialogContent,
- AlertDialogDescription,
- AlertDialogFooter,
- AlertDialogHeader,
- AlertDialogTitle,
-} from "@evobgp/ui/components/alert-dialog"
-import {
- Avatar,
- AvatarFallback,
- AvatarImage,
-} from "@evobgp/ui/components/avatar"
-import { Button } from "@evobgp/ui/components/button"
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuGroup,
- DropdownMenuItem,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from "@evobgp/ui/components/dropdown-menu"
-import { Item, ItemMedia } from "@evobgp/ui/components/item"
-import {
- Progress,
- ProgressLabel,
- ProgressValue,
-} from "@evobgp/ui/components/progress"
-import { Skeleton } from "@evobgp/ui/components/skeleton"
-import {
- type IRenewalRecord,
- type RenewalInvoiceStatus,
- type RenewalRisk,
- type RenewalSponsorStatus,
- type RenewalStage,
-} from "./data"
-import { MoreHorizontalIcon, EyeIcon, FileTextIcon, TriangleAlertIcon } from "lucide-react"
-
-// Intl formatters are costly to construct; build them once and reuse for every
-// cell render.
-const CURRENCY_FORMATTER = new Intl.NumberFormat("en-US", {
- style: "currency",
- currency: "USD",
- maximumFractionDigits: 0,
-})
-
-const RENEWAL_DATE_FORMATTER = new Intl.DateTimeFormat("en-US", {
- month: "short",
- day: "numeric",
- year: "numeric",
-})
-
-function formatCurrency(value: number) {
- return CURRENCY_FORMATTER.format(value)
-}
-
-function formatRenewalDate(iso: string) {
- return RENEWAL_DATE_FORMATTER.format(new Date(iso))
-}
-
-const stageVariant: Record<
- RenewalStage,
- ComponentProps["variant"]
-> = {
- Preparing: "outline",
- "Commercial review": "outline",
- "Legal review": "outline",
- Committed: "outline",
-}
-
-const stageDot = {
- Preparing: "bg-muted-foreground",
- "Commercial review": "bg-sky-500",
- "Legal review": "bg-amber-500",
- Committed: "bg-emerald-500",
-} satisfies Record
-
-const riskVariant: Record<
- RenewalRisk,
- ComponentProps["variant"]
-> = {
- Low: "secondary",
- Medium: "info-light",
- High: "warning-light",
- Critical: "destructive-light",
-}
-
-const invoiceVariant: Record<
- RenewalInvoiceStatus,
- ComponentProps["variant"]
-> = {
- Ready: "success-light",
- "Finance review": "warning-light",
- Blocked: "destructive-light",
-}
-
-const sponsorVariant: Record<
- RenewalSponsorStatus,
- ComponentProps["variant"]
-> = {
- Confirmed: "success-outline",
- "At risk": "warning-outline",
- Missing: "destructive-outline",
-}
-
-export const StageBadge = memo(function StageBadge({
- stage,
-}: {
- stage: RenewalStage
-}) {
- return (
-
-
- {stage}
-
- )
-})
-
-export const RiskBadge = memo(function RiskBadge({
- risk,
-}: {
- risk: RenewalRisk
-}) {
- return {risk}
-})
-
-export const InvoiceStatusBadge = memo(function InvoiceStatusBadge({
- status,
-}: {
- status: RenewalInvoiceStatus
-}) {
- return {status}
-})
-
-export const SponsorStatusBadge = memo(function SponsorStatusBadge({
- status,
-}: {
- status: RenewalSponsorStatus
-}) {
- return {status}
-})
-
-function initials(name: string) {
- return name
- .split(" ")
- .map((part) => part[0])
- .join("")
-}
-
-const AccountCell = memo(function AccountCell({
- row,
-}: {
- row: Row
-}) {
- return (
-
-
-
-
- {row.original.accountLogo}
-
-
-
-
- {row.original.accountName}
-
-
-
-
-
- {initials(row.original.ownerName)}
-
-
-
{row.original.ownerName}
-
-
{row.original.segment}
-
-
-
- )
-})
-
-const RenewalWindowCell = memo(function RenewalWindowCell({
- row,
-}: {
- row: Row
-}) {
- const dueSoon = row.original.daysToRenewal <= 30
- const urgencyTone =
- row.original.daysToRenewal <= 14
- ? "text-destructive"
- : row.original.daysToRenewal <= 30
- ? "text-warning"
- : "text-foreground"
-
- return (
-
-
- {dueSoon
- ? `Due in ${row.original.daysToRenewal}d`
- : `${row.original.daysToRenewal}d out`}
-
-
- {formatRenewalDate(row.original.renewalDate)}
-
-
- )
-})
-
-const RevenueCell = memo(function RevenueCell({
- row,
-}: {
- row: Row
-}) {
- return (
-
-
- {formatCurrency(row.original.arr)}
-
- 0
- ? "text-emerald-600 dark:text-emerald-400"
- : "text-muted-foreground"
- )}
- >
- {row.original.expansionPotential > 0 ? "+" : ""}
- {formatCurrency(row.original.expansionPotential)} upside
-
-
- )
-})
-
-const HealthCell = memo(function HealthCell({
- row,
-}: {
- row: Row
-}) {
- const score = row.original.healthScore
- const label = score >= 75 ? "Healthy" : score >= 55 ? "Watchlist" : "At risk"
- const indicatorClass =
- score >= 75
- ? "**:data-[slot=progress-indicator]:bg-emerald-500"
- : score >= 55
- ? "**:data-[slot=progress-indicator]:bg-amber-500"
- : "**:data-[slot=progress-indicator]:bg-red-500"
-
- return (
-
-
-
{label}
-
- )
-})
-
-function buildSmoothLinePath(points: { x: number; y: number }[]) {
- if (points.length === 0) return ""
- if (points.length === 1) return `M ${points[0].x} ${points[0].y}`
-
- let path = `M ${points[0].x} ${points[0].y}`
-
- for (let index = 0; index < points.length - 1; index++) {
- const p0 = points[Math.max(0, index - 1)]
- const p1 = points[index]
- const p2 = points[index + 1]
- const p3 = points[Math.min(points.length - 1, index + 2)]
- const cp1x = p1.x + (p2.x - p0.x) / 6
- const cp1y = p1.y + (p2.y - p0.y) / 6
- const cp2x = p2.x - (p3.x - p1.x) / 6
- const cp2y = p2.y - (p3.y - p1.y) / 6
-
- path += ` C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${p2.x} ${p2.y}`
- }
-
- return path
-}
-
-const UsageTrendCell = memo(function UsageTrendCell({
- row,
-}: {
- row: Row
-}) {
- const data = row.original.usageTrend
- const width = 92
- const height = 26
- const padX = 2
- const padY = 2
- const innerW = width - padX * 2
- const innerH = height - padY * 2
-
- const { linePath, strokeClass, deltaLabel } = useMemo(() => {
- const max = Math.max(...data)
- const min = Math.min(...data)
- const range = max - min || 1
- const step = innerW / Math.max(1, data.length - 1)
-
- const points = data.map((value, index) => ({
- x: padX + index * step,
- y: padY + ((max - value) / range) * innerH,
- }))
-
- const delta = data[data.length - 1] - data[0]
-
- return {
- linePath: buildSmoothLinePath(points),
- strokeClass:
- delta > 0
- ? "stroke-emerald-600 dark:stroke-emerald-400"
- : delta < 0
- ? "stroke-red-600 dark:stroke-red-400"
- : "stroke-muted-foreground",
- deltaLabel:
- delta > 0
- ? `Expanding ${delta} pts`
- : delta < 0
- ? `Contracting ${Math.abs(delta)} pts`
- : "Stable",
- }
- }, [data, innerH, innerW, padX, padY])
-
- return (
-
- )
-})
-
-function RenewalActionsCell({
- row,
- onOpenAccount,
- onCreateBrief,
- onEscalateReview,
-}: {
- row: Row
- onOpenAccount: (renewal: IRenewalRecord) => void
- onCreateBrief: (renewal: IRenewalRecord) => void
- onEscalateReview: (renewal: IRenewalRecord) => void
-}) {
- const [escalateOpen, setEscalateOpen] = useState(false)
-
- return (
- <>
-
-
- }
- >
-
-
-
-
- onOpenAccount(row.original)}>
-
- Open account
-
- onCreateBrief(row.original)}>
-
- {row.original.stage === "Legal review"
- ? "Legal redlines"
- : row.original.invoiceStatus !== "Ready"
- ? "Finance sign-off"
- : "Renewal brief"}
-
-
- setEscalateOpen(true)}
- >
-
- Escalate
-
-
-
-
-
-
-
-
- Create an exec escalation?
-
- This will push{" "}
-
- {row.original.accountName}
- {" "}
- into an urgent board-review path in a real revenue-ops workspace.
-
-
-
- Cancel
- onEscalateReview(row.original)}>
- Create escalation
-
-
-
-
- >
- )
-}
-
-export function createRenewalColumns({
- onOpenAccount,
- onCreateBrief,
- onEscalateReview,
-}: {
- onOpenAccount: (renewal: IRenewalRecord) => void
- onCreateBrief: (renewal: IRenewalRecord) => void
- onEscalateReview: (renewal: IRenewalRecord) => void
-}) {
- return [
- {
- accessorKey: "id",
- id: "select",
- header: () => ,
- cell: ({ row }) => ,
- enableSorting: false,
- enableResizing: false,
- enableHiding: false,
- size: 36,
- meta: {
- headerClassName: "ps-4!",
- cellClassName: "ps-4!",
- },
- },
- {
- accessorKey: "accountName",
- id: "account",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => ,
- minSize: 280,
- size: 320,
- enableSorting: true,
- enableResizing: true,
- enableHiding: false,
- meta: {
- autoSize: true,
- skeleton: (
-
- ),
- },
- },
- {
- accessorFn: (row) => row.daysToRenewal,
- id: "renewalWindow",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => ,
- size: 150,
- minSize: 140,
- enableSorting: true,
- enableResizing: true,
- enableHiding: true,
- meta: {
- skeleton: (
-
-
-
-
- ),
- },
- },
- {
- accessorKey: "arr",
- id: "arr",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => ,
- size: 150,
- minSize: 140,
- enableSorting: true,
- enableResizing: true,
- enableHiding: true,
- meta: {
- skeleton: (
-
-
-
-
- ),
- },
- },
- {
- accessorKey: "healthScore",
- id: "health",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => ,
- size: 140,
- minSize: 130,
- enableSorting: true,
- enableResizing: true,
- enableHiding: true,
- },
- {
- accessorKey: "usageTrend",
- id: "usage",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => ,
- size: 120,
- minSize: 112,
- enableSorting: false,
- enableResizing: true,
- enableHiding: true,
- },
- {
- accessorKey: "stage",
- id: "stage",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => ,
- size: 168,
- minSize: 160,
- enableSorting: true,
- enableResizing: true,
- enableHiding: true,
- },
- {
- accessorKey: "risk",
- id: "risk",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => ,
- size: 116,
- minSize: 108,
- enableSorting: true,
- enableResizing: true,
- enableHiding: true,
- },
- {
- accessorKey: "invoiceStatus",
- id: "invoiceStatus",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => (
-
- ),
- size: 148,
- minSize: 138,
- enableSorting: true,
- enableResizing: true,
- enableHiding: true,
- },
- {
- accessorKey: "sponsorStatus",
- id: "sponsorStatus",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => (
-
- ),
- size: 136,
- minSize: 128,
- enableSorting: true,
- enableResizing: true,
- enableHiding: true,
- },
- {
- accessorKey: "region",
- id: "region",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => (
- {row.original.region}
- ),
- size: 124,
- minSize: 116,
- enableSorting: true,
- enableResizing: true,
- enableHiding: true,
- },
- {
- id: "actions",
- header: "",
- cell: ({ row }) => (
-
- ),
- enableSorting: false,
- enableResizing: false,
- enableHiding: false,
- size: 56,
- meta: {
- cellClassName: "pe-4!",
- headerClassName: "pe-4!",
- },
- },
- ] satisfies ColumnDef[]
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/data-grid-filtering-3/components/data-grid-view.tsx b/apps/web/src/components/blocks/data-grid-filtering-3/components/data-grid-view.tsx
deleted file mode 100644
index 6636d80..0000000
--- a/apps/web/src/components/blocks/data-grid-filtering-3/components/data-grid-view.tsx
+++ /dev/null
@@ -1,766 +0,0 @@
-"use client"
-
-import { useCallback, useMemo, useState } from "react"
-import { DataGrid } from "@/components/reui/data-grid/data-grid"
-import { DataGridScrollArea } from "@/components/reui/data-grid/data-grid-scroll-area"
-import { DataGridTable } from "@/components/reui/data-grid/data-grid-table"
-import {
- createFilter,
- Filters,
- type Filter,
- type FilterFieldConfig,
-} from "@/components/reui/filters"
-import {
- getCoreRowModel,
- getSortedRowModel,
- useReactTable,
- type RowSelectionState,
- type SortingState,
- type VisibilityState,
-} from "@tanstack/react-table"
-import { toast } from "sonner"
-
-import { cn } from "@evobgp/ui/lib/utils"
-import { Button } from "@evobgp/ui/components/button"
-import {
- Field,
- FieldGroup,
- FieldLabel,
- FieldSeparator,
-} from "@evobgp/ui/components/field"
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from "@evobgp/ui/components/popover"
-import {
- Select,
- SelectContent,
- SelectGroup,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@evobgp/ui/components/select"
-import { Separator } from "@evobgp/ui/components/separator"
-import { Switch } from "@evobgp/ui/components/switch"
-import { TooltipProvider } from "@evobgp/ui/components/tooltip"
-import { createRenewalColumns, RiskBadge, StageBadge } from "./columns"
-import {
- getRenewalWindowValue,
- RENEWAL_OWNERS,
- RENEWAL_RECORDS,
- RENEWAL_RISK_ORDER,
- RENEWAL_SEGMENT_ORDER,
- RENEWAL_STAGE_ORDER,
- RENEWAL_WINDOW_OPTIONS,
- type IRenewalRecord,
- type RenewalRisk,
- type RenewalStage,
-} from "./data"
-import { RenewalSelectionBar } from "./renewal-selection-bar"
-import { RenewalsCommandCard } from "./renewals-command-card"
-import { FilterIcon, Settings2Icon, CheckIcon, Building2Icon, LayersIcon, GitBranchIcon, TriangleAlertIcon, CalendarClockIcon, UserRoundIcon, PlusIcon } from "lucide-react"
-
-function getActiveFilters(filters: Filter[]) {
- return filters.filter((filter) => {
- const { values } = filter
- if (!values || values.length === 0) return false
- if (
- values.every((value) => typeof value === "string" && value.trim() === "")
- ) {
- return false
- }
- if (values.every((value) => value === null || value === undefined)) {
- return false
- }
- if (values.every((value) => Array.isArray(value) && value.length === 0)) {
- return false
- }
- return true
- })
-}
-
-function filterFieldValue(item: IRenewalRecord, field: string): unknown {
- if (field === "renewalWindow") {
- return getRenewalWindowValue(item.daysToRenewal)
- }
- return item[field as keyof IRenewalRecord]
-}
-
-function applyFiltersToData(
- data: IRenewalRecord[],
- filters: Filter[]
-): IRenewalRecord[] {
- const active = getActiveFilters(filters)
- let result = [...data]
-
- active.forEach((filter) => {
- const { field, operator, values } = filter
-
- result = result.filter((item) => {
- const raw = filterFieldValue(item, field)
- const fieldValue = raw != null ? raw : ""
-
- switch (operator) {
- case "is":
- return values.includes(fieldValue)
- case "is_not":
- return !values.includes(fieldValue)
- case "is_any_of":
- return values.some((value) => fieldValue === value)
- case "is_not_any_of":
- return !values.some((value) => fieldValue === value)
- case "contains": {
- const tokens = values
- .map((value) => String(value).trim())
- .filter(Boolean)
- if (tokens.length === 0) return true
- return tokens.some((token) =>
- String(fieldValue).toLowerCase().includes(token.toLowerCase())
- )
- }
- case "not_contains":
- return !values.some((value) =>
- String(fieldValue)
- .toLowerCase()
- .includes(String(value).toLowerCase())
- )
- case "starts_with":
- return values.some((value) =>
- String(fieldValue)
- .toLowerCase()
- .startsWith(String(value).toLowerCase())
- )
- case "ends_with":
- return values.some((value) =>
- String(fieldValue)
- .toLowerCase()
- .endsWith(String(value).toLowerCase())
- )
- case "equals":
- return fieldValue === values[0]
- case "not_equals":
- return fieldValue !== values[0]
- case "greater_than":
- return Number(fieldValue) > Number(values[0])
- case "less_than":
- return Number(fieldValue) < Number(values[0])
- case "greater_than_or_equal":
- return Number(fieldValue) >= Number(values[0])
- case "less_than_or_equal":
- return Number(fieldValue) <= Number(values[0])
- case "between":
- if (values.length >= 2) {
- const min = Number(values[0])
- const max = Number(values[1])
- return Number(fieldValue) >= min && Number(fieldValue) <= max
- }
- return true
- case "not_between":
- if (values.length >= 2) {
- const min = Number(values[0])
- const max = Number(values[1])
- return Number(fieldValue) < min || Number(fieldValue) > max
- }
- return true
- case "empty":
- return fieldValue === "" || fieldValue == null
- case "not_empty":
- return fieldValue !== "" && fieldValue != null
- default:
- return true
- }
- })
- })
-
- return result
-}
-
-function renderSelectedCount(values: unknown[]) {
- if (values.length === 0) return "Select..."
- if (values.length > 1) return `${values.length} selected`
- return null
-}
-
-function formatCompactCurrency(value: number) {
- return new Intl.NumberFormat("en-US", {
- style: "currency",
- currency: "USD",
- notation: "compact",
- maximumFractionDigits: 1,
- }).format(value)
-}
-
-function createDefaultRenewalFilters(): Filter[] {
- return [createFilter("accountName", "contains", [""])]
-}
-
-const DEFAULT_COLUMN_ORDER = [
- "select",
- "account",
- "renewalWindow",
- "arr",
- "health",
- "usage",
- "stage",
- "risk",
- "invoiceStatus",
- "sponsorStatus",
- "region",
- "actions",
-]
-
-type TableDensity = "compact" | "comfortable"
-type DisplayColumn =
- | "health"
- | "usage"
- | "stage"
- | "risk"
- | "invoiceStatus"
- | "sponsorStatus"
- | "region"
-
-const TABLE_DENSITY_OPTIONS: { value: TableDensity; label: string }[] = [
- { value: "compact", label: "Compact" },
- { value: "comfortable", label: "Comfortable" },
-]
-
-const DISPLAY_COLUMNS: { key: DisplayColumn; label: string }[] = [
- { key: "health", label: "Health" },
- { key: "usage", label: "Usage" },
- { key: "stage", label: "Stage" },
- { key: "risk", label: "Risk" },
- { key: "invoiceStatus", label: "Invoice" },
- { key: "sponsorStatus", label: "Sponsor" },
- { key: "region", label: "Region" },
-]
-
-interface ToolbarProps {
- filters: Filter[]
- fields: FilterFieldConfig[]
- onFiltersChange: (filters: Filter[]) => void
- onClearFilters: () => void
- showClearButton: boolean
- tableDensity: TableDensity
- onTableDensityChange: (value: TableDensity) => void
- columnsResizable: boolean
- onColumnsResizableChange: (value: boolean) => void
- columnsMovable: boolean
- onColumnsMovableChange: (value: boolean) => void
- visibleColumns: Record
- onToggleColumn: (columnId: DisplayColumn) => void
-}
-
-function Toolbar({
- filters,
- fields,
- onFiltersChange,
- onClearFilters,
- showClearButton,
- tableDensity,
- onTableDensityChange,
- columnsResizable,
- onColumnsResizableChange,
- columnsMovable,
- onColumnsMovableChange,
- visibleColumns,
- onToggleColumn,
-}: ToolbarProps) {
- return (
-
- {/* Actions */}
-
-
-
- Filters
-
- }
- />
-
- {showClearButton ? (
-
- ) : null}
-
-
-
-
-
-
- Settings
-
- }
- />
-
-
-
-
- Table
-
-
-
-
- Density
-
-
-
-
-
-
- Resizable columns
-
-
-
-
-
-
- Movable columns
-
-
-
-
-
-
-
-
-
-
- Display columns
-
-
- {DISPLAY_COLUMNS.map((column) => {
- const active = visibleColumns[column.key]
-
- return (
-
- )
- })}
-
-
-
-
-
-
-
- )
-}
-
-export function RenewalsCommandGridView() {
- const [renewals, setRenewals] = useState(RENEWAL_RECORDS)
- const [tableDensity, setTableDensity] = useState("compact")
- const [columnsResizable, setColumnsResizable] = useState(true)
- const [columnsMovable, setColumnsMovable] = useState(true)
- const [sorting, setSorting] = useState([
- { id: "renewalWindow", desc: false },
- { id: "arr", desc: true },
- ])
- const [columnVisibility, setColumnVisibility] = useState({
- health: true,
- stage: true,
- risk: true,
- usage: false,
- invoiceStatus: false,
- sponsorStatus: false,
- region: false,
- })
- const [rowSelection, setRowSelection] = useState({})
- const [columnOrder, setColumnOrder] = useState(DEFAULT_COLUMN_ORDER)
- const [filters, setFilters] = useState(createDefaultRenewalFilters)
- const [bulkOwnerValue, setBulkOwnerValue] = useState(RENEWAL_OWNERS[0].value)
- const [bulkStageValue, setBulkStageValue] =
- useState("Commercial review")
-
- const filterFields: FilterFieldConfig[] = useMemo(
- () => [
- {
- key: "accountName",
- label: "Account",
- icon: (
-
- ),
- type: "text",
- className: "w-[200px]",
- placeholder: "Search...",
- },
- {
- key: "segment",
- label: "Segment",
- icon: (
-
- ),
- type: "select",
- searchable: false,
- className: "w-[160px]",
- options: RENEWAL_SEGMENT_ORDER.map((segment) => ({
- value: segment,
- label: segment,
- })),
- },
- {
- key: "stage",
- label: "Stage",
- icon: (
-
- ),
- type: "select",
- searchable: false,
- className: "w-[180px]",
- options: RENEWAL_STAGE_ORDER.map((stage) => ({
- value: stage,
- label: stage,
- })),
- customValueRenderer: (values) => {
- const state = renderSelectedCount(values)
- if (state) return state
- return
- },
- },
- {
- key: "risk",
- label: "Risk",
- icon: (
-
- ),
- type: "select",
- searchable: false,
- className: "w-[140px]",
- options: RENEWAL_RISK_ORDER.map((risk) => ({
- value: risk,
- label: risk,
- })),
- customValueRenderer: (values) => {
- const state = renderSelectedCount(values)
- if (state) return state
- return
- },
- },
- {
- key: "renewalWindow",
- label: "Renewal window",
- icon: (
-
- ),
- type: "select",
- searchable: false,
- className: "w-[170px]",
- options: RENEWAL_WINDOW_OPTIONS.map((option) => ({
- value: option.value,
- label: option.label,
- })),
- },
- {
- key: "ownerName",
- label: "Owner",
- icon: (
-
- ),
- type: "select",
- searchable: false,
- className: "w-[170px]",
- options: RENEWAL_OWNERS.map((owner) => ({
- value: owner.label,
- label: owner.label,
- })),
- },
- ],
- []
- )
-
- const filteredData = useMemo(
- () => applyFiltersToData(renewals, filters),
- [filters, renewals]
- )
- const visibleColumns = useMemo>(
- () => ({
- health: columnVisibility.health !== false,
- usage: columnVisibility.usage !== false,
- stage: columnVisibility.stage !== false,
- risk: columnVisibility.risk !== false,
- invoiceStatus: columnVisibility.invoiceStatus !== false,
- sponsorStatus: columnVisibility.sponsorStatus !== false,
- region: columnVisibility.region !== false,
- }),
- [columnVisibility]
- )
-
- const dueInThirtyCount = useMemo(
- () => filteredData.filter((renewal) => renewal.daysToRenewal <= 30).length,
- [filteredData]
- )
-
- const blockerCount = useMemo(
- () =>
- filteredData.filter(
- (renewal) =>
- renewal.invoiceStatus !== "Ready" ||
- renewal.sponsorStatus !== "Confirmed"
- ).length,
- [filteredData]
- )
-
- const arrAtRisk = useMemo(
- () =>
- filteredData.reduce(
- (total, renewal) =>
- renewal.risk === "High" || renewal.risk === "Critical"
- ? total + renewal.arr
- : total,
- 0
- ),
- [filteredData]
- )
-
- const handleOpenAccount = useCallback((renewal: IRenewalRecord) => {
- toast.info("Open account workspace", {
- description: `${renewal.accountName} · ${renewal.ownerName}`,
- })
- }, [])
-
- const handleCreateBrief = useCallback((renewal: IRenewalRecord) => {
- toast.success("Renewal brief created", {
- description: `${renewal.accountName} is ready for executive review.`,
- })
- }, [])
-
- const handleEscalateReview = useCallback((renewal: IRenewalRecord) => {
- setRenewals((current) =>
- current.map((record) =>
- record.id === renewal.id
- ? {
- ...record,
- risk: "Critical",
- stage:
- record.stage === "Committed" ? record.stage : "Legal review",
- }
- : record
- )
- )
-
- toast.success("Exec escalation created", {
- description: `${renewal.accountName} moved into an urgent board-review track.`,
- })
- }, [])
-
- const columns = useMemo(
- () =>
- createRenewalColumns({
- onOpenAccount: handleOpenAccount,
- onCreateBrief: handleCreateBrief,
- onEscalateReview: handleEscalateReview,
- }),
- [handleCreateBrief, handleEscalateReview, handleOpenAccount]
- )
-
- const table = useReactTable({
- columns,
- data: filteredData,
- getRowId: (row) => row.id,
- state: {
- sorting,
- columnVisibility,
- columnOrder,
- rowSelection,
- },
- enableRowSelection: true,
- columnResizeMode: "onChange",
- onSortingChange: setSorting,
- onColumnVisibilityChange: setColumnVisibility,
- onColumnOrderChange: setColumnOrder,
- onRowSelectionChange: setRowSelection,
- getCoreRowModel: getCoreRowModel(),
- getSortedRowModel: getSortedRowModel(),
- })
-
- const selectedRows = table
- .getSelectedRowModel()
- .rows.map((row) => row.original.id)
-
- const selectedCount = selectedRows.length
- const showClearButton = filters.length > 0
-
- const handleFiltersChange = useCallback((nextFilters: Filter[]) => {
- setFilters(nextFilters)
- }, [])
-
- const handleClearFilters = useCallback(() => {
- setFilters(createDefaultRenewalFilters())
- }, [])
-
- const handleClearSelection = useCallback(() => {
- setRowSelection({})
- }, [])
-
- const handleToggleColumn = useCallback((columnId: DisplayColumn) => {
- setColumnVisibility((current) => ({
- ...current,
- [columnId]: current[columnId] === false,
- }))
- }, [])
-
- const handleApplySelected = useCallback(() => {
- if (selectedRows.length === 0) return
-
- const nextOwner = RENEWAL_OWNERS.find(
- (owner) => owner.value === bulkOwnerValue
- )
- if (!nextOwner) return
-
- setRenewals((current) =>
- current.map((renewal) =>
- selectedRows.includes(renewal.id)
- ? {
- ...renewal,
- ownerName: nextOwner.label,
- ownerAvatar: nextOwner.avatar,
- stage: bulkStageValue,
- }
- : renewal
- )
- )
-
- setRowSelection({})
- toast.success("Renewals updated", {
- description: `${selectedRows.length} account${selectedRows.length === 1 ? "" : "s"} moved to ${nextOwner.label} and ${bulkStageValue}.`,
- })
- }, [bulkOwnerValue, bulkStageValue, selectedRows])
-
- return (
-
- {/* Table */}
-
-
- toast.success("Transaction started", {
- description: "New renewal entry opened.",
- })
- }
- >
-
- New transaction
-
- }
- >
-
-
- {selectedCount > 0 ? (
-
- ) : (
-
- )}
-
-
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/data-grid-filtering-3/components/data.tsx b/apps/web/src/components/blocks/data-grid-filtering-3/components/data.tsx
deleted file mode 100644
index c9d4921..0000000
--- a/apps/web/src/components/blocks/data-grid-filtering-3/components/data.tsx
+++ /dev/null
@@ -1,738 +0,0 @@
-import { type ReactNode } from "react"
-
-import { AnthropicBlack } from "@evobgp/ui/components/svgs/anthropicBlack"
-import { AnthropicWhite } from "@evobgp/ui/components/svgs/anthropicWhite"
-import { Convex } from "@evobgp/ui/components/svgs/convex"
-import { GoogleCloud } from "@evobgp/ui/components/svgs/googleCloud"
-import { Hono } from "@evobgp/ui/components/svgs/hono"
-import { Mintlify } from "@evobgp/ui/components/svgs/mintlify"
-import { N8n } from "@evobgp/ui/components/svgs/n8n"
-import { Neon } from "@evobgp/ui/components/svgs/neon"
-import { Openai } from "@evobgp/ui/components/svgs/openai"
-import { OpenaiDark } from "@evobgp/ui/components/svgs/openaiDark"
-import { Paper } from "@evobgp/ui/components/svgs/paper"
-import { Planetscale } from "@evobgp/ui/components/svgs/planetscale"
-import { PlanetscaleDark } from "@evobgp/ui/components/svgs/planetscaleDark"
-import { Prisma } from "@evobgp/ui/components/svgs/prisma"
-import { PrismaDark } from "@evobgp/ui/components/svgs/prismaDark"
-import { RemixDark } from "@evobgp/ui/components/svgs/remixDark"
-import { RemixLight } from "@evobgp/ui/components/svgs/remixLight"
-import { ResendIconBlack } from "@evobgp/ui/components/svgs/resendIconBlack"
-import { ResendIconWhite } from "@evobgp/ui/components/svgs/resendIconWhite"
-import { Slack } from "@evobgp/ui/components/svgs/slack"
-import { Stripe } from "@evobgp/ui/components/svgs/stripe"
-import { Supabase } from "@evobgp/ui/components/svgs/supabase"
-import { Zoom } from "@evobgp/ui/components/svgs/zoom"
-
-export type RenewalStage =
- | "Preparing"
- | "Commercial review"
- | "Legal review"
- | "Committed"
-
-export type RenewalRisk = "Low" | "Medium" | "High" | "Critical"
-
-export type RenewalSegment = "Enterprise" | "Scale" | "Mid-market"
-
-export type RenewalInvoiceStatus = "Ready" | "Finance review" | "Blocked"
-
-export type RenewalSponsorStatus = "Confirmed" | "At risk" | "Missing"
-
-export interface RenewalOwnerOption {
- value: string
- label: string
- avatar: string
- teamLabel: string
-}
-
-export interface IRenewalRecord {
- id: string
- accountName: string
- accountLogo: ReactNode
- ownerName: string
- ownerAvatar: string
- segment: RenewalSegment
- region: string
- renewalDate: string
- daysToRenewal: number
- arr: number
- expansionPotential: number
- healthScore: number
- usageTrend: number[]
- stage: RenewalStage
- risk: RenewalRisk
- invoiceStatus: RenewalInvoiceStatus
- sponsorStatus: RenewalSponsorStatus
- tags: string[]
-}
-
-export const RENEWAL_STAGE_ORDER: RenewalStage[] = [
- "Preparing",
- "Commercial review",
- "Legal review",
- "Committed",
-]
-
-export const RENEWAL_RISK_ORDER: RenewalRisk[] = [
- "Low",
- "Medium",
- "High",
- "Critical",
-]
-
-export const RENEWAL_SEGMENT_ORDER: RenewalSegment[] = [
- "Enterprise",
- "Scale",
- "Mid-market",
-]
-
-export const RENEWAL_WINDOW_OPTIONS = [
- { value: "next-30", label: "Next 30 days" },
- { value: "31-60", label: "31-60 days" },
- { value: "61-90", label: "61-90 days" },
- { value: "90-plus", label: "90+ days" },
-] as const
-
-const OPENAI_LOGO = (
- <>
-
-
-
-
-
-
- >
-)
-
-const ANTHROPIC_LOGO = (
- <>
-
-
-
-
-
-
- >
-)
-
-const PRISMA_LOGO = (
- <>
-
-
-
-
-
-
- >
-)
-
-const PLANETSCALE_LOGO = (
- <>
-
-
-
-
-
-
- >
-)
-
-const RESEND_LOGO = (
- <>
-
-
-
-
-
-
- >
-)
-
-const REMIX_LOGO = (
- <>
-
-
-
-
-
-
- >
-)
-
-function VercelMark() {
- return (
-
- )
-}
-
-export function getRenewalWindowValue(daysToRenewal: number) {
- if (daysToRenewal <= 30) return "next-30"
- if (daysToRenewal <= 60) return "31-60"
- if (daysToRenewal <= 90) return "61-90"
- return "90-plus"
-}
-
-export function getRenewalWindowLabel(daysToRenewal: number) {
- const match = RENEWAL_WINDOW_OPTIONS.find(
- (option) => option.value === getRenewalWindowValue(daysToRenewal)
- )
-
- return match?.label ?? "90+ days"
-}
-
-export const RENEWAL_OWNERS: RenewalOwnerOption[] = [
- {
- value: "maya-patel",
- label: "Maya Patel",
- avatar:
- "https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=96&h=96&dpr=2&q=80",
- teamLabel: "Enterprise coverage",
- },
- {
- value: "jonah-lee",
- label: "Jonah Lee",
- avatar:
- "https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=96&h=96&dpr=2&q=80",
- teamLabel: "Strategic expansion",
- },
- {
- value: "nina-santos",
- label: "Nina Santos",
- avatar:
- "https://images.unsplash.com/photo-1544005313-94ddf0286df2?w=96&h=96&dpr=2&q=80",
- teamLabel: "Commercial renewals",
- },
- {
- value: "omar-haddad",
- label: "Omar Haddad",
- avatar:
- "https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?w=96&h=96&dpr=2&q=80",
- teamLabel: "Expansion programs",
- },
- {
- value: "priya-menon",
- label: "Priya Menon",
- avatar:
- "https://images.unsplash.com/photo-1557296387-5358ad7997bb?w=96&h=96&dpr=2&q=80",
- teamLabel: "Finance alignment",
- },
-]
-
-const RENEWAL_RECORD_SEEDS: IRenewalRecord[] = [
- {
- id: "REN-1842",
- accountName: "Stripe",
- accountLogo: ,
- ownerName: "Maya Patel",
- ownerAvatar: RENEWAL_OWNERS[0].avatar,
- segment: "Enterprise",
- region: "North America",
- renewalDate: "2026-05-12",
- daysToRenewal: 7,
- arr: 420000,
- expansionPotential: 95000,
- healthScore: 46,
- usageTrend: [86, 84, 82, 79, 74, 68, 63, 57],
- stage: "Legal review",
- risk: "Critical",
- invoiceStatus: "Blocked",
- sponsorStatus: "Missing",
- tags: ["Usage drop", "Legal redlines"],
- },
- {
- id: "REN-1837",
- accountName: "Supabase",
- accountLogo: ,
- ownerName: "Nina Santos",
- ownerAvatar: RENEWAL_OWNERS[2].avatar,
- segment: "Enterprise",
- region: "North America",
- renewalDate: "2026-05-17",
- daysToRenewal: 12,
- arr: 365000,
- expansionPotential: 120000,
- healthScore: 58,
- usageTrend: [72, 73, 71, 69, 66, 63, 61, 60],
- stage: "Commercial review",
- risk: "High",
- invoiceStatus: "Finance review",
- sponsorStatus: "At risk",
- tags: ["Procurement", "Price sensitivity"],
- },
- {
- id: "REN-1831",
- accountName: "OpenAI",
- accountLogo: OPENAI_LOGO,
- ownerName: "Jonah Lee",
- ownerAvatar: RENEWAL_OWNERS[1].avatar,
- segment: "Scale",
- region: "EMEA",
- renewalDate: "2026-05-29",
- daysToRenewal: 24,
- arr: 182000,
- expansionPotential: 42000,
- healthScore: 63,
- usageTrend: [68, 69, 70, 71, 70, 68, 66, 64],
- stage: "Preparing",
- risk: "High",
- invoiceStatus: "Ready",
- sponsorStatus: "Confirmed",
- tags: ["Champion change", "Board visibility"],
- },
- {
- id: "REN-1828",
- accountName: "Mintlify",
- accountLogo: ,
- ownerName: "Omar Haddad",
- ownerAvatar: RENEWAL_OWNERS[3].avatar,
- segment: "Scale",
- region: "North America",
- renewalDate: "2026-06-04",
- daysToRenewal: 30,
- arr: 148000,
- expansionPotential: 86000,
- healthScore: 74,
- usageTrend: [64, 66, 68, 70, 73, 76, 79, 82],
- stage: "Preparing",
- risk: "Medium",
- invoiceStatus: "Ready",
- sponsorStatus: "Confirmed",
- tags: ["Expansion ready", "Multi-team rollout"],
- },
- {
- id: "REN-1822",
- accountName: "Anthropic",
- accountLogo: ANTHROPIC_LOGO,
- ownerName: "Priya Menon",
- ownerAvatar: RENEWAL_OWNERS[4].avatar,
- segment: "Enterprise",
- region: "North America",
- renewalDate: "2026-06-11",
- daysToRenewal: 37,
- arr: 515000,
- expansionPotential: 0,
- healthScore: 54,
- usageTrend: [78, 76, 74, 71, 69, 66, 64, 61],
- stage: "Commercial review",
- risk: "High",
- invoiceStatus: "Blocked",
- sponsorStatus: "At risk",
- tags: ["Budget freeze", "CFO review"],
- },
- {
- id: "REN-1819",
- accountName: "Convex",
- accountLogo: ,
- ownerName: "Maya Patel",
- ownerAvatar: RENEWAL_OWNERS[0].avatar,
- segment: "Mid-market",
- region: "APAC",
- renewalDate: "2026-06-18",
- daysToRenewal: 44,
- arr: 92000,
- expansionPotential: 18000,
- healthScore: 71,
- usageTrend: [62, 63, 64, 64, 65, 67, 68, 69],
- stage: "Preparing",
- risk: "Medium",
- invoiceStatus: "Ready",
- sponsorStatus: "Confirmed",
- tags: ["Stable adoption", "Good champion"],
- },
- {
- id: "REN-1814",
- accountName: "Neon",
- accountLogo: ,
- ownerName: "Jonah Lee",
- ownerAvatar: RENEWAL_OWNERS[1].avatar,
- segment: "Scale",
- region: "North America",
- renewalDate: "2026-06-26",
- daysToRenewal: 52,
- arr: 206000,
- expansionPotential: 112000,
- healthScore: 67,
- usageTrend: [59, 60, 61, 60, 62, 64, 67, 70],
- stage: "Commercial review",
- risk: "Medium",
- invoiceStatus: "Finance review",
- sponsorStatus: "Confirmed",
- tags: ["Cross-sell", "Security add-on"],
- },
- {
- id: "REN-1810",
- accountName: "PlanetScale",
- accountLogo: PLANETSCALE_LOGO,
- ownerName: "Nina Santos",
- ownerAvatar: RENEWAL_OWNERS[2].avatar,
- segment: "Enterprise",
- region: "EMEA",
- renewalDate: "2026-07-02",
- daysToRenewal: 58,
- arr: 438000,
- expansionPotential: 64000,
- healthScore: 81,
- usageTrend: [74, 75, 77, 79, 81, 83, 84, 86],
- stage: "Preparing",
- risk: "Low",
- invoiceStatus: "Ready",
- sponsorStatus: "Confirmed",
- tags: ["Healthy usage", "Multi-year ask"],
- },
- {
- id: "REN-1806",
- accountName: "Prisma",
- accountLogo: PRISMA_LOGO,
- ownerName: "Omar Haddad",
- ownerAvatar: RENEWAL_OWNERS[3].avatar,
- segment: "Mid-market",
- region: "North America",
- renewalDate: "2026-07-08",
- daysToRenewal: 64,
- arr: 124000,
- expansionPotential: 52000,
- healthScore: 77,
- usageTrend: [61, 63, 65, 68, 70, 73, 76, 78],
- stage: "Preparing",
- risk: "Low",
- invoiceStatus: "Ready",
- sponsorStatus: "Confirmed",
- tags: ["Expansion champion", "Product depth"],
- },
- {
- id: "REN-1801",
- accountName: "Resend",
- accountLogo: RESEND_LOGO,
- ownerName: "Priya Menon",
- ownerAvatar: RENEWAL_OWNERS[4].avatar,
- segment: "Scale",
- region: "North America",
- renewalDate: "2026-07-13",
- daysToRenewal: 69,
- arr: 164000,
- expansionPotential: 0,
- healthScore: 52,
- usageTrend: [67, 66, 64, 62, 59, 57, 54, 52],
- stage: "Commercial review",
- risk: "High",
- invoiceStatus: "Finance review",
- sponsorStatus: "At risk",
- tags: ["Seat contraction", "Procurement loop"],
- },
- {
- id: "REN-1798",
- accountName: "Slack",
- accountLogo: ,
- ownerName: "Maya Patel",
- ownerAvatar: RENEWAL_OWNERS[0].avatar,
- segment: "Mid-market",
- region: "EMEA",
- renewalDate: "2026-07-18",
- daysToRenewal: 74,
- arr: 88000,
- expansionPotential: 24000,
- healthScore: 69,
- usageTrend: [54, 55, 56, 58, 60, 61, 63, 64],
- stage: "Preparing",
- risk: "Medium",
- invoiceStatus: "Ready",
- sponsorStatus: "Confirmed",
- tags: ["Team growth", "Regional rollout"],
- },
- {
- id: "REN-1794",
- accountName: "Zoom",
- accountLogo: ,
- ownerName: "Jonah Lee",
- ownerAvatar: RENEWAL_OWNERS[1].avatar,
- segment: "Scale",
- region: "North America",
- renewalDate: "2026-07-24",
- daysToRenewal: 80,
- arr: 212000,
- expansionPotential: 135000,
- healthScore: 83,
- usageTrend: [70, 72, 74, 77, 80, 83, 86, 88],
- stage: "Preparing",
- risk: "Low",
- invoiceStatus: "Ready",
- sponsorStatus: "Confirmed",
- tags: ["Expansion approved", "Executive sponsor"],
- },
- {
- id: "REN-1791",
- accountName: "Hono",
- accountLogo: ,
- ownerName: "Nina Santos",
- ownerAvatar: RENEWAL_OWNERS[2].avatar,
- segment: "Enterprise",
- region: "North America",
- renewalDate: "2026-07-31",
- daysToRenewal: 87,
- arr: 610000,
- expansionPotential: 180000,
- healthScore: 79,
- usageTrend: [73, 75, 77, 78, 80, 82, 83, 85],
- stage: "Commercial review",
- risk: "Medium",
- invoiceStatus: "Finance review",
- sponsorStatus: "Confirmed",
- tags: ["Security pack", "Platform standard"],
- },
- {
- id: "REN-1787",
- accountName: "Remix",
- accountLogo: REMIX_LOGO,
- ownerName: "Omar Haddad",
- ownerAvatar: RENEWAL_OWNERS[3].avatar,
- segment: "Mid-market",
- region: "APAC",
- renewalDate: "2026-08-05",
- daysToRenewal: 92,
- arr: 76000,
- expansionPotential: 21000,
- healthScore: 72,
- usageTrend: [58, 59, 60, 62, 64, 65, 67, 68],
- stage: "Preparing",
- risk: "Low",
- invoiceStatus: "Ready",
- sponsorStatus: "Confirmed",
- tags: ["Partner motion", "Upsell path"],
- },
- {
- id: "REN-1783",
- accountName: "Paper",
- accountLogo: ,
- ownerName: "Priya Menon",
- ownerAvatar: RENEWAL_OWNERS[4].avatar,
- segment: "Scale",
- region: "EMEA",
- renewalDate: "2026-08-11",
- daysToRenewal: 98,
- arr: 154000,
- expansionPotential: 0,
- healthScore: 57,
- usageTrend: [63, 62, 60, 58, 57, 55, 54, 53],
- stage: "Preparing",
- risk: "Medium",
- invoiceStatus: "Finance review",
- sponsorStatus: "At risk",
- tags: ["Utilization drift", "Renewal watch"],
- },
- {
- id: "REN-1778",
- accountName: "n8n",
- accountLogo: ,
- ownerName: "Maya Patel",
- ownerAvatar: RENEWAL_OWNERS[0].avatar,
- segment: "Enterprise",
- region: "North America",
- renewalDate: "2026-08-20",
- daysToRenewal: 107,
- arr: 448000,
- expansionPotential: 132000,
- healthScore: 85,
- usageTrend: [79, 80, 82, 84, 86, 87, 89, 90],
- stage: "Preparing",
- risk: "Low",
- invoiceStatus: "Ready",
- sponsorStatus: "Confirmed",
- tags: ["Expansion mapped", "Board champion"],
- },
- {
- id: "REN-1772",
- accountName: "Vercel",
- accountLogo: ,
- ownerName: "Jonah Lee",
- ownerAvatar: RENEWAL_OWNERS[1].avatar,
- segment: "Scale",
- region: "LATAM",
- renewalDate: "2026-08-29",
- daysToRenewal: 116,
- arr: 132000,
- expansionPotential: 34000,
- healthScore: 68,
- usageTrend: [60, 61, 62, 63, 65, 66, 66, 67],
- stage: "Preparing",
- risk: "Medium",
- invoiceStatus: "Ready",
- sponsorStatus: "Confirmed",
- tags: ["Regional growth", "Elastic usage"],
- },
- {
- id: "REN-1768",
- accountName: "Google Cloud",
- accountLogo: ,
- ownerName: "Nina Santos",
- ownerAvatar: RENEWAL_OWNERS[2].avatar,
- segment: "Enterprise",
- region: "North America",
- renewalDate: "2026-09-09",
- daysToRenewal: 127,
- arr: 388000,
- expansionPotential: 74000,
- healthScore: 64,
- usageTrend: [66, 67, 68, 68, 67, 66, 65, 64],
- stage: "Preparing",
- risk: "Medium",
- invoiceStatus: "Finance review",
- sponsorStatus: "Confirmed",
- tags: ["Compliance review", "Migration timing"],
- },
-]
-
-const REGIONS = ["North America", "EMEA", "APAC", "LATAM"] as const
-
-const TAG_VARIANTS = [
- "Budget review",
- "Champion shift",
- "Upsell path",
- "Contract cleanup",
- "Security review",
- "Procurement lane",
- "Usage rebound",
- "Expansion plan",
-] as const
-
-function clamp(value: number, min: number, max: number) {
- return Math.min(Math.max(value, min), max)
-}
-
-function roundCurrency(value: number) {
- return Math.round(value / 1000) * 1000
-}
-
-function addDays(isoDate: string, days: number) {
- const nextDate = new Date(`${isoDate}T00:00:00`)
- nextDate.setDate(nextDate.getDate() + days)
-
- return nextDate.toISOString().slice(0, 10)
-}
-
-function shiftUsageTrend(values: number[], cycle: number, risk: RenewalRisk) {
- const directionalOffset = risk === "Low" ? 1 : risk === "Critical" ? -2 : 0
- const cycleOffset = ((cycle % 4) - 1.5) * 1.5
-
- return values.map((value, index) =>
- clamp(Math.round(value + directionalOffset * index + cycleOffset), 34, 96)
- )
-}
-
-function resolveOwner(seed: IRenewalRecord, cycle: number, index: number) {
- const baseOwnerIndex = RENEWAL_OWNERS.findIndex(
- (owner) => owner.label === seed.ownerName
- )
-
- return RENEWAL_OWNERS[
- (Math.max(baseOwnerIndex, 0) + cycle + index) % RENEWAL_OWNERS.length
- ]
-}
-
-function resolveRisk(score: number, daysToRenewal: number): RenewalRisk {
- if (score < 50 || daysToRenewal <= 14) return "Critical"
- if (score < 62 || daysToRenewal <= 35) return "High"
- if (score < 78) return "Medium"
- return "Low"
-}
-
-function resolveStage(
- baseStage: RenewalStage,
- risk: RenewalRisk,
- daysToRenewal: number
-): RenewalStage {
- if (risk === "Critical" || daysToRenewal <= 14) return "Legal review"
- if (risk === "High") return "Commercial review"
- if (baseStage === "Committed" && risk === "Low") return "Committed"
- return "Preparing"
-}
-
-function resolveInvoiceStatus(
- baseStatus: RenewalInvoiceStatus,
- risk: RenewalRisk
-): RenewalInvoiceStatus {
- if (risk === "Critical") return "Blocked"
- if (risk === "High") return "Finance review"
- return baseStatus === "Blocked" ? "Finance review" : "Ready"
-}
-
-function resolveSponsorStatus(risk: RenewalRisk): RenewalSponsorStatus {
- if (risk === "Critical") return "Missing"
- if (risk === "High") return "At risk"
- return "Confirmed"
-}
-
-function buildRenewalRecord(
- seed: IRenewalRecord,
- cycle: number,
- index: number
-) {
- if (cycle === 0) {
- return seed
- }
-
- const owner = resolveOwner(seed, cycle, index)
- const daysOffset = cycle * 14 + (index % 3) * 3
- const daysToRenewal = seed.daysToRenewal + daysOffset
- const healthScore = clamp(
- seed.healthScore + (((cycle + index) % 5) - 2) * 4,
- 42,
- 92
- )
- const risk = resolveRisk(healthScore, daysToRenewal)
- const stage = resolveStage(seed.stage, risk, daysToRenewal)
- const invoiceStatus = resolveInvoiceStatus(seed.invoiceStatus, risk)
- const sponsorStatus = resolveSponsorStatus(risk)
- const arrFactor = 1 + cycle * 0.045 + ((index % 4) - 1.5) * 0.03
- const expansionFactor =
- seed.expansionPotential === 0
- ? risk === "Low"
- ? 0.12
- : 0
- : 1 + ((cycle % 3) - 1) * 0.12 + (risk === "Low" ? 0.14 : 0)
-
- return {
- ...seed,
- id: `REN-${2000 + cycle * 100 + index * 3}`,
- accountName: seed.accountName,
- ownerName: owner.label,
- ownerAvatar: owner.avatar,
- segment:
- RENEWAL_SEGMENT_ORDER[(cycle + index) % RENEWAL_SEGMENT_ORDER.length],
- region: REGIONS[(cycle + index) % REGIONS.length],
- renewalDate: addDays(seed.renewalDate, daysOffset),
- daysToRenewal,
- arr: roundCurrency(seed.arr * Math.max(0.68, arrFactor)),
- expansionPotential: roundCurrency(
- Math.max(0, seed.expansionPotential * expansionFactor)
- ),
- healthScore,
- usageTrend: shiftUsageTrend(seed.usageTrend, cycle + index, risk),
- stage,
- risk,
- invoiceStatus,
- sponsorStatus,
- tags: [seed.tags[0], TAG_VARIANTS[(cycle + index) % TAG_VARIANTS.length]],
- }
-}
-
-export const RENEWAL_RECORDS: IRenewalRecord[] = Array.from(
- { length: 2 },
- (_, cycle) =>
- RENEWAL_RECORD_SEEDS.map((seed, index) =>
- buildRenewalRecord(seed, cycle, index)
- )
-)
- .flat()
- .sort((left, right) => {
- if (left.daysToRenewal !== right.daysToRenewal) {
- return left.daysToRenewal - right.daysToRenewal
- }
-
- return right.arr - left.arr
- })
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/data-grid-filtering-3/components/renewal-selection-bar.tsx b/apps/web/src/components/blocks/data-grid-filtering-3/components/renewal-selection-bar.tsx
deleted file mode 100644
index e24f8cd..0000000
--- a/apps/web/src/components/blocks/data-grid-filtering-3/components/renewal-selection-bar.tsx
+++ /dev/null
@@ -1,108 +0,0 @@
-import { Badge } from "@/components/reui/badge"
-
-import { Button } from "@evobgp/ui/components/button"
-import {
- Select,
- SelectContent,
- SelectGroup,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@evobgp/ui/components/select"
-
-import {
- RENEWAL_STAGE_ORDER,
- type RenewalOwnerOption,
- type RenewalStage,
-} from "./data"
-
-interface RenewalSelectionBarProps {
- selectedCount: number
- ownerValue: string
- stageValue: RenewalStage
- ownerOptions: RenewalOwnerOption[]
- onOwnerChange: (value: string) => void
- onStageChange: (value: RenewalStage) => void
- onApply: () => void
- onClear: () => void
-}
-
-export function RenewalSelectionBar({
- selectedCount,
- ownerValue,
- stageValue,
- ownerOptions,
- onOwnerChange,
- onStageChange,
- onApply,
- onClear,
-}: RenewalSelectionBarProps) {
- return (
-
-
-
- {selectedCount} selected
-
-
- Update owner or stage.
-
-
-
-
-
-
-
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/data-grid-filtering-3/components/renewals-command-card.tsx b/apps/web/src/components/blocks/data-grid-filtering-3/components/renewals-command-card.tsx
deleted file mode 100644
index 8e69bd5..0000000
--- a/apps/web/src/components/blocks/data-grid-filtering-3/components/renewals-command-card.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import { type ReactNode } from "react"
-
-import { cn } from "@evobgp/ui/lib/utils"
-import {
- Card,
- CardAction,
- CardContent,
- CardDescription,
- CardFooter,
- CardHeader,
- CardTitle,
-} from "@evobgp/ui/components/card"
-
-interface RenewalsCommandCardProps {
- title: string
- description?: ReactNode
- meta?: ReactNode
- action?: ReactNode
- children: ReactNode
- footer?: ReactNode
- className?: string
- contentClassName?: string
-}
-
-export function RenewalsCommandCard({
- title,
- description,
- meta,
- action,
- children,
- footer,
- className,
- contentClassName,
-}: RenewalsCommandCardProps) {
- return (
-
- {/* Header */}
-
-
-
{title}
- {description ? (
-
{description}
- ) : null}
- {meta ? (
-
{meta}
- ) : null}
-
- {action ? (
- {action}
- ) : null}
-
-
- {/* Content */}
-
- {children}
-
-
- {footer ? (
- {footer}
- ) : null}
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/data-grid-filtering-3/page.tsx b/apps/web/src/components/blocks/data-grid-filtering-3/page.tsx
deleted file mode 100644
index 032282a..0000000
--- a/apps/web/src/components/blocks/data-grid-filtering-3/page.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-"use client"
-
-import { useEffect, useState } from "react"
-
-import { RenewalsCommandGridView } from "./components/data-grid-view"
-
-export function Page() {
- const [isReady, setIsReady] = useState(false)
-
- useEffect(() => {
- setIsReady(true)
- }, [])
-
- return (
-
-
- Renewals command data grid
-
- {isReady ? (
-
- ) : (
-
- )}
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/empty-state-11/components/empty-state.tsx b/apps/web/src/components/blocks/empty-state-11/components/empty-state.tsx
deleted file mode 100644
index 0288d02..0000000
--- a/apps/web/src/components/blocks/empty-state-11/components/empty-state.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import { IconStack } from "@/components/reui/icon-stack"
-
-import { Button } from "@evobgp/ui/components/button"
-import { Card, CardContent } from "@evobgp/ui/components/card"
-import {
- Empty,
- EmptyDescription,
- EmptyHeader,
- EmptyMedia,
- EmptyTitle,
-} from "@evobgp/ui/components/empty"
-import { RouteIcon } from "lucide-react"
-
-export function EmptyState() {
- return (
-
- {/* Heading */}
-
-
-
- Routing Signals
-
-
- Shape incoming work before it reaches the roadmap.
-
-
-
-
-
-
- {/* Card */}
-
-
-
-
-
-
-
-
-
-
-
-
- Create signals to route new work
-
-
- Define signals so every intake item starts with context.
-
-
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/empty-state-11/page.tsx b/apps/web/src/components/blocks/empty-state-11/page.tsx
deleted file mode 100644
index c64d3ac..0000000
--- a/apps/web/src/components/blocks/empty-state-11/page.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { EmptyState } from "./components/empty-state"
-
-export function Page() {
- return (
-
-
- Signal routing empty state
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/empty-state-3/components/empty-state.tsx b/apps/web/src/components/blocks/empty-state-3/components/empty-state.tsx
deleted file mode 100644
index 378a824..0000000
--- a/apps/web/src/components/blocks/empty-state-3/components/empty-state.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import { Button } from "@evobgp/ui/components/button"
-import {
- Empty,
- EmptyContent,
- EmptyDescription,
- EmptyHeader,
- EmptyMedia,
- EmptyTitle,
-} from "@evobgp/ui/components/empty"
-import { ProjectsEmptyIllustration } from "./projects-empty-illustration"
-import { QuickStartTemplates } from "./quick-start-templates"
-import { PlusIcon, LayoutTemplateIcon } from "lucide-react"
-
-export function EmptyState() {
- return (
-
- {/* Empty State */}
-
-
-
-
-
-
-
-
- No projects to show
-
-
- Start a project from scratch or pick a template to launch your
- first workspace and begin tracking tasks, goals, and progress.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/empty-state-3/components/projects-empty-illustration.tsx b/apps/web/src/components/blocks/empty-state-3/components/projects-empty-illustration.tsx
deleted file mode 100644
index 95b29bc..0000000
--- a/apps/web/src/components/blocks/empty-state-3/components/projects-empty-illustration.tsx
+++ /dev/null
@@ -1,325 +0,0 @@
-import { cn } from "@evobgp/ui/lib/utils"
-
-export function ProjectsEmptyIllustration({
- variant = "hero",
-}: {
- variant?: "hero" | "compact"
-}) {
- const isCompact = variant === "compact"
-
- return (
-
- {isCompact ? : }
-
- )
-}
-
-function HeroView() {
- return (
-
- )
-}
-
-function CompactView() {
- return (
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/empty-state-3/components/quick-start-templates.tsx b/apps/web/src/components/blocks/empty-state-3/components/quick-start-templates.tsx
deleted file mode 100644
index 6550c79..0000000
--- a/apps/web/src/components/blocks/empty-state-3/components/quick-start-templates.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import {
- Item,
- ItemActions,
- ItemContent,
- ItemMedia,
- ItemTitle,
-} from "@evobgp/ui/components/item"
-import { KanbanIcon, ChevronRightIcon, TimerIcon } from "lucide-react"
-
-export function QuickStartTemplates() {
- return (
-
-
Quick starts
- {/* Grid */}
-
- }
- className="hover:bg-muted/30 min-w-0 transition-colors"
- >
-
-
-
-
-
- Task board
-
-
-
-
-
-
-
- }
- className="hover:bg-muted/30 min-w-0 transition-colors"
- >
-
-
-
-
-
- Sprint tracker
-
-
-
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/empty-state-3/page.tsx b/apps/web/src/components/blocks/empty-state-3/page.tsx
deleted file mode 100644
index 2a38cb1..0000000
--- a/apps/web/src/components/blocks/empty-state-3/page.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { EmptyState } from "./components/empty-state"
-
-export function Page() {
- return (
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/schedule-1/components/data.ts b/apps/web/src/components/blocks/schedule-1/components/data.ts
deleted file mode 100644
index 64135e2..0000000
--- a/apps/web/src/components/blocks/schedule-1/components/data.ts
+++ /dev/null
@@ -1,380 +0,0 @@
-import type { BadgeProps } from "@/components/reui/badge"
-
-export type EventStatus = "Upcoming" | "Cancelled" | "Pending" | "Completed"
-
-export interface Attendee {
- id: string
- name: string
- avatar: string
-}
-
-export interface MockEvent {
- id: string
- title: string
- status: EventStatus
- date: string
- time: string
- location: string
- attendees: Attendee[]
-}
-
-export interface ScheduleCalendarProps {
- selected: Date | undefined
- onSelect: (date: Date | undefined) => void
- datesWithEvents?: Set
- className?: string
-}
-
-export type EventsFilter =
- | "all"
- | "upcoming"
- | "completed"
- | "cancelled"
- | "pending"
-
-export interface EventsListProps {
- selectedDate?: Date
- filter?: EventsFilter
- onFilterChange?: (filter: EventsFilter) => void
- className?: string
-}
-
-export interface EventCardProps {
- event: MockEvent
-}
-
-// ── Attendee pool ──
-
-const POOL: Attendee[] = [
- {
- id: "u01",
- name: "Sarah Chen",
- avatar:
- "https://images.unsplash.com/photo-1519699047748-de8e457a634e?w=96&h=96&dpr=2&q=80",
- },
- {
- id: "u02",
- name: "Michael Torres",
- avatar:
- "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=96&h=96&dpr=2&q=80",
- },
- {
- id: "u03",
- name: "Emma Wilson",
- avatar:
- "https://images.unsplash.com/photo-1485893086445-ed75865251e0?w=96&h=96&dpr=2&q=80",
- },
- {
- id: "u04",
- name: "James Park",
- avatar:
- "https://images.unsplash.com/photo-1463453091185-61582044d556?w=96&h=96&dpr=2&q=80",
- },
- {
- id: "u05",
- name: "Olivia Nguyen",
- avatar:
- "https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=96&h=96&dpr=2&q=80",
- },
- {
- id: "u06",
- name: "Alex Johnson",
- avatar:
- "https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?w=96&h=96&dpr=2&q=80",
- },
- {
- id: "u07",
- name: "Nina Patel",
- avatar:
- "https://images.unsplash.com/photo-1531746020798-e6953c6e8e04?w=96&h=96&dpr=2&q=80",
- },
- {
- id: "u08",
- name: "Ryan Murphy",
- avatar:
- "https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=96&h=96&dpr=2&q=80",
- },
- {
- id: "u09",
- name: "Zoe Martinez",
- avatar:
- "https://images.unsplash.com/photo-1580489944761-15a19d654956?w=96&h=96&dpr=2&q=80",
- },
- {
- id: "u10",
- name: "Tom Anderson",
- avatar:
- "https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=96&h=96&dpr=2&q=80",
- },
- {
- id: "u11",
- name: "Priya Sharma",
- avatar:
- "https://images.unsplash.com/photo-1488426862026-3ee34a7d66df?w=96&h=96&dpr=2&q=80",
- },
- {
- id: "u12",
- name: "Lucas Ferreira",
- avatar:
- "https://images.unsplash.com/photo-1560250097-0b93528c311a?w=96&h=96&dpr=2&q=80",
- },
-]
-
-// ── Event template pool ──
-
-const TEMPLATES: Array<{ title: string; location: string }> = [
- { title: "Daily Standup", location: "Slack Huddle" },
- { title: "Design System Review", location: "Figma / Remote" },
- { title: "Sprint Planning", location: "Conference Room A" },
- { title: "API Contract Review", location: "Remote" },
- { title: "Investor Briefing", location: "22nd Floor Boardroom" },
- { title: "Product Roadmap Review", location: "Conference Room B" },
- { title: "UX Research Session", location: "Lab 2" },
- { title: "Security Briefing", location: "Remote" },
- { title: "Analytics Kick-off", location: "Google Meet" },
- { title: "Backend Guild", location: "Meeting Room A" },
- { title: "Frontend Perf Deep-dive", location: "Remote" },
- { title: "Mobile QA Handoff", location: "Remote" },
- { title: "Hiring Interview · Senior FE", location: "Zoom" },
- { title: "Q2 OKR Planning", location: "Conference Room A" },
- { title: "Tech Talk: Next.js 15", location: "Main Hall" },
- { title: "Release Planning v3.0", location: "Zoom" },
- { title: "Accessibility Workshop", location: "Lab 1" },
- { title: "Partner API Workshop", location: "HQ · Floor 3" },
- { title: "Quarterly All-Hands", location: "Main Hall" },
- { title: "Customer Feedback Review", location: "Google Meet" },
- { title: "Board Meeting", location: "HQ · Floor 3" },
- { title: "Brand Identity Refresh", location: "Figma / Remote" },
- { title: "Infrastructure Cost Review", location: "Remote" },
- { title: "Database Migration Planning", location: "Remote" },
- { title: "Figma Handoff · Billing", location: "Figma / Remote" },
- { title: "Marketing Campaign Review", location: "Google Meet" },
- { title: "Platform Architecture Review", location: "HQ · Floor 3" },
- { title: "Payment Gateway Review", location: "Zoom" },
- { title: "Email A/B Results 2", location: "Google Meet" },
- { title: "Weekly Retro", location: "Slack Huddle" },
- { title: "Security Audit Debrief", location: "Remote" },
- { title: "Contractor Onboarding", location: "Zoom" },
- { title: "Hiring Panel · Design", location: "Zoom" },
- { title: "Growth Strategy Workshop", location: "HQ · Floor 2" },
- { title: "i18n Sprint Kick-off", location: "Remote" },
- { title: "Product Launch Q1", location: "1200 Innovation Way" },
- { title: "Partnership Summit", location: "Grand Plaza Hotel" },
- { title: "Tech Debt Triage", location: "Remote" },
- { title: "1:1 with Engineering Lead", location: "Zoom" },
- { title: "Release Sign-off", location: "Zoom" },
-]
-
-const TIMES = [
- "8:00 am",
- "8:30 am",
- "9:00 am",
- "9:30 am",
- "10:00 am",
- "10:30 am",
- "11:00 am",
- "11:30 am",
- "12:00 pm",
- "1:00 pm",
- "1:30 pm",
- "2:00 pm",
- "2:30 pm",
- "3:00 pm",
- "3:30 pm",
- "4:00 pm",
- "4:30 pm",
- "5:00 pm",
- "5:30 pm",
-]
-
-// ── Calendar data ──
-
-export const MONTHS = [
- "January",
- "February",
- "March",
- "April",
- "May",
- "June",
- "July",
- "August",
- "September",
- "October",
- "November",
- "December",
-]
-
-// Fixed demo clock: every "today" bucket derives from this reference date.
-const DEMO_REFERENCE_DATE_ISO = "2026-06-10"
-// Noon keeps UTC-based date keys (toISOString) on the same calendar day across timezones.
-export const DEMO_REFERENCE_DATE = new Date(
- `${DEMO_REFERENCE_DATE_ISO}T12:00:00`
-)
-
-export const CURRENT_YEAR = DEMO_REFERENCE_DATE.getFullYear()
-export const YEARS = Array.from({ length: 21 }, (_, i) => CURRENT_YEAR - 10 + i)
-
-// ── Event card data ──
-
-export const STATUS_VARIANT: Record = {
- Upcoming: "info-light",
- Cancelled: "destructive-light",
- Pending: "warning-light",
- Completed: "success-light",
-}
-
-export const MAX_VISIBLE = 3
-
-export function parseDateParts(dateStr: string) {
- const d = new Date(dateStr + "T12:00:00")
- return {
- day: d.getDate().toString().padStart(2, "0"),
- monthShort: d.toLocaleString("en-US", { month: "short" }),
- }
-}
-
-export function initials(name: string) {
- return name
- .split(" ")
- .map((n) => n[0])
- .join("")
- .toUpperCase()
- .slice(0, 2)
-}
-
-// ── Events list data ──
-
-export const FILTER_ITEMS = [
- { label: "All events", value: "all" },
- { label: "Upcoming", value: "upcoming" },
- { label: "Completed", value: "completed" },
- { label: "Cancelled", value: "cancelled" },
- { label: "Pending", value: "pending" },
-]
-
-export function toDateKey(date: Date): string {
- return date.toISOString().slice(0, 10)
-}
-
-export function matchesFilter(event: MockEvent, filter: EventsFilter): boolean {
- if (filter === "all") return true
- return event.status.toLowerCase() === filter
-}
-
-// ── Mock event generation ──
-
-function hash(n: number): number {
- let x = n
- x = ((x >> 16) ^ x) * 0x45d9f3b
- x = ((x >> 16) ^ x) * 0x45d9f3b
- x = (x >> 16) ^ x
- return Math.abs(x)
-}
-
-function toDateStr(d: Date): string {
- return d.toISOString().slice(0, 10)
-}
-
-function buildMockEvents(ref: Date = DEMO_REFERENCE_DATE): MockEvent[] {
- const Y = ref.getFullYear()
- const M = ref.getMonth()
- const todayNum = ref.getDate()
- const daysInMonth = new Date(Y, M + 1, 0).getDate()
-
- // Current week's Monday day-of-month (may be <= 0 → last month, skip)
- const dow = ref.getDay() // 0=Sun … 6=Sat
- const mondayNum = todayNum - (dow === 0 ? 6 : dow - 1)
-
- const seed = Y * 100 + M // stable per month
-
- const events: MockEvent[] = []
-
- for (let dayNum = 1; dayNum <= daysInMonth; dayNum++) {
- const isToday = dayNum === todayNum
- const isMonday = dayNum === mondayNum
- const dayHash = hash(seed + dayNum)
-
- // Decide whether this day gets events
- const guaranteed = isToday || isMonday
- const randomlyChosen = dayHash % 10 < 4 // ~40 %
- if (!guaranteed && !randomlyChosen) continue
-
- // How many events (4-9)
- const baseCount = isToday ? 5 : 4
- const count = baseCount + (dayHash % (isToday ? 3 : 6))
-
- const date = toDateStr(new Date(Y, M, dayNum, 12, 0, 0, 0))
-
- // Status for each slot
- const defaultStatus: EventStatus =
- dayNum < todayNum ? "Completed" : "Upcoming"
-
- // Spread events through the day using distinct templates and times
- for (let i = 0; i < count; i++) {
- const slot = hash(seed + dayNum * 37 + i * 13)
- const template = TEMPLATES[(dayHash + i * 7) % TEMPLATES.length]
- const time = TIMES[(slot + i * 3) % TIMES.length]
-
- // Occasionally vary status for non-today days
- let status: EventStatus = defaultStatus
- if (!isToday) {
- const r = (slot * 31 + i * 17) % 10
- if (dayNum < todayNum) {
- status = r < 2 ? "Cancelled" : "Completed"
- } else {
- status = r < 3 ? "Pending" : "Upcoming"
- }
- }
-
- // Pick 2-6 attendees deterministically
- const attendeeCount = 2 + (slot % 5)
- const attendees = Array.from(
- { length: attendeeCount },
- (_, k) => POOL[(slot + k * 3) % POOL.length]
- )
- // Deduplicate
- const seen = new Set()
- const uniqueAttendees = attendees.filter((a) => {
- if (seen.has(a.id)) return false
- seen.add(a.id)
- return true
- })
-
- events.push({
- id: `${Y}-${M}-${dayNum}-${i}`,
- title: template.title,
- status,
- date,
- time,
- location: template.location,
- attendees: uniqueAttendees,
- })
- }
- }
-
- return events
-}
-
-export const MOCK_EVENTS: MockEvent[] = buildMockEvents()
-
-export function getDatesWithEvents(events: MockEvent[]): Set {
- return new Set(events.map((e) => e.date))
-}
-
-export function getEventsForDate(
- events: MockEvent[],
- date: Date | undefined,
- filter: "all" | "upcoming" | "completed" | "cancelled" | "pending"
-): MockEvent[] {
- if (!date) return events
- const dateStr = toDateStr(date)
- let list = events.filter((e) => e.date === dateStr)
- if (filter === "upcoming") list = list.filter((e) => e.status === "Upcoming")
- else if (filter === "completed")
- list = list.filter((e) => e.status === "Completed")
- else if (filter === "cancelled")
- list = list.filter((e) => e.status === "Cancelled")
- else if (filter === "pending")
- list = list.filter((e) => e.status === "Pending")
- return list
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/schedule-1/components/event-card.tsx b/apps/web/src/components/blocks/schedule-1/components/event-card.tsx
deleted file mode 100644
index a5b1772..0000000
--- a/apps/web/src/components/blocks/schedule-1/components/event-card.tsx
+++ /dev/null
@@ -1,79 +0,0 @@
-import { Badge } from "@/components/reui/badge"
-
-import {
- Avatar,
- AvatarFallback,
- AvatarGroup,
- AvatarGroupCount,
- AvatarImage,
-} from "@evobgp/ui/components/avatar"
-import { Item } from "@evobgp/ui/components/item"
-import {
- initials,
- MAX_VISIBLE,
- parseDateParts,
- STATUS_VARIANT,
- type Attendee,
- type EventCardProps,
-} from "./data"
-import { ClockIcon, MapPinIcon } from "lucide-react"
-
-function AttendeeGroup({ attendees }: { attendees: Attendee[] }) {
- if (attendees.length === 0) return null
- const visible = attendees.slice(0, MAX_VISIBLE)
- const overflow = attendees.length - MAX_VISIBLE
-
- return (
-
- {visible.map((a) => (
-
-
-
- {initials(a.name)}
-
-
- ))}
- {overflow > 0 && (
-
- +{overflow}
-
- )}
-
- )
-}
-
-export function EventCard({ event }: EventCardProps) {
- const { day, monthShort } = parseDateParts(event.date)
- const variant = STATUS_VARIANT[event.status]
-
- return (
- -
-
-
- {event.title}
-
-
-
-
- {event.status}
-
-
-
-
-
-
- {monthShort} {day} · {event.time}
-
-
-
-
- {event.location}
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/schedule-1/components/events-list.tsx b/apps/web/src/components/blocks/schedule-1/components/events-list.tsx
deleted file mode 100644
index bc6f87f..0000000
--- a/apps/web/src/components/blocks/schedule-1/components/events-list.tsx
+++ /dev/null
@@ -1,135 +0,0 @@
-import { cn } from "@evobgp/ui/lib/utils"
-import { Button } from "@evobgp/ui/components/button"
-import { Item, ItemMedia } from "@evobgp/ui/components/item"
-import { ScrollArea } from "@evobgp/ui/components/scroll-area"
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@evobgp/ui/components/select"
-import {
- FILTER_ITEMS,
- matchesFilter,
- MOCK_EVENTS,
- toDateKey,
- type EventsFilter,
- type EventsListProps,
- type MockEvent,
-} from "./data"
-import { EventCard } from "./event-card"
-import { PlusIcon, CalendarIcon } from "lucide-react"
-
-export function EventsList({
- selectedDate,
- filter = "all",
- onFilterChange,
-}: EventsListProps) {
- const dateKey = selectedDate ? toDateKey(selectedDate) : null
-
- const events = MOCK_EVENTS.filter((event) => {
- if (dateKey && event.date !== dateKey) return false
- return matchesFilter(event, filter)
- })
-
- const headingLabel = selectedDate
- ? selectedDate.toLocaleString("en-US", {
- weekday: "long",
- month: "long",
- day: "numeric",
- })
- : "All Events"
-
- return (
-
- {/* Header */}
-
- {/* Heading */}
-
-
- {headingLabel}
-
-
- {events.length > 0
- ? `${events.length} event${events.length !== 1 ? "s" : ""}`
- : "No events found"}
-
-
-
- {/* Filters */}
-
-
-
-
-
-
-
- {/* Events */}
-
-
-
- {events.length === 0 ? (
-
-
}
- className="bg-muted flex size-10 items-center justify-center rounded-full p-0"
- >
-
-
-
-
-
-
- No events found
-
-
- {dateKey
- ? "Nothing scheduled for this day."
- : "Try changing the filter."}
-
-
-
- ) : (
-
- {events.map((event: MockEvent) => (
- -
-
-
- ))}
-
- )}
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/schedule-1/components/schedule-calendar.tsx b/apps/web/src/components/blocks/schedule-1/components/schedule-calendar.tsx
deleted file mode 100644
index 46cc087..0000000
--- a/apps/web/src/components/blocks/schedule-1/components/schedule-calendar.tsx
+++ /dev/null
@@ -1,190 +0,0 @@
-import { useState } from "react"
-import { DayButton } from "react-day-picker"
-
-import { cn } from "@evobgp/ui/lib/utils"
-import { Button } from "@evobgp/ui/components/button"
-import { Calendar, CalendarDayButton } from "@evobgp/ui/components/calendar"
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@evobgp/ui/components/select"
-import {
- DEMO_REFERENCE_DATE,
- MONTHS,
- YEARS,
- type ScheduleCalendarProps,
-} from "./data"
-import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"
-
-// Weekday header highlight tracks the fixed demo clock, not the live date.
-const TODAY_WEEKDAY_NAME = DEMO_REFERENCE_DATE.toLocaleString("en-US", {
- weekday: "short",
-}).toUpperCase()
-
-export function ScheduleCalendar({
- selected,
- onSelect,
- datesWithEvents = new Set(),
-}: ScheduleCalendarProps) {
- const [month, setMonth] = useState(selected ?? DEMO_REFERENCE_DATE)
-
- const stepMonth = (delta: number) =>
- setMonth((prev) => new Date(prev.getFullYear(), prev.getMonth() + delta, 1))
-
- const handleMonthSelect = (value: string) => {
- const i = MONTHS.indexOf(value)
- if (i >= 0) setMonth(new Date(month.getFullYear(), i, 1))
- }
-
- const handleYearSelect = (value: string) => {
- const y = parseInt(value, 10)
- if (!isNaN(y)) setMonth(new Date(y, month.getMonth(), 1))
- }
-
- return (
-
- {/* ── Custom header ── */}
-
- {/* Prev month */}
-
-
- {/* Month select */}
-
-
- {/* Year select */}
-
-
- {/* Next month */}
-
-
-
- {/* ── Calendar ── */}
-
- date.toLocaleString("en-US", { weekday: "short" }).toUpperCase(),
- }}
- classNames={{
- month_caption: "hidden",
- nav: "hidden",
- weekdays: "flex gap-1",
- weekday:
- "flex-1 flex items-center justify-center h-14 text-[0.65rem] font-medium text-muted-foreground",
- week: "flex gap-1 mt-1",
- day: "flex-1 aspect-square p-0",
- day_button: cn(
- "bg-muted/50 hover:bg-muted",
- " rounded-md ",
- "data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[selected-single=true]:hover:bg-primary data-[selected-single=true]:hover:text-primary-foreground!"
- ),
- outside: "opacity-60",
- disabled: "opacity-60",
- today: cn("bg-accent text-foreground", " rounded-md "),
- }}
- components={{
- Weekday: ({
- children,
- className: cls,
- ...props
- }: React.ComponentPropsWithoutRef<"th">) => {
- const isToday = children === TODAY_WEEKDAY_NAME
- return (
-
- {children}
- |
- )
- },
-
- DayButton: ({
- children,
- modifiers,
- day,
- ...props
- }: React.ComponentProps) => {
- const dateKey = day.date.toISOString().slice(0, 10)
- const hasEvents = !modifiers.outside && datesWithEvents.has(dateKey)
-
- return (
-
- {hasEvents ? (
-
- ) : (
-
- )}
- {children}
-
- )
- },
- }}
- />
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/schedule-1/components/schedule.tsx b/apps/web/src/components/blocks/schedule-1/components/schedule.tsx
deleted file mode 100644
index 90da747..0000000
--- a/apps/web/src/components/blocks/schedule-1/components/schedule.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-"use client"
-
-import { useState } from "react"
-import { Frame, FramePanel } from "@/components/reui/frame"
-
-import {
- DEMO_REFERENCE_DATE,
- EventsFilter,
- getDatesWithEvents,
- MOCK_EVENTS,
-} from "./data"
-import { EventsList } from "./events-list"
-import { ScheduleCalendar } from "./schedule-calendar"
-
-export function Schedule() {
- const [selectedDate, setSelectedDate] = useState(
- DEMO_REFERENCE_DATE
- )
- const [filter, setFilter] = useState("all")
- const datesWithEvents = getDatesWithEvents(MOCK_EVENTS)
-
- return (
-
-
- {/* Left - calendar */}
-
-
-
-
- {/* Right - events */}
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/schedule-1/page.tsx b/apps/web/src/components/blocks/schedule-1/page.tsx
deleted file mode 100644
index 0b32a73..0000000
--- a/apps/web/src/components/blocks/schedule-1/page.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { Schedule } from "./components/schedule"
-
-export function Page() {
- return (
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/settings-14/components/api-integrations-grid.tsx b/apps/web/src/components/blocks/settings-14/components/api-integrations-grid.tsx
deleted file mode 100644
index 2d76d54..0000000
--- a/apps/web/src/components/blocks/settings-14/components/api-integrations-grid.tsx
+++ /dev/null
@@ -1,151 +0,0 @@
-"use client"
-
-import { useMemo, useState } from "react"
-import { DataGrid } from "@/components/reui/data-grid/data-grid"
-import { DataGridPagination } from "@/components/reui/data-grid/data-grid-pagination"
-import { DataGridScrollArea } from "@/components/reui/data-grid/data-grid-scroll-area"
-import { DataGridTable } from "@/components/reui/data-grid/data-grid-table"
-import {
- Frame,
- FrameDescription,
- FrameFooter,
- FrameHeader,
- FramePanel,
- FrameTitle,
-} from "@/components/reui/frame"
-import {
- getCoreRowModel,
- getPaginationRowModel,
- getSortedRowModel,
- useReactTable,
- type PaginationState,
- type SortingState,
- type VisibilityState,
-} from "@tanstack/react-table"
-import { toast } from "sonner"
-
-import { Button } from "@evobgp/ui/components/button"
-import { Switch } from "@evobgp/ui/components/switch"
-import { TooltipProvider } from "@evobgp/ui/components/tooltip"
-import { createColumns } from "./columns"
-import { API_INTEGRATIONS } from "./data"
-import { BriefcaseBusinessIcon } from "lucide-react"
-
-// ── Main component ──
-
-export function ApiIntegrationsGrid() {
- const [rows, setRows] = useState(API_INTEGRATIONS)
- const [pagination, setPagination] = useState({
- pageIndex: 0,
- pageSize: 10,
- })
- const [sorting, setSorting] = useState([
- { id: "name", desc: false },
- ])
- const [columnVisibility, setColumnVisibility] = useState({})
-
- const pauseAll = rows.every((row) => !row.enabled)
-
- const handleToggle = (id: string, enabled: boolean) => {
- setRows((current) =>
- current.map((row) => (row.id === id ? { ...row, enabled } : row))
- )
- }
-
- const handlePauseAllChange = (checked: boolean) => {
- const nextEnabled = !checked
- setRows((current) =>
- current.map((row) => ({ ...row, enabled: nextEnabled }))
- )
- toast.message(
- checked ? "All integrations paused" : "Integrations resumed",
- {
- description: checked
- ? "API traffic is temporarily disabled for every listed integration."
- : "The listed integrations can receive traffic again.",
- }
- )
- }
-
- const columns = useMemo(() => createColumns({ onToggle: handleToggle }), [])
-
- const table = useReactTable({
- data: rows,
- columns,
- getRowId: (row) => row.id,
- state: {
- pagination,
- sorting,
- columnVisibility,
- },
- onPaginationChange: setPagination,
- onSortingChange: setSorting,
- onColumnVisibilityChange: setColumnVisibility,
- getCoreRowModel: getCoreRowModel(),
- getPaginationRowModel: getPaginationRowModel(),
- getSortedRowModel: getSortedRowModel(),
- })
-
- return (
-
- {/* Table */}
-
-
-
-
- API Integrations
-
- Oversee endpoints, key rotation, and traffic availability.
-
-
-
-
-
-
- Pause all
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/settings-14/components/columns.tsx b/apps/web/src/components/blocks/settings-14/components/columns.tsx
deleted file mode 100644
index fb2db34..0000000
--- a/apps/web/src/components/blocks/settings-14/components/columns.tsx
+++ /dev/null
@@ -1,222 +0,0 @@
-import { memo } from "react"
-import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
-import { Badge } from "@/components/reui/badge"
-import { DataGridColumnHeader } from "@/components/reui/data-grid/data-grid-column-header"
-import {
- DataGridTableRowSelect,
- DataGridTableRowSelectAll,
-} from "@/components/reui/data-grid/data-grid-table"
-import type { ColumnDef, Row } from "@tanstack/react-table"
-import { toast } from "sonner"
-
-import { Button } from "@evobgp/ui/components/button"
-import { Item, ItemMedia } from "@evobgp/ui/components/item"
-import { Skeleton } from "@evobgp/ui/components/skeleton"
-import { Switch } from "@evobgp/ui/components/switch"
-import type { ApiIntegration } from "./data"
-import { CheckIcon, CopyIcon, SquarePenIcon } from "lucide-react"
-
-// ── Formatters ──
-
-function formatCalls(value: number) {
- return new Intl.NumberFormat("en-US").format(value)
-}
-
-// ── Cell components ──
-
-const StatusSwitch = memo(function StatusSwitch({
- row,
- onToggle,
-}: {
- row: Row
- onToggle: (id: string, enabled: boolean) => void
-}) {
- return (
- onToggle(row.original.id, checked)}
- aria-label={`Toggle ${row.original.name}`}
- />
- )
-})
-
-const ApiKeyCell = memo(function ApiKeyCell({ value }: { value: string }) {
- const { copyToClipboard, isCopied } = useCopyToClipboard({
- onCopy: () => {
- toast.success("API key copied", { description: value })
- },
- })
-
- return (
-
- )
-})
-
-const ActionsCell = memo(function ActionsCell({
- row,
-}: {
- row: Row
-}) {
- return (
-
- )
-})
-
-// ── Column definitions ──
-
-export function createColumns({
- onToggle,
-}: {
- onToggle: (id: string, enabled: boolean) => void
-}): ColumnDef[] {
- return [
- {
- accessorKey: "select",
- id: "select",
- header: () => ,
- cell: ({ row }) => ,
- enableSorting: false,
- enableHiding: false,
- enableResizing: false,
- size: 38,
- meta: {
- headerClassName: "ps-4!",
- cellClassName: "ps-4!",
- },
- },
- {
- accessorKey: "name",
- id: "name",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => (
-
-
-
-
- {row.original.logo}
-
-
-
-
- {row.original.name}
-
-
- {row.original.provider} · {row.original.description}
-
-
-
- ),
- size: 450,
- enableSorting: true,
- enableHiding: true,
- meta: {
- headerTitle: "Integration",
- skeleton: (
-
- ),
- },
- },
- {
- accessorKey: "apiKey",
- id: "apiKey",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => ,
- size: 300,
- enableSorting: true,
- enableHiding: true,
- meta: {
- headerTitle: "API Key",
- skeleton: ,
- },
- },
- {
- accessorKey: "dailyCalls",
- id: "dailyCalls",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => (
-
- {formatCalls(row.original.dailyCalls)}
-
- ),
- size: 140,
- enableSorting: true,
- enableHiding: true,
- meta: {
- headerTitle: "Daily Calls",
- skeleton: ,
- },
- },
- {
- accessorKey: "enabled",
- id: "enabled",
- header: ({ column }) => (
-
- ),
- cell: ({ row }) => ,
- size: 110,
- enableSorting: true,
- enableHiding: true,
- meta: {
- headerTitle: "Status",
- skeleton: ,
- },
- },
- {
- accessorKey: "actions",
- id: "actions",
- header: "",
- cell: ({ row }) => ,
- enableSorting: false,
- enableHiding: false,
- enableResizing: false,
- size: 60,
- meta: {
- skeleton: ,
- headerClassName: "",
- cellClassName: "",
- },
- },
- ]
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/settings-14/components/data.tsx b/apps/web/src/components/blocks/settings-14/components/data.tsx
deleted file mode 100644
index a68f253..0000000
--- a/apps/web/src/components/blocks/settings-14/components/data.tsx
+++ /dev/null
@@ -1,345 +0,0 @@
-"use client"
-
-import type { ReactNode } from "react"
-
-import { Convex } from "@evobgp/ui/components/svgs/convex"
-import { Discord } from "@evobgp/ui/components/svgs/discord"
-import { Gemini } from "@evobgp/ui/components/svgs/gemini"
-import { Loom } from "@evobgp/ui/components/svgs/loom"
-import { Mintlify } from "@evobgp/ui/components/svgs/mintlify"
-import { N8n } from "@evobgp/ui/components/svgs/n8n"
-import { Neon } from "@evobgp/ui/components/svgs/neon"
-import { Slack } from "@evobgp/ui/components/svgs/slack"
-import { Stripe } from "@evobgp/ui/components/svgs/stripe"
-import { Supabase } from "@evobgp/ui/components/svgs/supabase"
-import { Zoom } from "@evobgp/ui/components/svgs/zoom"
-
-// ── Types ──
-
-export interface ApiIntegration {
- id: string
- name: string
- provider: string
- description: string
- logo: ReactNode
- apiKey: string
- dailyCalls: number
- enabled: boolean
-}
-
-// ── Data ──
-
-const logoClassName = "size-5"
-
-export const API_INTEGRATIONS: ApiIntegration[] = [
- {
- id: "auth-relay",
- name: "User Auth System",
- provider: "Supabase",
- description: "Session webhooks and identity sync",
- logo: ,
- apiKey: "f6g7Z8h9R0TfUaSdTf",
- dailyCalls: 15000,
- enabled: false,
- },
- {
- id: "social-mgr",
- name: "Social Media Manager",
- provider: "Slack",
- description: "Content scheduling and post distribution",
- logo: ,
- apiKey: "s1t2u3v4w5x6y7z8a9",
- dailyCalls: 13000,
- enabled: false,
- },
- {
- id: "sms-notify",
- name: "SMS Notification Service",
- provider: "n8n",
- description: "Transactional message delivery pipeline",
- logo: ,
- apiKey: "t2u3v4w5x6y7z8a9b1",
- dailyCalls: 19000,
- enabled: false,
- },
- {
- id: "ship-coord",
- name: "Shipping Coordinator",
- provider: "Convex",
- description: "Carrier rate lookups and label generation",
- logo: ,
- apiKey: "t6u7v8w9x0CvBnNlSc",
- dailyCalls: 14000,
- enabled: true,
- },
- {
- id: "seo-scan",
- name: "SEO Analyzer",
- provider: "Mintlify",
- description: "Site audit scores and keyword tracking",
- logo: ,
- apiKey: "b1c2d3e4f5g6h7i8j9",
- dailyCalls: 6000,
- enabled: false,
- },
- {
- id: "sales-fc",
- name: "Sales Forecasting",
- provider: "Stripe",
- description: "Revenue trend projections and pipeline modeling",
- logo: ,
- apiKey: "z8a9b1c2d3e4f5g6h7",
- dailyCalls: 11500,
- enabled: false,
- },
- {
- id: "quick-pay",
- name: "Quick Pay Service",
- provider: "Stripe",
- description: "One-tap checkout and express payment links",
- logo: ,
- apiKey: "a1b2Xc3dY4ZxQvPlQp",
- dailyCalls: 10000,
- enabled: true,
- },
- {
- id: "proj-mgmt",
- name: "Project Management",
- provider: "Loom",
- description: "Sprint planning and milestone handoff recordings",
- logo: ,
- apiKey: "v4w5x6y7z8a9b1c2d3",
- dailyCalls: 14500,
- enabled: false,
- },
- {
- id: "pay-gate",
- name: "Payment Gateway",
- provider: "Stripe",
- description: "Card processing and dispute lifecycle events",
- logo: ,
- apiKey: "1p2q3r4s5DfGhPgPy",
- dailyCalls: 25000,
- enabled: false,
- },
- {
- id: "order-track",
- name: "Order Tracking Sys",
- provider: "Neon",
- description: "Shipment status queries and delivery confirmations",
- logo: ,
- apiKey: "e1E2gH3hB4iYtUvOtS",
- dailyCalls: 9500,
- enabled: false,
- },
- {
- id: "ops-notifier",
- name: "Ops Notifier",
- provider: "Slack",
- description: "High-priority workspace alerts",
- logo: ,
- apiKey: "slk_live_p2d7n4w8v5q1m6r3",
- dailyCalls: 9600,
- enabled: false,
- },
- {
- id: "workflow-bridge",
- name: "Workflow Bridge",
- provider: "n8n",
- description: "Automation run intake and callbacks",
- logo: ,
- apiKey: "n8n_live_v7x2m5q9a4c1p8d6",
- dailyCalls: 13750,
- enabled: true,
- },
- {
- id: "community-sync",
- name: "Community Sync",
- provider: "Discord",
- description: "Community reports and moderation escalations",
- logo: ,
- apiKey: "dsc_live_m3q9v2k7r5n1x8c4",
- dailyCalls: 6400,
- enabled: false,
- },
- {
- id: "review-clips",
- name: "Review Clips",
- provider: "Loom",
- description: "Async review uploads and callback events",
- logo: ,
- apiKey: "lom_live_r8t4m1c6p9v2x5q7",
- dailyCalls: 7100,
- enabled: false,
- },
- {
- id: "meeting-webhooks",
- name: "Meeting Webhooks",
- provider: "Zoom",
- description: "Recording ready and host events",
- logo: ,
- apiKey: "zom_live_q5n8r2m4v7c1p9d3",
- dailyCalls: 11300,
- enabled: true,
- },
- {
- id: "edge-cache-sync",
- name: "Edge Cache Sync",
- provider: "Convex",
- description: "State snapshots for frontend refreshes",
- logo: ,
- apiKey: "cvx_live_t1p6m9q3r7v2x4c8",
- dailyCalls: 15400,
- enabled: false,
- },
- {
- id: "support-routing",
- name: "Support Routing",
- provider: "Slack",
- description: "Escalation queue dispatch automation",
- logo: ,
- apiKey: "slk_live_x9c4m2p7v1q8r5n6",
- dailyCalls: 8900,
- enabled: true,
- },
- {
- id: "revenue-monitor",
- name: "Revenue Monitor",
- provider: "Stripe",
- description: "Charge anomalies and recovery alerts",
- logo: ,
- apiKey: "stp_live_c6v2m8q1r4n7p5x9",
- dailyCalls: 12850,
- enabled: false,
- },
- {
- id: "member-audit",
- name: "Member Audit",
- provider: "Supabase",
- description: "Role changes and access review logs",
- logo: ,
- apiKey: "sbp_live_n4r7m1q8x5c2v9p6",
- dailyCalls: 5800,
- enabled: false,
- },
- {
- id: "retention-flows",
- name: "Retention Flows",
- provider: "n8n",
- description: "Churn prevention automations and retries",
- logo: ,
- apiKey: "n8n_live_m2v5q8p1c7r4x9d6",
- dailyCalls: 14600,
- enabled: true,
- },
- {
- id: "content-gen",
- name: "Content Generator",
- provider: "Gemini",
- description: "Draft generation and content summarization",
- logo: ,
- apiKey: "gem_live_k4r9m2q7v1p8x3n5",
- dailyCalls: 8200,
- enabled: false,
- },
- {
- id: "db-replication",
- name: "DB Replication",
- provider: "Neon",
- description: "Cross-region read replica synchronization",
- logo: ,
- apiKey: "neo_live_p7x3m1q9v4r2n8c6",
- dailyCalls: 22100,
- enabled: true,
- },
- {
- id: "docs-webhook",
- name: "Docs Webhook",
- provider: "Mintlify",
- description: "Documentation deploy and page-view events",
- logo: ,
- apiKey: "mnt_live_v2q8m5r1p9x4n7c3",
- dailyCalls: 4300,
- enabled: false,
- },
- {
- id: "incident-bot",
- name: "Incident Bot",
- provider: "Discord",
- description: "Automated incident threads and status page sync",
- logo: ,
- apiKey: "dsc_live_r6n1m4q9v7p2x8c5",
- dailyCalls: 3200,
- enabled: true,
- },
- {
- id: "video-analytics",
- name: "Video Analytics",
- provider: "Zoom",
- description: "Participation heatmaps and engagement scoring",
- logo: ,
- apiKey: "zom_live_m8v3q1r5p9x2n4c7",
- dailyCalls: 5600,
- enabled: false,
- },
- {
- id: "signup-funnel",
- name: "Signup Funnel",
- provider: "Supabase",
- description: "Registration step tracking and drop-off alerts",
- logo: ,
- apiKey: "sbp_live_q3r7m9v2p1x5n8c4",
- dailyCalls: 17800,
- enabled: true,
- },
- {
- id: "async-reviews",
- name: "Async Reviews",
- provider: "Loom",
- description: "Code review recordings and feedback collection",
- logo: ,
- apiKey: "lom_live_x5n2m8q4v1r9p7c3",
- dailyCalls: 2900,
- enabled: false,
- },
- {
- id: "pipeline-monitor",
- name: "Pipeline Monitor",
- provider: "n8n",
- description: "CI/CD build status and deployment tracking",
- logo: ,
- apiKey: "n8n_live_r4v7m2q9p1x8n5c3",
- dailyCalls: 16200,
- enabled: true,
- },
- {
- id: "real-time-sync",
- name: "Real-time Sync",
- provider: "Convex",
- description: "Live data propagation for collaborative editors",
- logo: ,
- apiKey: "cvx_live_m9q2r5v1p4x7n3c8",
- dailyCalls: 19400,
- enabled: false,
- },
- {
- id: "invoice-relay",
- name: "Invoice Relay",
- provider: "Stripe",
- description: "Automated invoice generation and delivery tracking",
- logo: ,
- apiKey: "stp_live_v3m8q1r6p9x2n4c7",
- dailyCalls: 7800,
- enabled: true,
- },
- {
- id: "ai-summarizer",
- name: "AI Summarizer",
- provider: "Gemini",
- description: "Meeting transcript distillation and action extraction",
- logo: ,
- apiKey: "gem_live_r1m5q8v3p9x7n2c4",
- dailyCalls: 4100,
- enabled: false,
- },
-]
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/settings-14/page.tsx b/apps/web/src/components/blocks/settings-14/page.tsx
deleted file mode 100644
index 912c270..0000000
--- a/apps/web/src/components/blocks/settings-14/page.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { ApiIntegrationsGrid } from "./components/api-integrations-grid"
-
-export function Page() {
- return (
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/settings-3/components/color-picker.tsx b/apps/web/src/components/blocks/settings-3/components/color-picker.tsx
deleted file mode 100644
index 7f2af7a..0000000
--- a/apps/web/src/components/blocks/settings-3/components/color-picker.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-"use client"
-
-import { useState } from "react"
-
-import { cn } from "@evobgp/ui/lib/utils"
-import { FieldLegend, FieldSet } from "@evobgp/ui/components/field"
-import { ACCENT_COLORS } from "./data"
-import { CheckIcon } from "lucide-react"
-
-// ── Color Picker ──
-
-export function ColorPicker() {
- const [selected, setSelected] = useState(ACCENT_COLORS[0].value)
-
- return (
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/settings-3/components/data.tsx b/apps/web/src/components/blocks/settings-3/components/data.tsx
deleted file mode 100644
index fad3846..0000000
--- a/apps/web/src/components/blocks/settings-3/components/data.tsx
+++ /dev/null
@@ -1,107 +0,0 @@
-import { type ReactNode } from "react"
-
-// ── Types ──
-
-export type SelectOption = {
- value: string
- label: string
-}
-
-export type Preference = {
- id: string
- label: string
- description: string
- defaultChecked: boolean
-}
-
-export type AccentColorOption = {
- name: string
- value: string
-}
-
-export type SettingBadgeVariant =
- | "primary-light"
- | "destructive-light"
- | "info-light"
-
-export type SettingFieldProps = {
- title: string
- description: string
- badge?: { label: string; variant: SettingBadgeVariant }
- children: ReactNode
- last?: boolean
-}
-
-export type DateFormatOption = {
- value: string
- label: string
-}
-
-// ── Data ──
-
-export const DAYS: SelectOption[] = [
- { value: "monday", label: "Monday" },
- { value: "tuesday", label: "Tuesday" },
- { value: "wednesday", label: "Wednesday" },
- { value: "thursday", label: "Thursday" },
- { value: "friday", label: "Friday" },
- { value: "saturday", label: "Saturday" },
- { value: "sunday", label: "Sunday" },
-]
-
-export const CURRENCIES: SelectOption[] = [
- { value: "usd", label: "USD · US Dollar" },
- { value: "eur", label: "EUR · Euro" },
- { value: "gbp", label: "GBP · British Pound" },
- { value: "jpy", label: "JPY · Japanese Yen" },
-]
-
-export const REGIONS: SelectOption[] = [
- { value: "us", label: "United States" },
- { value: "eu", label: "European Union" },
- { value: "uk", label: "United Kingdom" },
- { value: "jp", label: "Japan" },
- { value: "au", label: "Australia" },
-]
-
-export const ACCENT_COLORS: AccentColorOption[] = [
- { name: "Slate", value: "#64748b" },
- { name: "Rose", value: "#f43f5e" },
- { name: "Orange", value: "#f97316" },
- { name: "Violet", value: "#8b5cf6" },
- { name: "Emerald", value: "#10b981" },
- { name: "Sky", value: "#0ea5e9" },
-]
-
-export const PREFERENCES: Preference[] = [
- {
- id: "keyboard-shortcuts",
- label: "Enable keyboard shortcuts.",
- description: "Use shortcuts to speed up your workflow.",
- defaultChecked: true,
- },
- {
- id: "spellcheck",
- label: "Auto spell-check.",
- description: "Highlight spelling errors in text fields.",
- defaultChecked: true,
- },
- {
- id: "compact-mode",
- label: "Compact Mode.",
- description: "Reduce spacing for a denser interface layout.",
- defaultChecked: false,
- },
- {
- id: "animations",
- label: "Reduce animations.",
- description: "Minimize motion effects throughout the UI.",
- defaultChecked: false,
- },
-]
-
-export const DATE_FORMAT_OPTIONS: DateFormatOption[] = [
- { value: "mdy", label: "MM/DD/YYYY" },
- { value: "dmy", label: "DD/MM/YYYY" },
- { value: "ymd", label: "YYYY/MM/DD" },
-]
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/settings-3/components/editor-preferences.tsx b/apps/web/src/components/blocks/settings-3/components/editor-preferences.tsx
deleted file mode 100644
index 5dcec1a..0000000
--- a/apps/web/src/components/blocks/settings-3/components/editor-preferences.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-"use client"
-
-import { useState } from "react"
-
-import {
- Field,
- FieldContent,
- FieldDescription,
- FieldGroup,
- FieldLabel,
- FieldLegend,
- FieldSet,
-} from "@evobgp/ui/components/field"
-import { Switch } from "@evobgp/ui/components/switch"
-
-import { PREFERENCES } from "./data"
-
-// ── Editor Preferences ──
-
-export function EditorPreferences() {
- const [values, setValues] = useState>(() =>
- Object.fromEntries(PREFERENCES.map((p) => [p.id, p.defaultChecked]))
- )
-
- return (
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/settings-3/components/general-settings.tsx b/apps/web/src/components/blocks/settings-3/components/general-settings.tsx
deleted file mode 100644
index f6d26d1..0000000
--- a/apps/web/src/components/blocks/settings-3/components/general-settings.tsx
+++ /dev/null
@@ -1,265 +0,0 @@
-import { useState } from "react"
-import {
- Frame,
- FrameDescription,
- FrameFooter,
- FrameHeader,
- FramePanel,
- FrameTitle,
-} from "@/components/reui/frame"
-
-import { Button } from "@evobgp/ui/components/button"
-import {
- Field,
- FieldDescription,
- FieldGroup,
- FieldLabel,
- FieldLegend,
- FieldSet,
-} from "@evobgp/ui/components/field"
-import { Input } from "@evobgp/ui/components/input"
-import {
- InputGroup,
- InputGroupAddon,
- InputGroupInput,
- InputGroupText,
-} from "@evobgp/ui/components/input-group"
-import {
- Select,
- SelectContent,
- SelectGroup,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@evobgp/ui/components/select"
-import {
- ToggleGroup,
- ToggleGroupItem,
-} from "@evobgp/ui/components/toggle-group"
-import { ColorPicker } from "./color-picker"
-import { CURRENCIES, DAYS, REGIONS } from "./data"
-import { EditorPreferences } from "./editor-preferences"
-import { SettingField } from "./setting-field"
-import { PlusIcon, UploadIcon, GlobeIcon, CircleDollarSignIcon } from "lucide-react"
-
-export function GeneralSettings() {
- const [dateFormat, setDateFormat] = useState(["mdy"])
- const [startOfWeek, setStartOfWeek] = useState("monday")
- const [region, setRegion] = useState("us")
- const [currency, setCurrency] = useState("usd")
- const handleStartOfWeekChange = (value: string | null) => {
- if (value !== null) {
- setStartOfWeek(value)
- }
- }
- const handleRegionChange = (value: string | null) => {
- if (value !== null) {
- setRegion(value)
- }
- }
- const handleCurrencyChange = (value: string | null) => {
- if (value !== null) {
- setCurrency(value)
- }
- }
-
- return (
-
-
- General Settings
- Core app preferences.
-
-
-
-
- {/* ── Project name ── */}
-
-
-
-
- {/* ── API endpoint ── */}
-
-
-
- https://
-
-
-
-
-
- {/* ── Start of week ── */}
-
-
-
-
- {/* ── Allowed origins ── */}
-
-
-
-
-
-
-
-
- {/* ── Accent color ── */}
-
-
-
-
- {/* ── Region & currency ── */}
-
-
-
-
- {/* ── Date display format ── */}
-
- {
- if (value.length > 0) setDateFormat(value)
- }}
- variant="outline"
- size="sm"
- aria-label="Date display format"
- >
- MM/DD/YYYY
- DD/MM/YYYY
- YYYY/MM/DD
-
-
-
- {/* ── Editor preferences ── */}
-
-
-
-
-
-
-
-
-
-
-
- )
-}
-
-// ── Helper functions ──
-
-function getOptionLabel(
- options: Array<{ value: string; label: string }>,
- value: string
-) {
- return options.find((option) => option.value === value)?.label ?? value
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/settings-3/components/setting-field.tsx b/apps/web/src/components/blocks/settings-3/components/setting-field.tsx
deleted file mode 100644
index 0518031..0000000
--- a/apps/web/src/components/blocks/settings-3/components/setting-field.tsx
+++ /dev/null
@@ -1,71 +0,0 @@
-"use client"
-
-import { type ComponentProps, type ReactNode } from "react"
-import { Badge } from "@/components/reui/badge"
-
-import { cn } from "@evobgp/ui/lib/utils"
-import {
- Field,
- FieldContent,
- FieldDescription,
- FieldLabel,
- FieldSeparator,
- FieldTitle,
-} from "@evobgp/ui/components/field"
-
-interface SettingFieldProps {
- title: string
- description: string
- badge?: {
- label: string
- variant: ComponentProps["variant"]
- }
- children: ReactNode
- last?: boolean
- labelFor?: string
- contentClassName?: string
-}
-
-// ── Setting Field ──
-
-export function SettingField({
- title,
- description,
- badge,
- children,
- last,
- labelFor,
- contentClassName,
-}: SettingFieldProps) {
- return (
- <>
-
-
-
- {labelFor ? (
- {title}
- ) : (
- {title}
- )}
-
- {badge ? (
-
- {badge.label}
-
- ) : null}
-
-
-
{description}
-
-
-
- {children}
-
-
-
- {!last ? : null}
- >
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/settings-3/page.tsx b/apps/web/src/components/blocks/settings-3/page.tsx
deleted file mode 100644
index cffd7c0..0000000
--- a/apps/web/src/components/blocks/settings-3/page.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { GeneralSettings } from "./components/general-settings"
-
-export function Page() {
- return (
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/stats-12/components/data.tsx b/apps/web/src/components/blocks/stats-12/components/data.tsx
deleted file mode 100644
index 2d4e891..0000000
--- a/apps/web/src/components/blocks/stats-12/components/data.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import { ReactNode } from "react"
-import { Badge } from "@/components/reui/badge"
-import { Headphones, CircleCheckIcon, SmileIcon } from "lucide-react"
-
-// ── Types ──
-
-export interface CardData {
- icon: ReactNode
- iconBg: string
- value: string | number
- label: string
- info: ReactNode
-}
-
-// ── Data ──
-
-export const cards: CardData[] = [
- {
- icon: (
-
- ),
- iconBg: "text-blue-600 dark:text-blue-400",
- value: 320,
- label: "Support Tickets",
- info: 12 Open, 308 Closed,
- },
- {
- icon: (
-
- ),
- iconBg: "text-emerald-600 dark:text-emerald-400",
- value: "98%",
- label: "Resolved",
- info: +2.1% this month,
- },
- {
- icon: (
-
- ),
- iconBg: "text-amber-600 dark:text-amber-400",
- value: "4.8",
- label: "Satisfaction Rate",
- info: Avg. (out of 5),
- },
-]
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/stats-12/components/stats.tsx b/apps/web/src/components/blocks/stats-12/components/stats.tsx
deleted file mode 100644
index b3b89d6..0000000
--- a/apps/web/src/components/blocks/stats-12/components/stats.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-"use client"
-
-import { Frame, FramePanel } from "@/components/reui/frame"
-
-import { cn } from "@evobgp/ui/lib/utils"
-import { Item, ItemMedia } from "@evobgp/ui/components/item"
-
-import { cards } from "./data"
-
-export function Stats() {
- return (
-
- {/* Grid */}
-
- {cards.map((card, i) => (
-
-
- -
-
- {card.icon}
-
-
-
-
-
- {card.value}
-
-
- {card.label}
-
-
-
- {card.info}
-
-
- ))}
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/stats-12/page.tsx b/apps/web/src/components/blocks/stats-12/page.tsx
deleted file mode 100644
index 2261885..0000000
--- a/apps/web/src/components/blocks/stats-12/page.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { Stats } from "./components/stats"
-
-export function Page() {
- return (
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/stats-4/components/data.ts b/apps/web/src/components/blocks/stats-4/components/data.ts
deleted file mode 100644
index 60e0d9a..0000000
--- a/apps/web/src/components/blocks/stats-4/components/data.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-export const leadsData = {
- newLeads: 54,
- returningLeads: 198,
- newPercent: 21.43,
- returningPercent: 78.57,
- topSource: "LinkedIn",
- conversionRate: 12.8,
-}
-
-export const rangeOptions = [
- { label: "All Time", value: "all-time" },
- { label: "This Month", value: "this-month" },
- { label: "Last Month", value: "last-month" },
- { label: "This Year", value: "this-year" },
- { label: "Last Year", value: "last-year" },
-]
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/stats-4/components/stats.tsx b/apps/web/src/components/blocks/stats-4/components/stats.tsx
deleted file mode 100644
index 48ff5ab..0000000
--- a/apps/web/src/components/blocks/stats-4/components/stats.tsx
+++ /dev/null
@@ -1,131 +0,0 @@
-"use client"
-
-import { Badge } from "@/components/reui/badge"
-import {
- Frame,
- FramePanel,
- FrameTitle,
-} from "@/components/reui/frame"
-
-import { cn } from "@evobgp/ui/lib/utils"
-import { Progress } from "@evobgp/ui/components/progress"
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@evobgp/ui/components/select"
-import {
- Tooltip,
- TooltipContent,
- TooltipTrigger,
-} from "@evobgp/ui/components/tooltip"
-import { leadsData, rangeOptions } from "./data"
-import { InfoIcon } from "lucide-react"
-
-export function Stats() {
- return (
-
- {/* Content */}
-
-
- Leads Overview
-
-
-
-
-
-
-
- {leadsData.newLeads}
-
- {leadsData.newPercent}%
-
-
- New leads
-
-
-
-
-
-
-
- {leadsData.returningLeads}
-
-
-
- Returning leads
-
-
- {Array.from({ length: 30 }).map((_, i) => (
-
- ))}
-
-
-
-
-
-
- Top Source
-
- {leadsData.topSource}
-
-
-
-
- Conversion Rate
-
-
-
-
- }
- />
-
- Percentage of leads converted to customers.
-
-
-
-
- {leadsData.conversionRate}%
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/stats-4/page.tsx b/apps/web/src/components/blocks/stats-4/page.tsx
deleted file mode 100644
index 2261885..0000000
--- a/apps/web/src/components/blocks/stats-4/page.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { Stats } from "./components/stats"
-
-export function Page() {
- return (
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/stats-6/components/data.ts b/apps/web/src/components/blocks/stats-6/components/data.ts
deleted file mode 100644
index f2e0cc7..0000000
--- a/apps/web/src/components/blocks/stats-6/components/data.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-export const rangeOptions = [
- { label: "Q1 - 2024", value: "q1" },
- { label: "Q2 - 2024", value: "q2" },
- { label: "Q3 - 2024", value: "q3" },
- { label: "Q4 - 2024", value: "q4" },
-]
-
-export const performance = [
- { label: "Deals Closed", value: 27, trend: 12, trendDir: "up" as const },
- { label: "Revenue", value: "$182.4k", trend: 6, trendDir: "up" as const },
- { label: "Conversion", value: "72%", trend: 3, trendDir: "down" as const },
-]
-
-export const pipelineProgress = 76
-
-export const activity = [
- {
- text: "Closed deal with FinSight Inc.",
- date: "Today",
- state: "secondary",
- color: "text-emerald-500",
- },
- {
- text: "3 new leads added to Pipeline.",
- date: "Yesterday",
- state: "secondary",
- color: "text-emerald-500",
- },
- {
- text: "Follow-up scheduled.",
- date: "2 days ago",
- state: "destructive",
- color: "text-destructive",
- },
-]
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/stats-6/components/stats.tsx b/apps/web/src/components/blocks/stats-6/components/stats.tsx
deleted file mode 100644
index 2b68d6f..0000000
--- a/apps/web/src/components/blocks/stats-6/components/stats.tsx
+++ /dev/null
@@ -1,186 +0,0 @@
-"use client"
-
-import { Badge } from "@/components/reui/badge"
-import {
- Frame,
- FrameFooter,
- FramePanel,
-} from "@/components/reui/frame"
-
-import { cn } from "@evobgp/ui/lib/utils"
-import { Button } from "@evobgp/ui/components/button"
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuGroup,
- DropdownMenuItem,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from "@evobgp/ui/components/dropdown-menu"
-import { Progress } from "@evobgp/ui/components/progress"
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@evobgp/ui/components/select"
-import { Separator } from "@evobgp/ui/components/separator"
-import { activity, performance, pipelineProgress, rangeOptions } from "./data"
-import { MoreHorizontalIcon, SettingsIcon, TriangleAlertIcon, Pin, Share2Icon, Trash2Icon, TrendingUp, TrendingDown, CircleCheckIcon } from "lucide-react"
-
-export function Stats() {
- return (
-
- {/* Content */}
-
-
-
-
Staff Performance
-
- Sales Manager
-
-
-
-
-
- }
- >
-
-
-
-
-
-
- Settings
-
-
-
- Add Alert
-
-
-
- Pin to Dashboard
-
-
-
- Share
-
-
-
-
- Remove
-
-
-
-
-
-
-
-
-
- {performance.map((item) => (
-
-
- {item.value}
-
-
- {item.label}
-
-
- {item.trendDir === "up" ? (
-
- ) : (
-
- )}
- {item.trendDir === "up" ? "+" : "-"}
- {item.trend}%
-
-
- ))}
-
-
-
-
-
-
-
- Pipeline Progress
-
-
- {pipelineProgress}%
-
-
-
-
-
-
-
-
-
- Recent Activity
-
-
- {activity.map((a, i) => (
- -
-
-
-
- {a.text}
-
-
-
- {a.date}
-
-
- ))}
-
-
-
-
- {/* Footer */}
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/stats-6/page.tsx b/apps/web/src/components/blocks/stats-6/page.tsx
deleted file mode 100644
index 2261885..0000000
--- a/apps/web/src/components/blocks/stats-6/page.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { Stats } from "./components/stats"
-
-export function Page() {
- return (
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/timeline-3/components/data.ts b/apps/web/src/components/blocks/timeline-3/components/data.ts
deleted file mode 100644
index 7249150..0000000
--- a/apps/web/src/components/blocks/timeline-3/components/data.ts
+++ /dev/null
@@ -1,131 +0,0 @@
-export type ActivityDotBadge = {
- label: string
- dotClass: string
-}
-
-export type ActivityLightBadge = {
- label: string
- variant:
- | "primary-light"
- | "success-light"
- | "warning-light"
- | "info-light"
- | "destructive-light"
-}
-
-export type FinanceActivity = {
- id: number
- user: string
- avatar: string
- action: string
- target: string
- detail: string
- badges: [ActivityDotBadge] | [ActivityDotBadge, ActivityLightBadge]
- attachment?: {
- name: string
- size: string
- }
- participants?: {
- src: string
- fallback: string
- }[]
- participantCount?: number
- actions?: {
- label: string
- variant?: "default" | "outline"
- }[]
- date: string
- dateTime: string
-}
-
-export const financeActivities: FinanceActivity[] = [
- {
- id: 1,
- user: "Nadia Flores",
- avatar:
- "https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=96&h=96&dpr=2&q=80",
- action: "approved payout batch",
- target: "ACH-4182",
- detail: "$142,800 routed to 38 merchant accounts",
- badges: [
- { label: "Payout", dotClass: "bg-emerald-500" },
- { label: "Same Day", variant: "success-light" },
- ],
- date: "5 minutes ago",
- dateTime: "2026-05-06T09:45:00+05:00",
- },
- {
- id: 2,
- user: "Theo Ramsey",
- avatar:
- "https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=96&h=96&dpr=2&q=80",
- action: "flagged review on",
- target: "Transfer TX-9041",
- detail: "Velocity threshold exceeded for new payee",
- badges: [
- { label: "Risk", dotClass: "bg-amber-500" },
- { label: "High", variant: "warning-light" },
- ],
- actions: [{ label: "Review" }, { label: "Clear", variant: "outline" }],
- date: "18 minutes ago",
- dateTime: "2026-05-06T09:32:00+05:00",
- },
- {
- id: 3,
- user: "Iris Chen",
- avatar:
- "https://images.unsplash.com/photo-1531123897727-8f129e1688ce?w=96&h=96&dpr=2&q=80",
- action: "reconciled ledger entry",
- target: "LDG-7749",
- detail: "Subscription invoice matched to bank settlement",
- badges: [{ label: "Ledger", dotClass: "bg-sky-500" }],
- attachment: {
- name: "settlement-match.csv",
- size: "48kb",
- },
- date: "42 minutes ago",
- dateTime: "2026-05-06T09:08:00+05:00",
- },
- {
- id: 4,
- user: "Marcus Bell",
- avatar:
- "https://images.unsplash.com/photo-1519085360753-af0119f7cbe7?w=96&h=96&dpr=2&q=80",
- action: "raised limit for",
- target: "Northstar Workspace",
- detail: "Monthly card volume increased to $850K",
- badges: [{ label: "Limit Raised", dotClass: "bg-violet-500" }],
- participants: [
- {
- src: "https://images.unsplash.com/photo-1519699047748-de8e457a634e?w=96&h=96&dpr=2&q=80",
- fallback: "SC",
- },
- {
- src: "https://images.unsplash.com/photo-1584308972272-9e4e7685e80f?w=96&h=96&dpr=2&q=80",
- fallback: "MR",
- },
- ],
- participantCount: 2,
- date: "1 hour ago",
- dateTime: "2026-05-06T08:50:00+05:00",
- },
- {
- id: 5,
- user: "Amara Okafor",
- avatar:
- "https://images.unsplash.com/photo-1544723795-3fb6469f5b39?w=96&h=96&dpr=2&q=80",
- action: "sent invoice reminder",
- target: "INV-2098",
- detail: "Net 7 renewal balance due tomorrow",
- badges: [
- { label: "Invoice", dotClass: "bg-orange-500" },
- { label: "Due Tomorrow", variant: "destructive-light" },
- ],
- attachment: {
- name: "invoice-2098.pdf",
- size: "76kb",
- },
- date: "2 hours ago",
- dateTime: "2026-05-06T07:50:00+05:00",
- },
-]
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/timeline-3/components/finance-activity-timeline.tsx b/apps/web/src/components/blocks/timeline-3/components/finance-activity-timeline.tsx
deleted file mode 100644
index 246a94a..0000000
--- a/apps/web/src/components/blocks/timeline-3/components/finance-activity-timeline.tsx
+++ /dev/null
@@ -1,218 +0,0 @@
-import { Badge } from "@/components/reui/badge"
-import {
- Timeline,
- TimelineContent,
- TimelineDate,
- TimelineHeader,
- TimelineIndicator,
- TimelineItem,
- TimelineSeparator,
-} from "@/components/reui/timeline"
-
-import { cn } from "@evobgp/ui/lib/utils"
-import {
- Avatar,
- AvatarFallback,
- AvatarGroup,
- AvatarGroupCount,
- AvatarImage,
-} from "@evobgp/ui/components/avatar"
-import { Button } from "@evobgp/ui/components/button"
-import { ButtonGroup } from "@evobgp/ui/components/button-group"
-import {
- financeActivities,
- type ActivityDotBadge,
- type FinanceActivity,
-} from "./data"
-import { PaperclipIcon, DownloadIcon } from "lucide-react"
-
-function getInitials(name: string) {
- return name
- .split(" ")
- .map((part) => part[0])
- .join("")
-}
-
-function ActivityTarget({ target }: { target: string }) {
- const idMatch = target.match(/^(.*?)([A-Z]+-\d+)$/)
-
- if (!idMatch) {
- return {target}
- }
-
- const [, prefix, id] = idMatch
-
- return (
- <>
- {prefix}
-
- {id}
-
- >
- )
-}
-
-function ActivityAttachment({
- attachment,
-}: {
- attachment: NonNullable
-}) {
- return (
-
-
-
-
- )
-}
-
-function ParticipantGroup({
- participants,
- count,
-}: {
- participants: NonNullable
- count?: number
-}) {
- return (
-
- {participants.map((participant) => (
-
-
-
- {participant.fallback}
-
-
- ))}
- {count && count > 0 ? (
-
- +{count}
-
- ) : null}
-
- )
-}
-
-function DotBadge({ badge }: { badge: ActivityDotBadge }) {
- return (
-
-
- {badge.label}
-
- )
-}
-
-function ActivityStatusRow({ activity }: { activity: FinanceActivity }) {
- const [primaryBadge, secondaryBadge] = activity.badges
-
- return (
-
-
- {activity.date}
-
-
- {secondaryBadge ? (
- {secondaryBadge.label}
- ) : null}
-
- )
-}
-
-export function FinanceActivityTimeline() {
- return (
-
-
-
- Finance Activity
-
-
- Payouts, risk, invoices, and ledger updates.
-
-
-
-
- {financeActivities.map((activity) => (
-
-
-
-
-
-
-
- {getInitials(activity.user)}
-
-
-
-
-
-
-
-
- {activity.user}
- {" "}
-
- {activity.action}
- {" "}
-
-
-
-
- {activity.detail}
-
- {(activity.attachment ||
- activity.actions?.length ||
- activity.participants?.length) && (
-
- {activity.attachment ? (
-
- ) : null}
- {activity.participants?.length ? (
-
- ) : null}
- {activity.actions?.map((action) => (
-
- ))}
-
- )}
-
-
-
- ))}
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/components/blocks/timeline-3/page.tsx b/apps/web/src/components/blocks/timeline-3/page.tsx
deleted file mode 100644
index 9fb13f6..0000000
--- a/apps/web/src/components/blocks/timeline-3/page.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { FinanceActivityTimeline } from "./components/finance-activity-timeline"
-
-export function Page() {
- return (
-
-
-
- )
-}
\ No newline at end of file
diff --git a/apps/web/src/routes/_auth/directories.tsx b/apps/web/src/routes/_auth/directories.tsx
index 1a8da1f..0dcc587 100644
--- a/apps/web/src/routes/_auth/directories.tsx
+++ b/apps/web/src/routes/_auth/directories.tsx
@@ -6,6 +6,10 @@ import { Button } from '@evobgp/ui/components/button'
import { Badge } from '@/components/reui/badge'
import { BadgeTabs, TabsContent } from '@/components/badge-tabs'
import { FrameDataGrid, KpiStatGrid, type KpiStatItem } from '@/components/reui-kit'
+import { DirectoriesCommunitiesGrid } from '@/components/directories/directories-communities-grid'
+import { DirectoriesDohGrid } from '@/components/directories/directories-doh-grid'
+import { PageHeader } from '@/components/page-header'
+import { QueryState } from '@/components/query-state'
import { SectionCardsSkeleton, TableSkeleton } from '@/components/skeletons'
import { directoriesCommunitiesQueryOptions, directoriesDohQueryOptions } from '@/queries/directories'
diff --git a/web-legacy-svelte/.gitignore b/web-legacy-svelte/.gitignore
deleted file mode 100644
index b8a1419..0000000
--- a/web-legacy-svelte/.gitignore
+++ /dev/null
@@ -1,24 +0,0 @@
-node_modules
-
-# Output
-.output
-.vercel
-.netlify
-.wrangler
-/.svelte-kit
-/build
-
-# OS
-.DS_Store
-Thumbs.db
-
-# Env
-.env
-.env.local
-.env.*
-!.env.example
-!.env.test
-
-# Vite
-vite.config.js.timestamp-*
-vite.config.ts.timestamp-*
diff --git a/web-legacy-svelte/.npmrc b/web-legacy-svelte/.npmrc
deleted file mode 100644
index b6f27f1..0000000
--- a/web-legacy-svelte/.npmrc
+++ /dev/null
@@ -1 +0,0 @@
-engine-strict=true
diff --git a/web-legacy-svelte/.prettierignore b/web-legacy-svelte/.prettierignore
deleted file mode 100644
index 7d74fe2..0000000
--- a/web-legacy-svelte/.prettierignore
+++ /dev/null
@@ -1,9 +0,0 @@
-# Package Managers
-package-lock.json
-pnpm-lock.yaml
-yarn.lock
-bun.lock
-bun.lockb
-
-# Miscellaneous
-/static/
diff --git a/web-legacy-svelte/.prettierrc b/web-legacy-svelte/.prettierrc
deleted file mode 100644
index e2076ea..0000000
--- a/web-legacy-svelte/.prettierrc
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "useTabs": true,
- "singleQuote": true,
- "trailingComma": "none",
- "printWidth": 100,
- "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
- "tailwindStylesheet": "./src/routes/layout.css",
- "overrides": [
- {
- "files": "*.svelte",
- "options": {
- "parser": "svelte"
- }
- }
- ]
-}
diff --git a/web-legacy-svelte/.vscode/extensions.json b/web-legacy-svelte/.vscode/extensions.json
deleted file mode 100644
index 11efe25..0000000
--- a/web-legacy-svelte/.vscode/extensions.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "recommendations": ["svelte.svelte-vscode", "bradlc.vscode-tailwindcss", "esbenp.prettier-vscode"]
-}
diff --git a/web-legacy-svelte/.vscode/settings.json b/web-legacy-svelte/.vscode/settings.json
deleted file mode 100644
index bc31e15..0000000
--- a/web-legacy-svelte/.vscode/settings.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "files.associations": {
- "*.css": "tailwindcss"
- }
-}
diff --git a/web-legacy-svelte/README.md b/web-legacy-svelte/README.md
deleted file mode 100644
index cccd220..0000000
--- a/web-legacy-svelte/README.md
+++ /dev/null
@@ -1,43 +0,0 @@
-# EvoBGP WebUI
-
-SvelteKit-приложение панели управления EvoBGP. Запуск — [docs/quickstart.md](../docs/quickstart.md).
-
-Разработка UI **обязательно** следует официальной документации [shadcn-svelte](https://shadcn-svelte.com/docs) и правилам репозитория [.cursor/rules/web-shadcn.mdc](../.cursor/rules/web-shadcn.mdc): максимум нативных компонентов из registry, установка только через CLI в `src/lib/ui/core/`.
-
-## UI-архитектура
-
-| Путь | Назначение |
-| ------------------------ | ------------------------------------------------------------------- |
-| `src/lib/ui/core/` | shadcn-svelte (только через `npx shadcn-svelte@latest add … -y -o`) |
-| `src/lib/ui/app/` | Layout, PageHeader, toast (`notify`) |
-| `src/lib/ui/patterns/` | FormField, AppDataTable, ConfirmDialog, EmptyState |
-| `src/lib/components/ui/` | Re-export на `ui/core` (совместимость) |
-
-Токены темы: `src/routes/layout.css`. Справочник: `src/lib/ui/app/tokens.md`.
-
-## Разработка
-
-```sh
-npm install
-npm run dev
-npm run check
-npm run lint # prettier --check; обязательно перед PR (CI job web)
-```
-
-Из корня репозитория (обе проверки как в CI):
-
-```powershell
-powershell -NoProfile -File scripts/lint-web.ps1
-```
-
-```sh
-sh scripts/lint-web.sh
-```
-
-При падении `lint`: `npx prettier --write .` в каталоге `web/`, затем снова `check` + `lint`.
-
-Добавление компонентов shadcn (из каталога `web/`):
-
-```sh
-npx shadcn-svelte@latest add -y -o
-```
diff --git a/web-legacy-svelte/components.json b/web-legacy-svelte/components.json
deleted file mode 100644
index 93ec7a8..0000000
--- a/web-legacy-svelte/components.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "$schema": "https://www.shadcn-svelte.com/schema.json",
- "tailwind": {
- "css": "src/routes/layout.css",
- "baseColor": "neutral"
- },
- "aliases": {
- "components": "$lib/components",
- "utils": "$lib/utils",
- "ui": "$lib/ui/core",
- "hooks": "$lib/hooks",
- "lib": "$lib"
- },
- "typescript": true,
- "iconLibrary": "lucide"
-}
diff --git a/web-legacy-svelte/package-lock.json b/web-legacy-svelte/package-lock.json
deleted file mode 100644
index a12062c..0000000
--- a/web-legacy-svelte/package-lock.json
+++ /dev/null
@@ -1,3158 +0,0 @@
-{
- "name": "web",
- "version": "0.0.1",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "name": "web",
- "version": "0.0.1",
- "dependencies": {
- "@tanstack/table-core": "^8.21.3",
- "bits-ui": "^2.17.2",
- "clsx": "^2.1.1",
- "svelte-sonner": "^1.1.0",
- "tailwind-merge": "^3.5.0",
- "tailwind-variants": "^3.2.2",
- "tw-animate-css": "^1.4.0",
- "zod": "^4.4.3"
- },
- "devDependencies": {
- "@internationalized/date": "^3.12.0",
- "@lucide/svelte": "^1.16.0",
- "@sveltejs/adapter-static": "^3.0.10",
- "@sveltejs/kit": "^2.50.2",
- "@sveltejs/vite-plugin-svelte": "^6.2.4",
- "@tailwindcss/vite": "^4.1.18",
- "@types/node": "^22.15.0",
- "formsnap": "^2.0.1",
- "prettier": "^3.8.1",
- "prettier-plugin-svelte": "^3.4.1",
- "prettier-plugin-tailwindcss": "^0.7.2",
- "svelte": "^5.54.0",
- "svelte-check": "^4.4.2",
- "sveltekit-superforms": "^2.30.1",
- "tailwindcss": "^4.1.18",
- "typescript": "^5.9.3",
- "vite": "^7.3.1"
- }
- },
- "node_modules/@ark/schema": {
- "version": "0.56.0",
- "resolved": "https://registry.npmjs.org/@ark/schema/-/schema-0.56.0.tgz",
- "integrity": "sha512-ECg3hox/6Z/nLajxXqNhgPtNdHWC9zNsDyskwO28WinoFEnWow4IsERNz9AnXRhTZJnYIlAJ4uGn3nlLk65vZA==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@ark/util": "0.56.0"
- }
- },
- "node_modules/@ark/util": {
- "version": "0.56.0",
- "resolved": "https://registry.npmjs.org/@ark/util/-/util-0.56.0.tgz",
- "integrity": "sha512-BghfRC8b9pNs3vBoDJhcta0/c1J1rsoS1+HgVUreMFPdhz/CRAKReAu57YEllNaSy98rWAdY1gE+gFup7OXpgA==",
- "dev": true,
- "license": "MIT",
- "optional": true
- },
- "node_modules/@babel/runtime": {
- "version": "7.29.2",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
- "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@esbuild/aix-ppc64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz",
- "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==",
- "cpu": [
- "ppc64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "aix"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-arm": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz",
- "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==",
- "cpu": [
- "arm"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-arm64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz",
- "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-x64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz",
- "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/darwin-arm64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz",
- "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/darwin-x64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz",
- "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/freebsd-arm64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz",
- "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/freebsd-x64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz",
- "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-arm": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz",
- "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==",
- "cpu": [
- "arm"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-arm64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz",
- "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-ia32": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz",
- "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==",
- "cpu": [
- "ia32"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-loong64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz",
- "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==",
- "cpu": [
- "loong64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-mips64el": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz",
- "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==",
- "cpu": [
- "mips64el"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-ppc64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz",
- "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==",
- "cpu": [
- "ppc64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-riscv64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz",
- "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==",
- "cpu": [
- "riscv64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-s390x": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz",
- "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==",
- "cpu": [
- "s390x"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-x64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz",
- "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/netbsd-arm64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz",
- "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/netbsd-x64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz",
- "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openbsd-arm64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz",
- "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openbsd-x64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz",
- "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openharmony-arm64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz",
- "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "openharmony"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/sunos-x64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz",
- "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-arm64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz",
- "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-ia32": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz",
- "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==",
- "cpu": [
- "ia32"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-x64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz",
- "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@exodus/schemasafe": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.3.0.tgz",
- "integrity": "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==",
- "dev": true,
- "license": "MIT",
- "optional": true
- },
- "node_modules/@floating-ui/core": {
- "version": "1.7.5",
- "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz",
- "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==",
- "license": "MIT",
- "dependencies": {
- "@floating-ui/utils": "^0.2.11"
- }
- },
- "node_modules/@floating-ui/dom": {
- "version": "1.7.6",
- "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz",
- "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==",
- "license": "MIT",
- "dependencies": {
- "@floating-ui/core": "^1.7.5",
- "@floating-ui/utils": "^0.2.11"
- }
- },
- "node_modules/@floating-ui/utils": {
- "version": "0.2.11",
- "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz",
- "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
- "license": "MIT"
- },
- "node_modules/@hapi/hoek": {
- "version": "9.3.0",
- "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
- "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==",
- "dev": true,
- "license": "BSD-3-Clause",
- "optional": true
- },
- "node_modules/@hapi/topo": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz",
- "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==",
- "dev": true,
- "license": "BSD-3-Clause",
- "optional": true,
- "dependencies": {
- "@hapi/hoek": "^9.0.0"
- }
- },
- "node_modules/@internationalized/date": {
- "version": "3.12.0",
- "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.12.0.tgz",
- "integrity": "sha512-/PyIMzK29jtXaGU23qTvNZxvBXRtKbNnGDFD+PY6CZw/Y8Ex8pFUzkuCJCG9aOqmShjqhS9mPqP6Dk5onQY8rQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@swc/helpers": "^0.5.0"
- }
- },
- "node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.13",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
- "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
- "license": "MIT",
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.0",
- "@jridgewell/trace-mapping": "^0.3.24"
- }
- },
- "node_modules/@jridgewell/remapping": {
- "version": "2.3.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
- "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
- "license": "MIT",
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.24"
- }
- },
- "node_modules/@jridgewell/resolve-uri": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
- "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
- "license": "MIT",
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
- "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
- "license": "MIT"
- },
- "node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.31",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
- "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
- "license": "MIT",
- "dependencies": {
- "@jridgewell/resolve-uri": "^3.1.0",
- "@jridgewell/sourcemap-codec": "^1.4.14"
- }
- },
- "node_modules/@lucide/svelte": {
- "version": "1.16.0",
- "resolved": "https://registry.npmjs.org/@lucide/svelte/-/svelte-1.16.0.tgz",
- "integrity": "sha512-AvvPJnaWxeiNkAljI5MsSEc84yHPLMaWQIAJOcbX7k9au/f9ITS7cxTTQiautDiOFKVOXiYdZ+d6mtl88J+Kbg==",
- "dev": true,
- "license": "ISC",
- "peerDependencies": {
- "svelte": "^5"
- }
- },
- "node_modules/@polka/url": {
- "version": "1.0.0-next.29",
- "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz",
- "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==",
- "devOptional": true,
- "license": "MIT"
- },
- "node_modules/@poppinss/macroable": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@poppinss/macroable/-/macroable-1.1.2.tgz",
- "integrity": "sha512-FAVBRzzWhYP5mA3lCwLH1A0fKBqq5anyjGet90Z81aRK5c/+LTGUE1zJhZrErjaenBSOOI9BVUs3WVmotneFQA==",
- "dev": true,
- "license": "MIT",
- "optional": true
- },
- "node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz",
- "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==",
- "cpu": [
- "arm"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-android-arm64": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz",
- "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz",
- "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz",
- "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz",
- "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ]
- },
- "node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz",
- "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz",
- "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==",
- "cpu": [
- "arm"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz",
- "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==",
- "cpu": [
- "arm"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz",
- "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz",
- "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-loong64-gnu": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz",
- "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==",
- "cpu": [
- "loong64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-loong64-musl": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz",
- "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==",
- "cpu": [
- "loong64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-ppc64-gnu": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz",
- "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==",
- "cpu": [
- "ppc64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-ppc64-musl": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz",
- "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==",
- "cpu": [
- "ppc64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz",
- "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==",
- "cpu": [
- "riscv64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-riscv64-musl": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz",
- "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==",
- "cpu": [
- "riscv64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz",
- "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==",
- "cpu": [
- "s390x"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz",
- "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz",
- "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-openbsd-x64": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz",
- "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ]
- },
- "node_modules/@rollup/rollup-openharmony-arm64": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz",
- "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "openharmony"
- ]
- },
- "node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz",
- "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz",
- "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==",
- "cpu": [
- "ia32"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-x64-gnu": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz",
- "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz",
- "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@sideway/address": {
- "version": "4.1.5",
- "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
- "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==",
- "dev": true,
- "license": "BSD-3-Clause",
- "optional": true,
- "dependencies": {
- "@hapi/hoek": "^9.0.0"
- }
- },
- "node_modules/@sideway/formula": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz",
- "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==",
- "dev": true,
- "license": "BSD-3-Clause",
- "optional": true
- },
- "node_modules/@sideway/pinpoint": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz",
- "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==",
- "dev": true,
- "license": "BSD-3-Clause",
- "optional": true
- },
- "node_modules/@standard-schema/spec": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
- "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
- "devOptional": true,
- "license": "MIT"
- },
- "node_modules/@sveltejs/acorn-typescript": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.9.tgz",
- "integrity": "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==",
- "license": "MIT",
- "peerDependencies": {
- "acorn": "^8.9.0"
- }
- },
- "node_modules/@sveltejs/adapter-static": {
- "version": "3.0.10",
- "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.10.tgz",
- "integrity": "sha512-7D9lYFWJmB7zxZyTE/qxjksvMqzMuYrrsyh1f4AlZqeZeACPRySjbC3aFiY55wb1tWUaKOQG9PVbm74JcN2Iew==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "@sveltejs/kit": "^2.0.0"
- }
- },
- "node_modules/@sveltejs/kit": {
- "version": "2.56.1",
- "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.56.1.tgz",
- "integrity": "sha512-9hDOl3yUh8UXWt+mN29dbcdrW0vNwPvMqi01y2Mw+ceErNIISh8MeEY7fXT2Dx1CjC/kfsVqrbxw7DifYr4hsg==",
- "devOptional": true,
- "license": "MIT",
- "dependencies": {
- "@standard-schema/spec": "^1.0.0",
- "@sveltejs/acorn-typescript": "^1.0.5",
- "@types/cookie": "^0.6.0",
- "acorn": "^8.14.1",
- "cookie": "^0.6.0",
- "devalue": "^5.6.4",
- "esm-env": "^1.2.2",
- "kleur": "^4.1.5",
- "magic-string": "^0.30.5",
- "mrmime": "^2.0.0",
- "set-cookie-parser": "^3.0.0",
- "sirv": "^3.0.0"
- },
- "bin": {
- "svelte-kit": "svelte-kit.js"
- },
- "engines": {
- "node": ">=18.13"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.0.0",
- "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0",
- "svelte": "^4.0.0 || ^5.0.0-next.0",
- "typescript": "^5.3.3 || ^6.0.0",
- "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0"
- },
- "peerDependenciesMeta": {
- "@opentelemetry/api": {
- "optional": true
- },
- "typescript": {
- "optional": true
- }
- }
- },
- "node_modules/@sveltejs/vite-plugin-svelte": {
- "version": "6.2.4",
- "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.4.tgz",
- "integrity": "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==",
- "devOptional": true,
- "license": "MIT",
- "dependencies": {
- "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0",
- "deepmerge": "^4.3.1",
- "magic-string": "^0.30.21",
- "obug": "^2.1.0",
- "vitefu": "^1.1.1"
- },
- "engines": {
- "node": "^20.19 || ^22.12 || >=24"
- },
- "peerDependencies": {
- "svelte": "^5.0.0",
- "vite": "^6.3.0 || ^7.0.0"
- }
- },
- "node_modules/@sveltejs/vite-plugin-svelte-inspector": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.2.tgz",
- "integrity": "sha512-TZzRTcEtZffICSAoZGkPSl6Etsj2torOVrx6Uw0KpXxrec9Gg6jFWQ60Q3+LmNGfZSxHRCZL7vXVZIWmuV50Ig==",
- "devOptional": true,
- "license": "MIT",
- "dependencies": {
- "obug": "^2.1.0"
- },
- "engines": {
- "node": "^20.19 || ^22.12 || >=24"
- },
- "peerDependencies": {
- "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0",
- "svelte": "^5.0.0",
- "vite": "^6.3.0 || ^7.0.0"
- }
- },
- "node_modules/@swc/helpers": {
- "version": "0.5.21",
- "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.21.tgz",
- "integrity": "sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==",
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.8.0"
- }
- },
- "node_modules/@tailwindcss/node": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz",
- "integrity": "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/remapping": "^2.3.5",
- "enhanced-resolve": "^5.19.0",
- "jiti": "^2.6.1",
- "lightningcss": "1.32.0",
- "magic-string": "^0.30.21",
- "source-map-js": "^1.2.1",
- "tailwindcss": "4.2.2"
- }
- },
- "node_modules/@tailwindcss/oxide": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.2.tgz",
- "integrity": "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 20"
- },
- "optionalDependencies": {
- "@tailwindcss/oxide-android-arm64": "4.2.2",
- "@tailwindcss/oxide-darwin-arm64": "4.2.2",
- "@tailwindcss/oxide-darwin-x64": "4.2.2",
- "@tailwindcss/oxide-freebsd-x64": "4.2.2",
- "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2",
- "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2",
- "@tailwindcss/oxide-linux-arm64-musl": "4.2.2",
- "@tailwindcss/oxide-linux-x64-gnu": "4.2.2",
- "@tailwindcss/oxide-linux-x64-musl": "4.2.2",
- "@tailwindcss/oxide-wasm32-wasi": "4.2.2",
- "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2",
- "@tailwindcss/oxide-win32-x64-msvc": "4.2.2"
- }
- },
- "node_modules/@tailwindcss/oxide-android-arm64": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.2.tgz",
- "integrity": "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">= 20"
- }
- },
- "node_modules/@tailwindcss/oxide-darwin-arm64": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.2.tgz",
- "integrity": "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 20"
- }
- },
- "node_modules/@tailwindcss/oxide-darwin-x64": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.2.tgz",
- "integrity": "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 20"
- }
- },
- "node_modules/@tailwindcss/oxide-freebsd-x64": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.2.tgz",
- "integrity": "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">= 20"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.2.tgz",
- "integrity": "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 20"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.2.tgz",
- "integrity": "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 20"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.2.tgz",
- "integrity": "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 20"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.2.tgz",
- "integrity": "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 20"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-x64-musl": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.2.tgz",
- "integrity": "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 20"
- }
- },
- "node_modules/@tailwindcss/oxide-wasm32-wasi": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.2.tgz",
- "integrity": "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==",
- "bundleDependencies": [
- "@napi-rs/wasm-runtime",
- "@emnapi/core",
- "@emnapi/runtime",
- "@tybys/wasm-util",
- "@emnapi/wasi-threads",
- "tslib"
- ],
- "cpu": [
- "wasm32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@emnapi/core": "^1.8.1",
- "@emnapi/runtime": "^1.8.1",
- "@emnapi/wasi-threads": "^1.1.0",
- "@napi-rs/wasm-runtime": "^1.1.1",
- "@tybys/wasm-util": "^0.10.1",
- "tslib": "^2.8.1"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.2.tgz",
- "integrity": "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 20"
- }
- },
- "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.2.tgz",
- "integrity": "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 20"
- }
- },
- "node_modules/@tailwindcss/vite": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.2.tgz",
- "integrity": "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@tailwindcss/node": "4.2.2",
- "@tailwindcss/oxide": "4.2.2",
- "tailwindcss": "4.2.2"
- },
- "peerDependencies": {
- "vite": "^5.2.0 || ^6 || ^7 || ^8"
- }
- },
- "node_modules/@tanstack/table-core": {
- "version": "8.21.3",
- "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz",
- "integrity": "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==",
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/tannerlinsley"
- }
- },
- "node_modules/@types/cookie": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
- "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
- "devOptional": true,
- "license": "MIT"
- },
- "node_modules/@types/estree": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
- "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
- "license": "MIT"
- },
- "node_modules/@types/node": {
- "version": "22.19.17",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz",
- "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==",
- "devOptional": true,
- "license": "MIT",
- "dependencies": {
- "undici-types": "~6.21.0"
- }
- },
- "node_modules/@types/trusted-types": {
- "version": "2.0.7",
- "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
- "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
- "license": "MIT"
- },
- "node_modules/@types/validator": {
- "version": "13.15.10",
- "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz",
- "integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==",
- "dev": true,
- "license": "MIT",
- "optional": true
- },
- "node_modules/@typeschema/class-validator": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/@typeschema/class-validator/-/class-validator-0.3.0.tgz",
- "integrity": "sha512-OJSFeZDIQ8EK1HTljKLT5CItM2wsbgczLN8tMEfz3I1Lmhc5TBfkZ0eikFzUC16tI3d1Nag7um6TfCgp2I2Bww==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@typeschema/core": "0.14.0"
- },
- "peerDependencies": {
- "class-validator": "^0.14.1"
- },
- "peerDependenciesMeta": {
- "class-validator": {
- "optional": true
- }
- }
- },
- "node_modules/@typeschema/core": {
- "version": "0.14.0",
- "resolved": "https://registry.npmjs.org/@typeschema/core/-/core-0.14.0.tgz",
- "integrity": "sha512-Ia6PtZHcL3KqsAWXjMi5xIyZ7XMH4aSnOQes8mfMLx+wGFGtGRNlwe6Y7cYvX+WfNK67OL0/HSe9t8QDygV0/w==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "peerDependencies": {
- "@types/json-schema": "^7.0.15"
- },
- "peerDependenciesMeta": {
- "@types/json-schema": {
- "optional": true
- }
- }
- },
- "node_modules/@typescript-eslint/types": {
- "version": "8.58.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.0.tgz",
- "integrity": "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==",
- "license": "MIT",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@valibot/to-json-schema": {
- "version": "1.7.0",
- "resolved": "https://registry.npmjs.org/@valibot/to-json-schema/-/to-json-schema-1.7.0.tgz",
- "integrity": "sha512-Y3pPVibbIOHzohrlxSINvO7w/bvXkoYS3BQHoImV9ynE+bXKf171bdMucPurV2zp7gdmt0L1HCcNAsbo7cFRQw==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "peerDependencies": {
- "valibot": "^1.4.0"
- }
- },
- "node_modules/@vinejs/compiler": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/@vinejs/compiler/-/compiler-3.0.0.tgz",
- "integrity": "sha512-v9Lsv59nR56+bmy2p0+czjZxsLHwaibJ+SV5iK9JJfehlJMa501jUJQqqz4X/OqKXrxtE3uTQmSqjUqzF3B2mw==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@vinejs/vine": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/@vinejs/vine/-/vine-3.0.1.tgz",
- "integrity": "sha512-ZtvYkYpZOYdvbws3uaOAvTFuvFXoQGAtmzeiXu+XSMGxi5GVsODpoI9Xu9TplEMuD/5fmAtBbKb9cQHkWkLXDQ==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@poppinss/macroable": "^1.0.4",
- "@types/validator": "^13.12.2",
- "@vinejs/compiler": "^3.0.0",
- "camelcase": "^8.0.0",
- "dayjs": "^1.11.13",
- "dlv": "^1.1.3",
- "normalize-url": "^8.0.1",
- "validator": "^13.12.0"
- },
- "engines": {
- "node": ">=18.16.0"
- }
- },
- "node_modules/acorn": {
- "version": "8.16.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
- "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
- "license": "MIT",
- "bin": {
- "acorn": "bin/acorn"
- },
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/aria-query": {
- "version": "5.3.1",
- "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz",
- "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/arkregex": {
- "version": "0.0.5",
- "resolved": "https://registry.npmjs.org/arkregex/-/arkregex-0.0.5.tgz",
- "integrity": "sha512-ncYjBdLlh5/QnVsAA8De16Tc9EqmYM7y/WU9j+236KcyYNUXogpz3sC4ATIZYzzLxwI+0sEOaQLEmLmRleaEXw==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@ark/util": "0.56.0"
- }
- },
- "node_modules/arktype": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/arktype/-/arktype-2.2.0.tgz",
- "integrity": "sha512-t54MZ7ti5BhOEvzEkgKnWvqj+UbDfWig+DHr5I34xatymPusKLS0lQpNJd8M6DzmIto2QGszHfNKoFIT8tMCZQ==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@ark/schema": "0.56.0",
- "@ark/util": "0.56.0",
- "arkregex": "0.0.5"
- }
- },
- "node_modules/axobject-query": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
- "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/bits-ui": {
- "version": "2.17.2",
- "resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-2.17.2.tgz",
- "integrity": "sha512-Xbmrlf3ft/5RtMgUL/uam+rcrWYM4LoIGosklV34iSE6GGsPFWgaivzzX60rnWjWlG5F7ZuXXZ4iag949UekMA==",
- "license": "MIT",
- "dependencies": {
- "@floating-ui/core": "^1.7.1",
- "@floating-ui/dom": "^1.7.1",
- "esm-env": "^1.1.2",
- "runed": "^0.35.1",
- "svelte-toolbelt": "^0.10.6",
- "tabbable": "^6.2.0"
- },
- "engines": {
- "node": ">=20"
- },
- "funding": {
- "url": "https://github.com/sponsors/huntabyte"
- },
- "peerDependencies": {
- "@internationalized/date": "^3.8.1",
- "svelte": "^5.33.0"
- }
- },
- "node_modules/camelcase": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz",
- "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "engines": {
- "node": ">=16"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/chokidar": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
- "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "readdirp": "^4.0.1"
- },
- "engines": {
- "node": ">= 14.16.0"
- },
- "funding": {
- "url": "https://paulmillr.com/funding/"
- }
- },
- "node_modules/class-validator": {
- "version": "0.14.4",
- "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.4.tgz",
- "integrity": "sha512-AwNusCCam51q703dW82x95tOqQp6oC9HNUl724KxJJOfnKscI8dOloXFgyez7LbTTKWuRBA37FScqVbJEoq8Yw==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@types/validator": "^13.15.3",
- "libphonenumber-js": "^1.11.1",
- "validator": "^13.15.22"
- }
- },
- "node_modules/clsx": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
- "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/cookie": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
- "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
- "devOptional": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/dayjs": {
- "version": "1.11.20",
- "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz",
- "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==",
- "dev": true,
- "license": "MIT",
- "optional": true
- },
- "node_modules/deepmerge": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
- "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
- "devOptional": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/dequal": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
- "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/detect-libc": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
- "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
- "devOptional": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/devalue": {
- "version": "5.6.4",
- "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.4.tgz",
- "integrity": "sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA==",
- "license": "MIT"
- },
- "node_modules/dlv": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
- "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
- "dev": true,
- "license": "MIT",
- "optional": true
- },
- "node_modules/effect": {
- "version": "3.21.2",
- "resolved": "https://registry.npmjs.org/effect/-/effect-3.21.2.tgz",
- "integrity": "sha512-rXd2FGDM8KdjSIrc+mqEELo7ScW7xTVxEf1iInmPSpIde9/nyGuFM710cjTo7/EreGXiUX2MOonPpprbz2XHCg==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@standard-schema/spec": "^1.0.0",
- "fast-check": "^3.23.1"
- }
- },
- "node_modules/enhanced-resolve": {
- "version": "5.20.1",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz",
- "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "graceful-fs": "^4.2.4",
- "tapable": "^2.3.0"
- },
- "engines": {
- "node": ">=10.13.0"
- }
- },
- "node_modules/esbuild": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz",
- "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==",
- "devOptional": true,
- "hasInstallScript": true,
- "license": "MIT",
- "bin": {
- "esbuild": "bin/esbuild"
- },
- "engines": {
- "node": ">=18"
- },
- "optionalDependencies": {
- "@esbuild/aix-ppc64": "0.27.7",
- "@esbuild/android-arm": "0.27.7",
- "@esbuild/android-arm64": "0.27.7",
- "@esbuild/android-x64": "0.27.7",
- "@esbuild/darwin-arm64": "0.27.7",
- "@esbuild/darwin-x64": "0.27.7",
- "@esbuild/freebsd-arm64": "0.27.7",
- "@esbuild/freebsd-x64": "0.27.7",
- "@esbuild/linux-arm": "0.27.7",
- "@esbuild/linux-arm64": "0.27.7",
- "@esbuild/linux-ia32": "0.27.7",
- "@esbuild/linux-loong64": "0.27.7",
- "@esbuild/linux-mips64el": "0.27.7",
- "@esbuild/linux-ppc64": "0.27.7",
- "@esbuild/linux-riscv64": "0.27.7",
- "@esbuild/linux-s390x": "0.27.7",
- "@esbuild/linux-x64": "0.27.7",
- "@esbuild/netbsd-arm64": "0.27.7",
- "@esbuild/netbsd-x64": "0.27.7",
- "@esbuild/openbsd-arm64": "0.27.7",
- "@esbuild/openbsd-x64": "0.27.7",
- "@esbuild/openharmony-arm64": "0.27.7",
- "@esbuild/sunos-x64": "0.27.7",
- "@esbuild/win32-arm64": "0.27.7",
- "@esbuild/win32-ia32": "0.27.7",
- "@esbuild/win32-x64": "0.27.7"
- }
- },
- "node_modules/esm-env": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
- "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==",
- "license": "MIT"
- },
- "node_modules/esrap": {
- "version": "2.2.4",
- "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.4.tgz",
- "integrity": "sha512-suICpxAmZ9A8bzJjEl/+rLJiDKC0X4gYWUxT6URAWBLvlXmtbZd5ySMu/N2ZGEtMCAmflUDPSehrP9BQcsGcSg==",
- "license": "MIT",
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.4.15",
- "@typescript-eslint/types": "^8.2.0"
- }
- },
- "node_modules/fast-check": {
- "version": "3.23.2",
- "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz",
- "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==",
- "dev": true,
- "funding": [
- {
- "type": "individual",
- "url": "https://github.com/sponsors/dubzzz"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fast-check"
- }
- ],
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "pure-rand": "^6.1.0"
- },
- "engines": {
- "node": ">=8.0.0"
- }
- },
- "node_modules/fdir": {
- "version": "6.5.0",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
- "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
- "devOptional": true,
- "license": "MIT",
- "engines": {
- "node": ">=12.0.0"
- },
- "peerDependencies": {
- "picomatch": "^3 || ^4"
- },
- "peerDependenciesMeta": {
- "picomatch": {
- "optional": true
- }
- }
- },
- "node_modules/formsnap": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/formsnap/-/formsnap-2.0.1.tgz",
- "integrity": "sha512-iJSe4YKd/W6WhLwKDVJU9FQeaJRpEFuolhju7ZXlRpUVyDdqFdMP8AUBICgnVvQPyP41IPAlBa/v0Eo35iE6wQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "svelte-toolbelt": "^0.5.0"
- },
- "engines": {
- "node": ">=18",
- "pnpm": ">=8.7.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/huntabyte"
- },
- "peerDependencies": {
- "svelte": "^5.0.0",
- "sveltekit-superforms": "^2.19.0"
- }
- },
- "node_modules/formsnap/node_modules/svelte-toolbelt": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/svelte-toolbelt/-/svelte-toolbelt-0.5.0.tgz",
- "integrity": "sha512-t3tenZcnfQoIeRuQf/jBU7bvTeT3TGkcEE+1EUr5orp0lR7NEpprflpuie3x9Dn0W9nOKqs3HwKGJeeN5Ok1sQ==",
- "dev": true,
- "funding": [
- "https://github.com/sponsors/huntabyte"
- ],
- "dependencies": {
- "clsx": "^2.1.1",
- "style-to-object": "^1.0.8"
- },
- "engines": {
- "node": ">=18",
- "pnpm": ">=8.7.0"
- },
- "peerDependencies": {
- "svelte": "^5.0.0-next.126"
- }
- },
- "node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "hasInstallScript": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
- "node_modules/graceful-fs": {
- "version": "4.2.11",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
- "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/inline-style-parser": {
- "version": "0.2.7",
- "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz",
- "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==",
- "license": "MIT"
- },
- "node_modules/is-reference": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
- "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
- "license": "MIT",
- "dependencies": {
- "@types/estree": "^1.0.6"
- }
- },
- "node_modules/jiti": {
- "version": "2.6.1",
- "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
- "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
- "devOptional": true,
- "license": "MIT",
- "bin": {
- "jiti": "lib/jiti-cli.mjs"
- }
- },
- "node_modules/joi": {
- "version": "17.13.3",
- "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz",
- "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==",
- "dev": true,
- "license": "BSD-3-Clause",
- "optional": true,
- "dependencies": {
- "@hapi/hoek": "^9.3.0",
- "@hapi/topo": "^5.1.0",
- "@sideway/address": "^4.1.5",
- "@sideway/formula": "^3.0.1",
- "@sideway/pinpoint": "^2.0.0"
- }
- },
- "node_modules/json-schema-to-ts": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz",
- "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@babel/runtime": "^7.18.3",
- "ts-algebra": "^2.0.0"
- },
- "engines": {
- "node": ">=16"
- }
- },
- "node_modules/kleur": {
- "version": "4.1.5",
- "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
- "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
- "devOptional": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/libphonenumber-js": {
- "version": "1.13.2",
- "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.13.2.tgz",
- "integrity": "sha512-S3kmBrptp3yRTm83NUcHy9g1vbwiWMzI8WvY22+koBJ6zkRteLnedBL2VX0MIAGwx2yiyxX4J85pceZyQ6ffgg==",
- "dev": true,
- "license": "MIT",
- "optional": true
- },
- "node_modules/lightningcss": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
- "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
- "devOptional": true,
- "license": "MPL-2.0",
- "dependencies": {
- "detect-libc": "^2.0.3"
- },
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- },
- "optionalDependencies": {
- "lightningcss-android-arm64": "1.32.0",
- "lightningcss-darwin-arm64": "1.32.0",
- "lightningcss-darwin-x64": "1.32.0",
- "lightningcss-freebsd-x64": "1.32.0",
- "lightningcss-linux-arm-gnueabihf": "1.32.0",
- "lightningcss-linux-arm64-gnu": "1.32.0",
- "lightningcss-linux-arm64-musl": "1.32.0",
- "lightningcss-linux-x64-gnu": "1.32.0",
- "lightningcss-linux-x64-musl": "1.32.0",
- "lightningcss-win32-arm64-msvc": "1.32.0",
- "lightningcss-win32-x64-msvc": "1.32.0"
- }
- },
- "node_modules/lightningcss-android-arm64": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
- "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
- "cpu": [
- "arm64"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-darwin-arm64": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
- "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
- "cpu": [
- "arm64"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-darwin-x64": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
- "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
- "cpu": [
- "x64"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-freebsd-x64": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
- "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
- "cpu": [
- "x64"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-arm-gnueabihf": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
- "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
- "cpu": [
- "arm"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-arm64-gnu": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
- "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
- "cpu": [
- "arm64"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-arm64-musl": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
- "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
- "cpu": [
- "arm64"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-x64-gnu": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
- "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
- "cpu": [
- "x64"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-x64-musl": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
- "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
- "cpu": [
- "x64"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-win32-arm64-msvc": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
- "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
- "cpu": [
- "arm64"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-win32-x64-msvc": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
- "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
- "cpu": [
- "x64"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/locate-character": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
- "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
- "license": "MIT"
- },
- "node_modules/lz-string": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
- "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
- "license": "MIT",
- "bin": {
- "lz-string": "bin/bin.js"
- }
- },
- "node_modules/magic-string": {
- "version": "0.30.21",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
- "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
- "license": "MIT",
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.5"
- }
- },
- "node_modules/memoize-weak": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/memoize-weak/-/memoize-weak-1.0.2.tgz",
- "integrity": "sha512-gj39xkrjEw7nCn4nJ1M5ms6+MyMlyiGmttzsqAUsAKn6bYKwuTHh/AO3cKPF8IBrTIYTxb0wWXFs3E//Y8VoWQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/mri": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
- "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/mrmime": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
- "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==",
- "devOptional": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/nanoid": {
- "version": "3.3.11",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
- "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
- "devOptional": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "bin": {
- "nanoid": "bin/nanoid.cjs"
- },
- "engines": {
- "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
- }
- },
- "node_modules/normalize-url": {
- "version": "8.1.1",
- "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.1.tgz",
- "integrity": "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "engines": {
- "node": ">=14.16"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/obug": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
- "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==",
- "devOptional": true,
- "funding": [
- "https://github.com/sponsors/sxzz",
- "https://opencollective.com/debug"
- ],
- "license": "MIT"
- },
- "node_modules/picocolors": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
- "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
- "devOptional": true,
- "license": "ISC"
- },
- "node_modules/picomatch": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
- "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
- "devOptional": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/postcss": {
- "version": "8.5.8",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
- "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
- "devOptional": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/postcss"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "nanoid": "^3.3.11",
- "picocolors": "^1.1.1",
- "source-map-js": "^1.2.1"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- }
- },
- "node_modules/prettier": {
- "version": "3.8.1",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz",
- "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "prettier": "bin/prettier.cjs"
- },
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/prettier/prettier?sponsor=1"
- }
- },
- "node_modules/prettier-plugin-svelte": {
- "version": "3.5.1",
- "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.5.1.tgz",
- "integrity": "sha512-65+fr5+cgIKWKiqM1Doum4uX6bY8iFCdztvvp2RcF+AJoieaw9kJOFMNcJo/bkmKYsxFaM9OsVZK/gWauG/5mg==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "prettier": "^3.0.0",
- "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0"
- }
- },
- "node_modules/prettier-plugin-tailwindcss": {
- "version": "0.7.2",
- "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.7.2.tgz",
- "integrity": "sha512-LkphyK3Fw+q2HdMOoiEHWf93fNtYJwfamoKPl7UwtjFQdei/iIBoX11G6j706FzN3ymX9mPVi97qIY8328vdnA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=20.19"
- },
- "peerDependencies": {
- "@ianvs/prettier-plugin-sort-imports": "*",
- "@prettier/plugin-hermes": "*",
- "@prettier/plugin-oxc": "*",
- "@prettier/plugin-pug": "*",
- "@shopify/prettier-plugin-liquid": "*",
- "@trivago/prettier-plugin-sort-imports": "*",
- "@zackad/prettier-plugin-twig": "*",
- "prettier": "^3.0",
- "prettier-plugin-astro": "*",
- "prettier-plugin-css-order": "*",
- "prettier-plugin-jsdoc": "*",
- "prettier-plugin-marko": "*",
- "prettier-plugin-multiline-arrays": "*",
- "prettier-plugin-organize-attributes": "*",
- "prettier-plugin-organize-imports": "*",
- "prettier-plugin-sort-imports": "*",
- "prettier-plugin-svelte": "*"
- },
- "peerDependenciesMeta": {
- "@ianvs/prettier-plugin-sort-imports": {
- "optional": true
- },
- "@prettier/plugin-hermes": {
- "optional": true
- },
- "@prettier/plugin-oxc": {
- "optional": true
- },
- "@prettier/plugin-pug": {
- "optional": true
- },
- "@shopify/prettier-plugin-liquid": {
- "optional": true
- },
- "@trivago/prettier-plugin-sort-imports": {
- "optional": true
- },
- "@zackad/prettier-plugin-twig": {
- "optional": true
- },
- "prettier-plugin-astro": {
- "optional": true
- },
- "prettier-plugin-css-order": {
- "optional": true
- },
- "prettier-plugin-jsdoc": {
- "optional": true
- },
- "prettier-plugin-marko": {
- "optional": true
- },
- "prettier-plugin-multiline-arrays": {
- "optional": true
- },
- "prettier-plugin-organize-attributes": {
- "optional": true
- },
- "prettier-plugin-organize-imports": {
- "optional": true
- },
- "prettier-plugin-sort-imports": {
- "optional": true
- },
- "prettier-plugin-svelte": {
- "optional": true
- }
- }
- },
- "node_modules/property-expr": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz",
- "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==",
- "dev": true,
- "license": "MIT",
- "optional": true
- },
- "node_modules/pure-rand": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
- "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==",
- "dev": true,
- "funding": [
- {
- "type": "individual",
- "url": "https://github.com/sponsors/dubzzz"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fast-check"
- }
- ],
- "license": "MIT",
- "optional": true
- },
- "node_modules/readdirp": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
- "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 14.18.0"
- },
- "funding": {
- "type": "individual",
- "url": "https://paulmillr.com/funding/"
- }
- },
- "node_modules/rollup": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz",
- "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==",
- "devOptional": true,
- "license": "MIT",
- "dependencies": {
- "@types/estree": "1.0.8"
- },
- "bin": {
- "rollup": "dist/bin/rollup"
- },
- "engines": {
- "node": ">=18.0.0",
- "npm": ">=8.0.0"
- },
- "optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.60.1",
- "@rollup/rollup-android-arm64": "4.60.1",
- "@rollup/rollup-darwin-arm64": "4.60.1",
- "@rollup/rollup-darwin-x64": "4.60.1",
- "@rollup/rollup-freebsd-arm64": "4.60.1",
- "@rollup/rollup-freebsd-x64": "4.60.1",
- "@rollup/rollup-linux-arm-gnueabihf": "4.60.1",
- "@rollup/rollup-linux-arm-musleabihf": "4.60.1",
- "@rollup/rollup-linux-arm64-gnu": "4.60.1",
- "@rollup/rollup-linux-arm64-musl": "4.60.1",
- "@rollup/rollup-linux-loong64-gnu": "4.60.1",
- "@rollup/rollup-linux-loong64-musl": "4.60.1",
- "@rollup/rollup-linux-ppc64-gnu": "4.60.1",
- "@rollup/rollup-linux-ppc64-musl": "4.60.1",
- "@rollup/rollup-linux-riscv64-gnu": "4.60.1",
- "@rollup/rollup-linux-riscv64-musl": "4.60.1",
- "@rollup/rollup-linux-s390x-gnu": "4.60.1",
- "@rollup/rollup-linux-x64-gnu": "4.60.1",
- "@rollup/rollup-linux-x64-musl": "4.60.1",
- "@rollup/rollup-openbsd-x64": "4.60.1",
- "@rollup/rollup-openharmony-arm64": "4.60.1",
- "@rollup/rollup-win32-arm64-msvc": "4.60.1",
- "@rollup/rollup-win32-ia32-msvc": "4.60.1",
- "@rollup/rollup-win32-x64-gnu": "4.60.1",
- "@rollup/rollup-win32-x64-msvc": "4.60.1",
- "fsevents": "~2.3.2"
- }
- },
- "node_modules/runed": {
- "version": "0.35.1",
- "resolved": "https://registry.npmjs.org/runed/-/runed-0.35.1.tgz",
- "integrity": "sha512-2F4Q/FZzbeJTFdIS/PuOoPRSm92sA2LhzTnv6FXhCoENb3huf5+fDuNOg1LNvGOouy3u/225qxmuJvcV3IZK5Q==",
- "funding": [
- "https://github.com/sponsors/huntabyte",
- "https://github.com/sponsors/tglide"
- ],
- "license": "MIT",
- "dependencies": {
- "dequal": "^2.0.3",
- "esm-env": "^1.0.0",
- "lz-string": "^1.5.0"
- },
- "peerDependencies": {
- "@sveltejs/kit": "^2.21.0",
- "svelte": "^5.7.0"
- },
- "peerDependenciesMeta": {
- "@sveltejs/kit": {
- "optional": true
- }
- }
- },
- "node_modules/sade": {
- "version": "1.8.1",
- "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
- "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "mri": "^1.1.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/set-cookie-parser": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.0.tgz",
- "integrity": "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==",
- "devOptional": true,
- "license": "MIT"
- },
- "node_modules/sirv": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz",
- "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==",
- "devOptional": true,
- "license": "MIT",
- "dependencies": {
- "@polka/url": "^1.0.0-next.24",
- "mrmime": "^2.0.0",
- "totalist": "^3.0.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/source-map-js": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
- "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
- "devOptional": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/style-to-object": {
- "version": "1.0.14",
- "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz",
- "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==",
- "license": "MIT",
- "dependencies": {
- "inline-style-parser": "0.2.7"
- }
- },
- "node_modules/superstruct": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-2.0.2.tgz",
- "integrity": "sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/svelte": {
- "version": "5.55.1",
- "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.55.1.tgz",
- "integrity": "sha512-QjvU7EFemf6mRzdMGlAFttMWtAAVXrax61SZYHdkD6yoVGQ89VeyKfZD4H1JrV1WLmJBxWhFch9H6ig/87VGjw==",
- "license": "MIT",
- "dependencies": {
- "@jridgewell/remapping": "^2.3.4",
- "@jridgewell/sourcemap-codec": "^1.5.0",
- "@sveltejs/acorn-typescript": "^1.0.5",
- "@types/estree": "^1.0.5",
- "@types/trusted-types": "^2.0.7",
- "acorn": "^8.12.1",
- "aria-query": "5.3.1",
- "axobject-query": "^4.1.0",
- "clsx": "^2.1.1",
- "devalue": "^5.6.4",
- "esm-env": "^1.2.1",
- "esrap": "^2.2.4",
- "is-reference": "^3.0.3",
- "locate-character": "^3.0.0",
- "magic-string": "^0.30.11",
- "zimmerframe": "^1.1.2"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/svelte-check": {
- "version": "4.4.6",
- "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.4.6.tgz",
- "integrity": "sha512-kP1zG81EWaFe9ZyTv4ZXv44Csi6Pkdpb7S3oj6m+K2ec/IcDg/a8LsFsnVLqm2nxtkSwsd5xPj/qFkTBgXHXjg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/trace-mapping": "^0.3.25",
- "chokidar": "^4.0.1",
- "fdir": "^6.2.0",
- "picocolors": "^1.0.0",
- "sade": "^1.7.4"
- },
- "bin": {
- "svelte-check": "bin/svelte-check"
- },
- "engines": {
- "node": ">= 18.0.0"
- },
- "peerDependencies": {
- "svelte": "^4.0.0 || ^5.0.0-next.0",
- "typescript": ">=5.0.0"
- }
- },
- "node_modules/svelte-sonner": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/svelte-sonner/-/svelte-sonner-1.1.0.tgz",
- "integrity": "sha512-3lYM6ZIqWe+p9vwwWHGWP/ZdvHiUtzURsud2quIxivrX4rvpXh6i+geBGn0m3JS6KwW6W8VgbOl3xQMcDuh6gg==",
- "license": "MIT",
- "dependencies": {
- "runed": "^0.28.0"
- },
- "peerDependencies": {
- "svelte": "^5.0.0"
- }
- },
- "node_modules/svelte-sonner/node_modules/runed": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/runed/-/runed-0.28.0.tgz",
- "integrity": "sha512-k2xx7RuO9hWcdd9f+8JoBeqWtYrm5CALfgpkg2YDB80ds/QE4w0qqu34A7fqiAwiBBSBQOid7TLxwxVC27ymWQ==",
- "funding": [
- "https://github.com/sponsors/huntabyte",
- "https://github.com/sponsors/tglide"
- ],
- "license": "MIT",
- "dependencies": {
- "esm-env": "^1.0.0"
- },
- "peerDependencies": {
- "svelte": "^5.7.0"
- }
- },
- "node_modules/svelte-toolbelt": {
- "version": "0.10.6",
- "resolved": "https://registry.npmjs.org/svelte-toolbelt/-/svelte-toolbelt-0.10.6.tgz",
- "integrity": "sha512-YWuX+RE+CnWYx09yseAe4ZVMM7e7GRFZM6OYWpBKOb++s+SQ8RBIMMe+Bs/CznBMc0QPLjr+vDBxTAkozXsFXQ==",
- "funding": [
- "https://github.com/sponsors/huntabyte"
- ],
- "dependencies": {
- "clsx": "^2.1.1",
- "runed": "^0.35.1",
- "style-to-object": "^1.0.8"
- },
- "engines": {
- "node": ">=18",
- "pnpm": ">=8.7.0"
- },
- "peerDependencies": {
- "svelte": "^5.30.2"
- }
- },
- "node_modules/sveltekit-superforms": {
- "version": "2.30.1",
- "resolved": "https://registry.npmjs.org/sveltekit-superforms/-/sveltekit-superforms-2.30.1.tgz",
- "integrity": "sha512-wBzyqsE0idvEJWuNJ+HCiAtdxa7Z55GZ8jmtlVHJfonrk9bRYC49MoPaloYyFoYuU3QPy6Omna/Qzn1kaIkgew==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/ciscoheat"
- },
- {
- "type": "ko-fi",
- "url": "https://ko-fi.com/ciscoheat"
- },
- {
- "type": "paypal",
- "url": "https://www.paypal.com/donate/?hosted_button_id=NY7F5ALHHSVQS"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "devalue": "^5.6.4",
- "memoize-weak": "^1.0.2",
- "ts-deepmerge": "^7.0.3"
- },
- "optionalDependencies": {
- "@exodus/schemasafe": "^1.3.0",
- "@standard-schema/spec": "^1.1.0",
- "@typeschema/class-validator": "^0.3.0",
- "@valibot/to-json-schema": "^1.6.0",
- "@vinejs/vine": "^3.0.1",
- "arktype": "^2.2.0",
- "class-validator": "^0.14.4",
- "effect": "^3.21.0",
- "joi": "^17.13.3",
- "json-schema-to-ts": "^3.1.1",
- "superstruct": "^2.0.2",
- "typebox": "^1.1.6",
- "valibot": "^1.3.1",
- "yup": "^1.7.1",
- "zod": "^4.3.6",
- "zod-v3-to-json-schema": "^4.0.0"
- },
- "peerDependencies": {
- "@exodus/schemasafe": "^1.3.0",
- "@sveltejs/kit": "1.x || 2.x",
- "@typeschema/class-validator": "^0.3.0",
- "@vinejs/vine": "^1.8.0 || ^2.0.0 || ^3.0.0",
- "arktype": ">=2.0.0-rc.23",
- "class-validator": "^0.14.1",
- "effect": "^3.21.0",
- "joi": "^17.13.1",
- "superstruct": "^2.0.2",
- "svelte": "3.x || 4.x || >=5.0.0-next.51",
- "typebox": "^1.0.36",
- "valibot": "^1.2.0",
- "yup": "^1.4.0",
- "zod": "^3.25.0 || ^4.0.0"
- },
- "peerDependenciesMeta": {
- "@exodus/schemasafe": {
- "optional": true
- },
- "@typeschema/class-validator": {
- "optional": true
- },
- "@vinejs/vine": {
- "optional": true
- },
- "arktype": {
- "optional": true
- },
- "class-validator": {
- "optional": true
- },
- "effect": {
- "optional": true
- },
- "joi": {
- "optional": true
- },
- "superstruct": {
- "optional": true
- },
- "typebox": {
- "optional": true
- },
- "valibot": {
- "optional": true
- },
- "yup": {
- "optional": true
- },
- "zod": {
- "optional": true
- }
- }
- },
- "node_modules/tabbable": {
- "version": "6.4.0",
- "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz",
- "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==",
- "license": "MIT"
- },
- "node_modules/tailwind-merge": {
- "version": "3.5.0",
- "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz",
- "integrity": "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==",
- "license": "MIT",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/dcastil"
- }
- },
- "node_modules/tailwind-variants": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-3.2.2.tgz",
- "integrity": "sha512-Mi4kHeMTLvKlM98XPnK+7HoBPmf4gygdFmqQPaDivc3DpYS6aIY6KiG/PgThrGvii5YZJqRsPz0aPyhoFzmZgg==",
- "license": "MIT",
- "engines": {
- "node": ">=16.x",
- "pnpm": ">=7.x"
- },
- "peerDependencies": {
- "tailwind-merge": ">=3.0.0",
- "tailwindcss": "*"
- },
- "peerDependenciesMeta": {
- "tailwind-merge": {
- "optional": true
- }
- }
- },
- "node_modules/tailwindcss": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz",
- "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==",
- "license": "MIT"
- },
- "node_modules/tapable": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz",
- "integrity": "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
- }
- },
- "node_modules/tiny-case": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz",
- "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==",
- "dev": true,
- "license": "MIT",
- "optional": true
- },
- "node_modules/tinyglobby": {
- "version": "0.2.15",
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
- "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
- "devOptional": true,
- "license": "MIT",
- "dependencies": {
- "fdir": "^6.5.0",
- "picomatch": "^4.0.3"
- },
- "engines": {
- "node": ">=12.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/SuperchupuDev"
- }
- },
- "node_modules/toposort": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
- "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==",
- "dev": true,
- "license": "MIT",
- "optional": true
- },
- "node_modules/totalist": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
- "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==",
- "devOptional": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/ts-algebra": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz",
- "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==",
- "dev": true,
- "license": "MIT",
- "optional": true
- },
- "node_modules/ts-deepmerge": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/ts-deepmerge/-/ts-deepmerge-7.0.3.tgz",
- "integrity": "sha512-Du/ZW2RfwV/D4cmA5rXafYjBQVuvu4qGiEEla4EmEHVHgRdx68Gftx7i66jn2bzHPwSVZY36Ae6OuDn9el4ZKA==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=14.13.1"
- }
- },
- "node_modules/tslib": {
- "version": "2.8.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
- "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
- "license": "0BSD"
- },
- "node_modules/tw-animate-css": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz",
- "integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==",
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/Wombosvideo"
- }
- },
- "node_modules/type-fest": {
- "version": "2.19.0",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
- "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
- "dev": true,
- "license": "(MIT OR CC0-1.0)",
- "optional": true,
- "engines": {
- "node": ">=12.20"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/typebox": {
- "version": "1.1.38",
- "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.1.38.tgz",
- "integrity": "sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA==",
- "dev": true,
- "license": "MIT",
- "optional": true
- },
- "node_modules/typescript": {
- "version": "5.9.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
- "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
- "devOptional": true,
- "license": "Apache-2.0",
- "bin": {
- "tsc": "bin/tsc",
- "tsserver": "bin/tsserver"
- },
- "engines": {
- "node": ">=14.17"
- }
- },
- "node_modules/undici-types": {
- "version": "6.21.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
- "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
- "devOptional": true,
- "license": "MIT"
- },
- "node_modules/valibot": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.4.0.tgz",
- "integrity": "sha512-iC/x7fVcSyOwlm/VSt7RlHnzNGLGvR9GnxdifUeWoCJo0q4ZZvrVkIHC6faTlkxG47I2Y4UrFquPuVHCrOnrLg==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "peerDependencies": {
- "typescript": ">=5"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
- }
- },
- "node_modules/validator": {
- "version": "13.15.35",
- "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.35.tgz",
- "integrity": "sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "engines": {
- "node": ">= 0.10"
- }
- },
- "node_modules/vite": {
- "version": "7.3.1",
- "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
- "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
- "devOptional": true,
- "license": "MIT",
- "dependencies": {
- "esbuild": "^0.27.0",
- "fdir": "^6.5.0",
- "picomatch": "^4.0.3",
- "postcss": "^8.5.6",
- "rollup": "^4.43.0",
- "tinyglobby": "^0.2.15"
- },
- "bin": {
- "vite": "bin/vite.js"
- },
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- },
- "funding": {
- "url": "https://github.com/vitejs/vite?sponsor=1"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.3"
- },
- "peerDependencies": {
- "@types/node": "^20.19.0 || >=22.12.0",
- "jiti": ">=1.21.0",
- "less": "^4.0.0",
- "lightningcss": "^1.21.0",
- "sass": "^1.70.0",
- "sass-embedded": "^1.70.0",
- "stylus": ">=0.54.8",
- "sugarss": "^5.0.0",
- "terser": "^5.16.0",
- "tsx": "^4.8.1",
- "yaml": "^2.4.2"
- },
- "peerDependenciesMeta": {
- "@types/node": {
- "optional": true
- },
- "jiti": {
- "optional": true
- },
- "less": {
- "optional": true
- },
- "lightningcss": {
- "optional": true
- },
- "sass": {
- "optional": true
- },
- "sass-embedded": {
- "optional": true
- },
- "stylus": {
- "optional": true
- },
- "sugarss": {
- "optional": true
- },
- "terser": {
- "optional": true
- },
- "tsx": {
- "optional": true
- },
- "yaml": {
- "optional": true
- }
- }
- },
- "node_modules/vitefu": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz",
- "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==",
- "devOptional": true,
- "license": "MIT",
- "workspaces": [
- "tests/deps/*",
- "tests/projects/*",
- "tests/projects/workspace/packages/*"
- ],
- "peerDependencies": {
- "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
- },
- "peerDependenciesMeta": {
- "vite": {
- "optional": true
- }
- }
- },
- "node_modules/yup": {
- "version": "1.7.1",
- "resolved": "https://registry.npmjs.org/yup/-/yup-1.7.1.tgz",
- "integrity": "sha512-GKHFX2nXul2/4Dtfxhozv701jLQHdf6J34YDh2cEkpqoo8le5Mg6/LrdseVLrFarmFygZTlfIhHx/QKfb/QWXw==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "property-expr": "^2.0.5",
- "tiny-case": "^1.0.3",
- "toposort": "^2.0.2",
- "type-fest": "^2.19.0"
- }
- },
- "node_modules/zimmerframe": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz",
- "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==",
- "license": "MIT"
- },
- "node_modules/zod": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
- "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/colinhacks"
- }
- },
- "node_modules/zod-v3-to-json-schema": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/zod-v3-to-json-schema/-/zod-v3-to-json-schema-4.0.0.tgz",
- "integrity": "sha512-KixLrhX/uPmRFnDgsZrzrk4x5SSJA+PmaE5adbfID9+3KPJcdxqRobaHU397EfWBqfQircrjKqvEqZ/mW5QH6w==",
- "dev": true,
- "license": "ISC",
- "optional": true,
- "peerDependencies": {
- "zod": "^3.25 || ^4.0.14"
- }
- }
- }
-}
diff --git a/web-legacy-svelte/package.json b/web-legacy-svelte/package.json
deleted file mode 100644
index 2db0ef4..0000000
--- a/web-legacy-svelte/package.json
+++ /dev/null
@@ -1,45 +0,0 @@
-{
- "name": "web",
- "private": true,
- "version": "0.0.1",
- "type": "module",
- "scripts": {
- "dev": "vite dev",
- "build": "vite build",
- "preview": "vite preview",
- "prepare": "svelte-kit sync || echo ''",
- "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
- "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
- "lint": "prettier --check .",
- "format": "prettier --write ."
- },
- "devDependencies": {
- "@internationalized/date": "^3.12.0",
- "@lucide/svelte": "^1.16.0",
- "@sveltejs/adapter-static": "^3.0.10",
- "@sveltejs/kit": "^2.50.2",
- "@sveltejs/vite-plugin-svelte": "^6.2.4",
- "@tailwindcss/vite": "^4.1.18",
- "@types/node": "^22.15.0",
- "formsnap": "^2.0.1",
- "prettier": "^3.8.1",
- "prettier-plugin-svelte": "^3.4.1",
- "prettier-plugin-tailwindcss": "^0.7.2",
- "svelte": "^5.54.0",
- "svelte-check": "^4.4.2",
- "sveltekit-superforms": "^2.30.1",
- "tailwindcss": "^4.1.18",
- "typescript": "^5.9.3",
- "vite": "^7.3.1"
- },
- "dependencies": {
- "@tanstack/table-core": "^8.21.3",
- "bits-ui": "^2.17.2",
- "clsx": "^2.1.1",
- "svelte-sonner": "^1.1.0",
- "tailwind-merge": "^3.5.0",
- "tailwind-variants": "^3.2.2",
- "tw-animate-css": "^1.4.0",
- "zod": "^4.4.3"
- }
-}
diff --git a/web-legacy-svelte/src/app.d.ts b/web-legacy-svelte/src/app.d.ts
deleted file mode 100644
index da08e6d..0000000
--- a/web-legacy-svelte/src/app.d.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-// See https://svelte.dev/docs/kit/types#app.d.ts
-// for information about these interfaces
-declare global {
- namespace App {
- // interface Error {}
- // interface Locals {}
- // interface PageData {}
- // interface PageState {}
- // interface Platform {}
- }
-}
-
-export {};
diff --git a/web-legacy-svelte/src/app.html b/web-legacy-svelte/src/app.html
deleted file mode 100644
index 2571249..0000000
--- a/web-legacy-svelte/src/app.html
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
-
-
-
- %sveltekit.head%
-
-
- %sveltekit.body%
-
-
diff --git a/web-legacy-svelte/src/lib/api/client.ts b/web-legacy-svelte/src/lib/api/client.ts
deleted file mode 100644
index ab3bfaf..0000000
--- a/web-legacy-svelte/src/lib/api/client.ts
+++ /dev/null
@@ -1,185 +0,0 @@
-import { browser } from '$app/environment';
-import type {
- AsEntriesResponse,
- CdnSourcesResponse,
- DomainEntriesResponse,
- IpRangeEntriesResponse,
- JobRow,
- ModuleType,
- RevisionPrefix,
- RevisionPrefixesResponse
-} from './types.js';
-
-export const TOKEN_STORAGE_KEY = 'evobgp_api_token';
-
-export type Problem = {
- type?: string;
- title?: string;
- status?: number;
- detail?: string;
-};
-
-function getToken(): string | null {
- if (!browser) return null;
- return localStorage.getItem(TOKEN_STORAGE_KEY);
-}
-
-function mergeHeaders(init?: RequestInit, extraHeaders?: Record): Headers {
- const h = new Headers(init?.headers);
- if (!h.has('Accept')) h.set('Accept', 'application/json');
- const t = getToken();
- if (t && !h.has('Authorization')) h.set('Authorization', `Bearer ${t}`);
- if (extraHeaders) {
- for (const [k, v] of Object.entries(extraHeaders)) {
- if (!h.has(k)) h.set(k, v);
- }
- }
- return h;
-}
-
-/**
- * Idempotency keys: `crypto.randomUUID()` exists only in secure contexts (HTTPS / localhost).
- * Over plain HTTP to a LAN IP it is often undefined — use getRandomValues or a fallback.
- */
-function newIdempotencyKey(): string {
- const c = typeof globalThis !== 'undefined' ? globalThis.crypto : undefined;
- if (c?.randomUUID) return c.randomUUID();
- if (c?.getRandomValues) {
- const buf = new Uint8Array(16);
- c.getRandomValues(buf);
- buf[6] = (buf[6]! & 0x0f) | 0x40;
- buf[8] = (buf[8]! & 0x3f) | 0x80;
- const hex = [...buf].map((b) => b.toString(16).padStart(2, '0')).join('');
- return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
- }
- return `idem-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 14)}`;
-}
-
-export class ApiError extends Error {
- constructor(
- public readonly status: number,
- message: string,
- public readonly problem?: Problem
- ) {
- super(message);
- }
-}
-
-export async function apiFetch(path: string, init?: RequestInit): Promise {
- if (!browser) throw new Error('API is only available in the browser');
- return fetch(path, { ...init, headers: mergeHeaders(init) });
-}
-
-/** GET / DELETE без тела */
-export async function apiJSON(path: string, init?: RequestInit): Promise {
- const res = await apiFetch(path, init);
- return parseResponse(res);
-}
-
-/** POST / PATCH / PUT с JSON-телом и автоматическим Idempotency-Key */
-export async function apiMutate(
- path: string,
- method: 'POST' | 'PATCH' | 'PUT' | 'DELETE',
- body?: unknown,
- opts?: { idempotent?: boolean }
-): Promise {
- const headers: Record = {};
- if (body !== undefined) headers['Content-Type'] = 'application/json';
- if (opts?.idempotent !== false) {
- headers['Idempotency-Key'] = newIdempotencyKey();
- }
- const res = await fetch(path, {
- method,
- headers: mergeHeaders({ headers }, headers),
- body: body !== undefined ? JSON.stringify(body) : undefined
- });
- return parseResponse(res);
-}
-
-async function parseResponse(res: Response): Promise {
- if (res.status === 204 || res.status === 205) return undefined as T;
- const text = await res.text();
- if (!res.ok) {
- let problem: Problem | undefined;
- let detail = `HTTP ${res.status}`;
- try {
- problem = JSON.parse(text) as Problem;
- detail = problem.detail ?? problem.title ?? detail;
- } catch {
- if (text) detail = text;
- }
- throw new ApiError(res.status, detail, problem);
- }
- if (!text) return undefined as T;
- return JSON.parse(text) as T;
-}
-
-const terminalJobStatuses = new Set(['succeeded', 'failed', 'cancelled']);
-
-/** Ожидает завершения фоновой задачи (poll GET /v1/jobs/{id}). */
-export async function waitForJob(
- jobId: string,
- opts?: { pollMs?: number; timeoutMs?: number }
-): Promise {
- const pollMs = opts?.pollMs ?? 400;
- const timeoutMs = opts?.timeoutMs ?? 120000;
- const deadline = Date.now() + timeoutMs;
- while (Date.now() < deadline) {
- const j = await apiJSON(`/v1/jobs/${jobId}`);
- if (terminalJobStatuses.has(j.status)) return j;
- await new Promise((r) => setTimeout(r, pollMs));
- }
- throw new Error(`Таймаут ожидания задачи ${jobId}`);
-}
-
-export async function apiPageAll(path: string, limit = 500): Promise {
- const items: T[] = [];
- let cursor: string | null = null;
- while (true) {
- const [basePath, rawQuery = ''] = path.split('?');
- const query = new URLSearchParams(rawQuery);
- if (!query.has('limit')) query.set('limit', String(limit));
- if (cursor) query.set('cursor', cursor);
- else query.delete('cursor');
- const page = await apiJSON<{ items?: T[]; next_cursor?: string | null; has_more?: boolean }>(
- `${basePath}?${query.toString()}`
- );
- items.push(...(page.items ?? []));
- if (!page.has_more || !page.next_cursor) break;
- cursor = page.next_cursor;
- }
- return items;
-}
-
-export async function fetchRevisionPrefixesAll(revisionId: string): Promise {
- const items = await apiPageAll(`/v1/revisions/${revisionId}/prefixes`);
- return items;
-}
-
-export async function fetchModuleSourceCatalog(moduleId: string, moduleType: ModuleType) {
- if (moduleType === 'DOMAINS') {
- const entries = await apiPageAll(
- `/v1/modules/${moduleId}/domain-entries`
- );
- return { domains: entries };
- }
- if (moduleType === 'AS_PREFIXES') {
- const entries = await apiPageAll(
- `/v1/modules/${moduleId}/as-entries`
- );
- return { asns: entries };
- }
- if (moduleType === 'CDN_CIDRS') {
- const entries = await apiPageAll(
- `/v1/modules/${moduleId}/cdn-sources`
- );
- return { cdnSources: entries };
- }
- if (moduleType === 'IP_RANGES') {
- const entries = await apiPageAll(
- `/v1/modules/${moduleId}/ip-range-entries`
- );
- return { ipRanges: entries };
- }
- return {};
-}
diff --git a/web-legacy-svelte/src/lib/api/types.ts b/web-legacy-svelte/src/lib/api/types.ts
deleted file mode 100644
index a871031..0000000
--- a/web-legacy-svelte/src/lib/api/types.ts
+++ /dev/null
@@ -1,340 +0,0 @@
-// ---- Pagination ----
-export type Page = {
- items: T[];
- next_cursor: string | null;
- has_more: boolean;
-};
-
-// ---- Modules ----
-export type ModuleType = 'AS_PREFIXES' | 'CDN_CIDRS' | 'DOMAINS' | 'IP_RANGES';
-
-export type DohResolverPolicy = 'primary_only' | 'failover' | 'union';
-
-export type ModuleRow = {
- id: string;
- type: ModuleType;
- name: string;
- enabled: boolean;
- priority: number;
- refresh_interval_sec: number | null;
- cron_expr: string | null;
- default_community_id: string | null;
- /** @deprecated use doh_profile_ids */
- doh_profile_id: string | null;
- doh_profile_ids: string[];
- doh_resolver_policy: DohResolverPolicy;
- last_refreshed_at: string | null;
-};
-export type ModulesResponse = Page;
-
-export type RouterListsCatalogResponse = {
- modules: { items: ModuleRow[] };
- domains: { items: { module_id: string; entry: DomainEntry }[] };
- asns: { items: { module_id: string; entry: AsEntry }[] };
- ip_ranges: { items: { module_id: string; entry: IpRangeEntry }[] };
- communities: { items: BgpCommunity[] };
-};
-
-export type ModuleCreate = {
- type: ModuleType;
- name: string;
- enabled?: boolean;
- priority?: number;
- doh_profile_id?: string | null;
- doh_profile_ids?: string[];
- doh_resolver_policy?: DohResolverPolicy;
- refresh_interval_sec?: number | null;
- cron_expr?: string | null;
- default_community_id?: string | null;
-};
-export type ModulePatch = Partial>;
-
-// ---- AS Entries ----
-export type AsEntry = {
- id: string;
- asn: number;
- community_id: string | null;
- /** Имя/держатель AS (RIPEstat), после успешного обновления модуля */
- asn_name?: string | null;
- /** Число объявленных префиксов на момент последнего резолва */
- prefix_count?: number | null;
- /** ISO-время последнего успешного резолва ASN */
- asn_resolved_at?: string | null;
-};
-export type AsEntryCreate = {
- asn: number;
- community_id?: string | null;
-};
-export type AsEntryPatch = {
- asn?: number;
- community_id?: string | null;
-};
-export type AsEntriesResponse = Page;
-
-// ---- CDN Sources ----
-export type CdnSource = {
- id: string;
- url: string;
- source_kind: string;
- prefix_path: string;
- community_id: string | null;
- refresh_interval_sec: number | null;
- last_refreshed_at: string | null;
-};
-export type CdnSourceCreate = {
- url: string;
- source_kind: string;
- prefix_path?: string;
- community_id?: string | null;
-};
-export type CdnSourcePatch = Partial & { refresh_interval_sec?: number | null };
-export type CdnSourcesResponse = Page;
-
-export type CdnPreviewResponse = {
- items: string[];
- total: number;
- truncated: boolean;
- source_url: string;
-};
-
-// ---- Domain Entries ----
-export type DomainEntry = {
- id: string;
- fqdn: string;
- community_id: string | null;
-};
-export type DomainEntryCreate = {
- fqdn: string;
- community_id?: string | null;
-};
-export type DomainEntriesResponse = Page;
-
-// ---- IP Range Entries ----
-export type IpRangeEntry = {
- id: string;
- prefix: string;
- community_id: string;
-};
-export type IpRangeEntryCreate = {
- prefix: string;
- community_id: string;
-};
-export type IpRangeEntriesResponse = Page;
-
-// ---- DoH Profiles ----
-export type DohProfile = {
- id: string;
- name?: string;
- url: string;
- timeout_ms: number | null;
- vault_secret_ref: string | null;
-};
-export type DohProfileCreate = {
- name?: string;
- url: string;
- timeout_ms?: number | null;
- vault_secret_ref?: string | null;
-};
-export type DohProfilePatch = Partial;
-export type DohProfilesResponse = Page;
-
-// ---- Communities ----
-export type BgpCommunity = {
- id: string;
- community: string;
- title: string;
-};
-export type BgpCommunityCreate = {
- community: string;
- title?: string;
-};
-export type BgpCommunityPatch = Partial;
-export type CommunitiesResponse = Page;
-
-// ---- Peers ----
-export type PeerSessionOnSpeaker = {
- speaker_id: string;
- label: string;
- state: string;
- poll_error?: string;
-};
-
-export type PeerRow = {
- id: string;
- name?: string;
- neighbor: string;
- remote_asn?: number;
- enabled?: boolean;
- session_state: string;
- bgp_speaker_id: string | null;
- connected_speaker_id?: string | null;
- connected_speaker_label?: string;
- session_on_speakers?: PeerSessionOnSpeaker[];
- established_on_speakers?: PeerSessionOnSpeaker[];
- session_mismatch?: boolean;
-};
-export type LiveSpeakerPoll = {
- speaker_id: string;
- label: string;
- ok: boolean;
- session_count: number;
- poll_error?: string;
-};
-export type PeersResponse = Page & { live_speaker_poll?: LiveSpeakerPoll[] };
-export type BgpPeerCreate = {
- name?: string;
- neighbor: string;
- remote_asn: number;
- bgp_speaker_id?: string | null;
- enabled?: boolean;
-};
-export type BgpPeerPatch = Partial;
-
-// ---- Speakers ----
-export type BgpSessionLive = {
- name: string;
- neighbor?: string;
- state: string;
-};
-
-export type SpeakerLiveStatus = {
- label?: string;
- agent_ok?: boolean;
- agent_error?: string;
- agent_last_sync_at?: string;
- agent_last_applied_revision_id?: string;
- bgp_poll_ok?: boolean;
- bgp_poll_error?: string;
- bgp_sessions_total?: number;
- bgp_established?: number;
- sessions?: BgpSessionLive[];
-};
-
-export type SpeakerRow = {
- id: string;
- role: string;
- endpoint: string;
- last_applied_revision_id: string | null;
- published_revision_id?: string | null;
- published_at?: string | null;
- agent_domain?: string;
- node_ipv4?: string;
- bird_bgp_source_ipv4?: string;
- dispatch_status?: string;
- sync_status?: string;
- last_dispatch_at?: string | null;
- last_dispatch_error?: string | null;
- meta_json?: Record;
- agent_secret?: string;
- live?: SpeakerLiveStatus;
-};
-export type SpeakersResponse = Page;
-export type BgpSpeakerCreate = {
- endpoint: string;
- role?: string;
- meta_json?: string;
-};
-export type BgpSpeakerPatch = Partial;
-
-export type BundleSigningPublicKey = {
- public_key_base64: string;
-};
-
-// ---- Revisions ----
-export type RevisionRow = {
- id: string;
- content_hash: string;
- created_at: string;
- parent_revision_id: string | null;
- materialized_prefix_count: number;
- module_id: string | null;
-};
-export type RevisionsResponse = Page;
-
-export type RevisionPrefix = {
- /** Обычно CIDR; для AS-модуля в снимке ревизии — строка вида `as:<номер_asn>`. */
- prefix: string;
- /** Источник материализации (например, domain:, as:, cdn:, ip_range). */
- source?: string;
- community_id?: string | null;
-};
-export type RevisionPrefixesResponse = Page;
-
-export type RevisionPreview = {
- id: string;
- prefixes?: RevisionPrefix[];
- [key: string]: unknown;
-};
-
-/** Ответ GET /v1/revisions/{a}/diff/{b}: префиксы в `prefixes` (источник правды в бэкенде). */
-export type RevisionDiff = {
- revision_a?: string;
- revision_b?: string;
- prefixes?: {
- added: string[];
- removed: string[];
- unchanged_count?: number;
- };
- /** Устаревший/нормализованный вид — см. нормализацию в UI */
- added?: (string | RevisionPrefix)[];
- removed?: (string | RevisionPrefix)[];
- [key: string]: unknown;
-};
-
-// ---- BIRD (локальный birdc на хосте с API, если задан EVOBGP_BIRDC_SOCKET) ----
-export type BirdStatus = {
- birdc_configured: boolean;
- message?: string;
- error?: string;
- protocols_excerpt?: string;
- bgp_sessions_total: number;
- bgp_established: number;
- healthy: boolean | null;
-};
-
-// ---- Jobs ----
-export type JobRow = {
- job_id: string;
- kind: string;
- status: string;
- idempotency_key?: string | null;
- created_at?: string;
- started_at?: string | null;
- finished_at?: string | null;
- error?: string | null;
- meta?: Record;
-};
-export type JobsResponse = Page;
-
-// ---- Settings ----
-export type AppSettings = Record;
-
-// ---- Auth / API keys ----
-export type AuthSession = {
- tenant_id: string;
- role: 'viewer' | 'editor' | 'operator' | 'node';
-};
-
-export type ApiKeyRole = AuthSession['role'];
-
-export type ApiKey = {
- id: string;
- name: string;
- role: ApiKeyRole;
- prefix: string;
- created_at: string;
- updated_at: string;
- expires_at: string | null;
- revoked_at: string | null;
- last_used_at: string | null;
-};
-
-export type ApiKeysResponse = Page;
-
-export type ApiKeyCreate = {
- name: string;
- role: ApiKeyRole;
- expires_at?: string | null;
-};
-
-export type ApiKeyCreated = ApiKey & { token: string };
diff --git a/web-legacy-svelte/src/lib/api/version-info.ts b/web-legacy-svelte/src/lib/api/version-info.ts
deleted file mode 100644
index 58955a8..0000000
--- a/web-legacy-svelte/src/lib/api/version-info.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-/** Ответ GET /v1/version (см. docs/openapi.yaml VersionInfo). */
-export type VersionInfo = {
- version?: string;
- api_version?: string;
- git_sha?: string;
- build_time?: string;
-};
-
-const PLACEHOLDER_SHA = new Set(['', 'unknown']);
-
-function nonEmptyString(value: unknown): string {
- if (typeof value === 'string') {
- const s = value.trim();
- return s;
- }
- if (value == null) return '';
- return String(value).trim();
-}
-
-/** Semver или legacy api_version; пустая строка, если полей нет. */
-export function resolveVersionString(info: VersionInfo | null | undefined): string {
- if (!info) return '';
- return nonEmptyString(info.version) || nonEmptyString(info.api_version);
-}
-
-/** Короткий git SHA; пусто для placeholder «unknown». */
-export function resolveGitSha(info: VersionInfo | null | undefined): string {
- if (!info) return '';
- const sha = nonEmptyString(info.git_sha);
- if (!sha || PLACEHOLDER_SHA.has(sha.toLowerCase())) return '';
- return sha;
-}
-
-/** Заголовок карточки «Версия» на /monitoring. */
-export function formatVersionHeadline(info: VersionInfo | null | undefined): string {
- if (!info) return '—';
- const ver = resolveVersionString(info);
- const sha = resolveGitSha(info);
- if (ver && sha) return `${ver} (${sha.slice(0, 12)})`;
- if (ver) return ver;
- if (sha) return sha.slice(0, 12);
- return '—';
-}
-
-/** Подпись в сайдбаре (v1.2.3). */
-export function formatVersionSidebarLabel(info: VersionInfo | null | undefined): string | null {
- const ver = resolveVersionString(info);
- return ver ? `v${ver}` : null;
-}
diff --git a/web-legacy-svelte/src/lib/assets/favicon.svg b/web-legacy-svelte/src/lib/assets/favicon.svg
deleted file mode 100644
index cc5dc66..0000000
--- a/web-legacy-svelte/src/lib/assets/favicon.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/web-legacy-svelte/src/lib/components/access/AccessApiKeysCard.svelte b/web-legacy-svelte/src/lib/components/access/AccessApiKeysCard.svelte
deleted file mode 100644
index b1d594a..0000000
--- a/web-legacy-svelte/src/lib/components/access/AccessApiKeysCard.svelte
+++ /dev/null
@@ -1,279 +0,0 @@
-
-
-
-
-
- API-ключи
-
- Управление ключами tenant. Полный токен показывается только при создании и ротации.
-
-
-
-
-
-
-
-
- k.id}
- loading={initialLoading || loading}
- {error}
- emptyTitle="Нет ключей"
- emptyDescription="Создайте API-ключ для автоматизации или отдельного доступа."
- >
- {#snippet cell({ row: k, column })}
- {#if column.id === 'name'}
- {k.name}
- {:else if column.id === 'role'}
- {k.role}
- {:else if column.id === 'prefix'}
- {k.prefix}…
- {:else if column.id === 'revoked'}
- {#if k.revoked_at}
- отозван
- {:else}
- активен
- {/if}
- {:else if column.id === 'actions'}
-
-
-
-
- {/if}
- {/snippet}
-
-
-
-
-
-
-
diff --git a/web-legacy-svelte/src/lib/components/app/scroll-pre-block.svelte b/web-legacy-svelte/src/lib/components/app/scroll-pre-block.svelte
deleted file mode 100644
index 8df9161..0000000
--- a/web-legacy-svelte/src/lib/components/app/scroll-pre-block.svelte
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
diff --git a/web-legacy-svelte/src/lib/components/directories/DirectoriesCommunitiesCard.svelte b/web-legacy-svelte/src/lib/components/directories/DirectoriesCommunitiesCard.svelte
deleted file mode 100644
index 330c53f..0000000
--- a/web-legacy-svelte/src/lib/components/directories/DirectoriesCommunitiesCard.svelte
+++ /dev/null
@@ -1,188 +0,0 @@
-
-
-
-
-
- Сообщества BGP
- Используются для тегирования префиксов в AS- и CDN-модулях
-
-
-
-
- c.id}
- loading={initialLoading || loading}
- {error}
- emptyTitle="Нет сообществ BGP"
- emptyDescription="Создайте первое сообщество для тегирования префиксов."
- >
- {#snippet cell({ row: c, column })}
- {#if column.id === 'community'}
- {c.community}
- {:else if column.id === 'title'}
- {c.title?.trim() || '—'}
- {:else if column.id === 'id'}
- {c.id}
- {:else if column.id === 'actions'}
-
-
-
-
- {/if}
- {/snippet}
-
-
-
-
-
diff --git a/web-legacy-svelte/src/lib/components/directories/DirectoriesDohProfilesCard.svelte b/web-legacy-svelte/src/lib/components/directories/DirectoriesDohProfilesCard.svelte
deleted file mode 100644
index 380e2d0..0000000
--- a/web-legacy-svelte/src/lib/components/directories/DirectoriesDohProfilesCard.svelte
+++ /dev/null
@@ -1,178 +0,0 @@
-
-
-
-
-
- DoH профили
- DNS-over-HTTPS серверы для резолвинга доменных модулей
-
-
-
-
- d.id}
- loading={initialLoading || loading}
- {error}
- emptyTitle="Нет DoH профилей"
- emptyDescription="Добавьте DNS-over-HTTPS сервер для доменных модулей."
- >
- {#snippet cell({ row: d, column })}
- {#if column.id === 'url'}
- {d.url}
- {:else if column.id === 'timeout_ms'}
- {d.timeout_ms ?? '—'}
- {:else if column.id === 'id'}
- {d.id}
- {:else if column.id === 'actions'}
-
-
-
-
- {/if}
- {/snippet}
-
-
-
-
-
diff --git a/web-legacy-svelte/src/lib/components/modules/ModuleAsEntriesCard.svelte b/web-legacy-svelte/src/lib/components/modules/ModuleAsEntriesCard.svelte
deleted file mode 100644
index 8f236e9..0000000
--- a/web-legacy-svelte/src/lib/components/modules/ModuleAsEntriesCard.svelte
+++ /dev/null
@@ -1,342 +0,0 @@
-
-
-
-
-
-
-
- AS-записи
-
- Номер AS и community; имя, число префиксов и дата обновляются при успешном refresh
- (RIPEstat).
-
-
-
-
-
-
- {#if selectedCount > 0}
-
- {/if}
-
-
-
- e.id}
- {loading}
- emptyTitle="Нет AS-записей"
- emptyDescription="Добавьте ASN или импортируйте CSV."
- >
- {#snippet toolbar()}
- {#if entries.length > 0}
-
- toggleAll(v === true)}
- aria-label="Выбрать все AS-записи"
- />
- Выбрать все
-
- {/if}
- {/snippet}
- {#snippet cell({ row: entry, column })}
- {#if column.id === 'select'}
- toggleSelection(entry.id)}
- />
- {:else if column.id === 'asn'}
- {entry.asn}
- {:else if column.id === 'name'}
-
- {entry.asn_name?.trim() ? entry.asn_name : '—'}
-
- {:else if column.id === 'prefixes'}
-
- {entry.prefix_count != null ? entry.prefix_count : '—'}
-
- {:else if column.id === 'updated'}
-
- {formatDateTime(entry.asn_resolved_at)}
-
- {:else if column.id === 'community'}
-
- {communityLabel(entry.community_id, communities)}
-
- {:else if column.id === 'actions'}
-
-
-
-
- {/if}
- {/snippet}
-
-
-
-
- {
- editTarget = null;
- }}
-/>
diff --git a/web-legacy-svelte/src/lib/components/modules/ModuleAsEntryDialog.svelte b/web-legacy-svelte/src/lib/components/modules/ModuleAsEntryDialog.svelte
deleted file mode 100644
index 5b1e55a..0000000
--- a/web-legacy-svelte/src/lib/components/modules/ModuleAsEntryDialog.svelte
+++ /dev/null
@@ -1,137 +0,0 @@
-
-
-
diff --git a/web-legacy-svelte/src/lib/components/modules/ModuleCdnSourceDialog.svelte b/web-legacy-svelte/src/lib/components/modules/ModuleCdnSourceDialog.svelte
deleted file mode 100644
index bc4011f..0000000
--- a/web-legacy-svelte/src/lib/components/modules/ModuleCdnSourceDialog.svelte
+++ /dev/null
@@ -1,270 +0,0 @@
-
-
-
diff --git a/web-legacy-svelte/src/lib/components/modules/ModuleCdnSourcesCard.svelte b/web-legacy-svelte/src/lib/components/modules/ModuleCdnSourcesCard.svelte
deleted file mode 100644
index 5664935..0000000
--- a/web-legacy-svelte/src/lib/components/modules/ModuleCdnSourcesCard.svelte
+++ /dev/null
@@ -1,237 +0,0 @@
-
-
-
-
-
- CDN-источники
- URL источников для скачивания списков CIDR.
-
-
-
- {#if selectedCount > 0}
-
- {/if}
-
-
-
- s.id}
- {loading}
- emptyTitle="Нет CDN-источников"
- emptyDescription="Добавьте URL для загрузки списков CIDR."
- >
- {#snippet toolbar()}
- {#if sources.length > 0}
-
- toggleAll(v === true)}
- aria-label="Выбрать все CDN-источники"
- />
- Выбрать все
-
- {/if}
- {/snippet}
- {#snippet cell({ row: src, column })}
- {#if column.id === 'select'}
- toggleSelection(src.id)}
- />
- {:else if column.id === 'url'}
- {src.url}
- {:else if column.id === 'kind'}
-
- {normalizeCdnSourceKind(src.source_kind)}
- {#if src.prefix_path?.trim()}
- {src.prefix_path}
- {/if}
-
- {:else if column.id === 'community'}
-
- {communityLabel(src.community_id, communities)}
-
- {:else if column.id === 'interval'}
-
- {src.refresh_interval_sec != null ? `${src.refresh_interval_sec}с` : '—'}
-
- {:else if column.id === 'refreshed'}
-
- {formatDateTime(src.last_refreshed_at)}
-
- {:else if column.id === 'actions'}
-
-
-
-
- {/if}
- {/snippet}
-
-
-
-
- {
- editTarget = null;
- }}
-/>
diff --git a/web-legacy-svelte/src/lib/components/modules/ModuleCreateDialog.svelte b/web-legacy-svelte/src/lib/components/modules/ModuleCreateDialog.svelte
deleted file mode 100644
index 5d97705..0000000
--- a/web-legacy-svelte/src/lib/components/modules/ModuleCreateDialog.svelte
+++ /dev/null
@@ -1,144 +0,0 @@
-
-
-
diff --git a/web-legacy-svelte/src/lib/components/modules/ModuleDetailHeader.svelte b/web-legacy-svelte/src/lib/components/modules/ModuleDetailHeader.svelte
deleted file mode 100644
index 29f8c0f..0000000
--- a/web-legacy-svelte/src/lib/components/modules/ModuleDetailHeader.svelte
+++ /dev/null
@@ -1,57 +0,0 @@
-
-
-
-
-
- {#snippet actions()}
-
-
{moduleTypeRu(mod.type)}
-
- {moduleEnabledRu(!!mod.enabled)}
-
-
-
-
-
- {/snippet}
-
-
diff --git a/web-legacy-svelte/src/lib/components/modules/ModuleDomainEntriesCard.svelte b/web-legacy-svelte/src/lib/components/modules/ModuleDomainEntriesCard.svelte
deleted file mode 100644
index f8b7853..0000000
--- a/web-legacy-svelte/src/lib/components/modules/ModuleDomainEntriesCard.svelte
+++ /dev/null
@@ -1,302 +0,0 @@
-
-
-
-
-
-
-
- Домены
- FQDN для резолвинга через DoH.
-
-
-
-
-
- {#if selectedCount > 0}
-
- {/if}
-
-
-
- e.id}
- {loading}
- emptyTitle="Нет доменов"
- emptyDescription="Добавьте FQDN или импортируйте CSV."
- >
- {#snippet toolbar()}
- {#if entries.length > 0}
-
- toggleAll(v === true)}
- aria-label="Выбрать все домены"
- />
- Выбрать все
-
- {/if}
- {/snippet}
- {#snippet cell({ row: entry, column })}
- {#if column.id === 'select'}
- toggleSelection(entry.id)}
- />
- {:else if column.id === 'fqdn'}
- {entry.fqdn}
- {:else if column.id === 'community'}
-
- {communityLabel(entry.community_id, communities)}
-
- {:else if column.id === 'actions'}
-
-
-
-
- {/if}
- {/snippet}
-
-
-
-
- {
- editTarget = null;
- }}
-/>
diff --git a/web-legacy-svelte/src/lib/components/modules/ModuleDomainEntryDialog.svelte b/web-legacy-svelte/src/lib/components/modules/ModuleDomainEntryDialog.svelte
deleted file mode 100644
index 8b7ad28..0000000
--- a/web-legacy-svelte/src/lib/components/modules/ModuleDomainEntryDialog.svelte
+++ /dev/null
@@ -1,120 +0,0 @@
-
-
-
diff --git a/web-legacy-svelte/src/lib/components/modules/ModuleEditDialog.svelte b/web-legacy-svelte/src/lib/components/modules/ModuleEditDialog.svelte
deleted file mode 100644
index 44a62a5..0000000
--- a/web-legacy-svelte/src/lib/components/modules/ModuleEditDialog.svelte
+++ /dev/null
@@ -1,294 +0,0 @@
-
-
-
diff --git a/web-legacy-svelte/src/lib/components/modules/ModuleIpRangeEntryDialog.svelte b/web-legacy-svelte/src/lib/components/modules/ModuleIpRangeEntryDialog.svelte
deleted file mode 100644
index bf54d79..0000000
--- a/web-legacy-svelte/src/lib/components/modules/ModuleIpRangeEntryDialog.svelte
+++ /dev/null
@@ -1,115 +0,0 @@
-
-
-
diff --git a/web-legacy-svelte/src/lib/components/modules/ModuleIpRangesCard.svelte b/web-legacy-svelte/src/lib/components/modules/ModuleIpRangesCard.svelte
deleted file mode 100644
index 3e9ac94..0000000
--- a/web-legacy-svelte/src/lib/components/modules/ModuleIpRangesCard.svelte
+++ /dev/null
@@ -1,310 +0,0 @@
-
-
-
-
-
-
-
- IP-диапазоны
- Статические CIDR для анонса.
-
-
-
-
-
- {#if selectedCount > 0}
-
- {/if}
-
-
-
- e.id}
- {loading}
- emptyTitle="Нет диапазонов"
- emptyDescription="Добавьте CIDR или импортируйте CSV."
- >
- {#snippet toolbar()}
- {#if entries.length > 0}
-
- toggleAll(v === true)}
- aria-label="Выбрать все диапазоны"
- />
- Выбрать все
-
- {/if}
- {/snippet}
- {#snippet cell({ row: entry, column })}
- {#if column.id === 'select'}
- toggleSelection(entry.id)}
- />
- {:else if column.id === 'prefix'}
- {entry.prefix}
- {:else if column.id === 'community'}
-
- {communityLabel(entry.community_id, communities)}
-
- {:else if column.id === 'actions'}
-
-
-
-
- {/if}
- {/snippet}
-
-
-
-
- {
- editTarget = null;
- }}
-/>
diff --git a/web-legacy-svelte/src/lib/components/modules/ModuleKpiCards.svelte b/web-legacy-svelte/src/lib/components/modules/ModuleKpiCards.svelte
deleted file mode 100644
index e3e3a66..0000000
--- a/web-legacy-svelte/src/lib/components/modules/ModuleKpiCards.svelte
+++ /dev/null
@@ -1,155 +0,0 @@
-
-
-
- {#if loading}
- {#each Array(5) as _, i (i)}
-
- {/each}
- {:else}
- {#each kpiCards as card (card.id)}
- {@const Icon = card.icon}
- {@const a = card.accent}
-
-
-
-
-
-
- {card.label}
-
-
- {card.value}
-
-
-
- {card.description}
-
-
- {/each}
- {/if}
-
diff --git a/web-legacy-svelte/src/lib/components/modules/ModuleSummaryCard.svelte b/web-legacy-svelte/src/lib/components/modules/ModuleSummaryCard.svelte
deleted file mode 100644
index a0b920e..0000000
--- a/web-legacy-svelte/src/lib/components/modules/ModuleSummaryCard.svelte
+++ /dev/null
@@ -1,115 +0,0 @@
-
-
-
-
- Операционный отчёт модуля
-
- Читаемая сводка по данным модуля: источники, объёмы и ожидаемый результат для
- refresh/агрегации.
-
-
-
- {#if mod.type === 'DOMAINS'}
-
-
Домены и ожидаемые IP
-
- После refresh домены резолвятся в IP и конвертируются в префиксы.
-
-
- {#each domainEntries.slice(0, 8) as entry (entry.id)}
-
{entry.fqdn}
- {:else}
-
Нет доменов
- {/each}
- {#if domainEntries.length > 8}
-
…и ещё {domainEntries.length - 8}
- {/if}
-
-
- {:else if mod.type === 'AS_PREFIXES'}
-
-
ASN и число полученных префиксов
-
- Счётчик префиксов обновляется после успешного refresh (RIPEstat).
-
-
-
- Всего ASN: {asEntries.length}
-
-
- Сумма префиксов: {asPrefixTotal}
-
-
-
- {:else if mod.type === 'CDN_CIDRS'}
-
-
CDN ссылки и импортируемые префиксы
-
- Каждый URL поставляет список CIDR для агрегации.
-
-
- {#each cdnSources.slice(0, 6) as src (src.id)}
-
{src.url}
- {:else}
-
Нет CDN источников
- {/each}
- {#if cdnSources.length > 6}
-
…и ещё {cdnSources.length - 6}
- {/if}
-
-
- {:else if mod.type === 'IP_RANGES'}
-
-
IP ranges для агрегации
-
- Статические CIDR, которые попадают в итоговую ревизию.
-
-
- {#each ipEntries.slice(0, 8) as entry (entry.id)}
-
{entry.prefix}
- {:else}
-
Нет диапазонов
- {/each}
- {#if ipEntries.length > 8}
-
…и ещё {ipEntries.length - 8}
- {/if}
-
-
- {/if}
-
-
Результат операции
-
- Подробный результат по конкретному запуску refresh смотрите в Операции → Задачи →
- module_refresh: там отображаются источники, количество префиксов и итог агрегации.
-
-
-
-
-
diff --git a/web-legacy-svelte/src/lib/components/modules/module-helpers.ts b/web-legacy-svelte/src/lib/components/modules/module-helpers.ts
deleted file mode 100644
index f8288f6..0000000
--- a/web-legacy-svelte/src/lib/components/modules/module-helpers.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import type { BgpCommunity, DohProfile, ModuleRow } from '$lib/api/types.js';
-
-export const NONE_OPTION = '__none__';
-
-export function supportsCsvIO(type: ModuleRow['type'] | null | undefined): boolean {
- return type === 'AS_PREFIXES' || type === 'DOMAINS' || type === 'IP_RANGES';
-}
-
-export function sanitizeFilenamePart(v: string): string {
- const cleaned = v
- .trim()
- .toLowerCase()
- .replace(/[^a-z0-9._-]+/g, '-')
- .replace(/-+/g, '-')
- .replace(/^[-_.]+|[-_.]+$/g, '');
- return cleaned || 'module';
-}
-
-export function communityLabel(id: string | null, communities: BgpCommunity[]): string {
- if (!id) return '—';
- const c = communities.find((x) => x.id === id);
- if (!c) return id.slice(0, 8) + '…';
- const t = c.title?.trim();
- return t || c.community;
-}
-
-export function communityOptionLabel(c: BgpCommunity): string {
- const t = c.title?.trim();
- return t || c.community;
-}
-
-export function nullableSelectValue(value: string | null | undefined): string {
- if (value === null || value === undefined || value === '') return NONE_OPTION;
- return value;
-}
-
-export function fromNullableSelect(value: string): string | null {
- if (value === NONE_OPTION || value === '') return null;
- return value;
-}
-
-export function moduleDohProfileIds(modRow: ModuleRow | null): string[] {
- if (!modRow) return [];
- if (modRow.doh_profile_ids?.length) return modRow.doh_profile_ids;
- return modRow.doh_profile_id ? [modRow.doh_profile_id] : [];
-}
-
-export function dohProfileLabel(id: string, dohProfiles: DohProfile[]): string {
- const p = dohProfiles.find((d) => d.id === id);
- return p ? (p.name?.trim() ? `${p.name} (${p.url})` : p.url) : id.slice(0, 8) + '…';
-}
-
-export function normalizeCdnSourceKind(k: string): 'plaintext' | 'json' {
- return k.trim().toLowerCase() === 'json' ? 'json' : 'plaintext';
-}
-
-export function syncSelection(selected: Set, existingIds: string[]): Set {
- const validIds = new Set(existingIds);
- return new Set([...selected].filter((id) => validIds.has(id)));
-}
diff --git a/web-legacy-svelte/src/lib/components/monitoring/MaintenancePoliciesTab.svelte b/web-legacy-svelte/src/lib/components/monitoring/MaintenancePoliciesTab.svelte
deleted file mode 100644
index 22e0d4e..0000000
--- a/web-legacy-svelte/src/lib/components/monitoring/MaintenancePoliciesTab.svelte
+++ /dev/null
@@ -1,687 +0,0 @@
-
-
-{#if !isOperator}
-
- Только operator
- Политики обслуживания БД настраиваются с ролью operator.
-
-{/if}
-
-
-
-
- Политики обслуживания
-
- Единственный источник конфигурации retention, vacuum и расписания (UTC cron).
-
-
- {#if isOperator}
-
- {/if}
-
-
- {#if isOperator}
-
-
-
-
-
- Пресеты стратегий
-
-
- Выберите шаблоны и создайте политики одним действием или примените шаблон в форме.
-
-
-
-
-
- {#each maintenancePolicyPresets as preset (preset.id)}
- {@const applied = isPresetAlreadyApplied(preset, policies)}
- {@const checked = selectedPresetIds.includes(preset.id)}
-
- {/each}
-
-
- {/if}
-
- p.id}
- {loading}
- emptyTitle="Политики не созданы"
- emptyDescription="Добавьте первую политику через UI — это единственный способ настройки."
- >
- {#snippet cell({ row, column })}
- {#if column.id === 'status'}
- {statusBadge(row)}
- {#if row.last_run_at}
- {row.last_run_at}
- {/if}
- {:else if column.id === 'actions' && isOperator}
-
-
-
-
-
-
- {:else if column.id === 'name'}
- {row.name}
- {:else if column.id === 'table_name'}
- {row.table_name}
- {:else if column.id === 'schedule'}
-
- {describeCron(row.schedule)}
- {row.schedule}
-
- {:else if column.id !== 'actions'}
- —
- {/if}
- {/snippet}
-
-
-
-
-
diff --git a/web-legacy-svelte/src/lib/components/monitoring/MonitoringPostgresTab.svelte b/web-legacy-svelte/src/lib/components/monitoring/MonitoringPostgresTab.svelte
deleted file mode 100644
index 14d9941..0000000
--- a/web-legacy-svelte/src/lib/components/monitoring/MonitoringPostgresTab.svelte
+++ /dev/null
@@ -1,436 +0,0 @@
-
-
-
-
-
- Instance-level PostgreSQL (control plane)
-
-
-
-
-
-
-
-
-
-
-{#if unavailable}
-
- PostgreSQL недоступен
-
- Мониторинг требует EVOBGP_DATABASE_URL (не memory backend).
-
-
-{:else}
-
-
- Обзор
- Запросы
- Блокировки
- Таблицы
- Обслуживание
- Корреляция
-
-
-
- {#if overview}
-
-
-
- Подключения
-
-
-
- {overview.connections.active} / {overview.connections.max_connections}
-
-
-
- idle {overview.connections.idle}, total {overview.connections.total}
-
-
-
-
-
- Cache hit
-
-
-
- {overview.database.cache_hit_pct ?? '—'}%
-
-
-
-
-
-
- TPS (commits)
-
-
-
- {overview.database.xact_commit.toLocaleString()}
-
-
- rollback {overview.database.xact_rollback.toLocaleString()}, deadlocks {overview
- .database.deadlocks}
-
-
-
-
-
- Размер БД
-
-
- {formatBytes(overview.database_size_bytes)}
-
- shared_buffers {overview.memory_settings.shared_buffers}
-
-
-
-
- {#if overview.replication?.length}
-
-
- Репликация
-
-
-
-
-
- Адрес
- Состояние
- Lag ms
-
-
-
- {#each overview.replication as r (r.client_addr ?? r.state)}
-
- {r.client_addr ?? '—'}
- {r.state}
- {r.lag_ms ?? '—'}
-
- {/each}
-
-
-
-
- {/if}
- {/if}
-
-
-
-
-
- Медленные запросы
-
- Источник: {queries?.source ?? '—'}
- {#if queries?.statements_available === false || (overview && !overview.pg_stat_statements_enabled)}
- · pg_stat_statements недоступен
- {/if}
-
-
-
- {#if queries?.statements_hint}
-
- Нет статистики запросов
- {queries.statements_hint}
-
- {/if}
-
-
-
- mean ms
- calls
- query
-
-
-
- {#each queries?.items ?? [] as q (q.queryid ?? q.query)}
-
- {q.mean_exec_ms.toFixed(1)}
- {q.calls}
- {q.query}
-
- {:else}
-
- Нет данных
-
- {/each}
-
-
-
-
-
-
-
-
-
- Блокировки
-
-
-
-
-
- pid
- mode
- granted
- query
-
-
-
- {#each locks as l (l.pid)}
-
- {l.pid}
-
- {l.mode}
-
- {l.granted ? 'да' : 'нет'}
- {l.query ?? '—'}
-
- {:else}
-
- Нет активных блокировок
-
- {/each}
-
-
-
-
-
-
-
-
-
- Таблицы и хранилище
-
-
-
-
-
- table
- size
- seq_scan
- idx_scan
- bloat
-
-
-
- {#each tables as t (t.relname)}
-
- {t.relname}
- {formatBytes(t.total_bytes)}
- {t.seq_scan}
- {t.idx_scan}
- {(t.bloat_ratio ?? 0).toFixed(2)}
-
- {/each}
-
-
-
-
- {#if recommendations?.items?.length}
-
-
- Рекомендации
-
-
- {#each recommendations.items as item (item.code + item.title)}
-
- {item.title}
- {item.detail}
-
- {/each}
-
-
- {/if}
-
-
-
-
-
-
- Журнал обслуживания
-
-
-
-
-
- время
- kind
- status
- dry_run
-
-
-
- {#each maintLogs as log (log.id)}
-
- {log.created_at}
- {log.kind}
- {log.status}
- {log.dry_run ? 'да' : 'нет'}
-
- {:else}
-
- Пусто
-
- {/each}
-
-
-
-
-
-
-
-
-
- Корреляция (1ч)
- Pipeline refresh p99 vs cache hit по минутам
-
-
- {#each correlation?.points ?? [] as p (p.timestamp)}
-
- {p.timestamp}
- p99 refresh: {p.pipeline_refresh_p99_ms?.toFixed(0) ?? '—'} ms
- cache hit: {p.cache_hit_pct?.toFixed(1) ?? '—'}%
-
- {:else}
- Нет точек за окно
- {/each}
-
-
-
-
-{/if}
diff --git a/web-legacy-svelte/src/lib/components/monitoring/RuntimeLogsTab.svelte b/web-legacy-svelte/src/lib/components/monitoring/RuntimeLogsTab.svelte
deleted file mode 100644
index d343375..0000000
--- a/web-legacy-svelte/src/lib/components/monitoring/RuntimeLogsTab.svelte
+++ /dev/null
@@ -1,451 +0,0 @@
-
-
-
-
-
-
- Файловые логи Docker-сервисов (sidecar stack-runtime-logs)
-
-
-
-
- {#if filesUnavailable}
-
- {/if}
-
- {#if !filesUnavailable}
-
-
-
- Файлов
- {files.length}
-
-
-
-
- Суммарный размер
- {formatBytes(totalBytes)}
-
-
-
- {/if}
-
-
-
- Файлы
- Audit очистки
-
-
-
- {#if filesUnavailable}
-
- {:else}
-
-
- *.log на хосте
-
- Просмотр хвоста и синхронная очистка (truncate по умолчанию). Очистка — роль operator.
-
-
-
- f.name}
- loading={filesLoading}
- emptyTitle="Нет log-файлов"
- emptyDescription="Sidecar ещё не создал файлы или каталог пуст."
- >
- {#snippet cell({ row, column })}
- {#if column.id === 'name'}
- {row.name}
- {:else if column.id === 'size'}
- {formatBytes(row.size_bytes)}
- {:else if column.id === 'modified'}
- {formatDateTime(row.modified_at)}
- {:else if column.id === 'actions'}
-
-
- {#if isOperator}
-
-
-
- {#snippet child({ props })}
-
- {/snippet}
-
-
- requestCleanup(row, 'delete')}
- >
-
- Удалить файл
-
-
-
- {/if}
-
- {/if}
- {/snippet}
-
-
-
- {/if}
-
-
-
-
-
- История очистки
-
- Записи из runtime_log_cleanup_audit (viewer+).
-
-
-
- {#if auditError && auditItems.length === 0}
-
- {#snippet action()}
-
- {/snippet}
-
- {:else}
- r.id}
- loading={auditLoading && auditItems.length === 0}
- emptyTitle="Записей пока нет"
- emptyDescription="Очистка появится после ручного DELETE или автоочистки. Настройки — Параметры → Файловые логи."
- >
- {#snippet cell({ row, column })}
- {#if column.id === 'created'}
- {formatDateTime(row.created_at)}
- {:else if column.id === 'source'}
-
- {auditSourceLabel(row.actor_prefix)}
-
- {:else if column.id === 'actor'}
- {row.actor_prefix}
- {:else if column.id === 'filename'}
- {row.filename}
- {:else if column.id === 'action'}
- {row.action}
- {:else if column.id === 'sizes'}
-
- {formatBytes(row.size_before)}
- {#if row.size_after != null}
- → {formatBytes(row.size_after)}
- {/if}
-
- {/if}
- {/snippet}
-
- {/if}
- {#if auditHasMore}
-
- {/if}
-
-
-
-
-
-
-
diff --git a/web-legacy-svelte/src/lib/components/network/NetworkAutoRefreshToggle.svelte b/web-legacy-svelte/src/lib/components/network/NetworkAutoRefreshToggle.svelte
deleted file mode 100644
index c1a0ba1..0000000
--- a/web-legacy-svelte/src/lib/components/network/NetworkAutoRefreshToggle.svelte
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
diff --git a/web-legacy-svelte/src/lib/components/network/NetworkBirdSettingsSummaryCard.svelte b/web-legacy-svelte/src/lib/components/network/NetworkBirdSettingsSummaryCard.svelte
deleted file mode 100644
index 3c81b97..0000000
--- a/web-legacy-svelte/src/lib/components/network/NetworkBirdSettingsSummaryCard.svelte
+++ /dev/null
@@ -1,80 +0,0 @@
-
-
-
-
- BIRD (кратко)
-
- Глобальные параметры BIRD из tenant settings. Полная форма — в разделе «Параметры».
-
-
-
- {#if loading}
- Загрузка…
- {:else if Object.keys(values).length === 0}
- Параметры BIRD ещё не заданы.
- {:else}
-
- {#each Object.entries(values) as [key, value] (key)}
-
-
- {labels[key] ?? key}
- - {value}
-
- {/each}
-
- {/if}
-
-
-
-
diff --git a/web-legacy-svelte/src/lib/components/network/NetworkOverviewTab.svelte b/web-legacy-svelte/src/lib/components/network/NetworkOverviewTab.svelte
deleted file mode 100644
index 56b141e..0000000
--- a/web-legacy-svelte/src/lib/components/network/NetworkOverviewTab.svelte
+++ /dev/null
@@ -1,240 +0,0 @@
-
-
-
- {#if !initialLoading && !loading}
- {#if overallStatus === 'ok'}
-
-
- {networkOverallStatusLabel(overallStatus)}
- {overallHint}
-
- {:else if overallStatus === 'warn'}
-
-
- {networkOverallStatusLabel(overallStatus)}
-
- {overallHint}
- {#if issues.length > 0}
-
- {#each issues as issue (issue.id)}
- - {issue.message}
- {/each}
-
- {/if}
-
-
- {:else}
-
-
- {networkOverallStatusLabel(overallStatus)}
-
- {overallHint}
- {#if issues.length > 0}
-
- {#each issues as issue (issue.id)}
- - {issue.message}
- {/each}
-
- {/if}
-
-
- {/if}
- {/if}
-
-
-
-
-
-
Ноды
-
-
-
- {#if speakers.length === 0 && !initialLoading && !loading}
- Спикеры не зарегистрированы.
- {:else}
-
- {#each speakers as speaker (speaker.id)}
- onSpeakerSelect(speaker) : undefined}
- />
- {/each}
-
- {/if}
-
-
diff --git a/web-legacy-svelte/src/lib/components/network/NetworkPeersCard.svelte b/web-legacy-svelte/src/lib/components/network/NetworkPeersCard.svelte
deleted file mode 100644
index 0e9f8e9..0000000
--- a/web-legacy-svelte/src/lib/components/network/NetworkPeersCard.svelte
+++ /dev/null
@@ -1,342 +0,0 @@
-
-
-
-
-
- BGP-пиры
- Настройка BGP-соседей и привязка к спикерам
-
-
-
-
- p.id}
- loading={initialLoading || loading}
- {error}
- emptyTitle="Нет BGP-пиров"
- emptyDescription="Добавьте первого BGP-соседа для установки сессии."
- >
- {#snippet cell({ row: p, column })}
- {#if column.id === 'name'}
- {p.name?.trim() || '—'}
- {:else if column.id === 'neighbor'}
- {p.neighbor}
- {:else if column.id === 'remote_asn'}
- {p.remote_asn ?? '—'}
- {:else if column.id === 'enabled'}
-
- setEnabled(p, v)}
- />
-
- {:else if column.id === 'session_state'}
-
- {p.session_state || '—'}
-
- {peerConnectedLabel(p)}
-
- {#if peerSessionHint(p)}
- {peerSessionHint(p)}
- {/if}
-
- {:else if column.id === 'speaker'}
-
- {p.bgp_speaker_id ? speakerLabelById(p.bgp_speaker_id) : 'Все спикеры'}
-
- {:else if column.id === 'actions'}
-
-
-
-
- {/if}
- {/snippet}
-
-
-
-
-
diff --git a/web-legacy-svelte/src/lib/components/network/NetworkSpeakerDetailSheet.svelte b/web-legacy-svelte/src/lib/components/network/NetworkSpeakerDetailSheet.svelte
deleted file mode 100644
index ffe7dfd..0000000
--- a/web-legacy-svelte/src/lib/components/network/NetworkSpeakerDetailSheet.svelte
+++ /dev/null
@@ -1,203 +0,0 @@
-
-
-
-
- {#if speaker}
-
-
- {label}
-
- {speaker.role} · {speaker.agent_domain ?? speaker.endpoint}
-
-
-
-
- {#if status}
- {status.label}
- {/if}
- {#if speakerHasDrift(speaker)}
- Drift
- {/if}
-
-
-
- - BGP Established
- -
- {speaker.live?.bgp_established ?? '—'} / {speaker.live?.bgp_sessions_total ?? '—'}
-
- {#if speaker.live?.agent_last_sync_at}
- - Последний sync
- -
- {formatSyncAt(speaker.live.agent_last_sync_at)}
-
- {/if}
- - Drift (app / pub)
- - {driftLabel(speaker)}
- {#if speaker.last_dispatch_at}
- - Dispatch
- -
- {formatSyncAt(speaker.last_dispatch_at)}
-
- {/if}
-
-
- {#if dispatchError}
-
-
- {dispatchError.title}
- {dispatchError.detail}
-
- {/if}
- {#if agentError}
-
- {agentError.title}
- {agentError.detail}
-
- {/if}
- {#if bgpError}
-
- {bgpError.title}
- {bgpError.detail}
-
- {/if}
-
- {#if onApply && speaker.published_revision_id}
-
- {/if}
-
-
-
-
- BGP-сессии (live)
- {#if sessions.length === 0}
- Нет данных или сессий нет.
- {:else}
-
-
-
-
- Имя
- Состояние
-
-
-
- {#each sessions as sess, i (sess.name + i)}
-
-
- {sess.name}
- {#if sess.neighbor}
-
- {sess.neighbor}
-
- {/if}
-
-
- {sess.state}
-
-
- {/each}
-
-
-
- {/if}
-
-
-
-
-
- Пиры на ноде
- {#if relatedPeers.length === 0}
- Нет привязанных пиров.
- {:else}
-
- {#each relatedPeers as p (p.id)}
- -
-
-
- {p.name?.trim() || p.neighbor}
-
- {#if p.session_mismatch}
-
- Mismatch: сессия не на назначенной ноде
-
- {/if}
-
- {p.session_state || '—'}
-
- {/each}
-
- {/if}
-
-
- {/if}
-
-
diff --git a/web-legacy-svelte/src/lib/components/network/NetworkSpeakerStatusCard.svelte b/web-legacy-svelte/src/lib/components/network/NetworkSpeakerStatusCard.svelte
deleted file mode 100644
index 7c1d2f4..0000000
--- a/web-legacy-svelte/src/lib/components/network/NetworkSpeakerStatusCard.svelte
+++ /dev/null
@@ -1,85 +0,0 @@
-
-
-
- {
- if (onclick && (e.key === 'Enter' || e.key === ' ')) {
- e.preventDefault();
- onclick();
- }
- }}
->
-
-
-
-
-
- {label}
-
- {speaker.role}
-
-
{status.label}
-
-
-
-
- - BGP
- - {speakerBgpText(speaker)}
- - Пиры
- - {peerCount}
- - Drift
- -
-
- {drift ? 'есть' : 'нет'}
-
-
-
-
-
diff --git a/web-legacy-svelte/src/lib/components/network/NetworkSpeakersCard.svelte b/web-legacy-svelte/src/lib/components/network/NetworkSpeakersCard.svelte
deleted file mode 100644
index c55b1e6..0000000
--- a/web-legacy-svelte/src/lib/components/network/NetworkSpeakersCard.svelte
+++ /dev/null
@@ -1,526 +0,0 @@
-
-
-
-
-
- Спикеры
- Удалённые BIRD-ноды (Remnawave-style Panel→Node + signed bundle)
-
-
-
-
- s.id}
- loading={initialLoading || loading}
- {error}
- emptyTitle="Нет спикеров"
- emptyDescription="Добавьте реплику для применения signed bundle."
- >
- {#snippet cell({ row: s, column })}
- {#if column.id === 'status'}
- {statusLabel(s)}
- {:else if column.id === 'live_agent'}
- {liveAgentLabel(s)}
- {:else if column.id === 'bgp'}
- {speakerBgpText(s)}
- {:else if column.id === 'agent_domain'}
- {s.agent_domain ?? s.endpoint}
- {:else if column.id === 'role'}
- {s.role}
- {:else if column.id === 'drift'}
-
- {driftLabel(s)}
-
- {:else if column.id === 'actions'}
-
- {#if onSpeakerSelect}
-
- {/if}
-
-
-
-
-
- {/if}
- {/snippet}
-
-
-
-
-
-
-
-
-
-
-
diff --git a/web-legacy-svelte/src/lib/components/operations/OperationsDiffTab.svelte b/web-legacy-svelte/src/lib/components/operations/OperationsDiffTab.svelte
deleted file mode 100644
index 7379ca8..0000000
--- a/web-legacy-svelte/src/lib/components/operations/OperationsDiffTab.svelte
+++ /dev/null
@@ -1,213 +0,0 @@
-
-
-
-
- Сравнение ревизий
- Выберите ID двух ревизий для сравнения
-
-
-
-
-
-
-
-
- {#if diffData}
-
-
-
- +
- Добавлено ({addedSorted.length})
-
-
-
-
-
-
- −
- Удалено ({removedSorted.length})
-
-
-
-
- {/if}
-
-
diff --git a/web-legacy-svelte/src/lib/components/operations/OperationsJobsFilters.svelte b/web-legacy-svelte/src/lib/components/operations/OperationsJobsFilters.svelte
deleted file mode 100644
index ab9f2e5..0000000
--- a/web-legacy-svelte/src/lib/components/operations/OperationsJobsFilters.svelte
+++ /dev/null
@@ -1,163 +0,0 @@
-
-
-
-
-
-
Фильтры задач
-
-
-
-
-
-
-
-
-
- onSearchQChange((e.currentTarget as HTMLInputElement).value)}
- {disabled}
- autocomplete="off"
- aria-label="Текстовый поиск по задачам"
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/web-legacy-svelte/src/lib/components/operations/OperationsJobsTab.svelte b/web-legacy-svelte/src/lib/components/operations/OperationsJobsTab.svelte
deleted file mode 100644
index 8ed3378..0000000
--- a/web-legacy-svelte/src/lib/components/operations/OperationsJobsTab.svelte
+++ /dev/null
@@ -1,463 +0,0 @@
-
-
-
-
-
- Задачи
-
- Фоновые задачи (ingest, применение, обновление)
- {#if jobsFetchedTotal !== undefined}
-
- · Показано {jobs.length} из {jobsFetchedTotal}
-
- {/if}
-
-
-
-
-
-
- {#each jobs as job (job.job_id)}
- {@const kindSub = jobKindSubtitle(job, moduleNameById)}
-
-
-
-
-
-
-
{jobKindTitle(job, moduleNameById)}
- {#if kindSub}
-
{kindSub}
- {/if}
-
{job.job_id}
-
-
-
-
- {#if job.status === 'running' || job.status === 'queued'}
-
- {/if}
-
-
-
-
-
-
-
- Статус
-
-
{jobStatusRu(job.status)}
-
-
-
-
- Создана
-
-
{formatDateTime(job.created_at)}
-
-
-
-
- Запущена
-
-
{formatDateTime(job.started_at)}
-
-
-
-
- Завершена
-
-
{formatDateTime(job.finished_at)}
-
-
-
-
- {#if expandedJobIds.has(job.job_id)}
- {@const detailedJob = jobDetailsById.get(job.job_id) ?? job}
- {@const isDetailsLoading = jobDetailsLoading.has(job.job_id)}
- {@const isReportLoading = jobReportsLoading.has(job.job_id)}
- {@const jobReport = jobReportsById.get(job.job_id)}
- {@const logEntries = getJobLogEntries(detailedJob)}
- {@const logTotal = getJobLogTotal(detailedJob, logEntries)}
-
-
- {#if isDetailsLoading}
-
Догружаем свежие детали задачи…
- {/if}
-
- {#if detailedJob.error}
-
-
Ошибка
-
{detailedJob.error}
-
- {/if}
-
- {#if logEntries.length > 0}
-
-
-
Журнал обработки
-
- {logEntries.length} записей, всего {logTotal} префиксов
-
-
-
-
- {#each logEntries as entry, idx (`${job.job_id}-${idx}`)}
-
-
- {entry.message}
-
-
-
-
Источник
-
- {entry.source}
-
-
-
-
Тип
-
- {logKindRu(entry.kind)}
-
-
-
-
Сообщество BGP
-
- {entry.community_label?.trim() || entry.community}
-
-
-
-
Префиксы
-
- {entry.prefix_count}
-
-
-
- {#if entry.sample && entry.sample.length > 0}
-
-
- Примеры
-
-
-
- {#each entry.sample as sampleValue, sampleIdx (`${job.job_id}-${idx}-sample-${sampleIdx}`)}
-
- {sampleValue}
-
- {/each}
-
-
-
- {/if}
-
- {/each}
-
-
-
- {/if}
-
- {#if isReportLoading}
-
- Собираем подробный отчёт по источникам и агрегации…
-
- {/if}
-
- {#if jobReport}
-
-
-
Операции по модулю
-
- {#if jobReport.module}
- {moduleTypeRu(jobReport.module.type)}
- {jobReport.module.name}
- {/if}
- {#if jobReport.revisionId}
- {jobReport.revisionId.slice(0, 8)}…
- {/if}
-
-
-
-
-
-
- Агрегация
-
-
{jobReport.aggregationTotal} префиксов
-
-
-
-
- Домены
-
-
{jobReport.domains.length}
-
-
-
-
- ASN
-
-
{jobReport.asn.length}
-
-
-
-
- CDN / IP-диапазоны
-
-
- {jobReport.cdn.length}/{jobReport.ipRanges.length}
-
-
-
- {#if jobReport.aggregationByKind.length > 0}
-
-
Результат агрегации по типам
-
- {#each jobReport.aggregationByKind as row (`${job.job_id}-agg-${row.kind}`)}
- {logKindRu(row.kind)}: {row.prefixCount}
- {/each}
-
-
- {/if}
-
-
-
-
- Домены: какой домен какие IP/префиксы вернул
-
-
-
-
-
ASN: сколько префиксов получено по AS
-
-
-
-
- CDN: из каждой ссылки полученные IP/префиксы
-
-
-
-
-
- IP-диапазоны: итог по статическим диапазонам
-
-
-
-
-
- {/if}
-
-
-
Meta (JSON)
-
-
{JSON.stringify(
- detailedJob.meta ?? {},
- null,
- 2
- )}
-
-
-
-
- {/if}
-
- {:else}
- {#if jobsLoading}
- Загрузка…
- {:else}
-
- {/if}
- {/each}
-
-
diff --git a/web-legacy-svelte/src/lib/components/operations/OperationsQuickActions.svelte b/web-legacy-svelte/src/lib/components/operations/OperationsQuickActions.svelte
deleted file mode 100644
index 7ca2f4d..0000000
--- a/web-legacy-svelte/src/lib/components/operations/OperationsQuickActions.svelte
+++ /dev/null
@@ -1,174 +0,0 @@
-
-
-
-
-
-
-
-
-
Применить ко всем спикерам
-
- Применить текущую конфигурацию на всех BIRD-спикерах
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Перезагрузка BIRD
-
- Перезагрузить конфигурацию BIRD на всех спикерах
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Состояние BIRD
- {#if birdStatus}
-
- {birdHealthyShortLabel(birdStatus.healthy)}
-
- {/if}
-
-
- Локально на хосте API: birdc show protocols. Не заменяет мониторинг спикеров.
-
- {#if birdLoading}
-
Загрузка…
- {:else if birdStatus}
- {#if !birdStatus.birdc_configured}
-
- {birdStatus.message ?? 'birdc не настроен на API.'}
-
- {:else if birdStatus.error}
-
{birdStatus.error}
- {:else}
-
- BGP сессий:
- {birdStatus.bgp_established}
- /
- {birdStatus.bgp_sessions_total}
- установлено / всего
-
- {/if}
- {:else}
-
Статус не загружен
- {/if}
-
-
-
-
- {#if birdStatus?.birdc_configured && birdStatus.protocols_excerpt}
-
- {/if}
-
-
-
-
diff --git a/web-legacy-svelte/src/lib/components/operations/OperationsRevisionsTab.svelte b/web-legacy-svelte/src/lib/components/operations/OperationsRevisionsTab.svelte
deleted file mode 100644
index 83bb606..0000000
--- a/web-legacy-svelte/src/lib/components/operations/OperationsRevisionsTab.svelte
+++ /dev/null
@@ -1,120 +0,0 @@
-
-
-
-
-
- История ревизий
-
- Создаются задачей module_refresh (CDN / IP / AS и т.д.); в превью есть полный текст BIRD с
- инклюдами
-
-
-
-
-
- rev.id}
- loading={revLoading}
- emptyTitle="Нет ревизий"
- emptyDescription="Ревизии появятся после обновления модулей."
- >
- {#snippet cell({ row: rev, column })}
- {#if column.id === 'id'}
- {rev.id.slice(0, 8)}…
- {:else if column.id === 'created'}
- {formatDateTime(rev.created_at)}
- {:else if column.id === 'prefixes'}
- {rev.materialized_prefix_count}
- {:else if column.id === 'hash'}
- {rev.content_hash.slice(0, 12)}…
- {:else if column.id === 'actions'}
-
-
-
-
-
- {/if}
- {/snippet}
-
-
-
diff --git a/web-legacy-svelte/src/lib/components/operations/job-report-columns.ts b/web-legacy-svelte/src/lib/components/operations/job-report-columns.ts
deleted file mode 100644
index 117feab..0000000
--- a/web-legacy-svelte/src/lib/components/operations/job-report-columns.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import type { ColumnDef } from '@tanstack/table-core';
-import type { ReportRow } from './types.js';
-
-export const reportRowColumns: ColumnDef[] = [
- {
- accessorKey: 'label',
- header: 'Метка',
- cell: ({ row }) => {
- const v = row.original.label;
- return typeof v === 'string' ? v : '—';
- }
- },
- {
- accessorKey: 'prefixCount',
- header: 'Префиксов',
- cell: ({ row }) => String(row.original.prefixCount)
- },
- {
- id: 'prefixes',
- header: 'Префиксы',
- cell: ({ row }) => {
- const p = row.original.prefixes;
- if (!p?.length) return '—';
- return p.join(', ');
- }
- }
-];
-
-export type AsnReportRow = { asn: string; prefixCount: number };
-
-export const asnReportColumns: ColumnDef[] = [
- {
- accessorKey: 'asn',
- header: 'ASN',
- cell: ({ row }) => `AS${row.original.asn}`
- },
- {
- accessorKey: 'prefixCount',
- header: 'Префиксов',
- cell: ({ row }) => String(row.original.prefixCount)
- }
-];
diff --git a/web-legacy-svelte/src/lib/components/operations/job-report-table-block.svelte b/web-legacy-svelte/src/lib/components/operations/job-report-table-block.svelte
deleted file mode 100644
index 7df6ee9..0000000
--- a/web-legacy-svelte/src/lib/components/operations/job-report-table-block.svelte
+++ /dev/null
@@ -1,107 +0,0 @@
-
-
-
-
-
- {#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
-
- {#each headerGroup.headers as header (header.id)}
-
- {#if !header.isPlaceholder}
-
- {/if}
-
- {/each}
-
- {/each}
-
-
- {#each table.getRowModel().rows as row (row.id)}
-
- {#each row.getVisibleCells() as cell (cell.id)}
-
-
-
- {/each}
-
- {:else}
-
-
- {emptyLabel}
-
-
- {/each}
-
-
-
-{#if table.getPageCount() > 1}
-
-
- Стр. {table.getState().pagination.pageIndex + 1} из {table.getPageCount()} ({rows.length} строк)
-
-
-
-
-{/if}
diff --git a/web-legacy-svelte/src/lib/components/operations/types.ts b/web-legacy-svelte/src/lib/components/operations/types.ts
deleted file mode 100644
index 94de3c5..0000000
--- a/web-legacy-svelte/src/lib/components/operations/types.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import type { ModuleRow } from '$lib/api/types.js';
-
-export type JobLogEntry = {
- kind: string;
- source: string;
- community: string;
- /** Человекочитаемое имя из справочника (title или BGP community). */
- community_label?: string;
- prefix_count: number;
- sample?: string[];
- message: string;
-};
-
-export type ReportRow = {
- label: string;
- source: string;
- prefixes: string[];
- prefixCount: number;
-};
-
-export type JobDetailedReport = {
- revisionId: string | null;
- module: ModuleRow | null;
- aggregationTotal: number;
- aggregationByKind: Array<{ kind: string; prefixCount: number }>;
- domains: ReportRow[];
- asn: Array<{ asn: string; prefixCount: number }>;
- cdn: ReportRow[];
- ipRanges: ReportRow[];
-};
diff --git a/web-legacy-svelte/src/lib/components/overview/OverviewNetworkStatusCard.svelte b/web-legacy-svelte/src/lib/components/overview/OverviewNetworkStatusCard.svelte
deleted file mode 100644
index 8805d71..0000000
--- a/web-legacy-svelte/src/lib/components/overview/OverviewNetworkStatusCard.svelte
+++ /dev/null
@@ -1,120 +0,0 @@
-
-
-
-
-
-
-
- Сеть (BGP)
-
- Live-статус пиров и спикеров
-
-
-
-
- {#if error}
- {error}
- {:else if initialLoading || loading}
- Загрузка live-метрик…
- {:else if overallStatus === 'ok'}
-
-
- {networkOverallStatusLabel(overallStatus)}
- {overallHint}
-
- {:else if overallStatus === 'warn'}
-
-
- {networkOverallStatusLabel(overallStatus)}
-
- {overallHint}
- {#if issues.length > 0}
-
- {#each issues as issue (issue.id)}
- - {issue.message}
- {/each}
-
- {/if}
-
-
- {:else}
-
-
- {networkOverallStatusLabel(overallStatus)}
-
- {overallHint}
- {#if issues.length > 0}
-
- {#each issues as issue (issue.id)}
- - {issue.message}
- {/each}
-
- {/if}
-
-
- {/if}
-
-
-
-
Пиры Established
-
- {initialLoading ? '—' : `${metrics.peersEstablished}/${metrics.peersEnabled}`}
-
-
-
-
Спикеры online
-
- {initialLoading ? '—' : `${metrics.speakersOnline}/${metrics.speakersTotal}`}
-
-
-
-
Drift
-
{initialLoading ? '—' : metrics.speakersDrift}
-
-
-
-
diff --git a/web-legacy-svelte/src/lib/components/overview/OverviewRecentJobsCard.svelte b/web-legacy-svelte/src/lib/components/overview/OverviewRecentJobsCard.svelte
deleted file mode 100644
index b5b8769..0000000
--- a/web-legacy-svelte/src/lib/components/overview/OverviewRecentJobsCard.svelte
+++ /dev/null
@@ -1,94 +0,0 @@
-
-
-
-
-
- Последние задачи
- Фоновые задачи ingest, refresh и apply
-
-
-
-
- j.job_id}
- loading={initialLoading || loading}
- {error}
- emptyTitle="Нет задач"
- emptyDescription="Задачи появятся после refresh или деплоя."
- >
- {#snippet cell({ row: j, column })}
- {#if column.id === 'kind'}
- {jobKindTitle(j, moduleNameById)}
- {:else if column.id === 'status'}
- {jobStatusRu(j.status)}
- {:else if column.id === 'created'}
-
- {formatDateTime(j.created_at)}
-
- {:else if column.id === 'actions'}
-
- {/if}
- {/snippet}
-
-
-
diff --git a/web-legacy-svelte/src/lib/components/overview/OverviewRecentRevisionsCard.svelte b/web-legacy-svelte/src/lib/components/overview/OverviewRecentRevisionsCard.svelte
deleted file mode 100644
index acc788b..0000000
--- a/web-legacy-svelte/src/lib/components/overview/OverviewRecentRevisionsCard.svelte
+++ /dev/null
@@ -1,89 +0,0 @@
-
-
-
-
-
- Последние ревизии
- Снимки конфигурации BIRD после обновления модулей
-
-
-
-
- rev.id}
- loading={initialLoading || loading}
- {error}
- emptyTitle="Нет ревизий"
- emptyDescription="Ревизии появятся после обновления модулей."
- >
- {#snippet cell({ row: rev, column })}
- {#if column.id === 'id'}
- {rev.id.slice(0, 8)}…
- {:else if column.id === 'created'}
-
- {formatDateTime(rev.created_at)}
-
- {:else if column.id === 'prefixes'}
- {rev.materialized_prefix_count}
- {:else if column.id === 'actions'}
-
- {/if}
- {/snippet}
-
-
-
diff --git a/web-legacy-svelte/src/lib/components/tenant-settings/TenantAdditionalSettingsCard.svelte b/web-legacy-svelte/src/lib/components/tenant-settings/TenantAdditionalSettingsCard.svelte
deleted file mode 100644
index c9487b7..0000000
--- a/web-legacy-svelte/src/lib/components/tenant-settings/TenantAdditionalSettingsCard.svelte
+++ /dev/null
@@ -1,149 +0,0 @@
-
-
-
-
-
-
- Дополнительные параметры
- Произвольные KV-пары в global_settings (operator).
-
- {#if loaded}
-
- {/if}
-
-
-
- {#if loading && !loaded}
- Загрузка…
- {:else if !loaded}
-
- {:else if additionalSettings.length === 0}
-
- {:else}
-
-
-
- {/if}
-
-
diff --git a/web-legacy-svelte/src/lib/components/tenant-settings/TenantBirdSettingsCard.svelte b/web-legacy-svelte/src/lib/components/tenant-settings/TenantBirdSettingsCard.svelte
deleted file mode 100644
index f622908..0000000
--- a/web-legacy-svelte/src/lib/components/tenant-settings/TenantBirdSettingsCard.svelte
+++ /dev/null
@@ -1,212 +0,0 @@
-
-
-
-
- BIRD control plane
-
- Глобальные параметры BIRD для pipeline refresh/apply. Сохранение через
- PATCH /v1/settings (роль operator).
-
-
-
-
-
- Подстановка в конфиг
-
- Значения используются при генерации BIRD-конфигурации (router id, local AS, адреса). Пиры и
- спикеры настраиваются в разделе «Сеть».
-
-
-
- {#if loading && !loaded}
- Загрузка…
- {:else if !loaded}
-
- {:else}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {#if hasValidationErrors}
-
- Есть ошибки в полях. Исправьте их, чтобы сохранить изменения.
-
- {/if}
-
-
- {/if}
-
-
diff --git a/web-legacy-svelte/src/lib/components/tenant-settings/TenantRevisionSettingsCard.svelte b/web-legacy-svelte/src/lib/components/tenant-settings/TenantRevisionSettingsCard.svelte
deleted file mode 100644
index 0ecf925..0000000
--- a/web-legacy-svelte/src/lib/components/tenant-settings/TenantRevisionSettingsCard.svelte
+++ /dev/null
@@ -1,277 +0,0 @@
-
-
-
-
- Хранение ревизий
-
- Автоматическая очистка старых ревизий. Последняя ревизия и раскатанные на спикерах не
- удаляются.
-
-
-
- {#if loading && !loaded}
- Загрузка…
- {:else if !loaded}
-
- {:else}
-
-
-
-
- {#if parsedRetentionMinutes !== null}
-
-
Оценка очистки по введённому retention
- {#if estimateLoading}
-
Расчёт…
- {:else if estimate}
-
- Будет удалено ревизий: {estimate.revision_count}
-
-
- Освободится ориентировочно: ~{formatBytes(estimate.bytes_estimate)}
-
- {#if estimate.prefix_row_count > 0}
-
- Строк префиксов в снимках: {estimate.prefix_row_count}
- {#if estimate.orphan_snapshot_count > 0}
- · снимков: {estimate.orphan_snapshot_count}
- {/if}
-
- {/if}
- {:else}
-
Оценка недоступна
- {/if}
-
- Учитываются те же правила, что при автоочистке: последняя ревизия и раскатанные на
- спикерах не удаляются.
-
-
- {/if}
-
- {#if hasValidationErrors}
-
- Есть ошибки в полях. Исправьте их, чтобы сохранить изменения.
-
- {/if}
-
-
-
- {#if isOperator}
-
- {/if}
-
- {/if}
-
-
diff --git a/web-legacy-svelte/src/lib/components/tenant-settings/TenantRuntimeLogsSettingsCard.svelte b/web-legacy-svelte/src/lib/components/tenant-settings/TenantRuntimeLogsSettingsCard.svelte
deleted file mode 100644
index c3be090..0000000
--- a/web-legacy-svelte/src/lib/components/tenant-settings/TenantRuntimeLogsSettingsCard.svelte
+++ /dev/null
@@ -1,340 +0,0 @@
-
-
-
-
- Файловые логи (runtime-logs)
-
- Автоочистка *.log на диске evobgp-all (sidecar
- stack-runtime-logs). Требуется volume EVOBGP_RUNTIME_LOGS_DIR.
-
-
-
- {#if loading && !loaded}
- Загрузка…
- {:else if !loaded}
-
- {:else}
-
- {
- $form.runtime_logs_auto_enabled = v ? 'true' : 'false';
- }}
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {#if fsUnavailable}
-
- FS API недоступен (не evobgp-all или нет volume). Оценка и «Запустить сейчас» недоступны;
- настройки сохраняются для будущего прогона scheduler.
-
- {:else if parsedMaxMb !== null}
-
-
Оценка по текущему порогу
- {#if estimateLoading}
-
Расчёт…
- {:else if estimate}
-
- Файлов к очистке: {estimate.would_count ?? 0}
-
- {#if estimate.items?.length}
-
- {#each estimate.items.filter((i) => i.would_cleanup) as item (item.filename)}
- -
- {item.filename} · {formatBytes(item.size_bytes)}
-
- {/each}
-
- {/if}
- {:else}
-
Оценка недоступна
- {/if}
-
- {/if}
-
- {#if hasValidationErrors}
- Исправьте ошибки в полях перед сохранением.
- {/if}
-
-
- {#if isOperator}
-
-
- {/if}
-
- {/if}
-
-
diff --git a/web-legacy-svelte/src/lib/components/tenant-settings/TenantSettingsPage.svelte b/web-legacy-svelte/src/lib/components/tenant-settings/TenantSettingsPage.svelte
deleted file mode 100644
index be0290f..0000000
--- a/web-legacy-svelte/src/lib/components/tenant-settings/TenantSettingsPage.svelte
+++ /dev/null
@@ -1,90 +0,0 @@
-
-
-
-
-
-
-
- Operator-only
-
- Изменение значений через PATCH /v1/settings требует роли operator.
- При отсутствии прав API вернёт 403.
-
-
-
-
-
-
- BIRD
- Ревизии
- Файловые логи
- Дополнительно
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/web-legacy-svelte/src/lib/components/ui/alert-dialog/index.ts b/web-legacy-svelte/src/lib/components/ui/alert-dialog/index.ts
deleted file mode 100644
index 18f963c..0000000
--- a/web-legacy-svelte/src/lib/components/ui/alert-dialog/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from '$lib/ui/core/alert-dialog/index.js';
diff --git a/web-legacy-svelte/src/lib/components/ui/alert/index.ts b/web-legacy-svelte/src/lib/components/ui/alert/index.ts
deleted file mode 100644
index 099653e..0000000
--- a/web-legacy-svelte/src/lib/components/ui/alert/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from '$lib/ui/core/alert/index.js';
diff --git a/web-legacy-svelte/src/lib/components/ui/badge/index.ts b/web-legacy-svelte/src/lib/components/ui/badge/index.ts
deleted file mode 100644
index a274438..0000000
--- a/web-legacy-svelte/src/lib/components/ui/badge/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from '$lib/ui/core/badge/index.js';
diff --git a/web-legacy-svelte/src/lib/components/ui/button/index.ts b/web-legacy-svelte/src/lib/components/ui/button/index.ts
deleted file mode 100644
index 06ff5d3..0000000
--- a/web-legacy-svelte/src/lib/components/ui/button/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from '$lib/ui/core/button/index.js';
diff --git a/web-legacy-svelte/src/lib/components/ui/card/index.ts b/web-legacy-svelte/src/lib/components/ui/card/index.ts
deleted file mode 100644
index 7ddc276..0000000
--- a/web-legacy-svelte/src/lib/components/ui/card/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from '$lib/ui/core/card/index.js';
diff --git a/web-legacy-svelte/src/lib/components/ui/checkbox/index.ts b/web-legacy-svelte/src/lib/components/ui/checkbox/index.ts
deleted file mode 100644
index c89e035..0000000
--- a/web-legacy-svelte/src/lib/components/ui/checkbox/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from '$lib/ui/core/checkbox/index.js';
diff --git a/web-legacy-svelte/src/lib/components/ui/data-table/index.ts b/web-legacy-svelte/src/lib/components/ui/data-table/index.ts
deleted file mode 100644
index 12c5fb1..0000000
--- a/web-legacy-svelte/src/lib/components/ui/data-table/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from '$lib/ui/core/data-table/index.js';
diff --git a/web-legacy-svelte/src/lib/components/ui/dialog/index.ts b/web-legacy-svelte/src/lib/components/ui/dialog/index.ts
deleted file mode 100644
index 767e160..0000000
--- a/web-legacy-svelte/src/lib/components/ui/dialog/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from '$lib/ui/core/dialog/index.js';
diff --git a/web-legacy-svelte/src/lib/components/ui/dropdown-menu/index.ts b/web-legacy-svelte/src/lib/components/ui/dropdown-menu/index.ts
deleted file mode 100644
index c6bae74..0000000
--- a/web-legacy-svelte/src/lib/components/ui/dropdown-menu/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from '$lib/ui/core/dropdown-menu/index.js';
diff --git a/web-legacy-svelte/src/lib/components/ui/form/index.ts b/web-legacy-svelte/src/lib/components/ui/form/index.ts
deleted file mode 100644
index bf35b9b..0000000
--- a/web-legacy-svelte/src/lib/components/ui/form/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from '$lib/ui/core/form/index.js';
diff --git a/web-legacy-svelte/src/lib/components/ui/input/index.ts b/web-legacy-svelte/src/lib/components/ui/input/index.ts
deleted file mode 100644
index d660ada..0000000
--- a/web-legacy-svelte/src/lib/components/ui/input/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from '$lib/ui/core/input/index.js';
diff --git a/web-legacy-svelte/src/lib/components/ui/label/index.ts b/web-legacy-svelte/src/lib/components/ui/label/index.ts
deleted file mode 100644
index 729cf3b..0000000
--- a/web-legacy-svelte/src/lib/components/ui/label/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from '$lib/ui/core/label/index.js';
diff --git a/web-legacy-svelte/src/lib/components/ui/popover/index.ts b/web-legacy-svelte/src/lib/components/ui/popover/index.ts
deleted file mode 100644
index 857995c..0000000
--- a/web-legacy-svelte/src/lib/components/ui/popover/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from '$lib/ui/core/popover/index.js';
diff --git a/web-legacy-svelte/src/lib/components/ui/scroll-area/index.ts b/web-legacy-svelte/src/lib/components/ui/scroll-area/index.ts
deleted file mode 100644
index 4f5f5a7..0000000
--- a/web-legacy-svelte/src/lib/components/ui/scroll-area/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from '$lib/ui/core/scroll-area/index.js';
diff --git a/web-legacy-svelte/src/lib/components/ui/select/index.ts b/web-legacy-svelte/src/lib/components/ui/select/index.ts
deleted file mode 100644
index a428280..0000000
--- a/web-legacy-svelte/src/lib/components/ui/select/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from '$lib/ui/core/select/index.js';
diff --git a/web-legacy-svelte/src/lib/components/ui/separator/index.ts b/web-legacy-svelte/src/lib/components/ui/separator/index.ts
deleted file mode 100644
index e7333ee..0000000
--- a/web-legacy-svelte/src/lib/components/ui/separator/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from '$lib/ui/core/separator/index.js';
diff --git a/web-legacy-svelte/src/lib/components/ui/sheet/index.ts b/web-legacy-svelte/src/lib/components/ui/sheet/index.ts
deleted file mode 100644
index e23e688..0000000
--- a/web-legacy-svelte/src/lib/components/ui/sheet/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from '$lib/ui/core/sheet/index.js';
diff --git a/web-legacy-svelte/src/lib/components/ui/skeleton/index.ts b/web-legacy-svelte/src/lib/components/ui/skeleton/index.ts
deleted file mode 100644
index d05becd..0000000
--- a/web-legacy-svelte/src/lib/components/ui/skeleton/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from '$lib/ui/core/skeleton/index.js';
diff --git a/web-legacy-svelte/src/lib/components/ui/switch/index.ts b/web-legacy-svelte/src/lib/components/ui/switch/index.ts
deleted file mode 100644
index 8d48016..0000000
--- a/web-legacy-svelte/src/lib/components/ui/switch/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from '$lib/ui/core/switch/index.js';
diff --git a/web-legacy-svelte/src/lib/components/ui/table/index.ts b/web-legacy-svelte/src/lib/components/ui/table/index.ts
deleted file mode 100644
index 71ac428..0000000
--- a/web-legacy-svelte/src/lib/components/ui/table/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from '$lib/ui/core/table/index.js';
diff --git a/web-legacy-svelte/src/lib/components/ui/tabs/index.ts b/web-legacy-svelte/src/lib/components/ui/tabs/index.ts
deleted file mode 100644
index c26ef99..0000000
--- a/web-legacy-svelte/src/lib/components/ui/tabs/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from '$lib/ui/core/tabs/index.js';
diff --git a/web-legacy-svelte/src/lib/components/ui/textarea/index.ts b/web-legacy-svelte/src/lib/components/ui/textarea/index.ts
deleted file mode 100644
index 3ece123..0000000
--- a/web-legacy-svelte/src/lib/components/ui/textarea/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from '$lib/ui/core/textarea/index.js';
diff --git a/web-legacy-svelte/src/lib/components/ui/tooltip/index.ts b/web-legacy-svelte/src/lib/components/ui/tooltip/index.ts
deleted file mode 100644
index e6a87c4..0000000
--- a/web-legacy-svelte/src/lib/components/ui/tooltip/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from '$lib/ui/core/tooltip/index.js';
diff --git a/web-legacy-svelte/src/lib/dialog-layout.ts b/web-legacy-svelte/src/lib/dialog-layout.ts
deleted file mode 100644
index 4a4f2ab..0000000
--- a/web-legacy-svelte/src/lib/dialog-layout.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * Общая разметка для модалок с прокручиваемым контентом (превью BIRD, вывод birdc, логи).
- * DialogContent по умолчанию — grid + p-4; здесь переопределяем на flex-колонку без внешних отступов.
- */
-export const dialogContentDocument =
- '!flex w-[min(100vw-2rem,56rem)] max-h-[min(92vh,880px)] flex-col gap-0 overflow-hidden !p-0 sm:max-w-4xl';
-
-export const dialogHeaderDocument =
- 'shrink-0 space-y-1.5 border-b border-border/70 px-6 pt-5 pb-3 pr-14 text-left';
-
-export const dialogBodyDocument =
- 'flex min-h-0 min-w-0 flex-1 flex-col gap-3 overflow-hidden px-6 py-4';
-
-/** Компактные модалки (детали задачи, формы): единая шапка и тело */
-export const dialogContentPanel =
- '!flex max-h-[min(90vh,40rem)] w-full max-w-lg flex-col gap-0 overflow-hidden !p-0 sm:max-w-lg';
-
-export const dialogHeaderPanel =
- 'shrink-0 space-y-1.5 border-b border-border/70 px-6 pt-5 pb-3 pr-14 text-left';
-
-export const dialogBodyPanel = 'min-h-0 flex-1 overflow-y-auto px-6 py-4';
diff --git a/web-legacy-svelte/src/lib/index.ts b/web-legacy-svelte/src/lib/index.ts
deleted file mode 100644
index 856f2b6..0000000
--- a/web-legacy-svelte/src/lib/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-// place files you want to import through the `$lib` alias in this folder.
diff --git a/web-legacy-svelte/src/lib/maintenance/policy-api.ts b/web-legacy-svelte/src/lib/maintenance/policy-api.ts
deleted file mode 100644
index 4a6cd72..0000000
--- a/web-legacy-svelte/src/lib/maintenance/policy-api.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import { apiJSON, apiMutate } from '$lib/api/client.js';
-
-export type MaintenancePolicy = {
- id: string;
- name: string;
- table_name: string;
- condition: string;
- retention_period_sec?: number;
- max_rows?: number;
- vacuum_strategy: string;
- schedule: string;
- enabled: boolean;
- dry_run_enabled: boolean;
- last_run_at?: string;
- last_status?: string;
- last_error?: string;
- created_at?: string;
- updated_at?: string;
-};
-
-export type MaintenancePolicyHints = {
- table_name: string;
- n_dead_tup: number;
- bloat_ratio?: number;
- last_autovacuum?: string;
- recommend_vacuum: boolean;
- detail?: string;
-};
-
-export type MaintenancePoliciesResponse = {
- items: MaintenancePolicy[];
- next_cursor?: string;
- has_more?: boolean;
-};
-
-export async function listMaintenancePolicies(limit = 100): Promise {
- const r = await apiJSON(`/v1/maintenance/policies?limit=${limit}`);
- return r.items ?? [];
-}
-
-export async function createMaintenancePolicy(
- body: Record
-): Promise {
- return apiMutate('/v1/maintenance/policies', 'POST', body);
-}
-
-export async function updateMaintenancePolicy(
- id: string,
- body: Record
-): Promise {
- return apiMutate(`/v1/maintenance/policies/${id}`, 'PATCH', body);
-}
-
-export async function deleteMaintenancePolicy(id: string): Promise {
- await apiMutate(`/v1/maintenance/policies/${id}`, 'DELETE', undefined, { idempotent: false });
-}
-
-export async function runMaintenancePolicy(
- id: string,
- dryRun: boolean
-): Promise<{ job_id: string }> {
- const path = dryRun ? '/v1/maintenance/dry-run' : '/v1/maintenance/run';
- return apiMutate<{ job_id: string; status: string }>(path, 'POST', { policy_id: id });
-}
-
-export async function fetchPolicyHints(id: string): Promise {
- return apiJSON(`/v1/maintenance/policies/${id}/hints`);
-}
diff --git a/web-legacy-svelte/src/lib/maintenance/policy-presets.ts b/web-legacy-svelte/src/lib/maintenance/policy-presets.ts
deleted file mode 100644
index e073bee..0000000
--- a/web-legacy-svelte/src/lib/maintenance/policy-presets.ts
+++ /dev/null
@@ -1,148 +0,0 @@
-import type { MaintenancePolicy } from '$lib/maintenance/policy-api.js';
-import type { MaintenancePolicyForm } from '$lib/maintenance/policy.schema.js';
-
-const DAY_SEC = 86_400;
-
-/** Рекомендуемый шаблон политики (только UI; в БД не seed'ится). */
-export type MaintenancePolicyPreset = {
- id: string;
- label: string;
- description: string;
- /** Подсказка: таблица должна быть видна в pg_stat (не блокирует создание). */
- tableHint?: string;
- form: MaintenancePolicyForm;
-};
-
-/** Базовые пресеты EvoBGP — оператор выбирает, какие создать. */
-export const maintenancePolicyPresets: MaintenancePolicyPreset[] = [
- {
- id: 'job_audit_retention',
- label: 'Job audit — retention 90d',
- description:
- 'Удаляет завершённые записи job_audit старше 90 дней; VACUUM ANALYZE после очистки. Расписание 03:00 UTC.',
- tableHint: 'job_audit',
- form: {
- name: 'Job audit retention (90d)',
- table_name: 'job_audit',
- condition: "status IN ('succeeded', 'failed', 'cancelled')",
- retention_period_sec: String(90 * DAY_SEC),
- max_rows: '10000',
- vacuum_strategy: 'vacuum_analyze',
- schedule: '0 3 * * *',
- enabled: true,
- dry_run_enabled: true
- }
- },
- {
- id: 'runtime_log_cleanup_audit_retention',
- label: 'Runtime log cleanup audit — 90d',
- description:
- 'Удаляет записи runtime_log_cleanup_audit старше 90 дней (ручная и автоочистка FS).',
- tableHint: 'runtime_log_cleanup_audit',
- form: {
- name: 'Runtime log cleanup audit (90d)',
- table_name: 'runtime_log_cleanup_audit',
- condition: 'true',
- retention_period_sec: String(90 * DAY_SEC),
- max_rows: '5000',
- vacuum_strategy: 'none',
- schedule: '0 4 * * *',
- enabled: true,
- dry_run_enabled: true
- }
- },
- {
- id: 'postgres_maintenance_audit_retention',
- label: 'Maintenance audit — 30d',
- description: 'Очищает postgres_maintenance_audit старше 30 дней без vacuum.',
- tableHint: 'postgres_maintenance_audit',
- form: {
- name: 'Postgres maintenance audit (30d)',
- table_name: 'postgres_maintenance_audit',
- condition: 'true',
- retention_period_sec: String(30 * DAY_SEC),
- max_rows: '5000',
- vacuum_strategy: 'none',
- schedule: '0 4 * * *',
- enabled: true,
- dry_run_enabled: true
- }
- },
- {
- id: 'job_audit_vacuum_weekly',
- label: 'Job audit — VACUUM weekly',
- description: 'Только VACUUM ANALYZE job_audit по воскресеньям, без удаления строк.',
- tableHint: 'job_audit',
- form: {
- name: 'Job audit vacuum (weekly)',
- table_name: 'job_audit',
- condition: 'true',
- retention_period_sec: '',
- max_rows: '',
- vacuum_strategy: 'vacuum_analyze',
- schedule: '0 2 * * 0',
- enabled: true,
- dry_run_enabled: false
- }
- },
- {
- id: 'postgres_monitor_snapshot',
- label: 'PG monitor snapshots — 14d',
- description:
- 'Удаляет снимки postgres_monitor_snapshot старше 14 дней (фильтр по collected_at в condition).',
- tableHint: 'postgres_monitor_snapshot',
- form: {
- name: 'Postgres monitor snapshots (14d)',
- table_name: 'postgres_monitor_snapshot',
- condition: "collected_at < NOW() - INTERVAL '14 days'",
- retention_period_sec: '',
- max_rows: '10000',
- vacuum_strategy: 'none',
- schedule: '0 5 * * *',
- enabled: true,
- dry_run_enabled: true
- }
- },
- {
- id: 'config_revision_retention',
- label: 'Config revisions — 180d',
- description:
- 'Долгое хранение старых config_revision (180d). Перед включением проверьте revision_retention в настройках.',
- tableHint: 'config_revision',
- form: {
- name: 'Config revision retention (180d)',
- table_name: 'config_revision',
- condition: 'true',
- retention_period_sec: String(180 * DAY_SEC),
- max_rows: '5000',
- vacuum_strategy: 'vacuum',
- schedule: '0 6 * * 0',
- enabled: false,
- dry_run_enabled: true
- }
- }
-];
-
-export function presetForm(preset: MaintenancePolicyPreset): MaintenancePolicyForm {
- return structuredClone(preset.form);
-}
-
-/** Политика с тем же именем и таблицей считается уже созданной из пресета. */
-export function isPresetAlreadyApplied(
- preset: MaintenancePolicyPreset,
- policies: MaintenancePolicy[]
-): boolean {
- return policies.some(
- (p) => p.name === preset.form.name.trim() && p.table_name === preset.form.table_name.trim()
- );
-}
-
-export function filterAvailablePresets(
- policies: MaintenancePolicy[],
- selected: Iterable
-): MaintenancePolicyPreset[] {
- const ids = new Set(selected);
- return maintenancePolicyPresets.filter(
- (p) => ids.has(p.id) && !isPresetAlreadyApplied(p, policies)
- );
-}
diff --git a/web-legacy-svelte/src/lib/maintenance/policy-schedule.ts b/web-legacy-svelte/src/lib/maintenance/policy-schedule.ts
deleted file mode 100644
index 8d8a636..0000000
--- a/web-legacy-svelte/src/lib/maintenance/policy-schedule.ts
+++ /dev/null
@@ -1,181 +0,0 @@
-/** Режимы расписания (5-field cron, UTC) — совместимы с robfig/cron в scheduler. */
-export type ScheduleMode = 'minutes' | 'hours' | 'daily' | 'weekly' | 'custom';
-
-export type ScheduleEditor = {
- mode: ScheduleMode;
- /** Интервал в минутах (режим minutes). */
- intervalMinutes: string;
- /** Интервал в часах (режим hours). */
- intervalHours: string;
- minute: string;
- hour: string;
- /** 0 = воскресенье … 6 = суббота */
- weekday: string;
- customCron: string;
-};
-
-export const scheduleModeOptions: { value: ScheduleMode; label: string }[] = [
- { value: 'minutes', label: 'Каждые N минут' },
- { value: 'hours', label: 'Каждые N часов' },
- { value: 'daily', label: 'Ежедневно в указанное время' },
- { value: 'weekly', label: 'Еженедельно в указанный день' },
- { value: 'custom', label: 'Cron вручную (расширенный)' }
-];
-
-export const weekdayOptions = [
- { value: '0', label: 'Воскресенье' },
- { value: '1', label: 'Понедельник' },
- { value: '2', label: 'Вторник' },
- { value: '3', label: 'Среда' },
- { value: '4', label: 'Четверг' },
- { value: '5', label: 'Пятница' },
- { value: '6', label: 'Суббота' }
-] as const;
-
-export function defaultScheduleEditor(cron = '0 3 * * *'): ScheduleEditor {
- return cronToEditor(cron);
-}
-
-function splitCron(cron: string): [string, string, string, string, string] | null {
- const parts = cron.trim().split(/\s+/);
- if (parts.length !== 5) return null;
- return [parts[0], parts[1], parts[2], parts[3], parts[4]];
-}
-
-function clampInt(raw: string, min: number, max: number): number {
- const n = Math.floor(Number(String(raw).trim()));
- if (!Number.isFinite(n)) return min;
- return Math.min(max, Math.max(min, n));
-}
-
-function pad2(n: number): string {
- return String(n).padStart(2, '0');
-}
-
-/** Пытается разобрать cron в редактор; неизвестные выражения → custom. */
-export function cronToEditor(cron: string): ScheduleEditor {
- const base: ScheduleEditor = {
- mode: 'custom',
- intervalMinutes: '30',
- intervalHours: '2',
- minute: '0',
- hour: '3',
- weekday: '0',
- customCron: cron.trim() || '0 3 * * *'
- };
-
- const parts = splitCron(cron);
- if (!parts) return base;
-
- const [min, hour, dom, month, dow] = parts;
-
- const minEvery = min.match(/^\*\/(\d+)$/);
- if (minEvery && hour === '*' && dom === '*' && month === '*' && dow === '*') {
- return { ...base, mode: 'minutes', intervalMinutes: minEvery[1], customCron: cron.trim() };
- }
-
- const hourEvery = hour.match(/^\*\/(\d+)$/);
- if (hourEvery && !min.includes('*') && dom === '*' && month === '*' && dow === '*') {
- return {
- ...base,
- mode: 'hours',
- intervalHours: hourEvery[1],
- minute: min,
- customCron: cron.trim()
- };
- }
-
- if (!min.includes('*') && !hour.includes('*') && dom === '*' && month === '*' && dow === '*') {
- return { ...base, mode: 'daily', minute: min, hour, customCron: cron.trim() };
- }
-
- if (!min.includes('*') && !hour.includes('*') && dom === '*' && month === '*' && dow !== '*') {
- return { ...base, mode: 'weekly', minute: min, hour, weekday: dow, customCron: cron.trim() };
- }
-
- return base;
-}
-
-/** Собирает 5-field cron из редактора. */
-export function editorToCron(editor: ScheduleEditor): string {
- switch (editor.mode) {
- case 'minutes': {
- const n = clampInt(editor.intervalMinutes, 1, 59);
- return `*/${n} * * * *`;
- }
- case 'hours': {
- const n = clampInt(editor.intervalHours, 1, 23);
- const m = clampInt(editor.minute, 0, 59);
- return `${m} */${n} * * *`;
- }
- case 'daily': {
- const m = clampInt(editor.minute, 0, 59);
- const h = clampInt(editor.hour, 0, 23);
- return `${m} ${h} * * *`;
- }
- case 'weekly': {
- const m = clampInt(editor.minute, 0, 59);
- const h = clampInt(editor.hour, 0, 23);
- const d = clampInt(editor.weekday, 0, 6);
- return `${m} ${h} * * ${d}`;
- }
- case 'custom':
- return editor.customCron.trim() || '0 3 * * *';
- }
-}
-
-/** Человекочитаемое описание расписания для таблицы и подсказок. */
-export function describeCron(cron: string): string {
- const ed = cronToEditor(cron);
- switch (ed.mode) {
- case 'minutes': {
- const n = clampInt(ed.intervalMinutes, 1, 59);
- return `Каждые ${n} ${minutesLabel(n)} (UTC)`;
- }
- case 'hours': {
- const n = clampInt(ed.intervalHours, 1, 23);
- const m = clampInt(ed.minute, 0, 59);
- return `Каждые ${n} ${hoursLabel(n)}, в :${pad2(m)} (UTC)`;
- }
- case 'daily': {
- const h = clampInt(ed.hour, 0, 23);
- const m = clampInt(ed.minute, 0, 59);
- return `Ежедневно в ${pad2(h)}:${pad2(m)} UTC`;
- }
- case 'weekly': {
- const wd =
- weekdayOptions.find((w) => w.value === String(clampInt(ed.weekday, 0, 6)))?.label ??
- ed.weekday;
- const h = clampInt(ed.hour, 0, 23);
- const m = clampInt(ed.minute, 0, 59);
- return `Каждую ${wd.toLowerCase()} в ${pad2(h)}:${pad2(m)} UTC`;
- }
- case 'custom':
- return `Cron: ${cron.trim()}`;
- }
-}
-
-/** Синхронизирует form.schedule из редактора. */
-export function applyScheduleEditor(editor: ScheduleEditor): {
- editor: ScheduleEditor;
- cron: string;
-} {
- if (editor.mode === 'custom') {
- const cron = editor.customCron.trim() || '0 3 * * *';
- return { editor: { ...editor, customCron: cron }, cron };
- }
- const cron = editorToCron(editor);
- return { editor: { ...editor, customCron: cron }, cron };
-}
-
-function minutesLabel(n: number): string {
- if (n % 10 === 1 && n % 100 !== 11) return 'минуту';
- if (n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20)) return 'минуты';
- return 'минут';
-}
-
-function hoursLabel(n: number): string {
- if (n % 10 === 1 && n % 100 !== 11) return 'час';
- if (n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20)) return 'часа';
- return 'часов';
-}
diff --git a/web-legacy-svelte/src/lib/maintenance/policy.schema.ts b/web-legacy-svelte/src/lib/maintenance/policy.schema.ts
deleted file mode 100644
index 30ecb64..0000000
--- a/web-legacy-svelte/src/lib/maintenance/policy.schema.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import { z } from 'zod';
-
-export const vacuumStrategies = ['none', 'vacuum', 'analyze', 'vacuum_analyze', 'reindex'] as const;
-
-export type VacuumStrategy = (typeof vacuumStrategies)[number];
-
-export const maintenancePolicySchema = z.object({
- name: z.string().trim().min(1, 'Укажите название'),
- table_name: z.string().trim().min(1, 'Укажите таблицу'),
- condition: z
- .string()
- .trim()
- .min(1, 'Укажите условие')
- .refine((v) => !/[;]|--|\/\*/.test(v), 'Недопустимые символы в condition'),
- retention_period_sec: z.string().optional(),
- max_rows: z.string().optional(),
- vacuum_strategy: z.enum(vacuumStrategies),
- schedule: z.string().trim().min(1, 'Укажите cron (UTC)'),
- enabled: z.boolean(),
- dry_run_enabled: z.boolean()
-});
-
-export type MaintenancePolicyForm = z.infer;
-
-export function emptyMaintenancePolicyForm(): MaintenancePolicyForm {
- return {
- name: '',
- table_name: '',
- condition: 'true',
- retention_period_sec: '',
- max_rows: '10000',
- vacuum_strategy: 'none',
- schedule: '0 3 * * *',
- enabled: true,
- dry_run_enabled: true
- };
-}
-
-export function parseOptionalInt(raw: string | undefined): number | undefined {
- const v = String(raw ?? '').trim();
- if (!v) return undefined;
- const n = Number(v);
- return Number.isFinite(n) && n > 0 ? Math.floor(n) : undefined;
-}
-
-export function formToPayload(form: MaintenancePolicyForm) {
- return {
- name: form.name.trim(),
- table_name: form.table_name.trim(),
- condition: form.condition.trim() || 'true',
- retention_period_sec: parseOptionalInt(form.retention_period_sec),
- max_rows: parseOptionalInt(form.max_rows),
- vacuum_strategy: form.vacuum_strategy,
- schedule: form.schedule.trim(),
- enabled: form.enabled,
- dry_run_enabled: form.dry_run_enabled
- };
-}
diff --git a/web-legacy-svelte/src/lib/modules/display.ts b/web-legacy-svelte/src/lib/modules/display.ts
deleted file mode 100644
index 4dca283..0000000
--- a/web-legacy-svelte/src/lib/modules/display.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import type { ModuleRow } from '$lib/api/types.js';
-
-/** Форматирование ISO-даты для таблиц модулей и расписания. */
-export function formatDateTime(value: string | null | undefined): string {
- if (typeof value !== 'string' || value.trim().length === 0) return '—';
- const parsed = new Date(value);
- if (Number.isNaN(parsed.getTime())) return '—';
- return parsed.toLocaleString('ru-RU');
-}
-
-/** Подпись интервала refresh: секунды, cron или комбинация. */
-export function moduleIntervalLabel(moduleRow: ModuleRow): string {
- const cron = typeof moduleRow.cron_expr === 'string' ? moduleRow.cron_expr.trim() : '';
- const raw = moduleRow.refresh_interval_sec as unknown;
- const interval =
- typeof raw === 'number'
- ? raw
- : typeof raw === 'string' && raw.trim().length > 0
- ? Number(raw)
- : null;
- const intervalLabel = interval !== null && Number.isFinite(interval) ? `${interval}с` : '';
- if (cron && intervalLabel) return `${intervalLabel} (${cron})`;
- if (cron) return cron;
- if (intervalLabel) return intervalLabel;
- return '—';
-}
-
-export function moduleTypeBadgeVariant(
- type: string
-): 'default' | 'secondary' | 'outline' | 'destructive' {
- switch (type) {
- case 'AS_PREFIXES':
- return 'default';
- case 'CDN_CIDRS':
- return 'secondary';
- case 'DOMAINS':
- return 'outline';
- case 'IP_RANGES':
- return 'outline';
- default:
- return 'outline';
- }
-}
diff --git a/web-legacy-svelte/src/lib/monitoring/postgres.ts b/web-legacy-svelte/src/lib/monitoring/postgres.ts
deleted file mode 100644
index 1518a93..0000000
--- a/web-legacy-svelte/src/lib/monitoring/postgres.ts
+++ /dev/null
@@ -1,118 +0,0 @@
-/** Types and helpers for PostgreSQL monitoring API. */
-
-export const POSTGRES_POLL_MS = 20_000;
-export const POSTGRES_SLOW_POLL_MS = 60_000;
-
-export type PostgresOverview = {
- collected_at: string;
- connections: {
- active: number;
- idle: number;
- total: number;
- max_connections: number;
- };
- database: {
- backends: number;
- xact_commit: number;
- xact_rollback: number;
- deadlocks: number;
- blks_hit: number;
- blks_read: number;
- cache_hit_pct: number;
- };
- database_size_bytes: number;
- memory_settings: {
- shared_buffers: string;
- work_mem: string;
- effective_cache_size: string;
- };
- replication: Array<{
- client_addr?: string;
- state: string;
- sync_state?: string;
- lag_ms?: number;
- }>;
- pg_stat_statements_enabled: boolean;
-};
-
-export type PostgresQueryRow = {
- queryid?: number;
- query: string;
- calls: number;
- total_exec_ms: number;
- mean_exec_ms: number;
- rows: number;
-};
-
-export type PostgresQueriesResponse = {
- collected_at: string;
- source: string;
- items: PostgresQueryRow[];
- statements_available?: boolean;
- statements_hint?: string;
-};
-
-export type PostgresLockRow = {
- locktype: string;
- mode: string;
- granted: boolean;
- pid: number;
- usename?: string;
- state?: string;
- query?: string;
- blocked: boolean;
-};
-
-export type PostgresTableRow = {
- relname: string;
- total_bytes: number;
- idx_scan: number;
- seq_scan: number;
- n_dead_tup: number;
- bloat_ratio?: number;
- last_autovacuum?: string;
-};
-
-export type PostgresRecommendation = {
- severity: string;
- code: string;
- title: string;
- detail: string;
- refs?: string[];
-};
-
-export type PostgresRecommendationsResponse = {
- collected_at: string;
- items: PostgresRecommendation[];
-};
-
-export type PostgresMaintLog = {
- id: string;
- kind: string;
- target_table?: string;
- dry_run: boolean;
- status: string;
- error?: string;
- created_at: string;
-};
-
-export type CorrelationResponse = {
- window_minutes: number;
- points: Array<{
- timestamp: string;
- pipeline_refresh_p99_ms?: number;
- cache_hit_pct?: number;
- }>;
-};
-
-export function formatBytes(n: number): string {
- if (n >= 1 << 30) return `${(n / (1 << 30)).toFixed(1)} GiB`;
- if (n >= 1 << 20) return `${(n / (1 << 20)).toFixed(1)} MiB`;
- if (n >= 1 << 10) return `${(n / (1 << 10)).toFixed(1)} KiB`;
- return `${n} B`;
-}
-
-export function connUsagePct(ov: PostgresOverview | null): number {
- if (!ov?.connections.max_connections) return 0;
- return Math.min(100, (ov.connections.total / ov.connections.max_connections) * 100);
-}
diff --git a/web-legacy-svelte/src/lib/monitoring/status.ts b/web-legacy-svelte/src/lib/monitoring/status.ts
deleted file mode 100644
index cc7b025..0000000
--- a/web-legacy-svelte/src/lib/monitoring/status.ts
+++ /dev/null
@@ -1,169 +0,0 @@
-/** Pure-helpers для страницы /monitoring. */
-
-export type OverallStatus = 'ok' | 'warn' | 'error' | 'unknown';
-
-export type JobsKpi = { running: number; failed: number; total: number };
-
-export type ReadyStatus = { status: string; checks?: Record };
-
-export type HealthStatus = { ok: boolean; status?: string; error?: string };
-
-export type CheckBadge = {
- label: string;
- variant: 'default' | 'secondary' | 'destructive' | 'outline';
- class?: string;
- hint?: string;
-};
-
-export function toErrorMessage(error: unknown): string {
- if (error instanceof Error && error.message.trim() !== '') return error.message;
- return 'Ошибка запроса';
-}
-
-export function summarizeJobsStatuses(items: Array<{ status?: string }>): JobsKpi {
- let running = 0;
- let failed = 0;
- for (const row of items) {
- const status = String(row.status ?? '').toLowerCase();
- if (status === 'queued' || status === 'running' || status === 'cancel_requested') {
- running += 1;
- }
- if (status === 'failed' || status === 'error' || status === 'canceled') {
- failed += 1;
- }
- }
- return { running, failed, total: items.length };
-}
-
-export function deriveOverallStatus(input: {
- health: HealthStatus | null;
- ready: ReadyStatus | null;
- jobs: JobsKpi | null;
- birdConfigured: boolean;
- birdHealthy: boolean | null;
-}): OverallStatus {
- const { health, ready, jobs, birdConfigured, birdHealthy } = input;
- if (health === null && ready === null && jobs === null) return 'unknown';
- if (!health?.ok) return 'error';
- if (ready !== null && ready.status !== 'ready') return 'error';
- if (jobs !== null && jobs.failed > 0) return 'warn';
- if (birdConfigured && birdHealthy === false) return 'warn';
- return 'ok';
-}
-
-export function overallStatusLabel(status: OverallStatus): string {
- switch (status) {
- case 'ok':
- return 'В норме';
- case 'warn':
- return 'Внимание';
- case 'error':
- return 'Ошибка';
- default:
- return 'Нет данных';
- }
-}
-
-export function overallStatusHint(
- status: OverallStatus,
- input: {
- healthOk: boolean | undefined;
- jobsFailed: number;
- }
-): string {
- if (status === 'unknown') return 'Нет данных. Запустите обновление.';
- if (status === 'error') {
- if (!input.healthOk) return 'Проверьте доступность API и логи сервиса.';
- return 'Readiness не в норме: проверьте postgres/store/jobs.';
- }
- if (status === 'warn') {
- if (input.jobsFailed > 0) return 'Есть ошибки в задачах: откройте операции и последние jobs.';
- return 'Проверьте BGP-сессии и вывод birdc.';
- }
- return 'Критичных отклонений не обнаружено.';
-}
-
-export function overallBadgeVariant(status: OverallStatus): CheckBadge['variant'] {
- if (status === 'ok') return 'default';
- if (status === 'warn') return 'secondary';
- if (status === 'error') return 'destructive';
- return 'outline';
-}
-
-export function overallBadgeClass(status: OverallStatus): string | undefined {
- if (status === 'ok') return 'border-success/30 bg-success/15 text-success';
- if (status === 'warn') return 'border-warning/30 bg-warning/15 text-warning';
- return undefined;
-}
-
-/** Нормализация значений ready.checks и health/readiness в badge. */
-export function checkStatusBadge(value: unknown): CheckBadge {
- const raw = String(value ?? '').trim();
- const lower = raw.toLowerCase();
-
- if (lower === 'ok' || lower === 'ready' || lower === 'true' || lower === 'up') {
- return {
- label: 'OK',
- variant: 'default',
- class: 'border-success/30 bg-success/15 text-success'
- };
- }
- if (lower === 'memory') {
- return {
- label: 'In-memory',
- variant: 'secondary',
- hint: 'Очередь задач в памяти процесса, не shared между воркерами.'
- };
- }
- if (
- lower === 'failed' ||
- lower === 'false' ||
- lower === 'error' ||
- lower === 'down' ||
- lower === 'unavailable'
- ) {
- return { label: 'Ошибка', variant: 'destructive' };
- }
- if (raw === '') {
- return { label: '—', variant: 'outline' };
- }
- return { label: raw, variant: 'outline' };
-}
-
-/** Человекочитаемое имя проверки readiness. */
-export function checkDisplayName(key: string): string {
- switch (key) {
- case 'postgres':
- return 'PostgreSQL';
- case 'store':
- return 'Хранилище';
- case 'jobs':
- return 'Очередь задач';
- default:
- return key;
- }
-}
-
-export function livenessBadge(health: HealthStatus | null): CheckBadge {
- if (health === null) return { label: '—', variant: 'outline' };
- if (health.ok) {
- return {
- label: 'В норме',
- variant: 'default',
- class: 'border-success/30 bg-success/15 text-success'
- };
- }
- return { label: 'Недоступен', variant: 'destructive' };
-}
-
-export function readinessBadge(ready: ReadyStatus | null): CheckBadge {
- if (ready === null) return { label: '—', variant: 'outline' };
- if (ready.status === 'ready') {
- return {
- label: 'Готов',
- variant: 'default',
- class: 'border-success/30 bg-success/15 text-success'
- };
- }
- return { label: ready.status || 'Не готов', variant: 'destructive' };
-}
diff --git a/web-legacy-svelte/src/lib/network/network-metrics.ts b/web-legacy-svelte/src/lib/network/network-metrics.ts
deleted file mode 100644
index ad014da..0000000
--- a/web-legacy-svelte/src/lib/network/network-metrics.ts
+++ /dev/null
@@ -1,295 +0,0 @@
-import type { BirdStatus, PeerRow, SpeakerRow } from '$lib/api/types.js';
-
-export type NetworkOverallStatus = 'ok' | 'warn' | 'error';
-
-export type NetworkMetrics = {
- peersTotal: number;
- peersEnabled: number;
- peersEstablished: number;
- peersMismatch: number;
- speakersTotal: number;
- speakersOnline: number;
- speakersRemote: number;
- speakersRemoteOnline: number;
- speakersDrift: number;
- pollErrors: number;
- hasLiveData: boolean;
-};
-
-export type SpeakerStatusBadge = {
- label: string;
- variant: 'default' | 'secondary' | 'destructive' | 'outline';
-};
-
-export type NetworkIssue = {
- id: string;
- message: string;
- severity: 'warn' | 'error';
-};
-
-function isRemoteSpeaker(s: SpeakerRow): boolean {
- const role = (s.role ?? '').toLowerCase();
- return role !== 'master' && Boolean(s.agent_domain?.trim());
-}
-
-export function speakerHasDrift(s: SpeakerRow): boolean {
- const pub = s.published_revision_id?.trim();
- if (!pub) return false;
- return (s.last_applied_revision_id ?? '') !== pub;
-}
-
-export function speakerIsOnline(s: SpeakerRow): boolean {
- if (s.live) {
- return s.live.agent_ok === true && s.live.bgp_poll_ok !== false;
- }
- if (s.sync_status === 'synced') return true;
- if (s.sync_status === 'error' || s.last_dispatch_error) return false;
- return s.dispatch_status === 'ok';
-}
-
-export function speakerDisplayStatus(s: SpeakerRow): SpeakerStatusBadge {
- if (s.live) {
- if (s.live.agent_ok === true && s.live.bgp_poll_ok !== false) {
- return { label: 'Online', variant: 'default' };
- }
- if (s.live.bgp_poll_error || s.live.agent_error) {
- return { label: 'Offline', variant: 'destructive' };
- }
- return { label: 'Degraded', variant: 'secondary' };
- }
- if (s.sync_status === 'synced') return { label: 'Connected', variant: 'default' };
- if (s.sync_status === 'error' || s.last_dispatch_error) {
- return { label: 'Offline', variant: 'destructive' };
- }
- if (s.dispatch_status === 'ok') return { label: 'Synced', variant: 'outline' };
- return { label: 'Unknown', variant: 'outline' };
-}
-
-export function speakerLabel(s: SpeakerRow): string {
- return s.live?.label ?? s.agent_domain ?? s.endpoint ?? s.id;
-}
-
-export function speakerBgpText(s: SpeakerRow): string {
- if (s.live) {
- return `${s.live.bgp_established ?? 0}/${s.live.bgp_sessions_total ?? 0}`;
- }
- return '—';
-}
-
-export function peersForSpeaker(peers: PeerRow[], speakerId: string): PeerRow[] {
- return peers.filter(
- (p) =>
- p.bgp_speaker_id === speakerId || p.bgp_speaker_id === null || p.bgp_speaker_id === undefined
- );
-}
-
-export function aggregateNetworkMetrics(
- peers: PeerRow[],
- speakers: SpeakerRow[],
- bird?: BirdStatus | null
-): NetworkMetrics {
- const enabledPeers = peers.filter((p) => p.enabled !== false);
- const established = enabledPeers.filter((p) => p.session_state === 'Established').length;
- const mismatch = peers.filter((p) => p.session_mismatch).length;
- const remoteSpeakers = speakers.filter(isRemoteSpeaker);
- const online = speakers.filter(speakerIsOnline).length;
- const remoteOnline = remoteSpeakers.filter(speakerIsOnline).length;
- const drift = speakers.filter(speakerHasDrift).length;
- const pollErrors = speakers.filter(
- (s) => s.live?.bgp_poll_error || (s.live && s.live.agent_ok === false)
- ).length;
- const hasLiveData =
- speakers.some((s) => s.live != null) || peers.some((p) => p.session_on_speakers);
-
- void bird;
-
- return {
- peersTotal: peers.length,
- peersEnabled: enabledPeers.length,
- peersEstablished: established,
- peersMismatch: mismatch,
- speakersTotal: speakers.length,
- speakersOnline: online,
- speakersRemote: remoteSpeakers.length,
- speakersRemoteOnline: remoteOnline,
- speakersDrift: drift,
- pollErrors,
- hasLiveData
- };
-}
-
-export function deriveNetworkOverallStatus(metrics: NetworkMetrics): NetworkOverallStatus {
- if (!metrics.hasLiveData && metrics.speakersTotal === 0 && metrics.peersTotal === 0) {
- return 'ok';
- }
-
- const enabledNotEstablished =
- metrics.peersEnabled > 0 ? metrics.peersEnabled - metrics.peersEstablished : 0;
- const majorityPeersDown =
- metrics.peersEnabled > 0 && enabledNotEstablished / metrics.peersEnabled > 0.5;
-
- if ((metrics.speakersRemote > 0 && metrics.speakersRemoteOnline === 0) || majorityPeersDown) {
- return 'error';
- }
-
- if (
- metrics.pollErrors > 0 ||
- metrics.peersMismatch > 0 ||
- metrics.speakersDrift > 0 ||
- metrics.speakersOnline < metrics.speakersTotal
- ) {
- return 'warn';
- }
-
- return 'ok';
-}
-
-export function networkOverallStatusLabel(status: NetworkOverallStatus): string {
- switch (status) {
- case 'ok':
- return 'В норме';
- case 'warn':
- return 'Требует внимания';
- case 'error':
- return 'Проблема';
- }
-}
-
-export function networkOverallStatusHint(
- status: NetworkOverallStatus,
- metrics: NetworkMetrics
-): string {
- switch (status) {
- case 'ok':
- return metrics.hasLiveData
- ? `${metrics.peersEstablished} Established, ${metrics.speakersOnline}/${metrics.speakersTotal} спикеров online`
- : 'Сеть настроена; обновите для live-статуса';
- case 'warn':
- return 'Есть drift, mismatch или недоступные ноды — проверьте детали';
- case 'error':
- return 'Критичная деградация BGP или все remote-ноды недоступны';
- }
-}
-
-export function collectNetworkIssues(
- peers: PeerRow[],
- speakers: SpeakerRow[],
- limit = 3
-): NetworkIssue[] {
- const issues: NetworkIssue[] = [];
-
- for (const s of speakers) {
- if (!speakerIsOnline(s)) {
- issues.push({
- id: `speaker-offline-${s.id}`,
- message: `Нода offline: ${speakerLabel(s)}`,
- severity: 'error'
- });
- } else if (speakerHasDrift(s)) {
- issues.push({
- id: `speaker-drift-${s.id}`,
- message: `Drift ревизии: ${speakerLabel(s)}`,
- severity: 'warn'
- });
- } else if (s.live?.bgp_poll_error) {
- issues.push({
- id: `speaker-poll-${s.id}`,
- message: `Ошибка BGP-опроса: ${speakerLabel(s)}`,
- severity: 'warn'
- });
- } else if (s.last_dispatch_error) {
- const err = formatSpeakerError(s.last_dispatch_error);
- issues.push({
- id: `speaker-dispatch-${s.id}`,
- message: `Dispatch: ${speakerLabel(s)}${err ? ` — ${err.detail.slice(0, 80)}` : ''}`,
- severity: 'warn'
- });
- }
- }
-
- for (const p of peers) {
- if (p.session_mismatch) {
- const name = p.name?.trim() || p.neighbor;
- issues.push({
- id: `peer-mismatch-${p.id}`,
- message: `Mismatch сессии: ${name}`,
- severity: 'warn'
- });
- }
- }
-
- return issues.slice(0, limit);
-}
-
-export const NETWORK_AUTO_REFRESH_KEY = 'evobgp.network.autoRefresh';
-export const NETWORK_AUTO_REFRESH_MS = 15_000;
-
-export function readNetworkAutoRefresh(): boolean {
- if (typeof localStorage === 'undefined') return false;
- return localStorage.getItem(NETWORK_AUTO_REFRESH_KEY) === '1';
-}
-
-export function writeNetworkAutoRefresh(enabled: boolean): void {
- if (typeof localStorage === 'undefined') return;
- localStorage.setItem(NETWORK_AUTO_REFRESH_KEY, enabled ? '1' : '0');
-}
-
-export type FormattedSpeakerError = {
- title: string;
- detail: string;
-};
-
-/** Humanize stored dispatch/agent errors (avoid raw JSON in UI). */
-export function formatSpeakerError(raw: string | null | undefined): FormattedSpeakerError | null {
- if (!raw?.trim()) return null;
- const text = raw.trim();
-
- const jsonMatch = text.match(/\{[\s\S]*\}/);
- if (jsonMatch) {
- try {
- const obj = JSON.parse(jsonMatch[0]) as {
- detail?: string;
- title?: string;
- status?: number;
- };
- const detail = String(obj.detail ?? text);
- if (/403/.test(detail) && /bundle/i.test(detail)) {
- return {
- title: 'Dispatch: доступ к бандлу',
- detail:
- 'Нода не смогла скачать бандл с CP (403). Проверьте node API-ключ (роль node) и EVOBGP_NODE_TOKEN на реплике — см. docs/access.md.'
- };
- }
- const httpPrefix = text.match(/^HTTP \d+:\s*/)?.[0] ?? '';
- return {
- title: obj.title && obj.title !== 'Bad Gateway' ? obj.title : 'Ошибка dispatch',
- detail: httpPrefix ? `${httpPrefix.trim()} ${detail}`.trim() : detail
- };
- } catch {
- /* fall through */
- }
- }
-
- if (/^HTTP \d+:/.test(text)) {
- return { title: 'Ошибка HTTP', detail: text };
- }
- return { title: 'Ошибка', detail: text };
-}
-
-export function speakerDispatchError(s: SpeakerRow): FormattedSpeakerError | null {
- const liveRev = s.live?.agent_last_applied_revision_id?.trim();
- const pub = s.published_revision_id?.trim();
- // CP meta can keep a stale dispatch error after a later successful agent sync.
- if (s.live?.agent_ok && liveRev && pub && liveRev === pub) {
- return null;
- }
- return formatSpeakerError(s.last_dispatch_error);
-}
-
-export function speakerLiveAgentError(s: SpeakerRow): FormattedSpeakerError | null {
- return formatSpeakerError(s.live?.agent_error);
-}
-
-export function speakerLiveBgpError(s: SpeakerRow): FormattedSpeakerError | null {
- return formatSpeakerError(s.live?.bgp_poll_error);
-}
diff --git a/web-legacy-svelte/src/lib/operations/job-kind-label.ts b/web-legacy-svelte/src/lib/operations/job-kind-label.ts
deleted file mode 100644
index 3320b88..0000000
--- a/web-legacy-svelte/src/lib/operations/job-kind-label.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import type { JobRow } from '$lib/api/types.js';
-
-function metaString(meta: Record | undefined, key: string): string | null {
- if (!meta || typeof meta !== 'object') return null;
- const v = meta[key];
- return typeof v === 'string' && v.trim().length > 0 ? v : null;
-}
-
-/** Заголовок задачи для UI (русский), без технического `kind`. */
-export function jobKindTitle(job: JobRow, moduleNameById?: ReadonlyMap): string {
- switch (job.kind) {
- case 'module_refresh': {
- const mid = metaString(job.meta as Record | undefined, 'module_id');
- const name = mid ? moduleNameById?.get(mid) : undefined;
- if (name) return `Обновление модуля «${name}»`;
- return 'Обновление модуля';
- }
- case 'deploy_apply':
- return 'Применение конфигурации';
- case 'peer_reconcile':
- return 'Обновление BGP пиров';
- case 'revision_rollback':
- return 'Откат ревизии';
- case 'bird_reload':
- return 'Перезагрузка BIRD';
- case 'maintenance_policy_run':
- return 'Обслуживание PostgreSQL (политика)';
- default:
- return job.kind;
- }
-}
-
-/** Вторая строка под заголовком: `module_id`, если имя модуля ещё не известно. */
-export function jobKindSubtitle(
- job: JobRow,
- moduleNameById?: ReadonlyMap
-): string | null {
- if (job.kind !== 'module_refresh') return null;
- const mid = metaString(job.meta as Record | undefined, 'module_id');
- if (!mid) return null;
- if (moduleNameById?.has(mid)) return null;
- return mid;
-}
diff --git a/web-legacy-svelte/src/lib/runtime-logs/runtime-logs-api.ts b/web-legacy-svelte/src/lib/runtime-logs/runtime-logs-api.ts
deleted file mode 100644
index ede1482..0000000
--- a/web-legacy-svelte/src/lib/runtime-logs/runtime-logs-api.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-import { apiJSON, apiMutate, ApiError } from '$lib/api/client.js';
-
-export type RuntimeLogFile = {
- name: string;
- size_bytes: number;
- modified_at: string;
-};
-
-export type RuntimeLogTail = {
- filename: string;
- content: string;
- truncated: boolean;
- lines_returned: number;
-};
-
-export type RuntimeLogCleanupMode = 'truncate' | 'delete';
-
-export type RuntimeLogCleanupResult = {
- audit_id: string;
- filename: string;
- action: RuntimeLogCleanupMode;
- size_before: number;
- size_after?: number | null;
-};
-
-export type RuntimeLogCleanupAudit = {
- id: string;
- tenant_id: string;
- actor_prefix: string;
- filename: string;
- action: RuntimeLogCleanupMode;
- size_before: number;
- size_after?: number | null;
- detail?: Record;
- created_at: string;
-};
-
-export type RuntimeLogCleanupAuditList = {
- items: RuntimeLogCleanupAudit[];
- next_cursor?: string;
- has_more?: boolean;
-};
-
-/** True when FS API is disabled (not evobgp-all or no volume). */
-export function isRuntimeLogsUnavailable(err: unknown): boolean {
- if (!(err instanceof ApiError) || err.status !== 503) return false;
- const detail = err.problem?.detail ?? err.message;
- return detail === 'runtime_logs_unavailable' || detail.includes('runtime_logs_unavailable');
-}
-
-export async function listRuntimeLogFiles(): Promise {
- const r = await apiJSON<{ items: RuntimeLogFile[] }>('/v1/runtime-logs/files');
- return r.items ?? [];
-}
-
-export async function getRuntimeLogTail(
- filename: string,
- opts?: { lines?: number; grep?: string }
-): Promise {
- const q = new URLSearchParams();
- q.set('lines', String(opts?.lines ?? 200));
- if (opts?.grep?.trim()) q.set('grep', opts.grep.trim());
- return apiJSON(
- `/v1/runtime-logs/files/${encodeURIComponent(filename)}?${q.toString()}`
- );
-}
-
-export async function cleanupRuntimeLogFile(
- filename: string,
- mode: RuntimeLogCleanupMode = 'truncate'
-): Promise {
- const q = new URLSearchParams({ mode });
- return apiMutate(
- `/v1/runtime-logs/files/${encodeURIComponent(filename)}?${q.toString()}`,
- 'DELETE',
- undefined,
- { idempotent: false }
- );
-}
-
-export async function listRuntimeLogCleanupAudit(opts?: {
- cursor?: string;
- limit?: number;
-}): Promise {
- const q = new URLSearchParams();
- if (opts?.limit != null) q.set('limit', String(opts.limit));
- if (opts?.cursor) q.set('cursor', opts.cursor);
- const suffix = q.toString() ? `?${q.toString()}` : '';
- return apiJSON(`/v1/runtime-logs/cleanup-audit${suffix}`);
-}
diff --git a/web-legacy-svelte/src/lib/runtime-logs/runtime-logs-auto-api.ts b/web-legacy-svelte/src/lib/runtime-logs/runtime-logs-auto-api.ts
deleted file mode 100644
index d96aa44..0000000
--- a/web-legacy-svelte/src/lib/runtime-logs/runtime-logs-auto-api.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { apiJSON, apiMutate } from '$lib/api/client.js';
-
-export type RuntimeLogAutoEstimateItem = {
- filename: string;
- size_bytes: number;
- would_cleanup: boolean;
- skip_reason?: string;
-};
-
-export type RuntimeLogAutoEstimate = {
- policy?: {
- enabled?: boolean;
- max_file_bytes?: number;
- schedule?: string;
- mode?: 'truncate' | 'delete';
- };
- items?: RuntimeLogAutoEstimateItem[];
- would_count?: number;
-};
-
-export type RuntimeLogAutoRunResult = {
- dry_run?: boolean;
- trigger?: string;
- cleaned_count?: number;
- skipped_count?: number;
- cleaned?: unknown[];
- skipped?: unknown[];
-};
-
-export async function fetchRuntimeLogAutoEstimate(): Promise {
- return apiJSON('/v1/runtime-logs/auto-estimate');
-}
-
-export async function runRuntimeLogAutoCleanup(dryRun = false): Promise {
- const q = dryRun ? '?dry_run=true' : '';
- return apiMutate(`/v1/runtime-logs/auto-run${q}`, 'POST');
-}
diff --git a/web-legacy-svelte/src/lib/settings/bird-settings.schema.ts b/web-legacy-svelte/src/lib/settings/bird-settings.schema.ts
deleted file mode 100644
index f7599e5..0000000
--- a/web-legacy-svelte/src/lib/settings/bird-settings.schema.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { z } from 'zod';
-import { optionalIPv4, optionalIPv6 } from './ip-validation.js';
-
-export const birdSettingsSchema = z.object({
- bird_router_id: optionalIPv4('router id'),
- bird_local_ipv4: optionalIPv4('local IPv4'),
- bird_local_ipv6: optionalIPv6('local IPv6'),
- bird_local_asn: z.string().refine((v) => v.trim() === '' || /^[1-9]\d*$/.test(v.trim()), {
- message: 'ASN должен быть целым числом больше 0'
- }),
- bird_bgp_source_ipv4: optionalIPv4('BGP source IPv4'),
- bird_bgp_source_ipv6: optionalIPv6('BGP source IPv6')
-});
-
-export type BirdSettingsForm = z.infer;
-
-export const emptyBirdSettingsForm = (): BirdSettingsForm => ({
- bird_router_id: '',
- bird_local_ipv4: '',
- bird_local_ipv6: '',
- bird_local_asn: '',
- bird_bgp_source_ipv4: '',
- bird_bgp_source_ipv6: ''
-});
diff --git a/web-legacy-svelte/src/lib/settings/ip-validation.ts b/web-legacy-svelte/src/lib/settings/ip-validation.ts
deleted file mode 100644
index 82e2be3..0000000
--- a/web-legacy-svelte/src/lib/settings/ip-validation.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import { z } from 'zod';
-
-function isValidIPv4(value: string): boolean {
- const parts = value.split('.');
- if (parts.length !== 4) return false;
- for (const part of parts) {
- if (!/^\d{1,3}$/.test(part)) return false;
- if (part.length > 1 && part.startsWith('0')) return false;
- const n = Number(part);
- if (!Number.isInteger(n) || n < 0 || n > 255) return false;
- }
- return true;
-}
-
-function isValidIPv6(value: string): boolean {
- if (!/^[0-9A-Fa-f:.]+$/.test(value)) return false;
- if ((value.match(/::/g) ?? []).length > 1) return false;
- const hasCompression = value.includes('::');
- const [leftRaw, rightRaw = ''] = value.split('::');
- const left = leftRaw === '' ? [] : leftRaw.split(':');
- const right = rightRaw === '' ? [] : rightRaw.split(':');
- if (left.some((part) => part === '') || right.some((part) => part === '')) return false;
- let segments = [...left, ...right];
- let ipv4TailSegments = 0;
- const lastSegment = segments.at(-1);
- if (lastSegment && lastSegment.includes('.')) {
- if (!isValidIPv4(lastSegment)) return false;
- segments = segments.slice(0, -1);
- ipv4TailSegments = 2;
- }
- for (const segment of segments) {
- if (!/^[0-9A-Fa-f]{1,4}$/.test(segment)) return false;
- }
- const totalSegments = segments.length + ipv4TailSegments;
- if (hasCompression) return totalSegments < 8;
- return totalSegments === 8;
-}
-
-export const optionalIPv4 = (label: string) =>
- z.string().refine((v) => v.trim() === '' || isValidIPv4(v.trim()), {
- message: `Введите корректный IPv4 адрес (${label})`
- });
-
-export const optionalIPv6 = (label: string) =>
- z.string().refine((v) => v.trim() === '' || isValidIPv6(v.trim()), {
- message: `Введите корректный IPv6 адрес (${label})`
- });
diff --git a/web-legacy-svelte/src/lib/settings/revision-prune-api.ts b/web-legacy-svelte/src/lib/settings/revision-prune-api.ts
deleted file mode 100644
index ba0ca26..0000000
--- a/web-legacy-svelte/src/lib/settings/revision-prune-api.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { apiJSON, apiMutate } from '$lib/api/client.js';
-
-export type RevisionPruneEstimate = {
- retention_minutes: number;
- cutoff_at: string;
- revision_count: number;
- prefix_row_count: number;
- orphan_snapshot_count: number;
- bytes_estimate: number;
-};
-
-export type RevisionPruneResult = {
- deleted_revisions: number;
- deleted_prefix_snapshots: number;
- deleted_prefix_rows: number;
- bytes_estimate: number;
-};
-
-export async function fetchRevisionPruneEstimate(
- retentionMinutes: number
-): Promise {
- const q = new URLSearchParams({ retention_minutes: String(retentionMinutes) });
- return apiJSON(`/v1/revisions/prune-estimate?${q}`);
-}
-
-export async function pruneRevisionsNow(retentionMinutes: number): Promise {
- return apiMutate('/v1/revisions/prune', 'POST', {
- retention_minutes: retentionMinutes
- });
-}
diff --git a/web-legacy-svelte/src/lib/settings/revision-settings.schema.ts b/web-legacy-svelte/src/lib/settings/revision-settings.schema.ts
deleted file mode 100644
index b2d0e6c..0000000
--- a/web-legacy-svelte/src/lib/settings/revision-settings.schema.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { z } from 'zod';
-
-/** HTML type=number binds number; API/store may return number — normalize to string for validation. */
-function retentionMinutesInput(val: unknown): string {
- if (val === undefined || val === null) return '';
- if (typeof val === 'number') {
- if (!Number.isFinite(val)) return '';
- return String(Math.trunc(val));
- }
- return String(val);
-}
-
-const revisionRetentionMinutes = z.preprocess(
- retentionMinutesInput,
- z.string().refine(
- (v) => {
- const s = v.trim();
- if (s === '') return true;
- const ttl = Number(s);
- return /^\d+$/.test(s) && Number.isInteger(ttl) && ttl >= 15 && ttl <= 43200;
- },
- { message: 'TTL ревизий должен быть целым числом от 15 до 43200 минут' }
- )
-);
-
-export const revisionSettingsSchema = z.object({
- revision_retention_minutes: revisionRetentionMinutes
-});
-
-export type RevisionSettingsForm = z.infer;
-
-export const emptyRevisionSettingsForm = (): RevisionSettingsForm => ({
- revision_retention_minutes: ''
-});
diff --git a/web-legacy-svelte/src/lib/settings/runtime-logs-settings.schema.ts b/web-legacy-svelte/src/lib/settings/runtime-logs-settings.schema.ts
deleted file mode 100644
index 2c4c6ac..0000000
--- a/web-legacy-svelte/src/lib/settings/runtime-logs-settings.schema.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import { z } from 'zod';
-
-function boolInput(val: unknown): string {
- if (val === undefined || val === null) return 'false';
- if (typeof val === 'boolean') return val ? 'true' : 'false';
- if (typeof val === 'number') return val !== 0 ? 'true' : 'false';
- return String(val).trim().toLowerCase() === 'true' || String(val).trim() === '1'
- ? 'true'
- : 'false';
-}
-
-function stringInput(val: unknown): string {
- if (val === undefined || val === null) return '';
- return String(val);
-}
-
-const runtimeLogsAutoEnabled = z.preprocess(boolInput, z.enum(['true', 'false']));
-
-const runtimeLogsMaxFileMb = z.preprocess(
- stringInput,
- z.string().refine(
- (v) => {
- const s = v.trim();
- if (s === '') return true;
- const n = Number(s);
- return /^\d+$/.test(s) && Number.isInteger(n) && n >= 1 && n <= 512;
- },
- { message: 'Порог должен быть целым числом от 1 до 512 MiB' }
- )
-);
-
-const runtimeLogsAutoSchedule = z.preprocess(
- stringInput,
- z.string().refine(
- (v) => {
- const s = v.trim();
- if (s === '') return true;
- const parts = s.split(/\s+/);
- return parts.length === 5;
- },
- { message: 'Cron: 5 полей (минута час день месяц день_недели), UTC' }
- )
-);
-
-const runtimeLogsAutoMode = z.preprocess(
- stringInput,
- z.enum(['truncate', 'delete', '']).refine((v) => v === '' || v === 'truncate' || v === 'delete', {
- message: 'Режим: truncate или delete'
- })
-);
-
-export const runtimeLogsSettingsSchema = z.object({
- runtime_logs_auto_enabled: runtimeLogsAutoEnabled,
- runtime_logs_max_file_mb: runtimeLogsMaxFileMb,
- runtime_logs_auto_schedule: runtimeLogsAutoSchedule,
- runtime_logs_auto_mode: runtimeLogsAutoMode
-});
-
-export type RuntimeLogsSettingsForm = z.infer;
-
-export const emptyRuntimeLogsSettingsForm = (): RuntimeLogsSettingsForm => ({
- runtime_logs_auto_enabled: 'false',
- runtime_logs_max_file_mb: '128',
- runtime_logs_auto_schedule: '0 */6 * * *',
- runtime_logs_auto_mode: 'truncate'
-});
diff --git a/web-legacy-svelte/src/lib/settings/settings-api.ts b/web-legacy-svelte/src/lib/settings/settings-api.ts
deleted file mode 100644
index 234d274..0000000
--- a/web-legacy-svelte/src/lib/settings/settings-api.ts
+++ /dev/null
@@ -1,117 +0,0 @@
-import { apiJSON, apiMutate } from '$lib/api/client.js';
-import type { AppSettings } from '$lib/api/types.js';
-import { emptyBirdSettingsForm, type BirdSettingsForm } from './bird-settings.schema.js';
-import {
- emptyRevisionSettingsForm,
- type RevisionSettingsForm
-} from './revision-settings.schema.js';
-import {
- emptyRuntimeLogsSettingsForm,
- type RuntimeLogsSettingsForm
-} from './runtime-logs-settings.schema.js';
-import {
- BIRD_SETTING_KEYS,
- BOOLEAN_SETTING_KEYS,
- KNOWN_SETTING_KEYS,
- NUMERIC_SETTING_KEYS,
- RUNTIME_LOGS_SETTING_KEYS,
- type BirdSettingKey,
- type KnownSettingKey,
- type RevisionSettingKey,
- type RuntimeLogsSettingKey
-} from './settings-known-keys.js';
-
-export type AdditionalSettingEntry = { id: number; key: string; value: string };
-
-export type PartitionedSettings = {
- bird: BirdSettingsForm;
- revision: RevisionSettingsForm;
- runtimeLogs: RuntimeLogsSettingsForm;
- additional: AdditionalSettingEntry[];
-};
-
-function parseKnownValue(key: KnownSettingKey, value: unknown): string {
- if (NUMERIC_SETTING_KEYS.has(key)) {
- if (typeof value === 'number' && Number.isFinite(value)) return String(value);
- if (typeof value === 'string') return value;
- return '';
- }
- if (typeof value === 'string') return value;
- return '';
-}
-
-export function partitionSettings(
- settings: AppSettings,
- nextId = 1
-): { partitioned: PartitionedSettings; nextId: number } {
- const bird = emptyBirdSettingsForm();
- const revision = emptyRevisionSettingsForm();
- const runtimeLogs = emptyRuntimeLogsSettingsForm();
- const additional: AdditionalSettingEntry[] = [];
- let idCounter = nextId;
-
- for (const [key, value] of Object.entries(settings as Record)) {
- if ((BIRD_SETTING_KEYS as readonly string[]).includes(key)) {
- bird[key as BirdSettingKey] = parseKnownValue(key as KnownSettingKey, value);
- } else if (key === 'revision_retention_minutes') {
- revision.revision_retention_minutes = parseKnownValue(key as RevisionSettingKey, value);
- } else if ((RUNTIME_LOGS_SETTING_KEYS as readonly string[]).includes(key)) {
- const rk = key as RuntimeLogsSettingKey;
- if (rk === 'runtime_logs_auto_enabled') {
- runtimeLogs.runtime_logs_auto_enabled =
- value === true || value === 1 || value === 'true' || value === '1' ? 'true' : 'false';
- } else if (rk === 'runtime_logs_auto_mode') {
- const m = String(value ?? '').trim();
- runtimeLogs.runtime_logs_auto_mode =
- m === 'delete' ? 'delete' : m === 'truncate' ? 'truncate' : '';
- } else {
- runtimeLogs[rk] = parseKnownValue(key as KnownSettingKey, value);
- }
- } else {
- additional.push({
- id: idCounter++,
- key,
- value: typeof value === 'string' ? value : String(value)
- });
- }
- }
-
- return {
- partitioned: { bird, revision, runtimeLogs, additional },
- nextId: idCounter
- };
-}
-
-export async function loadSettings(): Promise {
- return apiJSON('/v1/settings');
-}
-
-export async function patchSettings(
- payload: Record
-): Promise {
- await apiMutate('/v1/settings', 'PATCH', payload);
-}
-
-export function buildPayloadFromFormFields(
- keys: readonly KnownSettingKey[],
- form: Record,
- errors: Partial>
-): Record