Skip to content

Commit db99a15

Browse files
authored
Merge pull request #1713 from topcoder-platform/points
Support for points as a prize option
2 parents 15eb792 + 3023780 commit db99a15

File tree

13 files changed

+892
-1110
lines changed

13 files changed

+892
-1110
lines changed

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ workflows:
160160
context: org-global
161161
filters: &filters-dev
162162
branches:
163-
only: ["develop", "pm-2917"]
163+
only: ["develop", "pm-2917", "points"]
164164

165165
# Production builds are exectuted only on tagged commits to the
166166
# master branch.

pnpm-lock.yaml

Lines changed: 653 additions & 1059 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/ChallengeEditor/ChallengePrizes-Field/ChallengePrizes-Field.module.scss

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,28 @@
121121
height: 40px;
122122
align-self: flex-start;
123123
}
124+
125+
.prizeTypeToggle {
126+
display: flex;
127+
flex-direction: row;
128+
gap: 10px;
129+
}
130+
131+
.prizeTypeButton {
132+
@include roboto();
133+
134+
border: 1px solid $tc-gray-30;
135+
border-radius: 4px;
136+
background: $tc-gray-00;
137+
color: $tc-gray-80;
138+
cursor: pointer;
139+
height: 32px;
140+
min-width: 80px;
141+
padding: 4px 12px;
142+
}
143+
144+
.active {
145+
background: $tc-blue-30;
146+
border-color: $tc-blue-30;
147+
color: $tc-gray-00;
148+
}

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

Lines changed: 112 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import PropTypes from 'prop-types'
44
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
55
import { faTrash } from '@fortawesome/free-solid-svg-icons'
66
import PrizeInput from '../../PrizeInput'
7+
import ConfirmationModal from '../../Modal/ConfirmationModal'
78

89
import styles from './ChallengePrizes-Field.module.scss'
910
import cn from 'classnames'
@@ -15,48 +16,59 @@ import {
1516
CHALLENGE_TYPES_WITH_MULTIPLE_PRIZES
1617
} from '../../../config/constants'
1718
import { validateValue } from '../../../util/input-check'
19+
import { applyPrizeTypeToPrizeSets, getPrizeType } from '../../../util/prize'
1820

1921
class ChallengePrizesField extends Component {
2022
constructor (props) {
2123
super(props)
2224
this.state = {
23-
currentPrizeIndex: -1
25+
currentPrizeIndex: -1,
26+
pendingPrizeType: null,
27+
showPointsConfirmation: false
2428
}
2529
this.renderPrizes = this.renderPrizes.bind(this)
2630
this.addNewPrize = this.addNewPrize.bind(this)
2731
this.removePrize = this.removePrize.bind(this)
2832
this.getChallengePrize = this.getChallengePrize.bind(this)
2933
this.onUpdateInput = this.onUpdateInput.bind(this)
34+
this.getCurrentPrizeType = this.getCurrentPrizeType.bind(this)
35+
this.onSelectPrizeType = this.onSelectPrizeType.bind(this)
36+
this.onConfirmPoints = this.onConfirmPoints.bind(this)
37+
this.onCancelPoints = this.onCancelPoints.bind(this)
38+
this.onRequestPrizeType = this.onRequestPrizeType.bind(this)
3039
}
3140

3241
addNewPrize () {
33-
const challengePrize = this.getChallengePrize()
42+
const prizeType = this.getCurrentPrizeType()
43+
const challengePrize = this.getChallengePrize(prizeType)
3444
challengePrize.prizes = [
3545
...challengePrize.prizes,
36-
{ type: CHALLENGE_PRIZE_TYPE.USD, value: 1 }
46+
{ type: prizeType, value: 1 }
3747
]
38-
this.onUpdateValue(challengePrize)
48+
this.onUpdateValue(challengePrize, prizeType)
3949
}
4050

4151
removePrize (index) {
42-
const challengePrize = this.getChallengePrize()
52+
const prizeType = this.getCurrentPrizeType()
53+
const challengePrize = this.getChallengePrize(prizeType)
4354
challengePrize.prizes.splice(index, 1)
44-
this.onUpdateValue(challengePrize)
55+
this.onUpdateValue(challengePrize, prizeType)
4556
}
4657

4758
onUpdateInput (value, index) {
48-
const challengePrize = this.getChallengePrize()
59+
const prizeType = this.getCurrentPrizeType()
60+
const challengePrize = this.getChallengePrize(prizeType)
4961
challengePrize.prizes[index].value = validateValue(
5062
value,
5163
VALIDATION_VALUE_TYPE.INTEGER
5264
)
5365
if (parseInt(challengePrize.prizes[index].value) > 1000000) {
5466
challengePrize.prizes[index].value = '1000000'
5567
}
56-
this.onUpdateValue(challengePrize)
68+
this.onUpdateValue(challengePrize, prizeType)
5769
}
5870

59-
onUpdateValue (challengePrize) {
71+
onUpdateValue (challengePrize, prizeType = this.getCurrentPrizeType()) {
6072
const type = PRIZE_SETS_TYPE.CHALLENGE_PRIZES
6173
const { onUpdateOthers, challenge } = this.props
6274
const existingPrizes = challenge.prizeSets
@@ -65,32 +77,79 @@ class ChallengePrizesField extends Component {
6577

6678
onUpdateOthers({
6779
field: 'prizeSets',
68-
value: [...existingPrizes, challengePrize]
80+
value: applyPrizeTypeToPrizeSets(
81+
[...existingPrizes, challengePrize],
82+
prizeType
83+
)
6984
})
7085
}
7186

72-
getChallengePrize () {
87+
getChallengePrize (prizeType = this.getCurrentPrizeType()) {
7388
const type = PRIZE_SETS_TYPE.CHALLENGE_PRIZES
74-
return (
89+
const existingPrizeSet =
7590
(this.props.challenge.prizeSets &&
7691
this.props.challenge.prizeSets.length &&
77-
this.props.challenge.prizeSets.find(p => p.type === type)) || {
92+
this.props.challenge.prizeSets.find(p => p.type === type)) || null
93+
94+
if (existingPrizeSet) {
95+
return _.cloneDeep(existingPrizeSet)
96+
}
97+
98+
return (
99+
{
78100
type,
79-
prizes: [{ type: CHALLENGE_PRIZE_TYPE.USD, value: 0 }]
101+
prizes: [{ type: prizeType, value: 0 }]
80102
}
81103
)
82104
}
83105

84-
renderPrizes () {
106+
getCurrentPrizeType () {
107+
return getPrizeType(this.props.challenge.prizeSets)
108+
}
109+
110+
onSelectPrizeType (prizeType) {
111+
const challengePrize = this.getChallengePrize(prizeType)
112+
challengePrize.prizes = challengePrize.prizes.map(prize => ({
113+
...prize,
114+
type: prizeType
115+
}))
116+
this.onUpdateValue(challengePrize, prizeType)
117+
}
118+
119+
onRequestPrizeType (prizeType) {
120+
const currentPrizeType = this.getCurrentPrizeType()
121+
if (prizeType === currentPrizeType) return
122+
123+
if (prizeType === CHALLENGE_PRIZE_TYPE.POINT) {
124+
this.setState({ pendingPrizeType: prizeType, showPointsConfirmation: true })
125+
return
126+
}
127+
this.onSelectPrizeType(prizeType)
128+
}
129+
130+
onConfirmPoints () {
131+
const prizeType = this.state.pendingPrizeType || CHALLENGE_PRIZE_TYPE.POINT
132+
this.setState(
133+
{ showPointsConfirmation: false, pendingPrizeType: null },
134+
() => this.onSelectPrizeType(prizeType)
135+
)
136+
}
137+
138+
onCancelPoints () {
139+
this.setState({ showPointsConfirmation: false, pendingPrizeType: null })
140+
}
141+
142+
renderPrizes (prizeType) {
85143
const { currentPrizeIndex } = this.state
86144
const { readOnly, challenge } = this.props
87145
const typeName = typeof challenge.type === 'string' ? challenge.type : (challenge.type && challenge.type.name)
88146
const allowMultiplePrizes = _.includes(
89147
CHALLENGE_TYPES_WITH_MULTIPLE_PRIZES,
90148
typeName
91149
)
150+
const challengePrize = this.getChallengePrize(prizeType)
92151
return _.map(
93-
this.getChallengePrize().prizes,
152+
challengePrize.prizes,
94153
(prize, index, { length }) => {
95154
let errMessage = ''
96155
if (prize.value === '') {
@@ -99,12 +158,14 @@ class ChallengePrizesField extends Component {
99158
errMessage =
100159
'Prize amount must be more than 0 and no more than 1000000'
101160
} else if (index > 0) {
102-
const prePrize = this.getChallengePrize().prizes[index - 1]
161+
const prePrize = challengePrize.prizes[index - 1]
103162
if (+prePrize.value < +prize.value) {
104163
errMessage =
105164
'Prize for the higher place cannot be bigger than for lower place'
106165
}
107166
}
167+
const displayPrizeType = prize.type || prizeType
168+
const symbol = displayPrizeType === CHALLENGE_PRIZE_TYPE.POINT ? 'Pts' : '$'
108169
return (
109170
<div key={`${index}-${prize.amount}-edit`}>
110171
<div className={styles.row}>
@@ -115,7 +176,7 @@ class ChallengePrizesField extends Component {
115176
</label>
116177
</div>
117178
{readOnly ? (
118-
<span>${prize.value}</span>
179+
<span>{symbol}{symbol === '$' ? '' : ' '}{prize.value}</span>
119180
) : (
120181
<div className={cn(styles.field, styles.col2)}>
121182
<PrizeInput
@@ -124,6 +185,7 @@ class ChallengePrizesField extends Component {
124185
onUpdateInput={this.onUpdateInput}
125186
index={index}
126187
activeIndex={currentPrizeIndex}
188+
prizeType={prizeType}
127189
/>
128190
{index > 0 && (
129191
<div
@@ -157,19 +219,50 @@ class ChallengePrizesField extends Component {
157219
CHALLENGE_TYPES_WITH_MULTIPLE_PRIZES,
158220
typeName
159221
)
222+
const prizeType = this.getCurrentPrizeType()
160223
return (
161224
<div className={styles.container}>
162225
<div className={styles.row}>
163226
<div className={cn(styles.field, styles.col1)}>
164227
<label htmlFor={`challengePrizes`}>Challenge Prizes :</label>
165228
</div>
229+
{!readOnly && (
230+
<div className={cn(styles.field, styles.col2)}>
231+
<div className={styles.prizeTypeToggle}>
232+
<button
233+
type='button'
234+
className={cn(styles.prizeTypeButton, prizeType === CHALLENGE_PRIZE_TYPE.USD && styles.active)}
235+
onClick={() => this.onRequestPrizeType(CHALLENGE_PRIZE_TYPE.USD)}
236+
>
237+
$ USD
238+
</button>
239+
<button
240+
type='button'
241+
className={cn(styles.prizeTypeButton, prizeType === CHALLENGE_PRIZE_TYPE.POINT && styles.active)}
242+
onClick={() => this.onRequestPrizeType(CHALLENGE_PRIZE_TYPE.POINT)}
243+
>
244+
Points
245+
</button>
246+
</div>
247+
</div>
248+
)}
166249
</div>
167-
{this.renderPrizes()}
250+
{this.renderPrizes(prizeType)}
168251
{!readOnly && allowMultiplePrizes && (
169252
<div className={styles.button} onClick={this.addNewPrize}>
170253
<PrimaryButton text={'Add New Prize'} type={'info'} />
171254
</div>
172255
)}
256+
{this.state.showPointsConfirmation && (
257+
<ConfirmationModal
258+
title='Confirm Points Prize'
259+
message='You have selected POINTS as a payment for this challenge. Please be aware that POINTS are only approved for Wipro internal challenges and fun challenges. POINTS are not acceptable for customer work.'
260+
onCancel={this.onCancelPoints}
261+
onConfirm={this.onConfirmPoints}
262+
confirmText='Confirm'
263+
cancelText='Cancel'
264+
/>
265+
)}
173266
</div>
174267
)
175268
}

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

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ import PropTypes from 'prop-types'
33
import { connect } from 'react-redux'
44
import cn from 'classnames'
55
import { PrimaryButton, OutlineButton } from '../../Buttons'
6-
import { REVIEW_OPPORTUNITY_TYPE_LABELS, REVIEW_OPPORTUNITY_TYPES, VALIDATION_VALUE_TYPE, MARATHON_TYPE_ID, DES_TRACK_ID } from '../../../config/constants'
6+
import { REVIEW_OPPORTUNITY_TYPE_LABELS, REVIEW_OPPORTUNITY_TYPES, VALIDATION_VALUE_TYPE, MARATHON_TYPE_ID, DES_TRACK_ID, CHALLENGE_PRIZE_TYPE } from '../../../config/constants'
77
import { loadScorecards, loadDefaultReviewers, loadWorkflows, replaceResourceInRole, createResource, deleteResource } from '../../../actions/challenges'
88
import styles from './ChallengeReviewer-Field.module.scss'
99
import { validateValue } from '../../../util/input-check'
1010
import AssignedMemberField from '../AssignedMember-Field'
1111
import { getResourceRoleByName } from '../../../util/tc'
1212
import { isEqual } from 'lodash'
13+
import { getPrizeType } from '../../../util/prize'
1314

1415
const ResourceToPhaseNameMap = {
1516
Reviewer: 'Review',
@@ -959,7 +960,8 @@ class ChallengeReviewerField extends Component {
959960
}
960961

961962
getFirstPlacePrizeValue (challenge) {
962-
const placementPrizeSet = challenge.prizeSets.find(set => set.type === 'PLACEMENT')
963+
const prizeSets = challenge.prizeSets || []
964+
const placementPrizeSet = prizeSets.find(set => set.type === 'PLACEMENT')
963965
if (placementPrizeSet && placementPrizeSet.prizes && placementPrizeSet.prizes[0] && placementPrizeSet.prizes[0].value) {
964966
return placementPrizeSet.prizes[0].value
965967
}
@@ -971,20 +973,23 @@ class ChallengeReviewerField extends Component {
971973
const { error } = this.state
972974
const { scorecards = [], defaultReviewers = [], workflows = [] } = metadata
973975
const reviewers = challenge.reviewers || []
974-
const firstPlacePrize = this.getFirstPlacePrizeValue(challenge)
976+
const prizeType = getPrizeType(challenge.prizeSets)
977+
const firstPlacePrize = prizeType === CHALLENGE_PRIZE_TYPE.POINT ? 0 : this.getFirstPlacePrizeValue(challenge)
975978
const estimatedSubmissionsCount = 2 // Estimate assumes two submissions
976-
const reviewersCost = reviewers
977-
.filter((r) => !this.isAIReviewer(r))
978-
.reduce((sum, r) => {
979-
const fixedAmount = parseFloat(r.fixedAmount || 0)
980-
const baseCoefficient = parseFloat(r.baseCoefficient || 0)
981-
const incrementalCoefficient = parseFloat(r.incrementalCoefficient || 0)
982-
const reviewerCost = fixedAmount + (baseCoefficient + incrementalCoefficient * estimatedSubmissionsCount) * firstPlacePrize
983-
984-
const count = parseInt(r.memberReviewerCount) || 1
985-
return sum + reviewerCost * count
986-
}, 0)
987-
.toFixed(2)
979+
const reviewersCost = prizeType === CHALLENGE_PRIZE_TYPE.POINT
980+
? '0.00'
981+
: reviewers
982+
.filter((r) => !this.isAIReviewer(r))
983+
.reduce((sum, r) => {
984+
const fixedAmount = parseFloat(r.fixedAmount || 0)
985+
const baseCoefficient = parseFloat(r.baseCoefficient || 0)
986+
const incrementalCoefficient = parseFloat(r.incrementalCoefficient || 0)
987+
const reviewerCost = fixedAmount + (baseCoefficient + incrementalCoefficient * estimatedSubmissionsCount) * firstPlacePrize
988+
989+
const count = parseInt(r.memberReviewerCount) || 1
990+
return sum + reviewerCost * count
991+
}, 0)
992+
.toFixed(2)
988993

989994
if (isLoading) {
990995
return (

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,27 @@ import PropTypes from 'prop-types'
44
import styles from './ChallengeTotal-Field.module.scss'
55
import cn from 'classnames'
66
import { convertDollarToInteger } from '../../../util/input-check'
7+
import { CHALLENGE_PRIZE_TYPE, PRIZE_SETS_TYPE } from '../../../config/constants'
8+
import { getPrizeType } from '../../../util/prize'
79

810
const ChallengeTotalField = ({ challenge }) => {
911
let challengeTotal = null
10-
if (challenge.prizeSets) {
11-
challengeTotal = _.flatten(challenge.prizeSets.map(p => p.prizes))
12+
const prizeSets = challenge.prizeSets || []
13+
const prizeType = getPrizeType(prizeSets)
14+
const prizeSetsForTotal = prizeType === CHALLENGE_PRIZE_TYPE.POINT
15+
? prizeSets.filter(p => p.type === PRIZE_SETS_TYPE.COPILOT_PAYMENT)
16+
: prizeSets
17+
18+
if (prizeSetsForTotal.length) {
19+
challengeTotal = _.flatten(prizeSetsForTotal.map(p => p.prizes))
1220
.map(p => p.value)
1321
.map(v => convertDollarToInteger(v, '$'))
1422
.reduce((prev, next) => prev + next, 0)
1523
}
16-
const placementPrizeSet = challenge.prizeSets.find(set => set.type === 'PLACEMENT')
17-
const firstPlacePrize = (placementPrizeSet && placementPrizeSet.prizes && placementPrizeSet.prizes[0] && placementPrizeSet.prizes[0].value) || 0
24+
const placementPrizeSet = prizeSets.find(set => set.type === PRIZE_SETS_TYPE.CHALLENGE_PRIZES)
25+
const firstPlacePrize = prizeType === CHALLENGE_PRIZE_TYPE.POINT
26+
? 0
27+
: (placementPrizeSet && placementPrizeSet.prizes && placementPrizeSet.prizes[0] && placementPrizeSet.prizes[0].value) || 0
1828
let reviewerTotal = 0
1929
if (challenge.reviewers && Array.isArray(challenge.reviewers)) {
2030
reviewerTotal = challenge.reviewers

0 commit comments

Comments
 (0)