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";
|
"use client";
|
||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import { useFileActions } from "~/app/_components/FileActions";
|
import { useFileActions } from "~/app/_components/FileActions";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
export function FileActionsContainer({
|
export function FileActionsContainer({
|
||||||
fileId,
|
fileId,
|
||||||
fileName,
|
fileName,
|
||||||
fileUrl,
|
fileUrl,
|
||||||
isOwner,
|
isOwner,
|
||||||
isPublic,
|
isPublic: initialIsPublic, // Rename to avoid conflict with local state
|
||||||
}: {
|
}: {
|
||||||
fileId: string;
|
fileId: string;
|
||||||
fileName: string;
|
fileName: string;
|
||||||
@ -15,77 +16,91 @@ export function FileActionsContainer({
|
|||||||
isOwner: boolean;
|
isOwner: boolean;
|
||||||
isPublic: boolean;
|
isPublic: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { handleDownload, handleCopyUrl, handleRemove} = useFileActions(() => fileId, (description: string) => {
|
const [isPublic, setIsPublic] = useState(initialIsPublic); // Local state for toggle
|
||||||
if (isOwner) {
|
const { handleDownload, handleCopyUrl, handleRemove } = useFileActions(
|
||||||
console.log(description);
|
() => fileId,
|
||||||
|
(description: string) => {
|
||||||
|
if (isOwner) {
|
||||||
|
console.log(description);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex self-center gap-2">
|
<div className="flex gap-2 self-center">
|
||||||
{/* Download Button */}
|
{/* Download Button */}
|
||||||
<button
|
<button
|
||||||
onClick={() => handleDownload(fileId, fileName)}
|
onClick={() => handleDownload(fileId, fileName)}
|
||||||
className="flex items-center justify-center rounded-full bg-blue-500 p-2 hover:bg-blue-600"
|
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>
|
</button>
|
||||||
|
|
||||||
{/* Copy URL Button */}
|
{/* Copy URL Button */}
|
||||||
<button
|
<button
|
||||||
onClick={() => handleCopyUrl(fileUrl)}
|
onClick={() => {
|
||||||
className="flex items-center justify-center rounded-full bg-green-500 p-2 hover:bg-green-600"
|
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>
|
</button>
|
||||||
|
|
||||||
{/* Remove Button */}
|
{/* Remove Button */}
|
||||||
{isOwner && (
|
{isOwner && (
|
||||||
<button
|
<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"
|
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>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Public/Private Toggle */}
|
||||||
{isOwner && (
|
{isOwner && (
|
||||||
<div className="mt-4 flex items-center gap-2">
|
<button
|
||||||
<label className="text-sm">
|
onClick={async () => {
|
||||||
Public:
|
const newIsPublic = !isPublic;
|
||||||
</label>
|
try {
|
||||||
<label
|
const response = await fetch(`/api/files/update-public`, {
|
||||||
htmlFor={`public-toggle-${fileId}`}
|
method: "POST",
|
||||||
className="relative inline-flex items-center cursor-pointer"
|
headers: {
|
||||||
>
|
"Content-Type": "application/json",
|
||||||
<input
|
},
|
||||||
id={`public-toggle-${fileId}`}
|
body: JSON.stringify({
|
||||||
type="checkbox"
|
fileId: fileId,
|
||||||
checked={isPublic} // Ensure this reflects the prop
|
isPublic: newIsPublic,
|
||||||
onChange={async (e) => {
|
}),
|
||||||
const newIsPublic = e.target.checked;
|
});
|
||||||
try {
|
if (!response.ok) {
|
||||||
const response = await fetch(`/api/files/update-public`, {
|
throw new Error("Failed to update public status");
|
||||||
method: "POST",
|
}
|
||||||
headers: {
|
setIsPublic(newIsPublic); // Update local state
|
||||||
"Content-Type": "application/json",
|
toast.success(
|
||||||
},
|
`File is now ${newIsPublic ? "Public" : "Private"}!`
|
||||||
body: JSON.stringify({ fileId: fileId, isPublic: newIsPublic }),
|
);
|
||||||
});
|
} catch (err) {
|
||||||
if (!response.ok) {
|
toast.error("Error updating public status.");
|
||||||
throw new Error("Failed to update public status");
|
console.error(err);
|
||||||
}
|
}
|
||||||
console.log("Public status updated successfully");
|
}}
|
||||||
} catch (err) {
|
className="flex items-center justify-center rounded-full bg-gray-500 p-2 hover:bg-gray-600"
|
||||||
console.error("Error updating public status:", err);
|
>
|
||||||
}
|
<img
|
||||||
}}
|
src={isPublic ? "/icons/public.svg" : "/icons/private.svg"}
|
||||||
className="sr-only peer"
|
alt={isPublic ? "Public" : "Private"}
|
||||||
/>
|
className="h-6 w-6"
|
||||||
{/* 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>
|
</button>
|
||||||
{/* 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>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -98,12 +113,15 @@ export function FileDescriptionContainer({
|
|||||||
fileId: string;
|
fileId: string;
|
||||||
fileDescription?: string;
|
fileDescription?: string;
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
const [description, setDescription] = useState(fileDescription || ""); // Add state for description
|
const [description, setDescription] = useState(fileDescription || ""); // Add state for description
|
||||||
const { handleDescriptionChange } = useFileActions(() => {}, (description: string) => {
|
const { handleDescriptionChange } = useFileActions(
|
||||||
setDescription(description);
|
() => {},
|
||||||
return undefined;
|
(description: string) => {
|
||||||
}, fileId); // Wrap setDescription in a function
|
setDescription(description);
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
fileId,
|
||||||
|
); // Wrap setDescription in a function
|
||||||
const debounceTimer = useRef<NodeJS.Timeout | null>(null); // Initialize debounce timer
|
const debounceTimer = useRef<NodeJS.Timeout | null>(null); // Initialize debounce timer
|
||||||
|
|
||||||
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
@ -111,9 +129,9 @@ export function FileDescriptionContainer({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex self-center gap-2">
|
<div className="flex gap-2 self-center">
|
||||||
<textarea
|
<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
|
value={description} // Use state value
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
placeholder="Enter file description..."
|
placeholder="Enter file description..."
|
||||||
@ -121,4 +139,4 @@ export function FileDescriptionContainer({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,7 +15,7 @@ interface FileDetails {
|
|||||||
description: string;
|
description: string;
|
||||||
extension: string;
|
extension: string;
|
||||||
isOwner: boolean; // Indicates if the user owns the file
|
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 {
|
interface FileGridProps {
|
||||||
@ -118,7 +118,7 @@ export default function FileGrid({ session }: FileGridProps) {
|
|||||||
fileName={file.name}
|
fileName={file.name}
|
||||||
fileUrl={file.url}
|
fileUrl={file.url}
|
||||||
isOwner={true}
|
isOwner={true}
|
||||||
isPublic={file.isPublic}
|
isPublic={file.public}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -67,9 +67,9 @@ async function fetchFileDetails(fileId: string): Promise<FileDetails | null> {
|
|||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${process.env.NEXT_PUBLIC_PAGE_URL}/api/files/share?id=${encodeURIComponent(
|
`${process.env.NEXT_PUBLIC_PAGE_URL}/api/files/share?id=${encodeURIComponent(
|
||||||
fileId
|
fileId,
|
||||||
)}`,
|
)}`,
|
||||||
{ cache: "no-store" }
|
{ cache: "no-store" },
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@ -96,7 +96,6 @@ export default async function FilePreviewContainer({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const fileDetails = await fetchFileDetails(fileId);
|
const fileDetails = await fetchFileDetails(fileId);
|
||||||
|
|
||||||
|
|
||||||
if (!fileDetails) {
|
if (!fileDetails) {
|
||||||
return (
|
return (
|
||||||
@ -138,10 +137,10 @@ export default async function FilePreviewContainer({
|
|||||||
{fileDetails.size > 1024 * 1024 * 1024
|
{fileDetails.size > 1024 * 1024 * 1024
|
||||||
? (fileDetails.size / (1024 * 1024 * 1024)).toFixed(2) + " GB"
|
? (fileDetails.size / (1024 * 1024 * 1024)).toFixed(2) + " GB"
|
||||||
: fileDetails.size > 1024 * 1024
|
: fileDetails.size > 1024 * 1024
|
||||||
? (fileDetails.size / (1024 * 1024)).toFixed(2) + " MB"
|
? (fileDetails.size / (1024 * 1024)).toFixed(2) + " MB"
|
||||||
: fileDetails.size > 1024
|
: fileDetails.size > 1024
|
||||||
? (fileDetails.size / 1024).toFixed(2) + " KB"
|
? (fileDetails.size / 1024).toFixed(2) + " KB"
|
||||||
: fileDetails.size + " Bytes"}
|
: fileDetails.size + " Bytes"}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>Owner:</strong>{" "}
|
<strong>Owner:</strong>{" "}
|
||||||
@ -163,46 +162,14 @@ export default async function FilePreviewContainer({
|
|||||||
fileDescription={fileDetails.description}
|
fileDescription={fileDetails.description}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-white/10 shadow-md rounded-lg p-6 w-full max-w-md text-white">
|
<div className="mt-4 flex justify-center">
|
||||||
<p>
|
<FileActionsContainer
|
||||||
<strong>Name:</strong> {fileDetails.name}
|
fileId={fileDetails.id}
|
||||||
</p>
|
fileName={fileDetails.name}
|
||||||
<p>
|
fileUrl={fileDetails.url}
|
||||||
<strong>Size:</strong>{" "}
|
isOwner={true}
|
||||||
{fileDetails.size > 1024 * 1024 * 1024
|
isPublic={fileDetails.isPublic}
|
||||||
? (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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user