import { useEffect, useState, useCallback, useRef } from "react"; import { ShieldCheck, ShieldOff, ExternalLink, RefreshCw, LogOut, Terminal, LogIn, } from "lucide-react"; import { api, type OAuthProvider } from "@/lib/api"; import { Button } from "@nous-research/ui/ui/components/button"; import { CopyButton } from "@nous-research/ui/ui/components/command-block"; import { Spinner } from "@nous-research/ui/ui/components/spinner"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Badge } from "@nous-research/ui/ui/components/badge"; import { OAuthLoginModal } from "@/components/OAuthLoginModal"; import { useI18n } from "@/i18n"; interface Props { onError?: (msg: string) => void; onSuccess?: (msg: string) => void; } function formatExpiresAt( expiresAt: string | null | undefined, expiresInTemplate: string, ): string | null { if (!expiresAt) return null; try { const dt = new Date(expiresAt); if (Number.isNaN(dt.getTime())) return null; const now = Date.now(); const diff = dt.getTime() - now; if (diff < 0) return "expired"; const mins = Math.floor(diff / 60_000); if (mins < 60) return expiresInTemplate.replace("{time}", `${mins}m`); const hours = Math.floor(mins / 60); if (hours < 24) return expiresInTemplate.replace("{time}", `${hours}h`); const days = Math.floor(hours / 24); return expiresInTemplate.replace("{time}", `${days}d`); } catch { return null; } } export function OAuthProvidersCard({ onError, onSuccess }: Props) { const [providers, setProviders] = useState(null); const [loading, setLoading] = useState(true); const [busyId, setBusyId] = useState(null); const [loginFor, setLoginFor] = useState(null); const { t } = useI18n(); const onErrorRef = useRef(onError); onErrorRef.current = onError; const refresh = useCallback(() => { setLoading(true); api .getOAuthProviders() .then((resp) => setProviders(resp.providers)) .catch((e) => onErrorRef.current?.(`Failed to load providers: ${e}`)) .finally(() => setLoading(false)); }, []); useEffect(() => { refresh(); }, [refresh]); const handleDisconnect = async (provider: OAuthProvider) => { if (!confirm(`${t.oauth.disconnect} ${provider.name}?`)) { return; } setBusyId(provider.id); try { await api.disconnectOAuthProvider(provider.id); onSuccess?.(`${provider.name} ${t.oauth.disconnect.toLowerCase()}ed`); refresh(); } catch (e) { onError?.(`${t.oauth.disconnect} failed: ${e}`); } finally { setBusyId(null); } }; const connectedCount = providers?.filter((p) => p.status.logged_in).length ?? 0; const totalCount = providers?.length ?? 0; return (
{t.oauth.providerLogins}
{t.oauth.description .replace("{connected}", String(connectedCount)) .replace("{total}", String(totalCount))}
{loading && providers === null && (
)} {providers && providers.length === 0 && (

{t.oauth.noProviders}

)}
{providers?.map((p) => { const expiresLabel = formatExpiresAt( p.status.expires_at, t.oauth.expiresIn, ); const isBusy = busyId === p.id; return (
{p.status.logged_in ? ( ) : ( )}
{p.name} {t.oauth.flowLabels[p.flow]} {p.status.logged_in && ( {t.oauth.connected} )} {expiresLabel === "expired" && ( {t.oauth.expired} )} {expiresLabel && expiresLabel !== "expired" && ( {expiresLabel} )}
{p.status.logged_in && p.status.token_preview && ( token {p.status.token_preview} {p.status.source_label && ( {" "} ยท {p.status.source_label} )} )} {!p.status.logged_in && ( {t.oauth.notConnected.split("{command}")[0]} {p.cli_command} {t.oauth.notConnected.split("{command}")[1]} )} {p.status.error && ( {p.status.error} )}
{p.docs_url && ( )} {!p.status.logged_in && p.flow !== "external" && ( )} {!p.status.logged_in && ( )} {p.status.logged_in && p.flow !== "external" && ( )} {p.status.logged_in && p.flow === "external" && ( {t.oauth.managedExternally} )}
); })}
{loginFor && ( { setLoginFor(null); refresh(); }} onSuccess={(msg) => onSuccess?.(msg)} onError={(msg) => onError?.(msg)} /> )}
); }