import { useActionQuery } from "@agent-native/core/client/hooks"; import { useT } from "@agent-native/core/client/i18n"; import { IconApps, IconChevronDown, IconClockHour4, IconEyeOff, IconPlus, } from "@tabler/icons-react"; import { useState } from "react"; import { Outlet, useParams } from "react-router"; import { ActionQueryError } from "../../components/action-query-error"; import { APP_LIST_GRID_CLASS, APP_LIST_GRID_ROW_CLASS, AppList, } from "../../components/app-list-row"; import { CreateAppPopover } from "../../components/create-app-popover"; import { DispatchShell } from "../../components/dispatch-shell"; import { mergeOtherAppEntries, otherAppEntryMatchesQuery, OtherAppsSection, } from "../../components/other-apps-section"; import { Button } from "../../components/ui/button"; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "../../components/ui/collapsible"; import { Skeleton } from "../../components/ui/skeleton"; import { WorkspaceAppCard } from "../../components/workspace-app-card"; import { WorkspaceAppSearch, WorkspaceAppSearchEmpty, } from "../../components/workspace-app-search"; import type { CuratedWorkspaceTemplatesResult, WorkspaceTemplateLabels, } from "../../components/workspace-template-card"; import type { ConnectedAppSummary } from "../../lib/other-apps"; import { cn } from "../../lib/utils"; import { orderWorkspaceApps, useWorkspaceAppLayout, workspaceAppMatchesQuery, } from "../../lib/workspace-app-layout"; import { isWorkspaceAppVisibleInDefaultLaunchers, type WorkspaceAppSummary, } from "../../lib/workspace-apps"; export function meta() { return [{ title: "Apps — Dispatch" }]; } interface WorkspaceInfo { name: string | null; displayName: string | null; appCount: number; } export default function AppsRouteEntry() { const { appId } = useParams(); return appId ? : ; } function AppsRoute() { const t = useT(); const [showPending, setShowPending] = useState(false); const [showHidden, setShowHidden] = useState(false); const [searchQuery, setSearchQuery] = useState(""); const { layout, persistenceError, togglePinned } = useWorkspaceAppLayout(); const appsQuery = useActionQuery("list-workspace-apps", { includeAgentCards: false, includeArchived: true, }); const connectedAppsQuery = useActionQuery("list-connected-agents", {}); const curatedTemplatesQuery = useActionQuery( "list-curated-workspace-templates", {}, ); const { data: apps = [], isLoading: appsLoading } = appsQuery; const { data: workspace } = useActionQuery( "get-workspace-info", {}, { staleTime: 60_000 }, ); const ws = workspace as WorkspaceInfo | undefined; const workspaceLabel = ws?.displayName ?? ws?.name ?? null; const allApps = (apps as WorkspaceAppSummary[]).filter( isWorkspaceAppVisibleInDefaultLaunchers, ); const visibleApps = allApps.filter((app) => !app.archived); const activeApps = visibleApps.filter((app) => app.status !== "pending"); const pendingApps = visibleApps.filter((app) => app.status === "pending"); const archivedApps = allApps.filter((app) => app.archived); const connectedApps = (connectedAppsQuery.data as ConnectedAppSummary[] | undefined) ?? []; const curatedTemplates = curatedTemplatesQuery.data as | CuratedWorkspaceTemplatesResult | undefined; const otherAppEntries = mergeOtherAppEntries({ templates: curatedTemplates, connectedApps, workspaceApps: allApps, }); const otherAppsSearchReady = !connectedAppsQuery.isLoading && !curatedTemplatesQuery.isLoading; const otherAppsMatchSearch = !searchQuery.trim() || !otherAppsSearchReady || otherAppEntries.some((entry) => otherAppEntryMatchesQuery(entry, searchQuery), ); const orderedActiveApps = orderWorkspaceApps(activeApps, layout); const orderedPendingApps = orderWorkspaceApps(pendingApps, layout); const orderedArchivedApps = orderWorkspaceApps(archivedApps, layout); const filteredActiveApps = orderedActiveApps.filter((app) => workspaceAppMatchesQuery(app, searchQuery), ); const filteredPendingApps = orderedPendingApps.filter((app) => workspaceAppMatchesQuery(app, searchQuery), ); const filteredArchivedApps = orderedArchivedApps.filter((app) => workspaceAppMatchesQuery(app, searchQuery), ); const hasSearchResults = filteredActiveApps.length > 0 || filteredPendingApps.length > 0 || filteredArchivedApps.length > 0 || otherAppsMatchSearch; const showAppSkeletons = appsLoading && allApps.length === 0; const templateLabels: WorkspaceTemplateLabels = { appId: t("dispatch.pages.remixAppIdLabel"), appIdDescription: t("dispatch.pages.remixAppIdDescription"), cancel: t("dispatch.pages.cancel"), integrationSetup: t("dispatch.pages.integrationSetup"), installed: t("dispatch.pages.alreadyInWorkspace"), remix: t("dispatch.pages.addApp", { defaultValue: "Add app" }), remixing: t("dispatch.pages.remixing"), remixSuccess: t("dispatch.pages.remixSuccess"), remixError: t("dispatch.pages.remixError"), appIdRequired: t("dispatch.pages.appIdRequired"), source: t("dispatch.pages.source"), openApp: t("dispatch.pages.openApp", { defaultValue: "Open app" }), viewLiveApp: t("dispatch.pages.openApp", { defaultValue: "Open" }), }; return (

{t("dispatch.pages.yourApps", { defaultValue: "Your apps" })}

{!showAppSkeletons && allApps.length > 0 ? ( ) : null} {activeApps.length > 0 || pendingApps.length > 0 ? ( {t("dispatch.pages.addApp", { defaultValue: "Add app", })} } /> ) : null}
{!showAppSkeletons && allApps.length > 0 && persistenceError ? (

{persistenceError === "both" ? t("dispatch.pages.appPinSaveFailed", { defaultValue: "App pins could not be saved.", }) : t("dispatch.pages.appPinSavedLocally", { defaultValue: "App pins are saved on this device only.", })}

) : null} {appsQuery.isError ? ( void appsQuery.refetch()} /> ) : showAppSkeletons ? ( ) : !hasSearchResults && searchQuery.trim() ? ( setSearchQuery("")} /> ) : filteredActiveApps.length > 0 ? ( {filteredActiveApps.map((app) => ( togglePinned(app.id)} /> ))} ) : searchQuery.trim() ? null : pendingApps.length > 0 ? ( ) : ( )}
{pendingApps.length > 0 && (!searchQuery.trim() || filteredPendingApps.length > 0) ? (

{t("dispatch.pages.pendingApps")}

{t("dispatch.pages.pendingCount", { count: pendingApps.length, })}

{filteredPendingApps.map((app) => ( ))}
) : null} void curatedTemplatesQuery.refetch()} onRetryConnectedApps={() => void connectedAppsQuery.refetch()} templateLabels={templateLabels} onRemixSuccess={() => { void appsQuery.refetch(); void curatedTemplatesQuery.refetch(); }} /> {archivedApps.length > 0 && (!searchQuery.trim() || filteredArchivedApps.length > 0) ? (

{t("dispatch.pages.hiddenApps")}

{t("dispatch.pages.hiddenAppCount", { count: archivedApps.length, })}

{filteredArchivedApps.map((app) => ( ))}
) : null}
); } function AppsSkeletonGrid() { return ( {Array.from({ length: 3 }).map((_, index) => (
))}
); } function EmptyAppsState() { const t = useT(); return (

{t("dispatch.pages.noWorkspaceApps")}

{t("dispatch.pages.noWorkspaceAppsDescription")}

{t("dispatch.pages.addApp", { defaultValue: "Add app" })} } />
); } function EmptyActiveAppsState() { const t = useT(); return (

{t("dispatch.pages.noActiveWorkspaceApps")}

{t("dispatch.pages.noActiveWorkspaceAppsDescription")}

); }