@@ -115,7 +88,7 @@ class Dialog extends React.Component {
return (
@@ -150,7 +123,7 @@ class Dialog extends React.Component {
return (
-
+
invite more people
-
- { this.state.showAlreadyMemberError &&
- Project Member(s) can't be invited again. Please remove them from list.
-
}
+ {this.state.showAlreadyMemberError &&
+ Project Member(s) can't be invited again. Please remove them from list.
+
}
-
+
@@ -203,8 +177,12 @@ class Dialog extends React.Component {
}
}
+Dialog.defaultProps = {
+ invites: [],
+ members: []
+}
+
Dialog.propTypes = {
- processingInvites: PT.bool.isRequired,
error: PT.oneOfType([PT.object, PT.bool]),
currentUser: PT.object.isRequired,
members: PT.arrayOf(PT.object).isRequired,
@@ -214,6 +192,9 @@ Dialog.propTypes = {
invites: PT.arrayOf(PT.object),
sendInvite: PT.func.isRequired,
removeInvite: PT.func.isRequired,
+ onSelectedMembersUpdate: PT.func.isRequired,
+ selectedMembers: PT.arrayOf(PT.object),
+ processingInvites: PT.bool.isRequired,
}
export default Dialog
diff --git a/src/components/TeamManagement/TeamManagement.jsx b/src/components/TeamManagement/TeamManagement.jsx
index 52447321e..7d5fcb84b 100644
--- a/src/components/TeamManagement/TeamManagement.jsx
+++ b/src/components/TeamManagement/TeamManagement.jsx
@@ -56,7 +56,8 @@ class TeamManagement extends React.Component {
showNewMemberConfirmation, onJoin, onJoinConfirm, onShowProjectDialog, isShowProjectDialog,
projectTeamInvites, onProjectInviteDeleteConfirm, onProjectInviteSend, deletingInvite, changeRole,
onDeleteInvite, isShowTopcoderDialog, onShowTopcoderDialog, processingInvites, processingMembers,
- onTopcoderInviteSend, onTopcoderInviteDeleteConfirm, topcoderTeamInvites, error
+ onTopcoderInviteSend, onTopcoderInviteDeleteConfirm, topcoderTeamInvites, error,
+ onSelectedMembersUpdate, selectedMembers
} = this.props
const currentMember = members.filter((member) => member.userId === currentUser.userId)[0]
const modalActive = isAddingTeamMember || deletingMember || isShowJoin || showNewMemberConfirmation || deletingInvite
@@ -183,7 +184,6 @@ class TeamManagement extends React.Component {
}
return (
)
})())}
@@ -206,7 +209,6 @@ class TeamManagement extends React.Component {
}
return (
)
})())}
@@ -370,6 +375,16 @@ TeamManagement.propTypes = {
*/
onTopcoderInviteSend: PropTypes.func,
+ /**
+ * Callback fired when selected members are updated
+ */
+ onSelectedMembersUpdate: PropTypes.func,
+
+ /**
+ * List of members added to auto complete input
+ */
+ selectedMembers: PropTypes.arrayOf(PropTypes.object),
+
/**
* Callback to send member role
*/
diff --git a/src/components/TeamManagement/TeamManagement.scss b/src/components/TeamManagement/TeamManagement.scss
index 5239b16c2..3bd441a82 100644
--- a/src/components/TeamManagement/TeamManagement.scss
+++ b/src/components/TeamManagement/TeamManagement.scss
@@ -337,8 +337,12 @@
text-transform: uppercase;
}
- input {
- width: stretch;
+ form {
+ width: 100%;
+ }
+
+ input, .autocomplete-wrapper {
+ width: 100%;
margin: $base-unit*4 $base-unit*2;
}
diff --git a/src/components/TeamManagement/TopcoderManagementDialog.js b/src/components/TeamManagement/TopcoderManagementDialog.js
index 24a81e2cd..e13a214ca 100644
--- a/src/components/TeamManagement/TopcoderManagementDialog.js
+++ b/src/components/TeamManagement/TopcoderManagementDialog.js
@@ -7,11 +7,9 @@ import Modal from 'react-modal'
import XMarkIcon from '../../assets/icons/icon-x-mark.svg'
import Avatar from 'appirio-tech-react-components/components/Avatar/Avatar'
import { getAvatarResized } from '../../helpers/tcHelpers'
-import FormsyForm from 'appirio-tech-react-components/components/Formsy'
-import { INVITE_TOPCODER_MEMBER_FAILURE } from '../../config/constants'
import SelectDropdown from '../SelectDropdown/SelectDropdown'
import Tooltip from 'appirio-tech-react-components/components/Tooltip/Tooltip'
-const TCFormFields = FormsyForm.Fields
+import AutocompleteInput from './AutocompleteInput'
class Dialog extends React.Component {
constructor(props) {
@@ -52,18 +50,6 @@ class Dialog extends React.Component {
})
}
- componentWillReceiveProps(nextProps) {
- if (this.state.clearText && nextProps.processingInvites !== this.props.processingInvites &&
- !nextProps.processingInvites) {
- this.setState((prevState) => ({
- userText: nextProps.error && nextProps.error.type === INVITE_TOPCODER_MEMBER_FAILURE ? prevState.userText : '',
- validUserText: false,
- clearText: false,
- members: this.props.members
- }))
- }
- }
-
onUserRoleChange(memberId, id, type) {
const managerType = Object.assign({}, this.state.managerType)
managerType[memberId] = type
@@ -78,43 +64,38 @@ class Dialog extends React.Component {
}
addUsers() {
- let handles = this.state.userText.split(/[,;]/g)
- handles = handles.map(handle => handle.trim())
- handles = handles.filter((handle) => (handle.startsWith('@') && handle.length > 1))
- handles = handles.map(handle => handle.replace(/^@/, ''))
-
- this.props.addUsers({
- handles,
- role: this.state.userRole
- })
- this.setState({clearText: true})
+ this.props.addUsers(this.state.userRole )
}
- onChange(currentValues) {
- const text = currentValues.handlesText
- let handles = text.split(/[,;]/g)
- const isInvalid = handles.some(user => {
- user = user.trim()
- if (user === '') {
+ onChange(selectedMembers) {
+ // If a member invited with email exists in selectedMembers
+ let present = _.some(this.props.invites, invited => _.findIndex(selectedMembers,
+ selectedMember => selectedMember.isEmail && selectedMember.handle === invited.email) > -1)
+ // If a member invited with handle exists in selectedMembers
+ present = present || _.some(this.props.invites, invited => {
+ if (!invited.member) {
return false
}
- return !(user.startsWith('@') && user.length > 1)
+ return _.findIndex(selectedMembers,
+ selectedMember => !selectedMember.isEmail && selectedMember.handle === invited.member.handle) > -1
})
- const validText = !isInvalid && text.trim().length > 0
- handles = handles.filter((handle) => (handle.trim().startsWith('@') && handle.length > 1))
- handles = handles.map(handle => handle.trim().replace(/^@/, ''))
- const present = _.some(this.state.members, m => handles.indexOf(m.handle) > -1)
+ present = present || _.some(this.state.members, m => _.findIndex(selectedMembers,
+ selectedMember => selectedMember.handle === m.handle) > -1)
this.setState({
- validUserText: !present && validText,
+ validUserText: !present,
showAlreadyMemberError: present,
- userText: text
})
+ this.props.onSelectedMembersUpdate(selectedMembers)
}
render() {
- const {members, currentUser, isMember, removeMember, onCancel, removeInvite, invites = []} = this.props
+ const {
+ members, currentUser, isMember, removeMember, onCancel, removeInvite, invites = [],
+ selectedMembers, processingInvites
+ } = this.props
const showRemove = currentUser.isAdmin || (isMember && currentUser.isManager)
let i = 0
+ const allMembers = [...members, ...invites.map(i => i.member)]
return (
- {showRemove &&
+ {showRemove &&
invite more people
-
{ this.state.showAlreadyMemberError &&
Project Member(s) can't be invited again. Please remove them from list.
}
-
+
+
+
-
+
}
{!showRemove && }
@@ -294,8 +277,12 @@ class Dialog extends React.Component {
}
}
+Dialog.defaultProps = {
+ invites: [],
+ members: []
+}
+
Dialog.propTypes = {
- processingInvites: PT.bool.isRequired,
error: PT.oneOfType([PT.object, PT.bool]),
currentUser: PT.object.isRequired,
members: PT.arrayOf(PT.object).isRequired,
@@ -306,6 +293,9 @@ Dialog.propTypes = {
invites: PT.arrayOf(PT.object),
addUsers: PT.func.isRequired,
removeInvite: PT.func.isRequired,
+ onSelectedMembersUpdate: PT.func.isRequired,
+ selectedMembers: PT.arrayOf(PT.object),
+ processingInvites: PT.bool.isRequired,
}
export default Dialog
diff --git a/src/projects/detail/containers/TeamManagementContainer.jsx b/src/projects/detail/containers/TeamManagementContainer.jsx
index 27012e5fe..5f6cd5523 100644
--- a/src/projects/detail/containers/TeamManagementContainer.jsx
+++ b/src/projects/detail/containers/TeamManagementContainer.jsx
@@ -1,16 +1,29 @@
-import React, { Component } from 'react'
+import React, {Component} from 'react'
import PropTypes from 'prop-types'
-import { connect } from 'react-redux'
-import { withRouter } from 'react-router-dom'
+import {connect} from 'react-redux'
+import {withRouter} from 'react-router-dom'
import _ from 'lodash'
import {
- ROLE_CONNECT_COPILOT, ROLE_CONNECT_MANAGER, ROLE_ADMINISTRATOR, ROLE_CONNECT_ADMIN,
- PROJECT_ROLE_COPILOT, PROJECT_ROLE_MANAGER, PROJECT_ROLE_CUSTOMER, ROLE_CONNECT_COPILOT_MANAGER,
+ PROJECT_ROLE_COPILOT,
+ PROJECT_ROLE_CUSTOMER,
+ PROJECT_ROLE_MANAGER,
+ ROLE_ADMINISTRATOR,
+ ROLE_CONNECT_ADMIN,
+ ROLE_CONNECT_COPILOT,
+ ROLE_CONNECT_COPILOT_MANAGER,
+ ROLE_CONNECT_MANAGER,
} from '../../../config/constants'
import TeamManagement from '../../../components/TeamManagement/TeamManagement'
-import { addProjectMember, updateProjectMember, removeProjectMember,
- loadMemberSuggestions, inviteProjectMembers, deleteProjectInvite,
- inviteTopcoderMembers, deleteTopcoderMemberInvite, acceptOrRefuseInvite
+import {
+ acceptOrRefuseInvite,
+ addProjectMember,
+ deleteProjectInvite,
+ deleteTopcoderMemberInvite,
+ inviteProjectMembers,
+ inviteTopcoderMembers,
+ loadMemberSuggestions,
+ removeProjectMember,
+ updateProjectMember
} from '../../actions/projectMember'
class TeamManagementContainer extends Component {
@@ -24,6 +37,8 @@ class TeamManagementContainer extends Component {
this.onMemberDeleteConfirm = this.onMemberDeleteConfirm.bind(this)
this.onJoinConfirm = this.onJoinConfirm.bind(this)
this.changeRole = this.changeRole.bind(this)
+ this.onSelectedMembersUpdate = this.onSelectedMembersUpdate.bind(this)
+ this.onShowDialog = this.onShowDialog.bind(this)
}
componentWillMount() {
@@ -40,21 +55,25 @@ class TeamManagementContainer extends Component {
// navigate to project listing
this.props.history.push('/projects/')
}
+ const {processingInvites} = this.props
+ if (processingInvites && !nextProps.processingInvites && !nextProps.error ) {
+ this.resetSelectedMembers()
+ }
}
onMemberDeleteConfirm(member) {
// is user leaving current project
const isLeaving = member.userId === this.props.currentUser.userId
this.props.removeProjectMember(this.props.projectId, member.id, isLeaving)
- this.setState({ isUserLeaving: isLeaving })
+ this.setState({isUserLeaving: isLeaving})
}
onJoinConfirm() {
- const { currentUser, projectId, addProjectMember } = this.props
+ const {currentUser, projectId, addProjectMember} = this.props
const role = currentUser.isCopilot ? PROJECT_ROLE_COPILOT : PROJECT_ROLE_MANAGER
addProjectMember(
projectId,
- { userId: currentUser.userId, role }
+ {userId: currentUser.userId, role}
)
}
@@ -62,15 +81,17 @@ class TeamManagementContainer extends Component {
this.props.deleteTopcoderMemberInvite(this.props.projectId, invite)
}
- onTopcoderInviteSend(items) {
- this.props.inviteTopcoderMembers(this.props.projectId, items)
+ onTopcoderInviteSend(role) {
+ const {handles} = this.getEmailsAndHandles()
+ this.props.inviteTopcoderMembers(this.props.projectId, {role, handles})
}
onProjectInviteDelete(invite) {
this.props.deleteProjectInvite(this.props.projectId, invite)
}
- onProjectInviteSend(emails, handles) {
+ onProjectInviteSend() {
+ const {handles, emails} = this.getEmailsAndHandles()
this.props.inviteProjectMembers(this.props.projectId, emails, handles)
}
@@ -79,7 +100,7 @@ class TeamManagementContainer extends Component {
}
anontateMemberProps() {
- const { members, allMembers } = this.props
+ const {members, allMembers} = this.props
// fill project members from state.members object
return _.map(members, m => {
if (!m.userId) return m
@@ -103,13 +124,44 @@ class TeamManagementContainer extends Component {
})
}
- annotateInvites(invites, members){
+ annotateInvites(invites, members) {
return _.map(invites, i => {
i.member = _.find(members, m => m.userId === i.userId)
return i
})
}
+ onSelectedMembersUpdate(selectedMembers) {
+ this.setState({selectedMembers})
+ }
+
+ resetSelectedMembers() {
+ this.onSelectedMembersUpdate([])
+ }
+
+ onShowDialog(visible) {
+ if(!visible) {
+ this.resetSelectedMembers()
+ }
+ }
+
+ getEmailsAndHandles() {
+ const {selectedMembers} = this.state
+ const handles = []
+ const emails = []
+ selectedMembers.map(selectedOption => {
+ const value = selectedOption.handle
+ // Test if its email
+ if (selectedOption.isEmail) {
+ emails.push(value)
+ } else {
+ handles.push(value)
+ }
+ })
+ return {emails, handles}
+ }
+
+
render() {
const projectMembers = this.anontateMemberProps()
const projectTeamInvites = this.annotateInvites(this.props.projectTeamInvites, this.props.allMembers)
@@ -133,16 +185,20 @@ class TeamManagementContainer extends Component {
onTopcoderInviteDeleteConfirm={this.onTopcoderInviteDelete}
onTopcoderInviteSend={this.onTopcoderInviteSend}
changeRole={this.changeRole}
+ onSelectedMembersUpdate={this.onSelectedMembersUpdate}
+ selectedMembers={this.state.selectedMembers}
+ onShowTopcoderDialog={this.onShowDialog}
+ onShowProjectDialot={this.onShowDialog}
/>
)
}
}
-const mapStateToProps = ({ loadUser, members, projectState }) => {
+const mapStateToProps = ({loadUser, members, projectState}) => {
const adminRoles = [ROLE_ADMINISTRATOR, ROLE_CONNECT_ADMIN]
const powerUserRoles = [ROLE_CONNECT_COPILOT, ROLE_CONNECT_MANAGER, ROLE_ADMINISTRATOR, ROLE_CONNECT_ADMIN]
- const managerRoles = [ ROLE_ADMINISTRATOR, ROLE_CONNECT_ADMIN, ROLE_CONNECT_MANAGER ]
+ const managerRoles = [ROLE_ADMINISTRATOR, ROLE_CONNECT_ADMIN, ROLE_CONNECT_MANAGER]
return {
currentUser: {
userId: parseInt(loadUser.user.id),