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,
})}
{showPending || Boolean(searchQuery.trim())
? t("dispatch.pages.hidePendingApps")
: t("dispatch.pages.showPendingApps")}
{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,
})}
{showHidden || Boolean(searchQuery.trim())
? t("dispatch.pages.hide")
: t("dispatch.pages.show")}
{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")}
);
}