From 99397e774c41980ef13b3155e13330ad4eaa7753 Mon Sep 17 00:00:00 2001 From: ZareMate <0.zaremate@gmail.com> Date: Fri, 9 May 2025 10:51:48 +0200 Subject: [PATCH] WIP: implement public file visibility feature and enhance search functionality --- prisma/schema.prisma | 13 +-- src/app/_components/ActionButtons.tsx | 47 ++++++++- src/app/_components/FileGrid.tsx | 4 +- src/app/api/files/search/route.ts | 25 +++++ src/app/api/files/share/route.ts | 1 + src/app/api/files/update-public/route.ts | 22 +++++ src/app/page.tsx | 45 ++++++--- src/app/search/page.tsx | 121 +++++++++++++++++++++++ src/app/share/page.tsx | 3 + tailwind.config.ts | 6 -- 10 files changed, 254 insertions(+), 33 deletions(-) create mode 100644 src/app/api/files/search/route.ts create mode 100644 src/app/api/files/update-public/route.ts create mode 100644 src/app/search/page.tsx delete mode 100644 tailwind.config.ts diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 97f9ba3..80636c9 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -15,17 +15,6 @@ datasource db { url = env("DATABASE_URL") } -model Post { - id Int @id @default(autoincrement()) - name String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - createdBy User @relation(fields: [createdById], references: [id]) - createdById String - - @@index([name]) -} // Necessary for Next auth model Account { @@ -63,7 +52,6 @@ model User { image String? accounts Account[] sessions Session[] - posts Post[] files File[] // Relation to the File model } @@ -85,4 +73,5 @@ model File { description String @default("") uploadedBy User? @relation(fields: [uploadedById], references: [id], onDelete: SetNull) uploadedById String? + public Boolean @default(false) // Indicates if the file is public or private } diff --git a/src/app/_components/ActionButtons.tsx b/src/app/_components/ActionButtons.tsx index fc975ab..2253ca9 100644 --- a/src/app/_components/ActionButtons.tsx +++ b/src/app/_components/ActionButtons.tsx @@ -1,4 +1,4 @@ -'use client'; +"use client"; import { useRef, useState } from "react"; import { useFileActions } from "~/app/_components/FileActions"; @@ -7,11 +7,13 @@ export function FileActionsContainer({ fileName, fileUrl, isOwner, + isPublic, }: { fileId: string; fileName: string; fileUrl: string; isOwner: boolean; + isPublic: boolean; }) { const { handleDownload, handleCopyUrl, handleRemove} = useFileActions(() => fileId, (description: string) => { if (isOwner) { @@ -36,14 +38,55 @@ export function FileActionsContainer({ > Copy URL - {/* Remove Button */} + {isOwner && ( + )} + {isOwner && ( +
+ + +
+ )} ); } diff --git a/src/app/_components/FileGrid.tsx b/src/app/_components/FileGrid.tsx index e98bc9b..091566f 100644 --- a/src/app/_components/FileGrid.tsx +++ b/src/app/_components/FileGrid.tsx @@ -15,6 +15,7 @@ interface FileDetails { description: string; extension: string; isOwner: boolean; // Indicates if the user owns the file + isPublic: boolean; // Indicates if the file is public } interface FileGridProps { @@ -116,7 +117,8 @@ export default function FileGrid({ session }: FileGridProps) { fileId={file.id} fileName={file.name} fileUrl={file.url} - isOwner={file.isOwner} + isOwner={true} + isPublic={file.isPublic} /> diff --git a/src/app/api/files/search/route.ts b/src/app/api/files/search/route.ts new file mode 100644 index 0000000..d1c4144 --- /dev/null +++ b/src/app/api/files/search/route.ts @@ -0,0 +1,25 @@ +import { NextResponse } from "next/server"; +import { db } from "~/server/db"; + +export async function GET(req: Request) { + const url = new URL(req.url); + const query = url.searchParams.get("query") || ""; + + try { + // if query is empty, return no files + const files = await db.file.findMany({ + where: { + OR: [ + { name: { contains: query } }, + { description: { contains: query } }, + ], + public: true, + }, + }); + + return NextResponse.json({ files }); + } catch (error) { + console.error("Error fetching files:", error); + return NextResponse.json({ error: "Failed to fetch files" }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/files/share/route.ts b/src/app/api/files/share/route.ts index 6b92650..fd7f25e 100644 --- a/src/app/api/files/share/route.ts +++ b/src/app/api/files/share/route.ts @@ -32,6 +32,7 @@ export async function GET(req: Request) { type: file.extension, url: file.url, description: file.description, + isPublic: file.public, // Ensure this is included }); } catch (error) { console.error("Error fetching file details:", error); diff --git a/src/app/api/files/update-public/route.ts b/src/app/api/files/update-public/route.ts new file mode 100644 index 0000000..7d675e0 --- /dev/null +++ b/src/app/api/files/update-public/route.ts @@ -0,0 +1,22 @@ +import { NextResponse } from "next/server"; +import { db } from "~/server/db"; + +export async function POST(req: Request) { + try { + const { fileId, isPublic } = await req.json(); + + if (!fileId) { + return NextResponse.json({ error: "File ID is required" }, { status: 400 }); + } + + await db.file.update({ + where: { id: fileId }, + data: { public: isPublic }, + }); + + return NextResponse.json({ success: true }); + } catch (error) { + console.error("Error updating public status:", error); + return NextResponse.json({ error: "Failed to update public status" }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/page.tsx b/src/app/page.tsx index 37629a1..534bb04 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -8,14 +8,35 @@ import { Toaster } from "react-hot-toast"; export default async function Home() { const session = await auth(); - return (
{/* Top-right corner sign-out button */} {session?.user && ( -
+
+ {/* Search Button */} + + + + + + - + ) : (

@@ -41,16 +62,16 @@ export default async function Home() {

)} {!session?.user && ( -
-
- - {session ? "Sign out" : "Sign in"} - +
+
+ + {session ? "Sign out" : "Sign in"} + +
-
)}
diff --git a/src/app/search/page.tsx b/src/app/search/page.tsx new file mode 100644 index 0000000..a778de6 --- /dev/null +++ b/src/app/search/page.tsx @@ -0,0 +1,121 @@ +"use client"; +import { useEffect, useState } from "react"; +import { useRouter } from "next/navigation"; +import { env } from "~/env.js"; +import { FilePreview } from "~/app/_components/FilePreview"; +import { FileActionsContainer } from "~/app/_components/ActionButtons"; +import { HomeButton } from "~/app/_components/HomeButton"; + +interface FileDetails { + name: string; + size: number; + owner: string; + ownerAvatar: string | null; + uploadDate: string; + id: string; + isOwner: boolean; + extension: string; + url: string; + description: string; + isPublic: boolean; +} + +export default function SearchFile() { + const [searchQuery, setSearchQuery] = useState(""); + const [files, setFiles] = useState([]); + const [error, setError] = useState(null); + const pageUrl = env.NEXT_PUBLIC_PAGE_URL; + const router = useRouter(); + + useEffect(() => { + const fetchFiles = async () => { + try { + const response = await fetch( + `/api/files/search?query=${encodeURIComponent(searchQuery)}` + ); + if (!response.ok) { + throw new Error("Failed to fetch files"); + } + const data = await response.json(); + setFiles(data.files); + } catch (err) { + console.error(err); + setError("Failed to load files."); + } + }; + + fetchFiles(); + }, [searchQuery]); + + return ( +
+
+ +
+ {/* Search Bar */} +
+
+ setSearchQuery(e.target.value)} + className="w-full p-3 pl-12 text-white bg-[#3b0764] rounded-full shadow-md focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent placeholder-gray-400" + /> + + + +
+
+ + {/* File Grid */} +
+ {error &&
{error}
} +
+ {files.map((file) => { + return ( +
+
+ +
+ + + {file.description && ( +

+ Description: {file.description} +

+ )} + +
+ +
+
+ ); + })} +
+
+
+ ); +} diff --git a/src/app/share/page.tsx b/src/app/share/page.tsx index 88891f5..fe0ea4c 100644 --- a/src/app/share/page.tsx +++ b/src/app/share/page.tsx @@ -16,6 +16,7 @@ interface FileDetails { type: string; url: string; description: string; + isPublic: boolean; } async function fetchFileDetails(fileId: string): Promise { @@ -51,6 +52,7 @@ export default async function FilePreviewContainer({ } const fileDetails = await fetchFileDetails(fileId); + if (!fileDetails) { return ( @@ -154,6 +156,7 @@ export default async function FilePreviewContainer({ fileName={fileDetails.name} fileUrl={fileDetails.url} isOwner={fileDetails.isOwner} + isPublic={fileDetails.isPublic} /> diff --git a/tailwind.config.ts b/tailwind.config.ts deleted file mode 100644 index 3d83e5c..0000000 --- a/tailwind.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - content: [ - "./src/**/*.{js,ts,jsx,tsx}", // Ensure your paths are correct - ], - plugins: [require("@tailwindcss/typography")], -}; \ No newline at end of file