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..."
|
||||
@ -121,4 +139,4 @@ export function FileDescriptionContainer({
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
@ -96,7 +96,6 @@ export default async function FilePreviewContainer({
|
||||
}
|
||||
|
||||
const fileDetails = await fetchFileDetails(fileId);
|
||||
|
||||
|
||||
if (!fileDetails) {
|
||||
return (
|
||||
@ -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