diff --git a/.circleci/config.yml b/.circleci/config.yml index e5ee2959..5eefc91f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -152,7 +152,7 @@ workflows: context: org-global filters: &filters-dev branches: - only: ["develop", "PM-803_wm-regression-fixes", "PM-902_show-all-projects-on-challenge-page", "pm-1355_1"] + only: ["develop", "PM-803_wm-regression-fixes", "PM-902_show-all-projects-on-challenge-page", "pm-1365"] # Production builds are exectuted only on tagged commits to the # master branch. diff --git a/src/components/ChallengesComponent/index.js b/src/components/ChallengesComponent/index.js index dd932641..038461b1 100644 --- a/src/components/ChallengesComponent/index.js +++ b/src/components/ChallengesComponent/index.js @@ -98,7 +98,7 @@ const ChallengesComponent = ({ )} diff --git a/src/components/Users/Users.module.scss b/src/components/Users/Users.module.scss index 7451b0c6..a37c2395 100644 --- a/src/components/Users/Users.module.scss +++ b/src/components/Users/Users.module.scss @@ -40,6 +40,11 @@ } } +.textContent { + font-size: 18px; + margin-top: 25px; +} + .row { display: flex; @@ -400,7 +405,7 @@ justify-content: center; } -.addButtonContainer { +.addButtonContainer, .buttonWrapper { display: flex; justify-content: flex-start; height: 30px; @@ -412,10 +417,6 @@ } } -.addUserContentContainer { - -} - .tcRadioButton { .tc-radioButton-label { @include roboto-light(); diff --git a/src/components/Users/index.js b/src/components/Users/index.js index 5ddc2e67..cfbe5671 100644 --- a/src/components/Users/index.js +++ b/src/components/Users/index.js @@ -225,6 +225,9 @@ class Users extends Component { addNewProjectMember={this.props.addNewProjectMember} onMemberInvited={this.props.addNewProjectInvite} onClose={this.resetAddUserState} + projectOption={this.state.projectOption} + projectMembers={projectMembers} + updateProjectMember={updateProjectMember} /> ) } diff --git a/src/components/Users/user-add.modal.js b/src/components/Users/user-add.modal.js index 58b68d20..12a269e4 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, inviteUserToProject } from '../../services/projects' +import { addUserToProject, inviteUserToProject, updateProjectMemberRole } from '../../services/projects' import styles from './Users.module.scss' @@ -14,12 +14,22 @@ const theme = { container: styles.modalContainer } -const UserAddModalContent = ({ projectId, addNewProjectMember, onMemberInvited, onClose }) => { +const UserAddModalContent = ({ + projectMembers, + projectOption, + projectId, + addNewProjectMember, + onMemberInvited, + onClose, + updateProjectMember +}) => { const [userToAdd, setUserToAdd] = useState(null) const [userPermissionToAdd, setUserPermissionToAdd] = useState(PROJECT_ROLES.READ) const [showSelectUserError, setShowSelectUserError] = useState(false) const [addUserError, setAddUserError] = useState(null) const [isAdding, setIsAdding] = useState(false) + const [isUserAddingFailed, setUserAddingFailed] = useState(false) + const [existingRole, setExistingRole] = useState('') const onUpdateUserToAdd = (option) => { if (option && option.value) { @@ -52,8 +62,12 @@ const UserAddModalContent = ({ projectId, addNewProjectMember, onMemberInvited, }) if (failed) { const error = get(failed, '0.message', 'User cannot be invited') + const errorCode = get(failed, '0.error') + const role = get(failed, '0.role') setAddUserError(error) setIsAdding(false) + setUserAddingFailed(errorCode === 'ALREADY_MEMBER') + setExistingRole(role) } else if (rest.message) { setAddUserError(rest.message) setIsAdding(false) @@ -74,113 +88,141 @@ const UserAddModalContent = ({ projectId, addNewProjectMember, onMemberInvited, } } + const onConfirmCopilotRoleChange = async () => { + const member = projectMembers.find(item => item.userId === userToAdd.userId) + const action = member.role === 'manager' ? 'complete-copilot-requests' : '' + const response = await updateProjectMemberRole(projectId, member.id, 'copilot', action) + updateProjectMember(response) + onClose() + } + + const onCancelCopilotRoleChange = () => { + setUserAddingFailed(false) + setAddUserError('') + } + return ( -
-
Add User
-
-
-
- Member* : -
-
- + { + isUserAddingFailed && (['observer', 'customer', 'copilot', 'manager'].includes(existingRole)) && ( +
+
{`The copilot ${userToAdd.handle} is part of ${projectOption.label} project with ${existingRole} role.`}
+
+ +
- {showSelectUserError && ( -
-
Please select a member.
-
- )} -
-
- -
-
-
- setUserPermissionToAdd(PROJECT_ROLES.READ)} - /> - + ) + } + { + !isUserAddingFailed && ( +
+
Add User
+
+
+
+ Member* : +
+
+ +
-
-
-
- setUserPermissionToAdd(PROJECT_ROLES.WRITE)} - /> - + {showSelectUserError && ( +
+
Please select a member.
+
+ )} +
+
+ +
+
+
+ setUserPermissionToAdd(PROJECT_ROLES.READ)} + /> + +
+
+
+
+ setUserPermissionToAdd(PROJECT_ROLES.WRITE)} + /> + +
+
+
+
+ setUserPermissionToAdd(PROJECT_ROLES.MANAGER)} + /> + +
+
+
+
+ setUserPermissionToAdd(PROJECT_ROLES.COPILOT)} + /> + +
+
+ {addUserError && ( +
{addUserError}
+ )}
-
-
- setUserPermissionToAdd(PROJECT_ROLES.MANAGER)} +
+
+ -
-
-
-
- setUserPermissionToAdd(PROJECT_ROLES.COPILOT)} +
+ -
- {addUserError && ( -
{addUserError}
- )} -
-
-
- -
-
- -
-
-
+ ) + } ) } @@ -188,7 +230,10 @@ UserAddModalContent.propTypes = { projectId: PropTypes.number.isRequired, addNewProjectMember: PropTypes.func.isRequired, onMemberInvited: PropTypes.func.isRequired, - onClose: PropTypes.func.isRequired + onClose: PropTypes.func.isRequired, + projectOption: PropTypes.any.isRequired, + projectMembers: PropTypes.array.isRequired, + updateProjectMember: PropTypes.func.isRequired } export default UserAddModalContent diff --git a/src/config/constants.js b/src/config/constants.js index 0b7de032..47e4d2ed 100644 --- a/src/config/constants.js +++ b/src/config/constants.js @@ -354,7 +354,7 @@ export const GROUPS_DROPDOWN_PER_PAGE = 1000000 // make sure we are getting all /** * The list of challenge types which can have multiple prizes */ -export const CHALLENGE_TYPES_WITH_MULTIPLE_PRIZES = ['Challenge'] +export const CHALLENGE_TYPES_WITH_MULTIPLE_PRIZES = ['Challenge', 'Marathon Match'] /** * All the repeating messages. diff --git a/src/containers/ProjectInvitations/index.js b/src/containers/ProjectInvitations/index.js index 58e6f20d..fc9c4e49 100644 --- a/src/containers/ProjectInvitations/index.js +++ b/src/containers/ProjectInvitations/index.js @@ -46,16 +46,22 @@ const ProjectInvitations = ({ match, auth, isProjectLoading, history, projectDet const updateInvite = useCallback(async (status, source) => { setIsUpdating(status) - await updateProjectMemberInvite(projectId, invitation.id, status, source) - - // await for the project details to propagate - await delay(1000) - await loadProjectInvites(projectId) - toastr.success('Success', `Successfully ${status} the invitation.`) - - // await for the project details to fetch - await delay(1000) - history.push(status === PROJECT_MEMBER_INVITE_STATUS_ACCEPTED ? `/projects/${projectId}/challenges` : '/projects') + try { + await updateProjectMemberInvite(projectId, invitation.id, status, source) + + // await for the project details to propagate + await delay(1000) + await loadProjectInvites(projectId) + toastr.success('Success', `Successfully ${status} the invitation.`) + + // await for the project details to fetch + await delay(1000) + history.push(status === PROJECT_MEMBER_INVITE_STATUS_ACCEPTED ? `/projects/${projectId}/challenges` : '/projects') + } catch (e) { + toastr.error('Error', e.response.data.message) + await delay(1000) + history.push('/projects') + } }, [projectId, invitation, loadProjectInvites, history]) const acceptInvite = useCallback(() => updateInvite(PROJECT_MEMBER_INVITE_STATUS_ACCEPTED, source), [updateInvite, source]) diff --git a/src/services/projects.js b/src/services/projects.js index a3cf43c0..1a03221c 100644 --- a/src/services/projects.js +++ b/src/services/projects.js @@ -103,9 +103,10 @@ export async function fetchProjectPhases (id) { * @param newRole the new role * @returns {Promise<*>} */ -export async function updateProjectMemberRole (projectId, memberRecordId, newRole) { +export async function updateProjectMemberRole (projectId, memberRecordId, newRole, action) { const response = await axiosInstance.patch(`${PROJECTS_API_URL}/${projectId}/members/${memberRecordId}`, { - role: newRole + role: newRole, + action }) return _.get(response, 'data') }