import { useEffect, useState } from "react"; import { Effect, Exit } from "effect"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@executor-js/react/components/alert-dialog"; import { Alert, AlertDescription, AlertTitle } from "@executor-js/react/components/alert"; import { Button } from "@executor-js/react/components/button"; import type { GitHubCustomToolsSourceSummary } from "../api"; import { consoleIntegrationHref, getCustomToolSourceEffect, removeCustomToolSourceEffect, syncCustomToolSourceEffect, } from "./custom-tools-client"; import { sourcePanelModel, syncNoticeFromResult, type SyncNoticeModel } from "./source-panel-model"; type LoadState = | { readonly status: "loading" } | { readonly status: "error"; readonly message: string } | { readonly status: "missing" } | { readonly status: "ready"; readonly source: GitHubCustomToolsSourceSummary }; export default function CustomToolsAccountsPanel(props: { readonly sourceId: string; readonly integrationName: string; }) { const [loadState, setLoadState] = useState({ status: "loading" }); const [syncing, setSyncing] = useState(false); const [removing, setRemoving] = useState(false); const [notice, setNotice] = useState(null); const [removeError, setRemoveError] = useState(null); const loadSource = async () => { setLoadState({ status: "loading" }); const exit = await Effect.runPromiseExit(getCustomToolSourceEffect(props.sourceId)); if (Exit.isFailure(exit)) { setLoadState({ status: "error", message: "Failed to load custom tools source." }); return; } const source = exit.value.source; setLoadState(source ? { status: "ready", source } : { status: "missing" }); }; useEffect(() => { let active = true; void (async () => { const exit = await Effect.runPromiseExit(getCustomToolSourceEffect(props.sourceId)); if (!active) return; if (Exit.isFailure(exit)) { setLoadState({ status: "error", message: "Failed to load custom tools source." }); return; } const source = exit.value.source; setLoadState(source ? { status: "ready", source } : { status: "missing" }); })(); return () => { active = false; }; }, [props.sourceId]); const syncSource = async (source: GitHubCustomToolsSourceSummary) => { setSyncing(true); setNotice(null); const beforeTools = source.tools; const exit = await Effect.runPromiseExit(syncCustomToolSourceEffect({ slug: source.slug })); if (Exit.isFailure(exit)) { setNotice({ status: "failed", message: "Sync failed.", added: [], removed: [], errors: ["Failed to sync custom tools."], skipped: [], }); setSyncing(false); return; } const result = exit.value; setNotice(syncNoticeFromResult(result, beforeTools)); if (result.status !== "failed") await loadSource(); setSyncing(false); }; const removeSource = async (source: GitHubCustomToolsSourceSummary) => { setRemoving(true); setRemoveError(null); const exit = await Effect.runPromiseExit(removeCustomToolSourceEffect(source.slug)); if (Exit.isFailure(exit)) { setRemoveError("Failed to remove custom tools source."); setRemoving(false); return; } window.location.assign(consoleIntegrationHref("/integrations")); }; return (
{loadState.status === "loading" && (
Loading source...
)} {loadState.status === "error" && ( void loadSource()} /> )} {loadState.status === "missing" && ( Source not found The custom tools source for {props.integrationName} is no longer available. )} {loadState.status === "ready" && ( void syncSource(loadState.source)} onRemove={() => void removeSource(loadState.source)} /> )}
); } function ErrorWithRetry(props: { readonly message: string; readonly onRetry: () => void }) { return ( Failed to load source

{props.message}

); } function SourceDetail(props: { readonly source: GitHubCustomToolsSourceSummary; readonly notice: SyncNoticeModel | null; readonly removeError: string | null; readonly syncing: boolean; readonly removing: boolean; readonly onSync: () => void; readonly onRemove: () => void; }) { const { source, notice } = props; const model = sourcePanelModel(source); const noticeHasDetails = notice !== null && (notice.added.length > 0 || notice.removed.length > 0 || notice.errors.length > 0 || notice.skipped.length > 0 || notice.upstreamSha !== undefined); return (
Remove {source.name}? This removes {source.tools.length} {source.tools.length === 1 ? "tool" : "tools"}{" "} from the catalog. The GitHub repository is untouched; re-add it to sync again. Cancel {props.removing ? "Removing..." : "Remove source"}

{model.lastSynced}

{model.publishedTools.label} {" "} published

{notice && ( {notice.message} {noticeHasDetails && (
{notice.added.length > 0 &&

Added: {notice.added.join(", ")}

} {notice.removed.length > 0 &&

Removed: {notice.removed.join(", ")}

} {notice.errors.map((error) => (

{error}

))} {notice.skipped.length > 0 && (

Skipped:

    {notice.skipped.map((entry) => (
  • {entry.path} {entry.reason}
  • ))}
)} {notice.upstreamSha && (

Commit {notice.upstreamSha}

)}
)}
)} {props.removeError && }
); } function FormErrorMessage(props: { readonly message: string }) { return (

{props.message}

); }