import { useState, useEffect, useCallback } from 'react'; import { createLogger } from '@automaker/utils/logger'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { getHttpApiClient } from '@/lib/http-api-client'; import { getErrorMessage } from '@/lib/utils'; import { toast } from 'sonner'; import { Upload, RefreshCw, AlertTriangle, Sparkles, Plus, Link } from 'lucide-react'; import { Spinner } from '@/components/ui/spinner'; import type { WorktreeInfo } from '../worktree-panel/types'; interface RemoteInfo { name: string; url: string; } const logger = createLogger('PushToRemoteDialog'); interface PushToRemoteDialogProps { open: boolean; onOpenChange: (open: boolean) => void; worktree: WorktreeInfo | null; onConfirm: (worktree: WorktreeInfo, remote: string) => void; } export function PushToRemoteDialog({ open, onOpenChange, worktree, onConfirm, }: PushToRemoteDialogProps) { const [remotes, setRemotes] = useState([]); const [selectedRemote, setSelectedRemote] = useState(''); const [isLoading, setIsLoading] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false); const [error, setError] = useState(null); // Add remote form state const [showAddRemoteForm, setShowAddRemoteForm] = useState(false); const [newRemoteName, setNewRemoteName] = useState('origin'); const [newRemoteUrl, setNewRemoteUrl] = useState(''); const [isAddingRemote, setIsAddingRemote] = useState(false); const [addRemoteError, setAddRemoteError] = useState(null); /** * Transforms API remote data to RemoteInfo format */ const transformRemoteData = useCallback( (remotes: Array<{ name: string; url: string }>): RemoteInfo[] => { return remotes.map((r) => ({ name: r.name, url: r.url, })); }, [] ); /** * Updates remotes state and hides add form if remotes exist */ const updateRemotesState = useCallback((remoteInfos: RemoteInfo[]) => { setRemotes(remoteInfos); if (remoteInfos.length > 0) { setShowAddRemoteForm(false); } }, []); const fetchRemotes = useCallback(async () => { if (!worktree) return; setIsLoading(true); setError(null); try { const api = getHttpApiClient(); const result = await api.worktree.listRemotes(worktree.path); if (result.success && result.result) { const remoteInfos = transformRemoteData(result.result.remotes); updateRemotesState(remoteInfos); } else { setError(result.error || 'Failed to fetch remotes'); } } catch (err) { logger.error('Failed to fetch remotes:', err); setError(getErrorMessage(err)); } finally { setIsLoading(false); } }, [worktree, transformRemoteData, updateRemotesState]); // Fetch remotes when dialog opens useEffect(() => { if (open && worktree) { fetchRemotes(); } }, [open, worktree, fetchRemotes]); // Reset state when dialog closes useEffect(() => { if (!open) { setSelectedRemote(''); setError(null); setShowAddRemoteForm(false); setNewRemoteName('origin'); setNewRemoteUrl(''); setAddRemoteError(null); } }, [open]); // Auto-select default remote when remotes are loaded useEffect(() => { if (remotes.length > 0 && !selectedRemote) { // Default to 'origin' if available, otherwise first remote const defaultRemote = remotes.find((r) => r.name === 'origin') || remotes[0]; setSelectedRemote(defaultRemote.name); } }, [remotes, selectedRemote]); // Show add remote form when no remotes (but not when there's an error) useEffect(() => { if (!isLoading && remotes.length === 0 && !error) { setShowAddRemoteForm(true); } }, [isLoading, remotes.length, error]); const handleRefresh = async () => { if (!worktree) return; setIsRefreshing(true); setError(null); try { const api = getHttpApiClient(); const result = await api.worktree.listRemotes(worktree.path); if (result.success && result.result) { const remoteInfos = transformRemoteData(result.result.remotes); updateRemotesState(remoteInfos); toast.success('Remotes refreshed'); } else { toast.error(result.error || 'Failed to refresh remotes'); } } catch (err) { logger.error('Failed to refresh remotes:', err); toast.error(getErrorMessage(err)); } finally { setIsRefreshing(false); } }; const handleAddRemote = async () => { if (!worktree || !newRemoteName.trim() || !newRemoteUrl.trim()) return; setIsAddingRemote(true); setAddRemoteError(null); try { const api = getHttpApiClient(); const result = await api.worktree.addRemote( worktree.path, newRemoteName.trim(), newRemoteUrl.trim() ); if (result.success && result.result) { toast.success(result.result.message); // Add the new remote to the list and select it const newRemote: RemoteInfo = { name: result.result.remoteName, url: result.result.remoteUrl, }; setRemotes((prev) => [...prev, newRemote]); setSelectedRemote(newRemote.name); setShowAddRemoteForm(false); setNewRemoteName('origin'); setNewRemoteUrl(''); } else { setAddRemoteError(result.error || 'Failed to add remote'); } } catch (err) { logger.error('Failed to add remote:', err); setAddRemoteError(getErrorMessage(err)); } finally { setIsAddingRemote(false); } }; const handleConfirm = () => { if (!worktree || !selectedRemote) return; onConfirm(worktree, selectedRemote); onOpenChange(false); }; const renderAddRemoteForm = () => (
{remotes.length === 0 ? 'No remotes found. Add a remote to push your branch.' : 'Add a new remote'}
{ setNewRemoteName(e.target.value); setAddRemoteError(null); }} disabled={isAddingRemote} />
{ setNewRemoteUrl(e.target.value); setAddRemoteError(null); }} onKeyDown={(e) => { if ( e.key === 'Enter' && newRemoteName.trim() && newRemoteUrl.trim() && !isAddingRemote ) { handleAddRemote(); } }} disabled={isAddingRemote} />

Supports HTTPS, SSH (git@github.com:user/repo.git), or git:// URLs

{addRemoteError && (
{addRemoteError}
)}
); const renderRemoteSelector = () => (
{selectedRemote && (

This will create a new remote branch{' '} {selectedRemote}/{worktree?.branch} {' '} and set up tracking.

)}
); const renderFooter = () => { if (showAddRemoteForm) { return ( {remotes.length > 0 && ( )} ); } return ( ); }; return ( {showAddRemoteForm ? ( <> Add Remote ) : ( <> Push New Branch to Remote new )} {showAddRemoteForm ? ( <>Add a remote repository to push your changes to. ) : ( <> Push{' '} {worktree?.branch || 'current branch'} {' '} to a remote repository for the first time. )} {isLoading ? (
) : error && !showAddRemoteForm ? (
{error}
) : showAddRemoteForm ? ( renderAddRemoteForm() ) : ( renderRemoteSelector() )} {renderFooter()}
); }