import { useState, useEffect } from 'react'; import { Download, ExternalLink } from 'lucide-react'; import { Button, Dialog, DialogContent, LoadingState, TypeBadge } from '@/components'; import { useStorage } from '@/features/storage/hooks/useStorage'; import { StorageFileSchema } from '@insforge/shared-schemas'; interface FilePreviewDialogProps { open: boolean; onOpenChange: (open: boolean) => void; file: StorageFileSchema | null; bucket: string; } export function FilePreviewDialog({ open, onOpenChange, file, bucket }: FilePreviewDialogProps) { const [isLoading, setIsLoading] = useState(false); const [previewUrl, setPreviewUrl] = useState(null); const [error, setError] = useState(null); const { downloadObject } = useStorage(); // Reset state when file changes useEffect(() => { if (!file || !open) { setPreviewUrl(null); setError(null); return; } let currentUrl: string | null = null; const loadPreview = async () => { if (!file) { return; } setIsLoading(true); setError(null); try { const fileBucket = file.bucket || bucket; // Fetch file with authentication and create blob URL const blob = await downloadObject(fileBucket, file.key); const url = URL.createObjectURL(blob); currentUrl = url; setPreviewUrl(url); } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Failed to load preview'; setError(errorMessage); } finally { setIsLoading(false); } }; void loadPreview(); // Cleanup: Revoke blob URL when component unmounts or file changes return () => { if (currentUrl) { URL.revokeObjectURL(currentUrl); } }; }, [file, open, bucket, downloadObject]); const handleDownload = () => { if (!file || !previewUrl) { return; } try { const fileName = file.key.split('/').pop() || file.key; const a = document.createElement('a'); a.href = previewUrl; a.download = fileName; document.body.appendChild(a); a.click(); document.body.removeChild(a); } catch { setError('Download failed'); } }; const openInNewTab = () => { if (previewUrl) { window.open(previewUrl, '_blank'); } }; const isPreviewable = (mimeType?: string): boolean => { if (!mimeType) { return false; } return ( mimeType.startsWith('image/') || mimeType.startsWith('video/') || mimeType.startsWith('audio/') || mimeType.startsWith('text/') || mimeType === 'application/json' || mimeType === 'application/pdf' ); }; const formatFileSize = (bytes: number) => { if (bytes < 1024) { return `${bytes} B`; } if (bytes < 1024 * 1024) { return `${(bytes / 1024).toFixed(2)} KB`; } if (bytes < 1024 * 1024 * 1024) { return `${(bytes / (1024 * 1024)).toFixed(2)} MB`; } return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`; }; const renderPreview = () => { if (!file) { return null; } // Show empty preview template for non-previewable files or errors if (!isPreviewable(file.mimeType) || error || !previewUrl) { return
; } const mimeType = file.mimeType || ''; const fileName = file.key.split('/').pop() || file.key; if (mimeType.startsWith('image/')) { return (
{fileName} setError('Failed to load image')} />
); } if (mimeType.startsWith('video/')) { return ( ); } if (mimeType.startsWith('audio/')) { return (
); } if (mimeType.startsWith('text/') || mimeType === 'application/json') { return ; } if (mimeType === 'application/pdf') { return (