From 85fa1942e9020432e0c63b8cbee6bb74d7dfb937 Mon Sep 17 00:00:00 2001
From: ZareMate <0.zaremate@gmail.com>
Date: Tue, 20 May 2025 13:10:37 +0200
Subject: [PATCH] Fuck git
---
 package-lock.json                        |  16 +++
 package.json                             |   2 +
 src/app/LoadingSkeleton.tsx              |  83 ++++++++++++---
 src/app/_components/ActionButtons.tsx    |   2 +-
 src/app/_components/FileGrid.tsx         |  16 +--
 src/app/_components/GenerateMetadata.tsx |  45 ++++++++
 src/app/_components/UploadForm.tsx       | 130 +++++++++++++----------
 src/app/api/upload/route.ts              |  23 +++-
 src/app/page.tsx                         | 100 ++++++++++++++---
 src/app/share/LoadingSkeleton.tsx        |  31 ++++--
 src/app/share/page.tsx                   |  41 ++++---
 src/utils/fileType.ts                    |  20 +++-
 12 files changed, 383 insertions(+), 126 deletions(-)
 create mode 100644 src/app/_components/GenerateMetadata.tsx
diff --git a/package-lock.json b/package-lock.json
index ddf8714..3d30c2f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -18,6 +18,7 @@
         "@trpc/client": "^11.0.0",
         "@trpc/react-query": "^11.0.0",
         "@trpc/server": "^11.0.0",
+        "cuid": "^3.0.0",
         "dompurify": "^3.2.5",
         "github-markdown-css": "^5.8.1",
         "gray-matter": "^4.0.3",
@@ -41,6 +42,7 @@
         "@eslint/eslintrc": "^3.3.1",
         "@tailwindcss/postcss": "^4.0.15",
         "@types/busboy": "^1.5.4",
+        "@types/mime-types": "^2.1.4",
         "@types/node": "^20.14.10",
         "@types/react": "^19.0.0",
         "@types/react-dom": "^19.0.0",
@@ -2317,6 +2319,13 @@
         "@types/unist": "*"
       }
     },
+    "node_modules/@types/mime-types": {
+      "version": "2.1.4",
+      "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz",
+      "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/@types/ms": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
@@ -3524,6 +3533,13 @@
       "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
       "license": "MIT"
     },
+    "node_modules/cuid": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/cuid/-/cuid-3.0.0.tgz",
+      "integrity": "sha512-WZYYkHdIDnaxdeP8Misq3Lah5vFjJwGuItJuV+tvMafosMzw0nF297T7mrm8IOWiPJkV6gc7sa8pzx27+w25Zg==",
+      "deprecated": "Cuid and other k-sortable and non-cryptographic ids (Ulid, ObjectId, KSUID, all UUIDs) are all insecure. Use @paralleldrive/cuid2 instead.",
+      "license": "MIT"
+    },
     "node_modules/cytoscape": {
       "version": "3.32.0",
       "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.32.0.tgz",
diff --git a/package.json b/package.json
index 052bf01..61b4188 100644
--- a/package.json
+++ b/package.json
@@ -30,6 +30,7 @@
     "@trpc/client": "^11.0.0",
     "@trpc/react-query": "^11.0.0",
     "@trpc/server": "^11.0.0",
+    "cuid": "^3.0.0",
     "dompurify": "^3.2.5",
     "github-markdown-css": "^5.8.1",
     "gray-matter": "^4.0.3",
@@ -53,6 +54,7 @@
     "@eslint/eslintrc": "^3.3.1",
     "@tailwindcss/postcss": "^4.0.15",
     "@types/busboy": "^1.5.4",
+    "@types/mime-types": "^2.1.4",
     "@types/node": "^20.14.10",
     "@types/react": "^19.0.0",
     "@types/react-dom": "^19.0.0",
diff --git a/src/app/LoadingSkeleton.tsx b/src/app/LoadingSkeleton.tsx
index fdfd7ce..0aeb0eb 100644
--- a/src/app/LoadingSkeleton.tsx
+++ b/src/app/LoadingSkeleton.tsx
@@ -1,21 +1,70 @@
-import React from 'react';
+import React, { Suspense } from "react";
+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
 
-const LoadingSkeleton = () => (
-  
-    {/* Title Skeleton */}
-    
-    {/* FileGrid Skeleton */}
-    
-      {[...Array(6)].map((_, i) => (
-        
-      ))}
-    
-    {/* UploadForm Skeleton */}
-    
-  
+         
+      
+      
+      
+        
+          File Details
+        
+        
+        
+          
+            Name: 
+          
+          
+            Size: 
+          
+          
+            Owner: 
+          
+          
+            Upload Date: 
+          
+          
+            Description: 
+          
+          
+            
+          
+        
+      
 diff --git a/src/app/_components/FileGrid.tsx b/src/app/_components/FileGrid.tsx
index 21a23ed..cf3779a 100644
--- a/src/app/_components/FileGrid.tsx
+++ b/src/app/_components/FileGrid.tsx
@@ -1,13 +1,11 @@
 "use client";
 
 import { useEffect, useState } from "react";
-import toast from "react-hot-toast";
 import { useRouter } from "next/navigation";
 import { env } from "~/env.js";
 import { FilePreview } from "~/app/_components/FilePreview";
 import { useFileActions } from "~/app/_components/FileActions";
 import { FileActionsContainer } from "./ActionButtons";
-import { checkOwner } from "~/utils/checkOwner"; // Import the client component
 
 interface FileDetails {
   id: string;
@@ -73,13 +71,15 @@ export default function FileGrid({ session }: FileGridProps) {
 
     const eventSource = new EventSource("/api/files/stream");
     eventSource.onmessage = (event) => {
-      const data: { type: string; file?: FileDetails; fileId?: string } = JSON.parse(event.data);
-
-      if (data.type === "file-added" && data.file) {
-        setFiles((prevFiles) => (data.file ? [...prevFiles, data.file] : prevFiles));
-        toast.success(`File "${data.file.name}" added!`);
+      const data: { type: string; fileId?: string } = JSON.parse(event.data);
+      console.log("SSE event:", data);
+      if (data.type === "file-added" && data.fileId) {
+        fetchFiles();
+      } else if (data.type === "file-updated" && data.fileId) {
+        // Fetch the updated file details
+        fetchFiles();
       } else if (data.type === "file-removed" && data.fileId) {
-        setFiles((prevFiles) => prevFiles.filter((file) => file.id !== data.fileId));
+        setFiles((prevFiles => prevFiles.filter(file => file.id !== data.fileId)));
       }
     };
 
diff --git a/src/app/_components/GenerateMetadata.tsx b/src/app/_components/GenerateMetadata.tsx
new file mode 100644
index 0000000..f593917
--- /dev/null
+++ b/src/app/_components/GenerateMetadata.tsx
@@ -0,0 +1,45 @@
+import type { Metadata } from "next";
+
+export async function generateMetadata({
+  searchParams,
+}: {
+  searchParams: { id?: string };
+}): Promise {
+  const fileId = searchParams.id;
+
+  if (!fileId) {
+    return {
+      title: "File Not Found",
+      description: "The file you are looking for does not exist.",
+    };
+  }
+
+  // Fetch file details for metadata
+  const response = await fetch(
+    `${process.env.NEXT_PUBLIC_PAGE_URL}/api/files/share?id=${encodeURIComponent(fileId)}`,
+    { cache: "no-store" },
+  );
+  if (!response.ok) {
+    return {
+      title: "File Not Found",
+      description: "The file you are looking for does not exist.",
+    };
+  }
+  const fileDetails = await response.json();
+
+  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`,
+        },
+      ],
+    },
+  };
+}
diff --git a/src/app/_components/UploadForm.tsx b/src/app/_components/UploadForm.tsx
index a25dff7..044b234 100644
--- a/src/app/_components/UploadForm.tsx
+++ b/src/app/_components/UploadForm.tsx
@@ -10,6 +10,7 @@ export default function UploadForm() {
   const [uploadedFileUrl, setUploadedFileUrl] = useState(null);
   const [progress, setProgress] = useState(0); // Track upload progress
   const fileInputRef = useRef(null); // Ref for the file input
+  const [isDragActive, setIsDragActive] = useState(false); // Track drag state
 
   const handleFileChange = (e: React.ChangeEvent) => {
     if (e.target.files) {
@@ -20,6 +21,34 @@ export default function UploadForm() {
     }
   };
 
+  // Drag and drop handlers
+  const handleDragOver = (e: React.DragEvent) => {
+    e.preventDefault();
+    e.stopPropagation();
+    setIsDragActive(true);
+  };
+
+  const handleDragLeave = (e: React.DragEvent) => {
+    e.preventDefault();
+    e.stopPropagation();
+    setIsDragActive(false);
+  };
+
+  const handleDrop = (e: React.DragEvent) => {
+    e.preventDefault();
+    e.stopPropagation();
+    setIsDragActive(false);
+    if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
+      setFile(e.dataTransfer.files[0] ?? null);
+      setUploadedFileUrl(null);
+      setProgress(0);
+      setUploading(false);
+      if (fileInputRef.current) {
+        fileInputRef.current.value = "";
+      }
+    }
+  };
+
   const handleUpload = async () => {
     if (!file) return toast.error("Please select a file to upload.");
     setUploading(true);
@@ -41,15 +70,14 @@ export default function UploadForm() {
 
       xhr.onload = () => {
         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
+          const response = JSON.parse(xhr.responseText);
+          setUploadedFileUrl(response.file?.url || null); // Use the new response structure
           toast.success("File uploaded successfully!");
 
           // Clear the file input and reset state
           setFile(null);
           if (fileInputRef.current) {
-            fileInputRef.current.value = ""; // Clear the file input
+            fileInputRef.current.value = "";
           }
         } else {
           console.error("Upload failed:", xhr.responseText);
@@ -86,42 +114,48 @@ export default function UploadForm() {
       {/* Toast container */}
       
 
-
       
diff --git a/src/app/_components/FileGrid.tsx b/src/app/_components/FileGrid.tsx
index 21a23ed..cf3779a 100644
--- a/src/app/_components/FileGrid.tsx
+++ b/src/app/_components/FileGrid.tsx
@@ -1,13 +1,11 @@
 "use client";
 
 import { useEffect, useState } from "react";
-import toast from "react-hot-toast";
 import { useRouter } from "next/navigation";
 import { env } from "~/env.js";
 import { FilePreview } from "~/app/_components/FilePreview";
 import { useFileActions } from "~/app/_components/FileActions";
 import { FileActionsContainer } from "./ActionButtons";
-import { checkOwner } from "~/utils/checkOwner"; // Import the client component
 
 interface FileDetails {
   id: string;
@@ -73,13 +71,15 @@ export default function FileGrid({ session }: FileGridProps) {
 
     const eventSource = new EventSource("/api/files/stream");
     eventSource.onmessage = (event) => {
-      const data: { type: string; file?: FileDetails; fileId?: string } = JSON.parse(event.data);
-
-      if (data.type === "file-added" && data.file) {
-        setFiles((prevFiles) => (data.file ? [...prevFiles, data.file] : prevFiles));
-        toast.success(`File "${data.file.name}" added!`);
+      const data: { type: string; fileId?: string } = JSON.parse(event.data);
+      console.log("SSE event:", data);
+      if (data.type === "file-added" && data.fileId) {
+        fetchFiles();
+      } else if (data.type === "file-updated" && data.fileId) {
+        // Fetch the updated file details
+        fetchFiles();
       } else if (data.type === "file-removed" && data.fileId) {
-        setFiles((prevFiles) => prevFiles.filter((file) => file.id !== data.fileId));
+        setFiles((prevFiles => prevFiles.filter(file => file.id !== data.fileId)));
       }
     };
 
diff --git a/src/app/_components/GenerateMetadata.tsx b/src/app/_components/GenerateMetadata.tsx
new file mode 100644
index 0000000..f593917
--- /dev/null
+++ b/src/app/_components/GenerateMetadata.tsx
@@ -0,0 +1,45 @@
+import type { Metadata } from "next";
+
+export async function generateMetadata({
+  searchParams,
+}: {
+  searchParams: { id?: string };
+}): Promise {
+  const fileId = searchParams.id;
+
+  if (!fileId) {
+    return {
+      title: "File Not Found",
+      description: "The file you are looking for does not exist.",
+    };
+  }
+
+  // Fetch file details for metadata
+  const response = await fetch(
+    `${process.env.NEXT_PUBLIC_PAGE_URL}/api/files/share?id=${encodeURIComponent(fileId)}`,
+    { cache: "no-store" },
+  );
+  if (!response.ok) {
+    return {
+      title: "File Not Found",
+      description: "The file you are looking for does not exist.",
+    };
+  }
+  const fileDetails = await response.json();
+
+  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`,
+        },
+      ],
+    },
+  };
+}
diff --git a/src/app/_components/UploadForm.tsx b/src/app/_components/UploadForm.tsx
index a25dff7..044b234 100644
--- a/src/app/_components/UploadForm.tsx
+++ b/src/app/_components/UploadForm.tsx
@@ -10,6 +10,7 @@ export default function UploadForm() {
   const [uploadedFileUrl, setUploadedFileUrl] = useState(null);
   const [progress, setProgress] = useState(0); // Track upload progress
   const fileInputRef = useRef(null); // Ref for the file input
+  const [isDragActive, setIsDragActive] = useState(false); // Track drag state
 
   const handleFileChange = (e: React.ChangeEvent) => {
     if (e.target.files) {
@@ -20,6 +21,34 @@ export default function UploadForm() {
     }
   };
 
+  // Drag and drop handlers
+  const handleDragOver = (e: React.DragEvent) => {
+    e.preventDefault();
+    e.stopPropagation();
+    setIsDragActive(true);
+  };
+
+  const handleDragLeave = (e: React.DragEvent) => {
+    e.preventDefault();
+    e.stopPropagation();
+    setIsDragActive(false);
+  };
+
+  const handleDrop = (e: React.DragEvent) => {
+    e.preventDefault();
+    e.stopPropagation();
+    setIsDragActive(false);
+    if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
+      setFile(e.dataTransfer.files[0] ?? null);
+      setUploadedFileUrl(null);
+      setProgress(0);
+      setUploading(false);
+      if (fileInputRef.current) {
+        fileInputRef.current.value = "";
+      }
+    }
+  };
+
   const handleUpload = async () => {
     if (!file) return toast.error("Please select a file to upload.");
     setUploading(true);
@@ -41,15 +70,14 @@ export default function UploadForm() {
 
       xhr.onload = () => {
         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
+          const response = JSON.parse(xhr.responseText);
+          setUploadedFileUrl(response.file?.url || null); // Use the new response structure
           toast.success("File uploaded successfully!");
 
           // Clear the file input and reset state
           setFile(null);
           if (fileInputRef.current) {
-            fileInputRef.current.value = ""; // Clear the file input
+            fileInputRef.current.value = "";
           }
         } else {
           console.error("Upload failed:", xhr.responseText);
@@ -86,42 +114,48 @@ export default function UploadForm() {
       {/* Toast container */}
       
 
-      
-        {/* Custom file input */}
-        
+      {/* Drag and Drop Area */}
+      
 fileInputRef.current?.click()}
+        style={{ cursor: "pointer" }}
+      >
+        {/* Hidden file input for click-to-select */}
         
+        
+          {isDragActive ? "Drop your file here" : "Drag & drop a file here, or click to select"}
+        
+        {file && (
+          
+        
{file.name}
+        {/* Add button to remove file */}
+        
+          
+        )}
+      
+      {/* Show upload button only when file is selected */}
+      {file && (
+      
         
-      
+      
@@ -142,31 +176,17 @@ export default function UploadForm() {
         
       )}
 
-      {uploadedFileUrl && (
+      {/* {uploadedFileUrl && file && (
         
-          
{uploadedFileUrl}
+          
{file.name}
           
         
+      {[...Array(6)].map((_, i) => (
+        
+      ))}
+    
               Please log in to upload and view files.
             
-          )}
+          ) : null}
           {!session?.user && (
             
               
-                
-                  {session ? "Sign out" : "Sign in"}
-                
+                {!loading ? (
+                  
+                    {session ? "Sign out" : "Sign in"}
+                  
+                ) : (
+                  
+                )}
               
 
           )}
         
       
-    
+    >
   );
 }
+
+export default Home;
diff --git a/src/app/share/LoadingSkeleton.tsx b/src/app/share/LoadingSkeleton.tsx
index dfddfdc..0aeb0eb 100644
--- a/src/app/share/LoadingSkeleton.tsx
+++ b/src/app/share/LoadingSkeleton.tsx
@@ -16,23 +16,42 @@ const LoadingSkeleton: React.FC = () => (
           File Details
         
         
-          {" Loading..."}
+          
         
           
-            Name:{" Loading..."}
+            Name: 
           
           
-            Size:{" Loading..."}
+            Size: 
           
           
-            Owner:{" Loading..."}
+            Owner: 
           
           
-            Upload Date:{" Loading..."}
+            Upload Date: 
           
           
-            Description:{" Loading..."}
+            Description: 
           
           
             
         
           {fileDetails.type !== "unknown" && (
-            
+            Loading...
}>
+              
+            
           )}
          
         
@@ -148,32 +151,40 @@ export default async function FilePreviewContainer({
           
           
             Owner:{" "}
-            ![]() Loading...
Loading...
}>
+              

{" "}
+              />{" "}
             {fileDetails.owner}
+            
           
           
             Upload Date:{" "}
-            {new Date(fileDetails.uploadDate).toLocaleString()}
+            Loading...
 }>
+              {new Date(fileDetails.uploadDate).toLocaleString()}
+            
           
           
             Description:{" "}
-            
+            Loading...
}>
+              
+            
           
           
-            
+            Loading...
}>
+              
+            
           
         
       
diff --git a/src/utils/fileType.ts b/src/utils/fileType.ts
index 59b9efc..4487466 100644
--- a/src/utils/fileType.ts
+++ b/src/utils/fileType.ts
@@ -1,32 +1,48 @@
 // This function takes a file name as input and returns the file type based on its extension.
+import mime from "mime-types";
 
 export function getFileType(fileName: string): string {
     const extension = fileName.split(".").pop()?.toLowerCase();
     const fileTypes: Record = {
+        // Video
         "mp4": "video/mp4",
         "webm": "video/webm",
         "ogg": "video/ogg",
+        // Image
         "jpg": "image/jpeg",
         "jpeg": "image/jpeg",
         "png": "image/png",
         "gif": "image/gif",
         "svg": "image/svg+xml",
+        // Audio
         "mp3": "audio/mpeg",
         "wav": "audio/wav",
+        // Archive
         "zip": "archive/zip",
         "rar": "archive/rar",
+        "jar": "archive/jar",
+        "iso": "archive/iso",
+        // Text
         "pdf": "text/pdf",
         "txt": "text/plain",
+        // Code
         "c": "code/c",
         "cpp": "code/cpp",
         "py": "code/python",
         "js": "code/javascript",
         "html": "code/html",
         "css": "code/css",
-        "md": "markdown/markdown",
         "json": "code/json",
         "xml": "code/xml",
         "csv": "code/csv",
+        // Markdown
+        "md": "markdown/markdown",
+        // Applications
+        "exe": "application/executable",
+        "apk": "application/android",
     };
-    return extension ? fileTypes[extension] || "unknown" : "unknown";
+    return extension ? fileTypes[extension] ||
+    //get the file type using the mime type library
+    mime.lookup(extension) || "application/octet-stream" : "application/octet-stream";
+
   };
\ No newline at end of file