mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-30 15:03:57 +00:00
fix: surface expired Composio auth state (#1893)
Co-authored-by: honor2030 <19909783+honor2030@users.noreply.github.com> Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai>
This commit is contained in:
co-authored by
honor2030
Steven Enamakel
parent
c8cd15be91
commit
9400f77364
@@ -223,6 +223,23 @@ describe('<ComposioConnectModal>', () => {
|
||||
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(
|
||||
<ComposioConnectModal toolkit={mockToolkit} connection={connection} onClose={() => {}} />
|
||||
);
|
||||
|
||||
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:
|
||||
|
||||
@@ -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<Phase>(
|
||||
initiallyConnected ? 'connected' : initialState === 'pending' ? 'waiting' : 'idle'
|
||||
initiallyConnected
|
||||
? 'connected'
|
||||
: initiallyExpired
|
||||
? 'expired'
|
||||
: initialState === 'pending'
|
||||
? 'waiting'
|
||||
: 'idle'
|
||||
);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [connectUrl, setConnectUrl] = useState<string | null>(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 = (
|
||||
<div
|
||||
@@ -653,6 +672,27 @@ export default function ComposioConnectModal({
|
||||
</>
|
||||
)}
|
||||
|
||||
{phase === 'expired' && (
|
||||
<>
|
||||
<div className="rounded-xl border border-coral-200 bg-coral-50 p-3">
|
||||
<div className="flex items-center gap-2 text-sm font-medium text-coral-800">
|
||||
<div className="w-2 h-2 rounded-full bg-coral-500" />
|
||||
{toolkit.name} authorization expired
|
||||
</div>
|
||||
<p className="mt-2 text-xs leading-relaxed text-coral-700">
|
||||
Reconnect to re-enable {toolkit.name} tools. OpenHuman will keep this integration
|
||||
unavailable until you refresh OAuth access.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void handleConnect()}
|
||||
className="w-full rounded-xl bg-primary-500 text-white text-sm font-medium py-2.5 hover:bg-primary-600 transition-colors">
|
||||
Reconnect {toolkit.name}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{phase === 'connected' && (
|
||||
<>
|
||||
<div className="flex items-center gap-2 text-sm text-sage-700">
|
||||
@@ -706,7 +746,9 @@ export default function ComposioConnectModal({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setPhase(initiallyConnected ? 'connected' : 'idle');
|
||||
setPhase(
|
||||
initiallyConnected ? 'connected' : initiallyExpired ? 'expired' : 'idle'
|
||||
);
|
||||
setError(null);
|
||||
}}
|
||||
className="w-full rounded-xl border border-stone-200 bg-white text-stone-700 text-sm font-medium py-2 hover:bg-stone-50 transition-colors">
|
||||
|
||||
Reference in New Issue
Block a user