refactor: update file retrieval logic and enhance media handling in SharePage component

This commit is contained in:
ZareMate 2025-04-16 02:20:32 +02:00
parent 568a6556af
commit caedd0ae88
Signed by: zaremate
GPG Key ID: 369A0E45E03A81C3
5 changed files with 139 additions and 205 deletions

View 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" />;
}

View File

@ -14,7 +14,7 @@ export async function GET(req: Request) {
try { try {
// Fetch file metadata from the database // Fetch file metadata from the database
const file = await db.file.findFirst({ const file = await db.file.findFirst({
where: { name: fileId }, where: { id: fileId },
}); });
if (!file) { if (!file) {
@ -22,15 +22,35 @@ export async function GET(req: Request) {
} }
// Construct the file path // 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 // Read the file from the filesystem
const fileBuffer = await fs.readFile(filePath); 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 the file as a binary response
return new Response(fileBuffer, { return new Response(fileBuffer, {
headers: { headers: {
"Content-Type": file.extension.startsWith(".png") ? "image/png" : "application/octet-stream", "Content-Type": mimeType,
"Content-Disposition": `inline; filename="${file.name}"`, "Content-Disposition": `inline; filename="${file.name}"`,
}, },
}); });

View File

@ -24,7 +24,8 @@ export async function GET(req: Request) {
return NextResponse.json({ return NextResponse.json({
name: file.name, name: file.name,
size: file.size, 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, uploadDate: file.uploadDate,
id: file.id, id: file.id,
isOwner: session?.user?.id === file.uploadedById, isOwner: session?.user?.id === file.uploadedById,

View File

@ -4,15 +4,13 @@ import { Suspense } from "react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useSearchParams, useRouter } from "next/navigation"; import { useSearchParams, useRouter } from "next/navigation";
import toast, { Toaster } from "react-hot-toast"; import toast, { Toaster } from "react-hot-toast";
import Head from "next/head"; import { SharePage } from "~/app/_components/SharePage";
import { number } from "zod";
// import { SharePage } from "~/components/SharePage";
interface FileDetails { interface FileDetails {
name: string; name: string;
size: number; size: number;
owner: string; owner: string;
owneravatar: string | null;
uploadDate: string; uploadDate: string;
id: string; id: string;
isOwner: boolean; isOwner: boolean;
@ -20,13 +18,35 @@ interface FileDetails {
url: string; url: string;
} }
function UploadsPage() {
function Details() {
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const router = useRouter(); const router = useRouter();
const fileId = searchParams.get("id"); const fileId = searchParams.get("id");
const [fileDetails, setFileDetails] = useState<FileDetails | null>(null); const [fileDetails, setFileDetails] = useState<FileDetails | null>(null);
const [error, setError] = useState<string | 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(() => { useEffect(() => {
if (!fileId) { if (!fileId) {
@ -41,7 +61,7 @@ function UploadsPage() {
throw new Error("Failed to fetch file details"); 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); setFileDetails(data);
} catch (err) { } catch (err) {
console.error(err); console.error(err);
@ -49,110 +69,9 @@ function UploadsPage() {
} }
}; };
void fetchFileDetails(); // Use `void` to mark the promise as intentionally ignored fetchFileDetails();
}, [fileId]); }, [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) { if (error) {
return <div className="text-red-500">{error}</div>; return <div className="text-red-500">{error}</div>;
} }
@ -194,58 +113,36 @@ function UploadsPage() {
<span className="text-[hsl(280,100%,70%)]">File</span> Details <span className="text-[hsl(280,100%,70%)]">File</span> Details
</h1> </h1>
<div className="mt-6"> <div className="mt-6">
{/* {(fileDetails.type.startsWith(".png") || {// if fileType is not ubknown, show the media player
fileDetails.type.startsWith(".jpg") || fileType && !fileType.startsWith("unknown") ? (
fileDetails.type.startsWith(".jpeg") || <SharePage fileId={fileDetails.id} fileType={fileType} />
fileDetails.type.startsWith(".gif")) && (mediasrc && <img src={mediasrc} alt="Media preview" />)} ) : null}
{(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>
)} */}
</div> </div>
<div className="bg-white/10 shadow-md rounded-lg p-6 w-full max-w-md text-white"> <div className="bg-white/10 shadow-md rounded-lg p-6 w-full max-w-md text-white">
<p> <p>
<strong>Name:</strong> {fileDetails.name} <strong>Name:</strong> {fileDetails.name}
</p> </p>
<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>
<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>
<p> <p>
<strong>Upload Date:</strong> {new Date(fileDetails.uploadDate).toLocaleString()} <strong>Upload Date:</strong> {new Date(fileDetails.uploadDate).toLocaleString()}
</p> </p>
</div> </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> </div>
</main> </main>
); );
@ -254,7 +151,7 @@ function UploadsPage() {
export default function Page() { export default function Page() {
return ( return (
<Suspense fallback={<div>Loading...</div>}> <Suspense fallback={<div>Loading...</div>}>
<UploadsPage /> <Details />
</Suspense> </Suspense>
); );
} }

View File

@ -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
);
}