+);
+
+InviteUsersResults.propTypes = {
+ results: shape({
+ invite_results: arrayOf.isRequired,
+ team_name: string.isRequired
+ }).isRequired
+};
+
+export default InviteUsersResults;
diff --git a/app/javascript/src/components/InviteUsersModal/index.jsx b/app/javascript/src/components/InviteUsersModal/index.jsx
new file mode 100644
index 000000000..a6a82a0d2
--- /dev/null
+++ b/app/javascript/src/components/InviteUsersModal/index.jsx
@@ -0,0 +1,120 @@
+import React, { Component } from "react";
+import { bool, func, shape, number, string } from "prop-types";
+import { FormattedMessage } from "react-intl";
+import { Modal, ButtonToolbar, Button } from "react-bootstrap";
+import styled from "styled-components";
+import axios from "../../config/axios";
+
+import {
+ INVITE_USERS_PATH,
+ TEAM_DETAILS_PATH
+} from "../../config/api_endpoints";
+import InviteUsersForm from "./components/InviteUsersForm";
+import InviteUsersResults from "./components/InviteUsersResults";
+import InviteUsersButton from "./components/InviteUsersButton";
+
+const StyledButtonToolbar = styled(ButtonToolbar)`float: right;`;
+
+class InviteUsersModal extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ showInviteUsersResults: false,
+ inputTags: [],
+ inviteResults: []
+ };
+ this.handleInputChange = this.handleInputChange.bind(this);
+ this.inviteAs = this.inviteAs.bind(this);
+ this.handleCloseModal = this.handleCloseModal.bind(this);
+ }
+
+ handleCloseModal() {
+ const path = TEAM_DETAILS_PATH.replace(":team_id", this.props.team.id);
+ this.props.onCloseModal();
+ this.setState({
+ showInviteUsersResults: false,
+ inputTags: [],
+ inviteResults: []
+ });
+ // Update team members table
+ axios.get(path).then(response => {
+ const { users } = response.data.team_details;
+ this.props.updateUsersCallback(users);
+ });
+ }
+
+ handleInputChange(inputTags) {
+ this.setState({ inputTags });
+ }
+
+ inviteAs(role) {
+ axios
+ .put(INVITE_USERS_PATH, {
+ user_role: role,
+ emails: this.state.inputTags,
+ team_id: this.props.team.id
+ })
+ .then(({ data }) => {
+ this.setState({ inviteResults: data, showInviteUsersResults: true});
+ })
+ .catch(error => {
+ console.log("Invite As Error: ", error);
+ if (error.response) {
+ console.log("Error message:", error.response.data);
+ // TO DO: put this error in flash msg
+ }
+ });
+ }
+
+ render() {
+ let modalBody = null;
+ let inviteButton = null;
+ if (this.state.showInviteUsersResults) {
+ modalBody = ;
+ inviteButton = null;
+ } else {
+ modalBody = (
+
+ );
+ inviteButton = ;
+ }
+
+ return (
+
+
+
+
+
+
+ {modalBody}
+
+
+
+ {inviteButton}
+
+
+
+ );
+ }
+}
+
+InviteUsersModal.propTypes = {
+ showModal: bool.isRequired,
+ onCloseModal: func.isRequired,
+ team: shape({
+ id: number.isRequired,
+ name: string.isRequired
+ }).isRequired,
+ updateUsersCallback: func.isRequired
+};
+
+export default InviteUsersModal;
diff --git a/app/javascript/src/config/api_endpoints.js b/app/javascript/src/config/api_endpoints.js
index ae5c3693e..9c066088b 100644
--- a/app/javascript/src/config/api_endpoints.js
+++ b/app/javascript/src/config/api_endpoints.js
@@ -39,6 +39,7 @@ export const CHANGE_USER_RECENT_NOTIFICATION_EMAIL_PATH =
"/client_api/users/change_recent_notification_email";
export const CHANGE_USER_SYSTEM_MESSAGE_NOTIFICATION_EMAIL_PATH =
"/client_api/users/change_system_notification_email";
+export const INVITE_USERS_PATH = "/client_api/users/invite_users";
// info dropdown_title
export const CUSTOMER_SUPPORT_LINK = "http://scinote.net/support";
diff --git a/app/javascript/src/config/constants/numeric.js b/app/javascript/src/config/constants/numeric.js
index 601d72a10..1e917b60a 100644
--- a/app/javascript/src/config/constants/numeric.js
+++ b/app/javascript/src/config/constants/numeric.js
@@ -2,3 +2,4 @@ export const ENTER_KEY_CODE = 13;
export const NAME_MIN_LENGTH = 2;
export const NAME_MAX_LENGTH = 255;
export const TEXT_MAX_LENGTH = 10000;
+export const INVITE_USERS_LIMIT = 20;
diff --git a/app/javascript/src/config/locales/messages.js b/app/javascript/src/config/locales/messages.js
index 798c7bebd..07c3295eb 100644
--- a/app/javascript/src/config/locales/messages.js
+++ b/app/javascript/src/config/locales/messages.js
@@ -21,6 +21,43 @@ export default {
notifications_label: "Notifications",
info_label: "Info"
},
+ invite_users: {
+ modal_title: "Invite users to team {team}",
+ input_text: "Invite more people to team {team} and start using sciNote.",
+ input_help:
+ "Input one or multiple emails, confirm each email with ENTER key.",
+ dropdown_button: {
+ invite: "Invite user/s",
+ guest: "as Guest/s",
+ normal_user: "as Normal user/s",
+ admin: "as Administrator/s"
+ },
+ results_title: "Invitation results:",
+ roles: {
+ guest: "Guest",
+ normal_user: "Normal user",
+ admin: "Administrator"
+ },
+ results_msg: {
+ user_exists: "User is already a member of sciNote.",
+ user_exists_unconfirmed:
+ "User is already a member of sciNote but is not confirmed yet.",
+ user_exists_and_in_team_unconfirmed:
+ "User is already a member of sciNote and team {team} as {role} but is not confirmed yet.",
+ user_exists_invited_to_team_unconfirmed:
+ "User is already a member of sciNote but is not confirmed yet - successfully invited to team {team} as {role}.",
+ user_exists_and_in_team:
+ "User is already a member of sciNote and team {team} as {role}.",
+ user_exists_invited_to_team:
+ "User was already a member of sciNote - successfully invited to team {team} as {role}.",
+ user_created: "User succesfully invited to sciNote.",
+ user_created_invited_to_team:
+ "User successfully invited to sciNote and team {team} as {role}.",
+ user_invalid: "Invalid email.",
+ too_many_emails:
+ "Only invited first {nr} emails. To invite more users, fill in another invitation form."
+ }
+ },
settings_page: {
all_teams: "All teams",
in_team: "You are member of {num} team",
@@ -64,20 +101,28 @@ export default {
yes: "Yes",
leave_team_modal: {
title: "Leave team {teamName}",
- subtitle: "Are you sure you wish to leave team My projects? This action is irreversible.",
+ subtitle:
+ "Are you sure you wish to leave team My projects? This action is irreversible.",
warnings: "Leaving team has following consequences:",
- warning_message_one: "you will lose access to all content belonging to the team (including projects, tasks, protocols and activities);",
- warning_message_two: "all projects in the team where you were the sole Owner will receive a new owner from the team administrators;",
- warning_message_three: "all repository protocols in the team belonging to you will be reassigned onto a new owner from team administrators.",
+ warning_message_one:
+ "you will lose access to all content belonging to the team (including projects, tasks, protocols and activities);",
+ warning_message_two:
+ "all projects in the team where you were the sole Owner will receive a new owner from the team administrators;",
+ warning_message_three:
+ "all repository protocols in the team belonging to you will be reassigned onto a new owner from team administrators.",
leave_team: "Leave"
},
remove_user_modal: {
title: "Remove user {user} from team {team}",
- subtitle: "Are you sure you wish to remove user {user} from team {team}?",
+ subtitle:
+ "Are you sure you wish to remove user {user} from team {team}?",
warnings: "Removing user from team has following consequences:",
- warning_message_one: "user will lose access to all content belonging to the team (including projects, tasks, protocols and activities);",
- warning_message_two: "all projects in the team where user was the sole Owner will be reassigned onto you as a new owner;",
- warning_message_three: "all repository protocols in the team belonging to user will be reassigned onto you.",
+ warning_message_one:
+ "user will lose access to all content belonging to the team (including projects, tasks, protocols and activities);",
+ warning_message_two:
+ "all projects in the team where user was the sole Owner will be reassigned onto you as a new owner;",
+ warning_message_three:
+ "all repository protocols in the team belonging to user will be reassigned onto you.",
remove_user: "Remove user"
},
update_team_description_modal: {
diff --git a/app/javascript/src/scenes/SettingsPage/scenes/team/components/TeamsMembers.jsx b/app/javascript/src/scenes/SettingsPage/scenes/team/components/TeamsMembers.jsx
index 35a027e81..8f703018b 100644
--- a/app/javascript/src/scenes/SettingsPage/scenes/team/components/TeamsMembers.jsx
+++ b/app/javascript/src/scenes/SettingsPage/scenes/team/components/TeamsMembers.jsx
@@ -10,6 +10,7 @@ import {
import { FormattedMessage } from "react-intl";
import axios from "../../../../../config/axios";
+import InviteUsersModal from "../../../../../components/InviteUsersModal";
import RemoveUserModal from "./RemoveUserModal";
import DataTable from "../../../../../components/data_table";
import { UPDATE_USER_TEAM_ROLE_PATH } from "../../../../../config/api_endpoints";
@@ -23,8 +24,18 @@ const initalUserToRemove = {
class TeamsMembers extends Component {
constructor(params) {
super(params);
- this.state = { showModal: false, userToRemove: initalUserToRemove };
+ this.state = {
+ showModal: false,
+ showInviteUsersModal: false,
+ userToRemove: initalUserToRemove
+ };
this.memberAction = this.memberAction.bind(this);
+ this.showInviteUsersModalCallback = this.showInviteUsersModalCallback.bind(
+ this
+ );
+ this.closeInviteUsersModalCallback = this.closeInviteUsersModalCallback.bind(
+ this
+ );
this.hideModal = this.hideModal.bind(this);
}
@@ -45,6 +56,14 @@ class TeamsMembers extends Component {
.catch(error => console.log(error));
}
+ showInviteUsersModalCallback() {
+ this.setState({ showInviteUsersModal: true });
+ }
+
+ closeInviteUsersModalCallback() {
+ this.setState({ showInviteUsersModal: false });
+ }
+
hideModal() {
this.setState({ showModal: false, userToRemove: initalUserToRemove });
}
@@ -169,7 +188,7 @@ class TeamsMembers extends Component {
}
>
-