fix(settings): remove duplicate breadcrumbs and system sidebar badges (#1080)
This commit is contained in:
@@ -8,7 +8,7 @@ import { Input } from "@kandev/ui/input";
|
||||
import { Label } from "@kandev/ui/label";
|
||||
import { Switch } from "@kandev/ui/switch";
|
||||
import { Separator } from "@kandev/ui/separator";
|
||||
import { IconArrowLeft, IconTrash } from "@tabler/icons-react";
|
||||
import { IconTrash } from "@tabler/icons-react";
|
||||
import { useAutomations } from "@/hooks/domains/settings/use-automations";
|
||||
import {
|
||||
addTrigger,
|
||||
@@ -352,7 +352,6 @@ export function AutomationEditor({ workspaceId, automationId }: AutomationEditor
|
||||
|
||||
return (
|
||||
<div className="max-w-3xl space-y-6" data-testid="automation-editor">
|
||||
<EditorHeader workspaceId={workspaceId} />
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="automation-name">Name</Label>
|
||||
<Input
|
||||
@@ -519,23 +518,6 @@ function ThenSection({
|
||||
);
|
||||
}
|
||||
|
||||
function EditorHeader({ workspaceId }: { workspaceId: string }) {
|
||||
const router = useRouter();
|
||||
return (
|
||||
<div className="flex items-center gap-3">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
className="cursor-pointer"
|
||||
onClick={() => router.push(`/settings/workspace/${workspaceId}/automations`)}
|
||||
>
|
||||
<IconArrowLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
<span className="text-sm text-muted-foreground">Automations</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SettingsSection({
|
||||
form,
|
||||
updateField,
|
||||
|
||||
@@ -24,11 +24,11 @@ type PageTopbarProps = {
|
||||
/** Label for the parent breadcrumb (default: "Kandev") */
|
||||
backLabel?: string;
|
||||
/**
|
||||
* Optional middle crumb inserted between the back link and the title — use
|
||||
* for nested sections (e.g. Home > Settings > Prompts). When omitted the
|
||||
* breadcrumb is two segments wide.
|
||||
* Optional middle crumbs inserted between the back link and the title — use
|
||||
* for nested sections (e.g. Home > Settings > Automations > New). When
|
||||
* omitted the breadcrumb is two segments wide.
|
||||
*/
|
||||
parent?: { label: string; href: string };
|
||||
parents?: Array<{ label: string; href: string }>;
|
||||
/** Optional content rendered before the breadcrumb */
|
||||
leading?: ReactNode;
|
||||
/** Optional content rendered at the visual center of the topbar */
|
||||
@@ -66,14 +66,14 @@ function BackLink({ href, label }: { href: string; label: string }) {
|
||||
function TopbarBreadcrumb({
|
||||
backHref,
|
||||
backLabel,
|
||||
parent,
|
||||
parents,
|
||||
title,
|
||||
subtitle,
|
||||
icon,
|
||||
}: {
|
||||
backHref: string;
|
||||
backLabel: string;
|
||||
parent: { label: string; href: string } | undefined;
|
||||
parents: Array<{ label: string; href: string }> | undefined;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
icon?: ReactNode;
|
||||
@@ -87,21 +87,19 @@ function TopbarBreadcrumb({
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator className="shrink-0" />
|
||||
{parent && (
|
||||
<>
|
||||
<BreadcrumbItem className="shrink-0">
|
||||
<BreadcrumbLink asChild>
|
||||
<Link
|
||||
href={parent.href}
|
||||
className="cursor-pointer text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
{parent.label}
|
||||
</Link>
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator className="shrink-0" />
|
||||
</>
|
||||
)}
|
||||
{parents?.flatMap((p) => [
|
||||
<BreadcrumbItem key={`${p.href}-item`} className="shrink-0">
|
||||
<BreadcrumbLink asChild>
|
||||
<Link
|
||||
href={p.href}
|
||||
className="cursor-pointer text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
{p.label}
|
||||
</Link>
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>,
|
||||
<BreadcrumbSeparator key={`${p.href}-sep`} className="shrink-0" />,
|
||||
])}
|
||||
<BreadcrumbItem className="min-w-0">
|
||||
<BreadcrumbPage className="flex min-w-0 items-center gap-2">
|
||||
{icon}
|
||||
@@ -128,7 +126,7 @@ export const PageTopbar = forwardRef<HTMLElement, PageTopbarProps>(function Page
|
||||
icon,
|
||||
backHref = "/",
|
||||
backLabel = "Kandev",
|
||||
parent,
|
||||
parents,
|
||||
leading,
|
||||
center,
|
||||
leftActions,
|
||||
@@ -154,7 +152,7 @@ export const PageTopbar = forwardRef<HTMLElement, PageTopbarProps>(function Page
|
||||
<TopbarBreadcrumb
|
||||
backHref={backHref}
|
||||
backLabel={backLabel}
|
||||
parent={parent}
|
||||
parents={parents}
|
||||
title={title}
|
||||
subtitle={subtitle}
|
||||
icon={icon}
|
||||
|
||||
@@ -38,7 +38,6 @@ import {
|
||||
SidebarGroupLabel,
|
||||
SidebarGroupContent,
|
||||
SidebarMenu,
|
||||
SidebarMenuBadge,
|
||||
SidebarMenuItem,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuSub,
|
||||
@@ -51,7 +50,7 @@ import { ScrollArea } from "@kandev/ui/scroll-area";
|
||||
import { ScrollOnOverflow } from "@kandev/ui/scroll-on-overflow";
|
||||
import { useAppStore } from "@/components/state-provider";
|
||||
import { useAvailableAgents } from "@/hooks/domains/settings/use-available-agents";
|
||||
import { useSystemBadgeCount } from "@/hooks/domains/system/use-system-badge-count";
|
||||
|
||||
import { AgentLogo } from "@/components/agent-logo";
|
||||
import { getExecutorIcon } from "@/lib/executor-icons";
|
||||
import { getCapabilityWarning } from "@/lib/capability-warning";
|
||||
@@ -173,10 +172,6 @@ function SystemSidebarSection({ pathname }: { pathname: string }) {
|
||||
{ href: "/settings/system/licenses", label: "Licenses", Icon: IconScale },
|
||||
];
|
||||
const isSystem = pathname.startsWith("/settings/system");
|
||||
// Sum of non-info health issues + 1 when an update is available. Surfaced
|
||||
// on the group row and the Status child entry so the user gets a hint
|
||||
// without having to open System.
|
||||
const badgeCount = useSystemBadgeCount();
|
||||
return (
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild tooltip="System" isActive={isSystem}>
|
||||
@@ -185,9 +180,6 @@ function SystemSidebarSection({ pathname }: { pathname: string }) {
|
||||
<span>System</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
{badgeCount > 0 && (
|
||||
<SidebarMenuBadge data-testid="system-sidebar-badge">{badgeCount}</SidebarMenuBadge>
|
||||
)}
|
||||
<SidebarMenuSub className="ml-3 mt-1">
|
||||
{items.map(({ href, label, Icon }) => (
|
||||
<SidebarMenuSubItem key={href}>
|
||||
@@ -197,11 +189,6 @@ function SystemSidebarSection({ pathname }: { pathname: string }) {
|
||||
<span>{label}</span>
|
||||
</Link>
|
||||
</SidebarMenuSubButton>
|
||||
{href === "/settings/system/status" && badgeCount > 0 && (
|
||||
<SidebarMenuBadge data-testid="system-sidebar-status-badge">
|
||||
{badgeCount}
|
||||
</SidebarMenuBadge>
|
||||
)}
|
||||
</SidebarMenuSubItem>
|
||||
))}
|
||||
</SidebarMenuSub>
|
||||
|
||||
@@ -42,18 +42,41 @@ function deriveCurrentPageLabel(pathname: string): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Build the intermediate breadcrumb crumbs between the back link and the
|
||||
// current page title. For workspace-scoped automation pages, inject an
|
||||
// "Automations" crumb so the breadcrumb reads e.g.
|
||||
// Home > Settings > Automations > New.
|
||||
function deriveParents(pathname: string): Array<{ label: string; href: string }> {
|
||||
const segments = pathname.split("/").filter(Boolean);
|
||||
if (segments.length <= 1) return [];
|
||||
|
||||
const parents: Array<{ label: string; href: string }> = [
|
||||
{ label: "Settings", href: "/settings" },
|
||||
];
|
||||
|
||||
const automationsMatch = pathname.match(
|
||||
/^\/settings\/workspace\/([^/]+)\/automations(?:\/(.+))?/,
|
||||
);
|
||||
if (automationsMatch && automationsMatch[2]) {
|
||||
// Only inject the Automations crumb when we're on a sub-page (new or
|
||||
// edit), not on the listing page itself — the listing page title is
|
||||
// already "Automations".
|
||||
parents.push({
|
||||
label: "Automations",
|
||||
href: `/settings/workspace/${automationsMatch[1]}/automations`,
|
||||
});
|
||||
}
|
||||
|
||||
return parents;
|
||||
}
|
||||
|
||||
export function SettingsLayoutClient({ children }: { children: React.ReactNode }) {
|
||||
const pathname = usePathname();
|
||||
const isAgentDetail = pathname.startsWith("/settings/agents/") && pathname !== "/settings/agents";
|
||||
|
||||
if (isAgentDetail) {
|
||||
return (
|
||||
<SettingsShell
|
||||
title="Agent"
|
||||
backHref="/settings/agents"
|
||||
backLabel="Agents"
|
||||
parent={undefined}
|
||||
>
|
||||
<SettingsShell title="Agent" backHref="/settings/agents" backLabel="Agents" parents={[]}>
|
||||
{children}
|
||||
</SettingsShell>
|
||||
);
|
||||
@@ -61,10 +84,10 @@ export function SettingsLayoutClient({ children }: { children: React.ReactNode }
|
||||
|
||||
const pageLabel = deriveCurrentPageLabel(pathname);
|
||||
const title = pageLabel ?? "Settings";
|
||||
const parent = pageLabel ? { label: "Settings", href: "/settings" } : undefined;
|
||||
const parents = deriveParents(pathname);
|
||||
|
||||
return (
|
||||
<SettingsShell title={title} backHref="/" backLabel="Kandev" parent={parent}>
|
||||
<SettingsShell title={title} backHref="/" backLabel="Kandev" parents={parents}>
|
||||
{children}
|
||||
</SettingsShell>
|
||||
);
|
||||
@@ -74,13 +97,13 @@ function SettingsShell({
|
||||
title,
|
||||
backHref,
|
||||
backLabel,
|
||||
parent,
|
||||
parents,
|
||||
children,
|
||||
}: {
|
||||
title: string;
|
||||
backHref: string;
|
||||
backLabel: string;
|
||||
parent: { label: string; href: string } | undefined;
|
||||
parents: Array<{ label: string; href: string }>;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
@@ -92,7 +115,7 @@ function SettingsShell({
|
||||
title={title}
|
||||
backHref={backHref}
|
||||
backLabel={backLabel}
|
||||
parent={parent}
|
||||
parents={parents}
|
||||
className="h-16 border-b-0"
|
||||
leading={<SidebarTrigger size="lg" className="md:hidden h-10 w-10 cursor-pointer" />}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user