'use client' import React, { useState, useEffect } from 'react' import { X, Minus, Square, CaretLeft, CaretRight, House, MagnifyingGlass, Folder as FolderIcon, File, Image as ImageIcon, FilePdf, FileDoc, FileText, Upload, Download, Trash, Plus, Eye, Users, Globe, Code, AppWindow, Desktop, DownloadSimple, Flask, DeviceMobile, Function, Calendar as CalendarIcon, Clock as ClockIcon, Sparkle, Lightning, Brain, Lock, Key, List } from '@phosphor-icons/react' import { motion, AnimatePresence } from 'framer-motion' import { FilePreview } from './FilePreview' import Window from './Window' interface FileManagerProps { currentPath: string onNavigate: (path: string) => void onClose: () => void onMinimize?: () => void onMaximize?: () => void onFocus?: () => void zIndex?: number onOpenFlutterApp?: (appFile: any) => void onOpenApp?: (appId: string) => void onOpenTextFile?: (fileData: { content: string, fileName: string, filePath: string, passkey: string }) => void } interface FileItem { name: string type: 'folder' | 'file' | 'flutter_app' size?: number modified?: string path: string extension?: string dartCode?: string dependencies?: string[] pubspecYaml?: string } export function FileManager({ currentPath, onNavigate, onClose, onOpenFlutterApp, onMinimize, onMaximize, onFocus, zIndex, onOpenApp, onOpenTextFile }: FileManagerProps) { const [files, setFiles] = useState([]) const [loading, setLoading] = useState(false) const [searchQuery, setSearchQuery] = useState('') const [uploadModalOpen, setUploadModalOpen] = useState(false) const [previewFile, setPreviewFile] = useState(null) const [sidebarSelection, setSidebarSelection] = useState('public') // Default to public const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false) // Secure Data State const [passkey, setPasskey] = useState('') const [showPasskeyModal, setShowPasskeyModal] = useState(false) const [tempPasskey, setTempPasskey] = useState('') // Helper function to check if a file can be edited as text const isTextFile = (extension: string): boolean => { const textExtensions = [ 'txt', 'md', 'tex', 'json', 'xml', 'html', 'htm', 'css', 'scss', 'sass', 'js', 'jsx', 'ts', 'tsx', 'py', 'java', 'c', 'cpp', 'h', 'hpp', 'sh', 'bash', 'yaml', 'yml', 'toml', 'ini', 'cfg', 'conf', 'sql', 'php', 'rb', 'go', 'rs', 'swift', 'kt', 'scala', 'r', 'dart', 'vue', 'svelte', 'astro', 'csv', 'log' ] return textExtensions.includes(extension.toLowerCase()) } // Load files when path or selection changes useEffect(() => { if (currentPath === 'Applications') { setSidebarSelection('applications') setLoading(false) setFiles([]) return } if (currentPath === 'public' || currentPath.startsWith('public/')) { setSidebarSelection('public') loadFiles() } else if (sidebarSelection === 'secure') { if (passkey) { loadFiles() } else { setFiles([]) setShowPasskeyModal(true) } } else { // Default to public if nothing else matches setSidebarSelection('public') onNavigate('public') } }, [currentPath, sidebarSelection, passkey]) const loadFiles = async () => { setLoading(true) try { let response if (sidebarSelection === 'public' || currentPath.startsWith('public')) { const publicPath = currentPath === 'public' ? '' : currentPath.replace('public/', '') response = await fetch(`/api/public?folder=${encodeURIComponent(publicPath)}`) } else if (sidebarSelection === 'secure' && passkey) { response = await fetch(`/api/data?key=${encodeURIComponent(passkey)}&folder=${encodeURIComponent(currentPath)}`) } else { setLoading(false) return } const data = await response.json() if (data.error) { console.error('Error loading files:', data.error) setFiles([]) if (data.error === 'Invalid passkey' || data.error === 'Access denied') { setPasskey('') setShowPasskeyModal(true) } return } let normalizedFiles = (data.files || []).map((file: any) => ({ ...file, path: file.path || file.name || '', })) setFiles(normalizedFiles) } catch (error) { console.error('Error loading files:', error) setFiles([]) } finally { setLoading(false) } } const handlePasskeySubmit = () => { if (tempPasskey.trim()) { setPasskey(tempPasskey.trim()) setShowPasskeyModal(false) setTempPasskey('') onNavigate('') // Reset to root of secure drive } } const handleLock = () => { setPasskey('') setFiles([]) setShowPasskeyModal(true) } const handleUpload = async (file: File, targetFolder: string) => { const formData = new FormData() formData.append('file', file) try { let response if (sidebarSelection === 'public') { const publicPath = currentPath === 'public' ? '' : currentPath.replace('public/', '') formData.append('folder', publicPath) formData.append('uploadedBy', 'User') response = await fetch('/api/public', { method: 'POST', body: formData }) } else if (sidebarSelection === 'secure' && passkey) { formData.append('folder', targetFolder) formData.append('key', passkey) response = await fetch('/api/data', { method: 'POST', body: formData }) } else { alert('Cannot upload here') return } const result = await response.json() if (result.success) { loadFiles() setUploadModalOpen(false) } else { alert(`Upload failed: ${result.error}`) } } catch (error) { console.error('Error uploading file:', error) alert('Failed to upload file') } } const handleDownload = (file: FileItem) => { if (sidebarSelection === 'public') { window.open(`/api/sessions/download?file=${encodeURIComponent(file.path)}&public=true`, '_blank') } else if (sidebarSelection === 'secure' && passkey) { window.open(`/api/sessions/download?file=${encodeURIComponent(file.path)}&key=${encodeURIComponent(passkey)}`, '_blank') } } const handlePreview = (file: FileItem) => { setPreviewFile(file) } const handleDelete = async (file: FileItem) => { if (!confirm(`Delete ${file.name}?`)) return try { let response if (sidebarSelection === 'secure' && passkey) { response = await fetch(`/api/data?key=${encodeURIComponent(passkey)}&path=${encodeURIComponent(file.path)}`, { method: 'DELETE' }) } else { // Public delete (if allowed) or fallback const headers: Record = {} response = await fetch(`/api/files?path=${encodeURIComponent(file.path)}`, { method: 'DELETE' }) } const result = await response.json() if (result.success) { loadFiles() } else { alert(`Delete failed: ${result.error}`) } } catch (error) { console.error('Error deleting file:', error) alert('Failed to delete file') } } const handleCreateFolder = async () => { const folderName = prompt('Enter folder name:') if (!folderName) return // Folder creation for secure data is implicit on upload usually, // but we can implement it if needed. // For now, let's skip explicit empty folder creation for secure data // as our API is simple. alert('Folder creation is handled automatically when uploading files.') } const getFileIcon = (file: FileItem) => { if (file.type === 'folder') { if (file.path === 'public' || file.name === 'Public Folder') { return } return } if (file.type === 'flutter_app') { return } const ext = file.extension?.toLowerCase() switch (ext) { case 'pdf': return case 'doc': case 'docx': return case 'txt': case 'md': return case 'dart': return case 'tex': return case 'jpg': case 'jpeg': case 'png': case 'gif': return default: return } } const formatFileSize = (bytes?: number) => { if (!bytes) return '' const units = ['B', 'KB', 'MB', 'GB'] let size = bytes let unitIndex = 0 while (size >= 1024 && unitIndex < units.length - 1) { size /= 1024 unitIndex++ } return `${size.toFixed(1)} ${units[unitIndex]}` } const filteredFiles = files.filter(file => file.name.toLowerCase().includes(searchQuery.toLowerCase()) ) const handleSidebarClick = (item: string) => { setSidebarSelection(item) if (item === 'applications') { onNavigate('Applications') } else if (item === 'secure') { onNavigate('') if (!passkey) { setShowPasskeyModal(true) } } else if (item === 'public') { onNavigate('public') } } const applications = [ { id: 'files', name: 'Finder', icon:
}, { id: 'calendar', name: 'Calendar', icon:
}, { id: 'clock', name: 'Clock', icon:
}, { id: 'gemini', name: 'Gemini', icon: (
) }, { id: 'flutter-editor', name: 'Flutter IDE', icon: (
) }, { id: 'latex-editor', name: 'LaTeX Studio', icon: (
TEX
) }, { id: 'quiz', name: 'Quiz Master', icon: (
) }, ] return ( <>
{/* Mobile Sidebar Overlay */} {mobileSidebarOpen && ( setMobileSidebarOpen(false)} /> )} {/* Sidebar */} {(mobileSidebarOpen || typeof window !== 'undefined') && ( = 768 ? 0 : -192) }} className={`${mobileSidebarOpen ? 'absolute z-30 h-full' : 'hidden'} md:relative md:flex w-40 sm:w-48 bg-[#F3F3F3]/95 md:bg-[#F3F3F3]/90 backdrop-blur-xl border-r border-gray-200 pt-3 sm:pt-4 flex-col`} >
Locations
)}
{/* Main Content */}
{/* Toolbar */}
{currentPath === '' ? (sidebarSelection === 'secure' ? 'Secure Data' : 'Public') : currentPath}
{sidebarSelection === 'secure' && passkey && ( )} {currentPath !== 'Applications' && ( )}
setSearchQuery(e.target.value)} className="bg-transparent text-xs sm:text-sm outline-none w-full placeholder-gray-400 min-w-0" />
{/* File Grid */}
{currentPath === 'Applications' ? (
{applications.map((app) => ( ))}
) : ( <> {loading ? (
Loading files...
) : filteredFiles.length === 0 ? (
{searchQuery ? 'No files found' : 'Folder is empty'}
) : (
{filteredFiles.map((file) => (
{file.type === 'file' && (
)}
))}
)} )}
{/* Status Bar */}
{currentPath === 'Applications' ? `${applications.length} items` : `${filteredFiles.length} items` }
{/* Passkey Modal */} {showPasskeyModal && (

Secure Storage

Enter your passkey to access your files.

setTempPasskey(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handlePasskeySubmit()} placeholder="Enter Passkey" className="w-full px-3 sm:px-4 py-2 sm:py-3 rounded-xl border border-gray-300 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 outline-none mb-3 sm:mb-4 text-center text-base sm:text-lg tracking-widest" autoFocus />
)}
{/* Upload Modal */} {uploadModalOpen && ( setUploadModalOpen(false)} /> )} {/* File Preview Modal */} {previewFile && ( setPreviewFile(null)} onDownload={() => handleDownload(previewFile)} passkey={passkey} isPublic={sidebarSelection === 'public'} /> )} ) } // Upload Modal Component function UploadModal({ currentPath, onUpload, onClose }: { currentPath: string onUpload: (file: File, folder: string) => void onClose: () => void }) { const [selectedFile, setSelectedFile] = useState(null) const [targetFolder, setTargetFolder] = useState(currentPath) const handleSubmit = () => { if (selectedFile) { onUpload(selectedFile, targetFolder) } } return (

Upload File

setSelectedFile(e.target.files?.[0] || null)} className="w-full p-2 border border-gray-300 rounded-lg text-sm" />
setTargetFolder(e.target.value)} placeholder="e.g., homework/math" className="w-full p-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none" />

Leave empty for root, or enter path like "folder/subfolder"

) }