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 (

Git Configuration

Configure your git identity to ensure proper attribution for your commits

setGitName(e.target.value)} className="w-full px-4 py-3 border border-border rounded-lg bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" placeholder="John Doe" required disabled={isSubmitting} />

This will be used as: git config --global user.name

setGitEmail(e.target.value)} className="w-full px-4 py-3 border border-border rounded-lg bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" placeholder="john@example.com" required disabled={isSubmitting} />

This will be used as: git config --global user.email

); case 1: return (

Connect Your AI Agents

Login to one or more AI coding assistants. All are optional.

{/* Agent Cards Grid */}
{/* Claude */}
Claude Code {claudeAuthStatus.authenticated && }
{claudeAuthStatus.loading ? 'Checking...' : claudeAuthStatus.authenticated ? claudeAuthStatus.email || 'Connected' : 'Not connected'}
{!claudeAuthStatus.authenticated && !claudeAuthStatus.loading && ( )}
{/* Cursor */}
Cursor {cursorAuthStatus.authenticated && }
{cursorAuthStatus.loading ? 'Checking...' : cursorAuthStatus.authenticated ? cursorAuthStatus.email || 'Connected' : 'Not connected'}
{!cursorAuthStatus.authenticated && !cursorAuthStatus.loading && ( )}
{/* Codex */}
OpenAI Codex {codexAuthStatus.authenticated && }
{codexAuthStatus.loading ? 'Checking...' : codexAuthStatus.authenticated ? codexAuthStatus.email || 'Connected' : 'Not connected'}
{!codexAuthStatus.authenticated && !codexAuthStatus.loading && ( )}

You can configure these later in Settings.

); default: return null; } }; const isStepValid = () => { switch (currentStep) { case 0: return gitName.trim() && gitEmail.trim() && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(gitEmail); case 1: return true; default: return false; } }; return ( <>
{/* Progress Steps */}
{steps.map((step, index) => (
{index < currentStep ? ( ) : typeof step.icon === 'function' ? ( ) : ( )}

{step.title}

{step.required && ( Required )}
{index < steps.length - 1 && (
)} ))}
{/* Main Card */}
{renderStepContent()} {/* Error Message */} {error && (

{error}

)} {/* Navigation Buttons */}
{currentStep < steps.length - 1 ? ( ) : ( )}
{activeLoginProvider && ( setActiveLoginProvider(null)} provider={activeLoginProvider} project={selectedProject} onComplete={handleLoginComplete} /> )} ); }; export default Onboarding;