import { createContext, use, useId, useMemo, useState, type CSSProperties, type ReactNode, } from "react"; import { useAtomSet } from "@effect/atom-react"; import * as Exit from "effect/Exit"; import { createConnection } from "../api/atoms"; import { connectionWriteKeys } from "../api/reactivity-keys"; import { AuthTemplateSlug, ConnectionName, IntegrationSlug, type Owner, } from "@executor-js/sdk/shared"; import { Button, type buttonVariants } from "../components/button"; import { Field, FieldError, FieldLabel } from "../components/field"; import { Input } from "../components/input"; import type { VariantProps } from "class-variance-authority"; import { secretValueInputType } from "./secret-input"; import { getUniqueSecretId, isSecretIdTaken } from "./secret-id"; // --------------------------------------------------------------------------- // Connection-create form (v2) — successor to v1's secret form. // // v1 minted a standalone secret bound to a scope. v2 mints a Connection: a // value pasted by the user, saved under an owner (Personal | Workspace) for one // integration's auth template. The compound API (`state`/`actions`/`meta`) is // kept so existing call sites compose `SecretForm.*` parts; the payload is now a // `createConnection` mutation. // --------------------------------------------------------------------------- type SubmitStatus = | { readonly kind: "idle" } | { readonly kind: "submitting" } | { readonly kind: "error"; readonly message: string }; interface SecretFormState { readonly name: string; readonly value: string; readonly idOverride: string | null; readonly provider: string; readonly revealed: boolean; readonly status: SubmitStatus; } interface SecretFormActions { readonly setName: (v: string) => void; readonly setValue: (v: string) => void; readonly setIdOverride: (v: string) => void; readonly setProvider: (v: string) => void; readonly toggleReveal: () => void; readonly submit: () => Promise; } interface SecretFormMeta { readonly id: string; readonly duplicateError: string | null; readonly canSubmit: boolean; } interface SecretFormContextValue { readonly state: SecretFormState; readonly actions: SecretFormActions; readonly meta: SecretFormMeta; } const SecretFormContext = createContext(null); function useSecretForm(): SecretFormContextValue { const ctx = use(SecretFormContext); if (!ctx) { // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: React context invariant surfaces programmer misuse during render throw new Error("SecretForm parts must be rendered inside "); } return ctx; } // --------------------------------------------------------------------------- // Provider // --------------------------------------------------------------------------- interface SecretFormProviderProps { readonly existingSecretIds: readonly string[]; readonly suggestedName?: string; readonly fallbackId?: string; /** Force the connection name to a pre-allocated value. */ readonly initialIdOverride?: string; readonly initialProvider?: string; /** Owner the new connection is saved under (Personal | Workspace). */ readonly owner: Owner; /** The integration the connection authenticates. */ readonly integration: IntegrationSlug; /** Which of the integration's auth methods the value applies through. */ readonly template: AuthTemplateSlug; readonly onCreated: (connectionName: string) => void; readonly children: ReactNode; } function SecretFormProvider(props: SecretFormProviderProps) { const { existingSecretIds, suggestedName = "", fallbackId = "credential", initialIdOverride, initialProvider = "auto", owner, integration, template, onCreated, children, } = props; const doCreate = useAtomSet(createConnection, { mode: "promiseExit" }); const [state, setState] = useState(() => ({ name: suggestedName, value: "", idOverride: initialIdOverride ?? null, provider: initialProvider, revealed: false, status: { kind: "idle" }, })); const baseName = state.name || suggestedName; const autoId = useMemo( () => getUniqueSecretId(baseName, existingSecretIds, fallbackId), [baseName, existingSecretIds, fallbackId], ); const id = state.idOverride ?? autoId; const duplicateError = state.idOverride !== null && isSecretIdTaken(state.idOverride, existingSecretIds) ? "Name already exists" : null; const displayName = state.name.trim() || suggestedName.trim(); const canSubmit = id.trim() !== "" && state.value.trim() !== "" && duplicateError === null && state.status.kind !== "submitting"; const submit = async () => { if (!canSubmit) return; setState((s) => ({ ...s, status: { kind: "submitting" } })); const exit = await doCreate({ payload: { owner, name: ConnectionName.make(id.trim()), integration, template, identityLabel: displayName || id.trim(), value: state.value.trim(), }, reactivityKeys: connectionWriteKeys, }); if (Exit.isFailure(exit)) { setState((s) => ({ ...s, status: { kind: "error", message: "Failed to save credential", }, })); return; } onCreated(id.trim()); }; const value: SecretFormContextValue = { state, actions: { setName: (v) => setState((s) => ({ ...s, name: v })), setValue: (v) => setState((s) => ({ ...s, value: v })), setIdOverride: (v) => setState((s) => ({ ...s, idOverride: v })), setProvider: (v) => setState((s) => ({ ...s, provider: v })), toggleReveal: () => setState((s) => ({ ...s, revealed: !s.revealed })), submit, }, meta: { id, duplicateError, canSubmit }, }; return {children}; } // --------------------------------------------------------------------------- // Parts // --------------------------------------------------------------------------- function NameField(props: { label?: string; placeholder?: string }) { const { state, actions } = useSecretForm(); const inputId = useId(); return ( {props.label ?? "Name"} actions.setName((e.target as HTMLInputElement).value)} placeholder={props.placeholder ?? "GitHub PAT"} /> ); } function IdField(props: { placeholder?: string }) { const { actions, meta } = useSecretForm(); const inputId = useId(); return ( ID actions.setIdOverride((e.target as HTMLInputElement).value)} placeholder={props.placeholder ?? "github-token"} className="font-mono" /> {meta.duplicateError && {meta.duplicateError}} ); } function ValueField(props: { revealable?: boolean; placeholder?: string; autoFocus?: boolean }) { const { state, actions } = useSecretForm(); const inputId = useId(); const revealable = props.revealable ?? false; const revealed = revealable && state.revealed; const errored = state.status.kind === "error"; return ( Value
actions.setValue((e.target as HTMLInputElement).value)} placeholder={props.placeholder ?? "ghp_xxxxxxxxxxxxxxxxxxxx"} autoFocus={props.autoFocus} autoComplete="new-password" className={revealable ? "pr-9 font-mono" : "font-mono"} style={ revealable && !revealed ? ({ WebkitTextSecurity: "disc" } as CSSProperties) : undefined } data-ph-block /> {revealable && ( )}
{errored && ( {state.status.kind === "error" ? state.status.message : ""} )}
); } function ErrorBanner() { const { state } = useSecretForm(); if (state.status.kind !== "error") return null; return (

{state.status.message}

); } type ButtonProps = React.ComponentProps<"button"> & VariantProps; function SubmitButton(props: ButtonProps & { children?: ReactNode }) { const { state, meta, actions } = useSecretForm(); const { children, disabled, onClick, ...rest } = props; const submitting = state.status.kind === "submitting"; return ( ); } // --------------------------------------------------------------------------- // Reveal-eye icon (used by ValueField when `revealable`) // --------------------------------------------------------------------------- function SecretVisibilityIcon(props: { revealed: boolean }) { return props.revealed ? ( ) : ( ); } // --------------------------------------------------------------------------- // Compound export // --------------------------------------------------------------------------- export const SecretForm = { Provider: SecretFormProvider, NameField, IdField, ValueField, ErrorBanner, SubmitButton, }; export type { SecretFormProviderProps };