Compare commits
3 Commits
8bc35d979d
...
1d302eb217
| Author | SHA1 | Date | |
|---|---|---|---|
| 1d302eb217 | |||
| 344334592e | |||
| 5ec3e011e1 |
120
src/app/_components/ActionButtons.tsx
Normal file
120
src/app/_components/ActionButtons.tsx
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
'use client';
|
||||||
|
import { useRef, useState } from "react";
|
||||||
|
import { useFileActions } from "~/app/_components/FileActions";
|
||||||
|
|
||||||
|
export function FileActionsContainer({
|
||||||
|
fileId,
|
||||||
|
fileName,
|
||||||
|
fileUrl,
|
||||||
|
isOwner,
|
||||||
|
}: {
|
||||||
|
fileId: string;
|
||||||
|
fileName: string;
|
||||||
|
fileUrl: string;
|
||||||
|
isOwner: boolean;
|
||||||
|
}) {
|
||||||
|
const { handleDownload, handleCopyUrl, handleRemove} = useFileActions(() => fileId, (description: string) => {
|
||||||
|
if (isOwner) {
|
||||||
|
console.log(description);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex self-center gap-2">
|
||||||
|
{/* Download Button */}
|
||||||
|
<button
|
||||||
|
onClick={() => handleDownload(fileId, fileName)}
|
||||||
|
className="flex items-center justify-center rounded-full bg-blue-500 p-2 hover:bg-blue-600"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={2}
|
||||||
|
stroke="currentColor"
|
||||||
|
className="h-5 w-5 text-white"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M4 16v2a2 2 0 002 2h12a2 2 0 002-2v-2M7 10l5 5m0 0l5-5m-5 5V3"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Copy URL Button */}
|
||||||
|
<button
|
||||||
|
onClick={() => handleCopyUrl(fileUrl)}
|
||||||
|
className="flex items-center justify-center rounded-full bg-green-500 p-2 hover:bg-green-600"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={2}
|
||||||
|
stroke="currentColor"
|
||||||
|
className="h-5 w-5 text-white"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M8 9h8m-6 4h4m-7 8h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Remove Button */}
|
||||||
|
<button
|
||||||
|
onClick={() => handleRemove(fileId)}
|
||||||
|
className="flex items-center justify-center rounded-full bg-red-500 p-2 hover:bg-red-600"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={2}
|
||||||
|
stroke="currentColor"
|
||||||
|
className="h-5 w-5 text-white"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M6 18L18 6M6 6l12 12"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function FileDescriptionContainer({
|
||||||
|
fileId,
|
||||||
|
fileDescriprtion,
|
||||||
|
}: {
|
||||||
|
fileId: string;
|
||||||
|
fileDescriprtion?: string;
|
||||||
|
}) {
|
||||||
|
|
||||||
|
const [description, setDescription] = useState(fileDescriprtion || ""); // Add state for description
|
||||||
|
const { handleDescriptionChange } = useFileActions(() => {}, (description: string) => {
|
||||||
|
setDescription(description);
|
||||||
|
return undefined;
|
||||||
|
}, fileId); // Wrap setDescription in a function
|
||||||
|
const debounceTimer = useRef<NodeJS.Timeout | null>(null); // Initialize debounce timer
|
||||||
|
|
||||||
|
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
|
handleDescriptionChange(e, debounceTimer); // Pass the debounce timer
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex self-center gap-2">
|
||||||
|
<textarea
|
||||||
|
className="w-full h-24 p-2 border rounded-md bg-gray-800 text-white"
|
||||||
|
value={description} // Use state value
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="Enter file description..."
|
||||||
|
maxLength={200} // Limit to 200 characters
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,12 +1,13 @@
|
|||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { env } from "~/env.js";
|
import { env } from "~/env.js";
|
||||||
|
import { notifyClients } from "~/utils/notifyClients";
|
||||||
|
|
||||||
export const useFileActions = (
|
export const useFileActions = (
|
||||||
setFiles: (callback: (prevFiles: any[]) => any[]) => void,
|
setFiles: (callback: (prevFiles: any[]) => any[]) => void,
|
||||||
setDescription?: (description: string) => void,
|
setDescription?: (description: string) => undefined,
|
||||||
fileId?: string
|
fileId?: string
|
||||||
) => {
|
) => {
|
||||||
const pageUrl = `${env.NEXT_PUBLIC_PAGE_URL}/share?id=`;
|
const pageUrl = `${env.NEXT_PUBLIC_PAGE_URL}`;
|
||||||
|
|
||||||
// Handle file download
|
// Handle file download
|
||||||
const handleDownload = async (fileId: string, fileName: string) => {
|
const handleDownload = async (fileId: string, fileName: string) => {
|
||||||
@ -44,15 +45,25 @@ export const useFileActions = (
|
|||||||
// Remove a file
|
// Remove a file
|
||||||
const handleRemove = async (fileId: string) => {
|
const handleRemove = async (fileId: string) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/remove`, {
|
const response = await fetch(`/api/files/remove`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ id: fileId }),
|
body: JSON.stringify({ id: fileId }),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (response.status === 403) {
|
||||||
|
toast.error("You are not authorized to remove this file.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!response.ok) throw new Error("Failed to delete file");
|
if (!response.ok) throw new Error("Failed to delete file");
|
||||||
|
|
||||||
setFiles((prevFiles) => prevFiles.filter((file) => file.id !== fileId));
|
setFiles((prevFiles) => prevFiles.filter((file) => file.id !== fileId));
|
||||||
toast.success("File removed successfully!");
|
toast.success("File removed successfully!");
|
||||||
|
// Go to the home page after removing the file
|
||||||
|
window.location.href = `${pageUrl}/`;
|
||||||
|
notifyClients({ type: "file-removed", fileId });
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
toast.error("Failed to remove file.");
|
toast.error("Failed to remove file.");
|
||||||
@ -64,7 +75,9 @@ export const useFileActions = (
|
|||||||
e: React.ChangeEvent<HTMLTextAreaElement>,
|
e: React.ChangeEvent<HTMLTextAreaElement>,
|
||||||
debounceTimer: React.RefObject<NodeJS.Timeout | null>
|
debounceTimer: React.RefObject<NodeJS.Timeout | null>
|
||||||
) => {
|
) => {
|
||||||
if (!setDescription) return;
|
if (setDescription === undefined) {console.error("setDescription function is not provided")
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
const newDescription = e.target.value;
|
const newDescription = e.target.value;
|
||||||
setDescription(newDescription);
|
setDescription(newDescription);
|
||||||
@ -72,7 +85,9 @@ export const useFileActions = (
|
|||||||
if (debounceTimer.current) {
|
if (debounceTimer.current) {
|
||||||
clearTimeout(debounceTimer.current);
|
clearTimeout(debounceTimer.current);
|
||||||
}
|
}
|
||||||
|
|
||||||
debounceTimer.current = setTimeout(() => {
|
debounceTimer.current = setTimeout(() => {
|
||||||
|
console.log("Calling handleDescriptionSave"); // Debug log
|
||||||
handleDescriptionSave(newDescription);
|
handleDescriptionSave(newDescription);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
};
|
};
|
||||||
@ -85,14 +100,22 @@ export const useFileActions = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/share?id=${encodeURIComponent(fileId)}`, {
|
const response = await fetch(`/api/files/share?id=${encodeURIComponent(fileId)}`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ description }),
|
// pass the fileId and description in the request body
|
||||||
|
body: JSON.stringify({ description, id: fileId }),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (response.status === 403) {
|
||||||
|
toast.error("You are not authorized to modify this file's description.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!response.ok) throw new Error("Failed to update description");
|
if (!response.ok) throw new Error("Failed to update description");
|
||||||
|
|
||||||
toast.success("Description updated successfully!");
|
toast.success("Description updated successfully!");
|
||||||
|
notifyClients({ type: "file-updated", fileId });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
toast.error("Failed to update description.");
|
toast.error("Failed to update description.");
|
||||||
|
|||||||
@ -21,7 +21,7 @@ export function FilePreview({ fileId, fileType }: FilePreviewProps) {
|
|||||||
|
|
||||||
const fetchMedia = async () => {
|
const fetchMedia = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/serv?id=${encodeURIComponent(fileId)}`);
|
const response = await fetch(`/api/files/serv?id=${encodeURIComponent(fileId)}`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Failed to fetch media");
|
throw new Error("Failed to fetch media");
|
||||||
}
|
}
|
||||||
|
|||||||
12
src/app/_components/HomeButton.tsx
Normal file
12
src/app/_components/HomeButton.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
export function HomeButton() {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={() => (window.location.href = "/")}
|
||||||
|
className="rounded-full bg-white/10 px-4 py-2 font-semibold hover:bg-white/20"
|
||||||
|
>
|
||||||
|
Home
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState, useRef } from "react";
|
import { useState, useRef } from "react";
|
||||||
import toast, { Toaster } from "react-hot-toast";
|
import toast, { Toaster } from "react-hot-toast";
|
||||||
|
import { notifyClients } from "~/utils/notifyClients";
|
||||||
|
|
||||||
export default function UploadForm() {
|
export default function UploadForm() {
|
||||||
const [file, setFile] = useState<File | null>(null);
|
const [file, setFile] = useState<File | null>(null);
|
||||||
@ -42,6 +43,7 @@ export default function UploadForm() {
|
|||||||
if (xhr.status === 200) {
|
if (xhr.status === 200) {
|
||||||
const response: { url: string } = JSON.parse(xhr.responseText); // Explicitly type the response
|
const response: { url: string } = JSON.parse(xhr.responseText); // Explicitly type the response
|
||||||
setUploadedFileUrl(response.url); // Assume the API returns the uploaded file URL
|
setUploadedFileUrl(response.url); // Assume the API returns the uploaded file URL
|
||||||
|
notifyClients({type: "file-uploaded", fileUrl: response.url}); // Notify other clients about the new file
|
||||||
toast.success("File uploaded successfully!");
|
toast.success("File uploaded successfully!");
|
||||||
|
|
||||||
// Clear the file input and reset state
|
// Clear the file input and reset state
|
||||||
|
|||||||
@ -22,8 +22,12 @@ export async function DELETE(req: Request) {
|
|||||||
where: { id: body.id },
|
where: { id: body.id },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!resource || resource.uploadedById !== session.user.id) {
|
if (!resource) {
|
||||||
return NextResponse.json({ error: "Resource not found or unauthorized" }, { status: 404 });
|
return NextResponse.json({ error: "File not found" }, { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resource.uploadedById !== session.user.id) {
|
||||||
|
return NextResponse.json({ error: "You are not authorized to delete this file" }, { status: 403 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const filePath = path.join(process.cwd(), "uploads", path.basename(body.id));
|
const filePath = path.join(process.cwd(), "uploads", path.basename(body.id));
|
||||||
@ -37,9 +41,9 @@ export async function DELETE(req: Request) {
|
|||||||
|
|
||||||
notifyClients({ type: "file-removed", fileId: body.id });
|
notifyClients({ type: "file-removed", fileId: body.id });
|
||||||
|
|
||||||
return NextResponse.json({ message: "Resource deleted successfully" });
|
return NextResponse.json({ message: "File deleted successfully" });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error deleting resource:", error);
|
console.error("Error deleting file:", error);
|
||||||
return NextResponse.json({ error: "Failed to delete resource" }, { status: 500 });
|
return NextResponse.json({ error: "Failed to delete file" }, { status: 500 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -41,23 +41,38 @@ export async function GET(req: Request) {
|
|||||||
|
|
||||||
export async function PUT(req: Request) {
|
export async function PUT(req: Request) {
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
const url = new URL(req.url);
|
|
||||||
const fileId = url.searchParams.get("id");
|
|
||||||
const { description = "" } = await req.json();
|
|
||||||
|
|
||||||
if (!fileId) {
|
if (!session?.user) {
|
||||||
return NextResponse.json({ error: "File name is required" }, { status: 400 });
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const file = await db.file.update({
|
const body = (await req.json()) as { id: string; description: string } | null;
|
||||||
where: { id: fileId },
|
if (!body?.id || body.description === undefined) {
|
||||||
data: { description },
|
// Allow empty description but ensure id is present
|
||||||
|
return NextResponse.json({ error: "Invalid request body" }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const resource = await db.file.findUnique({
|
||||||
|
where: { id: body.id },
|
||||||
});
|
});
|
||||||
|
|
||||||
return NextResponse.json(file);
|
if (!resource) {
|
||||||
|
return NextResponse.json({ error: "File not found" }, { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resource.uploadedById !== session.user.id) {
|
||||||
|
return NextResponse.json({ error: "You are not authorized to modify this file" }, { status: 403 });
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.file.update({
|
||||||
|
where: { id: body.id },
|
||||||
|
data: { description: body.description },
|
||||||
|
});
|
||||||
|
|
||||||
|
return NextResponse.json({ message: "Description updated successfully" });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating file description:", error);
|
console.error("Error updating description:", error);
|
||||||
return NextResponse.json({ error: "Failed to update file description" }, { status: 500 });
|
return NextResponse.json({ error: "Failed to update description" }, { status: 500 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,10 +1,8 @@
|
|||||||
"use client";
|
import { notFound } from "next/navigation";
|
||||||
|
|
||||||
import { useEffect, useState, useRef, Suspense } from "react";
|
|
||||||
import { useSearchParams, useRouter } from "next/navigation";
|
|
||||||
import toast, { Toaster } from "react-hot-toast";
|
|
||||||
import { FilePreview } from "~/app/_components/FilePreview";
|
import { FilePreview } from "~/app/_components/FilePreview";
|
||||||
import { useFileActions } from "~/app/_components/FileActions";
|
import { HomeButton } from "~/app/_components/HomeButton"; // Import the client component
|
||||||
|
import { Toaster } from "react-hot-toast";
|
||||||
|
import { FileActionsContainer, FileDescriptionContainer } from "~/app/_components/ActionButtons"; // Import the client component
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
|
|
||||||
interface FileDetails {
|
interface FileDetails {
|
||||||
@ -20,75 +18,45 @@ interface FileDetails {
|
|||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function FilePreviewContainerContent() {
|
async function fetchFileDetails(fileId: string): Promise<FileDetails | null> {
|
||||||
const searchParams = useSearchParams();
|
try {
|
||||||
const router = useRouter();
|
const response = await fetch(
|
||||||
const fileId = searchParams.get("id");
|
`${process.env.NEXT_PUBLIC_PAGE_URL}/api/files/share?id=${encodeURIComponent(
|
||||||
const [fileDetails, setFileDetails] = useState<FileDetails | null>(null);
|
fileId
|
||||||
const [error, setError] = useState<string | null>(null);
|
)}`,
|
||||||
const [description, setDescription] = useState<string>("");
|
{ cache: "no-store" }
|
||||||
const debounceTimer = useRef<NodeJS.Timeout | null>(null);
|
|
||||||
|
|
||||||
const { handleDescriptionChange, handleCopyUrl, handleDownload, handleRemove } = useFileActions(
|
|
||||||
() => {},
|
|
||||||
setDescription,
|
|
||||||
fileId || undefined
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const getFileType = (extension: string): string => {
|
if (!response.ok) {
|
||||||
const fileTypes: Record<string, string> = {
|
return null;
|
||||||
".mp4": "video/mp4",
|
|
||||||
".webm": "video/webm",
|
|
||||||
".ogg": "video/ogg",
|
|
||||||
".jpg": "image/jpeg",
|
|
||||||
".jpeg": "image/jpeg",
|
|
||||||
".png": "image/png",
|
|
||||||
".gif": "image/gif",
|
|
||||||
".svg": "image/svg+xml",
|
|
||||||
".mp3": "audio/mpeg",
|
|
||||||
".wav": "audio/wav",
|
|
||||||
};
|
|
||||||
return fileTypes[extension] || "unknown";
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!fileId) {
|
|
||||||
setError("File name is required.");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchFileDetails = async () => {
|
return response.json();
|
||||||
try {
|
|
||||||
const response = await fetch(`/api/share?id=${encodeURIComponent(fileId)}`);
|
|
||||||
if (!response.ok) throw new Error("Failed to fetch file details");
|
|
||||||
|
|
||||||
const data: FileDetails = await response.json();
|
|
||||||
setFileDetails(data);
|
|
||||||
setDescription(data.description);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error("Failed to fetch file details:", err);
|
||||||
setError("Failed to load file details.");
|
return null;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
fetchFileDetails();
|
export default async function FilePreviewContainer({
|
||||||
}, [fileId]);
|
searchParams,
|
||||||
|
}: {
|
||||||
|
searchParams: { id?: string };
|
||||||
|
}) {
|
||||||
|
const fileId = searchParams.id;
|
||||||
|
|
||||||
if (error) {
|
if (!fileId) {
|
||||||
return <div className="text-red-500">{error}</div>;
|
notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fileDetails = await fetchFileDetails(fileId);
|
||||||
|
|
||||||
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">
|
<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} />
|
<Toaster position="top-right" reverseOrder={false} />
|
||||||
<div className="absolute top-4 left-4">
|
<div className="absolute top-4 left-4">
|
||||||
<button
|
<HomeButton />
|
||||||
onClick={() => router.push("/")}
|
|
||||||
className="rounded-full bg-white/10 px-4 py-2 font-semibold hover:bg-white/20"
|
|
||||||
>
|
|
||||||
Home
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="container flex flex-col items-center gap-12 px-4 py-16">
|
<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]">
|
<h1 className="text-5xl font-extrabold tracking-tight sm:text-[5rem]">
|
||||||
@ -122,16 +90,19 @@ function FilePreviewContainerContent() {
|
|||||||
).toLocaleString()}`}
|
).toLocaleString()}`}
|
||||||
/>
|
/>
|
||||||
<meta property="og:type" content="website" />
|
<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>
|
</Head>
|
||||||
<main className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c] text-white">
|
<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">
|
<div className="absolute top-4 left-4">
|
||||||
<button
|
<HomeButton /> {/* Use the client component */}
|
||||||
onClick={() => router.push("/")}
|
|
||||||
className="rounded-full bg-white/10 px-4 py-2 font-semibold hover:bg-white/20"
|
|
||||||
>
|
|
||||||
Home
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="container flex flex-col items-center gap-12 px-4 py-16">
|
<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]">
|
<h1 className="text-5xl font-extrabold tracking-tight sm:text-[5rem]">
|
||||||
@ -139,7 +110,10 @@ function FilePreviewContainerContent() {
|
|||||||
</h1>
|
</h1>
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
{fileDetails.type !== "unknown" && (
|
{fileDetails.type !== "unknown" && (
|
||||||
<FilePreview fileId={fileDetails.id} fileType={getFileType(fileDetails.type)} />
|
<FilePreview
|
||||||
|
fileId={fileDetails.id}
|
||||||
|
fileType={fileDetails.type}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</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">
|
||||||
@ -169,81 +143,18 @@ function FilePreviewContainerContent() {
|
|||||||
<strong>Upload Date:</strong>{" "}
|
<strong>Upload Date:</strong>{" "}
|
||||||
{new Date(fileDetails.uploadDate).toLocaleString()}
|
{new Date(fileDetails.uploadDate).toLocaleString()}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<div>
|
||||||
<strong>Description:</strong>{" "}
|
<strong>Description:</strong>{" "}
|
||||||
{fileDetails.isOwner ? (
|
<FileDescriptionContainer fileId={fileDetails.id} fileDescriprtion={fileDetails.description}/>
|
||||||
<textarea
|
</div>
|
||||||
value={description}
|
<div className="mt-4 flex justify-center">
|
||||||
onChange={(e) => handleDescriptionChange(e, debounceTimer)}
|
<FileActionsContainer
|
||||||
className="w-full h-24 p-2 bg-white/10 rounded-lg text-white"
|
fileId={fileDetails.id}
|
||||||
/>
|
fileName={fileDetails.name}
|
||||||
) : fileDetails.description === "" ? (
|
fileUrl={fileDetails.url}
|
||||||
<span>No description available</span>
|
isOwner={fileDetails.isOwner}
|
||||||
) : (
|
/>
|
||||||
<span>{fileDetails.description}</span>
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
{fileDetails.isOwner && (
|
|
||||||
<div className="flex place-content-center gap-4 mt-4">
|
|
||||||
<button
|
|
||||||
onClick={() => handleDownload(fileDetails.id, fileDetails.name)}
|
|
||||||
className="rounded-full bg-blue-500 px-4 py-2 text-white hover:bg-blue-600 flex items-center gap-2"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
strokeWidth={2}
|
|
||||||
stroke="currentColor"
|
|
||||||
className="h-5 w-5 text-white"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
d="M4 16v2a2 2 0 002 2h12a2 2 0 002-2v-2M7 10l5 5m0 0l5-5m-5 5V3"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => handleCopyUrl(fileDetails.url)}
|
|
||||||
className="rounded-full bg-green-500 px-4 py-2 text-white hover:bg-green-600 flex items-center gap-2"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
strokeWidth={2}
|
|
||||||
stroke="currentColor"
|
|
||||||
className="h-5 w-5 text-white"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
d="M8 9h8m-6 4h4m-7 8h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v12a2 2 0 002 2z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => handleRemove(fileDetails.id)}
|
|
||||||
className="rounded-full bg-red-500 px-4 py-2 text-white hover:bg-red-600 flex items-center gap-2"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
strokeWidth={2}
|
|
||||||
stroke="currentColor"
|
|
||||||
className="h-5 w-5 text-white"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
d="M6 18L18 6M6 6l12 12"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
@ -251,11 +162,3 @@ function FilePreviewContainerContent() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function FilePreviewContainer() {
|
|
||||||
return (
|
|
||||||
<Suspense fallback={<div>Loading...</div>}>
|
|
||||||
<FilePreviewContainerContent />
|
|
||||||
</Suspense>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user