import { useState, type ReactNode } from "react"; import { PlusIcon } from "lucide-react"; import type { ScopeId } from "@executor-js/sdk"; import { Button } from "../components/button"; import { CardStack, CardStackContent, CardStackEmpty, CardStackEntry, } from "../components/card-stack"; import { defaultHeaderAuthPresets, type HeaderAuthPreset, type HeaderState, SecretHeaderAuthRow, type SecretCredentialPreviewComponent, type SecretCredentialRowCopy, } from "./secret-header-auth"; import type { CredentialTargetScopeOption } from "./credential-target-scope"; import type { SecretPickerSecret } from "./secret-picker"; export interface HeadersListProps { readonly headers: readonly HeaderState[]; readonly onHeadersChange: (headers: HeaderState[]) => void; readonly existingSecrets?: readonly SecretPickerSecret[]; /** Presets offered in the quick-add picker. Defaults to `defaultHeaderAuthPresets`. */ readonly presets?: readonly HeaderAuthPreset[]; /** When true, only allow a single header (hide add button, disable remove). */ readonly singleHeader?: boolean; /** Text shown in the empty state. */ readonly emptyLabel?: ReactNode; readonly addLabel?: ReactNode; readonly addAriaLabel?: string; readonly rowCopy?: Partial; readonly rowPreviewComponent?: SecretCredentialPreviewComponent; /** * Display name of the source that owns these headers (e.g. "Axiom"). Used * to derive unique default secret labels/IDs like `axiom-authorization`. */ readonly sourceName?: string; /** Inline-created secrets are written to this explicit scope. */ readonly targetScope: ScopeId; /** Scope choices shown only inside the inline "+ New secret" form. */ readonly credentialScopeOptions?: readonly CredentialTargetScopeOption[]; /** Scope choices for where this source credential is used. */ readonly bindingScopeOptions?: readonly CredentialTargetScopeOption[]; readonly restrictSecretsToTargetScope?: boolean; } export function HeadersList({ headers, onHeadersChange, existingSecrets = [], presets = defaultHeaderAuthPresets, singleHeader = false, emptyLabel = "No headers", addLabel, addAriaLabel = "Add header", rowCopy, rowPreviewComponent, sourceName, targetScope, credentialScopeOptions, bindingScopeOptions, restrictSecretsToTargetScope, }: HeadersListProps) { const [picking, setPicking] = useState(false); const canAddMore = !singleHeader || headers.length === 0; const addFirstPreset = () => { const preset = presets[0]; if (presets.length === 1 && preset) { addHeaderFromPreset(preset); return; } setPicking(true); }; const addHeaderFromPreset = (preset: HeaderAuthPreset) => { onHeadersChange([ ...headers, { name: preset.name, prefix: preset.prefix, presetKey: preset.key, secretId: null, targetScope, }, ]); setPicking(false); }; const updateHeader = ( index: number, update: Partial<{ name: string; secretId: string | null; prefix?: string; presetKey?: string; targetScope?: ScopeId; secretScope?: ScopeId; }>, ) => { onHeadersChange(headers.map((entry, i) => (i === index ? { ...entry, ...update } : entry))); }; const removeHeader = (index: number) => { onHeadersChange(headers.filter((_, i) => i !== index)); }; return ( {picking ? ( setPicking(false)} /> ) : headers.length === 0 ? ( canAddMore ? ( {emptyLabel}} onClick={addFirstPreset} ariaLabel={addAriaLabel} /> ) : ( {emptyLabel} ) ) : ( <> {headers.map((header, index) => ( updateHeader(index, update)} onSelectSecret={(secretId, scopeId) => updateHeader(index, { secretId, ...(scopeId ? { secretScope: scopeId } : {}), }) } onRemove={singleHeader ? undefined : () => removeHeader(index)} existingSecrets={existingSecrets} sourceName={sourceName} targetScope={header.targetScope ?? targetScope} credentialScopeOptions={credentialScopeOptions} bindingScopeOptions={bindingScopeOptions} restrictSecretsToTargetScope={restrictSecretsToTargetScope} copy={rowCopy} previewComponent={rowPreviewComponent} /> ))} {canAddMore && ( )} )} ); } interface AddHeaderRowProps { readonly onClick: () => void; readonly leading?: ReactNode; readonly ariaLabel: string; } function AddHeaderRow({ onClick, leading, ariaLabel }: AddHeaderRowProps) { return ( // oxlint-disable-next-line react/forbid-elements ); } interface HeaderPresetPickerProps { readonly presets: readonly HeaderAuthPreset[]; readonly onPick: (preset: HeaderAuthPreset) => void; readonly onCancel: () => void; } function HeaderPresetPicker({ presets, onPick, onCancel }: HeaderPresetPickerProps) { return ( {presets.map((preset) => ( ))} ); }