import { useState } from "react"; import { Effect, Exit } from "effect"; import { Button } from "@executor-js/react/components/button"; import { CardStack, CardStackContent, CardStackEntryField, } from "@executor-js/react/components/card-stack"; import { FloatActions } from "@executor-js/react/components/float-actions"; import { Input } from "@executor-js/react/components/input"; import { toast } from "@executor-js/react/components/sonner"; import { FormErrorAlert, useSlugAlreadyExists } from "@executor-js/react/lib/integration-add"; import { formatSyncErrors, slugifyCustomToolsAppName, suggestCustomToolsAppName, syncCustomToolSourceEffect, syncStatusLabel, validateCustomToolsAppSlug, validateGitHubSourceUrl, } from "./custom-tools-client"; export default function AddCustomToolsSource(props: { readonly onComplete: (slug?: string) => void; readonly onCancel: () => void; readonly initialUrl?: string; readonly initialNamespace?: string; }) { const [url, setUrl] = useState(props.initialUrl ?? ""); const [name, setName] = useState( props.initialNamespace ? slugifyCustomToolsAppName(props.initialNamespace) : "", ); const [nameTouched, setNameTouched] = useState(props.initialNamespace !== undefined); const [token, setToken] = useState(""); const [tokenRevealed, setTokenRevealed] = useState(false); const [urlError, setUrlError] = useState(null); const [nameError, setNameError] = useState(null); const [syncing, setSyncing] = useState(false); const [syncError, setSyncError] = useState(null); const effectiveName = nameTouched ? name : slugifyCustomToolsAppName(suggestCustomToolsAppName(url)); const slug = effectiveName; const slugAlreadyExists = useSlugAlreadyExists(slug); const submit = async () => { const validation = validateGitHubSourceUrl(url); const nextNameError = validateCustomToolsAppSlug(slug); setUrlError(validation); setNameError(nextNameError); setSyncError(null); if (validation || nextNameError) return; if (slugAlreadyExists) { setSyncError(`An integration named "${slug}" already exists. Choose another name.`); return; } setSyncing(true); const exit = await Effect.runPromiseExit( syncCustomToolSourceEffect({ name: slug, url, token, }), ); if (Exit.isFailure(exit)) { setSyncError("Failed to sync custom tools."); setSyncing(false); return; } const result = exit.value; if (result.status === "failed") { setSyncError(formatSyncErrors(result).join("\n") || "Sync failed."); setSyncing(false); return; } toast.success(syncStatusLabel(result)); props.onComplete(slug); }; return (

Add custom tools

Sync a GitHub repository that defines tools with executor:app.

{ setUrl((event.target as HTMLInputElement).value); setUrlError(null); setNameError(null); setSyncError(null); }} onBlur={() => setUrlError(validateGitHubSourceUrl(url))} placeholder="https://github.com/UsefulSoftwareCo/executor" className="font-mono text-sm" aria-invalid={urlError ? true : undefined} /> {urlError &&

{urlError}

}
{ setNameTouched(true); setName(slugifyCustomToolsAppName((event.target as HTMLInputElement).value)); setNameError(null); setSyncError(null); }} placeholder="executor-custom-tools-demo" className="text-sm" aria-invalid={nameError ? true : undefined} /> {nameError &&

{nameError}

} {slugAlreadyExists && !syncing && !nameError && (

An integration named "{slug}" already exists.

)}
{ setToken((event.target as HTMLInputElement).value); setSyncError(null); }} autoComplete="off" className="font-mono text-sm" />
{syncError && }
); }