"use client"; import { useEffect, useRef, useState } from "react"; import { Loader2, RefreshCw, Trash2, UserCheck, ArrowRight, } from "lucide-react"; import { notify } from "@/components/ui/sonner"; import { getApiUrl } from "@/utils/api"; import { MixpanelEvent, trackEvent } from "@/utils/mixpanel"; import { usePathname, useRouter } from "next/navigation"; import { syncStoreAfterCodexSignOut } from "@/utils/storeHelpers"; import { DEFAULT_CODEX_MODEL, isSupportedCodexModel, } from "@/utils/codexModels"; import { isChatGptAuthRequiredResponse, normalizeChatGptAuthMessage, requestChatGptReauth, } from "@/utils/chatgptAuth"; interface CodexConfigProps { codexModel: string; onInputChange: (value: string | boolean, field: string) => void; onAuthStatusChange?: (authenticated: boolean) => void; } type AuthStatus = "checking" | "unauthenticated" | "polling" | "authenticated"; interface StatusResponse { status: string; account_id?: string; username?: string; email?: string; is_pro?: boolean; detail?: string; } export default function CodexConfig({ codexModel, onInputChange, onAuthStatusChange, }: CodexConfigProps) { const [authStatus, setAuthStatus] = useState("checking"); const [accountId, setAccountId] = useState(null); const [username, setUsername] = useState(null); const [email, setEmail] = useState(null); const [sessionId, setSessionId] = useState(null); const [manualCode, setManualCode] = useState(""); const [isExchanging, setIsExchanging] = useState(false); const [isLoggingOut, setIsLoggingOut] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false); const pollIntervalRef = useRef | null>(null); const pathname = usePathname(); const router = useRouter(); const stopPolling = () => { if (pollIntervalRef.current) { clearInterval(pollIntervalRef.current); pollIntervalRef.current = null; } }; useEffect(() => { checkCurrentAuthStatus(); return () => stopPolling(); }, []); useEffect(() => { onAuthStatusChange?.(authStatus === "authenticated"); }, [authStatus, onAuthStatusChange]); useEffect(() => { if (codexModel && !isSupportedCodexModel(codexModel)) { onInputChange(DEFAULT_CODEX_MODEL, "codex_model"); } }, [codexModel, onInputChange]); const applyProfile = (data: Partial) => { setAccountId(data.account_id ?? null); setUsername(data.username ?? null); setEmail(data.email ?? null); }; const checkCurrentAuthStatus = async () => { try { const res = await fetch(getApiUrl("/api/v1/ppt/codex/auth/status")); if (!res.ok) { setAuthStatus("unauthenticated"); applyProfile({}); return; } const data: StatusResponse = await res.json(); if (data.status === "authenticated") { onInputChange('codex', 'LLM'); if (!isSupportedCodexModel(codexModel)) { onInputChange(DEFAULT_CODEX_MODEL, 'codex_model'); } setAuthStatus("authenticated"); applyProfile(data); } else { setAuthStatus("unauthenticated"); applyProfile({}); } } catch { setAuthStatus("unauthenticated"); applyProfile({}); } }; const handleSignIn = async () => { try { trackEvent(MixpanelEvent.Codex_SignIn_API_Call); onInputChange('codex', 'LLM'); const res = await fetch(getApiUrl("/api/v1/ppt/codex/auth/initiate"), { method: "POST", }); if (!res.ok) throw new Error("Failed to initiate auth"); const data = await res.json(); const { session_id, url } = data; setSessionId(session_id); setAuthStatus("polling"); window.open(url, "_blank", "noopener,noreferrer"); pollIntervalRef.current = setInterval(async () => { try { const pollRes = await fetch( getApiUrl(`/api/v1/ppt/codex/auth/status/${session_id}`) ); if (!pollRes.ok) return; const pollData: StatusResponse = await pollRes.json(); if (pollData.status === "success") { trackEvent(MixpanelEvent.Codex_SignIn_Completed, { method: "browser_poll" }); stopPolling(); setAuthStatus("authenticated"); applyProfile(pollData); setSessionId(null); if (!isSupportedCodexModel(codexModel)) { onInputChange(DEFAULT_CODEX_MODEL, "codex_model"); } notify.success( "Signed in to ChatGPT", "Your ChatGPT account is connected and ready to use." ); } else if (pollData.status === "failed") { trackEvent(MixpanelEvent.Codex_SignIn_Failed, { method: "browser_poll" }); stopPolling(); setAuthStatus("unauthenticated"); applyProfile({}); notify.error( "Sign-in failed", "Authentication did not complete. Please try signing in again." ); } } catch { // keep polling on transient errors } }, 2000); } catch (err) { trackEvent(MixpanelEvent.Codex_SignIn_Failed, { method: "initiate" }); notify.error( "Sign-in failed", "Could not start the sign-in flow. Please try again." ); setAuthStatus("unauthenticated"); applyProfile({}); } }; const handleManualExchange = async () => { if (!sessionId || !manualCode.trim()) return; setIsExchanging(true); try { const res = await fetch(getApiUrl("/api/v1/ppt/codex/auth/exchange"), { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ session_id: sessionId, code: manualCode.trim() }), }); if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error(err.detail || "Exchange failed"); } const data = await res.json(); trackEvent(MixpanelEvent.Codex_SignIn_Completed, { method: "manual_exchange" }); stopPolling(); setAuthStatus("authenticated"); applyProfile(data); setSessionId(null); setManualCode(""); if (!isSupportedCodexModel(codexModel)) { onInputChange(DEFAULT_CODEX_MODEL, "codex_model"); } notify.success( "Signed in to ChatGPT", "Your ChatGPT account is connected and ready to use." ); } catch (err: any) { trackEvent(MixpanelEvent.Codex_SignIn_Failed, { method: "manual_exchange" }); notify.error( "Sign-in failed", err.message || "The verification code could not be accepted. Please try again." ); } finally { setIsExchanging(false); } }; const handleCancelPolling = () => { trackEvent(MixpanelEvent.Codex_SignIn_Cancelled); stopPolling(); setSessionId(null); setManualCode(""); setAuthStatus("unauthenticated"); }; const handleSignOut = async () => { setIsLoggingOut(true); try { await fetch(getApiUrl("/api/v1/ppt/codex/auth/logout"), { method: "POST" }); trackEvent(MixpanelEvent.Codex_Signed_Out); setAuthStatus("unauthenticated"); setAccountId(null); setUsername(null); setEmail(null); onInputChange("", "codex_model"); onInputChange("", "CODEX_ACCESS_TOKEN"); onInputChange("", "CODEX_REFRESH_TOKEN"); onInputChange("", "CODEX_TOKEN_EXPIRES"); onInputChange("", "CODEX_ACCOUNT_ID"); onInputChange("", "CODEX_USERNAME"); onInputChange("", "CODEX_EMAIL"); onInputChange(false, "CODEX_IS_PRO"); syncStoreAfterCodexSignOut(); router.replace(pathname.startsWith("/settings") ? "/settings" : "/"); notify.success( "Signed out", "You have been disconnected from ChatGPT." ); } catch { notify.error( "Sign-out failed", "Could not disconnect from ChatGPT. Please try again." ); } finally { setIsLoggingOut(false); } }; const handleRefreshToken = async () => { setIsRefreshing(true); try { const res = await fetch(getApiUrl("/api/v1/ppt/codex/auth/refresh"), { method: "POST", }); if (!res.ok) { let errorData: { detail?: unknown; message?: string; error?: string } | null = null; let message = "Your ChatGPT session could not be renewed. Please sign in again."; try { const parsedError: { detail?: unknown; message?: string; error?: string } = await res.json(); errorData = parsedError; message = (typeof parsedError.detail === "string" && parsedError.detail) || parsedError.message || parsedError.error || message; } catch {} if (isChatGptAuthRequiredResponse(res, errorData, message)) { requestChatGptReauth({ message: normalizeChatGptAuthMessage(message), source: "codex-refresh", }); return; } throw new Error(message); } const data = await res.json(); applyProfile(data); notify.success( "Session refreshed", "Your ChatGPT connection was renewed successfully." ); } catch { notify.error( "Session refresh failed", "Your ChatGPT session could not be renewed. Please sign in again." ); setAuthStatus("unauthenticated"); applyProfile({}); } finally { setIsRefreshing(false); } }; if (authStatus === "checking") { return (

Checking status

Verifying your ChatGPT connection…

); } if (authStatus === "polling") { return (

Waiting for sign-in

Complete sign-in in the browser tab we opened.

Paste redirect URL or code if you were not redirected automatically

setManualCode(e.target.value)} />
); } if (authStatus === "authenticated") { return (
openai Logo

{username || email || (accountId ? `Account ${accountId}` : "ChatGPT Account")}

{email && username && (

{email}

)} {!email && accountId && (

ID: {accountId}

)}

Signed in to ChatGPT

); } return ( ); }