diff --git a/frontend/src/app/components/Onboarding/flow/OnboardingFlow.tsx b/frontend/src/app/components/Onboarding/flow/OnboardingFlow.tsx index d8ed48f7..69355859 100644 --- a/frontend/src/app/components/Onboarding/flow/OnboardingFlow.tsx +++ b/frontend/src/app/components/Onboarding/flow/OnboardingFlow.tsx @@ -30,7 +30,6 @@ export const OnboardingFlow: React.FC<{ onExit: () => void }> = ({ onExit }) => const [persona, setPersona] = useState(null); const [name, setName] = useState(''); const [consent, setConsent] = useState(false); - const [connectedIds, setConnectedIds] = useState([]); const floor = useMemo(() => demoPayoff(persona), [persona]); // Background read-only profiling (only if they consented); upgrades the payoff in place when it lands. @@ -77,14 +76,7 @@ export const OnboardingFlow: React.FC<{ onExit: () => void }> = ({ onExit }) => case 'consent': return { setConsent(yes); setStep(yes ? 'connect' : 'payoff'); }} />; case 'connect': - return ( - setConnectedIds((ids) => (ids.includes(id) ? ids : [...ids, id]))} - onContinue={() => setStep('payoff')} - onSkip={() => setStep('payoff')} - /> - ); + return setStep('payoff')} onSkip={() => setStep('payoff')} />; case 'payoff': return ( ); - -// The 12-ray coral burst; drawn, not a font glyph, so it's crisp at any size. -export const Spark: React.FC<{ size?: number; color?: string }> = ({ size = 20, color = '#D97757' }) => { - const rays = []; - for (let i = 0; i < 12; i++) { - const a = (i * 30 * Math.PI) / 180; - rays.push( - , - ); - } - return ( - - {rays} - - ); -}; diff --git a/frontend/src/app/components/Onboarding/flow/OnboardingShell.tsx b/frontend/src/app/components/Onboarding/flow/OnboardingShell.tsx index cd6a7647..a8f64e6e 100644 --- a/frontend/src/app/components/Onboarding/flow/OnboardingShell.tsx +++ b/frontend/src/app/components/Onboarding/flow/OnboardingShell.tsx @@ -7,7 +7,6 @@ import { createPortal } from 'react-dom'; import { motion } from 'framer-motion'; import { useReducedMotion } from '@/shared/hooks/useReducedMotion'; import { useOnboardingSkin, ONBOARDING_EASE } from './onboardingSkin'; -import { Spark } from './OnboardingIcons'; interface Props { children: React.ReactNode; @@ -34,9 +33,7 @@ export const OnboardingShell: React.FC = ({ children, stepKey }) => { }} >
- - - + OpenSwarm OpenSwarm
diff --git a/frontend/src/app/components/Onboarding/flow/onboardingFlowTypes.ts b/frontend/src/app/components/Onboarding/flow/onboardingFlowTypes.ts index 2b91315b..7995f8f9 100644 --- a/frontend/src/app/components/Onboarding/flow/onboardingFlowTypes.ts +++ b/frontend/src/app/components/Onboarding/flow/onboardingFlowTypes.ts @@ -18,13 +18,6 @@ export interface PersonaOption { export type FlowStepId = 'help' | 'name' | 'consent' | 'connect' | 'payoff'; -export interface ConnectorOption { - id: string; - name: string; - description: string; - icon: IconName; -} - export interface PayoffIdea { id: string; icon: IconName; diff --git a/frontend/src/app/components/Onboarding/flow/steps/ConnectApps.tsx b/frontend/src/app/components/Onboarding/flow/steps/ConnectApps.tsx index 6b270b98..afc5bd50 100644 --- a/frontend/src/app/components/Onboarding/flow/steps/ConnectApps.tsx +++ b/frontend/src/app/components/Onboarding/flow/steps/ConnectApps.tsx @@ -1,46 +1,45 @@ -// D4: optional connect. The connectors the profiling agent may later read. Skippable; payoff still works. -// UI-only here: real OAuth (integrations.tsx / POST /{tool_id}/oauth/start) wires in a follow step. +// D4: optional connect. Reuses the REAL shared connector catalog (icons + metadata), the REAL +// connection state from toolsSlice, and the REAL startOAuth thunk. No hardcoded list, no fake toggle. -import React from 'react'; +import React, { useEffect } from 'react'; +import { useAppDispatch, useAppSelector } from '@/shared/hooks'; +import { INTEGRATIONS, Integration } from '@/shared/integrations/catalog'; +import { fetchTools, startOAuth, ToolDefinition } from '@/shared/state/toolsSlice'; import { useOnboardingSkin } from '../onboardingSkin'; -import { LineIcon } from '../OnboardingIcons'; import { Heading, Sub, PrimaryButton, GhostLink } from '../OnboardingAtoms'; -import type { ConnectorOption } from '../onboardingFlowTypes'; -const CONNECTORS: ConnectorOption[] = [ - { id: 'google', name: 'Google', description: 'Gmail, Calendar, Drive', icon: 'mail' }, - { id: 'notion', name: 'Notion', description: 'notes, docs, tasks', icon: 'doc' }, - { id: 'slack', name: 'Slack', description: 'messages, channels', icon: 'chat' }, -]; +// The connectors most useful for the profiling payoff; drawn from the real catalog by id. +const SHOWN_IDS = ['google-workspace', 'notion', 'slack']; -const Row: React.FC<{ option: ConnectorOption; connected: boolean; onConnect: () => void }> = ({ - option, - connected, - onConnect, -}) => { +function toolFor(integration: Integration, items: Record): ToolDefinition | undefined { + return Object.values(items).find((t) => t.name === integration.name); +} + +const Row: React.FC<{ integration: Integration }> = ({ integration }) => { const S = useOnboardingSkin(); + const dispatch = useAppDispatch(); + const items = useAppSelector((s) => s.tools.items); + const tool = toolFor(integration, items); + const connected = tool?.auth_status === 'connected'; + + const connect = async () => { + try { + const res = await dispatch(startOAuth(tool?.id ?? integration.id)).unwrap(); + if (res.auth_url) window.open(res.auth_url, '_blank'); + } catch { /* connect failed; leave as-is */ } + }; + return ( -
- - - +
+ {integration.icon}
-
{option.name}
-
{option.description}
+
{integration.name}
+
+ {connected && tool?.connected_account_email ? tool.connected_account_email : integration.description} +
{connected ? 'Connected' : 'Connect'} @@ -58,21 +58,29 @@ const Row: React.FC<{ option: ConnectorOption; connected: boolean; onConnect: () ); }; -export const ConnectApps: React.FC<{ - connectedIds: string[]; - onConnect: (id: string) => void; - onContinue: () => void; - onSkip: () => void; -}> = ({ connectedIds, onConnect, onContinue, onSkip }) => ( - <> - Connect what I can work with - optional, and I'll only ever read it to understand you -
- {CONNECTORS.map((c) => ( - onConnect(c.id)} /> - ))} -
- Continue - skip for now - -); +export const ConnectApps: React.FC<{ onContinue: () => void; onSkip: () => void }> = ({ onContinue, onSkip }) => { + const dispatch = useAppDispatch(); + const shown = INTEGRATIONS.filter((i) => SHOWN_IDS.includes(i.id)); + + // Load real connection state, and re-check when the user returns from the OAuth browser tab. + useEffect(() => { + dispatch(fetchTools()); + const onFocus = () => dispatch(fetchTools()); + window.addEventListener('focus', onFocus); + return () => window.removeEventListener('focus', onFocus); + }, [dispatch]); + + return ( + <> + Connect what I can work with + optional, and I'll only ever read it to understand you +
+ {shown.map((i) => ( + + ))} +
+ Continue + skip for now + + ); +}; diff --git a/frontend/src/app/pages/Tools/Tools.tsx b/frontend/src/app/pages/Tools/Tools.tsx index c1048263..c2d3065a 100644 --- a/frontend/src/app/pages/Tools/Tools.tsx +++ b/frontend/src/app/pages/Tools/Tools.tsx @@ -29,7 +29,7 @@ import { import { Skeleton } from '@/app/components/feedback/Loading'; import { useClaudeTokens } from '@/shared/styles/ThemeContext'; -import { Integration, INTEGRATIONS } from './integrations'; +import { Integration, INTEGRATIONS } from '@/shared/integrations/catalog'; import { CATEGORY_ORDER } from './toolsHelpers'; import ToolSection from './cards/ToolSection'; import BrowserPermissionCard from './cards/BrowserPermissionCard'; diff --git a/frontend/src/app/pages/Tools/cards/CustomToolCard.tsx b/frontend/src/app/pages/Tools/cards/CustomToolCard.tsx index 05248bd1..e1a10a50 100644 --- a/frontend/src/app/pages/Tools/cards/CustomToolCard.tsx +++ b/frontend/src/app/pages/Tools/cards/CustomToolCard.tsx @@ -23,7 +23,7 @@ import SecurityIcon from '@mui/icons-material/Security'; import RefreshIcon from '@mui/icons-material/Refresh'; import { ToolDefinition } from '@/shared/state/toolsSlice'; import { useClaudeTokens } from '@/shared/styles/ThemeContext'; -import { Integration } from '../integrations'; +import { Integration } from '@/shared/integrations/catalog'; import ServiceGroup from './ServiceGroup'; import CustomToolDevInfo from './CustomToolDevInfo'; import CustomToolConnect from './CustomToolConnect'; diff --git a/frontend/src/app/pages/Tools/cards/CustomToolConnect.tsx b/frontend/src/app/pages/Tools/cards/CustomToolConnect.tsx index 6adca560..5a195cbc 100644 --- a/frontend/src/app/pages/Tools/cards/CustomToolConnect.tsx +++ b/frontend/src/app/pages/Tools/cards/CustomToolConnect.tsx @@ -6,7 +6,7 @@ import LinkIcon from '@mui/icons-material/Link'; import CheckCircleIcon from '@mui/icons-material/CheckCircle'; import { ToolDefinition } from '@/shared/state/toolsSlice'; import { useClaudeTokens } from '@/shared/styles/ThemeContext'; -import { Integration } from '../integrations'; +import { Integration } from '@/shared/integrations/catalog'; interface CustomToolConnectProps { tool: ToolDefinition; diff --git a/frontend/src/app/pages/Tools/cards/IntegrationGalleryCard.tsx b/frontend/src/app/pages/Tools/cards/IntegrationGalleryCard.tsx index 5b1bcbc1..8e0442e1 100644 --- a/frontend/src/app/pages/Tools/cards/IntegrationGalleryCard.tsx +++ b/frontend/src/app/pages/Tools/cards/IntegrationGalleryCard.tsx @@ -8,7 +8,7 @@ import CircularProgress from '@mui/material/CircularProgress'; import Switch from '@mui/material/Switch'; import OpenInNewIcon from '@mui/icons-material/OpenInNew'; import { useClaudeTokens } from '@/shared/styles/ThemeContext'; -import { Integration } from '../integrations'; +import { Integration } from '@/shared/integrations/catalog'; interface IntegrationGalleryCardProps { integration: Integration; diff --git a/frontend/src/app/pages/Tools/cards/ServiceGroup.tsx b/frontend/src/app/pages/Tools/cards/ServiceGroup.tsx index aa919af7..618a2073 100644 --- a/frontend/src/app/pages/Tools/cards/ServiceGroup.tsx +++ b/frontend/src/app/pages/Tools/cards/ServiceGroup.tsx @@ -13,7 +13,7 @@ import VisibilityIcon from '@mui/icons-material/Visibility'; import PanToolIcon from '@mui/icons-material/PanTool'; import { ToolDefinition } from '@/shared/state/toolsSlice'; import { useClaudeTokens } from '@/shared/styles/ThemeContext'; -import { Integration } from '../integrations'; +import { Integration } from '@/shared/integrations/catalog'; const toDisplayName = (name: string, serviceName?: string) => { let display = name.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()); diff --git a/frontend/src/app/pages/Tools/dialogs/ToolDialogs.tsx b/frontend/src/app/pages/Tools/dialogs/ToolDialogs.tsx index b27b26da..36576d28 100644 --- a/frontend/src/app/pages/Tools/dialogs/ToolDialogs.tsx +++ b/frontend/src/app/pages/Tools/dialogs/ToolDialogs.tsx @@ -12,7 +12,7 @@ import CheckCircleIcon from '@mui/icons-material/CheckCircle'; import LinkIcon from '@mui/icons-material/Link'; import { McpServer } from '@/shared/state/mcpRegistrySlice'; import { useClaudeTokens } from '@/shared/styles/ThemeContext'; -import { Integration } from '../integrations'; +import { Integration } from '@/shared/integrations/catalog'; import { ToolForm } from '../toolsHelpers'; import McpConfigDialog from './McpConfigDialog'; diff --git a/frontend/src/app/pages/Tools/hooks/useToolConnections.ts b/frontend/src/app/pages/Tools/hooks/useToolConnections.ts index 68ca30ea..57d9c641 100644 --- a/frontend/src/app/pages/Tools/hooks/useToolConnections.ts +++ b/frontend/src/app/pages/Tools/hooks/useToolConnections.ts @@ -11,7 +11,7 @@ import { ToolDefinition, } from '@/shared/state/toolsSlice'; import { API_BASE } from '@/shared/config'; -import { Integration } from '../integrations'; +import { Integration } from '@/shared/integrations/catalog'; type Snackbar = { open: boolean; message: string; severity?: 'success' | 'error' }; diff --git a/frontend/src/app/pages/Tools/hooks/useToolsActions.ts b/frontend/src/app/pages/Tools/hooks/useToolsActions.ts index 5b0d1ba6..81890ae5 100644 --- a/frontend/src/app/pages/Tools/hooks/useToolsActions.ts +++ b/frontend/src/app/pages/Tools/hooks/useToolsActions.ts @@ -11,7 +11,7 @@ import { } from '@/shared/state/toolsSlice'; import { McpServer } from '@/shared/state/mcpRegistrySlice'; import { ToolForm, emptyForm } from '../toolsHelpers'; -import { Integration } from '../integrations'; +import { Integration } from '@/shared/integrations/catalog'; import { useToolConnections } from './useToolConnections'; import { useRegistryBrowser } from './useRegistryBrowser'; diff --git a/frontend/src/app/pages/Tools/integrations.tsx b/frontend/src/shared/integrations/catalog.tsx similarity index 100% rename from frontend/src/app/pages/Tools/integrations.tsx rename to frontend/src/shared/integrations/catalog.tsx