Skip to content

Commit 8b86d0c

Browse files
authored
Merge pull request #2877 from maxceem/feature/individual-invitation-errors
Feature/individual invitation errors
2 parents dfd75f1 + 887cf4e commit 8b86d0c

File tree

9 files changed

+202
-79
lines changed

9 files changed

+202
-79
lines changed

src/api/projectMemberInvites.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,16 @@ export function updateProjectMemberInvite(projectId, member) {
2323
*/
2424
export function createProjectMemberInvite(projectId, member) {
2525
const url = `${PROJECTS_API_URL}/v4/projects/${projectId}/members/invite/`
26-
return axios.post(url, { param: member})
26+
return axios({
27+
method: 'post',
28+
url,
29+
data: {
30+
param: member
31+
},
32+
validateStatus (status) {
33+
return (status >= 200 && status < 300) || status === 403
34+
},
35+
})
2736
.then(resp => {
2837
return resp.data.result.content
2938
})

src/components/TeamManagement/AutocompleteInput.jsx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ class AutocompleteInput extends React.Component {
4141
AutocompleteInput.defaultProps = {
4242
placeholder: 'Enter one or more user handles',
4343
selectedMembers: [],
44-
allMembers: [],
4544
disabled: false
4645
}
4746

@@ -71,11 +70,6 @@ AutocompleteInput.propTypes = {
7170
* The flag if component is disabled
7271
*/
7372
disabled: PropTypes.bool,
74-
75-
/**
76-
* List of both current and invited members of project
77-
*/
78-
allMembers: PropTypes.arrayOf(PropTypes.object)
7973
}
8074

8175
export default AutocompleteInput

src/components/TeamManagement/AutocompleteInputContainer.jsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class AutocompleteInputContainer extends React.Component {
3535

3636
render() {
3737

38-
const { placeholder, currentUser, selectedMembers, disabled, allMembers} = this.props
38+
const { placeholder, currentUser, selectedMembers, disabled } = this.props
3939

4040
return (
4141
<AutocompleteInput
@@ -46,7 +46,6 @@ class AutocompleteInputContainer extends React.Component {
4646
currentUser={currentUser}
4747
selectedMembers={selectedMembers}
4848
disabled={disabled}
49-
allMembers={allMembers}
5049
/>
5150
)
5251
}

src/components/TeamManagement/ProjectManagementDialog.js

Lines changed: 75 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,54 +8,95 @@ import Avatar from 'appirio-tech-react-components/components/Avatar/Avatar'
88
import {getAvatarResized} from '../../helpers/tcHelpers'
99
import AutocompleteInputContainer from './AutocompleteInputContainer'
1010

11-
class Dialog extends React.Component {
11+
class ProjectManagementDialog extends React.Component {
1212
constructor(props) {
1313
super(props)
1414
this.state = {
15-
clearText: false,
1615
showAlreadyMemberError: false,
17-
invitedMembers: {},
18-
members: {}
16+
errorMessage: null
1917
}
2018
this.onChange = this.onChange.bind(this)
19+
this.showIndividualErrors = this.showIndividualErrors.bind(this)
2120
}
2221

23-
componentWillMount() {
22+
componentWillReceiveProps(nextProps) {
23+
const { processingInvites, selectedMembers } = this.props
24+
25+
if (processingInvites && !nextProps.processingInvites ) {
26+
const notInvitedSelectedMembers = _.reject(selectedMembers, (selectedMember) => (
27+
this.isSelectedMemberAlreadyInvited(nextProps.invites, selectedMember)
28+
))
29+
30+
this.props.onSelectedMembersUpdate(notInvitedSelectedMembers)
31+
32+
if (nextProps.error) {
33+
this.showIndividualErrors(nextProps.error, notInvitedSelectedMembers)
34+
}
35+
}
36+
}
37+
38+
onChange(selectedMembers) {
39+
const { invites } = this.props
40+
41+
const present = _.some(selectedMembers, (selectedMember) => (
42+
this.isSelectedMemberAlreadyInvited(invites, selectedMember)
43+
))
44+
2445
this.setState({
25-
invitedMembers: this.props.invites,
26-
members: this.props.members
46+
validUserText: !present,
47+
showAlreadyMemberError: present,
48+
errorMessage: null,
2749
})
50+
51+
this.props.onSelectedMembersUpdate(selectedMembers)
2852
}
2953

30-
onChange(selectedMembers) {
31-
// If a member invited with email exists in selectedMembers
32-
let present = _.some(this.state.invitedMembers, invited => _.findIndex(selectedMembers,
33-
selectedMember => selectedMember.isEmail && selectedMember.label === invited.email) > -1)
34-
// If a member invited with handle exists in selectedMembers
35-
present = present || _.some(this.state.invitedMembers, invited => {
36-
if (!invited.member) {
37-
return false
38-
}
39-
return _.findIndex(selectedMembers,
40-
selectedMember => !selectedMember.isEmail && selectedMember.label === invited.member.handle) > -1
54+
isSelectedMemberAlreadyInvited(invites = [], selectedMember) {
55+
return !!invites.find((invite) => (
56+
invite.email && invite.email === selectedMember.label ||
57+
invite.userId && this.resolveUserHandle(invite.userId) === selectedMember.label
58+
))
59+
}
60+
61+
/**
62+
* Get user handle using `allMembers` which comes from props and contains all the users
63+
* which are loaded to `members.members` in the Redux store
64+
*
65+
* @param {Number} userId user id
66+
*/
67+
resolveUserHandle(userId) {
68+
const { allMembers } = this.props
69+
70+
return _.find(allMembers, { userId }).handle
71+
}
72+
73+
showIndividualErrors(error) {
74+
const uniqueMessages = _.groupBy(error.failed, 'message')
75+
76+
const msgs = _.keys(uniqueMessages).map((message) => {
77+
const users = uniqueMessages[message].map((failed) => (
78+
failed.email ? failed.email : this.resolveUserHandle(failed.userId)
79+
))
80+
81+
return ({
82+
message,
83+
users,
84+
})
4185
})
42-
// If members exist in selectedMembers
43-
present = present || _.some(this.state.members, m => _.findIndex(selectedMembers,
44-
selectedMember => selectedMember.label === m.handle) > -1)
86+
87+
const listMessages = msgs.map((m) => `${m.users.join(', ')}: ${m.message}`)
88+
4589
this.setState({
46-
validInviteText: !present,
47-
showAlreadyMemberError: present
90+
errorMessage: listMessages.length > 0 ? listMessages.join('\n') : null
4891
})
49-
this.props.onSelectedMembersUpdate(selectedMembers)
5092
}
5193

5294
render() {
5395
const {
5496
members, currentUser, isMember, removeMember, removeInvite,
55-
onCancel, invites = [], selectedMembers, processingInvites
97+
onCancel, invites = [], selectedMembers, processingInvites,
5698
} = this.props
5799
const showRemove = currentUser.isAdmin || (!currentUser.isCopilot && isMember)
58-
const allMembers = [...members, ...invites.map(i => i.member)]
59100
let i = 0
60101
return (
61102
<Modal
@@ -156,15 +197,17 @@ class Dialog extends React.Component {
156197
currentUser={currentUser}
157198
selectedMembers={selectedMembers}
158199
disabled={processingInvites || (!currentUser.isAdmin && !isMember)}
159-
allMembers={allMembers}
160200
/>
161201
{this.state.showAlreadyMemberError && <div className="error-message">
162202
Project Member(s) can't be invited again. Please remove them from list.
163203
</div>}
204+
{ this.state.errorMessage && <div className="error-message">
205+
{this.state.errorMessage}
206+
</div> }
164207
<button
165208
className="tc-btn tc-btn-primary tc-btn-md"
166209
type="submit"
167-
disabled={processingInvites || !this.state.validInviteText || this.state.clearText}
210+
disabled={processingInvites || this.state.showAlreadyMemberError || selectedMembers.length === 0}
168211
onClick={this.props.sendInvite}
169212
>
170213
Send Invite
@@ -177,15 +220,16 @@ class Dialog extends React.Component {
177220
}
178221
}
179222

180-
Dialog.defaultProps = {
223+
ProjectManagementDialog.defaultProps = {
181224
invites: [],
182225
members: []
183226
}
184227

185-
Dialog.propTypes = {
228+
ProjectManagementDialog.propTypes = {
186229
error: PT.oneOfType([PT.object, PT.bool]),
187230
currentUser: PT.object.isRequired,
188231
members: PT.arrayOf(PT.object).isRequired,
232+
allMembers: PT.arrayOf(PT.object).isRequired,
189233
isMember: PT.bool.isRequired,
190234
onCancel: PT.func.isRequired,
191235
removeMember: PT.func.isRequired,
@@ -197,4 +241,4 @@ Dialog.propTypes = {
197241
processingInvites: PT.bool.isRequired,
198242
}
199243

200-
export default Dialog
244+
export default ProjectManagementDialog

src/components/TeamManagement/TeamManagement.jsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ class TeamManagement extends React.Component {
5757
projectTeamInvites, onProjectInviteDeleteConfirm, onProjectInviteSend, deletingInvite, changeRole,
5858
onDeleteInvite, isShowTopcoderDialog, onShowTopcoderDialog, processingInvites, processingMembers,
5959
onTopcoderInviteSend, onTopcoderInviteDeleteConfirm, topcoderTeamInvites, onAcceptOrRefuse, error,
60-
onSelectedMembersUpdate, selectedMembers
60+
onSelectedMembersUpdate, selectedMembers, allMembers,
6161
} = this.props
6262
const currentMember = members.filter((member) => member.userId === currentUser.userId)[0]
6363
const modalActive = isAddingTeamMember || deletingMember || isShowJoin || showNewMemberConfirmation || deletingInvite
@@ -192,6 +192,7 @@ class TeamManagement extends React.Component {
192192
error={error}
193193
currentUser={currentUser}
194194
members={members}
195+
allMembers={allMembers}
195196
isMember={!!currentMember}
196197
onCancel={onClickCancel}
197198
removeMember={removeMember}
@@ -221,6 +222,7 @@ class TeamManagement extends React.Component {
221222
error={error}
222223
currentUser={currentUser}
223224
members={members}
225+
allMembers={allMembers}
224226
isMember={!!currentMember}
225227
onCancel={onClickCancel}
226228
removeMember={removeMember}
@@ -304,6 +306,11 @@ TeamManagement.propTypes = {
304306
*/
305307
members: PropTypes.arrayOf(userShape).isRequired,
306308

309+
/**
310+
* The list of all members which data is loaded client side at the moment
311+
*/
312+
allMembers: PropTypes.arrayOf(PropTypes.object).isRequired,
313+
307314
/**
308315
* The current deleting member. When defined a confirmation overlay will be displayed
309316
*/

0 commit comments

Comments
 (0)