import { useEffect, useState, useCallback, useRef } from "react"; import { ShieldCheck, ShieldOff, Copy, ExternalLink, RefreshCw, LogOut, Terminal, LogIn } from "lucide-react"; import { api, type OAuthProvider } from "@/lib/api"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/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 [copiedId, setCopiedId] = 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 handleCopy = async (provider: OAuthProvider) => { try { await navigator.clipboard.writeText(provider.cli_command); setCopiedId(provider.id); onSuccess?.(`Copied: ${provider.cli_command}`); setTimeout(() => setCopiedId((v) => (v === provider.id ? null : v)), 1500); } catch { onError?.("Clipboard write failed — copy the command manually"); } }; 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 (
{/* Left: status icon + name + source */}
{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} )}
{/* Right: action buttons */}
{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)} /> )} ); }