Spaces:
Build error
Build error
| import { useState, useEffect } from 'react'; | |
| import { Loader2, ExternalLink, RefreshCw, Terminal } from 'lucide-react'; | |
| interface SandboxPreviewProps { | |
| sandboxId: string; | |
| port: number; | |
| type: 'vite' | 'nextjs' | 'console'; | |
| output?: string; | |
| isLoading?: boolean; | |
| } | |
| export default function SandboxPreview({ | |
| sandboxId, | |
| port, | |
| type, | |
| output, | |
| isLoading = false | |
| }: SandboxPreviewProps) { | |
| const [previewUrl, setPreviewUrl] = useState<string>(''); | |
| const [showConsole, setShowConsole] = useState(false); | |
| const [iframeKey, setIframeKey] = useState(0); | |
| useEffect(() => { | |
| if (sandboxId && type !== 'console') { | |
| // In production, this would be the actual E2B sandbox URL | |
| // Format: https://{sandboxId}-{port}.e2b.dev | |
| setPreviewUrl(`https://${sandboxId}-${port}.e2b.dev`); | |
| } | |
| }, [sandboxId, port, type]); | |
| const handleRefresh = () => { | |
| setIframeKey(prev => prev + 1); | |
| }; | |
| if (type === 'console') { | |
| return ( | |
| <div className="bg-gray-800 rounded-lg p-4 border border-gray-700"> | |
| <div className="font-mono text-sm whitespace-pre-wrap text-gray-300"> | |
| {output || 'No output yet...'} | |
| </div> | |
| </div> | |
| ); | |
| } | |
| return ( | |
| <div className="space-y-4"> | |
| {/* Preview Controls */} | |
| <div className="flex items-center justify-between bg-gray-800 rounded-lg p-3 border border-gray-700"> | |
| <div className="flex items-center gap-3"> | |
| <span className="text-sm text-gray-400"> | |
| {type === 'vite' ? '⚡ Vite' : '▲ Next.js'} Preview | |
| </span> | |
| <code className="text-xs bg-gray-900 px-2 py-1 rounded text-blue-400"> | |
| {previewUrl} | |
| </code> | |
| </div> | |
| <div className="flex items-center gap-2"> | |
| <button | |
| onClick={() => setShowConsole(!showConsole)} | |
| className="p-2 hover:bg-gray-700 rounded transition-colors" | |
| title="Toggle console" | |
| > | |
| <Terminal className="w-4 h-4" /> | |
| </button> | |
| <button | |
| onClick={handleRefresh} | |
| className="p-2 hover:bg-gray-700 rounded transition-colors" | |
| title="Refresh preview" | |
| > | |
| <RefreshCw className="w-4 h-4" /> | |
| </button> | |
| <a | |
| href={previewUrl} | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| className="p-2 hover:bg-gray-700 rounded transition-colors" | |
| title="Open in new tab" | |
| > | |
| <ExternalLink className="w-4 h-4" /> | |
| </a> | |
| </div> | |
| </div> | |
| {/* Main Preview */} | |
| <div className="relative bg-gray-900 rounded-lg overflow-hidden border border-gray-700"> | |
| {isLoading && ( | |
| <div className="absolute inset-0 bg-gray-900/80 flex items-center justify-center z-10"> | |
| <div className="text-center"> | |
| <Loader2 className="w-8 h-8 animate-spin mx-auto mb-2" /> | |
| <p className="text-sm text-gray-400"> | |
| {type === 'vite' ? 'Starting Vite dev server...' : 'Starting Next.js dev server...'} | |
| </p> | |
| </div> | |
| </div> | |
| )} | |
| <iframe | |
| key={iframeKey} | |
| src={previewUrl} | |
| className="w-full h-[600px] bg-white" | |
| title={`${type} preview`} | |
| sandbox="allow-scripts allow-same-origin allow-forms" | |
| /> | |
| </div> | |
| {/* Console Output (Toggle) */} | |
| {showConsole && output && ( | |
| <div className="bg-gray-800 rounded-lg p-4 border border-gray-700"> | |
| <div className="flex items-center justify-between mb-2"> | |
| <span className="text-sm font-semibold text-gray-400">Console Output</span> | |
| </div> | |
| <div className="font-mono text-xs whitespace-pre-wrap text-gray-300 max-h-48 overflow-y-auto"> | |
| {output} | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } |