feat: enhance file actions with toast notifications and update public/private toggle logic
This commit is contained in:
		
							parent
							
								
									551cf2e2cb
								
							
						
					
					
						commit
						b368473216
					
				
							
								
								
									
										1
									
								
								public/icons/private.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								public/icons/private.svg
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12,17A2,2 0 0,0 14,15C14,13.89 13.1,13 12,13A2,2 0 0,0 10,15A2,2 0 0,0 12,17M18,8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H6A2,2 0 0,1 4,20V10C4,8.89 4.9,8 6,8H7V6A5,5 0 0,1 12,1A5,5 0 0,1 17,6V8H18M12,3A3,3 0 0,0 9,6V8H15V6A3,3 0 0,0 12,3Z" /></svg> | ||||
| After Width: | Height: | Size: 314 B | 
							
								
								
									
										1
									
								
								public/icons/public.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								public/icons/public.svg
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M17.9,17.39C17.64,16.59 16.89,16 16,16H15V13A1,1 0 0,0 14,12H8V10H10A1,1 0 0,0 11,9V7H13A2,2 0 0,0 15,5V4.59C17.93,5.77 20,8.64 20,12C20,14.08 19.2,15.97 17.9,17.39M11,19.93C7.05,19.44 4,16.08 4,12C4,11.38 4.08,10.78 4.21,10.21L9,15V16A2,2 0 0,0 11,18M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" /></svg> | ||||
| After Width: | Height: | Size: 406 B | 
| @ -1,13 +1,14 @@ | ||||
| "use client"; | ||||
| import { useRef, useState } from "react"; | ||||
| import { useFileActions } from "~/app/_components/FileActions"; | ||||
| import toast from "react-hot-toast"; | ||||
| 
 | ||||
| export function FileActionsContainer({ | ||||
|   fileId, | ||||
|   fileName, | ||||
|   fileUrl, | ||||
|   isOwner, | ||||
|   isPublic, | ||||
|   isPublic: initialIsPublic, // Rename to avoid conflict with local state
 | ||||
| }: { | ||||
|   fileId: string; | ||||
|   fileName: string; | ||||
| @ -15,77 +16,91 @@ export function FileActionsContainer({ | ||||
|   isOwner: boolean; | ||||
|   isPublic: boolean; | ||||
| }) { | ||||
|   const { handleDownload, handleCopyUrl, handleRemove} = useFileActions(() => fileId, (description: string) => { | ||||
|     if (isOwner) { | ||||
|       console.log(description); | ||||
|   const [isPublic, setIsPublic] = useState(initialIsPublic); // Local state for toggle
 | ||||
|   const { handleDownload, handleCopyUrl, handleRemove } = useFileActions( | ||||
|     () => fileId, | ||||
|     (description: string) => { | ||||
|       if (isOwner) { | ||||
|         console.log(description); | ||||
|       } | ||||
|     } | ||||
|   }); | ||||
|   ); | ||||
| 
 | ||||
|   return ( | ||||
|     <div className="flex self-center gap-2"> | ||||
|     <div className="flex gap-2 self-center"> | ||||
|       {/* Download Button */} | ||||
|       <button | ||||
|         onClick={() => handleDownload(fileId, fileName)} | ||||
|         className="flex items-center justify-center rounded-full bg-blue-500 p-2 hover:bg-blue-600" | ||||
|       onClick={() => handleDownload(fileId, fileName)} | ||||
|       className="flex items-center justify-center rounded-full bg-blue-500 p-2 hover:bg-blue-600" | ||||
|       > | ||||
|         <img src="/icons/download.svg" alt="Download" className="w-6 h-6" /> | ||||
|       <img src="/icons/download.svg" alt="Download" className="h-6 w-6" /> | ||||
|       </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" | ||||
|       onClick={() => { | ||||
|         handleCopyUrl(fileUrl); | ||||
|         toast.success("File URL copied to clipboard!"); | ||||
|       }} | ||||
|       className="flex items-center justify-center rounded-full bg-green-500 p-2 hover:bg-green-600" | ||||
|       > | ||||
|         <img src="/icons/copy.svg" alt="Copy URL" className="w-6 h-6" /> | ||||
|       <img src="/icons/copy.svg" alt="Copy URL" className="h-6 w-6" /> | ||||
|       </button> | ||||
| 
 | ||||
|       {/* Remove Button */} | ||||
|       {isOwner && ( | ||||
|       <button | ||||
|         onClick={() => handleRemove(fileId)} | ||||
|         onClick={async () => { | ||||
|         try { | ||||
|           await handleRemove(fileId); | ||||
|           toast.success("File removed successfully!"); | ||||
|         } catch (err) { | ||||
|           toast.error("Failed to remove file."); | ||||
|           console.error(err); | ||||
|         } | ||||
|         }} | ||||
|         className="flex items-center justify-center rounded-full bg-red-500 p-2 hover:bg-red-600" | ||||
|       > | ||||
|         <img src="/icons/delete.svg" alt="Remove" className="w-6 h-6" /> | ||||
|         <img src="/icons/delete.svg" alt="Remove" className="h-6 w-6" /> | ||||
|       </button> | ||||
|       )} | ||||
| 
 | ||||
|       {/* Public/Private Toggle */} | ||||
|       {isOwner && ( | ||||
|         <div className="mt-4 flex items-center gap-2"> | ||||
|         <label className="text-sm"> | ||||
|           Public: | ||||
|         </label> | ||||
|         <label | ||||
|           htmlFor={`public-toggle-${fileId}`} | ||||
|           className="relative inline-flex items-center cursor-pointer" | ||||
|         > | ||||
|           <input | ||||
|             id={`public-toggle-${fileId}`} | ||||
|             type="checkbox" | ||||
|             checked={isPublic} // Ensure this reflects the prop
 | ||||
|             onChange={async (e) => { | ||||
|               const newIsPublic = e.target.checked; | ||||
|               try { | ||||
|                 const response = await fetch(`/api/files/update-public`, { | ||||
|                   method: "POST", | ||||
|                   headers: { | ||||
|                     "Content-Type": "application/json", | ||||
|                   }, | ||||
|                   body: JSON.stringify({ fileId: fileId, isPublic: newIsPublic }), | ||||
|                 }); | ||||
|                 if (!response.ok) { | ||||
|                   throw new Error("Failed to update public status"); | ||||
|                 } | ||||
|                 console.log("Public status updated successfully"); | ||||
|               } catch (err) { | ||||
|                 console.error("Error updating public status:", err); | ||||
|               } | ||||
|             }} | ||||
|             className="sr-only peer" | ||||
|           /> | ||||
|           {/* Toggle Background */} | ||||
|           <div className="w-10 h-5 bg-gray-300 rounded-full peer-checked:bg-green-500 peer-focus:ring-2 peer-focus:ring-green-300 transition-colors"></div> | ||||
|           {/* Toggle Handle */} | ||||
|           <div className="absolute top-0.5 left-0.5 w-4 h-4 bg-white rounded-full shadow-md peer-checked:translate-x-5 transition-transform"></div> | ||||
|         </label> | ||||
|       </div> | ||||
|       <button | ||||
|         onClick={async () => { | ||||
|         const newIsPublic = !isPublic; | ||||
|         try { | ||||
|           const response = await fetch(`/api/files/update-public`, { | ||||
|           method: "POST", | ||||
|           headers: { | ||||
|             "Content-Type": "application/json", | ||||
|           }, | ||||
|           body: JSON.stringify({ | ||||
|             fileId: fileId, | ||||
|             isPublic: newIsPublic, | ||||
|           }), | ||||
|           }); | ||||
|           if (!response.ok) { | ||||
|           throw new Error("Failed to update public status"); | ||||
|           } | ||||
|           setIsPublic(newIsPublic); // Update local state
 | ||||
|           toast.success( | ||||
|           `File is now ${newIsPublic ? "Public" : "Private"}!` | ||||
|           ); | ||||
|         } catch (err) { | ||||
|           toast.error("Error updating public status."); | ||||
|           console.error(err); | ||||
|         } | ||||
|         }} | ||||
|         className="flex items-center justify-center rounded-full bg-gray-500 p-2 hover:bg-gray-600" | ||||
|       > | ||||
|         <img | ||||
|         src={isPublic ? "/icons/public.svg" : "/icons/private.svg"} | ||||
|         alt={isPublic ? "Public" : "Private"} | ||||
|         className="h-6 w-6" | ||||
|         /> | ||||
|       </button> | ||||
|       )} | ||||
|     </div> | ||||
|   ); | ||||
| @ -98,12 +113,15 @@ export function FileDescriptionContainer({ | ||||
|   fileId: string; | ||||
|   fileDescription?: string; | ||||
| }) { | ||||
| 
 | ||||
|   const [description, setDescription] = useState(fileDescription || ""); // Add state for description
 | ||||
|   const { handleDescriptionChange } = useFileActions(() => {}, (description: string) => { | ||||
|     setDescription(description); | ||||
|     return undefined; | ||||
|   }, fileId); // Wrap setDescription in a function
 | ||||
|   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>) => { | ||||
| @ -111,9 +129,9 @@ export function FileDescriptionContainer({ | ||||
|   }; | ||||
| 
 | ||||
|   return ( | ||||
|     <div className="flex self-center gap-2"> | ||||
|     <div className="flex gap-2 self-center"> | ||||
|       <textarea | ||||
|         className="w-full h-24 p-2 border rounded-md bg-gray-800 text-white" | ||||
|         className="h-24 w-full rounded-md border bg-gray-800 p-2 text-white" | ||||
|         value={description} // Use state value
 | ||||
|         onChange={handleChange} | ||||
|         placeholder="Enter file description..." | ||||
|  | ||||
| @ -15,7 +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
 | ||||
|   public: boolean; // Indicates if the file is public
 | ||||
| } | ||||
| 
 | ||||
| interface FileGridProps { | ||||
| @ -118,7 +118,7 @@ export default function FileGrid({ session }: FileGridProps) { | ||||
|                 fileName={file.name} | ||||
|                 fileUrl={file.url} | ||||
|                 isOwner={true} | ||||
|                 isPublic={file.isPublic} | ||||
|                 isPublic={file.public} | ||||
|               /> | ||||
|             </div> | ||||
|           </div> | ||||
|  | ||||
| @ -67,9 +67,9 @@ async function fetchFileDetails(fileId: string): Promise<FileDetails | null> { | ||||
|   try { | ||||
|     const response = await fetch( | ||||
|       `${process.env.NEXT_PUBLIC_PAGE_URL}/api/files/share?id=${encodeURIComponent( | ||||
|         fileId | ||||
|         fileId, | ||||
|       )}`,
 | ||||
|       { cache: "no-store" } | ||||
|       { cache: "no-store" }, | ||||
|     ); | ||||
| 
 | ||||
|     if (!response.ok) { | ||||
| @ -97,7 +97,6 @@ export default async function FilePreviewContainer({ | ||||
| 
 | ||||
|   const fileDetails = await fetchFileDetails(fileId); | ||||
| 
 | ||||
| 
 | ||||
|   if (!fileDetails) { | ||||
|     return ( | ||||
|       <main className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c] text-white"> | ||||
| @ -138,10 +137,10 @@ export default async function FilePreviewContainer({ | ||||
|             {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 | ||||
|               ? (fileDetails.size / 1024).toFixed(2) + " KB" | ||||
|               : fileDetails.size + " Bytes"} | ||||
|                 ? (fileDetails.size / (1024 * 1024)).toFixed(2) + " MB" | ||||
|                 : fileDetails.size > 1024 | ||||
|                   ? (fileDetails.size / 1024).toFixed(2) + " KB" | ||||
|                   : fileDetails.size + " Bytes"} | ||||
|           </p> | ||||
|           <p> | ||||
|             <strong>Owner:</strong>{" "} | ||||
| @ -163,46 +162,14 @@ export default async function FilePreviewContainer({ | ||||
|               fileDescription={fileDetails.description} | ||||
|             /> | ||||
|           </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 | ||||
|                 ? (fileDetails.size / 1024).toFixed(2) + " KB" | ||||
|                 : fileDetails.size + " Bytes"} | ||||
|             </p> | ||||
|             <p> | ||||
|               <strong>Owner:</strong>{" "} | ||||
|               <img | ||||
|                 className="rounded-md inline size-5" | ||||
|                 src={fileDetails.ownerAvatar || ""} | ||||
|                 alt="Owner avatar" | ||||
|               />{" "} | ||||
|               {fileDetails.owner} | ||||
|             </p> | ||||
|             <p> | ||||
|               <strong>Upload Date:</strong>{" "} | ||||
|               {new Date(fileDetails.uploadDate).toLocaleString()} | ||||
|             </p> | ||||
|             <div> | ||||
|               <strong>Description:</strong>{" "} | ||||
|               <FileDescriptionContainer fileId={fileDetails.id} fileDescription={fileDetails.description}/> | ||||
|             </div> | ||||
|             <div className="mt-4 flex justify-center"> | ||||
|               <FileActionsContainer | ||||
|                 fileId={fileDetails.id} | ||||
|                 fileName={fileDetails.name} | ||||
|                 fileUrl={fileDetails.url} | ||||
|                 isOwner={fileDetails.isOwner} | ||||
|                 isPublic={fileDetails.isPublic} | ||||
|               /> | ||||
|             </div> | ||||
|           <div className="mt-4 flex justify-center"> | ||||
|             <FileActionsContainer | ||||
|               fileId={fileDetails.id} | ||||
|               fileName={fileDetails.name} | ||||
|               fileUrl={fileDetails.url} | ||||
|               isOwner={true} | ||||
|               isPublic={fileDetails.isPublic} | ||||
|             /> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user