feat: Adds menu item to resend outstanding invites (#3348)
* feat: Adds menu item to resend outstanding invites * i18n * snapshots
This commit is contained in:
@@ -8,6 +8,7 @@ import OverflowMenuButton from "~/components/ContextMenu/OverflowMenuButton";
|
||||
import Template from "~/components/ContextMenu/Template";
|
||||
import usePolicy from "~/hooks/usePolicy";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import useToasts from "~/hooks/useToasts";
|
||||
|
||||
type Props = {
|
||||
user: User;
|
||||
@@ -20,6 +21,7 @@ function UserMenu({ user }: Props) {
|
||||
modal: true,
|
||||
});
|
||||
const can = usePolicy(user.id);
|
||||
const { showToast } = useToasts();
|
||||
|
||||
const handlePromote = React.useCallback(
|
||||
(ev: React.SyntheticEvent) => {
|
||||
@@ -113,6 +115,22 @@ function UserMenu({ user }: Props) {
|
||||
[users, user]
|
||||
);
|
||||
|
||||
const handleResendInvite = React.useCallback(
|
||||
async (ev: React.SyntheticEvent) => {
|
||||
ev.preventDefault();
|
||||
|
||||
try {
|
||||
await users.resendInvite(user);
|
||||
showToast(t(`Invite was resent to ${user.name}`), { type: "success" });
|
||||
} catch (err) {
|
||||
showToast(t(`An error occurred while sending the invite`), {
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
},
|
||||
[users, user, t, showToast]
|
||||
);
|
||||
|
||||
const handleActivate = React.useCallback(
|
||||
(ev: React.SyntheticEvent) => {
|
||||
ev.preventDefault();
|
||||
@@ -152,6 +170,12 @@ function UserMenu({ user }: Props) {
|
||||
onClick: handlePromote,
|
||||
visible: can.promote && user.role !== "admin",
|
||||
},
|
||||
{
|
||||
type: "button",
|
||||
title: t("Resend invite"),
|
||||
onClick: handleResendInvite,
|
||||
visible: can.resendInvite,
|
||||
},
|
||||
{
|
||||
type: "separator",
|
||||
},
|
||||
|
||||
@@ -135,6 +135,13 @@ export default class UsersStore extends BaseStore<User> {
|
||||
return res.data;
|
||||
};
|
||||
|
||||
@action
|
||||
resendInvite = async (user: User) => {
|
||||
return client.post(`/users.resendInvite`, {
|
||||
id: user.id,
|
||||
});
|
||||
};
|
||||
|
||||
@action
|
||||
fetchCounts = async (teamId: string): Promise<any> => {
|
||||
const res = await client.post(`/users.count`, {
|
||||
|
||||
@@ -80,6 +80,20 @@ allow(User, "promote", User, (actor, user) => {
|
||||
throw AdminRequiredError();
|
||||
});
|
||||
|
||||
allow(User, "resendInvite", User, (actor, user) => {
|
||||
if (!user || user.teamId !== actor.teamId) {
|
||||
return false;
|
||||
}
|
||||
if (!user.isInvited) {
|
||||
return false;
|
||||
}
|
||||
if (actor.isAdmin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
throw AdminRequiredError();
|
||||
});
|
||||
|
||||
allow(User, "demote", User, (actor, user) => {
|
||||
if (!user || user.teamId !== actor.teamId) {
|
||||
return false;
|
||||
|
||||
@@ -25,6 +25,7 @@ Object {
|
||||
"promote": true,
|
||||
"read": true,
|
||||
"readDetails": true,
|
||||
"resendInvite": true,
|
||||
"suspend": true,
|
||||
"update": false,
|
||||
},
|
||||
@@ -78,6 +79,7 @@ Object {
|
||||
"promote": true,
|
||||
"read": true,
|
||||
"readDetails": true,
|
||||
"resendInvite": true,
|
||||
"suspend": true,
|
||||
"update": false,
|
||||
},
|
||||
@@ -113,6 +115,7 @@ Object {
|
||||
"promote": true,
|
||||
"read": true,
|
||||
"readDetails": true,
|
||||
"resendInvite": true,
|
||||
"suspend": true,
|
||||
"update": false,
|
||||
},
|
||||
@@ -148,6 +151,7 @@ Object {
|
||||
"promote": true,
|
||||
"read": true,
|
||||
"readDetails": true,
|
||||
"resendInvite": true,
|
||||
"suspend": true,
|
||||
"update": false,
|
||||
},
|
||||
@@ -201,6 +205,7 @@ Object {
|
||||
"promote": false,
|
||||
"read": true,
|
||||
"readDetails": true,
|
||||
"resendInvite": true,
|
||||
"suspend": true,
|
||||
"update": false,
|
||||
},
|
||||
@@ -263,6 +268,7 @@ Object {
|
||||
"promote": false,
|
||||
"read": true,
|
||||
"readDetails": true,
|
||||
"resendInvite": true,
|
||||
"suspend": true,
|
||||
"update": false,
|
||||
},
|
||||
|
||||
@@ -3,6 +3,8 @@ import { Op, WhereOptions } from "sequelize";
|
||||
import userDestroyer from "@server/commands/userDestroyer";
|
||||
import userInviter from "@server/commands/userInviter";
|
||||
import userSuspender from "@server/commands/userSuspender";
|
||||
import InviteEmail from "@server/emails/templates/InviteEmail";
|
||||
import logger from "@server/logging/logger";
|
||||
import auth from "@server/middlewares/authentication";
|
||||
import { Event, User, Team } from "@server/models";
|
||||
import { can, authorize } from "@server/policies";
|
||||
@@ -306,6 +308,36 @@ router.post("users.invite", auth(), async (ctx) => {
|
||||
};
|
||||
});
|
||||
|
||||
router.post("users.resendInvite", auth(), async (ctx) => {
|
||||
const { id } = ctx.body;
|
||||
const actor = ctx.state.user;
|
||||
|
||||
const user = await User.findByPk(id);
|
||||
authorize(actor, "resendInvite", user);
|
||||
|
||||
await InviteEmail.schedule({
|
||||
to: user.email,
|
||||
name: user.name,
|
||||
actorName: actor.name,
|
||||
actorEmail: actor.email,
|
||||
teamName: actor.team.name,
|
||||
teamUrl: actor.team.url,
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
logger.info(
|
||||
"email",
|
||||
`Sign in immediately: ${
|
||||
process.env.URL
|
||||
}/auth/email.callback?token=${user.getEmailSigninToken()}`
|
||||
);
|
||||
}
|
||||
|
||||
ctx.body = {
|
||||
success: true,
|
||||
};
|
||||
});
|
||||
|
||||
router.post("users.delete", auth(), async (ctx) => {
|
||||
const { confirmation, id } = ctx.body;
|
||||
assertPresent(confirmation, "confirmation is required");
|
||||
|
||||
@@ -289,10 +289,12 @@
|
||||
"Are you sure you want to make {{ userName }} a member?": "Are you sure you want to make {{ userName }} a member?",
|
||||
"Are you sure you want to make {{ userName }} a read-only viewer? They will not be able to edit any content": "Are you sure you want to make {{ userName }} a read-only viewer? They will not be able to edit any content",
|
||||
"Are you sure you want to suspend this account? Suspended users will be prevented from logging in.": "Are you sure you want to suspend this account? Suspended users will be prevented from logging in.",
|
||||
"An error occurred while sending the invite": "An error occurred while sending the invite",
|
||||
"User options": "User options",
|
||||
"Make {{ userName }} a member": "Make {{ userName }} a member",
|
||||
"Make {{ userName }} a viewer": "Make {{ userName }} a viewer",
|
||||
"Make {{ userName }} an admin…": "Make {{ userName }} an admin…",
|
||||
"Resend invite": "Resend invite",
|
||||
"Revoke invite": "Revoke invite",
|
||||
"Activate account": "Activate account",
|
||||
"Suspend account": "Suspend account",
|
||||
|
||||
Reference in New Issue
Block a user