import { useState } from "react"; import { Eye, EyeOff, Loader2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; import { PopoverTitle, PopoverDescription } from "@/components/ui/popover"; const SECRET_NAME_RE = /^[a-z][a-z0-9_]*$/; const fieldClass = "w-full rounded-md border border-border px-2.5 py-1.5 bg-transparent outline-none text-sm font-mono placeholder:text-muted-foreground/40 focus-visible:ring-2 focus-visible:ring-ring/40"; export interface SecretPopoverFormProps { /** Popover heading / body copy differ between the two flows. */ mode: "create" | "store"; initialName: string; /** The plaintext value being stored. Editable (create) or read-only (store). */ initialValue: string; /** For a same-name uniqueness hint before the server round-trips. */ existingSecretNames?: readonly string[]; onCancel: () => void; /** Resolves once the secret is created + the row is bound; rejects with a message. */ onSubmit: (name: string, value: string) => Promise; } /** * Shared anchored-popover form behind {@link CreateSecretPopover} and * {@link ConvertToSecretPopover}. Replaces the old `window.prompt` seal flow * (plan §6.5). Meant to be rendered inside a ``. */ export function SecretPopoverForm({ mode, initialName, initialValue, existingSecretNames, onCancel, onSubmit, }: SecretPopoverFormProps) { const [name, setName] = useState(initialName); const [value, setValue] = useState(initialValue); const [reveal, setReveal] = useState(false); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(null); const [touched, setTouched] = useState(false); const trimmedName = name.trim(); const nameError = (() => { if (!trimmedName) return touched ? "Name is required" : null; if (!SECRET_NAME_RE.test(trimmedName)) return "Use lowercase letters, digits and _"; if (existingSecretNames?.some((existing) => existing.toLowerCase() === trimmedName)) { return "A secret with this name already exists"; } return null; })(); const valueError = value.length === 0 ? (touched ? "Value is required" : null) : null; const canSubmit = !submitting && trimmedName.length > 0 && value.length > 0 && !nameError; async function handleSubmit() { setTouched(true); if (!canSubmit) return; setSubmitting(true); setError(null); try { await onSubmit(trimmedName, value); } catch (submitError) { setError(submitError instanceof Error ? submitError.message : "Failed to create secret"); setSubmitting(false); } } const ctaLabel = mode === "create" ? "Create & bind" : "Store & bind"; const heading = mode === "create" ? "Create secret" : "Store value as secret"; return (
{heading} {mode === "store" ? ( Moves the typed value into an encrypted organization secret and binds{" "} {initialName || "this variable"} to it. ) : null}
{error ?

{error}

: null}
); } /** Create a brand-new secret from the fuzzy picker's creatable item (§6.4/§6.5). */ export function CreateSecretPopover(props: Omit) { return ; } /** Store a typed Text value as a secret and bind the row (replaces "Seal", §6.5). */ export function ConvertToSecretPopover(props: Omit) { return ; }