@@ -584,6 +589,7 @@ ChallengeHeader.propTypes = {
platforms: PT.any,
tags: PT.any,
skills: PT.any,
+ funChallenge: PT.bool,
prizes: PT.any,
timelineTemplateId: PT.string,
reliabilityBonus: PT.any,
diff --git a/src/shared/components/challenge-detail/Header/style.scss b/src/shared/components/challenge-detail/Header/style.scss
index d7b79956a..d63b588ef 100644
--- a/src/shared/components/challenge-detail/Header/style.scss
+++ b/src/shared/components/challenge-detail/Header/style.scss
@@ -406,6 +406,22 @@
background-color: #d98f64;
}
}
+
+ .fun-challenge-prize {
+ @include roboto-bold;
+
+ color: #2a2a2a;
+ font-size: 24px;
+ font-weight: 500;
+ line-height: 32px;
+ margin: 0;
+
+ @include xs-to-md {
+ font-size: 14px;
+ line-height: 30px;
+ text-align: center;
+ }
+ }
}
.bonus-div {
diff --git a/src/shared/components/challenge-detail/Registrants/index.jsx b/src/shared/components/challenge-detail/Registrants/index.jsx
index 0e90622b4..9b24d8989 100644
--- a/src/shared/components/challenge-detail/Registrants/index.jsx
+++ b/src/shared/components/challenge-detail/Registrants/index.jsx
@@ -293,7 +293,7 @@ export default class Registrants extends React.Component {
prizeSets,
prizeSet => ((prizeSet && prizeSet.type) || '').toLowerCase() === 'placement',
);
- const { prizes } = placementPrizes || [];
+ const prizes = _.get(placementPrizes, 'prizes', []);
const checkpoints = challenge.checkpoints || [];
diff --git a/src/shared/components/challenge-detail/Winners/Winner/index.jsx b/src/shared/components/challenge-detail/Winners/Winner/index.jsx
index 7cb6a54cc..aa311f262 100644
--- a/src/shared/components/challenge-detail/Winners/Winner/index.jsx
+++ b/src/shared/components/challenge-detail/Winners/Winner/index.jsx
@@ -21,6 +21,7 @@ export default function Winner({
isMM,
isRDM,
prizes,
+ isFunChallenge,
submissions,
viewable,
winner,
@@ -52,12 +53,12 @@ export default function Winner({
prizeType = prizes[prizeIndex].type;
}
- // Handle point prizes on the winners display
+ // Hide prize text for fun challenges, as they do not have individual payouts.
let prizeText = '';
- if (prizeType === 'POINT') {
- prizeText = numberWithCommas(prize);
- } else {
- prizeText = `$${numberWithCommas(prize)}`;
+ if (!isFunChallenge) {
+ prizeText = prizeType === 'POINT'
+ ? numberWithCommas(prize)
+ : `$${numberWithCommas(prize)}`;
}
return (
@@ -157,6 +158,7 @@ export default function Winner({
Winner.defaultProps = {
prizes: [],
+ isFunChallenge: false,
};
Winner.propTypes = {
@@ -164,6 +166,7 @@ Winner.propTypes = {
isMM: PT.bool.isRequired,
isRDM: PT.bool.isRequired,
prizes: PT.arrayOf(PT.shape()),
+ isFunChallenge: PT.bool,
submissions: PT.arrayOf(PT.object).isRequired,
viewable: PT.bool.isRequired,
winner: PT.shape({
diff --git a/src/shared/components/challenge-detail/Winners/index.jsx b/src/shared/components/challenge-detail/Winners/index.jsx
index a29dca041..4499dd48c 100644
--- a/src/shared/components/challenge-detail/Winners/index.jsx
+++ b/src/shared/components/challenge-detail/Winners/index.jsx
@@ -18,6 +18,7 @@ const { getService } = services.submissions;
export default function Winners({
winners,
prizes,
+ isFunChallenge,
submissions,
viewable,
isDesign,
@@ -94,6 +95,7 @@ export default function Winners({
isRDM={isRDM}
key={`${w.handle}-${w.placement}`}
prizes={prizes}
+ isFunChallenge={isFunChallenge}
submissions={submissions}
viewable={viewable}
winner={w}
@@ -109,6 +111,7 @@ export default function Winners({
Winners.defaultProps = {
winners: [],
prizes: [],
+ isFunChallenge: false,
submissions: [],
viewable: false,
isDesign: false,
@@ -121,6 +124,7 @@ Winners.defaultProps = {
Winners.propTypes = {
winners: PT.arrayOf(PT.shape()),
prizes: PT.arrayOf(PT.shape()),
+ isFunChallenge: PT.bool,
submissions: PT.arrayOf(PT.shape()),
viewable: PT.bool,
isDesign: PT.bool,
diff --git a/src/shared/components/challenge-listing/ChallengeCard/index.jsx b/src/shared/components/challenge-listing/ChallengeCard/index.jsx
index 80803a771..cb5f0d539 100644
--- a/src/shared/components/challenge-listing/ChallengeCard/index.jsx
+++ b/src/shared/components/challenge-listing/ChallengeCard/index.jsx
@@ -163,7 +163,7 @@ ChallengeCard.propTypes = {
openChallengesInNewTabs: PT.bool,
sampleWinnerProfile: PT.shape(),
selectChallengeDetailsTab: PT.func.isRequired,
- userId: PT.number,
+ userId: PT.oneOfType([PT.number, PT.string]),
expandedTags: PT.arrayOf(PT.number),
expandTag: PT.func,
domRef: PT.func,
diff --git a/src/shared/components/challenge-listing/Listing/Bucket/index.jsx b/src/shared/components/challenge-listing/Listing/Bucket/index.jsx
index 7021ad799..76078acc4 100644
--- a/src/shared/components/challenge-listing/Listing/Bucket/index.jsx
+++ b/src/shared/components/challenge-listing/Listing/Bucket/index.jsx
@@ -91,7 +91,7 @@ export default function Bucket({
&& ch.task
&& ch.task.isTask
&& ch.task.isAssigned
- && Number(ch.task.memberId) !== Number(userId)) {
+ && `${ch.task.memberId}` !== `${userId}`) {
return null;
}
return ch;
@@ -313,7 +313,7 @@ Bucket.propTypes = {
setFilterState: PT.func.isRequired,
setSort: PT.func.isRequired,
sort: PT.string,
- userId: PT.number,
+ userId: PT.oneOfType([PT.number, PT.string]),
auth: PT.shape(),
expandedTags: PT.arrayOf(PT.number),
expandTag: PT.func,
diff --git a/src/shared/containers/ErrorMessage.jsx b/src/shared/containers/ErrorMessage.jsx
index 5fb3ac372..e28dc4eea 100644
--- a/src/shared/containers/ErrorMessage.jsx
+++ b/src/shared/containers/ErrorMessage.jsx
@@ -7,24 +7,95 @@
* be used directly.
*/
import { actions } from 'topcoder-react-lib';
+import { DangerButton, Modal } from 'topcoder-react-ui-kit';
import React from 'react';
import PT from 'prop-types';
import { connect } from 'react-redux';
-import { ErrorMessage } from 'topcoder-react-ui-kit';
+import style from './ErrorMessage.scss';
-function ErrorMessageContainer({ error, clearError }) {
- return (
-
- { error
- ? (
- clearError()}
- />
- ) : undefined }
-
- );
+const WIPRO_REGISTRATION_BLOCKED_MESSAGE = 'Wipro employees are not allowed to participate in this Topcoder challenge';
+const WIPRO_REGISTRATION_SUPPORT_MESSAGE = 'If you think this is an error, please contact support@topcoder.com';
+const SCROLLING_DISABLED_CLASS_NAME = 'scrolling-disabled-by-modal';
+
+/**
+ * Detects if the current error is the Wipro registration blocked popup.
+ * @param {Object} error Error payload.
+ * @return {Boolean}
+ */
+function isWiproRegistrationBlockedError(error) {
+ const title = error && error.title ? error.title : '';
+ return title.trim().toLowerCase() === WIPRO_REGISTRATION_BLOCKED_MESSAGE.toLowerCase();
+}
+
+class ErrorMessageContainer extends React.Component {
+ componentDidMount() {
+ const { error } = this.props;
+ if (error) {
+ document.body.classList.add(SCROLLING_DISABLED_CLASS_NAME);
+ }
+ }
+
+ componentDidUpdate(prevProps) {
+ const { error } = this.props;
+ if (!prevProps.error && error) {
+ document.body.classList.add(SCROLLING_DISABLED_CLASS_NAME);
+ }
+ if (prevProps.error && !error) {
+ document.body.classList.remove(SCROLLING_DISABLED_CLASS_NAME);
+ }
+ }
+
+ componentWillUnmount() {
+ document.body.classList.remove(SCROLLING_DISABLED_CLASS_NAME);
+ }
+
+ renderSupportMessage() {
+ const { error } = this.props;
+ if (isWiproRegistrationBlockedError(error)) {
+ return WIPRO_REGISTRATION_SUPPORT_MESSAGE;
+ }
+
+ return (
+
+ We are sorry that you have encountered this problem. Please, contact
+ {' '}
+ our support
+ {' '}
+ support@topcoder.com
+ {' '}
+ to help us resolve it as soon as possible.
+
+ );
+ }
+
+ render() {
+ const { error, clearError } = this.props;
+ const isWiproError = isWiproRegistrationBlockedError(error);
+
+ return (
+
+ {error ? (
+
+ {error.title}
+ {error.details && !isWiproError ? (
+ {error.details}
+ ) : null}
+
+ {this.renderSupportMessage()}
+
+ {
+ e.preventDefault();
+ clearError();
+ }}
+ >
+ OK
+
+
+ ) : undefined}
+
+ );
+ }
}
/**
@@ -41,7 +112,7 @@ ErrorMessageContainer.propTypes = {
clearError: PT.func.isRequired,
error: PT.shape({
title: PT.string.isRequired,
- details: PT.string.isRequired,
+ details: PT.string,
}),
};
diff --git a/src/shared/containers/ErrorMessage.scss b/src/shared/containers/ErrorMessage.scss
new file mode 100644
index 000000000..0fb5040d5
--- /dev/null
+++ b/src/shared/containers/ErrorMessage.scss
@@ -0,0 +1,51 @@
+@import '~styles/mixins';
+
+$sm-space-10: $base-unit * 2;
+$sm-space-15: $base-unit * 3;
+$sm-space-25: $base-unit * 5;
+$sm-space-40: $base-unit * 8;
+
+.container {
+ @include roboto-regular;
+
+ overflow: hidden;
+ padding: 8 * $base-unit;
+ text-align: center;
+
+ @include xs-to-sm {
+ padding: 40px 10px;
+ }
+}
+
+.details {
+ font-weight: 400;
+ font-size: 13px;
+ color: $tc-gray-60;
+ line-height: $sm-space-25;
+ padding: 0 $sm-space-15;
+ margin-bottom: $sm-space-10;
+ text-align: justify;
+
+ a {
+ color: $tc-dark-blue;
+ text-decoration: underline;
+ }
+}
+
+.wiproSupport {
+ text-align: center;
+}
+
+.title {
+ color: $tc-red;
+ font-size: 15px;
+ font-weight: bold;
+ line-height: $sm-space-25;
+ margin-bottom: $sm-space-10;
+ padding: 0 $sm-space-15;
+
+ .id {
+ color: #000;
+ font-weight: 500;
+ }
+}
diff --git a/src/shared/containers/challenge-detail/index.jsx b/src/shared/containers/challenge-detail/index.jsx
index 57a312467..3327d436b 100644
--- a/src/shared/containers/challenge-detail/index.jsx
+++ b/src/shared/containers/challenge-detail/index.jsx
@@ -52,7 +52,7 @@ import { hasOpenSubmissionPhase } from 'utils/challengePhases';
import { config } from 'topcoder-react-utils';
import MetaTags from 'components/MetaTags';
import { decodeToken } from '@topcoder-platform/tc-auth-lib';
-import { actions, services } from 'topcoder-react-lib';
+import { actions, errors, services } from 'topcoder-react-lib';
import { getService } from 'services/contentful';
import { getSubmissionArtifacts as getSubmissionArtifactsService } from 'services/submissions';
import getReviewSummationsService from 'services/reviewSummations';
@@ -98,6 +98,41 @@ import './styles.scss';
const MIN = 60 * 1000;
const DAY = 24 * 60 * MIN;
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
+const { fireErrorMessage } = errors;
+const WIPRO_REGISTRATION_BLOCKED_MESSAGE = 'Wipro employees are not allowed to participate in this Topcoder challenge';
+const WIPRO_REGISTRATION_SUPPORT_MESSAGE = 'If you think this is an error, please contact support@topcoder.com';
+
+/**
+ * Checks whether challenge registration should be blocked for Wipro members.
+ * @param {String} email User email.
+ * @param {Boolean} wiproAllowed Challenge-level flag.
+ * @return {Boolean}
+ */
+export function isWiproRegistrationBlocked(email, wiproAllowed) {
+ if (wiproAllowed !== false) return false;
+ return /@wipro\.com$/i.test(_.trim(email || ''));
+}
+
+/**
+ * Returns winners to display in challenge detail.
+ * For non-task challenges we only keep final winners, while supporting
+ * legacy winner type values like "Final".
+ *
+ * @param {Object} challenge Challenge details object.
+ * @returns {Array