import React, { useState, useEffect, useRef } from 'react'; import { ChevronRight, ChevronLeft, Check, GitBranch, User, Mail, LogIn, ExternalLink, Loader2 } from 'lucide-react'; import ClaudeLogo from './ClaudeLogo'; import CursorLogo from './CursorLogo'; import CodexLogo from './CodexLogo'; import LoginModal from './LoginModal'; import { authenticatedFetch } from '../utils/api'; import { useAuth } from '../contexts/AuthContext'; const Onboarding = ({ onComplete }) => { const [currentStep, setCurrentStep] = useState(0); const [gitName, setGitName] = useState(''); const [gitEmail, setGitEmail] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); const [error, setError] = useState(''); const [activeLoginProvider, setActiveLoginProvider] = useState(null); const [selectedProject] = useState({ name: 'default', fullPath: '' }); const [claudeAuthStatus, setClaudeAuthStatus] = useState({ authenticated: false, email: null, loading: true, error: null }); const [cursorAuthStatus, setCursorAuthStatus] = useState({ authenticated: false, email: null, loading: true, error: null }); const [codexAuthStatus, setCodexAuthStatus] = useState({ authenticated: false, email: null, loading: true, error: null }); const { user } = useAuth(); const prevActiveLoginProviderRef = useRef(undefined); useEffect(() => { loadGitConfig(); }, []); const loadGitConfig = async () => { try { const response = await authenticatedFetch('/api/user/git-config'); if (response.ok) { const data = await response.json(); if (data.gitName) setGitName(data.gitName); if (data.gitEmail) setGitEmail(data.gitEmail); } } catch (error) { console.error('Error loading git config:', error); } }; useEffect(() => { const prevProvider = prevActiveLoginProviderRef.current; prevActiveLoginProviderRef.current = activeLoginProvider; const isInitialMount = prevProvider === undefined; const isModalClosing = prevProvider !== null && activeLoginProvider === null; if (isInitialMount || isModalClosing) { checkClaudeAuthStatus(); checkCursorAuthStatus(); checkCodexAuthStatus(); } }, [activeLoginProvider]); const checkClaudeAuthStatus = async () => { try { const response = await authenticatedFetch('/api/cli/claude/status'); if (response.ok) { const data = await response.json(); setClaudeAuthStatus({ authenticated: data.authenticated, email: data.email, loading: false, error: data.error || null }); } else { setClaudeAuthStatus({ authenticated: false, email: null, loading: false, error: 'Failed to check authentication status' }); } } catch (error) { console.error('Error checking Claude auth status:', error); setClaudeAuthStatus({ authenticated: false, email: null, loading: false, error: error.message }); } }; const checkCursorAuthStatus = async () => { try { const response = await authenticatedFetch('/api/cli/cursor/status'); if (response.ok) { const data = await response.json(); setCursorAuthStatus({ authenticated: data.authenticated, email: data.email, loading: false, error: data.error || null }); } else { setCursorAuthStatus({ authenticated: false, email: null, loading: false, error: 'Failed to check authentication status' }); } } catch (error) { console.error('Error checking Cursor auth status:', error); setCursorAuthStatus({ authenticated: false, email: null, loading: false, error: error.message }); } }; const checkCodexAuthStatus = async () => { try { const response = await authenticatedFetch('/api/cli/codex/status'); if (response.ok) { const data = await response.json(); setCodexAuthStatus({ authenticated: data.authenticated, email: data.email, loading: false, error: data.error || null }); } else { setCodexAuthStatus({ authenticated: false, email: null, loading: false, error: 'Failed to check authentication status' }); } } catch (error) { console.error('Error checking Codex auth status:', error); setCodexAuthStatus({ authenticated: false, email: null, loading: false, error: error.message }); } }; const handleClaudeLogin = () => setActiveLoginProvider('claude'); const handleCursorLogin = () => setActiveLoginProvider('cursor'); const handleCodexLogin = () => setActiveLoginProvider('codex'); const handleLoginComplete = (exitCode) => { if (exitCode === 0) { if (activeLoginProvider === 'claude') { checkClaudeAuthStatus(); } else if (activeLoginProvider === 'cursor') { checkCursorAuthStatus(); } else if (activeLoginProvider === 'codex') { checkCodexAuthStatus(); } } }; const handleNextStep = async () => { setError(''); // Step 0: Git config validation and submission if (currentStep === 0) { if (!gitName.trim() || !gitEmail.trim()) { setError('Both git name and email are required'); return; } // Validate email format const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(gitEmail)) { setError('Please enter a valid email address'); return; } setIsSubmitting(true); try { // Save git config to backend (which will also apply git config --global) const response = await authenticatedFetch('/api/user/git-config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ gitName, gitEmail }) }); if (!response.ok) { const data = await response.json(); throw new Error(data.error || 'Failed to save git configuration'); } setCurrentStep(currentStep + 1); } catch (err) { setError(err.message); } finally { setIsSubmitting(false); } return; } setCurrentStep(currentStep + 1); }; const handlePrevStep = () => { setError(''); setCurrentStep(currentStep - 1); }; const handleFinish = async () => { setIsSubmitting(true); setError(''); try { const response = await authenticatedFetch('/api/user/complete-onboarding', { method: 'POST' }); if (!response.ok) { const data = await response.json(); throw new Error(data.error || 'Failed to complete onboarding'); } if (onComplete) { onComplete(); } } catch (err) { setError(err.message); } finally { setIsSubmitting(false); } }; const steps = [ { title: 'Git Configuration', description: 'Set up your git identity for commits', icon: GitBranch, required: true }, { title: 'Connect Agents', description: 'Connect your AI coding assistants', icon: LogIn, required: false } ]; const renderStepContent = () => { switch (currentStep) { case 0: return (
Configure your git identity to ensure proper attribution for your commits
This will be used as: git config --global user.name
This will be used as: git config --global user.email
Login to one or more AI coding assistants. All are optional.
You can configure these later in Settings.
{step.title}
{step.required && ( Required )}{error}