import { ChevronLeft } from "lucide-react"; import { useQuery } from "@tanstack/react-query"; import { aiConnectionRouterPluginKey, aiConnectionRouterSlug, humanizeConnectionDisplayName } from "@paperclipai/shared"; import type { ToolApplication, ToolConnection } from "@paperclipai/shared"; import { Link } from "@/lib/router"; import { toolsApi } from "@/api/tools"; import { useCompany } from "@/context/CompanyContext"; import { useSidebar } from "@/context/SidebarContext"; import { queryKeys } from "@/lib/queryKeys"; import { APP_TABS, appApplicationTabHref, appTabHref, type AppTabKey, } from "@/pages/apps/app-tabs"; import { AppLogo } from "@/pages/apps/AppLogo"; import { appApplicationSourceSlug, appConnectionSourceSlug, appDefinitionDarkLogoUrl, appDefinitionLogoUrl, appDefinitionName, appDefinitionSlug, type AppGalleryDisplayEntry, } from "@/pages/apps/app-definition-display"; import { SidebarNavItem } from "./SidebarNavItem"; type AppDetailSidebarProps = | { kind: "connection"; connectionId: string } | { kind: "application"; applicationId: string }; export function AppDetailSidebar(props: AppDetailSidebarProps) { const { selectedCompanyId } = useCompany(); const { isMobile, setSidebarOpen } = useSidebar(); const connectionQuery = useQuery({ queryKey: queryKeys.tools.connection(props.kind === "connection" ? props.connectionId : "__none__"), queryFn: () => toolsApi.getConnection(props.kind === "connection" ? props.connectionId : ""), enabled: props.kind === "connection" && !!props.connectionId, }); const applicationsQuery = useQuery({ queryKey: queryKeys.tools.applications(selectedCompanyId ?? "__none__"), queryFn: () => toolsApi.listApplications(selectedCompanyId!), enabled: !!selectedCompanyId, }); const connectionsQuery = useQuery({ queryKey: queryKeys.tools.connections(selectedCompanyId ?? "__none__"), queryFn: () => toolsApi.listConnections(selectedCompanyId!), enabled: props.kind === "application" && !!selectedCompanyId, }); const galleryQuery = useQuery({ queryKey: queryKeys.apps.gallery(selectedCompanyId ?? "__none__"), queryFn: () => toolsApi.listGallery(selectedCompanyId!), enabled: !!selectedCompanyId, }); const attentionQuery = useQuery({ queryKey: queryKeys.apps.attention(selectedCompanyId ?? "__none__"), queryFn: () => toolsApi.listAppsAttention(selectedCompanyId!), enabled: !!selectedCompanyId, refetchInterval: 30_000, }); const connection = connectionQuery.data; const poolPluginKey = connection && aiConnectionRouterPluginKey(connection); const applicationId = props.kind === "application" ? props.applicationId : connection?.applicationId; const application = (applicationsQuery.data?.applications ?? []).find((app) => app.id === applicationId) ?? null; const appConnections = props.kind === "application" ? (connectionsQuery.data?.connections ?? []).filter((candidate) => candidate.applicationId === props.applicationId) : []; const previousConnection = latestArchivedConnection(appConnections); const appName = connection ? humanizeConnectionDisplayName(connection) : application?.name ?? "App"; const logoEntry = galleryEntryFor( (galleryQuery.data?.apps ?? []) as AppGalleryDisplayEntry[], connection, application ?? undefined, ); const brandKey = appApplicationSourceSlug(application) ?? appConnectionSourceSlug(connection) ?? (logoEntry ? appDefinitionSlug(logoEntry) : null); const reviewConnectionId = connection?.id ?? previousConnection?.id ?? null; const attentionItem = reviewConnectionId ? attentionQuery.data?.apps.find((app) => app.connection.id === reviewConnectionId) : null; const reviewCount = (attentionItem?.pendingActionRequestCount ?? 0) + ((attentionItem?.quarantinedCatalogEntryCount ?? 0) > 0 ? 1 : 0); return ( ); } function tabHref(props: AppDetailSidebarProps, tab: AppTabKey): string { return props.kind === "connection" ? appTabHref(props.connectionId, tab) : appApplicationTabHref(props.applicationId, tab); } function galleryEntryFor( apps: AppGalleryDisplayEntry[], connection: ToolConnection | undefined, application: ToolApplication | undefined, ): AppGalleryDisplayEntry | null { const router = connection && aiConnectionRouterPluginKey(connection); if (router) return apps.find(app => appDefinitionSlug(app) === aiConnectionRouterSlug(router)) ?? null; const sourceSlug = appApplicationSourceSlug(application) ?? appConnectionSourceSlug(connection); if (sourceSlug) { const keyed = apps.find((app) => appDefinitionSlug(app) === sourceSlug); if (keyed) return keyed; } const name = (connection?.name ?? application?.name)?.toLowerCase(); if (!name) return null; return apps.find((app) => appDefinitionName(app).toLowerCase() === name) ?? apps.find((app) => appDefinitionSlug(app) === name) ?? null; } function latestArchivedConnection(connections: ToolConnection[]): ToolConnection | null { const archived = connections.filter((connection) => connection.status === "archived"); if (archived.length === 0) return null; return archived.reduce((latest, connection) => { const latestTime = new Date(latest.updatedAt ?? latest.createdAt ?? 0).getTime(); const connectionTime = new Date(connection.updatedAt ?? connection.createdAt ?? 0).getTime(); return connectionTime > latestTime ? connection : latest; }); }