Skip to content

Commit 715c427

Browse files
committed
PM-686 - NDA & work groups for projects
1 parent 01b0ee1 commit 715c427

File tree

4 files changed

+161
-16
lines changed

4 files changed

+161
-16
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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+
const fetchGroups = debounce((inputValue, callback) => {
10+
fetchGroupsApi({ name: inputValue })
11+
.then(groups => {
12+
const suggestedOptions = groups.map(group => ({
13+
label: group.name,
14+
value: group.id
15+
}))
16+
return callback(suggestedOptions)
17+
})
18+
.catch(() => {
19+
return callback(null)
20+
})
21+
}, AUTOCOMPLETE_DEBOUNCE_TIME_MS)
22+
23+
const GroupsFormField = ({ value, name, onBlur, onChange, id, placeholder, ref }) => {
24+
const selectedGroups = useMapSelectedGroups(value)
25+
26+
const handleChange = useCallback((values) => {
27+
onChange({ target: { name, value: map(values, 'value') } })
28+
}, [])
29+
30+
return (
31+
<Select
32+
id={id}
33+
ref={ref}
34+
isMulti
35+
onBlur={onBlur}
36+
simpleValue
37+
isAsync
38+
name={name}
39+
value={selectedGroups}
40+
onChange={handleChange}
41+
cacheOptions
42+
loadOptions={fetchGroups}
43+
placeholder={placeholder}
44+
/>
45+
)
46+
}
47+
48+
GroupsFormField.defaultProps = {
49+
onChange: () => {},
50+
onBlur: () => {},
51+
id: 'group-select',
52+
value: [],
53+
name: '',
54+
ref: undefined,
55+
placeholder: ''
56+
}
57+
58+
GroupsFormField.propTypes = {
59+
value: PropTypes.arrayOf(PropTypes.shape()),
60+
id: PropTypes.string,
61+
onBlur: PropTypes.func,
62+
onChange: PropTypes.func,
63+
name: PropTypes.string,
64+
ref: PropTypes.ref,
65+
placeholder: PropTypes.string
66+
}
67+
68+
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)