Skip to content

Commit d2d2a07

Browse files
authored
Merge pull request #1607 from topcoder-platform/PM-686_add-nda
PM-686 - NDA & work groups for projects
2 parents f87645c + cccd7f3 commit d2d2a07

File tree

4 files changed

+167
-16
lines changed

4 files changed

+167
-16
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import React, { useCallback } from 'react'
2+
import { debounce, map } from 'lodash'
3+
import PropTypes from 'prop-types'
4+
import Select from '../../Select'
5+
import { fetchGroups as fetchGroupsApi } from '../../../services/challenges'
6+
import { AUTOCOMPLETE_DEBOUNCE_TIME_MS } from '../../../config/constants'
7+
import { useMapSelectedGroups } from './use-map-selected-groups.hook'
8+
9+
/**
10+
* Search & fetch groups from api, filtering by group name
11+
*/
12+
const fetchGroups = debounce((inputValue, callback) => {
13+
fetchGroupsApi({ name: inputValue })
14+
.then(groups => {
15+
const suggestedOptions = groups.map(group => ({
16+
label: group.name,
17+
value: group.id
18+
}))
19+
return callback(suggestedOptions)
20+
})
21+
.catch(() => {
22+
return callback(null)
23+
})
24+
}, AUTOCOMPLETE_DEBOUNCE_TIME_MS)
25+
26+
/**
27+
* Component to handle project groups
28+
*/
29+
const GroupsFormField = ({ value, name, onBlur, onChange, id, placeholder, ref }) => {
30+
const selectedGroups = useMapSelectedGroups(value)
31+
32+
const handleChange = useCallback((values) => {
33+
onChange({ target: { name, value: map(values, 'value') } })
34+
}, [])
35+
36+
return (
37+
<Select
38+
id={id}
39+
ref={ref}
40+
isMulti
41+
onBlur={onBlur}
42+
simpleValue
43+
isAsync
44+
name={name}
45+
value={selectedGroups}
46+
onChange={handleChange}
47+
cacheOptions
48+
loadOptions={fetchGroups}
49+
placeholder={placeholder}
50+
/>
51+
)
52+
}
53+
54+
GroupsFormField.defaultProps = {
55+
onChange: () => {},
56+
onBlur: () => {},
57+
id: 'group-select',
58+
value: [],
59+
name: '',
60+
ref: undefined,
61+
placeholder: ''
62+
}
63+
64+
GroupsFormField.propTypes = {
65+
value: PropTypes.arrayOf(PropTypes.shape()),
66+
id: PropTypes.string,
67+
onBlur: PropTypes.func,
68+
onChange: PropTypes.func,
69+
name: PropTypes.string,
70+
ref: PropTypes.ref,
71+
placeholder: PropTypes.string
72+
}
73+
74+
export default GroupsFormField
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { useEffect, useState } from 'react'
2+
import { loadGroupDetails } from '../../../actions/challenges'
3+
4+
export const useMapSelectedGroups = (groupIds) => {
5+
const [selectedGroups, setSelectedGroups] = useState([])
6+
useEffect(() => {
7+
if (!groupIds || !groupIds.length) {
8+
setSelectedGroups([])
9+
return
10+
}
11+
12+
loadGroupDetails(groupIds).then(res => {
13+
setSelectedGroups(res.map(d => ({ label: d.name, value: d.id })))
14+
})
15+
}, [groupIds])
16+
return selectedGroups
17+
}

src/components/ProjectForm/ProjectForm.module.scss

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,15 @@ form {
5959
color: $tc-gray-80;
6060
padding: 10px 20px;
6161
}
62+
input[type=radio] {
63+
width: 18px;
64+
}
65+
}
66+
67+
.flexField {
68+
display: flex;
69+
flex-direction: row;
70+
gap: 15px;
6271
}
6372

6473
.error {
@@ -81,3 +90,9 @@ form {
8190
}
8291
}
8392
}
93+
94+
.flexRow {
95+
display: flex;
96+
gap: 5px;
97+
align-items: center;
98+
}

src/components/ProjectForm/index.js

Lines changed: 61 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
import React, { useState } from 'react'
1+
import React, { useMemo, useState } from 'react'
22
import { useForm, Controller } from 'react-hook-form'
33
import cn from 'classnames'
4+
import { get } from 'lodash'
45
import styles from './ProjectForm.module.scss'
56
import { PrimaryButton } from '../Buttons'
67
import Select from '../Select'
7-
import { PROJECT_STATUS } from '../../config/constants'
8+
import { PROJECT_STATUS, DEFAULT_NDA_UUID } from '../../config/constants'
9+
import GroupsFormField from './GroupsFormField'
810

911
const ProjectForm = ({
1012
projectTypes,
@@ -31,7 +33,9 @@ const ProjectForm = ({
3133
: null,
3234
projectType: isEdit
3335
? projectTypes.find((item) => item.key === projectDetail.type) || null
34-
: null // we'll store type as an object from react-select
36+
: null, // we'll store type as an object from react-select
37+
terms: get(projectDetail, ['terms', 0], ''),
38+
groups: get(projectDetail, ['groups'], [])
3539
}
3640
})
3741

@@ -41,18 +45,18 @@ const ProjectForm = ({
4145
setIsSaving(true)
4246

4347
try {
48+
const payload = {
49+
name: data.projectName,
50+
description: data.description,
51+
type: data.projectType.value,
52+
groups: data.groups,
53+
terms: data.terms ? [data.terms] : []
54+
}
55+
4456
if (isEdit) {
45-
await updateProject(projectDetail.id, {
46-
name: data.projectName,
47-
description: data.description,
48-
status: data.status.value
49-
})
57+
await updateProject(projectDetail.id, payload)
5058
} else {
51-
const res = await createProject({
52-
name: data.projectName,
53-
description: data.description,
54-
type: data.projectType.value
55-
})
59+
const res = await createProject(payload)
5660

5761
history.push(`/projects/${res.value.id}/challenges`)
5862
setActiveProject(res.value.id)
@@ -65,10 +69,10 @@ const ProjectForm = ({
6569
}
6670

6771
// Build options for react-select from `types`
68-
const selectOptions = projectTypes.map((t) => ({
72+
const projectTypeOptions = useMemo(() => projectTypes.map((t) => ({
6973
value: t.key,
7074
label: t.displayName
71-
}))
75+
})), [projectTypes])
7276

7377
return (
7478
<div>
@@ -149,7 +153,7 @@ const ProjectForm = ({
149153
rules={{ required: 'Please select a type' }}
150154
render={({ field }) => (
151155
<Select
152-
options={selectOptions}
156+
options={projectTypeOptions}
153157
id='projectType'
154158
{...field}
155159
isClearable
@@ -188,6 +192,47 @@ const ProjectForm = ({
188192
)}
189193
</div>
190194
</div>
195+
<div className={cn(styles.row)}>
196+
<div className={cn(styles.formLabel, styles.field)}>
197+
<label label htmlFor='description'>
198+
Enforce Topcoder NDA:
199+
</label>
200+
</div>
201+
<div className={cn(styles.field, styles.formField, styles.flexField)}>
202+
<label className={cn(styles.flexRow)}>
203+
Yes
204+
<input
205+
type='radio'
206+
value={DEFAULT_NDA_UUID}
207+
{...register('terms', {})}
208+
/>
209+
</label>
210+
<label className={cn(styles.flexRow)}>
211+
No
212+
<input
213+
type='radio'
214+
value=''
215+
{...register('terms', {})}
216+
/>
217+
</label>
218+
</div>
219+
</div>
220+
<div className={cn(styles.row)}>
221+
<div className={cn(styles.formLabel, styles.field)}>
222+
<label label htmlFor='description'>
223+
Intended Work Groups:
224+
</label>
225+
</div>
226+
<div className={cn(styles.field, styles.formField)}>
227+
<Controller
228+
name='groups'
229+
control={control}
230+
render={({ field }) => (
231+
<GroupsFormField {...field} />
232+
)}
233+
/>
234+
</div>
235+
</div>
191236
</div>
192237
<div className={styles.actionButtons}>
193238
<PrimaryButton

0 commit comments

Comments
 (0)