import { Check, ChevronLeft, ChevronRight, Loader2 } from 'lucide-react'; import { useCallback, useEffect, useRef, useState } from 'react'; import type { LLMProvider } from '../../../types/app'; import { authenticatedFetch } from '../../../utils/api'; import { useProviderAuthStatus } from '../../provider-auth/hooks/useProviderAuthStatus'; import ProviderLoginModal from '../../provider-auth/view/ProviderLoginModal'; import AgentConnectionsStep from './subcomponents/AgentConnectionsStep'; import GitConfigurationStep from './subcomponents/GitConfigurationStep'; import OnboardingStepProgress from './subcomponents/OnboardingStepProgress'; import { gitEmailPattern, readErrorMessageFromResponse, } 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 { providerAuthStatus, checkProviderAuthStatus, refreshProviderAuthStatuses, } = useProviderAuthStatus(); const previousActiveLoginProviderRef = useRef(undefined); 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 refreshProviderAuthStatuses(); }, [loadGitConfig, refreshProviderAuthStatuses]); useEffect(() => { const previousProvider = previousActiveLoginProviderRef.current; previousActiveLoginProviderRef.current = activeLoginProvider; const didCloseModal = previousProvider !== undefined && previousProvider !== null && activeLoginProvider === null; // Refresh statuses after the login modal is closed. if (didCloseModal) { void refreshProviderAuthStatuses(); } }, [activeLoginProvider, refreshProviderAuthStatuses]); const handleProviderLoginOpen = (provider: LLMProvider) => { 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} onComplete={handleLoginComplete} /> )} ); }