import React, { useState, useEffect } from 'react'; import { AlertCircle, Check, X, Play, Square, FolderOpen, Lock, Unlock, RefreshCw, Key, GitBranch, Trash2 } from 'lucide-react'; interface Project { id: string; name: string; path: string; port: number; isRunning: boolean; gitBranch: string | null; exists: boolean; packageInfo: { name: string; version: string; description?: string; } | null; hasProjectPassword: boolean; isActive: boolean; lastAccessed: string; } interface ProjectSelectorProps { onProjectSelect?: (project: Project) => void; } export default function ProjectSelector({ onProjectSelect }: ProjectSelectorProps) { const [projects, setProjects] = useState([]); const [loading, setLoading] = useState(true); const [selectedProject, setSelectedProject] = useState(null); const [showPasswordModal, setShowPasswordModal] = useState(false); const [passwordMode, setPasswordMode] = useState<'setup' | 'verify' | 'change' | 'recover'>('setup'); const [passwordForm, setPasswordForm] = useState({ password: '', newPassword: '', confirmPassword: '', recoveryPhrase: '', projectPassword: '' }); const [passwordError, setPasswordError] = useState(''); const [recoveryPhrase, setRecoveryPhrase] = useState(''); const [hasGlobalPassword, setHasGlobalPassword] = useState(false); const [refreshing, setRefreshing] = useState(false); useEffect(() => { loadProjects(); checkPasswordStatus(); }, []); const loadProjects = async () => { try { const response = await fetch('/api/projects'); const data = await response.json(); setProjects(data.projects || []); } catch (error) { console.error('Failed to load projects:', error); } finally { setLoading(false); } }; const checkPasswordStatus = async () => { try { const response = await fetch('/api/password'); const data = await response.json(); setHasGlobalPassword(data.hasGlobalPassword); // Don't automatically show password setup - only show when needed } catch (error) { console.error('Failed to check password status:', error); } }; const handleProjectSelect = async (project: Project) => { setSelectedProject(project); // Only ask for password if project has one set if (project.hasProjectPassword) { setPasswordMode('verify'); setShowPasswordModal(true); } else { // No password required, proceed if (onProjectSelect) { onProjectSelect(project); } } }; const handlePasswordSubmit = async () => { setPasswordError(''); try { if (passwordMode === 'setup') { // Setup new global password if (passwordForm.password !== passwordForm.confirmPassword) { setPasswordError('Passwords do not match'); return; } const response = await fetch('/api/password', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'setup', password: passwordForm.password }) }); const data = await response.json(); if (!response.ok) { setPasswordError(data.error); return; } setRecoveryPhrase(data.recoveryPhrase); setHasGlobalPassword(true); alert(`Password set! Your recovery phrase is:\n\n${data.recoveryPhrase}\n\nSAVE THIS PHRASE! You'll need it to recover your password.`); setShowPasswordModal(false); resetPasswordForm(); } if (passwordMode === 'verify') { // Verify password const response = await fetch('/api/password', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'verify', password: passwordForm.password, projectPath: selectedProject?.path }) }); const data = await response.json(); if (!response.ok || !data.valid) { setPasswordError('Invalid password'); return; } // Check if project password is also required if (data.requiresProjectPassword && !data.projectValid) { setPasswordError('Project password required'); return; } setShowPasswordModal(false); resetPasswordForm(); // Proceed with project selection if (selectedProject && onProjectSelect) { onProjectSelect(selectedProject); } } if (passwordMode === 'change') { // Change password if (passwordForm.newPassword !== passwordForm.confirmPassword) { setPasswordError('New passwords do not match'); return; } const response = await fetch('/api/password', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'change', password: passwordForm.password, newPassword: passwordForm.newPassword }) }); const data = await response.json(); if (!response.ok) { setPasswordError(data.error); return; } setRecoveryPhrase(data.recoveryPhrase); alert(`Password changed! Your new recovery phrase is:\n\n${data.recoveryPhrase}\n\nSAVE THIS PHRASE!`); setShowPasswordModal(false); resetPasswordForm(); } if (passwordMode === 'recover') { // Recover password if (passwordForm.newPassword !== passwordForm.confirmPassword) { setPasswordError('New passwords do not match'); return; } const response = await fetch('/api/password', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'recover', recoveryPhrase: passwordForm.recoveryPhrase, newPassword: passwordForm.newPassword }) }); const data = await response.json(); if (!response.ok) { setPasswordError(data.error); return; } setRecoveryPhrase(data.recoveryPhrase); alert(`Password recovered! Your new recovery phrase is:\n\n${data.recoveryPhrase}\n\nSAVE THIS PHRASE!`); setShowPasswordModal(false); resetPasswordForm(); } } catch (error) { setPasswordError('Operation failed. Please try again.'); } }; const handleProjectAction = async (project: Project, action: 'start' | 'stop' | 'remove') => { try { const response = await fetch('/api/projects', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action, projectPath: project.path, port: project.port }) }); if (response.ok) { // Refresh project list await loadProjects(); } } catch (error) { console.error(`Failed to ${action} project:`, error); } }; const resetPasswordForm = () => { setPasswordForm({ password: '', newPassword: '', confirmPassword: '', recoveryPhrase: '', projectPassword: '' }); setPasswordError(''); }; const handleRefresh = async () => { setRefreshing(true); await loadProjects(); setTimeout(() => setRefreshing(false), 500); }; if (loading) { return (
Loading projects...
); } return (

Projects

{hasGlobalPassword && ( )}
{projects.length === 0 ? (

No projects found

Run "env-manager init" in a project directory to get started

) : (
{projects.map((project) => (

{project.packageInfo?.name || project.name}

{project.isRunning && ( LIVE )} {project.gitBranch && ( {project.gitBranch} )} {project.hasProjectPassword && ( )}
{project.packageInfo?.version && (

Version: {project.packageInfo.version}

)}

Path: {project.path}

Port: {project.port}

Last accessed: {new Date(project.lastAccessed).toLocaleDateString()}

{project.exists ? ( <> {project.isRunning ? ( ) : ( )} ) : ( )}
))}
)} {/* Password Modal */} {showPasswordModal && (

{passwordMode === 'setup' && '🔐 Setup Master Password'} {passwordMode === 'verify' && '🔓 Enter Password'} {passwordMode === 'change' && '🔄 Change Password'} {passwordMode === 'recover' && '🔑 Recover Password'}

{passwordError && (
{passwordError}
)}
{ e.preventDefault(); handlePasswordSubmit(); }}>
{passwordMode === 'recover' ? ( <>
setPasswordForm({ ...passwordForm, recoveryPhrase: e.target.value })} className="pixel-input w-full bg-gray-800 text-white p-2 rounded" placeholder="Enter your recovery phrase" />
) : ( <> {(passwordMode === 'verify' || passwordMode === 'change') && (
setPasswordForm({ ...passwordForm, password: e.target.value })} className="pixel-input w-full bg-gray-800 text-white p-2 rounded" placeholder="Enter password" />
)} )} {(passwordMode === 'setup' || passwordMode === 'change' || passwordMode === 'recover') && ( <>
setPasswordForm({ ...passwordForm, [passwordMode === 'setup' ? 'password' : 'newPassword']: e.target.value })} className="pixel-input w-full bg-gray-800 text-white p-2 rounded" placeholder="Enter new password (min 6 chars)" />
setPasswordForm({ ...passwordForm, confirmPassword: e.target.value })} className="pixel-input w-full bg-gray-800 text-white p-2 rounded" placeholder="Confirm password" />
)} {selectedProject?.hasProjectPassword && passwordMode === 'verify' && (
setPasswordForm({ ...passwordForm, projectPassword: e.target.value })} className="pixel-input w-full bg-gray-800 text-white p-2 rounded" placeholder="Enter project-specific password" />
)}
{passwordMode === 'verify' && ( )}
{passwordMode !== 'setup' && ( )}
)}
); }