History

Use shared secret picker for Google Discovery (#450)

Rhys Sullivan committed 6 months ago

Commit 9638cc31 parent a99aceb1 file · +8 −123Browse files

Unified · Split

packages/plugins/google-discovery/src/react/AddGoogleDiscoverySource.tsx+8−123
@@ -1,12 +1,12 @@
11 import { useCallback, useEffect, useMemo, useRef, useState } from "react";
22 import { useAtomSet, useAtomValue, Result } from "@effect-atom/atom-react";
33
4−import { secretsAtom, setSecret } from "@executor/react/api/atoms";
4+import { secretsAtom } from "@executor/react/api/atoms";
55 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";
77 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";
1010 import { Badge } from "@executor/react/components/badge";
1111 import { Button } from "@executor/react/components/button";
1212 import {
@@ -42,7 +42,6 @@ import { · 29 unmodified lines
4242 import { FilterTabs } from "@executor/react/components/filter-tabs";
4343 import { FloatActions } from "@executor/react/components/float-actions";
4444 import { Input } from "@executor/react/components/input";
45−import { Label } from "@executor/react/components/label";
4645 import { RadioGroup, RadioGroupItem } from "@executor/react/components/radio-group";
4746 import { IOSSpinner, Spinner } from "@executor/react/components/spinner";
4847 import { addGoogleDiscoverySource, probeGoogleDiscovery } from "./atoms";
@@ -63,97 +62,6 @@ const GOOGLE_EXTRA_AUTHORIZATION_PARAMS = { · 14 unmodified lines
6362
6463 type GoogleAuthKind = "none" | "oauth2";
6564
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−
15765 // ---------------------------------------------------------------------------
15866 // Client secret field with inline creation
15967 // ---------------------------------------------------------------------------
@@ -161,48 +69,27 @@ function InlineCreateSecret(props: { · 1 unmodified line
16169 function SecretBackedField(props: {
16270 label: string;
16371 suggestedSecretId: string;
164− headerName: string;
16572 secretId: string | null;
16673 onSelect: (secretId: string | null) => void;
16774 secretList: readonly SecretPickerSecret[];
16875 placeholder: string;
16976 clearable?: boolean;
17077 }) {
171− const [creating, setCreating] = useState(false);
17278 const { label, secretId, onSelect, secretList, placeholder, clearable = true } = props;
17379
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−
19180 return (
19281 <div className="space-y-2">
193− <Label>{label}</Label>
19482 <div className="flex items-center gap-2">
19583 <div className="flex-1 min-w-0">
196− <SecretPicker
84+ <CreatableSecretPicker
19785 value={secretId}
198− onSelect={onSelect}
86+ onSelect={(id) => onSelect(id)}
19987 secrets={secretList}
20088 placeholder={placeholder}
89+ suggestedId={props.suggestedSecretId}
90+ secretLabel={label}
20191 />
20292 </div>
203− <Button variant="outline" size="sm" className="shrink-0" onClick={() => setCreating(true)}>
204− + New
205− </Button>
20693 {clearable && secretId && (
20794 <Button variant="outline" onClick={() => onSelect(null)}>
20895 Clear
@@ -725,7 +612,6 @@ export default function AddGoogleDiscoverySource(props: { · 516 unmodified lines
725612 <div className="space-y-3 rounded-xl border border-border bg-card px-4 py-4">
726613 <SecretBackedField
727614 label="OAuth Client ID"
728− headerName="Client ID"
729615 suggestedSecretId="google-oauth-client-id"
730616 secretId={clientIdSecretId}
731617 onSelect={setClientIdSecretId}
@@ -735,7 +621,6 @@ export default function AddGoogleDiscoverySource(props: { · 3 unmodified lines
735621 />
736622 <SecretBackedField
737623 label="OAuth Client Secret"
738− headerName="Client Secret"
739624 suggestedSecretId="google-oauth-client-secret"
740625 secretId={clientSecretSecretId}
741626 onSelect={setClientSecretSecretId}