diff --git a/src/app/_components/ActionButtons.tsx b/src/app/_components/ActionButtons.tsx new file mode 100644 index 0000000..336a711 --- /dev/null +++ b/src/app/_components/ActionButtons.tsx @@ -0,0 +1,87 @@ +'use client'; +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 ( +
+ {/* Download Button */} + + + {/* Copy URL Button */} + + + {/* Remove Button */} + +
+ ); +} \ No newline at end of file diff --git a/src/app/_components/FileActions.tsx b/src/app/_components/FileActions.tsx index 804c053..5bda400 100644 --- a/src/app/_components/FileActions.tsx +++ b/src/app/_components/FileActions.tsx @@ -1,12 +1,13 @@ import toast from "react-hot-toast"; import { env } from "~/env.js"; +import { notifyClients } from "~/utils/notifyClients"; export const useFileActions = ( setFiles: (callback: (prevFiles: any[]) => any[]) => void, setDescription?: (description: string) => void, fileId?: string ) => { - const pageUrl = `${env.NEXT_PUBLIC_PAGE_URL}/share?id=`; + const pageUrl = `${env.NEXT_PUBLIC_PAGE_URL}`; // Handle file download const handleDownload = async (fileId: string, fileName: string) => { @@ -44,15 +45,25 @@ export const useFileActions = ( // Remove a file const handleRemove = async (fileId: string) => { try { - const response = await fetch(`/api/remove`, { + const response = await fetch(`/api/files/remove`, { method: "DELETE", headers: { "Content-Type": "application/json" }, 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"); setFiles((prevFiles) => prevFiles.filter((file) => file.id !== fileId)); 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) { console.error(err); toast.error("Failed to remove file."); @@ -85,14 +96,21 @@ export const useFileActions = ( } try { - const response = await fetch(`/api/share?id=${encodeURIComponent(fileId)}`, { + const response = await fetch(`/api/files/share?id=${encodeURIComponent(fileId)}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ description }), }); + + 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"); toast.success("Description updated successfully!"); + notifyClients({ type: "file-updated", fileId }); } catch (err) { console.error(err); toast.error("Failed to update description."); diff --git a/src/app/_components/FilePreview.tsx b/src/app/_components/FilePreview.tsx index 87c3f22..02c4d15 100644 --- a/src/app/_components/FilePreview.tsx +++ b/src/app/_components/FilePreview.tsx @@ -21,7 +21,7 @@ export function FilePreview({ fileId, fileType }: FilePreviewProps) { const fetchMedia = async () => { try { - const response = await fetch(`/api/serv?id=${encodeURIComponent(fileId)}`); + const response = await fetch(`/api/files/serv?id=${encodeURIComponent(fileId)}`); if (!response.ok) { throw new Error("Failed to fetch media"); } diff --git a/src/app/_components/HomeButton.tsx b/src/app/_components/HomeButton.tsx new file mode 100644 index 0000000..51befaf --- /dev/null +++ b/src/app/_components/HomeButton.tsx @@ -0,0 +1,12 @@ +"use client"; + +export function HomeButton() { + return ( + + ); +} \ No newline at end of file diff --git a/src/app/_components/UploadForm.tsx b/src/app/_components/UploadForm.tsx index 29aae38..a25dff7 100644 --- a/src/app/_components/UploadForm.tsx +++ b/src/app/_components/UploadForm.tsx @@ -2,6 +2,7 @@ import { useState, useRef } from "react"; import toast, { Toaster } from "react-hot-toast"; +import { notifyClients } from "~/utils/notifyClients"; export default function UploadForm() { const [file, setFile] = useState(null); @@ -42,6 +43,7 @@ export default function UploadForm() { if (xhr.status === 200) { const response: { url: string } = JSON.parse(xhr.responseText); // Explicitly type the response 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!"); // Clear the file input and reset state diff --git a/src/app/api/remove/route.ts b/src/app/api/files/remove/route.ts similarity index 69% rename from src/app/api/remove/route.ts rename to src/app/api/files/remove/route.ts index 6ebe2a0..9a14cbf 100644 --- a/src/app/api/remove/route.ts +++ b/src/app/api/files/remove/route.ts @@ -22,8 +22,12 @@ export async function DELETE(req: Request) { where: { id: body.id }, }); - if (!resource || resource.uploadedById !== session.user.id) { - return NextResponse.json({ error: "Resource not found or unauthorized" }, { status: 404 }); + 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 delete this file" }, { status: 403 }); } 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 }); - return NextResponse.json({ message: "Resource deleted successfully" }); + return NextResponse.json({ message: "File deleted successfully" }); } catch (error) { - console.error("Error deleting resource:", error); - return NextResponse.json({ error: "Failed to delete resource" }, { status: 500 }); + console.error("Error deleting file:", error); + return NextResponse.json({ error: "Failed to delete file" }, { status: 500 }); } } \ No newline at end of file diff --git a/src/app/api/serv/route.ts b/src/app/api/files/serv/route.ts similarity index 100% rename from src/app/api/serv/route.ts rename to src/app/api/files/serv/route.ts diff --git a/src/app/api/share/route.ts b/src/app/api/files/share/route.ts similarity index 55% rename from src/app/api/share/route.ts rename to src/app/api/files/share/route.ts index 3e60153..bfc2978 100644 --- a/src/app/api/share/route.ts +++ b/src/app/api/files/share/route.ts @@ -41,23 +41,37 @@ export async function GET(req: Request) { export async function PUT(req: Request) { const session = await auth(); - const url = new URL(req.url); - const fileId = url.searchParams.get("id"); - const { description = "" } = await req.json(); - if (!fileId) { - return NextResponse.json({ error: "File name is required" }, { status: 400 }); + if (!session?.user) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } try { - const file = await db.file.update({ - where: { id: fileId }, - data: { description }, + const body = (await req.json()) as { id: string; description: string } | null; + if (!body?.id || !body.description) { + 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) { - console.error("Error updating file description:", error); - return NextResponse.json({ error: "Failed to update file description" }, { status: 500 }); + console.error("Error updating description:", error); + return NextResponse.json({ error: "Failed to update description" }, { status: 500 }); } } \ No newline at end of file diff --git a/src/app/share/page.tsx b/src/app/share/page.tsx index e30be2f..4dd29c0 100644 --- a/src/app/share/page.tsx +++ b/src/app/share/page.tsx @@ -1,10 +1,8 @@ -"use client"; - -import { useEffect, useState, useRef } from "react"; -import { useSearchParams, useRouter } from "next/navigation"; -import toast, { Toaster } from "react-hot-toast"; +import { notFound } from "next/navigation"; 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 } from "~/app/_components/ActionButtons"; // Import the client component import Head from "next/head"; interface FileDetails { @@ -20,75 +18,45 @@ interface FileDetails { description: string; } -export default function FilePreviewContainer() { - const searchParams = useSearchParams(); - const router = useRouter(); - const fileId = searchParams.get("id"); - const [fileDetails, setFileDetails] = useState(null); - const [error, setError] = useState(null); - const [description, setDescription] = useState(""); - const debounceTimer = useRef(null); +async function fetchFileDetails(fileId: string): Promise { + try { + const response = await fetch( + `${process.env.NEXT_PUBLIC_PAGE_URL}/api/files/share?id=${encodeURIComponent( + fileId + )}`, + { cache: "no-store" } + ); - const { handleDescriptionChange, handleCopyUrl, handleDownload, handleRemove } = useFileActions( - () => {}, - setDescription, - fileId || undefined - ); - - const getFileType = (extension: string): string => { - const fileTypes: Record = { - ".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; + if (!response.ok) { + return null; } - const fetchFileDetails = async () => { - 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) { - console.error(err); - setError("Failed to load file details."); - } - }; - - fetchFileDetails(); - }, [fileId]); - - if (error) { - return
{error}
; + return response.json(); + } catch (err) { + console.error("Failed to fetch file details:", err); + return null; } +} + +export default async function FilePreviewContainer({ + searchParams, +}: { + searchParams: { id?: string }; +}) { + const fileId = searchParams.id; + + if (!fileId) { + notFound(); + } + + const fileDetails = await fetchFileDetails(fileId); if (!fileDetails) { return (
- +

@@ -122,16 +90,19 @@ export default function FilePreviewContainer() { ).toLocaleString()}`} /> + + +
-
- + {/* Use the client component */}

@@ -139,8 +110,10 @@ export default function FilePreviewContainer() {

{fileDetails.type !== "unknown" && ( - - + )}
@@ -172,79 +145,16 @@ export default function FilePreviewContainer() {

Description:{" "} - {fileDetails.isOwner ? ( -