import React, { useCallback, useEffect, useState } from 'react'; import { checkIsGitClean, checkNeedsClaudeAiLogin } from 'src/utils/background/remote/preconditions.js'; import { gracefulShutdownSync } from 'src/utils/gracefulShutdown.js'; import { Box, Text } from '@anthropic/ink'; import { ConsoleOAuthFlow } from './ConsoleOAuthFlow.js'; import { Select } from './CustomSelect/index.js'; import { Dialog } from '@anthropic/ink'; import { TeleportStash } from './TeleportStash.js'; export type TeleportLocalErrorType = 'needsLogin' | 'needsGitStash'; type TeleportErrorProps = { onComplete: () => void; errorsToIgnore?: ReadonlySet; }; // Module-level sentinel so the default parameter has stable identity. // Previously `= new Set()` created a fresh Set every render, which put // a new object in checkErrors' deps and caused the mount effect to // re-fire on every render. const EMPTY_ERRORS_TO_IGNORE: ReadonlySet = new Set(); export function TeleportError({ onComplete, errorsToIgnore = EMPTY_ERRORS_TO_IGNORE, }: TeleportErrorProps): React.ReactNode { const [currentError, setCurrentError] = useState(null); const [isLoggingIn, setIsLoggingIn] = useState(false); // Check for errors on mount and when error resolution occurs const checkErrors = useCallback(async () => { const currentErrors = await getTeleportErrors(); const filteredErrors = new Set( Array.from(currentErrors).filter((error: TeleportLocalErrorType) => !errorsToIgnore.has(error)), ); // If no errors remain, call onComplete if (filteredErrors.size === 0) { onComplete(); return; } // Set current error to handle (prioritize login over git) if (filteredErrors.has('needsLogin')) { setCurrentError('needsLogin'); } else if (filteredErrors.has('needsGitStash')) { setCurrentError('needsGitStash'); } }, [onComplete, errorsToIgnore]); // Check errors on mount useEffect(() => { void checkErrors(); }, [checkErrors]); const onCancel = useCallback(() => { gracefulShutdownSync(0); }, []); const handleLoginComplete = useCallback(() => { setIsLoggingIn(false); void checkErrors(); }, [checkErrors]); const handleLoginWithClaudeAI = useCallback(() => { setIsLoggingIn(true); }, [setIsLoggingIn]); const handleLoginDialogSelect = useCallback( (value: string) => { if (value === 'login') { handleLoginWithClaudeAI(); } else { // User selected exit onCancel(); } }, [handleLoginWithClaudeAI, onCancel], ); const handleStashComplete = useCallback(() => { void checkErrors(); }, [checkErrors]); // Don't render anything if no current error (onComplete will be called) if (!currentError) { return null; } switch (currentError) { case 'needsGitStash': return ; case 'needsLogin': { if (isLoggingIn) { return ; } return ( Teleport requires a Claude.ai account. Your Claude Pro/Max subscription will be used by Claude Code.