import React, { useState, useRef } from 'react' import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from './ui/8bit/dialog' import { Button } from './ui/8bit/button' import { Input } from './ui/8bit/input' import { Label } from './ui/8bit/label' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/8bit/select' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/8bit/card' import { Download, Upload, Copy, Check, FileDown, FileUp, AlertCircle, Info, Save } from 'lucide-react' interface Variable { name: string value: string encrypted: boolean description?: string sensitive?: boolean category?: string } interface ParsedVariable { name: string value: string description?: string category?: string isRequired?: boolean isSensitive?: boolean isNew?: boolean isDuplicate?: boolean } interface ImportExportDialogProps { isOpen: boolean onClose: () => void projectPath?: string branch?: string onImport?: (variables: ParsedVariable[]) => Promise requiredVariables?: string[] existingVariables?: Variable[] } export default function ImportExportDialog({ isOpen, onClose, projectPath, branch = 'main', onImport, requiredVariables = [], existingVariables = [] }: ImportExportDialogProps) { const [activeTab, setActiveTab] = useState<'export' | 'import'>('export') const [exportFormat, setExportFormat] = useState('.env') const [customExtension, setCustomExtension] = useState('') const [copySuccess, setCopySuccess] = useState(false) const [exportLoading, setExportLoading] = useState(false) const [importLoading, setImportLoading] = useState(false) const [importContent, setImportContent] = useState('') const [parsedVariables, setParsedVariables] = useState([]) const [parseError, setParseError] = useState('') const fileInputRef = useRef(null) const exportFormats = [ { value: '.env', label: '.env' }, { value: '.env.local', label: '.env.local' }, { value: '.env.development', label: '.env.development' }, { value: '.env.production', label: '.env.production' }, { value: '.env.staging', label: '.env.staging' }, { value: 'custom', label: 'Custom...' } ] const handleExport = async (toClipboard = false) => { setExportLoading(true) try { const url = `/api/export?${new URLSearchParams({ ...(projectPath && { projectPath }), branch, format: 'env' })}` const response = await fetch(url) if (!response.ok) throw new Error('Failed to export') const data = await response.json() const content = data.content || '' if (toClipboard) { await navigator.clipboard.writeText(content) setCopySuccess(true) setTimeout(() => setCopySuccess(false), 2000) } else { // Download file const blob = new Blob([content], { type: 'text/plain' }) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = exportFormat === 'custom' ? `.env.${customExtension}` : exportFormat document.body.appendChild(a) a.click() document.body.removeChild(a) URL.revokeObjectURL(url) } } catch (error) { console.error('Export error:', error) } finally { setExportLoading(false) } } const parseEnvContent = (content: string): ParsedVariable[] => { const lines = content.split('\n') const variables: ParsedVariable[] = [] let currentCategory = 'general' let lastDescription = '' const existingNames = new Set(existingVariables.map(v => v.name)) for (const line of lines) { const trimmed = line.trim() // Skip empty lines if (!trimmed) { lastDescription = '' continue } // Parse comments for descriptions and categories if (trimmed.startsWith('#')) { const comment = trimmed.substring(1).trim() // Check for category markers like "# === Database ===" or "# [Database]" if (comment.match(/^(===|---|\[).*?(===|---|\])/)) { currentCategory = comment.replace(/[=\-\[\]]/g, '').trim().toLowerCase() } else { // Store as potential description for next variable lastDescription = comment } continue } // Parse variable const match = trimmed.match(/^([A-Z_][A-Z0-9_]*)\s*=\s*(.*)$/) if (match) { const [, name, value] = match // Clean up value (remove quotes if present) let cleanValue = value if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) { cleanValue = value.slice(1, -1) } // Detect if it's sensitive based on common patterns const isSensitive = name.includes('SECRET') || name.includes('KEY') || name.includes('PASSWORD') || name.includes('TOKEN') || name.includes('PRIVATE') variables.push({ name, value: cleanValue, description: lastDescription || undefined, category: currentCategory, isRequired: requiredVariables.includes(name), isSensitive, isNew: !existingNames.has(name), isDuplicate: existingNames.has(name) }) lastDescription = '' } } return variables } const handleFileSelect = (e: React.ChangeEvent) => { const file = e.target.files?.[0] if (!file) return const reader = new FileReader() reader.onload = (e) => { const content = e.target?.result as string setImportContent(content) handleParseContent(content) } reader.readAsText(file) } const handleParseContent = (content: string) => { try { setParseError('') const parsed = parseEnvContent(content) setParsedVariables(parsed) } catch (error) { setParseError('Failed to parse .env file') setParsedVariables([]) } } const handleImportVariables = async () => { if (!onImport || parsedVariables.length === 0) return setImportLoading(true) try { await onImport(parsedVariables) onClose() } catch (error) { console.error('Import error:', error) setParseError('Failed to import variables') } finally { setImportLoading(false) } } const getVariableIcon = (variable: ParsedVariable) => { if (variable.isRequired) return '⚠️' if (variable.isDuplicate) return '🔄' if (variable.isNew) return '✨' return '📝' } const getVariableColor = (variable: ParsedVariable) => { if (variable.isRequired) return 'text-yellow-400' if (variable.isDuplicate) return 'text-blue-400' if (variable.isNew) return 'text-green-400' return 'text-gray-400' } return ( IMPORT/EXPORT ENVIRONMENT VARIABLES Export your variables to a file or clipboard, or import from an existing .env file
{activeTab === 'export' && (
EXPORT FORMAT
{exportFormat === 'custom' && (
.env. setCustomExtension(e.target.value)} placeholder="custom" className="flex-1" />
)}
EXPORT INFO
Sensitive values will be decrypted during export
Comments with descriptions will be included
Variables are grouped by category
)} {activeTab === 'import' && (
IMPORT SOURCE
OR PASTE CONTENT