From af53d51e58947928049fc6ddda62d24cdb7c8c02 Mon Sep 17 00:00:00 2001 From: Jon Saad-Falcon <41205309+jonsaadfalcon@users.noreply.github.com> Date: Fri, 27 Mar 2026 15:07:03 -0700 Subject: [PATCH] feat: add StepByStepPanel with per-connector setup instructions Adds StepByStepPanel component to SourceConnectFlow that renders numbered setup steps with optional links and input fields for each connector. Wires it as the primary panel for any connector with steps defined, with existing OAuth/Local/Filesystem panels as fallbacks. Also updates connectors.ts to use ConnectorMeta (with SetupStep/inputFields) as the canonical type and exports SourceCard as a backward-compatible alias. Co-Authored-By: Claude Sonnet 4.6 --- .../components/setup/SourceConnectFlow.tsx | 181 +++++++++++++++++- frontend/src/types/connectors.ts | 11 +- 2 files changed, 182 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/setup/SourceConnectFlow.tsx b/frontend/src/components/setup/SourceConnectFlow.tsx index 7ca3f52d..b977b275 100644 --- a/frontend/src/components/setup/SourceConnectFlow.tsx +++ b/frontend/src/components/setup/SourceConnectFlow.tsx @@ -9,7 +9,7 @@ import { } from 'lucide-react'; import { SOURCE_CATALOG } from '../../types/connectors'; import { connectSource } from '../../lib/connectors-api'; -import type { ConnectRequest } from '../../types/connectors'; +import type { ConnectRequest, ConnectorMeta } from '../../types/connectors'; // --------------------------------------------------------------------------- // Types @@ -285,6 +285,178 @@ function LocalPanel({ ); } +// --------------------------------------------------------------------------- +// StepByStepPanel — per-connector numbered setup instructions +// --------------------------------------------------------------------------- + +function StepByStepPanel({ + connector, + onConnect, + onSkip, + isConnecting, +}: { + connector: ConnectorMeta; + onConnect: (req: ConnectRequest) => void; + onSkip: () => void; + isConnecting: boolean; +}) { + const [inputs, setInputs] = useState>({}); + const steps = connector.steps || []; + const fields = connector.inputFields || []; + + const updateInput = (name: string, value: string) => { + setInputs((prev) => ({ ...prev, [name]: value })); + }; + + const handleSubmit = () => { + const req: ConnectRequest = {}; + for (const field of fields) { + if (field.name === 'email') req.email = inputs.email; + else if (field.name === 'password') req.password = inputs.password; + else if (field.name === 'token') req.token = inputs.token; + else if (field.name === 'path') req.path = inputs.path; + } + // For email+password connectors, also set token as email:password + if (req.email && req.password) { + req.token = `${req.email}:${req.password}`; + req.code = req.token; + } + if (req.token && !req.code) { + req.code = req.token; + } + onConnect(req); + }; + + const allFilled = fields.every((f) => inputs[f.name]?.trim()); + + return ( +
+
+ + {connector.icon === 'Mail' ? '\u2709\uFE0F' : + connector.icon === 'Hash' ? '#\uFE0F\u20E3' : + connector.icon === 'FileText' ? '\uD83D\uDCC4' : + connector.icon === 'Mic' ? '\uD83C\uDF99\uFE0F' : + connector.icon === 'FolderOpen' ? '\uD83D\uDCC1' : '\uD83D\uDD17'} + + + {connector.display_name} + +
+ + {steps.map((step, i) => ( +
+
+ STEP {i + 1} +
+
+ {step.label} +
+ {step.url && ( + + {step.urlLabel || 'Open'} → + + )} +
+ ))} + + {fields.length > 0 && ( +
+ {fields.map((field) => ( + updateInput(field.name, e.target.value)} + placeholder={field.placeholder} + type={field.type || 'text'} + style={{ + width: '100%', + padding: '8px 10px', + background: 'var(--color-bg-secondary)', + border: '1px solid var(--color-border)', + borderRadius: 4, + color: 'var(--color-text)', + fontSize: 13, + marginBottom: 8, + boxSizing: 'border-box', + }} + /> + ))} +
+ )} + +
+ Read-only access · No data leaves your device +
+ +
+ + +
+
+ ); +} + // --------------------------------------------------------------------------- // SourceConnectFlow // --------------------------------------------------------------------------- @@ -392,6 +564,13 @@ export function SourceConnectFlow({ Connected + ) : activeCard.steps ? ( + handleConnect(activeEntry.id, req)} + onSkip={() => handleSkip(activeEntry.id)} + isConnecting={activeEntry.state === 'connecting'} + /> ) : activeCard.auth_type === 'filesystem' ? (