import React, { useState, useEffect } from 'react' import { Button } from './ui/8bit/button' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/8bit/card' import { Input } from './ui/8bit/input' import { Label } from './ui/8bit/label' import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from './ui/8bit/dialog' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/8bit/select' import { Lock, Unlock, Save, Download, Upload, RefreshCw, Shield, Settings, Terminal, Key, Database, Clock, FolderOpen, History, FileEdit, Import, FileInput, FileOutput } from 'lucide-react' import ProjectSelector from './ProjectSelector' import MissingVariableItem from './MissingVariableItem' import VersionHistory from './VersionHistory' import DraftMode from './DraftMode' import ImportExportDialog from './ImportExportDialog' import type { ValidationResults, GroupResult, VariableResult } from '../types' interface Variable { name: string value: string encrypted: boolean description?: string } interface EnvManagerProps { project?: any; onBack?: () => void; skipAuth?: boolean; onBranchChange?: (branch: string) => void; initialBranch?: string; } export default function EnvManager8Bit({ project, onBack, skipAuth = false, onBranchChange, initialBranch }: EnvManagerProps) { const [isAuthenticated, setIsAuthenticated] = useState(skipAuth) const [activeTab, setActiveTab] = useState<'variables' | 'draft' | 'history'>('variables') const [password, setPassword] = useState('') const [branches, setBranches] = useState([]) const [selectedBranch, setSelectedBranch] = useState(initialBranch || 'main') const [variables, setVariables] = useState([]) const [newVar, setNewVar] = useState({ name: '', value: '', type: 'server', description: '', sensitive: false }) const [isLoading, setIsLoading] = useState(false) const [deletingVariable, setDeletingVariable] = useState(null) const [error, setError] = useState('') const [notification, setNotification] = useState<{type: 'success' | 'error', message: string} | null>(null) const [currentProject, setCurrentProject] = useState(project || null) const [projectStatus, setProjectStatus] = useState(null) const [showImportExport, setShowImportExport] = useState(false) useEffect(() => { if (!skipAuth) { checkAuthStatus() } else if (project) { loadBranches() loadProjectStatus() } }, []) const checkAuthStatus = async () => { try { const res = await fetch('/api/auth/status') const data = await res.json() setIsAuthenticated(data.authenticated) if (data.authenticated) { loadBranches() } } catch (err) { console.error('Auth check failed:', err) } } const handleLogin = async () => { setIsLoading(true) setError('') try { const res = await fetch('/api/auth', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password }) }) if (res.ok) { setIsAuthenticated(true) setPassword('') loadBranches() // Play success sound playSound('success') } else { setError('Invalid password') playSound('error') } } catch (err) { setError('Login failed') playSound('error') } setIsLoading(false) } const loadBranches = async () => { try { // Pass project path if available const url = currentProject?.path ? `/api/branches?projectPath=${encodeURIComponent(currentProject.path)}` : '/api/branches' const res = await fetch(url) if (!res.ok) { console.error('Failed to load branches:', res.status) return } const data = await res.json() // Check if it's a git repo let branchList: string[] = [] if (data.gitInfo?.isGitRepo && data.gitBranches?.length > 0) { // Use git branches if available branchList = data.gitBranches } else { // Non-git project - use default environments branchList = ['development', 'staging', 'production'] } setBranches(branchList) // Set current/default branch as selected const defaultBranch = data.current || branchList[0] || 'development' setSelectedBranch(defaultBranch) loadVariables(defaultBranch) } catch (err) { console.error('Failed to load branches:', err) // Fallback to default branches const defaultBranches = ['development', 'staging', 'production'] setBranches(defaultBranches) setSelectedBranch(defaultBranches[0]) loadVariables(defaultBranches[0]) } } const loadProjectStatus = async () => { if (!currentProject?.path) return try { const url = `/api/project/status?projectPath=${encodeURIComponent(currentProject.path)}` const res = await fetch(url) if (res.ok) { const status = await res.json() setProjectStatus(status) } } catch (err) { console.error('Failed to load project status:', err) } } const loadVariables = async (branch: string) => { try { let url = `/api/variables?branch=${branch}` if (currentProject?.path) { url += `&projectPath=${encodeURIComponent(currentProject.path)}` } const res = await fetch(url) if (!res.ok) { console.error('Failed to load variables:', res.status) setVariables([]) return } const data = await res.json() setVariables(data.variables || []) } catch (err) { console.error('Failed to load variables:', err) setVariables([]) } } // Optimized version that doesn't block UI const refreshVariables = () => { loadVariables(selectedBranch) } const handleAddVariable = async () => { if (!newVar.name || !newVar.value) return setIsLoading(true) try { let url = '/api/variables' if (currentProject?.path) { url += `?projectPath=${encodeURIComponent(currentProject.path)}` } const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: newVar.name, value: newVar.value, description: newVar.description, sensitive: newVar.sensitive, category: selectedBranch, branch: selectedBranch }) }) if (res.ok) { await loadVariables(selectedBranch) loadProjectStatus() // Refresh required variables list setNewVar({ name: '', value: '', type: 'server', description: '', sensitive: false }) playSound('powerup') // Show success notification setNotification({type: 'success', message: `Variable "${newVar.name}" added successfully!`}) setTimeout(() => setNotification(null), 3000) setError('') } else { const errorData = await res.json().catch(() => ({})) const errorMsg = errorData.error || 'Failed to add variable' setNotification({type: 'error', message: errorMsg}) setTimeout(() => setNotification(null), 5000) playSound('error') } } catch (err) { setNotification({type: 'error', message: 'Failed to add variable'}) setTimeout(() => setNotification(null), 5000) playSound('error') } setIsLoading(false) } const handleDeleteVariable = async (name: string) => { if (!confirm(`Delete variable ${name}?`)) return setDeletingVariable(name) try { let url = '/api/variables' if (currentProject?.path) { url += `?projectPath=${encodeURIComponent(currentProject.path)}` } const res = await fetch(url, { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name }) }) if (res.ok) { await loadVariables(selectedBranch) loadProjectStatus() // Refresh required variables list playSound('hit') // Show success notification setNotification({type: 'success', message: `Variable "${name}" deleted successfully!`}) setTimeout(() => setNotification(null), 3000) } else { const errorData = await res.json().catch(() => ({})) const errorMsg = errorData.error || 'Failed to delete variable' setNotification({type: 'error', message: errorMsg}) setTimeout(() => setNotification(null), 5000) playSound('error') } } catch (err) { console.error('Failed to delete variable:', err) setNotification({type: 'error', message: 'Failed to delete variable'}) setTimeout(() => setNotification(null), 5000) playSound('error') } setDeletingVariable(null) } const handleImportVariables = async (parsedVariables: any[]) => { setIsLoading(true) let successCount = 0 let errorCount = 0 try { for (const variable of parsedVariables) { try { let url = '/api/variables' if (currentProject?.path) { url += `?projectPath=${encodeURIComponent(currentProject.path)}` } // Check if variable exists (update) or is new (create) const method = variable.isDuplicate ? 'PUT' : 'POST' const res = await fetch(url, { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: variable.name, value: variable.value, description: variable.description || '', sensitive: variable.isSensitive || false, category: variable.category || selectedBranch, branch: selectedBranch }) }) if (res.ok) { successCount++ } else { errorCount++ } } catch (err) { errorCount++ } } // Reload variables and project status await loadVariables(selectedBranch) loadProjectStatus() // Show notification if (errorCount === 0) { setNotification({ type: 'success', message: `Successfully imported ${successCount} variable${successCount !== 1 ? 's' : ''}!` }) playSound('powerup') } else { setNotification({ type: 'error', message: `Imported ${successCount} variable${successCount !== 1 ? 's' : ''}, ${errorCount} failed` }) playSound('error') } setTimeout(() => setNotification(null), 5000) } catch (err) { setNotification({type: 'error', message: 'Import failed'}) setTimeout(() => setNotification(null), 5000) playSound('error') } finally { setIsLoading(false) } } const playSound = (type: 'success' | 'error' | 'powerup' | 'hit') => { // Optional: Add actual 8-bit sound effects switch(type) { case 'success': // Play success beep break case 'error': // Play error buzz break case 'powerup': // Play powerup sound break case 'hit': // Play hit sound break } } if (!isAuthenticated) { return (
🔐
ENV MANAGER ENTER PASSWORD TO CONTINUE
{ e.preventDefault(); handleLogin(); }} className="space-y-4"> {/* Hidden username field for accessibility */}
setPassword(e.target.value)} placeholder="••••••••" className="font-mono" autoComplete="current-password" />
{error && (
⚠️ {error}
)}
) } return (
{/* Header */}

ENV MANAGER

PROJECT: {currentProject?.packageInfo?.name || currentProject?.name || 'Unknown'} | BRANCH: {selectedBranch} | VARIABLES: {variables.length}

{/* Branch Selector */} SELECT BRANCH {/* Add Variable */} ADD NEW VARIABLE {!currentProject ? (

NO PROJECT SELECTED

) : (
{ e.preventDefault(); handleAddVariable(); }}>
setNewVar({...newVar, name: e.target.value})} placeholder="VARIABLE_NAME" className="font-mono" autoComplete="off" />
setNewVar({...newVar, value: e.target.value})} placeholder="value" className="font-mono" autoComplete={newVar.sensitive ? "new-password" : "off"} />
setNewVar({...newVar, description: e.target.value})} placeholder="Optional description" className="font-mono" autoComplete="off" />
)}
{/* Missing Variables by Category */} {projectStatus && projectStatus.groups && Object.keys(projectStatus.groups).length > 0 && ( REQUIRED VARIABLES {projectStatus.missing.length > 0 ? `Missing ${projectStatus.missing.length} required variable(s)` : 'All required variables configured'} {Object.entries(projectStatus.groups).map(([groupKey, group]) => { const typedGroup = group as GroupResult if (typedGroup.missing.length === 0) return null return (

{typedGroup.name.toUpperCase()} ({typedGroup.missing.length} missing)

{typedGroup.variables .filter((v: VariableResult) => !v.configured) .map((variable: VariableResult) => ( { loadVariables(selectedBranch) loadProjectStatus() }} playSound={playSound as (type: string) => void} /> )) }
) })}
)} {/* Main Content Tabs */}
{activeTab === 'variables' && (
)}
{activeTab === 'variables' && ( VARIABLES ({variables.length}) )}
{/* Notification Display */} {notification && (
{notification.type === 'success' ? '✅' : '⚠️'} {notification.message}
)} {activeTab === 'variables' && (
{!currentProject ? (

NO PROJECT SELECTED

) : variables.length === 0 ? (

NO VARIABLES FOUND

ADD YOUR FIRST VARIABLE ABOVE

) : (
{variables.map((variable) => (
{variable.name} {variable.encrypted && ( )}
{variable.description && (

{variable.description}

)}
{variable.encrypted ? '••••••••' : variable.value}
))}
)}
)} {activeTab === 'draft' && ( { loadVariables(selectedBranch) playSound('success') }} onDiscard={() => { playSound('hit') }} playSound={playSound} /> )} {activeTab === 'history' && ( { setActiveTab('draft') playSound('powerup') }} playSound={playSound} /> )}
{/* Import/Export Dialog */} setShowImportExport(false)} projectPath={currentProject?.path} branch={selectedBranch} onImport={handleImportVariables} requiredVariables={projectStatus?.missing || []} existingVariables={variables} />
) }