Unified · Split
| @@ -1,12 +1,12 @@ | ||
| 1 | 1 | import { useCallback, useEffect, useMemo, useRef, useState } from "react"; |
| 2 | 2 | import { useAtomSet, useAtomValue, Result } from "@effect-atom/atom-react"; |
| 3 | 3 | |
| 4 | −import { secretsAtom, setSecret } from "@executor/react/api/atoms"; | |
| 4 | +import { secretsAtom } from "@executor/react/api/atoms"; | |
| 5 | 5 | import { usePendingSources } from "@executor/react/api/optimistic"; |
| 6 | −import { secretWriteKeys, sourceWriteKeys } from "@executor/react/api/reactivity-keys"; | |
| 6 | +import { sourceWriteKeys } from "@executor/react/api/reactivity-keys"; | |
| 7 | 7 | import { useScope } from "@executor/react/api/scope-context"; |
| 8 | −import { SecretPicker, type SecretPickerSecret } from "@executor/react/plugins/secret-picker"; | |
| 9 | −import { SecretId } from "@executor/sdk"; | |
| 8 | +import type { SecretPickerSecret } from "@executor/react/plugins/secret-picker"; | |
| 9 | +import { CreatableSecretPicker } from "@executor/react/plugins/secret-header-auth"; | |
| 10 | 10 | import { Badge } from "@executor/react/components/badge"; |
| 11 | 11 | import { Button } from "@executor/react/components/button"; |
| 12 | 12 | import { |
| @@ -42,7 +42,6 @@ import { · 29 unmodified lines | ||
| 42 | 42 | import { FilterTabs } from "@executor/react/components/filter-tabs"; |
| 43 | 43 | import { FloatActions } from "@executor/react/components/float-actions"; |
| 44 | 44 | import { Input } from "@executor/react/components/input"; |
| 45 | −import { Label } from "@executor/react/components/label"; | |
| 46 | 45 | import { RadioGroup, RadioGroupItem } from "@executor/react/components/radio-group"; |
| 47 | 46 | import { IOSSpinner, Spinner } from "@executor/react/components/spinner"; |
| 48 | 47 | import { addGoogleDiscoverySource, probeGoogleDiscovery } from "./atoms"; |
| @@ -63,97 +62,6 @@ const GOOGLE_EXTRA_AUTHORIZATION_PARAMS = { · 14 unmodified lines | ||
| 63 | 62 | |
| 64 | 63 | type GoogleAuthKind = "none" | "oauth2"; |
| 65 | 64 | |
| 66 | −// --------------------------------------------------------------------------- | |
| 67 | −// Inline secret creation | |
| 68 | −// --------------------------------------------------------------------------- | |
| 69 | − | |
| 70 | −function InlineCreateSecret(props: { | |
| 71 | − headerName: string; | |
| 72 | − suggestedId: string; | |
| 73 | − onCreated: (secretId: string) => void; | |
| 74 | − onCancel: () => void; | |
| 75 | −}) { | |
| 76 | − const [secretId, setSecretIdValue] = useState(props.suggestedId); | |
| 77 | − const [secretName, setSecretName] = useState(props.headerName); | |
| 78 | − const [secretValue, setSecretValue] = useState(""); | |
| 79 | − const [saving, setSaving] = useState(false); | |
| 80 | − const [error, setError] = useState<string | null>(null); | |
| 81 | − const scopeId = useScope(); | |
| 82 | − const doSet = useAtomSet(setSecret, { mode: "promise" }); | |
| 83 | − | |
| 84 | − const handleSave = async () => { | |
| 85 | − if (!secretId.trim() || !secretValue.trim()) return; | |
| 86 | − setSaving(true); | |
| 87 | − setError(null); | |
| 88 | − try { | |
| 89 | − await doSet({ | |
| 90 | − path: { scopeId }, | |
| 91 | − payload: { | |
| 92 | − id: SecretId.make(secretId.trim()), | |
| 93 | − name: secretName.trim() || secretId.trim(), | |
| 94 | − value: secretValue.trim(), | |
| 95 | − }, | |
| 96 | − reactivityKeys: [...secretWriteKeys], | |
| 97 | − }); | |
| 98 | − props.onCreated(secretId.trim()); | |
| 99 | − } catch (e) { | |
| 100 | − setError(e instanceof Error ? e.message : "Failed to save secret"); | |
| 101 | − setSaving(false); | |
| 102 | − } | |
| 103 | − }; | |
| 104 | − | |
| 105 | − return ( | |
| 106 | − <div className="rounded-lg border border-primary/20 bg-primary/[0.02] p-3 space-y-2.5"> | |
| 107 | − <p className="text-[11px] font-semibold text-primary tracking-wide uppercase">New secret</p> | |
| 108 | − <div className="grid grid-cols-2 gap-2"> | |
| 109 | − <div className="space-y-1"> | |
| 110 | − <Label className="text-[10px] uppercase tracking-wider text-muted-foreground">ID</Label> | |
| 111 | − <Input | |
| 112 | − value={secretId} | |
| 113 | − onChange={(e) => setSecretIdValue((e.target as HTMLInputElement).value)} | |
| 114 | − placeholder="google-client-secret" | |
| 115 | − className="h-8 text-xs font-mono" | |
| 116 | − /> | |
| 117 | − </div> | |
| 118 | − <div className="space-y-1"> | |
| 119 | − <Label className="text-[10px] uppercase tracking-wider text-muted-foreground"> | |
| 120 | − Label | |
| 121 | − </Label> | |
| 122 | − <Input | |
| 123 | − value={secretName} | |
| 124 | − onChange={(e) => setSecretName((e.target as HTMLInputElement).value)} | |
| 125 | − placeholder="Client Secret" | |
| 126 | − className="h-8 text-xs" | |
| 127 | − /> | |
| 128 | − </div> | |
| 129 | − </div> | |
| 130 | − <div className="space-y-1"> | |
| 131 | − <Label className="text-[10px] uppercase tracking-wider text-muted-foreground">Value</Label> | |
| 132 | − <Input | |
| 133 | − type="password" | |
| 134 | − value={secretValue} | |
| 135 | − onChange={(e) => setSecretValue((e.target as HTMLInputElement).value)} | |
| 136 | − placeholder="paste your client secret…" | |
| 137 | − className="h-8 text-xs font-mono" | |
| 138 | − /> | |
| 139 | − </div> | |
| 140 | − {error && <p className="text-[11px] text-destructive">{error}</p>} | |
| 141 | − <div className="flex justify-end gap-1.5 pt-0.5"> | |
| 142 | − <Button variant="outline" size="xs" onClick={props.onCancel}> | |
| 143 | − Cancel | |
| 144 | − </Button> | |
| 145 | − <Button | |
| 146 | − size="xs" | |
| 147 | − onClick={handleSave} | |
| 148 | − disabled={!secretId.trim() || !secretValue.trim() || saving} | |
| 149 | − > | |
| 150 | − {saving ? "Saving…" : "Create and use"} | |
| 151 | − </Button> | |
| 152 | − </div> | |
| 153 | − </div> | |
| 154 | − ); | |
| 155 | −} | |
| 156 | − | |
| 157 | 65 | // --------------------------------------------------------------------------- |
| 158 | 66 | // Client secret field with inline creation |
| 159 | 67 | // --------------------------------------------------------------------------- |
| @@ -161,48 +69,27 @@ function InlineCreateSecret(props: { · 1 unmodified line | ||
| 161 | 69 | function SecretBackedField(props: { |
| 162 | 70 | label: string; |
| 163 | 71 | suggestedSecretId: string; |
| 164 | − headerName: string; | |
| 165 | 72 | secretId: string | null; |
| 166 | 73 | onSelect: (secretId: string | null) => void; |
| 167 | 74 | secretList: readonly SecretPickerSecret[]; |
| 168 | 75 | placeholder: string; |
| 169 | 76 | clearable?: boolean; |
| 170 | 77 | }) { |
| 171 | − const [creating, setCreating] = useState(false); | |
| 172 | 78 | const { label, secretId, onSelect, secretList, placeholder, clearable = true } = props; |
| 173 | 79 | |
| 174 | − if (creating) { | |
| 175 | − return ( | |
| 176 | − <div className="space-y-2"> | |
| 177 | − <Label>{label}</Label> | |
| 178 | − <InlineCreateSecret | |
| 179 | − headerName={props.headerName} | |
| 180 | − suggestedId={props.suggestedSecretId} | |
| 181 | − onCreated={(id) => { | |
| 182 | − onSelect(id); | |
| 183 | − setCreating(false); | |
| 184 | − }} | |
| 185 | − onCancel={() => setCreating(false)} | |
| 186 | − /> | |
| 187 | − </div> | |
| 188 | − ); | |
| 189 | − } | |
| 190 | − | |
| 191 | 80 | return ( |
| 192 | 81 | <div className="space-y-2"> |
| 193 | − <Label>{label}</Label> | |
| 194 | 82 | <div className="flex items-center gap-2"> |
| 195 | 83 | <div className="flex-1 min-w-0"> |
| 196 | − <SecretPicker | |
| 84 | + <CreatableSecretPicker | |
| 197 | 85 | value={secretId} |
| 198 | − onSelect={onSelect} | |
| 86 | + onSelect={(id) => onSelect(id)} | |
| 199 | 87 | secrets={secretList} |
| 200 | 88 | placeholder={placeholder} |
| 89 | + suggestedId={props.suggestedSecretId} | |
| 90 | + secretLabel={label} | |
| 201 | 91 | /> |
| 202 | 92 | </div> |
| 203 | − <Button variant="outline" size="sm" className="shrink-0" onClick={() => setCreating(true)}> | |
| 204 | − + New | |
| 205 | − </Button> | |
| 206 | 93 | {clearable && secretId && ( |
| 207 | 94 | <Button variant="outline" onClick={() => onSelect(null)}> |
| 208 | 95 | Clear |
| @@ -725,7 +612,6 @@ export default function AddGoogleDiscoverySource(props: { · 516 unmodified lines | ||
| 725 | 612 | <div className="space-y-3 rounded-xl border border-border bg-card px-4 py-4"> |
| 726 | 613 | <SecretBackedField |
| 727 | 614 | label="OAuth Client ID" |
| 728 | − headerName="Client ID" | |
| 729 | 615 | suggestedSecretId="google-oauth-client-id" |
| 730 | 616 | secretId={clientIdSecretId} |
| 731 | 617 | onSelect={setClientIdSecretId} |
| @@ -735,7 +621,6 @@ export default function AddGoogleDiscoverySource(props: { · 3 unmodified lines | ||
| 735 | 621 | /> |
| 736 | 622 | <SecretBackedField |
| 737 | 623 | label="OAuth Client Secret" |
| 738 | − headerName="Client Secret" | |
| 739 | 624 | suggestedSecretId="google-oauth-client-secret" |
| 740 | 625 | secretId={clientSecretSecretId} |
| 741 | 626 | onSelect={setClientSecretSecretId} |