- Added a new linting command for OpenAPI specifications in the package.json, improving code quality checks. - Updated frontend documentation to clarify component usage and structure, including detailed descriptions for `SettingsShell` and `Auth callback`. - Refactored agent-related API routes to streamline control-plane functionalities, consolidating multiple routes for better organization. - Improved error handling in the API to provide more informative responses for validation errors, enhancing user feedback during interactions. These changes enhance the overall development experience and improve the management of agents within the application.
90 lines
2.7 KiB
TypeScript
90 lines
2.7 KiB
TypeScript
import type { ReactNode } from 'react'
|
|
import { Link, Outlet, useRouterState } from '@tanstack/react-router'
|
|
|
|
import { useIsMobile } from '@evofw/ui/hooks/use-mobile'
|
|
import { cn } from '@evofw/ui/lib/utils'
|
|
import { PageShell } from '@/components/page-shell'
|
|
import { PageHeader } from '@/components/page-header'
|
|
|
|
/**
|
|
* Multi-section settings layout — only when tabs are provided.
|
|
* Single-page settings use PageShell + Frame + SettingRow directly.
|
|
* Preview: https://reui.io/preview/base/settings-16
|
|
*/
|
|
|
|
export interface SettingsTabConfig {
|
|
id: string
|
|
to: string
|
|
label: string
|
|
icon?: ReactNode
|
|
}
|
|
|
|
interface SettingsShellProps {
|
|
title?: string
|
|
description?: string
|
|
/** Required — no phantom default routes. */
|
|
tabs: SettingsTabConfig[]
|
|
}
|
|
|
|
export function SettingsShell({
|
|
title = 'Настройки',
|
|
description = 'Конфигурация control plane',
|
|
tabs,
|
|
}: SettingsShellProps) {
|
|
const isMobile = useIsMobile()
|
|
const pathname = useRouterState({ select: (s) => s.location.pathname })
|
|
|
|
return (
|
|
<PageShell>
|
|
<div className="mx-auto flex w-full max-w-4xl flex-col gap-5">
|
|
<PageHeader title={title} description={description} />
|
|
|
|
<div
|
|
className={cn(
|
|
'flex gap-5',
|
|
isMobile ? 'flex-col' : 'flex-row items-start',
|
|
)}
|
|
>
|
|
{tabs.length > 1 ? (
|
|
<nav
|
|
aria-label="Разделы настроек"
|
|
className={cn(
|
|
'flex gap-1',
|
|
isMobile
|
|
? 'scrollbar-none -mx-1 overflow-x-auto overflow-y-hidden pb-1'
|
|
: 'w-44 shrink-0 flex-col',
|
|
)}
|
|
>
|
|
{tabs.map((tab) => {
|
|
const isActive = pathname.startsWith(tab.to)
|
|
return (
|
|
<Link
|
|
key={tab.id}
|
|
to={tab.to}
|
|
aria-current={isActive ? 'page' : undefined}
|
|
className={cn(
|
|
'flex items-center gap-2 rounded-lg px-3 py-2 text-sm transition-colors',
|
|
isMobile && 'shrink-0',
|
|
!isMobile && 'w-full',
|
|
isActive
|
|
? 'bg-muted text-foreground font-medium shadow-sm ring-1 ring-border/60'
|
|
: 'text-muted-foreground hover:bg-muted/60 hover:text-foreground',
|
|
)}
|
|
>
|
|
{tab.icon}
|
|
{tab.label}
|
|
</Link>
|
|
)
|
|
})}
|
|
</nav>
|
|
) : null}
|
|
|
|
<div className="min-w-0 flex-1">
|
|
<Outlet />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</PageShell>
|
|
)
|
|
}
|