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' ? (