feat: implement metadata generation for file details and improve error handling

This commit is contained in:
ZareMate 2025-05-12 07:41:51 +02:00
parent 011695cf46
commit 6115851147
Signed by: zaremate
GPG Key ID: 369A0E45E03A81C3

View File

@ -2,8 +2,12 @@ import { notFound } from "next/navigation";
import { FilePreview } from "~/app/_components/FilePreview"; import { FilePreview } from "~/app/_components/FilePreview";
import { HomeButton } from "~/app/_components/HomeButton"; // Import the client component import { HomeButton } from "~/app/_components/HomeButton"; // Import the client component
import { Toaster } from "react-hot-toast"; import { Toaster } from "react-hot-toast";
import { FileActionsContainer, FileDescriptionContainer } from "~/app/_components/ActionButtons"; // Import the client component import {
FileActionsContainer,
FileDescriptionContainer,
} from "~/app/_components/ActionButtons"; // Import the client component
import Head from "next/head"; import Head from "next/head";
import type { Metadata } from "next";
interface FileDetails { interface FileDetails {
name: string; name: string;
@ -18,6 +22,46 @@ interface FileDetails {
description: string; description: string;
} }
export async function generateMetadata({
searchParams,
}: {
searchParams: { id?: string };
}): Promise<Metadata> {
const fileId = searchParams.id;
if (!fileId) {
return {
title: "File Not Found",
description: "The file you are looking for does not exist.",
};
}
const fileDetails = await fetchFileDetails(fileId);
if (!fileDetails) {
return {
title: "File Not Found",
description: "The file you are looking for does not exist.",
};
}
return {
title: fileDetails.name,
description: fileDetails.description || fileDetails.name,
openGraph: {
title: fileDetails.name,
description: fileDetails.description || fileDetails.name,
url: `${process.env.NEXT_PUBLIC_PAGE_URL}/share?id=${fileDetails.id}`,
images: [
{
url: `${process.env.NEXT_PUBLIC_PAGE_URL}/api/files/serv?id=${fileDetails.id}`,
alt: `${fileDetails.name} preview`,
},
],
},
};
}
async function fetchFileDetails(fileId: string): Promise<FileDetails | null> { async function fetchFileDetails(fileId: string): Promise<FileDetails | null> {
try { try {
const response = await fetch( const response = await fetch(
@ -41,10 +85,9 @@ async function fetchFileDetails(fileId: string): Promise<FileDetails | null> {
export default async function FilePreviewContainer({ export default async function FilePreviewContainer({
searchParams, searchParams,
}: { }: {
searchParams: Promise<{ id?: string }>; searchParams: { id?: string };
}) { }) {
const resolvedSearchParams = await searchParams; // Resolve the promise const fileId = searchParams.id;
const fileId = resolvedSearchParams.id;
if (!fileId) { if (!fileId) {
notFound(); notFound();
@ -54,130 +97,79 @@ export default async function FilePreviewContainer({
if (!fileDetails) { if (!fileDetails) {
return ( return (
<> <main className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c] text-white">
<Head> <Toaster position="top-right" reverseOrder={false} />
<title>No File Found</title> <div className="absolute top-4 left-4">
<meta property="og:title" content="No File Found" /> <HomeButton />
<meta </div>
property="og:description" <div className="container flex flex-col items-center gap-12 px-4 py-16">
content="The file you are looking for does not exist." <h1 className="text-5xl font-extrabold tracking-tight sm:text-[5rem]">
/> <span className="text-[hsl(280,100%,70%)]">No</span> File Found
<meta property="og:type" content="website" /> </h1>
<meta property="og:image" content="/images/no-file.png" /> </div>
<meta property="og:image:alt" content="No file found" /> </main>
<meta
property="og:url"
content={`${process.env.NEXT_PUBLIC_PAGE_URL}/share?id=${fileId}`}
/>
</Head>
<main className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c] text-white">
<Toaster position="top-right" reverseOrder={false} />
<div className="absolute top-4 left-4">
<HomeButton />
</div>
<div className="container flex flex-col items-center gap-12 px-4 py-16">
<h1 className="text-5xl font-extrabold tracking-tight sm:text-[5rem]">
<span className="text-[hsl(280,100%,70%)]">No</span> File Found
</h1>
</div>
</main>
</>
); );
} }
return ( return (
<> <main className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c] text-white">
<Head> <div className="absolute top-4 left-4">
<title>{fileDetails.name} - File Details</title> <HomeButton />
<meta </div>
property="og:title" <Toaster position="top-right" reverseOrder={false} />
content={`${fileDetails.name} - File Details`} <div className="container flex flex-col items-center gap-12 px-4 py-16">
/> <h1 className="text-5xl font-extrabold tracking-tight sm:text-[5rem]">
<meta <span className="text-[hsl(280,100%,70%)]">File</span> Details
property="og:description" </h1>
content={`Size: ${ <div className="mt-6">
fileDetails.size > 1024 * 1024 * 1024 {fileDetails.type !== "unknown" && (
<FilePreview fileId={fileDetails.id} fileType={fileDetails.type} />
)}
</div>
<div className="w-full max-w-md rounded-lg bg-white/10 p-6 text-white shadow-md">
<p>
<strong>Name:</strong> {fileDetails.name}
</p>
<p>
<strong>Size:</strong>{" "}
{fileDetails.size > 1024 * 1024 * 1024
? (fileDetails.size / (1024 * 1024 * 1024)).toFixed(2) + " GB" ? (fileDetails.size / (1024 * 1024 * 1024)).toFixed(2) + " GB"
: fileDetails.size > 1024 * 1024 : fileDetails.size > 1024 * 1024
? (fileDetails.size / (1024 * 1024)).toFixed(2) + " MB"
: fileDetails.size > 1024
? (fileDetails.size / 1024).toFixed(2) + " KB"
: fileDetails.size + " Bytes"
}, Owner: ${fileDetails.owner}, Uploaded on: ${new Date(
fileDetails.uploadDate
).toLocaleString()}`}
/>
<meta property="og:type" content="website" />
<meta
property="og:image"
content={`${process.env.NEXT_PUBLIC_PAGE_URL}/api/files/serv?id=${fileId}`}
/>
<meta property="og:image:alt" content={`${fileDetails.name} preview`} />
<meta
property="og:url"
content={`${process.env.NEXT_PUBLIC_PAGE_URL}/share?id=${fileId}`}
/>
</Head>
<main className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c] text-white">
<div className="absolute top-4 left-4">
<HomeButton />
</div>
<Toaster position="top-right" reverseOrder={false} />
<div className="container flex flex-col items-center gap-12 px-4 py-16">
<h1 className="text-5xl font-extrabold tracking-tight sm:text-[5rem]">
<span className="text-[hsl(280,100%,70%)]">File</span> Details
</h1>
<div className="mt-6">
{fileDetails.type !== "unknown" && (
<FilePreview
fileId={fileDetails.id}
fileType={fileDetails.type}
/>
)}
</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 * 1024 * 1024
? (fileDetails.size / (1024 * 1024 * 1024)).toFixed(2) + " GB"
: fileDetails.size > 1024 * 1024
? (fileDetails.size / (1024 * 1024)).toFixed(2) + " MB" ? (fileDetails.size / (1024 * 1024)).toFixed(2) + " MB"
: fileDetails.size > 1024 : fileDetails.size > 1024
? (fileDetails.size / 1024).toFixed(2) + " KB" ? (fileDetails.size / 1024).toFixed(2) + " KB"
: fileDetails.size + " Bytes"} : fileDetails.size + " Bytes"}
</p> </p>
<p> <p>
<strong>Owner:</strong>{" "} <strong>Owner:</strong>{" "}
<img <img
className="rounded-md inline size-5" className="inline size-5 rounded-md"
src={fileDetails.ownerAvatar || ""} src={fileDetails.ownerAvatar || ""}
alt="Owner avatar" alt="Owner avatar"
/>{" "} />{" "}
{fileDetails.owner} {fileDetails.owner}
</p> </p>
<p> <p>
<strong>Upload Date:</strong>{" "} <strong>Upload Date:</strong>{" "}
{new Date(fileDetails.uploadDate).toLocaleString()} {new Date(fileDetails.uploadDate).toLocaleString()}
</p> </p>
<div> <div>
<strong>Description:</strong>{" "} <strong>Description:</strong>{" "}
<FileDescriptionContainer fileId={fileDetails.id} fileDescription={fileDetails.description}/> <FileDescriptionContainer
</div> fileId={fileDetails.id}
<div className="mt-4 flex justify-center"> fileDescription={fileDetails.description}
<FileActionsContainer />
fileId={fileDetails.id} </div>
fileName={fileDetails.name} <div className="mt-4 flex justify-center">
fileUrl={fileDetails.url} <FileActionsContainer
isOwner={fileDetails.isOwner} fileId={fileDetails.id}
/> fileName={fileDetails.name}
</div> fileUrl={fileDetails.url}
isOwner={fileDetails.isOwner}
/>
</div> </div>
</div> </div>
</main> </div>
</> </main>
); );
} }