Spaces:
Paused
Paused
| import { useState, useEffect, useCallback } from 'react'; | |
| import { createLogger } from '@automaker/utils/logger'; | |
| import { Button } from '@/components/ui/button'; | |
| import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; | |
| import { useSetupStore } from '@/store/setup-store'; | |
| import { getElectronAPI } from '@/lib/electron'; | |
| import { | |
| CheckCircle2, | |
| ArrowRight, | |
| ArrowLeft, | |
| ExternalLink, | |
| Copy, | |
| RefreshCw, | |
| AlertTriangle, | |
| Github, | |
| XCircle, | |
| } from 'lucide-react'; | |
| import { Spinner } from '@/components/ui/spinner'; | |
| import { toast } from 'sonner'; | |
| import { StatusBadge } from '../components'; | |
| const logger = createLogger('GitHubSetupStep'); | |
| interface GitHubSetupStepProps { | |
| onNext: () => void; | |
| onBack: () => void; | |
| onSkip: () => void; | |
| } | |
| export function GitHubSetupStep({ onNext, onBack, onSkip }: GitHubSetupStepProps) { | |
| const { ghCliStatus, setGhCliStatus } = useSetupStore(); | |
| const [isChecking, setIsChecking] = useState(false); | |
| const checkStatus = useCallback(async () => { | |
| setIsChecking(true); | |
| try { | |
| const api = getElectronAPI(); | |
| if (!api.setup?.getGhStatus) { | |
| return; | |
| } | |
| const result = await api.setup.getGhStatus(); | |
| if (result.success) { | |
| setGhCliStatus({ | |
| installed: result.installed, | |
| authenticated: result.authenticated, | |
| version: result.version, | |
| path: result.path, | |
| user: result.user, | |
| }); | |
| } | |
| } catch (error) { | |
| logger.error('Failed to check gh status:', error); | |
| } finally { | |
| setIsChecking(false); | |
| } | |
| }, [setGhCliStatus]); | |
| useEffect(() => { | |
| checkStatus(); | |
| }, [checkStatus]); | |
| const copyCommand = (command: string) => { | |
| navigator.clipboard.writeText(command); | |
| toast.success('Command copied to clipboard'); | |
| }; | |
| const isReady = ghCliStatus?.installed && ghCliStatus?.authenticated; | |
| const getStatusBadge = () => { | |
| if (isChecking) { | |
| return <StatusBadge status="checking" label="Checking..." />; | |
| } | |
| if (ghCliStatus?.authenticated) { | |
| return <StatusBadge status="authenticated" label="Ready" />; | |
| } | |
| if (ghCliStatus?.installed) { | |
| return <StatusBadge status="unverified" label="Not Logged In" />; | |
| } | |
| return <StatusBadge status="not_installed" label="Not Installed" />; | |
| }; | |
| return ( | |
| <div className="space-y-6"> | |
| <div className="text-center mb-8"> | |
| <div className="w-16 h-16 rounded-xl bg-zinc-800 flex items-center justify-center mx-auto mb-4"> | |
| <Github className="w-8 h-8 text-white" /> | |
| </div> | |
| <h2 className="text-2xl font-bold text-foreground mb-2">GitHub CLI Setup</h2> | |
| <p className="text-muted-foreground">Optional - Used for creating pull requests</p> | |
| </div> | |
| {/* Info Banner */} | |
| <Card className="bg-amber-500/10 border-amber-500/20"> | |
| <CardContent className="pt-4"> | |
| <div className="flex items-start gap-3"> | |
| <AlertTriangle className="w-5 h-5 text-amber-500 shrink-0 mt-0.5" /> | |
| <div> | |
| <p className="font-medium text-foreground">This step is optional</p> | |
| <p className="text-sm text-muted-foreground mt-1"> | |
| The GitHub CLI allows you to create pull requests directly from the app. Without it, | |
| you can still create PRs manually in your browser. | |
| </p> | |
| </div> | |
| </div> | |
| </CardContent> | |
| </Card> | |
| {/* Status Card */} | |
| <Card className="bg-card border-border"> | |
| <CardHeader> | |
| <div className="flex items-center justify-between"> | |
| <CardTitle className="text-lg flex items-center gap-2"> | |
| <Github className="w-5 h-5" /> | |
| GitHub CLI Status | |
| </CardTitle> | |
| <div className="flex items-center gap-2"> | |
| {getStatusBadge()} | |
| <Button variant="ghost" size="sm" onClick={checkStatus} disabled={isChecking}> | |
| {isChecking ? <Spinner size="sm" /> : <RefreshCw className="w-4 h-4" />} | |
| </Button> | |
| </div> | |
| </div> | |
| <CardDescription> | |
| {ghCliStatus?.installed | |
| ? ghCliStatus.authenticated | |
| ? `Logged in${ghCliStatus.user ? ` as ${ghCliStatus.user}` : ''}` | |
| : 'Installed but not logged in' | |
| : 'Not installed on your system'} | |
| </CardDescription> | |
| </CardHeader> | |
| <CardContent className="space-y-4"> | |
| {/* Success State */} | |
| {isReady && ( | |
| <div className="flex items-center gap-3 p-4 rounded-lg bg-green-500/10 border border-green-500/20"> | |
| <CheckCircle2 className="w-5 h-5 text-green-500" /> | |
| <div> | |
| <p className="font-medium text-foreground">GitHub CLI is ready!</p> | |
| <p className="text-sm text-muted-foreground"> | |
| You can create pull requests directly from the app. | |
| {ghCliStatus?.version && ( | |
| <span className="ml-1">Version: {ghCliStatus.version}</span> | |
| )} | |
| </p> | |
| </div> | |
| </div> | |
| )} | |
| {/* Not Installed */} | |
| {!ghCliStatus?.installed && !isChecking && ( | |
| <div className="space-y-4"> | |
| <div className="flex items-start gap-3 p-4 rounded-lg bg-muted/30 border border-border"> | |
| <XCircle className="w-5 h-5 text-muted-foreground shrink-0 mt-0.5" /> | |
| <div className="flex-1"> | |
| <p className="font-medium text-foreground">GitHub CLI not found</p> | |
| <p className="text-sm text-muted-foreground mt-1"> | |
| Install the GitHub CLI to enable PR creation from the app. | |
| </p> | |
| </div> | |
| </div> | |
| <div className="space-y-3 p-4 rounded-lg bg-muted/30 border border-border"> | |
| <p className="font-medium text-foreground text-sm">Installation Commands:</p> | |
| <div className="space-y-2"> | |
| <p className="text-xs text-muted-foreground">macOS (Homebrew)</p> | |
| <div className="flex items-center gap-2"> | |
| <code className="flex-1 bg-muted px-3 py-2 rounded text-sm font-mono text-foreground"> | |
| brew install gh | |
| </code> | |
| <Button | |
| variant="ghost" | |
| size="icon" | |
| onClick={() => copyCommand('brew install gh')} | |
| > | |
| <Copy className="w-4 h-4" /> | |
| </Button> | |
| </div> | |
| </div> | |
| <div className="space-y-2"> | |
| <p className="text-xs text-muted-foreground">Windows (winget)</p> | |
| <div className="flex items-center gap-2"> | |
| <code className="flex-1 bg-muted px-3 py-2 rounded text-sm font-mono text-foreground"> | |
| winget install GitHub.cli | |
| </code> | |
| <Button | |
| variant="ghost" | |
| size="icon" | |
| onClick={() => copyCommand('winget install GitHub.cli')} | |
| > | |
| <Copy className="w-4 h-4" /> | |
| </Button> | |
| </div> | |
| </div> | |
| <div className="space-y-2"> | |
| <p className="text-xs text-muted-foreground">Linux (apt)</p> | |
| <div className="flex items-center gap-2"> | |
| <code className="flex-1 bg-muted px-3 py-2 rounded text-sm font-mono text-foreground overflow-x-auto"> | |
| sudo apt install gh | |
| </code> | |
| <Button | |
| variant="ghost" | |
| size="icon" | |
| onClick={() => copyCommand('sudo apt install gh')} | |
| > | |
| <Copy className="w-4 h-4" /> | |
| </Button> | |
| </div> | |
| </div> | |
| <a | |
| href="https://cli.github.com/" | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| className="inline-flex items-center text-sm text-brand-500 hover:underline mt-2" | |
| > | |
| View all installation options | |
| <ExternalLink className="w-3 h-3 ml-1" /> | |
| </a> | |
| </div> | |
| </div> | |
| )} | |
| {/* Installed but not authenticated */} | |
| {ghCliStatus?.installed && !ghCliStatus?.authenticated && !isChecking && ( | |
| <div className="space-y-4"> | |
| <div className="flex items-start gap-3 p-4 rounded-lg bg-amber-500/10 border border-amber-500/20"> | |
| <AlertTriangle className="w-5 h-5 text-amber-500 shrink-0 mt-0.5" /> | |
| <div className="flex-1"> | |
| <p className="font-medium text-foreground">GitHub CLI not logged in</p> | |
| <p className="text-sm text-muted-foreground mt-1"> | |
| Run the login command to authenticate with GitHub. | |
| </p> | |
| </div> | |
| </div> | |
| <div className="space-y-2 p-4 rounded-lg bg-muted/30 border border-border"> | |
| <p className="text-sm text-muted-foreground">Run this command in your terminal:</p> | |
| <div className="flex items-center gap-2"> | |
| <code className="flex-1 bg-muted px-3 py-2 rounded text-sm font-mono text-foreground"> | |
| gh auth login | |
| </code> | |
| <Button variant="ghost" size="icon" onClick={() => copyCommand('gh auth login')}> | |
| <Copy className="w-4 h-4" /> | |
| </Button> | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| {/* Loading State */} | |
| {isChecking && ( | |
| <div className="flex items-center gap-3 p-4 rounded-lg bg-blue-500/10 border border-blue-500/20"> | |
| <Spinner size="md" /> | |
| <div> | |
| <p className="font-medium text-foreground">Checking GitHub CLI status...</p> | |
| </div> | |
| </div> | |
| )} | |
| </CardContent> | |
| </Card> | |
| {/* Navigation */} | |
| <div className="flex justify-between pt-4"> | |
| <Button variant="ghost" onClick={onBack} className="text-muted-foreground"> | |
| <ArrowLeft className="w-4 h-4 mr-2" /> | |
| Back | |
| </Button> | |
| <div className="flex gap-2"> | |
| <Button variant="ghost" onClick={onSkip} className="text-muted-foreground"> | |
| {isReady ? 'Skip' : 'Skip for now'} | |
| </Button> | |
| <Button | |
| onClick={onNext} | |
| className="bg-brand-500 hover:bg-brand-600 text-white" | |
| data-testid="github-next-button" | |
| > | |
| {isReady ? 'Continue' : 'Continue without GitHub CLI'} | |
| <ArrowRight className="w-4 h-4 ml-2" /> | |
| </Button> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |