Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ workflows:
context: org-global
filters: &filters-dev
branches:
only: ["develop", "pm-2917"]
only: ["develop", "pm-2917", "points"]

# Production builds are exectuted only on tagged commits to the
# master branch.
Expand Down
1,712 changes: 653 additions & 1,059 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,28 @@
height: 40px;
align-self: flex-start;
}

.prizeTypeToggle {
display: flex;
flex-direction: row;
gap: 10px;
}

.prizeTypeButton {
@include roboto();

border: 1px solid $tc-gray-30;
border-radius: 4px;
background: $tc-gray-00;
color: $tc-gray-80;
cursor: pointer;
height: 32px;
min-width: 80px;
padding: 4px 12px;
}

.active {
background: $tc-blue-30;
border-color: $tc-blue-30;
color: $tc-gray-00;
}
131 changes: 112 additions & 19 deletions src/components/ChallengeEditor/ChallengePrizes-Field/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import PropTypes from 'prop-types'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faTrash } from '@fortawesome/free-solid-svg-icons'
import PrizeInput from '../../PrizeInput'
import ConfirmationModal from '../../Modal/ConfirmationModal'

import styles from './ChallengePrizes-Field.module.scss'
import cn from 'classnames'
Expand All @@ -15,48 +16,59 @@ import {
CHALLENGE_TYPES_WITH_MULTIPLE_PRIZES
} from '../../../config/constants'
import { validateValue } from '../../../util/input-check'
import { applyPrizeTypeToPrizeSets, getPrizeType } from '../../../util/prize'

class ChallengePrizesField extends Component {
constructor (props) {
super(props)
this.state = {
currentPrizeIndex: -1
currentPrizeIndex: -1,
pendingPrizeType: null,
showPointsConfirmation: false
}
this.renderPrizes = this.renderPrizes.bind(this)
this.addNewPrize = this.addNewPrize.bind(this)
this.removePrize = this.removePrize.bind(this)
this.getChallengePrize = this.getChallengePrize.bind(this)
this.onUpdateInput = this.onUpdateInput.bind(this)
this.getCurrentPrizeType = this.getCurrentPrizeType.bind(this)
this.onSelectPrizeType = this.onSelectPrizeType.bind(this)
this.onConfirmPoints = this.onConfirmPoints.bind(this)
this.onCancelPoints = this.onCancelPoints.bind(this)
this.onRequestPrizeType = this.onRequestPrizeType.bind(this)
}

addNewPrize () {
const challengePrize = this.getChallengePrize()
const prizeType = this.getCurrentPrizeType()
const challengePrize = this.getChallengePrize(prizeType)
challengePrize.prizes = [
...challengePrize.prizes,
{ type: CHALLENGE_PRIZE_TYPE.USD, value: 1 }
{ type: prizeType, value: 1 }
]
this.onUpdateValue(challengePrize)
this.onUpdateValue(challengePrize, prizeType)
}

removePrize (index) {
const challengePrize = this.getChallengePrize()
const prizeType = this.getCurrentPrizeType()
const challengePrize = this.getChallengePrize(prizeType)
challengePrize.prizes.splice(index, 1)
this.onUpdateValue(challengePrize)
this.onUpdateValue(challengePrize, prizeType)
}

onUpdateInput (value, index) {
const challengePrize = this.getChallengePrize()
const prizeType = this.getCurrentPrizeType()
const challengePrize = this.getChallengePrize(prizeType)
challengePrize.prizes[index].value = validateValue(
value,
VALIDATION_VALUE_TYPE.INTEGER
)
if (parseInt(challengePrize.prizes[index].value) > 1000000) {
challengePrize.prizes[index].value = '1000000'
}
this.onUpdateValue(challengePrize)
this.onUpdateValue(challengePrize, prizeType)
}

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

onUpdateOthers({
field: 'prizeSets',
value: [...existingPrizes, challengePrize]
value: applyPrizeTypeToPrizeSets(
[...existingPrizes, challengePrize],
prizeType
)
})
}

getChallengePrize () {
getChallengePrize (prizeType = this.getCurrentPrizeType()) {
const type = PRIZE_SETS_TYPE.CHALLENGE_PRIZES
return (
const existingPrizeSet =
(this.props.challenge.prizeSets &&
this.props.challenge.prizeSets.length &&
this.props.challenge.prizeSets.find(p => p.type === type)) || {
this.props.challenge.prizeSets.find(p => p.type === type)) || null

if (existingPrizeSet) {
return _.cloneDeep(existingPrizeSet)
}

return (
{
type,
prizes: [{ type: CHALLENGE_PRIZE_TYPE.USD, value: 0 }]
prizes: [{ type: prizeType, value: 0 }]
}
)
}

renderPrizes () {
getCurrentPrizeType () {
return getPrizeType(this.props.challenge.prizeSets)
}

onSelectPrizeType (prizeType) {
const challengePrize = this.getChallengePrize(prizeType)
challengePrize.prizes = challengePrize.prizes.map(prize => ({
...prize,
type: prizeType
}))
this.onUpdateValue(challengePrize, prizeType)
}

onRequestPrizeType (prizeType) {
const currentPrizeType = this.getCurrentPrizeType()
if (prizeType === currentPrizeType) return

if (prizeType === CHALLENGE_PRIZE_TYPE.POINT) {
this.setState({ pendingPrizeType: prizeType, showPointsConfirmation: true })
return
}
this.onSelectPrizeType(prizeType)
}

onConfirmPoints () {
const prizeType = this.state.pendingPrizeType || CHALLENGE_PRIZE_TYPE.POINT
this.setState(
{ showPointsConfirmation: false, pendingPrizeType: null },
() => this.onSelectPrizeType(prizeType)
)
}

onCancelPoints () {
this.setState({ showPointsConfirmation: false, pendingPrizeType: null })
}

renderPrizes (prizeType) {
const { currentPrizeIndex } = this.state
const { readOnly, challenge } = this.props
const typeName = typeof challenge.type === 'string' ? challenge.type : (challenge.type && challenge.type.name)
const allowMultiplePrizes = _.includes(
CHALLENGE_TYPES_WITH_MULTIPLE_PRIZES,
typeName
)
const challengePrize = this.getChallengePrize(prizeType)
return _.map(
this.getChallengePrize().prizes,
challengePrize.prizes,
(prize, index, { length }) => {
let errMessage = ''
if (prize.value === '') {
Expand All @@ -99,12 +158,14 @@ class ChallengePrizesField extends Component {
errMessage =
'Prize amount must be more than 0 and no more than 1000000'
} else if (index > 0) {
const prePrize = this.getChallengePrize().prizes[index - 1]
const prePrize = challengePrize.prizes[index - 1]
if (+prePrize.value < +prize.value) {
errMessage =
'Prize for the higher place cannot be bigger than for lower place'
}
}
const displayPrizeType = prize.type || prizeType
const symbol = displayPrizeType === CHALLENGE_PRIZE_TYPE.POINT ? 'Pts' : '$'
return (
<div key={`${index}-${prize.amount}-edit`}>
<div className={styles.row}>
Expand All @@ -115,7 +176,7 @@ class ChallengePrizesField extends Component {
</label>
</div>
{readOnly ? (
<span>${prize.value}</span>
<span>{symbol}{symbol === '$' ? '' : ' '}{prize.value}</span>
) : (
<div className={cn(styles.field, styles.col2)}>
<PrizeInput
Expand All @@ -124,6 +185,7 @@ class ChallengePrizesField extends Component {
onUpdateInput={this.onUpdateInput}
index={index}
activeIndex={currentPrizeIndex}
prizeType={prizeType}
/>
{index > 0 && (
<div
Expand Down Expand Up @@ -157,19 +219,50 @@ class ChallengePrizesField extends Component {
CHALLENGE_TYPES_WITH_MULTIPLE_PRIZES,
typeName
)
const prizeType = this.getCurrentPrizeType()
return (
<div className={styles.container}>
<div className={styles.row}>
<div className={cn(styles.field, styles.col1)}>
<label htmlFor={`challengePrizes`}>Challenge Prizes :</label>
</div>
{!readOnly && (
<div className={cn(styles.field, styles.col2)}>
<div className={styles.prizeTypeToggle}>
<button
type='button'
className={cn(styles.prizeTypeButton, prizeType === CHALLENGE_PRIZE_TYPE.USD && styles.active)}
onClick={() => this.onRequestPrizeType(CHALLENGE_PRIZE_TYPE.USD)}
>
$ USD
</button>
<button
type='button'
className={cn(styles.prizeTypeButton, prizeType === CHALLENGE_PRIZE_TYPE.POINT && styles.active)}
onClick={() => this.onRequestPrizeType(CHALLENGE_PRIZE_TYPE.POINT)}
>
Points
</button>
</div>
</div>
)}
</div>
{this.renderPrizes()}
{this.renderPrizes(prizeType)}
{!readOnly && allowMultiplePrizes && (
<div className={styles.button} onClick={this.addNewPrize}>
<PrimaryButton text={'Add New Prize'} type={'info'} />
</div>
)}
{this.state.showPointsConfirmation && (
<ConfirmationModal
title='Confirm Points Prize'
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.'
onCancel={this.onCancelPoints}
onConfirm={this.onConfirmPoints}
confirmText='Confirm'
cancelText='Cancel'
/>
)}
</div>
)
}
Expand Down
35 changes: 20 additions & 15 deletions src/components/ChallengeEditor/ChallengeReviewer-Field/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import cn from 'classnames'
import { PrimaryButton, OutlineButton } from '../../Buttons'
import { REVIEW_OPPORTUNITY_TYPE_LABELS, REVIEW_OPPORTUNITY_TYPES, VALIDATION_VALUE_TYPE, MARATHON_TYPE_ID, DES_TRACK_ID } from '../../../config/constants'
import { REVIEW_OPPORTUNITY_TYPE_LABELS, REVIEW_OPPORTUNITY_TYPES, VALIDATION_VALUE_TYPE, MARATHON_TYPE_ID, DES_TRACK_ID, CHALLENGE_PRIZE_TYPE } from '../../../config/constants'
import { loadScorecards, loadDefaultReviewers, loadWorkflows, replaceResourceInRole, createResource, deleteResource } from '../../../actions/challenges'
import styles from './ChallengeReviewer-Field.module.scss'
import { validateValue } from '../../../util/input-check'
import AssignedMemberField from '../AssignedMember-Field'
import { getResourceRoleByName } from '../../../util/tc'
import { isEqual } from 'lodash'
import { getPrizeType } from '../../../util/prize'

const ResourceToPhaseNameMap = {
Reviewer: 'Review',
Expand Down Expand Up @@ -959,7 +960,8 @@ class ChallengeReviewerField extends Component {
}

getFirstPlacePrizeValue (challenge) {
const placementPrizeSet = challenge.prizeSets.find(set => set.type === 'PLACEMENT')
const prizeSets = challenge.prizeSets || []
const placementPrizeSet = prizeSets.find(set => set.type === 'PLACEMENT')
if (placementPrizeSet && placementPrizeSet.prizes && placementPrizeSet.prizes[0] && placementPrizeSet.prizes[0].value) {
return placementPrizeSet.prizes[0].value
}
Expand All @@ -971,20 +973,23 @@ class ChallengeReviewerField extends Component {
const { error } = this.state
const { scorecards = [], defaultReviewers = [], workflows = [] } = metadata
const reviewers = challenge.reviewers || []
const firstPlacePrize = this.getFirstPlacePrizeValue(challenge)
const prizeType = getPrizeType(challenge.prizeSets)
const firstPlacePrize = prizeType === CHALLENGE_PRIZE_TYPE.POINT ? 0 : this.getFirstPlacePrizeValue(challenge)
const estimatedSubmissionsCount = 2 // Estimate assumes two submissions
const reviewersCost = reviewers
.filter((r) => !this.isAIReviewer(r))
.reduce((sum, r) => {
const fixedAmount = parseFloat(r.fixedAmount || 0)
const baseCoefficient = parseFloat(r.baseCoefficient || 0)
const incrementalCoefficient = parseFloat(r.incrementalCoefficient || 0)
const reviewerCost = fixedAmount + (baseCoefficient + incrementalCoefficient * estimatedSubmissionsCount) * firstPlacePrize

const count = parseInt(r.memberReviewerCount) || 1
return sum + reviewerCost * count
}, 0)
.toFixed(2)
const reviewersCost = prizeType === CHALLENGE_PRIZE_TYPE.POINT
? '0.00'
: reviewers
.filter((r) => !this.isAIReviewer(r))
.reduce((sum, r) => {
const fixedAmount = parseFloat(r.fixedAmount || 0)
const baseCoefficient = parseFloat(r.baseCoefficient || 0)
const incrementalCoefficient = parseFloat(r.incrementalCoefficient || 0)
const reviewerCost = fixedAmount + (baseCoefficient + incrementalCoefficient * estimatedSubmissionsCount) * firstPlacePrize

const count = parseInt(r.memberReviewerCount) || 1
return sum + reviewerCost * count
}, 0)
.toFixed(2)

if (isLoading) {
return (
Expand Down
18 changes: 14 additions & 4 deletions src/components/ChallengeEditor/ChallengeTotal-Field/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,27 @@ import PropTypes from 'prop-types'
import styles from './ChallengeTotal-Field.module.scss'
import cn from 'classnames'
import { convertDollarToInteger } from '../../../util/input-check'
import { CHALLENGE_PRIZE_TYPE, PRIZE_SETS_TYPE } from '../../../config/constants'
import { getPrizeType } from '../../../util/prize'

const ChallengeTotalField = ({ challenge }) => {
let challengeTotal = null
if (challenge.prizeSets) {
challengeTotal = _.flatten(challenge.prizeSets.map(p => p.prizes))
const prizeSets = challenge.prizeSets || []
const prizeType = getPrizeType(prizeSets)
const prizeSetsForTotal = prizeType === CHALLENGE_PRIZE_TYPE.POINT
? prizeSets.filter(p => p.type === PRIZE_SETS_TYPE.COPILOT_PAYMENT)
: prizeSets

if (prizeSetsForTotal.length) {
challengeTotal = _.flatten(prizeSetsForTotal.map(p => p.prizes))
.map(p => p.value)
.map(v => convertDollarToInteger(v, '$'))
.reduce((prev, next) => prev + next, 0)
}
const placementPrizeSet = challenge.prizeSets.find(set => set.type === 'PLACEMENT')
const firstPlacePrize = (placementPrizeSet && placementPrizeSet.prizes && placementPrizeSet.prizes[0] && placementPrizeSet.prizes[0].value) || 0
const placementPrizeSet = prizeSets.find(set => set.type === PRIZE_SETS_TYPE.CHALLENGE_PRIZES)
const firstPlacePrize = prizeType === CHALLENGE_PRIZE_TYPE.POINT
? 0
: (placementPrizeSet && placementPrizeSet.prizes && placementPrizeSet.prizes[0] && placementPrizeSet.prizes[0].value) || 0
let reviewerTotal = 0
if (challenge.reviewers && Array.isArray(challenge.reviewers)) {
reviewerTotal = challenge.reviewers
Expand Down
Loading
Loading