Skip to content

Commit 128cbb3

Browse files
author
vikasrohit
authored
Merge pull request #803 from topcoder-platform/develop
Moving latest to prod
2 parents b23a5e8 + c9d7467 commit 128cbb3

File tree

27 files changed

+942
-127
lines changed

27 files changed

+942
-127
lines changed

src/actions/challenges.js

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ import {
1212
fetchResources,
1313
fetchResourceRoles,
1414
fetchChallengeTimelines,
15-
fetchChallengeTracks
15+
fetchChallengeTracks,
16+
updateChallenge,
17+
patchChallenge,
18+
createChallenge as createChallengeAPI
1619
} from '../services/challenges'
1720
import {
1821
LOAD_CHALLENGE_DETAILS_PENDING,
@@ -30,7 +33,13 @@ import {
3033
LOAD_CHALLENGE_RESOURCES_SUCCESS,
3134
LOAD_CHALLENGE_RESOURCES_FAILURE,
3235
REMOVE_ATTACHMENT,
33-
PAGE_SIZE
36+
PAGE_SIZE,
37+
UPDATE_CHALLENGE_DETAILS_PENDING,
38+
UPDATE_CHALLENGE_DETAILS_SUCCESS,
39+
UPDATE_CHALLENGE_DETAILS_FAILURE,
40+
CREATE_CHALLENGE_PENDING,
41+
CREATE_CHALLENGE_SUCCESS,
42+
CREATE_CHALLENGE_FAILURE
3443
} from '../config/constants'
3544
import { fetchProjectById } from '../services/projects'
3645
import { loadProject } from './projects'
@@ -197,6 +206,88 @@ export function loadChallengeDetails (projectId, challengeId) {
197206
}
198207
}
199208

209+
/**
210+
* Update challenge details
211+
*
212+
* @param {String} challengeId challenge id
213+
* @param {Object} challengeDetails challenge data
214+
*
215+
* @returns {Promise<{ type: string, challengeDetails: object }>} action object
216+
*/
217+
export function updateChallengeDetails (challengeId, challengeDetails) {
218+
return async (dispatch) => {
219+
dispatch({
220+
type: UPDATE_CHALLENGE_DETAILS_PENDING
221+
})
222+
223+
return updateChallenge(challengeId, challengeDetails).then((challenge) => {
224+
return dispatch({
225+
type: UPDATE_CHALLENGE_DETAILS_SUCCESS,
226+
challengeDetails: challenge
227+
})
228+
}).catch(() => {
229+
dispatch({
230+
type: UPDATE_CHALLENGE_DETAILS_FAILURE
231+
})
232+
})
233+
}
234+
}
235+
236+
/**
237+
* Create a new challenge
238+
*
239+
* @param {Object} challengeDetails challenge data
240+
*
241+
* @returns {Promise<{ type: string, challengeDetails: object }>} action object
242+
*/
243+
export function createChallenge (challengeDetails) {
244+
return async (dispatch) => {
245+
dispatch({
246+
type: CREATE_CHALLENGE_PENDING
247+
})
248+
249+
return createChallengeAPI(challengeDetails).then((challenge) => {
250+
return dispatch({
251+
type: CREATE_CHALLENGE_SUCCESS,
252+
challengeDetails: challenge
253+
})
254+
}).catch(() => {
255+
dispatch({
256+
type: CREATE_CHALLENGE_FAILURE
257+
})
258+
})
259+
}
260+
}
261+
262+
/**
263+
* Partially update challenge details
264+
*
265+
* The difference from `updateChallengeDetails` that this method internally uses `PATCH` API method instead of `PUT`.
266+
*
267+
* @param {String} challengeId challenge id
268+
* @param {Object} partialChallengeDetails partial challenge data
269+
*
270+
* @returns {Promise<{ type: string, challengeDetails: object }>} action object
271+
*/
272+
export function partiallyUpdateChallengeDetails (challengeId, partialChallengeDetails) {
273+
return async (dispatch) => {
274+
dispatch({
275+
type: UPDATE_CHALLENGE_DETAILS_PENDING
276+
})
277+
278+
return patchChallenge(challengeId, partialChallengeDetails).then((challenge) => {
279+
return dispatch({
280+
type: UPDATE_CHALLENGE_DETAILS_SUCCESS,
281+
challengeDetails: challenge
282+
})
283+
}).catch(() => {
284+
dispatch({
285+
type: UPDATE_CHALLENGE_DETAILS_FAILURE
286+
})
287+
})
288+
}
289+
}
290+
200291
export function loadTimelineTemplates () {
201292
return async (dispatch) => {
202293
const timelineTemplates = await fetchTimelineTemplates()

src/actions/members.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { LOAD_MEMBER_PENDING, LOAD_MEMBER_FAILURE, LOAD_MEMBER_SUCCESS } from '../config/constants'
2+
import { searchProfilesByUserIds } from '../services/user'
3+
import _ from 'lodash'
4+
5+
/**
6+
* Load user details into the store
7+
* @param userId user id
8+
* @param forceReload if `true` then member details would be reloaded even if already loaded
9+
* @returns {Function}
10+
*/
11+
export function loadMemberDetails (userId, forceReload = false) {
12+
return (dispatch, getState) => {
13+
const members = getState().members.members
14+
const existentMember = _.find(members, { userId })
15+
16+
// don't reload member details if already loaded, unless we force to
17+
if (existentMember && !forceReload) {
18+
return
19+
}
20+
21+
dispatch({
22+
type: LOAD_MEMBER_PENDING,
23+
meta: {
24+
userId
25+
}
26+
})
27+
searchProfilesByUserIds([userId]).then(([foundUser]) => {
28+
if (foundUser) {
29+
dispatch({
30+
type: LOAD_MEMBER_SUCCESS,
31+
payload: foundUser
32+
})
33+
} else {
34+
dispatch({
35+
type: LOAD_MEMBER_FAILURE
36+
})
37+
}
38+
}).catch(() => {
39+
dispatch({
40+
type: LOAD_MEMBER_FAILURE
41+
})
42+
})
43+
}
44+
}

src/components/Buttons/OutlineButton/Outline.module.scss

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,12 @@
4141
}
4242
}
4343

44-
&.disable {
44+
&.disable,
45+
&[disabled] {
4546
@include roboto-light();
4647
font-weight: 300;
47-
4848
border: 1px solid $tc-gray-40;
49+
cursor: default;
4950

5051
span {
5152
color: $tc-gray-40;

src/components/Buttons/OutlineButton/index.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,15 @@ import cn from 'classnames'
66
import styles from './Outline.module.scss'
77
import _ from 'lodash'
88

9-
const OutlineButton = ({ type, text, link, onClick, url, className, submit }) => {
9+
const OutlineButton = ({ type, text, link, onClick, url, className, submit, disabled }) => {
1010
if (_.isEmpty(link) && _.isEmpty(url)) {
1111
return (
12-
<button type={submit ? 'submit' : 'button'} className={cn(styles.container, styles[type], className)} onClick={submit ? null : onClick}>
12+
<button
13+
type={submit ? 'submit' : 'button'}
14+
className={cn(styles.container, styles[type], className)}
15+
onClick={submit ? null : onClick}
16+
disabled={disabled}
17+
>
1318
<span>{text}</span>
1419
</button>
1520
)
@@ -37,7 +42,8 @@ OutlineButton.propTypes = {
3742
url: PropTypes.string,
3843
className: PropTypes.string,
3944
onClick: PropTypes.func,
40-
submit: PropTypes.bool
45+
submit: PropTypes.bool,
46+
disabled: PropTypes.bool
4147
}
4248

4349
export default OutlineButton
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
@import "../../../styles/includes";
2+
3+
.row {
4+
box-sizing: border-box;
5+
display: flex;
6+
flex-direction: row;
7+
margin: 30px 30px 0 30px;
8+
align-content: space-between;
9+
justify-content: flex-start;
10+
11+
.field {
12+
@include upto-sm {
13+
display: block;
14+
padding-bottom: 10px;
15+
}
16+
17+
label {
18+
@include roboto-bold();
19+
20+
font-size: 16px;
21+
line-height: 19px;
22+
font-weight: 500;
23+
color: $tc-gray-80;
24+
}
25+
26+
&.col1 {
27+
max-width: 185px;
28+
min-width: 185px;
29+
margin-right: 14px;
30+
white-space: nowrap;
31+
display: flex;
32+
align-items: center;
33+
}
34+
35+
&.col2 {
36+
align-self: flex-end;
37+
margin-bottom: auto;
38+
margin-top: auto;
39+
display: flex;
40+
flex-direction: row;
41+
flex-wrap: wrap;
42+
}
43+
}
44+
45+
.readOnlyValue {
46+
margin-bottom: 0.5rem; // the same like `label` to be aligned
47+
}
48+
}
49+
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
* Field to choose assigned member using autocomplete.
3+
*/
4+
import React from 'react'
5+
import PropTypes from 'prop-types'
6+
import cn from 'classnames'
7+
import styles from './AssignedMember-Field.module.scss'
8+
import SelectUserAutocomplete from '../../SelectUserAutocomplete'
9+
10+
const AssignedMemberField = ({ challenge, onChange, assignedMemberDetails, readOnly }) => {
11+
const value = challenge.task.memberId ? {
12+
// if we know assigned member details, then show user `handle`, otherwise fallback to `userId`
13+
label: assignedMemberDetails ? assignedMemberDetails.handle : `User id: ${challenge.task.memberId}`,
14+
value: challenge.task.memberId
15+
} : null
16+
17+
return (
18+
<div className={styles.row}>
19+
<div className={cn(styles.field, styles.col1)}>
20+
<label htmlFor='assignedMember'>Assigned Member :</label>
21+
</div>
22+
<div className={cn(styles.field, styles.col2)}>
23+
{readOnly ? (
24+
value && <div className={styles.readOnlyValue}>{value.label}</div>
25+
) : (
26+
<SelectUserAutocomplete
27+
value={value}
28+
onChange={onChange}
29+
/>
30+
)}
31+
</div>
32+
</div>
33+
)
34+
}
35+
36+
AssignedMemberField.defaultProps = {
37+
assignedMemberDetails: null,
38+
readOnly: false
39+
}
40+
41+
AssignedMemberField.propTypes = {
42+
challenge: PropTypes.shape().isRequired,
43+
onChange: PropTypes.func,
44+
assignedMemberDetails: PropTypes.shape(),
45+
readOnly: PropTypes.bool
46+
}
47+
48+
export default AssignedMemberField

src/components/ChallengeEditor/ChallengeEditor.module.scss

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -239,14 +239,21 @@
239239
}
240240

241241
.actionButtons {
242-
&.button {
242+
position: absolute;
243+
top: 30px;
244+
a {
243245
height: 40px;
244-
position: absolute;
245-
top: 30px;
246-
right: 20px;
247246
}
248247
}
249248

249+
.actionButtonsLeft {
250+
left: 20px;
251+
}
252+
253+
.actionButtonsRight {
254+
right: 20px;
255+
}
256+
250257
.buttonContainer {
251258
display: flex;
252259
margin: 0px 30px;

src/components/ChallengeEditor/ChallengePrizes-Field/index.js

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,13 @@ class ChallengePrizesField extends Component {
5959

6060
renderPrizes () {
6161
const { currentPrizeIndex } = this.state
62-
const { readOnly } = this.props
62+
const { readOnly, challenge } = this.props
63+
const isTask = _.get(challenge, 'task.isTask', false)
6364
return _.map(this.getChallengePrize().prizes, (prize, index, { length }) => (
6465
<div key={`${index}-${prize.amount}-edit`}>
6566
<div className={styles.row}>
6667
<div className={cn(styles.field, styles.col1)}>
67-
<label htmlFor={`${index}-prize`}>Prize {index + 1} {!readOnly && (<span>*</span>)}:</label>
68+
<label htmlFor={`${index}-prize`}>Prize {!isTask ? index + 1 : ''} {!readOnly && (<span>*</span>)}:</label>
6869
</div>
6970
{readOnly ? (
7071
<span>${prize.value}</span>
@@ -83,20 +84,23 @@ class ChallengePrizesField extends Component {
8384
}
8485
</div>)}
8586
</div>
86-
{!readOnly && (prize.value === '' || (length > 1 && +prize.value === 0)) && <div className={styles.row}>
87-
<div className={cn(styles.field, styles.col1)} />
88-
<div className={cn(styles.field, styles.col2, styles.error)}>
89-
{prize.value === ''
90-
? 'Prize amount is required field'
91-
: 'Prize amount must be more than zero'}
87+
{!readOnly && challenge.submitTriggered && (prize.value === '' || (+prize.value <= 0 || +prize.value > 1000000)) && (
88+
<div className={styles.row}>
89+
<div className={cn(styles.field, styles.col1)} />
90+
<div className={cn(styles.field, styles.col2, styles.error)}>
91+
{prize.value === ''
92+
? 'Prize amount is required field'
93+
: 'Prize amount must be more than 0 and no more than 1000000'}
94+
</div>
9295
</div>
93-
</div>}
96+
)}
9497
</div>
9598
))
9699
}
97100

98101
render () {
99-
const { readOnly } = this.props
102+
const { readOnly, challenge } = this.props
103+
const isTask = _.get(challenge, 'task.isTask', false)
100104
return (
101105
<div className={styles.container}>
102106
<div className={styles.row}>
@@ -105,7 +109,7 @@ class ChallengePrizesField extends Component {
105109
</div>
106110
</div>
107111
{ this.renderPrizes() }
108-
{!readOnly && (<div className={styles.button} onClick={this.addNewPrize}>
112+
{!readOnly && !isTask && (<div className={styles.button} onClick={this.addNewPrize}>
109113
<PrimaryButton text={'Add New Prize'} type={'info'} />
110114
</div>)}
111115
</div>

0 commit comments

Comments
 (0)