diff --git a/config/constants/development.js b/config/constants/development.js index 311336ed..58e34497 100644 --- a/config/constants/development.js +++ b/config/constants/development.js @@ -22,6 +22,7 @@ module.exports = { PROJECT_API_URL: `${DEV_API_HOSTNAME}/v5/projects`, GROUPS_API_URL: `${DEV_API_HOSTNAME}/v5/groups`, TERMS_API_URL: `${DEV_API_HOSTNAME}/v5/terms`, + MEMBERS_API_URL: `${DEV_API_HOSTNAME}/v5/members`, RESOURCES_API_URL: `${DEV_API_HOSTNAME}/v5/resources`, RESOURCE_ROLES_API_URL: `${DEV_API_HOSTNAME}/v5/resource-roles`, SUBMISSIONS_API_URL: `${DEV_API_HOSTNAME}/v5/submissions`, diff --git a/config/constants/production.js b/config/constants/production.js index ff837ee2..10a13349 100644 --- a/config/constants/production.js +++ b/config/constants/production.js @@ -21,6 +21,7 @@ module.exports = { PROJECT_API_URL: `${PROD_API_HOSTNAME}/v5/projects`, GROUPS_API_URL: `${PROD_API_HOSTNAME}/v5/groups`, TERMS_API_URL: `${PROD_API_HOSTNAME}/v5/terms`, + MEMBERS_API_URL: `${PROD_API_HOSTNAME}/v5/members`, RESOURCES_API_URL: `${PROD_API_HOSTNAME}/v5/resources`, RESOURCE_ROLES_API_URL: `${PROD_API_HOSTNAME}/v5/resource-roles`, SUBMISSIONS_API_URL: `${PROD_API_HOSTNAME}/v5/submissions`, diff --git a/src/components/UserCard/index.js b/src/components/UserCard/index.js index 67f1cc08..ef79ce82 100644 --- a/src/components/UserCard/index.js +++ b/src/components/UserCard/index.js @@ -91,7 +91,7 @@ class UserCard extends Component { )}
- {isInvite ? user.email : user.handle} + {isInvite ? (user.email || user.handle) : user.handle}
{!isInvite && ( <> diff --git a/src/components/Users/index.js b/src/components/Users/index.js index e328e230..3d00edc0 100644 --- a/src/components/Users/index.js +++ b/src/components/Users/index.js @@ -221,6 +221,7 @@ class Users extends Component { ) diff --git a/src/components/Users/invite-user.modal.js b/src/components/Users/invite-user.modal.js index 41b1dff4..37caa3b7 100644 --- a/src/components/Users/invite-user.modal.js +++ b/src/components/Users/invite-user.modal.js @@ -55,7 +55,10 @@ const InviteUserModalContent = ({ projectId, onClose, onMemberInvited, projectMe try { // api restriction: ONLY "customer" role can be invited via email - const { success: invitations = [], failed } = await inviteUserToProject(projectId, emailToInvite, PROJECT_ROLES.CUSTOMER) + const { success: invitations = [], failed } = await inviteUserToProject(projectId, { + emails: [emailToInvite], + role: PROJECT_ROLES.CUSTOMER + }) if (failed) { const error = get(failed, '0.message', 'Unable to invite user') diff --git a/src/components/Users/user-add.modal.js b/src/components/Users/user-add.modal.js index 79f08f93..58b68d20 100644 --- a/src/components/Users/user-add.modal.js +++ b/src/components/Users/user-add.modal.js @@ -6,7 +6,7 @@ import Modal from '../Modal' import SelectUserAutocomplete from '../SelectUserAutocomplete' import { PROJECT_ROLES } from '../../config/constants' import PrimaryButton from '../Buttons/PrimaryButton' -import { addUserToProject } from '../../services/projects' +import { addUserToProject, inviteUserToProject } from '../../services/projects' import styles from './Users.module.scss' @@ -14,7 +14,7 @@ const theme = { container: styles.modalContainer } -const UserAddModalContent = ({ projectId, addNewProjectMember, onClose }) => { +const UserAddModalContent = ({ projectId, addNewProjectMember, onMemberInvited, onClose }) => { const [userToAdd, setUserToAdd] = useState(null) const [userPermissionToAdd, setUserPermissionToAdd] = useState(PROJECT_ROLES.READ) const [showSelectUserError, setShowSelectUserError] = useState(false) @@ -45,10 +45,28 @@ const UserAddModalContent = ({ projectId, addNewProjectMember, onClose }) => { setAddUserError(null) try { - const newUserInfo = await addUserToProject(projectId, userToAdd.userId, userPermissionToAdd) - newUserInfo.handle = userToAdd.handle - addNewProjectMember(newUserInfo) - onClose() + if (userPermissionToAdd === PROJECT_ROLES.COPILOT) { + const { success: invitations = [], failed, ...rest } = await inviteUserToProject(projectId, { + handles: [userToAdd.handle], + role: userPermissionToAdd + }) + if (failed) { + const error = get(failed, '0.message', 'User cannot be invited') + setAddUserError(error) + setIsAdding(false) + } else if (rest.message) { + setAddUserError(rest.message) + setIsAdding(false) + } else { + onMemberInvited(invitations[0] || {}) + onClose() + } + } else { + const newUserInfo = await addUserToProject(projectId, userToAdd.userId, userPermissionToAdd) + newUserInfo.handle = userToAdd.handle + addNewProjectMember(newUserInfo) + onClose() + } } catch (e) { const error = get(e, 'response.data.message', 'Unable to add user') setAddUserError(error) @@ -169,6 +187,7 @@ const UserAddModalContent = ({ projectId, addNewProjectMember, onClose }) => { UserAddModalContent.propTypes = { projectId: PropTypes.number.isRequired, addNewProjectMember: PropTypes.func.isRequired, + onMemberInvited: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired } diff --git a/src/config/constants.js b/src/config/constants.js index 8729c02c..d40ddcbb 100644 --- a/src/config/constants.js +++ b/src/config/constants.js @@ -59,6 +59,7 @@ export const FILE_PICKER_PROGRESS_INTERVAL = 100 export const FILE_PICKER_UPLOAD_RETRY = 2 export const FILE_PICKER_UPLOAD_TIMEOUT = 30 * 60 * 1000 // 30 minutes export const SPECIFICATION_ATTACHMENTS_FOLDER = 'SPECIFICATION_ATTACHMENTS' +export const MEMBERS_API_URL = process.env.MEMBERS_API_URL export const getAWSContainerFileURL = (key) => `https://${FILE_PICKER_CONTAINER_NAME}.s3.amazonaws.com/${key}` diff --git a/src/containers/Users/index.js b/src/containers/Users/index.js index dabaa0b5..08ee1f01 100644 --- a/src/containers/Users/index.js +++ b/src/containers/Users/index.js @@ -4,7 +4,7 @@ import _ from 'lodash' import PT from 'prop-types' import UsersComponent from '../../components/Users' import { PROJECT_ROLES } from '../../config/constants' -import { fetchProjectById } from '../../services/projects' +import { fetchInviteMembers, fetchProjectById } from '../../services/projects' import { checkAdmin, checkManager } from '../../util/tc' import { @@ -80,12 +80,18 @@ class Users extends Component { } loadProject (projectId) { - fetchProjectById(projectId).then((project) => { + fetchProjectById(projectId).then(async (project) => { const projectMembers = _.get(project, 'members') const invitedMembers = _.get(project, 'invites') + const invitedUserIds = _.filter(_.map(invitedMembers, 'userId')) + const invitedUsers = await fetchInviteMembers(invitedUserIds) + this.setState({ projectMembers, - invitedMembers + invitedMembers: invitedMembers.map(m => ({ + ...m, + email: m.email || invitedUsers[m.userId].handle + })) }) const { loggedInUser } = this.props this.updateLoginUserRoleInProject(projectMembers, loggedInUser) diff --git a/src/services/projects.js b/src/services/projects.js index e586a144..a3cf43c0 100644 --- a/src/services/projects.js +++ b/src/services/projects.js @@ -8,7 +8,8 @@ import { GENERIC_PROJECT_MILESTONE_PRODUCT_TYPE, PHASE_PRODUCT_CHALLENGE_ID_FIELD, PHASE_PRODUCT_TEMPLATE_ID, - PROJECTS_API_URL + PROJECTS_API_URL, + MEMBERS_API_URL } from '../config/constants' import { paginationHeaders } from '../util/pagination' import { createProjectMemberInvite } from './projectMemberInvites' @@ -68,6 +69,19 @@ export async function fetchProjectById (id) { return _.get(response, 'data') } +/** + * This fetches the user corresponding to the given userIds + * @param {*} userIds + */ +export async function fetchInviteMembers (userIds) { + const url = `${MEMBERS_API_URL}?${userIds.map(id => `userIds[]=${id}`).join('&')}` + const { data = [] } = await axiosInstance.get(url) + return data.reduce((acc, member) => { + acc[member.userId] = member + return acc + }, {}) +} + /** * Api request for fetching project phases * @param id Project id @@ -118,11 +132,8 @@ export async function addUserToProject (projectId, userId, role) { * @param role * @returns {Promise<*>} */ -export async function inviteUserToProject (projectId, email, role) { - return createProjectMemberInvite(projectId, { - emails: [email], - role: role - }) +export async function inviteUserToProject (projectId, params) { + return createProjectMemberInvite(projectId, params) } /**