Skip to content

Commit

Permalink
Merge branch 'main' into Sparkle37Front
Browse files Browse the repository at this point in the history
  • Loading branch information
Duncid authored Nov 13, 2023
2 parents 4f3f55b + 4a09b1b commit 217c4e7
Showing 1 changed file with 169 additions and 71 deletions.
240 changes: 169 additions & 71 deletions front/pages/w/[wId]/members/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ export default function WorkspaceAdmin({
setInviteEmailModalOpen(false);
}}
owner={owner}
members={members}
/>
<RevokeInvitationModal
showModal={revokeInvitationModalOpen}
Expand All @@ -250,7 +251,6 @@ export default function WorkspaceAdmin({
showModal={changeRoleModalOpen}
member={changeRoleMember}
onClose={() => setChangeRoleModalOpen(false)}
owner={owner}
/>
<Page.Vertical gap="sm" align="stretch">
<Page.H variant="h5">Member list</Page.H>
Expand Down Expand Up @@ -392,21 +392,35 @@ 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<UserType | null>(null);
const { mutate } = useSWRConfig();
const sendNotification = useContext(SendNotificationsContext);
async function handleSendInvitation(): Promise<void> {
if (!isEmailValid(inviteEmail)) {
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: {
Expand All @@ -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({
Expand All @@ -433,44 +448,109 @@ function InviteEmailModal({
}

return (
<Modal
isOpen={showModal}
onClose={onClose}
hasChanged={emailError === "" && inviteEmail !== "" && !isSending}
title="Invite new users"
variant="side-sm"
saveLabel="Invite"
isSaving={isSending}
onSave={async () => {
setIsSending(true);
await handleSendInvitation();
setIsSending(false);
setInviteEmail("");
}}
>
<div className="mt-6 flex flex-col gap-6 px-2 text-sm">
<Page.P>
Invite a new user to your workspace. They will receive an email with a
link to join your workspace.
</Page.P>
<div className="flex flex-grow flex-col gap-1.5">
<div className="font-semibold">Email to send invite to:</div>
<div className="flex items-start gap-2">
<div className="flex-grow">
<Input
placeholder={"Email address"}
value={inviteEmail || ""}
name={""}
error={emailError}
showErrorLabel={true}
onChange={(e) => {
setInviteEmail(e.trim());
setEmailError("");
}}
/>
<>
<ReinviteUserModal
onClose={() => setExistingRevokedUser(null)}
user={existingRevokedUser}
/>
<Modal
isOpen={showModal}
onClose={onClose}
hasChanged={emailError === "" && inviteEmail !== "" && !isSending}
title="Invite new users"
variant="side-sm"
saveLabel="Invite"
isSaving={isSending}
onSave={async () => {
setIsSending(true);
await handleSendInvitation();
setIsSending(false);
setInviteEmail("");
}}
>
<div className="mt-6 flex flex-col gap-6 px-2 text-sm">
<Page.P>
Invite a new user to your workspace. They will receive an email with
a link to join your workspace.
</Page.P>
<div className="flex flex-grow flex-col gap-1.5">
<div className="font-semibold">Email to send invite to:</div>
<div className="flex items-start gap-2">
<div className="flex-grow">
<Input
placeholder={"Email address"}
value={inviteEmail || ""}
name={""}
error={emailError}
showErrorLabel={true}
onChange={(e) => {
setInviteEmail(e.trim());
setEmailError("");
}}
/>
</div>
</div>
</div>
</div>
</Modal>
</>
);
}

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 (
<Modal
isOpen={!!user}
onClose={() => onClose(false)}
hasChanged={false}
title="Reinstate user?"
type="default"
>
<div className="mt-6 flex flex-col gap-6 px-2">
<div>
{" "}
{`${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.
</div>
<div className="flex gap-2">
<Button
variant="tertiary"
label="Cancel"
onClick={() => onClose(false)}
/>
<Button
variant="primaryWarning"
label={isSaving ? "Reinstating..." : "Yes, reinstate"}
onClick={async () => {
setIsSaving(true);
await handleMemberRoleChange({
member: user,
role: "user",
mutate,
sendNotification,
});
onClose(false);
/* Delay to let react close the modal before cleaning isSaving, to
* avoid the user seeing the button change label again during the closing animation */
setTimeout(() => {
setIsSaving(false);
}, CLOSING_ANIMATION_DURATION);
}}
/>
</div>
</div>
</Modal>
);
Expand Down Expand Up @@ -652,53 +732,61 @@ function RevokeInvitationModal({
);
}

async function handleMemberRoleChange({
member,
role,
mutate,
sendNotification,
}: {
member: UserType;
role: RoleType;
mutate: any;
sendNotification: any;
}): Promise<void> {
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<RoleType | null>(null);
const [isSaving, setIsSaving] = useState(false);
const sendNotification = useContext(SendNotificationsContext);
if (!member) return null; // Unreachable

async function handleMemberRoleChange(
member: UserType,
role: RoleType
): Promise<void> {
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:
Expand All @@ -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 */
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 217c4e7

Please sign in to comment.