import { Check, ChevronLeft, ChevronRight, Loader2 } from 'lucide-react'; import { useCallback, useEffect, useRef, useState } from 'react'; import { authenticatedFetch } from '../../../utils/api'; import ProviderLoginModal from '../../provider-auth/view/ProviderLoginModal'; import AgentConnectionsStep from './subcomponents/AgentConnectionsStep'; import GitConfigurationStep from './subcomponents/GitConfigurationStep'; import OnboardingStepProgress from './subcomponents/OnboardingStepProgress'; import type { CliProvider, ProviderStatusMap } from './types'; import { cliProviders, createInitialProviderStatuses, gitEmailPattern, readErrorMessageFromResponse, selectedProject, } from './utils'; type OnboardingProps = { onComplete?: () => void | Promise; }; export default function Onboarding({ onComplete }: OnboardingProps) { const [currentStep, setCurrentStep] = useState(0); const [gitName, setGitName] = useState(''); const [gitEmail, setGitEmail] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); const [errorMessage, setErrorMessage] = useState(''); const [activeLoginProvider, setActiveLoginProvider] = useState(null); const [providerStatuses, setProviderStatuses] = useState(createInitialProviderStatuses); const previousActiveLoginProviderRef = useRef(undefined); const checkProviderAuthStatus = useCallback(async (provider: CliProvider) => { try { const response = await authenticatedFetch(`/api/cli/${provider}/status`); if (!response.ok) { setProviderStatuses((previous) => ({ ...previous, [provider]: { authenticated: false, email: null, loading: false, error: 'Failed to check authentication status', }, })); return; } const payload = (await response.json()) as { authenticated?: boolean; email?: string | null; error?: string | null; }; setProviderStatuses((previous) => ({ ...previous, [provider]: { authenticated: Boolean(payload.authenticated), email: payload.email ?? null, loading: false, error: payload.error ?? null, }, })); } catch (caughtError) { console.error(`Error checking ${provider} auth status:`, caughtError); setProviderStatuses((previous) => ({ ...previous, [provider]: { authenticated: false, email: null, loading: false, error: caughtError instanceof Error ? caughtError.message : 'Unknown error', }, })); } }, []); const refreshAllProviderStatuses = useCallback(async () => { await Promise.all(cliProviders.map((provider) => checkProviderAuthStatus(provider))); }, [checkProviderAuthStatus]); const loadGitConfig = useCallback(async () => { try { const response = await authenticatedFetch('/api/user/git-config'); if (!response.ok) { return; } const payload = (await response.json()) as { gitName?: string; gitEmail?: string }; if (payload.gitName) { setGitName(payload.gitName); } if (payload.gitEmail) { setGitEmail(payload.gitEmail); } } catch (caughtError) { console.error('Error loading git config:', caughtError); } }, []); useEffect(() => { void loadGitConfig(); void refreshAllProviderStatuses(); }, [loadGitConfig, refreshAllProviderStatuses]); useEffect(() => { const previousProvider = previousActiveLoginProviderRef.current; previousActiveLoginProviderRef.current = activeLoginProvider; const isInitialMount = previousProvider === undefined; const didCloseModal = previousProvider !== null && activeLoginProvider === null; // Refresh statuses once on mount and again after the login modal is closed. if (isInitialMount || didCloseModal) { void refreshAllProviderStatuses(); } }, [activeLoginProvider, refreshAllProviderStatuses]); const handleProviderLoginOpen = (provider: CliProvider) => { setActiveLoginProvider(provider); }; const handleLoginComplete = (exitCode: number) => { if (exitCode === 0 && activeLoginProvider) { void checkProviderAuthStatus(activeLoginProvider); } }; const handleNextStep = async () => { setErrorMessage(''); if (currentStep !== 0) { setCurrentStep((previous) => previous + 1); return; } if (!gitName.trim() || !gitEmail.trim()) { setErrorMessage('Both git name and email are required.'); return; } if (!gitEmailPattern.test(gitEmail)) { setErrorMessage('Please enter a valid email address.'); return; } setIsSubmitting(true); try { const response = await authenticatedFetch('/api/user/git-config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ gitName, gitEmail }), }); if (!response.ok) { const message = await readErrorMessageFromResponse(response, 'Failed to save git configuration'); throw new Error(message); } setCurrentStep((previous) => previous + 1); } catch (caughtError) { setErrorMessage(caughtError instanceof Error ? caughtError.message : 'Failed to save git configuration'); } finally { setIsSubmitting(false); } }; const handlePreviousStep = () => { setErrorMessage(''); setCurrentStep((previous) => previous - 1); }; const handleFinish = async () => { setIsSubmitting(true); setErrorMessage(''); try { const response = await authenticatedFetch('/api/user/complete-onboarding', { method: 'POST' }); if (!response.ok) { const message = await readErrorMessageFromResponse(response, 'Failed to complete onboarding'); throw new Error(message); } await onComplete?.(); } catch (caughtError) { setErrorMessage(caughtError instanceof Error ? caughtError.message : 'Failed to complete onboarding'); } finally { setIsSubmitting(false); } }; const isCurrentStepValid = currentStep === 0 ? Boolean(gitName.trim() && gitEmail.trim() && gitEmailPattern.test(gitEmail)) : true; return ( <>
{currentStep === 0 ? ( ) : ( )} {errorMessage && (

{errorMessage}

)}
{currentStep < 1 ? ( ) : ( )}
{activeLoginProvider && ( setActiveLoginProvider(null)} provider={activeLoginProvider} project={selectedProject} onComplete={handleLoginComplete} isOnboarding={true} /> )} ); }