Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit e3a73cb

Browse files
committed
Moved logic for AddModal from MemberList to new container component. Made style more consistent with rest of app. Cleanup and documentation
1 parent 49ab386 commit e3a73cb

File tree

8 files changed

+296
-171
lines changed

8 files changed

+296
-171
lines changed

src/components/BaseModal/index.jsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,15 @@ const containerStyle = {
2727
padding: "10px",
2828
};
2929

30-
function BaseModal({ open, onClose, children, title, button, disabled, extraModalStyle }) {
30+
function BaseModal({
31+
open,
32+
onClose,
33+
children,
34+
title,
35+
button,
36+
disabled,
37+
extraModalStyle,
38+
}) {
3139
return (
3240
<Modal
3341
open={open}
@@ -63,7 +71,7 @@ BaseModal.propTypes = {
6371
title: PT.string,
6472
button: PT.element,
6573
disabled: PT.bool,
66-
extraModalStyle: PT.object
74+
extraModalStyle: PT.object,
6775
};
6876

6977
export default BaseModal;

src/components/ReactSelect/index.jsx

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import React from "react";
77
import PT from "prop-types";
88
import Select from "react-select";
9-
import CreatableSelect from "react-select/creatable"
9+
import CreatableSelect from "react-select/creatable";
1010
import "./styles.module.scss";
1111

1212
const ReactSelect = (props) => {
@@ -71,37 +71,37 @@ const ReactSelect = (props) => {
7171
return (
7272
<div styleName="select-wrapper">
7373
{props.isCreatable ? (
74-
<CreatableSelect
75-
value={props.value}
76-
styles={customStyles}
77-
onChange={props.onChange}
78-
options={props.options}
79-
styleName={props.error ? "error" : ""}
80-
isMulti={props.isMulti}
81-
onBlur={props.onBlur}
82-
onFocus={props.onFocus}
83-
placeholder={props.placeholder}
84-
onInputChange={props.onInputChange}
85-
noOptionsMessage={() => props.noOptionsText}
86-
createOptionPosition="first"
87-
/>
74+
<CreatableSelect
75+
value={props.value}
76+
styles={customStyles}
77+
onChange={props.onChange}
78+
options={props.options}
79+
styleName={props.error ? "error" : ""}
80+
isMulti={props.isMulti}
81+
onBlur={props.onBlur}
82+
onFocus={props.onFocus}
83+
placeholder={props.placeholder}
84+
onInputChange={props.onInputChange}
85+
noOptionsMessage={() => props.noOptionsText}
86+
createOptionPosition="first"
87+
/>
8888
) : (
89-
<Select
90-
value={props.value}
91-
styles={customStyles}
92-
onChange={props.onChange}
93-
options={props.options}
94-
styleName={props.error ? "error" : ""}
95-
isMulti={props.isMulti}
96-
onBlur={props.onBlur}
97-
onFocus={props.onFocus}
98-
placeholder={props.placeholder}
99-
onInputChange={props.onInputChange}
100-
noOptionsMessage={() => props.noOptionsText}
101-
/>
89+
<Select
90+
value={props.value}
91+
styles={customStyles}
92+
onChange={props.onChange}
93+
options={props.options}
94+
styleName={props.error ? "error" : ""}
95+
isMulti={props.isMulti}
96+
onBlur={props.onBlur}
97+
onFocus={props.onFocus}
98+
placeholder={props.placeholder}
99+
onInputChange={props.onInputChange}
100+
noOptionsMessage={() => props.noOptionsText}
101+
/>
102102
)}
103103
</div>
104-
)
104+
);
105105
};
106106

107107
ReactSelect.propTypes = {
@@ -120,7 +120,7 @@ ReactSelect.propTypes = {
120120
}).isRequired
121121
),
122122
isCreatable: PT.bool,
123-
noOptionsText: PT.string
123+
noOptionsText: PT.string,
124124
};
125125

126126
export default ReactSelect;

src/constants/index.js

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -224,18 +224,28 @@ export const STATUS_OPTIONS = [
224224
];
225225

226226
/*
227-
* TopCoder User Roles
228-
*/
229-
export const ROLE_TOPCODER_USER = 'Topcoder User'
230-
export const ROLE_CONNECT_COPILOT = 'Connect Copilot'
231-
export const ROLE_CONNECT_MANAGER = 'Connect Manager'
232-
export const ROLE_CONNECT_ACCOUNT_MANAGER = 'Connect Account Manager'
233-
export const ROLE_CONNECT_ADMIN = 'Connect Admin'
234-
export const ROLE_ADMINISTRATOR = 'administrator'
235-
export const ROLE_CONNECT_COPILOT_MANAGER = 'Connect Copilot Manager'
236-
export const ROLE_BUSINESS_DEVELOPMENT_REPRESENTATIVE = 'Business Development Representative'
237-
export const ROLE_PRESALES = 'Presales'
238-
export const ROLE_ACCOUNT_EXECUTIVE = 'Account Executive'
239-
export const ROLE_PROGRAM_MANAGER = 'Program Manager'
240-
export const ROLE_SOLUTION_ARCHITECT = 'Solution Architect'
241-
export const ROLE_PROJECT_MANAGER = 'Project Manager'
227+
* TopCoder user roles
228+
*/
229+
export const ROLE_TOPCODER_USER = "Topcoder User";
230+
export const ROLE_CONNECT_COPILOT = "Connect Copilot";
231+
export const ROLE_CONNECT_MANAGER = "Connect Manager";
232+
export const ROLE_CONNECT_ACCOUNT_MANAGER = "Connect Account Manager";
233+
export const ROLE_CONNECT_ADMIN = "Connect Admin";
234+
export const ROLE_ADMINISTRATOR = "administrator";
235+
export const ROLE_CONNECT_COPILOT_MANAGER = "Connect Copilot Manager";
236+
export const ROLE_BUSINESS_DEVELOPMENT_REPRESENTATIVE =
237+
"Business Development Representative";
238+
export const ROLE_PRESALES = "Presales";
239+
export const ROLE_ACCOUNT_EXECUTIVE = "Account Executive";
240+
export const ROLE_PROGRAM_MANAGER = "Program Manager";
241+
export const ROLE_SOLUTION_ARCHITECT = "Solution Architect";
242+
export const ROLE_PROJECT_MANAGER = "Project Manager";
243+
244+
// User roles that can see suggestions when adding new members to project
245+
export const SEE_SUGGESTION_ROLES = [
246+
ROLE_ADMINISTRATOR,
247+
ROLE_CONNECT_ADMIN,
248+
ROLE_CONNECT_MANAGER,
249+
ROLE_CONNECT_ACCOUNT_MANAGER,
250+
ROLE_CONNECT_COPILOT_MANAGER,
251+
];

src/routes/TeamAccess/components/AddModal/index.jsx

Lines changed: 83 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { useCallback, useState } from "react";
22
import _ from "lodash";
3+
import PT from "prop-types";
34
import { useDispatch, useSelector } from "react-redux";
45
import { toastr } from "react-redux-toastr";
56
import { loadSuggestions, clearSuggestions, addMembers } from "../../actions";
@@ -8,37 +9,51 @@ import BaseModal from "components/BaseModal";
89
import ReactSelect from "components/ReactSelect";
910
import "./styles.module.scss";
1011

12+
// Minimum length of input for suggestions to trigger
1113
const SUGGESTION_TRIGGER_LENGTH = 3;
1214

15+
/**
16+
* Filters selected members, keeping those who could not be added to team
17+
* @param {Object[]} members The list of selected members
18+
* @param {Object[]} failedList The list of members who could not be added
19+
*
20+
* @returns {Object[]} The filtered list
21+
*/
1322
const filterFailed = (members, failedList) => {
14-
return members.filter(member => {
15-
return _.some(failedList, failedMem => {
23+
return members.filter((member) => {
24+
return _.some(failedList, (failedMem) => {
1625
if (failedMem.email) {
1726
return failedMem.email === member.label;
1827
}
1928
return failedMem.handle === member.label;
20-
})
21-
})
22-
}
23-
29+
});
30+
});
31+
};
32+
33+
/**
34+
* Groups users by error message so they can be displayed together
35+
* @param {Object[]} errorList A list of errors returned from server
36+
*
37+
* @returns {string[]} A list of messages, ready to be displayed
38+
*/
2439
const groupErrors = (errorList) => {
25-
const grouped = _.groupBy(errorList, 'error');
40+
const grouped = _.groupBy(errorList, "error");
2641

27-
const messages = Object.keys(grouped).map(error => {
28-
const labels = grouped[error].map(failure => (
42+
const messages = Object.keys(grouped).map((error) => {
43+
const labels = grouped[error].map((failure) =>
2944
failure.email ? failure.email : failure.handle
30-
))
45+
);
3146

32-
return ({
47+
return {
3348
message: error,
34-
users: labels
35-
})
36-
})
49+
users: labels,
50+
};
51+
});
3752

38-
return messages.map(msg => `${msg.users.join(", ")}: ${msg.message}`)
39-
}
53+
return messages.map((msg) => `${msg.users.join(", ")}: ${msg.message}`);
54+
};
4055

41-
function AddModal({ open, onClose, teamId, validateAdds, showSuggestions }) {
56+
const AddModal = ({ open, onClose, teamId, validateAdds, showSuggestions }) => {
4257
const [loading, setLoading] = useState(false);
4358
const [validationError, setValidationError] = useState(false);
4459
const [responseErrors, setResponseErrors] = useState([]);
@@ -80,39 +95,39 @@ function AddModal({ open, onClose, teamId, validateAdds, showSuggestions }) {
8095

8196
setLoading(true);
8297

83-
dispatch(addMembers(teamId, handles, emails)).then((res) => {
84-
setLoading(false);
85-
const { success, failed } = res.value;
86-
if (success.length) {
87-
const numAdds = success.length;
88-
const plural = numAdds !== 1 ? "s" : "";
89-
toastr.success(
90-
"Members Added",
91-
`Successfully added ${numAdds} member${plural}`
92-
);
93-
}
94-
95-
if (failed.length) {
96-
const remaining = filterFailed(selectedMembers, failed);
97-
const errors = groupErrors(failed);
98+
dispatch(addMembers(teamId, handles, emails))
99+
.then((res) => {
100+
setLoading(false);
101+
const { success, failed } = res.value;
102+
if (success.length) {
103+
const numAdds = success.length;
104+
const plural = numAdds !== 1 ? "s" : "";
105+
toastr.success(
106+
"Members Added",
107+
`Successfully added ${numAdds} member${plural}`
108+
);
109+
}
98110

99-
setSelectedMembers(remaining);
100-
setResponseErrors(errors);
101-
} else {
102-
handleClose();
103-
}
111+
if (failed.length) {
112+
const remaining = filterFailed(selectedMembers, failed);
113+
const errors = groupErrors(failed);
104114

105-
})
106-
.catch(err => {
107-
setLoading(false);
115+
setSelectedMembers(remaining);
116+
setResponseErrors(errors);
117+
} else {
118+
handleClose();
119+
}
120+
})
121+
.catch((err) => {
122+
setLoading(false);
108123

109-
// Display message from server error, else display generic message
110-
if (!!err.response) {
111-
setResponseErrors([err.message]);
112-
} else {
113-
setResponseErrors(["Error occured when adding members"]);
114-
}
115-
})
124+
// Display message from server error, else display generic message
125+
if (!!err.response) {
126+
setResponseErrors([err.message]);
127+
} else {
128+
setResponseErrors(["Error occured when adding members"]);
129+
}
130+
});
116131
}, [dispatch, selectedMembers, teamId]);
117132

118133
const onInputChange = useCallback(
@@ -191,10 +206,28 @@ function AddModal({ open, onClose, teamId, validateAdds, showSuggestions }) {
191206
isCreatable
192207
noOptionsText="Type to search"
193208
/>
194-
{validationError && <div styleName="error-message">Project member(s) can't be added again. Please remove them from list</div>}
195-
{responseErrors.length > 0 && <div styleName="error-message">{responseErrors.map(err => (<p>{err}</p>))}</div>}
209+
{validationError && (
210+
<div styleName="error-message">
211+
Project member(s) can't be added again. Please remove them from list
212+
</div>
213+
)}
214+
{responseErrors.length > 0 && (
215+
<div styleName="error-message">
216+
{responseErrors.map((err) => (
217+
<p>{err}</p>
218+
))}
219+
</div>
220+
)}
196221
</BaseModal>
197222
);
198-
}
223+
};
224+
225+
AddModal.propTypes = {
226+
open: PT.bool,
227+
onClose: PT.func,
228+
teamId: PT.string,
229+
validateAdds: PT.func,
230+
showSuggestions: PT.bool,
231+
};
199232

200233
export default AddModal;

src/routes/TeamAccess/components/AddModal/styles.module.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@
1212
border: 1px solid #ffd4d1;
1313
border-radius: 2px;
1414
background: #fff4f4;
15-
}
15+
}

0 commit comments

Comments
 (0)