import React, { useState, useEffect } from 'react'; import { X, FolderPlus, GitBranch, Key, ChevronRight, ChevronLeft, Check, Loader2, AlertCircle, FolderOpen, Eye, EyeOff } from 'lucide-react'; import { Button } from './ui/button'; import { Input } from './ui/input'; import { api } from '../utils/api'; const ProjectCreationWizard = ({ onClose, onProjectCreated }) => { // Wizard state const [step, setStep] = useState(1); // 1: Choose type, 2: Configure, 3: Confirm const [workspaceType, setWorkspaceType] = useState('existing'); // 'existing' or 'new' - default to 'existing' // Form state const [workspacePath, setWorkspacePath] = useState(''); const [githubUrl, setGithubUrl] = useState(''); const [selectedGithubToken, setSelectedGithubToken] = useState(''); const [tokenMode, setTokenMode] = useState('stored'); // 'stored' | 'new' | 'none' const [newGithubToken, setNewGithubToken] = useState(''); // UI state const [isCreating, setIsCreating] = useState(false); const [error, setError] = useState(null); const [availableTokens, setAvailableTokens] = useState([]); const [loadingTokens, setLoadingTokens] = useState(false); const [pathSuggestions, setPathSuggestions] = useState([]); const [showPathDropdown, setShowPathDropdown] = useState(false); const [showFolderBrowser, setShowFolderBrowser] = useState(false); const [browserCurrentPath, setBrowserCurrentPath] = useState('~'); const [browserFolders, setBrowserFolders] = useState([]); const [loadingFolders, setLoadingFolders] = useState(false); const [showHiddenFolders, setShowHiddenFolders] = useState(false); // Load available GitHub tokens when needed useEffect(() => { if (step === 2 && workspaceType === 'new' && githubUrl) { loadGithubTokens(); } }, [step, workspaceType, githubUrl]); // Load path suggestions useEffect(() => { if (workspacePath.length > 2) { loadPathSuggestions(workspacePath); } else { setPathSuggestions([]); setShowPathDropdown(false); } }, [workspacePath]); const loadGithubTokens = async () => { try { setLoadingTokens(true); const response = await api.get('/settings/credentials?type=github_token'); const data = await response.json(); const activeTokens = (data.credentials || []).filter(t => t.is_active); setAvailableTokens(activeTokens); // Auto-select first token if available if (activeTokens.length > 0 && !selectedGithubToken) { setSelectedGithubToken(activeTokens[0].id.toString()); } } catch (error) { console.error('Error loading GitHub tokens:', error); } finally { setLoadingTokens(false); } }; const loadPathSuggestions = async (inputPath) => { try { // Extract the directory to browse (parent of input) const lastSlash = inputPath.lastIndexOf('/'); const dirPath = lastSlash > 0 ? inputPath.substring(0, lastSlash) : '~'; const response = await api.browseFilesystem(dirPath); const data = await response.json(); if (data.suggestions) { // Filter suggestions based on the input const filtered = data.suggestions.filter(s => s.path.toLowerCase().startsWith(inputPath.toLowerCase()) ); setPathSuggestions(filtered.slice(0, 5)); setShowPathDropdown(filtered.length > 0); } } catch (error) { console.error('Error loading path suggestions:', error); } }; const handleNext = () => { setError(null); if (step === 1) { if (!workspaceType) { setError('Please select whether you have an existing workspace or want to create a new one'); return; } setStep(2); } else if (step === 2) { if (!workspacePath.trim()) { setError('Please provide a workspace path'); return; } // No validation for GitHub token - it's optional (only needed for private repos) setStep(3); } }; const handleBack = () => { setError(null); setStep(step - 1); }; const handleCreate = async () => { setIsCreating(true); setError(null); try { const payload = { workspaceType, path: workspacePath.trim(), }; // Add GitHub info if creating new workspace with GitHub URL if (workspaceType === 'new' && githubUrl) { payload.githubUrl = githubUrl.trim(); if (tokenMode === 'stored' && selectedGithubToken) { payload.githubTokenId = parseInt(selectedGithubToken); } else if (tokenMode === 'new' && newGithubToken) { payload.newGithubToken = newGithubToken.trim(); } } const response = await api.createWorkspace(payload); const data = await response.json(); if (!response.ok) { throw new Error(data.error || 'Failed to create workspace'); } // Success! if (onProjectCreated) { onProjectCreated(data.project); } onClose(); } catch (error) { console.error('Error creating workspace:', error); setError(error.message || 'Failed to create workspace'); } finally { setIsCreating(false); } }; const selectPathSuggestion = (suggestion) => { setWorkspacePath(suggestion.path); setShowPathDropdown(false); }; const openFolderBrowser = async () => { setShowFolderBrowser(true); await loadBrowserFolders('~'); }; const loadBrowserFolders = async (path) => { try { setLoadingFolders(true); setBrowserCurrentPath(path); const response = await api.browseFilesystem(path); const data = await response.json(); setBrowserFolders(data.suggestions || []); } catch (error) { console.error('Error loading folders:', error); } finally { setLoadingFolders(false); } }; const selectFolder = (folderPath, advanceToConfirm = false) => { setWorkspacePath(folderPath); setShowFolderBrowser(false); if (advanceToConfirm) { setStep(3); } }; const navigateToFolder = async (folderPath) => { await loadBrowserFolders(folderPath); }; return (
{error}
{workspaceType === 'existing' ? 'Full path to your existing workspace directory' : 'Full path where the new workspace will be created'}
Leave empty to create an empty workspace, or provide a GitHub URL to clone
Only required for private repositories. Public repos can be cloned without authentication.
This token will be used only for this operation
💡 Public repositories don't require authentication. You can skip providing a token if cloning a public repo.
No stored tokens available. You can add tokens in Settings → API Keys for easier reuse.
{workspaceType === 'existing' ? 'The workspace will be added to your project list and will be available for Claude/Cursor sessions.' : githubUrl ? 'A new workspace will be created and the repository will be cloned from GitHub.' : 'An empty workspace directory will be created at the specified path.'}
{browserCurrentPath}