refactor: update file retrieval logic and enhance media handling in SharePage component
This commit is contained in:
		
							parent
							
								
									568a6556af
								
							
						
					
					
						commit
						caedd0ae88
					
				
							
								
								
									
										68
									
								
								src/app/_components/SharePage.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								src/app/_components/SharePage.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,68 @@ | ||||
| "use client"; | ||||
| 
 | ||||
| import { useEffect, useState } from "react"; | ||||
| 
 | ||||
| interface SharePageProps { | ||||
|   fileId: string; | ||||
|   fileType: string; // Pass the file type as a prop
 | ||||
| } | ||||
| 
 | ||||
| export function SharePage({ fileId, fileType }: SharePageProps) { | ||||
|   const [mediaSrc, setMediaSrc] = useState<string | null>(null); | ||||
|   const [error, setError] = useState<string | null>(null); | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     if (!fileId) { | ||||
|       setError("File ID is required."); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     let objectUrl: string | null = null; | ||||
| 
 | ||||
|     const fetchMedia = async () => { | ||||
|       try { | ||||
|         const response = await fetch(`/api/serv?id=${encodeURIComponent(fileId)}`); | ||||
|         if (!response.ok) { | ||||
|           throw new Error("Failed to fetch media"); | ||||
|         } | ||||
| 
 | ||||
|         const blob = await response.blob(); | ||||
|         objectUrl = URL.createObjectURL(blob); | ||||
|         setMediaSrc(objectUrl); | ||||
|       } catch (err) { | ||||
|         console.error(err); | ||||
|         setError("Failed to load media."); | ||||
|       } | ||||
|     }; | ||||
| 
 | ||||
|     fetchMedia(); | ||||
| 
 | ||||
|     return () => { | ||||
|       if (objectUrl) { | ||||
|         URL.revokeObjectURL(objectUrl); | ||||
|       } | ||||
|     }; | ||||
|   }, [fileId]); | ||||
| 
 | ||||
|   if (error) { | ||||
|     return <div className="text-red-500">{error}</div>; | ||||
|   } | ||||
| 
 | ||||
|   if (!mediaSrc) { | ||||
|     return <div>Loading...</div>; | ||||
|   } | ||||
| 
 | ||||
|   if (fileType.startsWith("video")) { | ||||
|     return ( | ||||
|       <video | ||||
|         controls | ||||
|         className="max-w-full max-h-96 rounded-lg shadow-md" | ||||
|         src={mediaSrc} | ||||
|       > | ||||
|         Your browser does not support the video tag. | ||||
|       </video> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   return <img src={mediaSrc} alt="Media preview" className="max-w-full max-h-96 rounded-lg shadow-md" />; | ||||
| } | ||||
| @ -14,7 +14,7 @@ export async function GET(req: Request) { | ||||
|   try { | ||||
|     // Fetch file metadata from the database
 | ||||
|     const file = await db.file.findFirst({ | ||||
|       where: { name: fileId }, | ||||
|       where: { id: fileId }, | ||||
|     }); | ||||
| 
 | ||||
|     if (!file) { | ||||
| @ -22,15 +22,35 @@ export async function GET(req: Request) { | ||||
|     } | ||||
| 
 | ||||
|     // Construct the file path
 | ||||
|     const filePath = path.join(process.cwd(), "uploads", file.name); | ||||
|     const filePath = path.join(process.cwd(), "uploads", file.id); | ||||
| 
 | ||||
|     // Read the file from the filesystem
 | ||||
|     const fileBuffer = await fs.readFile(filePath); | ||||
| 
 | ||||
|     const mimeType = file.extension === ".mp4" | ||||
|       ? "video/mp4" | ||||
|       : file.extension === ".webm" | ||||
|       ? "video/webm" | ||||
|       : file.extension === ".ogg" | ||||
|       ? "video/ogg" | ||||
|       : file.extension === ".jpg" || file.extension === ".jpeg" | ||||
|       ? "image/jpeg" | ||||
|       : file.extension === ".png" | ||||
|       ? "image/png" | ||||
|       : file.extension === ".gif" | ||||
|       ? "image/gif" | ||||
|       : file.extension === ".svg" | ||||
|       ? "image/svg+xml" | ||||
|       : file.extension === ".mp3" | ||||
|       ? "audio/mpeg" | ||||
|       : file.extension === ".wav" | ||||
|       ? "audio/wav" | ||||
|       : "application/octet-stream"; | ||||
| 
 | ||||
|     // Return the file as a binary response
 | ||||
|     return new Response(fileBuffer, { | ||||
|       headers: { | ||||
|         "Content-Type": file.extension.startsWith(".png") ? "image/png" : "application/octet-stream", | ||||
|         "Content-Type": mimeType, | ||||
|         "Content-Disposition": `inline; filename="${file.name}"`, | ||||
|       }, | ||||
|     }); | ||||
|  | ||||
| @ -24,7 +24,8 @@ export async function GET(req: Request) { | ||||
|     return NextResponse.json({ | ||||
|       name: file.name, | ||||
|       size: file.size, | ||||
|       owner: file.uploadedBy?.name ?? "Unknown", // Use nullish coalescing
 | ||||
|       owner: file.uploadedBy?.name ?? null, // Use nullish coalescing
 | ||||
|       owneravatar: file.uploadedBy?.image ?? null, | ||||
|       uploadDate: file.uploadDate, | ||||
|       id: file.id, | ||||
|       isOwner: session?.user?.id === file.uploadedById, | ||||
|  | ||||
| @ -4,15 +4,13 @@ import { Suspense } from "react"; | ||||
| import { useEffect, useState } from "react"; | ||||
| import { useSearchParams, useRouter } from "next/navigation"; | ||||
| import toast, { Toaster } from "react-hot-toast"; | ||||
| import Head from "next/head"; | ||||
| import { number } from "zod"; | ||||
| 
 | ||||
| // import { SharePage } from "~/components/SharePage";
 | ||||
| import { SharePage } from "~/app/_components/SharePage"; | ||||
| 
 | ||||
| interface FileDetails { | ||||
|   name: string; | ||||
|   size: number; | ||||
|   owner: string; | ||||
|   owneravatar: string | null; | ||||
|   uploadDate: string; | ||||
|   id: string; | ||||
|   isOwner: boolean; | ||||
| @ -20,13 +18,35 @@ interface FileDetails { | ||||
|   url: string; | ||||
| } | ||||
| 
 | ||||
| function UploadsPage() { | ||||
| 
 | ||||
| function Details() { | ||||
|   const searchParams = useSearchParams(); | ||||
|   const router = useRouter(); | ||||
|   const fileId = searchParams.get("id"); | ||||
|   const [fileDetails, setFileDetails] = useState<FileDetails | null>(null); | ||||
|   const [error, setError] = useState<string | null>(null); | ||||
|   // const mediasrc = SharePage() as string; // Replace with a valid string URL or logic to generate the URL
 | ||||
| 
 | ||||
|   // Determine the file type based on the file extension
 | ||||
|   const fileType = fileDetails?.type === ".mp4" | ||||
|     ? "video/mp4" | ||||
|     : fileDetails?.type === ".webm" | ||||
|     ? "video/webm" | ||||
|     : fileDetails?.type === ".ogg" | ||||
|     ? "video/ogg" | ||||
|     : fileDetails?.type === ".jpg" || fileDetails?.type === ".jpeg" | ||||
|     ? "image/jpeg" | ||||
|     : fileDetails?.type === ".png" | ||||
|     ? "image/png" | ||||
|     : fileDetails?.type === ".gif" | ||||
|     ? "image/gif" | ||||
|     : fileDetails?.type === ".svg" | ||||
|     ? "image/svg+xml" | ||||
|     : fileDetails?.type === ".mp3" | ||||
|     ? "audio/mpeg" | ||||
|     : fileDetails?.type === ".wav" | ||||
|     ? "audio/wav" | ||||
|     // if fileType is not one of the above, set it to unknown
 | ||||
|     : "unknown"; | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     if (!fileId) { | ||||
| @ -41,7 +61,7 @@ function UploadsPage() { | ||||
|           throw new Error("Failed to fetch file details"); | ||||
|         } | ||||
| 
 | ||||
|         const data: FileDetails = await response.json(); // Explicitly type the response
 | ||||
|         const data: FileDetails = await response.json(); | ||||
|         setFileDetails(data); | ||||
|       } catch (err) { | ||||
|         console.error(err); | ||||
| @ -49,110 +69,9 @@ function UploadsPage() { | ||||
|       } | ||||
|     }; | ||||
| 
 | ||||
|     void fetchFileDetails(); // Use `void` to mark the promise as intentionally ignored
 | ||||
|     fetchFileDetails(); | ||||
|   }, [fileId]); | ||||
| 
 | ||||
|   // set page og meta tags
 | ||||
|   useEffect(() => { | ||||
|     if (fileDetails) { | ||||
|       const ogTitle = `File Details: ${fileDetails.name}`; | ||||
|       // proper size conversion
 | ||||
|       const sizeInKB = fileDetails.size / 1024; | ||||
|       const sizeInMB = sizeInKB / 1024; | ||||
|       const sizeInGB = sizeInMB / 1024; | ||||
|       let sizeDescription: string = `${sizeInKB.toFixed(2)} KB`; | ||||
|       if (sizeInMB >= 1) { | ||||
|         sizeDescription = `${sizeInMB.toFixed(2)} MB`; | ||||
|       } else if (sizeInGB >= 1) { | ||||
|         sizeDescription = `${sizeInGB.toFixed(2)} GB`; | ||||
|       } | ||||
|       const ogDescription = `File Name: ${fileDetails.name}, Size: ${sizeDescription}, Owner: ${fileDetails.owner}, Upload Date: ${new Date(fileDetails.uploadDate).toLocaleString()}`; | ||||
| 
 | ||||
|       // document.title = ogTitle;
 | ||||
|       // if meta og tags are not present, create them
 | ||||
|       if (!document.querySelector('meta[name="description"]')) { | ||||
|         const metaDescription = document.createElement("meta"); | ||||
|         metaDescription.name = "description"; | ||||
|         document.head.appendChild(metaDescription); | ||||
|       } | ||||
|       if (!document.querySelector('meta[property="og:title"]')) { | ||||
|         const metaOgTitle = document.createElement("meta"); | ||||
|         metaOgTitle.setAttribute("property", "og:title"); | ||||
|         document.head.appendChild(metaOgTitle); | ||||
|       } | ||||
|       if (!document.querySelector('meta[property="og:description"]')) { | ||||
|         const metaOgDescription = document.createElement("meta"); | ||||
|         metaOgDescription.setAttribute("property", "og:description"); | ||||
|         document.head.appendChild(metaOgDescription); | ||||
|       } | ||||
|       document.querySelector('meta[name="description"]')?.setAttribute("content", ogDescription); | ||||
|       document.querySelector('meta[property="og:title"]')?.setAttribute("content", ogTitle); | ||||
|       document.querySelector('meta[property="og:description"]')?.setAttribute("content", ogDescription); | ||||
|        | ||||
| 
 | ||||
|     } | ||||
|   }, [fileDetails]); | ||||
| 
 | ||||
|   const handleDownload = async () => { | ||||
|     try { | ||||
|       if (!fileDetails) { | ||||
|         toast.error("File details not available."); | ||||
|         return; | ||||
|       } | ||||
|           const response = await fetch(`/api/files/download?fileId=${encodeURIComponent(fileDetails.id)}&fileName=${encodeURIComponent(fileDetails.name)}`); | ||||
|           if (!response.ok) { | ||||
|             throw new Error("Failed to download file"); | ||||
|           } | ||||
|           // Download the file with the correct filename
 | ||||
|           const blob = await response.blob(); | ||||
|           const url = window.URL.createObjectURL(blob); | ||||
|           const a = document.createElement("a"); | ||||
|           a.href = url; | ||||
|           a.download = fileDetails.name; | ||||
|           document.body.appendChild(a); | ||||
|           a.click(); | ||||
|           a.remove(); | ||||
|           window.URL.revokeObjectURL(url); | ||||
|      | ||||
|           toast.success(`File "${fileDetails.name}" downloaded successfully!`); | ||||
|         } catch (err) { | ||||
|           console.error(err); | ||||
|           toast.error("Failed to download file."); | ||||
|         } | ||||
|       }; | ||||
| 
 | ||||
|   const handleShare = () => { | ||||
|     if (fileDetails) { | ||||
|       const shareableLink = `${window.location.origin}/share?id=${fileDetails.id}`; | ||||
|       navigator.clipboard | ||||
|         .writeText(shareableLink) | ||||
|         .then(() => toast.success("Shareable link copied to clipboard!")) | ||||
|         .catch(() => toast.error("Failed to copy link.")); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   const handleRemove = async () => { | ||||
|     try { | ||||
|       const response = await fetch(`/api/remove`, { | ||||
|         method: "DELETE", | ||||
|         headers: { | ||||
|           "Content-Type": "application/json", | ||||
|         }, | ||||
|         body: JSON.stringify({ id: fileDetails?.id }), // Use optional chaining
 | ||||
|       }); | ||||
| 
 | ||||
|       if (!response.ok) { | ||||
|         throw new Error("Failed to remove file"); | ||||
|       } | ||||
| 
 | ||||
|       toast.success("File removed successfully!"); | ||||
|       router.push("/"); | ||||
|     } catch (err) { | ||||
|       console.error(err); | ||||
|       toast.error("Failed to remove file."); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   if (error) { | ||||
|     return <div className="text-red-500">{error}</div>; | ||||
|   } | ||||
| @ -194,58 +113,36 @@ function UploadsPage() { | ||||
|           <span className="text-[hsl(280,100%,70%)]">File</span> Details | ||||
|         </h1> | ||||
|         <div className="mt-6"> | ||||
|           {/* {(fileDetails.type.startsWith(".png") || | ||||
|             fileDetails.type.startsWith(".jpg") || | ||||
|             fileDetails.type.startsWith(".jpeg") || | ||||
|             fileDetails.type.startsWith(".gif")) && (mediasrc && <img src={mediasrc} alt="Media preview" />)} | ||||
|           {(fileDetails.type.startsWith(".mp4") || | ||||
|             fileDetails.type.startsWith(".webm") || | ||||
|             fileDetails.type.startsWith(".ogg")) && | ||||
|               (mediasrc && | ||||
|               <video controls className="max-w-full max-h-96 rounded-lg shadow-md"> | ||||
|                 <source src={mediasrc} type="video" /> | ||||
|                 Your browser does not support the video tag. | ||||
|               </video> | ||||
|               )} */} | ||||
|           {// if fileType is not ubknown, show the media player
 | ||||
|             fileType && !fileType.startsWith("unknown") ? ( | ||||
|               <SharePage fileId={fileDetails.id} fileType={fileType} /> | ||||
|             ) : null} | ||||
|            | ||||
|         </div> | ||||
|         <div className="bg-white/10 shadow-md rounded-lg p-6 w-full max-w-md text-white"> | ||||
|           <p> | ||||
|             <strong>Name:</strong> {fileDetails.name} | ||||
|           </p> | ||||
|           <p> | ||||
|             <strong>Size:</strong> {(fileDetails.size / 1024).toFixed(2)} KB | ||||
|             <strong>Size:</strong> { // format size
 | ||||
|               fileDetails.size > 1024 * 1024 * 1024 * 1024 | ||||
|                 ? (fileDetails.size / (1024 * 1024 * 1024 * 1024)).toFixed(2) + " TB" | ||||
|                 : fileDetails.size > 1024 * 1024 * 1024 | ||||
|                 ? (fileDetails.size / (1024 * 1024 * 1024)).toFixed(2) + " GB" | ||||
|                 : fileDetails.size > 1024 * 1024 | ||||
|                 ? (fileDetails.size / (1024 * 1024)).toFixed(2) + " MB" | ||||
|                 : fileDetails.size > 1024 | ||||
|                 ? (fileDetails.size / 1024).toFixed(2) + " KB" | ||||
|                 : fileDetails.size + " Bytes" | ||||
|             } | ||||
|           </p> | ||||
|           <p> | ||||
|             <strong>Owner:</strong> {fileDetails.owner} | ||||
|             <strong>Owner:</strong> <img className="rounded-md inline size-5" src={ fileDetails.owneravatar ? ( fileDetails.owneravatar) : ""} alt="owner image" /> {fileDetails.owner} | ||||
|           </p> | ||||
|           <p> | ||||
|             <strong>Upload Date:</strong> {new Date(fileDetails.uploadDate).toLocaleString()} | ||||
|           </p> | ||||
|         </div> | ||||
|         <div className="flex gap-4 mt-6"> | ||||
|           <button | ||||
|             onClick={handleDownload} | ||||
|             className="rounded-full bg-blue-500 px-10 py-3 font-semibold no-underline transition hover:bg-blue-600" | ||||
|           > | ||||
|             Download | ||||
|           </button> | ||||
|           {fileDetails.isOwner && ( | ||||
|             <> | ||||
|               <button | ||||
|                 onClick={handleShare} | ||||
|                 className="rounded-full bg-green-500 px-10 py-3 font-semibold no-underline transition hover:bg-green-600" | ||||
|               > | ||||
|                 Share | ||||
|               </button> | ||||
|               <button | ||||
|                 onClick={handleRemove} | ||||
|                 className="rounded-full bg-red-500 px-10 py-3 font-semibold no-underline transition hover:bg-red-600" | ||||
|               > | ||||
|                 Remove | ||||
|               </button> | ||||
|             </> | ||||
|           )} | ||||
|         </div> | ||||
|       </div> | ||||
|     </main> | ||||
|   ); | ||||
| @ -254,7 +151,7 @@ function UploadsPage() { | ||||
| export default function Page() { | ||||
|   return ( | ||||
|     <Suspense fallback={<div>Loading...</div>}> | ||||
|       <UploadsPage /> | ||||
|       <Details /> | ||||
|     </Suspense> | ||||
|   ); | ||||
| } | ||||
|  | ||||
| @ -1,52 +0,0 @@ | ||||
| "use client"; | ||||
| 
 | ||||
| import { useEffect, useState } from "react"; | ||||
| import { useSearchParams } from "next/navigation"; | ||||
| 
 | ||||
| export function SharePage() { | ||||
|   const searchParams = useSearchParams(); | ||||
|   const fileId = searchParams.get("id"); | ||||
|   const [imageSrc, setImageSrc] = useState<string | null>(null); | ||||
|   const [error, setError] = useState<string | null>(null); | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     const fetchImage = async () => { | ||||
|       try { | ||||
|         if (!fileId) { | ||||
|           throw new Error("File name is required."); | ||||
|         } | ||||
|         const response = await fetch(`/api/serv?id=${encodeURIComponent(fileId)}`); | ||||
|         if (!response.ok) { | ||||
|           throw new Error("Failed to fetch image"); | ||||
|         } | ||||
| 
 | ||||
|         const blob = await response.blob(); | ||||
|         const objectUrl = URL.createObjectURL(blob); | ||||
|         setImageSrc(objectUrl); | ||||
|       } catch (err) { | ||||
|         console.error(err); | ||||
|         setError("Failed to load image."); | ||||
|       } | ||||
|     }; | ||||
| 
 | ||||
|     void fetchImage(); | ||||
| 
 | ||||
|     return () => { | ||||
|       if (imageSrc) { | ||||
|         URL.revokeObjectURL(imageSrc); | ||||
|       } | ||||
|     }; | ||||
|   }, [fileId, imageSrc]); | ||||
| 
 | ||||
|   if (error) { | ||||
|     return <div className="text-red-500">{error}</div>; | ||||
|   } | ||||
| 
 | ||||
|   if (!imageSrc) { | ||||
|     return <div>Loading...</div>; | ||||
|   } | ||||
| 
 | ||||
|   return ( | ||||
|       imageSrc | ||||
|   ); | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user