Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added functionality to Allow owners to grant admin privileges to shar… #14

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .idea/material_theme_project_new.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

231 changes: 152 additions & 79 deletions components/ActionDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"use client";

import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";

import {
DropdownMenu,
DropdownMenuContent,
Expand All @@ -15,11 +15,11 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { useState } from "react";
import React, { useState } from "react";
import Image from "next/image";
import Link from "next/link";
import { Models } from "node-appwrite";
import { actionsDropdownItems } from "@/constants";
import Link from "next/link";
import { constructDownloadUrl } from "@/lib/utils";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
Expand All @@ -29,57 +29,115 @@ import {
updateFileUsers,
} from "@/lib/actions/file.actions";
import { usePathname } from "next/navigation";
import { useToast } from "@/hooks/use-toast";
import { FileDetails, ShareInput } from "@/components/ActionsModalContent";

const ActionDropdown = ({ file }: { file: Models.Document }) => {
const ActionDropDown = ({
file,
currentUserEmail,
}: {
file: Models.Document;
currentUserEmail: string;
}) => {
const [isModalOpen, setIsModalOpen] = useState(false);
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const [action, setAction] = useState<ActionType | null>(null);
const [name, setName] = useState(file.name);
const [isLoading, setIsLoading] = useState(false);
const [emails, setEmails] = useState<string[]>([]);
const [isAdmin, setIsAdmin] = useState(false);

const path = usePathname();
const { toast } = useToast();
const { users: currentUsers, AdminUsers: currentAdminUsers } = file; // extracting user and admin emails

const closeAllModals = () => {
setIsModalOpen(false);
setIsDropdownOpen(false);
setAction(null);
setName(file.name);
// setEmails([]);
// setEmails([]);
};

const handleAction = async () => {
if (!action) return;
setIsLoading(true);
let success = false;

// Update the respective list based on admin status
const updatedUserEmails = isAdmin
? currentUsers
: Array.from(new Set([...currentUsers, ...emails]));

const updatedAdminEmails = isAdmin
? Array.from(new Set([...currentAdminUsers, ...emails]))
: currentAdminUsers;

const actions = {
rename: () =>
renameFile({ fileId: file.$id, name, extension: file.extension, path }),
share: () => updateFileUsers({ fileId: file.$id, emails, path }),

share: () =>
updateFileUsers({
fileId: file.$id,
userEmails: updatedUserEmails,
adminEmails: updatedAdminEmails,
path,
}),

delete: () =>
deleteFile({ fileId: file.$id, bucketFileId: file.bucketFileId, path }),
deleteFile({
fileId: file.$id,
bucketFileId: file.bucketFileId,
path,
}),
};

success = await actions[action.value as keyof typeof actions]();

if (success) closeAllModals();

if (success) {
closeAllModals();
toast({
description: (
<p className="body-2 text-white">{`${action.label} successfully done`}</p>
),
className: "success-toast",
});
}
setIsAdmin(false);
setIsLoading(false);
};

const handleRemoveUser = async (email: string) => {
const updatedEmails = emails.filter((e) => e !== email);
try {
// Extract current user and admin email lists

const success = await updateFileUsers({
fileId: file.$id,
emails: updatedEmails,
path,
});
// Determine the updated user and admin email lists
const updatedAdminEmails = currentAdminUsers.includes(email)
? currentAdminUsers.filter((e: string) => e !== email)
: currentAdminUsers;

if (success) setEmails(updatedEmails);
closeAllModals();
const updatedUserEmails = currentUsers.includes(email)
? currentUsers.filter((e: string) => e !== email)
: currentUsers;

// Update the file's user information
const isUpdated = await updateFileUsers({
fileId: file.$id,
userEmails: updatedUserEmails,
adminEmails: updatedAdminEmails,
path,
});

// Update the local state if the update was successful
if (isUpdated) {
setEmails((prevEmails) => prevEmails.filter((e) => e !== email));
// closeAllModals(); Uncomment if modal handling is needed
}
} catch (error) {
console.error("Failed to remove user:", error);
// Add any user-facing error handling or reporting logic here
}
};

const renderDialogContent = () => {
Expand All @@ -88,9 +146,9 @@ const ActionDropdown = ({ file }: { file: Models.Document }) => {
const { value, label } = action;

return (
<DialogContent className="shad-dialog button">
<DialogHeader className="flex flex-col gap-3">
<DialogTitle className="text-center text-light-100">
<DialogContent className={"shad-dialog button"}>
<DialogHeader className={"flex flex-col gap-3"}>
<DialogTitle className={"text-center text-light-100"}>
{label}
</DialogTitle>
{value === "rename" && (
Expand All @@ -100,35 +158,41 @@ const ActionDropdown = ({ file }: { file: Models.Document }) => {
onChange={(e) => setName(e.target.value)}
/>
)}

{value === "details" && <FileDetails file={file} />}

{value === "share" && (
<ShareInput
file={file}
onInputChange={setEmails}
onRemove={handleRemoveUser}
currentUserEmail={currentUserEmail}
setIsAdmin={setIsAdmin}
/>
)}

{value === "delete" && (
<p className="delete-confirmation">
Are you sure you want to delete{` `}
<span className="delete-file-name">{file.name}</span>?
<p className={"delete-confirmation"}>
Are you sure you want to delete{" "}
<span className={"delete-file-name"}>{file.name}</span>?
</p>
)}
</DialogHeader>
{["rename", "delete", "share"].includes(value) && (
<DialogFooter className="flex flex-col gap-3 md:flex-row">
<Button onClick={closeAllModals} className="modal-cancel-button">
<DialogFooter className={"flex flex-col gap-3 md:flex-row"}>
<Button onClick={closeAllModals} className={"modal-cancel-button"}>
Cancel
</Button>
<Button onClick={handleAction} className="modal-submit-button">
<p className="capitalize">{value}</p>

<Button onClick={handleAction} className={"modal-submit-button"}>
<p className={"capitalize"}>{value}</p>
{isLoading && (
<Image
src="/assets/icons/loader.svg"
alt="loader"
src={"/assets/icons/loader.svg"}
alt={"loader"}
width={24}
height={24}
className="animate-spin"
className={"animate-spin"}
/>
)}
</Button>
Expand All @@ -141,67 +205,76 @@ const ActionDropdown = ({ file }: { file: Models.Document }) => {
return (
<Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
<DropdownMenu open={isDropdownOpen} onOpenChange={setIsDropdownOpen}>
<DropdownMenuTrigger className="shad-no-focus">
<DropdownMenuTrigger className={"shad-no-focus"}>
<Image
src="/assets/icons/dots.svg"
alt="dots"
src={"/assets/icons/dots.svg"}
alt={"dots"}
width={34}
height={34}
height={24}
/>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuLabel className="max-w-[200px] truncate">
<DropdownMenuLabel className={"max-w-[200px] truncate"}>
{file.name}
</DropdownMenuLabel>
<DropdownMenuSeparator />
{actionsDropdownItems.map((actionItem) => (
<DropdownMenuItem
key={actionItem.value}
className="shad-dropdown-item"
onClick={() => {
setAction(actionItem);

if (
["rename", "share", "delete", "details"].includes(
actionItem.value,
)
) {
setIsModalOpen(true);
}
}}
>
{actionItem.value === "download" ? (
<Link
href={constructDownloadUrl(file.bucketFileId)}
download={file.name}
className="flex items-center gap-2"
>
<Image
src={actionItem.icon}
alt={actionItem.label}
width={30}
height={30}
/>
{actionItem.label}
</Link>
) : (
<div className="flex items-center gap-2">
<Image
src={actionItem.icon}
alt={actionItem.label}
width={30}
height={30}
/>
{actionItem.label}
</div>
)}
</DropdownMenuItem>
))}
{actionsDropdownItems
.filter(
(actionItem) =>
(actionItem.value !== "delete" &&
actionItem.value !== "rename") ||
file.owner.email === currentUserEmail ||
currentAdminUsers.includes(currentUserEmail),
// TODO: check if the currentUser has admin privileges
)
.map((actionItem) => (
<DropdownMenuItem
key={actionItem.value}
className={"shad-dropdown-item"}
onClick={() => {
setAction(actionItem);

if (
["rename", "share", "delete", "details"].includes(
actionItem.value,
)
) {
setIsModalOpen(true);
}
}}
>
{actionItem.value === "download" ? (
<Link
href={constructDownloadUrl(file.bucketFileId)}
download={file.name}
className="flex items-center gap-2"
>
<Image
src={actionItem.icon}
alt={actionItem.label}
width={30}
height={30}
/>
{actionItem.label}
</Link>
) : (
<div className="flex items-center gap-2">
<Image
src={actionItem.icon}
alt={actionItem.label}
width={30}
height={30}
/>
{actionItem.label}
</div>
)}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>

{renderDialogContent()}
</Dialog>
);
};
export default ActionDropdown;
export default ActionDropDown;
Loading