From 9b531aca4716c9f7f23d995fae772c67ede4f06d Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Tue, 25 Jul 2023 15:42:52 +0530 Subject: [PATCH] feat: workspace member bulk invite (#1530) * feat: workspace member bulk invite * style: invite dropdown styling --- .../project/send-project-invitation-modal.tsx | 1 - .../send-workspace-invitation-modal.tsx | 247 +++++++++++------- .../[workspaceSlug]/settings/members.tsx | 1 - 3 files changed, 155 insertions(+), 94 deletions(-) diff --git a/apps/app/components/project/send-project-invitation-modal.tsx b/apps/app/components/project/send-project-invitation-modal.tsx index 1e7f4f10e3..c2c1f4eda0 100644 --- a/apps/app/components/project/send-project-invitation-modal.tsx +++ b/apps/app/components/project/send-project-invitation-modal.tsx @@ -71,7 +71,6 @@ const SendProjectInvitationModal: React.FC = ({ isOpen, setIsOpen, member const { formState: { errors, isSubmitting }, - reset, handleSubmit, control, diff --git a/apps/app/components/workspace/send-workspace-invitation-modal.tsx b/apps/app/components/workspace/send-workspace-invitation-modal.tsx index cd48702fcf..a5ff08652b 100644 --- a/apps/app/components/workspace/send-workspace-invitation-modal.tsx +++ b/apps/app/components/workspace/send-workspace-invitation-modal.tsx @@ -1,23 +1,19 @@ -import React from "react"; - -import { mutate } from "swr"; +import React, { useEffect } from "react"; // react-hook-form -import { Controller, useForm } from "react-hook-form"; +import { Controller, useFieldArray, useForm } from "react-hook-form"; // headless import { Dialog, Transition } from "@headlessui/react"; // services import workspaceService from "services/workspace.service"; -// contexts -import { useWorkspaceMyMembership } from "contexts/workspace-member.context"; // hooks import useToast from "hooks/use-toast"; // ui import { CustomSelect, Input, PrimaryButton, SecondaryButton } from "components/ui"; +// icons +import { PlusIcon, XMarkIcon } from "@heroicons/react/24/outline"; // types -import { ICurrentUserResponse, IWorkspaceMemberInvitation } from "types"; -// fetch-keys -import { WORKSPACE_INVITATIONS } from "constants/fetch-keys"; +import { ICurrentUserResponse, IWorkspace, IWorkspaceMemberInvitation } from "types"; // constants import { ROLE } from "constants/workspace"; @@ -25,60 +21,92 @@ type Props = { isOpen: boolean; setIsOpen: React.Dispatch>; workspace_slug: string; - members: any[]; user: ICurrentUserResponse | undefined; }; -const defaultValues: Partial = { - email: "", - role: 5, +type EmailRole = { + email: string; + role: 5 | 10 | 15 | 20; +}; + +type FormValues = { + emails: EmailRole[]; +}; + +const defaultValues: FormValues = { + emails: [ + { + email: "", + role: 15, + }, + ], }; const SendWorkspaceInvitationModal: React.FC = ({ isOpen, setIsOpen, workspace_slug, - members, user, }) => { - const { setToastAlert } = useToast(); - const { memberDetails } = useWorkspaceMyMembership(); - const { control, - register, - formState: { errors, isSubmitting }, - handleSubmit, reset, - } = useForm({ - defaultValues, - reValidateMode: "onChange", + handleSubmit, + formState: { isSubmitting, errors }, + } = useForm(); + + const { fields, append, remove } = useFieldArray({ + control, + name: "emails", }); + const { setToastAlert } = useToast(); + const handleClose = () => { setIsOpen(false); - reset(defaultValues); + const timeout = setTimeout(() => { + reset(defaultValues); + clearTimeout(timeout); + }, 500); }; - const onSubmit = async (formData: IWorkspaceMemberInvitation) => { + const onSubmit = async (formData: FormValues) => { + if (!workspace_slug) return; + + const payload = { ...formData }; + await workspaceService - .inviteWorkspace(workspace_slug, { emails: [formData] }, user) - .then((res) => { + .inviteWorkspace(workspace_slug, payload, user) + .then(async (res) => { setIsOpen(false); handleClose(); - mutate(WORKSPACE_INVITATIONS, (prevData: any) => [ - { ...res, ...formData }, - ...(prevData ?? []), - ]); setToastAlert({ type: "success", title: "Success!", - message: "Member invited successfully.", + message: "Invitations sent successfully.", }); }) - .catch((err) => console.log(err)); + .catch((err) => { + setToastAlert({ + type: "error", + title: "Error!", + message: `${err.error}`, + }); + console.log(err); + }) + .finally(() => reset(defaultValues)); }; + const appendField = () => { + append({ email: "", role: 15 }); + }; + + useEffect(() => { + if (fields.length === 0) { + append([{ email: "", role: 15 }]); + } + }, [fields, append]); + return ( @@ -91,11 +119,11 @@ const SendWorkspaceInvitationModal: React.FC = ({ leaveFrom="opacity-100" leaveTo="opacity-0" > -
+
-
+
= ({ leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" > - -
+ + { + if (e.code === "Enter") e.preventDefault(); + }} + >
-
- - Members - + + Invite people to collaborate + +

Invite members to work on your workspace.

-
-
- { - if (members.find((member) => member.email === value)) - return "Email already exist"; - }, - }} - /> -
-
- ( - - {Object.entries(ROLE).map(([key, value]) => { - if (parseInt(key) > (memberDetails?.role ?? 5)) return null; - return ( - - {value} - - ); - })} - +
+ {fields.map((field, index) => ( +
+
+ ( + <> + + {errors.emails?.[index]?.email && ( + + {errors.emails?.[index]?.email?.message} + + )} + + )} + /> +
+
+ ( + {ROLE[value]}} + onChange={onChange} + width="w-full" + input + > + {Object.entries(ROLE).map(([key, value]) => ( + + {value} + + ))} + + )} + /> +
+ {fields.length > 1 && ( + )} - /> -
+
+ ))}
-
- Cancel - - {isSubmitting ? "Sending Invitation..." : "Send Invitation"} - + +
+ +
+ Cancel + + {isSubmitting ? "Sending Invitation..." : "Send Invitation"} + +
diff --git a/apps/app/pages/[workspaceSlug]/settings/members.tsx b/apps/app/pages/[workspaceSlug]/settings/members.tsx index e6c93de48b..35d7a03bd6 100644 --- a/apps/app/pages/[workspaceSlug]/settings/members.tsx +++ b/apps/app/pages/[workspaceSlug]/settings/members.tsx @@ -144,7 +144,6 @@ const MembersSettings: NextPage = () => { isOpen={inviteModal} setIsOpen={setInviteModal} workspace_slug={workspaceSlug as string} - members={members} user={user} />