diff --git a/front/pages/w/[wId]/members/index.tsx b/front/pages/w/[wId]/members/index.tsx index e57bdc58c9a0..e3874e3a6799 100644 --- a/front/pages/w/[wId]/members/index.tsx +++ b/front/pages/w/[wId]/members/index.tsx @@ -239,6 +239,7 @@ export default function WorkspaceAdmin({ setInviteEmailModalOpen(false); }} owner={owner} + members={members} /> setChangeRoleModalOpen(false)} - owner={owner} /> Member list @@ -392,14 +392,19 @@ function InviteEmailModal({ showModal, onClose, owner, + members, }: { showModal: boolean; onClose: () => void; owner: WorkspaceType; + members: UserType[]; }) { const [inviteEmail, setInviteEmail] = useState(""); const [isSending, setIsSending] = useState(false); const [emailError, setEmailError] = useState(""); + // when set, the modal to reinvite a user that was revoked will be shown + const [existingRevokedUser, setExistingRevokedUser] = + useState(null); const { mutate } = useSWRConfig(); const sendNotification = useContext(SendNotificationsContext); async function handleSendInvitation(): Promise { @@ -407,6 +412,15 @@ function InviteEmailModal({ setEmailError("Invalid email address."); return; } + const existing = members.find((m) => m.email === inviteEmail); + if (existing) { + if (existing.workspaces[0].role !== "none") { + setEmailError("User is already a member of this workspace."); + } else { + setExistingRevokedUser(existing); + } + return; + } const res = await fetch(`/api/w/${owner.sId}/invitations`, { method: "POST", headers: { @@ -420,7 +434,8 @@ function InviteEmailModal({ sendNotification({ type: "error", title: "Invite failed", - description: "Failed to invite new member to workspace.", + description: + "Failed to invite new member to workspace: " + res.statusText, }); } else { sendNotification({ @@ -433,44 +448,109 @@ function InviteEmailModal({ } return ( - { - setIsSending(true); - await handleSendInvitation(); - setIsSending(false); - setInviteEmail(""); - }} - > -
- - Invite a new user to your workspace. They will receive an email with a - link to join your workspace. - -
-
Email to send invite to:
-
-
- { - setInviteEmail(e.trim()); - setEmailError(""); - }} - /> + <> + setExistingRevokedUser(null)} + user={existingRevokedUser} + /> + { + setIsSending(true); + await handleSendInvitation(); + setIsSending(false); + setInviteEmail(""); + }} + > +
+ + Invite a new user to your workspace. They will receive an email with + a link to join your workspace. + +
+
Email to send invite to:
+
+
+ { + setInviteEmail(e.trim()); + setEmailError(""); + }} + /> +
+
+ + ); +} + +function ReinviteUserModal({ + onClose, + user, +}: { + onClose: (show: boolean) => void; + user: UserType | null; +}) { + const { mutate } = useSWRConfig(); + const sendNotification = useContext(SendNotificationsContext); + const [isSaving, setIsSaving] = useState(false); + if (!user) return null; + return ( + onClose(false)} + hasChanged={false} + title="Reinstate user?" + type="default" + > +
+
+ {" "} + {`${user.fullName} (${user.email}) `} was previously revoked from the + workspace. You can reinstate them, in which case they will be switched + back to a 'user' role and regain access to their historical data on + Dust (e.g. conversation history...). This will take effect + immediately. +
+
+
); @@ -652,53 +732,61 @@ function RevokeInvitationModal({ ); } +async function handleMemberRoleChange({ + member, + role, + mutate, + sendNotification, +}: { + member: UserType; + role: RoleType; + mutate: any; + sendNotification: any; +}): Promise { + const res = await fetch( + `/api/w/${member.workspaces[0].sId}/members/${member.id}`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + role: role === "none" ? "revoked" : role, + }), + } + ); + if (!res.ok) { + sendNotification({ + type: "error", + title: "Update failed", + description: "Failed to update member's role.", + }); + } else { + sendNotification({ + type: "success", + title: "Role updated", + description: `Role updated to ${role} for ${member.fullName}.`, + }); + await mutate(`/api/w/${member.workspaces[0].sId}/members`); + } +} + function ChangeMemberModal({ showModal, onClose, member, - owner, }: { showModal: boolean; onClose: () => void; member: UserType | null; - owner: WorkspaceType; }) { const { mutate } = useSWRConfig(); + const sendNotification = useContext(SendNotificationsContext); const [revokeMemberModalOpen, setRevokeMemberModalOpen] = useState(false); const [selectedRole, setSelectedRole] = useState(null); const [isSaving, setIsSaving] = useState(false); - const sendNotification = useContext(SendNotificationsContext); if (!member) return null; // Unreachable - async function handleMemberRoleChange( - member: UserType, - role: RoleType - ): Promise { - const res = await fetch(`/api/w/${owner.sId}/members/${member.id}`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - role: role === "none" ? "revoked" : role, - }), - }); - if (!res.ok) { - sendNotification({ - type: "error", - title: "Update failed", - description: "Failed to update member's role.", - }); - } else { - sendNotification({ - type: "success", - title: "Role updated", - description: `Role updated for ${member.fullName}.`, - }); - await mutate(`/api/w/${owner.sId}/members`); - } - } - const roleTexts: { [k: string]: string } = { admin: "Admins can manage members, in addition to builders' rights.", builder: @@ -718,7 +806,12 @@ function ChangeMemberModal({ onSave={async () => { setIsSaving(true); if (!selectedRole) return; // unreachable due to hasChanged - await handleMemberRoleChange(member, selectedRole); + await handleMemberRoleChange({ + member, + role: selectedRole, + mutate, + sendNotification, + }); onClose(); /* Delay to let react close the modal before cleaning isSaving, to * avoid the user seeing the button change label again during the closing animation */ @@ -805,7 +898,12 @@ function ChangeMemberModal({ label={isSaving ? "Revoking..." : "Yes, revoke"} onClick={async () => { setIsSaving(true); - await handleMemberRoleChange(member, "none"); + await handleMemberRoleChange({ + member, + role: "none", + mutate, + sendNotification, + }); setRevokeMemberModalOpen(false); onClose(); /* Delay to let react close the modal before cleaning isSaving, to