diff --git a/app/src/components/composio/ComposioConnectModal.test.tsx b/app/src/components/composio/ComposioConnectModal.test.tsx index adf6dbe3c..e41c1646e 100644 --- a/app/src/components/composio/ComposioConnectModal.test.tsx +++ b/app/src/components/composio/ComposioConnectModal.test.tsx @@ -223,6 +223,23 @@ describe('', () => { expect(screen.queryByText('(oxox)')).not.toBeInTheDocument(); }); + it('shows an expired-auth recovery state with a reconnect CTA', () => { + const connection: ComposioConnection = { + id: 'ca_expired', + toolkit: 'gmail', + status: 'EXPIRED', + }; + + render( + {}} /> + ); + + expect(screen.getByText(/Gmail authorization expired/i)).toBeInTheDocument(); + expect(screen.getByText(/Reconnect to re-enable Gmail tools/i)).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /Reconnect Gmail/i })).toBeInTheDocument(); + expect(screen.queryByText(/ca_expired/)).not.toBeInTheDocument(); + }); + // ── Connect flow → openUrl(connectUrl) ─────────────────────────── // // Verifies the end-to-end OAuth handoff plumbing for #1710: diff --git a/app/src/components/composio/ComposioConnectModal.tsx b/app/src/components/composio/ComposioConnectModal.tsx index c7db39f94..4c7d5c098 100644 --- a/app/src/components/composio/ComposioConnectModal.tsx +++ b/app/src/components/composio/ComposioConnectModal.tsx @@ -127,6 +127,7 @@ type Phase = | 'authorizing' | 'waiting' | 'connected' + | 'expired' | 'disconnecting' | 'error'; @@ -156,8 +157,15 @@ export default function ComposioConnectModal({ const initialState = deriveComposioState(connection); const initiallyConnected = initialState === 'connected'; + const initiallyExpired = initialState === 'expired'; const [phase, setPhase] = useState( - initiallyConnected ? 'connected' : initialState === 'pending' ? 'waiting' : 'idle' + initiallyConnected + ? 'connected' + : initiallyExpired + ? 'expired' + : initialState === 'pending' + ? 'waiting' + : 'idle' ); const [error, setError] = useState(null); const [connectUrl, setConnectUrl] = useState(null); @@ -255,6 +263,12 @@ export default function ComposioConnectModal({ setError(`Connection failed (status: ${hit.status}).`); return; } + if (state === 'expired') { + stopPolling(); + setPhase('expired'); + setError(null); + return; + } } } catch (err) { // Swallow transient errors during polling — we'll retry on next tick. @@ -481,7 +495,12 @@ export default function ComposioConnectModal({ if (e.target === e.currentTarget) onClose(); }; - const headerTitle = phase === 'connected' ? `Manage ${toolkit.name}` : `Connect ${toolkit.name}`; + const headerTitle = + phase === 'connected' + ? `Manage ${toolkit.name}` + : phase === 'expired' + ? `Reconnect ${toolkit.name}` + : `Connect ${toolkit.name}`; const modalContent = (
)} + {phase === 'expired' && ( + <> +
+
+
+ {toolkit.name} authorization expired +
+

+ Reconnect to re-enable {toolkit.name} tools. OpenHuman will keep this integration + unavailable until you refresh OAuth access. +

+
+ + + )} + {phase === 'connected' && ( <>
@@ -706,7 +746,9 @@ export default function ComposioConnectModal({ )}