fix preview errors

This commit is contained in:
chark1es 2025-02-15 03:27:33 -08:00
parent b4c238de1f
commit 05a92208ce
3 changed files with 466 additions and 289 deletions

View file

@ -3,7 +3,7 @@ import { Icon } from "astro-icon/components";
import { Get } from "../pocketbase/Get"; import { Get } from "../pocketbase/Get";
import { Authentication } from "../pocketbase/Authentication"; import { Authentication } from "../pocketbase/Authentication";
import EventEditor from "./Officer_EventManagement/EventEditor"; import EventEditor from "./Officer_EventManagement/EventEditor";
import FilePreview from "../modals/FilePreview"; import FilePreview from "./Officer_EventManagement/FilePreview";
// Get instances // Get instances
const get = Get.getInstance(); const get = Get.getInstance();
@ -714,7 +714,7 @@ const currentPage = eventResponse.page;
import { Update } from "../pocketbase/Update"; import { Update } from "../pocketbase/Update";
import { FileManager } from "../pocketbase/FileManager"; import { FileManager } from "../pocketbase/FileManager";
import { SendLog } from "../pocketbase/SendLog"; import { SendLog } from "../pocketbase/SendLog";
import FilePreview from "../modals/FilePreview"; import FilePreview from "./Officer_EventManagement/FilePreview";
// Add file storage // Add file storage
const selectedFileStorage = new Map<string, File>(); const selectedFileStorage = new Map<string, File>();
@ -1832,12 +1832,8 @@ const currentPage = eventResponse.page;
const modal = document.getElementById("filePreviewModal") as HTMLDialogElement; const modal = document.getElementById("filePreviewModal") as HTMLDialogElement;
const filePreview = document.getElementById("universalFilePreview") as any; const filePreview = document.getElementById("universalFilePreview") as any;
const previewFileName = document.getElementById("previewFileName"); const previewFileName = document.getElementById("previewFileName");
const loadingSpinner = document.getElementById("previewLoadingSpinner");
if (filePreview && modal && previewFileName && loadingSpinner) { if (filePreview && modal && previewFileName) {
// Show loading spinner
loadingSpinner.classList.remove("hidden");
// Update the filename display // Update the filename display
previewFileName.textContent = filename; previewFileName.textContent = filename;
@ -1847,27 +1843,20 @@ const currentPage = eventResponse.page;
// Update the preview component // Update the preview component
filePreview.setAttribute("url", url); filePreview.setAttribute("url", url);
filePreview.setAttribute("filename", filename); filePreview.setAttribute("filename", filename);
// Hide loading spinner after a short delay
setTimeout(() => {
loadingSpinner.classList.add("hidden");
}, 500);
} }
}; };
// Close file preview // Close file preview
window.closeFilePreview = function () { window.closeFilePreview = function () {
const modal = document.getElementById("filePreviewModal") as HTMLDialogElement; const modal = document.getElementById("filePreviewModal") as HTMLDialogElement;
const filePreview = document.getElementById("universalFilePreview"); const filePreview = document.getElementById("universalFilePreview") as any;
const previewFileName = document.getElementById("previewFileName"); const previewFileName = document.getElementById("previewFileName");
const filesContent = document.getElementById("filesContent"); const filesContent = document.getElementById("filesContent");
if (modal && filePreview && previewFileName) { if (modal && filePreview && previewFileName) {
// Reset the preview // Reset the preview
const event = new CustomEvent("updateFilePreview", { filePreview.setAttribute("url", "");
detail: { url: "", filename: "" }, filePreview.setAttribute("filename", "");
});
filePreview.dispatchEvent(event);
previewFileName.textContent = ""; previewFileName.textContent = "";
modal.close(); modal.close();

View file

@ -4,7 +4,7 @@ import { Authentication } from "../../pocketbase/Authentication";
import { Update } from "../../pocketbase/Update"; import { Update } from "../../pocketbase/Update";
import { FileManager } from "../../pocketbase/FileManager"; import { FileManager } from "../../pocketbase/FileManager";
import { SendLog } from "../../pocketbase/SendLog"; import { SendLog } from "../../pocketbase/SendLog";
import FilePreview from "../../modals/FilePreview"; import FilePreview from "./FilePreview";
// Extend Window interface // Extend Window interface
declare global { declare global {
@ -129,6 +129,7 @@ export default function EventEditor({ onEventSaved }: EventEditorProps) {
setPreviewFilename(filename); setPreviewFilename(filename);
setShowPreview(true); setShowPreview(true);
}; };
// Handle form submission // Handle form submission
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => { const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
@ -256,290 +257,306 @@ export default function EventEditor({ onEventSaved }: EventEditorProps) {
} }
}; };
// Add preview section
const previewSection = showPreview && event?.id && (
<FilePreview
url={fileManager.getFileUrl("events", event.id, previewFilename)}
filename={previewFilename}
onClose={() => {
setShowPreview(false);
const modal = document.getElementById('filePreviewModal') as HTMLDialogElement;
if (modal) {
modal.close();
}
}}
/>
);
return ( return (
<dialog id="editEventModal" className="modal"> <dialog id="editEventModal" className="modal">
{/* Preview Section */} {showPreview ? (
{previewSection} <div className="modal-box max-w-4xl">
<div className="modal-box max-w-2xl"> <div className="flex justify-between items-center mb-4">
{/* Main Edit Form Section */} <div className="flex items-center gap-3">
<div id="editFormSection"> <button
<h3 className="font-bold text-lg mb-4" id="editModalTitle">Edit Event</h3> className="btn btn-ghost btn-sm"
<form id="editEventForm" onSubmit={handleSubmit} className="space-y-4"> onClick={() => setShowPreview(false)}
<input type="hidden" id="editEventId" name="editEventId" value={event?.id || ''} /> >
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> Back
{/* Event Name */} </button>
<div className="form-control"> <h3 className="font-bold text-lg truncate">
<label className="label"> {previewFilename}
<span className="label-text">Event Name</span> </h3>
<span className="label-text-alt text-error">*</span>
</label>
<input
type="text"
name="editEventName"
className="input input-bordered"
value={event?.event_name || ""}
onChange={(e) => setEvent(prev => prev ? { ...prev, event_name: e.target.value } : null)}
required
/>
</div>
{/* Event Code */}
<div className="form-control">
<label className="label">
<span className="label-text">Event Code</span>
<span className="label-text-alt text-error">*</span>
</label>
<input
type="text"
name="editEventCode"
className="input input-bordered"
value={event?.event_code || ""}
onChange={(e) => setEvent(prev => prev ? { ...prev, event_code: e.target.value } : null)}
required
/>
</div>
{/* Location */}
<div className="form-control">
<label className="label">
<span className="label-text">Location</span>
<span className="label-text-alt text-error">*</span>
</label>
<input
type="text"
name="editEventLocation"
className="input input-bordered"
value={event?.location || ""}
onChange={(e) => setEvent(prev => prev ? { ...prev, location: e.target.value } : null)}
required
/>
</div>
{/* Points to Reward */}
<div className="form-control">
<label className="label">
<span className="label-text">Points to Reward</span>
<span className="label-text-alt text-error">*</span>
</label>
<input
type="number"
name="editEventPoints"
className="input input-bordered"
value={event?.points_to_reward || 0}
onChange={(e) => setEvent(prev => prev ? { ...prev, points_to_reward: Number(e.target.value) } : null)}
min="0"
required
/>
</div>
{/* Start Date */}
<div className="form-control">
<label className="label">
<span className="label-text">Start Date</span>
<span className="label-text-alt text-error">*</span>
</label>
<input
type="datetime-local"
name="editEventStartDate"
className="input input-bordered"
value={event?.start_date ? new Date(event.start_date).toISOString().slice(0, 16) : ""}
onChange={(e) => setEvent(prev => prev ? { ...prev, start_date: e.target.value } : null)}
required
/>
</div>
{/* End Date */}
<div className="form-control">
<label className="label">
<span className="label-text">End Date</span>
<span className="label-text-alt text-error">*</span>
</label>
<input
type="datetime-local"
name="editEventEndDate"
className="input input-bordered"
value={event?.end_date ? new Date(event.end_date).toISOString().slice(0, 16) : ""}
onChange={(e) => setEvent(prev => prev ? { ...prev, end_date: e.target.value } : null)}
required
/>
</div>
</div> </div>
</div>
<div className="relative">
<FilePreview
url={previewUrl}
filename={previewFilename}
/>
</div>
</div>
) : (
<div className="modal-box max-w-2xl">
{/* Main Edit Form Section */}
<div id="editFormSection">
<h3 className="font-bold text-lg mb-4" id="editModalTitle">
{event?.id ? 'Edit Event' : 'Add New Event'}
</h3>
<form id="editEventForm" onSubmit={handleSubmit} className="space-y-4">
<input type="hidden" id="editEventId" name="editEventId" value={event?.id || ''} />
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Event Name */}
<div className="form-control">
<label className="label">
<span className="label-text">Event Name</span>
<span className="label-text-alt text-error">*</span>
</label>
<input
type="text"
name="editEventName"
className="input input-bordered"
value={event?.event_name || ""}
onChange={(e) => setEvent(prev => prev ? { ...prev, event_name: e.target.value } : null)}
required
/>
</div>
{/* Description */} {/* Event Code */}
<div className="form-control"> <div className="form-control">
<label className="label"> <label className="label">
<span className="label-text">Description</span> <span className="label-text">Event Code</span>
<span className="label-text-alt text-error">*</span> <span className="label-text-alt text-error">*</span>
</label> </label>
<textarea <input
name="editEventDescription" type="text"
className="textarea textarea-bordered" name="editEventCode"
value={event?.event_description || ""} className="input input-bordered"
onChange={(e) => setEvent(prev => prev ? { ...prev, event_description: e.target.value } : null)} value={event?.event_code || ""}
rows={3} onChange={(e) => setEvent(prev => prev ? { ...prev, event_code: e.target.value } : null)}
required required
></textarea> />
</div> </div>
{/* Files */} {/* Location */}
<div className="form-control"> <div className="form-control">
<label className="label"> <label className="label">
<span className="label-text">Upload Files</span> <span className="label-text">Location</span>
</label> <span className="label-text-alt text-error">*</span>
<input </label>
type="file" <input
onChange={handleFileSelect} type="text"
className="file-input file-input-bordered" name="editEventLocation"
multiple className="input input-bordered"
/> value={event?.location || ""}
<div className="mt-4 space-y-2"> onChange={(e) => setEvent(prev => prev ? { ...prev, location: e.target.value } : null)}
{/* New Files */} required
{Array.from(selectedFiles.entries()).map(([name, file]) => ( />
<div key={name} className="flex items-center justify-between p-2 bg-base-200 rounded-lg"> </div>
<span className="truncate">{name}</span>
<div className="flex gap-2"> {/* Points to Reward */}
<div className="badge badge-primary">New</div> <div className="form-control">
<button <label className="label">
type="button" <span className="label-text">Points to Reward</span>
className="btn btn-ghost btn-xs text-error" <span className="label-text-alt text-error">*</span>
onClick={() => { </label>
const updatedFiles = new Map(selectedFiles); <input
updatedFiles.delete(name); type="number"
setSelectedFiles(updatedFiles); name="editEventPoints"
}} className="input input-bordered"
> value={event?.points_to_reward || 0}
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 20 20" fill="currentColor"> onChange={(e) => setEvent(prev => prev ? { ...prev, points_to_reward: Number(e.target.value) } : null)}
<path fillRule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clipRule="evenodd" /> min="0"
</svg> required
</button> />
</div>
{/* Start Date */}
<div className="form-control">
<label className="label">
<span className="label-text">Start Date</span>
<span className="label-text-alt text-error">*</span>
</label>
<input
type="datetime-local"
name="editEventStartDate"
className="input input-bordered"
value={event?.start_date ? new Date(event.start_date).toISOString().slice(0, 16) : ""}
onChange={(e) => setEvent(prev => prev ? { ...prev, start_date: e.target.value } : null)}
required
/>
</div>
{/* End Date */}
<div className="form-control">
<label className="label">
<span className="label-text">End Date</span>
<span className="label-text-alt text-error">*</span>
</label>
<input
type="datetime-local"
name="editEventEndDate"
className="input input-bordered"
value={event?.end_date ? new Date(event.end_date).toISOString().slice(0, 16) : ""}
onChange={(e) => setEvent(prev => prev ? { ...prev, end_date: e.target.value } : null)}
required
/>
</div>
</div>
{/* Description */}
<div className="form-control">
<label className="label">
<span className="label-text">Description</span>
<span className="label-text-alt text-error">*</span>
</label>
<textarea
name="editEventDescription"
className="textarea textarea-bordered"
value={event?.event_description || ""}
onChange={(e) => setEvent(prev => prev ? { ...prev, event_description: e.target.value } : null)}
rows={3}
required
></textarea>
</div>
{/* Files */}
<div className="form-control">
<label className="label">
<span className="label-text">Upload Files</span>
</label>
<input
type="file"
onChange={handleFileSelect}
className="file-input file-input-bordered"
multiple
/>
<div className="mt-4 space-y-2">
{/* New Files */}
{Array.from(selectedFiles.entries()).map(([name, file]) => (
<div key={name} className="flex items-center justify-between p-2 bg-base-200 rounded-lg">
<span className="truncate">{name}</span>
<div className="flex gap-2">
<div className="badge badge-primary">New</div>
<button
type="button"
className="btn btn-ghost btn-xs text-error"
onClick={() => {
const updatedFiles = new Map(selectedFiles);
updatedFiles.delete(name);
setSelectedFiles(updatedFiles);
}}
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clipRule="evenodd" />
</svg>
</button>
</div>
</div> </div>
</div> ))}
))}
{/* Current Files */} {/* Current Files */}
{event?.files && event.files.length > 0 && ( {event?.files && event.files.length > 0 && (
<> <>
<div className="divider">Current Files</div> <div className="divider">Current Files</div>
{event.files.map((filename) => ( {event.files.map((filename) => (
<div key={filename} className={`flex items-center justify-between p-2 bg-base-200 rounded-lg${filesToDelete.has(filename) ? " opacity-50" : ""}`}> <div key={filename} className={`flex items-center justify-between p-2 bg-base-200 rounded-lg${filesToDelete.has(filename) ? " opacity-50" : ""}`}>
<span className="truncate">{filename}</span> <span className="truncate">{filename}</span>
<div className="flex gap-2"> <div className="flex gap-2">
<button <button
type="button" type="button"
className="btn btn-ghost btn-xs" className="btn btn-ghost btn-xs"
onClick={() => handlePreviewFile(fileManager.getFileUrl("events", event.id, filename), filename)} onClick={() => {
> if (event?.id) {
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 20 20" fill="currentColor"> handlePreviewFile(
<path d="M10 12a2 2 0 100-4 2 2 0 000 4z" /> fileManager.getFileUrl("events", event.id, filename),
<path fillRule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clipRule="evenodd" /> filename
</svg> );
</button> }
<div className="text-error"> }}
{filesToDelete.has(filename) ? ( >
<button <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
type="button" <path d="M10 12a2 2 0 100-4 2 2 0 000 4z" />
className="btn btn-ghost btn-xs" <path fillRule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clipRule="evenodd" />
onClick={() => handleUndoFileDelete(filename)} </svg>
> </button>
Undo <div className="text-error">
</button> {filesToDelete.has(filename) ? (
) : ( <button
<button type="button"
type="button" className="btn btn-ghost btn-xs"
className="btn btn-ghost btn-xs text-error" onClick={() => handleUndoFileDelete(filename)}
onClick={() => handleFileDelete(filename)} >
> Undo
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 20 20" fill="currentColor"> </button>
<path fillRule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clipRule="evenodd" /> ) : (
</svg> <button
</button> type="button"
)} className="btn btn-ghost btn-xs text-error"
onClick={() => handleFileDelete(filename)}
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clipRule="evenodd" />
</svg>
</button>
)}
</div>
</div> </div>
</div> </div>
</div> ))}
))} </>
</> )}
)} </div>
</div> </div>
</div>
{/* Published */} {/* Published */}
<div className="form-control"> <div className="form-control">
<label className="label cursor-pointer justify-start gap-4"> <label className="label cursor-pointer justify-start gap-4">
<input <input
type="checkbox" type="checkbox"
name="editEventPublished" name="editEventPublished"
className="toggle" className="toggle"
checked={event?.published || false} checked={event?.published || false}
onChange={(e) => setEvent(prev => prev ? { ...prev, published: e.target.checked } : null)} onChange={(e) => setEvent(prev => prev ? { ...prev, published: e.target.checked } : null)}
/> />
<span className="label-text">Publish Event</span> <span className="label-text">Publish Event</span>
</label> </label>
<label className="label"> <label className="label">
<span className="label-text-alt text-info"> <span className="label-text-alt text-info">
This has to be clicked if you want to make this event available This has to be clicked if you want to make this event available
to the public to the public
</span> </span>
</label> </label>
</div> </div>
{/* Has Food */} {/* Has Food */}
<div className="form-control"> <div className="form-control">
<label className="label cursor-pointer justify-start gap-4"> <label className="label cursor-pointer justify-start gap-4">
<input <input
type="checkbox" type="checkbox"
name="editEventHasFood" name="editEventHasFood"
className="toggle" className="toggle"
checked={event?.has_food || false} checked={event?.has_food || false}
onChange={(e) => setEvent(prev => prev ? { ...prev, has_food: e.target.checked } : null)} onChange={(e) => setEvent(prev => prev ? { ...prev, has_food: e.target.checked } : null)}
/> />
<span className="label-text">Has Food</span> <span className="label-text">Has Food</span>
</label> </label>
<label className="label"> <label className="label">
<span className="label-text-alt text-info"> <span className="label-text-alt text-info">
Check this if food will be provided at the event Check this if food will be provided at the event
</span> </span>
</label> </label>
</div> </div>
</form> </form>
</div>
<div className="modal-action">
<button
type="submit"
className={`btn btn-primary ${isSubmitting ? 'loading' : ''}`}
form="editEventForm"
disabled={isSubmitting}
>
{isSubmitting ? 'Saving...' : 'Save Changes'}
</button>
<button
type="button"
className="btn"
onClick={() => {
const modal = document.getElementById("editEventModal") as HTMLDialogElement;
if (modal) modal.close();
}}
>
Cancel
</button>
</div>
</div> </div>
)}
<div className="modal-action">
<button
type="submit"
className={`btn btn-primary ${isSubmitting ? 'loading' : ''}`}
form="editEventForm"
disabled={isSubmitting}
>
{isSubmitting ? 'Saving...' : 'Save Changes'}
</button>
<button
type="button"
className="btn"
onClick={() => {
const modal = document.getElementById("editEventModal") as HTMLDialogElement;
if (modal) modal.close();
}}
>
Cancel
</button>
</div>
</div>
<form method="dialog" className="modal-backdrop"> <form method="dialog" className="modal-backdrop">
<button>close</button> <button>close</button>
</form> </form>

View file

@ -0,0 +1,171 @@
import React, { useEffect, useState } from 'react';
interface FilePreviewProps {
url: string;
filename: string;
}
const FilePreview: React.FC<FilePreviewProps> = ({ url, filename }) => {
const [content, setContent] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const [fileType, setFileType] = useState<string | null>(null);
useEffect(() => {
if (!url || !filename) {
setContent(null);
setError(null);
setFileType(null);
return;
}
const loadContent = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(url);
const contentType = response.headers.get('content-type');
setFileType(contentType);
if (contentType?.startsWith('image/')) {
setContent('image');
} else if (contentType?.startsWith('video/')) {
setContent('video');
} else if (contentType?.startsWith('application/pdf')) {
setContent('pdf');
} else if (contentType?.startsWith('text/')) {
const text = await response.text();
// Truncate text if it's too long (e.g., more than 100KB)
if (text.length > 100000) {
setContent(text.substring(0, 100000) + '\n\n... Content truncated. Please download the file to view the complete content.');
} else {
setContent(text);
}
} else if (filename.toLowerCase().endsWith('.mp4')) {
// Fallback for video files when content-type might not be correct
setContent('video');
} else {
setError(`This file type (${contentType || 'unknown'}) is not supported for preview. Please download the file to view it.`);
}
} catch (err) {
setError('Failed to load file');
console.error('Error loading file:', err);
} finally {
setLoading(false);
}
};
loadContent();
}, [url, filename]);
const handleDownload = async () => {
try {
const response = await fetch(url);
const blob = await response.blob();
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(downloadUrl);
} catch (err) {
console.error('Error downloading file:', err);
alert('Failed to download file. Please try again.');
}
};
return (
<div className="space-y-4">
{/* Header with filename and download button */}
<div className="flex justify-between items-center bg-base-200 p-3 rounded-lg">
<div className="flex items-center gap-2 flex-1 min-w-0">
<span className="truncate font-medium">{filename}</span>
{fileType && (
<span className="badge badge-sm">{fileType.split('/')[1]}</span>
)}
</div>
<button
onClick={handleDownload}
className="btn btn-sm btn-ghost gap-2"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clipRule="evenodd" />
</svg>
Download
</button>
</div>
{/* Preview content */}
<div className="preview-content">
{loading && (
<div className="flex justify-center items-center p-8">
<span className="loading loading-spinner loading-lg"></span>
</div>
)}
{error && (
<div className="flex flex-col items-center justify-center p-8 bg-base-200 rounded-lg text-center space-y-4">
<div className="bg-warning/20 p-4 rounded-full">
<svg xmlns="http://www.w3.org/2000/svg" className="h-12 w-12 text-warning" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
</div>
<div className="space-y-2">
<h3 className="text-lg font-semibold">Preview Unavailable</h3>
<p className="text-base-content/70 max-w-md">{error}</p>
</div>
<button
onClick={handleDownload}
className="btn btn-warning btn-sm gap-2 mt-4"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clipRule="evenodd" />
</svg>
Download File Instead
</button>
</div>
)}
{!loading && !error && content === 'image' && (
<div className="flex justify-center">
<img src={url} alt={filename} className="max-w-full h-auto rounded-lg" />
</div>
)}
{!loading && !error && content === 'video' && (
<div className="flex justify-center">
<video
controls
className="max-w-full rounded-lg"
style={{ maxHeight: '600px' }}
>
<source src={url} type="video/mp4" />
Your browser does not support the video tag.
</video>
</div>
)}
{!loading && !error && content === 'pdf' && (
<div className="w-full h-[600px]">
<iframe
src={url}
className="w-full h-full rounded-lg"
title={filename}
></iframe>
</div>
)}
{!loading && !error && content && !['image', 'video', 'pdf'].includes(content) && (
<div className="mockup-code bg-base-200 text-base-content overflow-x-auto max-h-[600px]">
<pre className="p-4"><code>{content}</code></pre>
</div>
)}
</div>
</div>
);
};
export default FilePreview;