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 {
|
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}"`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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