diff --git a/src/components/config-forms/field-renderer.tsx b/src/components/config-forms/field-renderer.tsx index 51f67f63..262d72be 100644 --- a/src/components/config-forms/field-renderer.tsx +++ b/src/components/config-forms/field-renderer.tsx @@ -329,7 +329,6 @@ export function FieldRenderer({ onChange(v)} - placeholder={placeholder} /> ) : isCertFile ? ( void; - placeholder?: string; } -export function SecretPickerInput({ value, onChange, placeholder }: SecretPickerInputProps) { +export function SecretPickerInput({ value, onChange }: SecretPickerInputProps) { const [popoverOpen, setPopoverOpen] = useState(false); + const [showCreate, setShowCreate] = useState(false); + const [newName, setNewName] = useState(""); + const [newValue, setNewValue] = useState(""); const trpc = useTRPC(); + const queryClient = useQueryClient(); const environmentId = useEnvironmentStore((s) => s.selectedEnvironmentId); const secretsQuery = useQuery( @@ -45,9 +50,38 @@ export function SecretPickerInput({ value, onChange, placeholder }: SecretPicker ); const secrets = secretsQuery.data ?? []; + const createMutation = useMutation( + trpc.secret.create.mutationOptions({ + onSuccess: (created) => { + queryClient.invalidateQueries({ queryKey: trpc.secret.list.queryKey({ environmentId: environmentId! }) }); + onChange(makeSecretRef(created.name)); + setPopoverOpen(false); + resetCreateForm(); + toast.success(`Secret "${created.name}" created`); + }, + onError: (err) => toast.error(err.message), + }) + ); + + function resetCreateForm() { + setShowCreate(false); + setNewName(""); + setNewValue(""); + } + + function handleCreate() { + if (!environmentId || !newName.trim() || !newValue.trim()) return; + createMutation.mutate({ + environmentId, + name: newName.trim(), + value: newValue.trim(), + }); + } + const secretRef = typeof value === "string" ? parseSecretRef(value) : null; + const isPlaintextLegacy = !!value && !secretRef; - // When a secret reference is active, show a badge instead of the input + // State 1: Secret reference selected — show badge if (secretRef) { return (
@@ -69,59 +103,145 @@ export function SecretPickerInput({ value, onChange, placeholder }: SecretPicker ); } - return ( -
- onChange(e.target.value)} - placeholder={placeholder} - className="flex-1" - /> - {environmentId && ( - - + // Shared popover content + const pickerPopover = environmentId ? ( + { setPopoverOpen(open); if (!open) resetCreateForm(); }}> + + {isPlaintextLegacy ? ( + + ) : ( + + )} + + +
+

Select Secret

+

+ Choose a secret from this environment +

+
+
+ {secrets.length === 0 && !secretsQuery.isLoading ? ( +

+ No secrets yet +

+ ) : secretsQuery.isLoading ? ( +

+ Loading... +

+ ) : ( + secrets.map((secret) => ( + + )) + )} +
+
+ {showCreate ? ( +
+
+ + setNewName(e.target.value)} + placeholder="MY_SECRET_NAME" + className="h-8 text-xs font-mono" + /> +
+
+ + setNewValue(e.target.value)} + placeholder="secret value" + className="h-8 text-xs" + /> +
+
+ + +
+
+ ) : ( - - -
-

Use Secret

-

- Select a secret from this environment -

-
-
- {secrets.length === 0 ? ( -

- {secretsQuery.isLoading ? "Loading..." : "No secrets available"} -

- ) : ( - secrets.map((secret) => ( - - )) - )} -
-
- - )} -
- ); + )} +
+ + + ) : null; + + // State 2: Legacy plaintext value — show warning + picker + if (isPlaintextLegacy) { + return ( +
+
+ + + Plaintext value — select a secret to replace + + +
+ {pickerPopover ?? ( +

+ Select an environment to choose a replacement secret +

+ )} +
+ ); + } + + // State 3: No value — show picker button + return pickerPopover ??

No environment selected

; }