"use client"; import React, { useEffect } from "react"; import { observer } from "mobx-react"; import { useForm, Controller, useFieldArray } from "react-hook-form"; import { ChevronDown, Plus, X } from "lucide-react"; import { Dialog, Transition } from "@headlessui/react"; // plane imports import { ROLE, PROJECT_MEMBER_ADDED, EUserPermissions } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { Avatar, Button, CustomSelect, CustomSearchSelect, TOAST_TYPE, setToast } from "@plane/ui"; // helpers import { getFileURL } from "@/helpers/file.helper"; // hooks import { useEventTracker, useMember, useUserPermissions } from "@/hooks/store"; type Props = { isOpen: boolean; onClose: () => void; onSuccess?: () => void; projectId: string; workspaceSlug: string; }; type member = { role: EUserPermissions; member_id: string; }; type FormValues = { members: member[]; }; const defaultValues: FormValues = { members: [ { role: 5, member_id: "", }, ], }; export const SendProjectInvitationModal: React.FC = observer((props) => { const { isOpen, onClose, onSuccess, projectId, workspaceSlug } = props; // plane hooks const { t } = useTranslation(); // store hooks const { captureEvent } = useEventTracker(); const { getProjectRoleByWorkspaceSlugAndProjectId } = useUserPermissions(); const { project: { getProjectMemberDetails, bulkAddMembersToProject }, workspace: { workspaceMemberIds, getWorkspaceMemberDetails }, } = useMember(); // form info const { formState: { errors, isSubmitting }, watch, setValue, reset, handleSubmit, control, } = useForm(); const { fields, append, remove } = useFieldArray({ control, name: "members", }); // derived values const currentProjectRole = getProjectRoleByWorkspaceSlugAndProjectId(workspaceSlug, projectId); const uninvitedPeople = workspaceMemberIds?.filter((userId) => { const projectMemberDetails = getProjectMemberDetails(userId, projectId); const isInvited = projectMemberDetails?.member.id && projectMemberDetails?.original_role; return !isInvited; }); const onSubmit = async (formData: FormValues) => { if (!workspaceSlug || !projectId || isSubmitting) return; const payload = { ...formData }; await bulkAddMembersToProject(workspaceSlug.toString(), projectId.toString(), payload) .then(() => { if (onSuccess) onSuccess(); onClose(); setToast({ title: "Success!", type: TOAST_TYPE.SUCCESS, message: "Members added successfully.", }); captureEvent(PROJECT_MEMBER_ADDED, { members: [ ...payload.members.map((member) => ({ member_id: member.member_id, role: ROLE[member.role], })), ], state: "SUCCESS", element: "Project settings members page", }); }) .catch((error) => { console.error(error); captureEvent(PROJECT_MEMBER_ADDED, { state: "FAILED", element: "Project settings members page", }); }) .finally(() => { reset(defaultValues); }); }; const handleClose = () => { onClose(); const timeout = setTimeout(() => { reset(defaultValues); clearTimeout(timeout); }, 500); }; const appendField = () => { append({ role: 5, member_id: "", }); }; useEffect(() => { if (fields.length === 0) { append([ { role: 5, member_id: "", }, ]); } }, [fields, append]); const options = uninvitedPeople ?.map((userId) => { const memberDetails = getWorkspaceMemberDetails(userId); if (!memberDetails?.member) return; return { value: `${memberDetails?.member.id}`, query: `${memberDetails?.member.first_name} ${ memberDetails?.member.last_name } ${memberDetails?.member.display_name.toLowerCase()}`, content: (
{memberDetails?.member.display_name} ( {memberDetails?.member.first_name + " " + memberDetails?.member.last_name})
), }; }) .filter((option) => !!option) as | { value: string; query: string; content: React.JSX.Element; }[] | undefined; const checkCurrentOptionWorkspaceRole = (value: string) => { const currentMemberWorkspaceRole = getWorkspaceMemberDetails(value)?.role; if (!value || !currentMemberWorkspaceRole) return ROLE; const isGuestOROwner = [EUserPermissions.ADMIN, EUserPermissions.GUEST].includes( currentMemberWorkspaceRole as EUserPermissions ); return Object.fromEntries( Object.entries(ROLE).filter(([key]) => !isGuestOROwner || [currentMemberWorkspaceRole].includes(parseInt(key))) ); }; return (
{t("project_settings.members.invite_members.title")}

{t("project_settings.members.invite_members.sub_heading")}

{fields.map((field, index) => (
{ const selectedMember = getWorkspaceMemberDetails(value); return ( {value && value !== "" ? (
{selectedMember?.member.display_name}
) : (
Select co-worker
)}
( {field.value ? ROLE[field.value] : "Select role"}
} input optionsClassName="w-full" > {Object.entries( checkCurrentOptionWorkspaceRole(watch(`members.${index}.member_id`)) ).map(([key, label]) => { if (parseInt(key) > (currentProjectRole ?? EUserPermissions.GUEST)) return null; return ( {label} ); })} )} /> {errors.members && errors.members[index]?.role && ( {errors.members[index]?.role?.message} )}
{fields.length > 1 && (
)}
))}
); });