'use client'; import { useState, useEffect, useRef, DragEvent, ChangeEvent } from 'react'; import { MinimizedApiKey } from '@/components/demo/MinimizedApiKey'; import { CodeExamplesWidget } from '@/components/demo/CodeExamplesWidget'; import { ImageZoomModal } from '@/components/demo/ImageZoomModal'; const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000'; const API_KEY_STORAGE_KEY = 'banatie_demo_api_key'; const UPLOAD_HISTORY_KEY = 'banatie_upload_history'; const ALLOWED_FILE_TYPES = ['image/png', 'image/jpeg', 'image/jpg', 'image/webp']; const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB interface UploadResponse { success: boolean; message: string; data?: { filename: string; originalName: string; path: string; url: string; size: number; contentType: string; uploadedAt: string; }; error?: string; } interface UploadHistoryItem { id: string; timestamp: Date; filename: string; originalName: string; url: string; size: number; contentType: string; durationMs: number; } interface ApiKeyInfo { organizationSlug?: string; projectSlug?: string; } export default function DemoUploadPage() { // API Key State const [apiKey, setApiKey] = useState(''); const [apiKeyVisible, setApiKeyVisible] = useState(false); const [apiKeyValidated, setApiKeyValidated] = useState(false); const [apiKeyInfo, setApiKeyInfo] = useState(null); const [apiKeyError, setApiKeyError] = useState(''); const [validatingKey, setValidatingKey] = useState(false); // Upload State const [selectedFile, setSelectedFile] = useState(null); const [previewUrl, setPreviewUrl] = useState(null); const [uploading, setUploading] = useState(false); const [uploadError, setUploadError] = useState(''); const [validationError, setValidationError] = useState(''); const [dragActive, setDragActive] = useState(false); // History State const [uploadHistory, setUploadHistory] = useState([]); // Zoom Modal State const [zoomedImageUrl, setZoomedImageUrl] = useState(null); // Copy Feedback State const [codeCopied, setCodeCopied] = useState(false); // Refs const fileInputRef = useRef(null); // Load API key from localStorage on mount useEffect(() => { const storedApiKey = localStorage.getItem(API_KEY_STORAGE_KEY); if (storedApiKey) { setApiKey(storedApiKey); validateStoredApiKey(storedApiKey); } }, []); // Load upload history from sessionStorage useEffect(() => { const storedHistory = sessionStorage.getItem(UPLOAD_HISTORY_KEY); if (storedHistory) { try { const parsed = JSON.parse(storedHistory); setUploadHistory( parsed.map((item: UploadHistoryItem) => ({ ...item, timestamp: new Date(item.timestamp), })), ); } catch (error) { console.error('Failed to parse upload history:', error); } } }, []); // Save upload history to sessionStorage useEffect(() => { if (uploadHistory.length > 0) { sessionStorage.setItem(UPLOAD_HISTORY_KEY, JSON.stringify(uploadHistory)); } }, [uploadHistory]); const validateStoredApiKey = async (keyToValidate: string) => { setValidatingKey(true); setApiKeyError(''); try { const response = await fetch(`${API_BASE_URL}/api/info`, { headers: { 'X-API-Key': keyToValidate, }, }); if (response.ok) { const data = await response.json(); setApiKeyValidated(true); if (data.keyInfo) { setApiKeyInfo({ organizationSlug: data.keyInfo.organizationSlug || data.keyInfo.organizationId, projectSlug: data.keyInfo.projectSlug || data.keyInfo.projectId, }); } else { setApiKeyInfo({ organizationSlug: 'Unknown', projectSlug: 'Unknown', }); } } else { localStorage.removeItem(API_KEY_STORAGE_KEY); setApiKeyError('Stored API key is invalid or expired'); setApiKeyValidated(false); } } catch (error) { setApiKeyError('Failed to validate stored API key'); setApiKeyValidated(false); } finally { setValidatingKey(false); } }; const validateApiKey = async () => { if (!apiKey.trim()) { setApiKeyError('Please enter an API key'); return; } setValidatingKey(true); setApiKeyError(''); try { const response = await fetch(`${API_BASE_URL}/api/info`, { headers: { 'X-API-Key': apiKey, }, }); if (response.ok) { const data = await response.json(); setApiKeyValidated(true); localStorage.setItem(API_KEY_STORAGE_KEY, apiKey); if (data.keyInfo) { setApiKeyInfo({ organizationSlug: data.keyInfo.organizationSlug || data.keyInfo.organizationId, projectSlug: data.keyInfo.projectSlug || data.keyInfo.projectId, }); } else { setApiKeyInfo({ organizationSlug: 'Unknown', projectSlug: 'Unknown', }); } } else { const error = await response.json(); setApiKeyError(error.message || 'Invalid API key'); setApiKeyValidated(false); } } catch (error) { setApiKeyError('Failed to validate API key. Please check your connection.'); setApiKeyValidated(false); } finally { setValidatingKey(false); } }; const revokeApiKey = () => { localStorage.removeItem(API_KEY_STORAGE_KEY); setApiKey(''); setApiKeyValidated(false); setApiKeyInfo(null); setApiKeyError(''); }; const validateFile = (file: File): string | null => { if (!ALLOWED_FILE_TYPES.includes(file.type)) { return `Invalid file type. Allowed: PNG, JPEG, JPG, WebP`; } if (file.size > MAX_FILE_SIZE) { return `File too large. Maximum size: ${MAX_FILE_SIZE / (1024 * 1024)}MB`; } return null; }; const handleFileSelect = (file: File) => { setValidationError(''); setUploadError(''); const error = validateFile(file); if (error) { setValidationError(error); setSelectedFile(null); setPreviewUrl(null); return; } setSelectedFile(file); const reader = new FileReader(); reader.onloadend = () => { setPreviewUrl(reader.result as string); }; reader.readAsDataURL(file); }; const handleFileInputChange = (e: ChangeEvent) => { const file = e.target.files?.[0]; if (file) { handleFileSelect(file); } }; const handleDragEnter = (e: DragEvent) => { e.preventDefault(); e.stopPropagation(); setDragActive(true); }; const handleDragLeave = (e: DragEvent) => { e.preventDefault(); e.stopPropagation(); setDragActive(false); }; const handleDragOver = (e: DragEvent) => { e.preventDefault(); e.stopPropagation(); }; const handleDrop = (e: DragEvent) => { e.preventDefault(); e.stopPropagation(); setDragActive(false); const file = e.dataTransfer.files?.[0]; if (file) { handleFileSelect(file); } }; const handleUpload = async () => { if (!selectedFile) return; setUploading(true); setUploadError(''); const startTime = Date.now(); try { const formData = new FormData(); formData.append('file', selectedFile); const response = await fetch(`${API_BASE_URL}/api/upload`, { method: 'POST', headers: { 'X-API-Key': apiKey, }, body: formData, }); const result: UploadResponse = await response.json(); if (result.success && result.data) { const endTime = Date.now(); const durationMs = endTime - startTime; const historyItem: UploadHistoryItem = { id: Date.now().toString(), timestamp: new Date(), filename: result.data.filename, originalName: result.data.originalName, url: result.data.url, size: result.data.size, contentType: result.data.contentType, durationMs, }; setUploadHistory((prev) => [historyItem, ...prev]); setSelectedFile(null); setPreviewUrl(null); if (fileInputRef.current) { fileInputRef.current.value = ''; } } else { setUploadError(result.error || 'Upload failed'); } } catch (error) { setUploadError(error instanceof Error ? error.message : 'Failed to upload file'); } finally { setUploading(false); } }; const formatFileSize = (bytes: number): string => { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / (1024 * 1024)).toFixed(2)} MB`; }; const formatDuration = (ms: number): string => { if (ms < 1000) return `${ms}ms`; return `${(ms / 1000).toFixed(2)}s`; }; const generateUploadCodeExamples = (item: UploadHistoryItem, key: string, baseUrl: string) => { const localPath = `./${item.originalName}`; return { curl: `curl -X POST "${baseUrl}/api/upload" \\ -H "X-API-Key: ${key}" \\ -F "file=@${localPath}"`, fetch: `const formData = new FormData(); formData.append('file', fileInput.files[0]); fetch('${baseUrl}/api/upload', { method: 'POST', headers: { 'X-API-Key': '${key}' }, body: formData }) .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error('Error:', error));`, rest: `POST ${baseUrl}/api/upload Headers: X-API-Key: ${key} Content-Type: multipart/form-data Body (form-data): file: @${localPath}`, }; }; const handleCopyCode = (code: string) => { navigator.clipboard.writeText(code); setCodeCopied(true); setTimeout(() => setCodeCopied(false), 2000); }; return (
{apiKeyValidated && apiKeyInfo && ( )}

File Upload Workbench

Developer tool for testing file upload API

{!apiKeyValidated && (

API Key

setApiKey(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); validateApiKey(); } }} placeholder="Enter your API key" className="w-full px-4 py-3 bg-slate-800 border border-slate-700 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent pr-12" aria-label="API key input" />
{apiKeyError && (

{apiKeyError}

)}
)}

Upload File

{ if (apiKeyValidated && !uploading) { fileInputRef.current?.click(); } }} > {!selectedFile ? (

Drag and drop your image here, or click to browse

PNG, JPEG, JPG, WebP up to 5MB

) : (
{previewUrl && ( Preview )}

{selectedFile.name}

{formatFileSize(selectedFile.size)}

)}
{validationError && (

{validationError}

)}
{uploading ? 'Uploading...' : selectedFile ? 'Ready to upload' : 'No file selected'}
{uploadError && (

{uploadError}

)}
{uploadHistory.length > 0 && (

Upload History

{uploadHistory.map((item) => (
{/* Column 1: Image Card */}
setZoomedImageUrl(item.url)} role="button" tabIndex={0} aria-label="View full size image" onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setZoomedImageUrl(item.url); } }} > {item.originalName}

{item.originalName}

{formatFileSize(item.size)} {formatDuration(item.durationMs)}

{item.timestamp.toLocaleString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', })}

{/* Columns 2-3: API Code Examples Widget */}
))}
)} {/* Image Zoom Modal */} setZoomedImageUrl(null)} />
); }