diff --git a/package.json b/package.json index c3681e5a40..d7b3c3337c 100644 --- a/package.json +++ b/package.json @@ -153,8 +153,8 @@ "supertest": "^3.1.0", "tc-core-library-js": "github:appirio-tech/tc-core-library-js#v2.6.3", "tc-ui": "^1.0.12", - "topcoder-react-lib": "1.2.10", - "topcoder-react-ui-kit": "2.0.1", + "topcoder-react-lib": "1000.29.11", + "topcoder-react-ui-kit": "1000.1.2", "topcoder-react-utils": "0.7.8", "turndown": "^4.0.2", "url-parse": "^1.4.1", diff --git a/src/shared/components/ProfilePage/Awards/AwardBadge/index.jsx b/src/shared/components/ProfilePage/Awards/AwardBadge/index.jsx index 39a2a126eb..1903b27c2f 100644 --- a/src/shared/components/ProfilePage/Awards/AwardBadge/index.jsx +++ b/src/shared/components/ProfilePage/Awards/AwardBadge/index.jsx @@ -17,6 +17,7 @@ const AwardBadge = ({ }
+ {/* eslint-disable-next-line react/no-danger */}
diff --git a/src/shared/components/challenge-detail/Header/DeadlinesPanel/index.jsx b/src/shared/components/challenge-detail/Header/DeadlinesPanel/index.jsx index 7eb853efd1..081d958d84 100644 --- a/src/shared/components/challenge-detail/Header/DeadlinesPanel/index.jsx +++ b/src/shared/components/challenge-detail/Header/DeadlinesPanel/index.jsx @@ -35,7 +35,6 @@ export default function DeadlinesPanel({ deadlines }) { } } if (index === deadlines.length - 1) { - name = 'Winners Announced'; showRange = false; } diff --git a/src/shared/components/challenge-detail/Header/TabSelector/style.scss b/src/shared/components/challenge-detail/Header/TabSelector/style.scss index f684f57548..33a3460bc2 100644 --- a/src/shared/components/challenge-detail/Header/TabSelector/style.scss +++ b/src/shared/components/challenge-detail/Header/TabSelector/style.scss @@ -133,8 +133,8 @@ .challenge-view-selector { display: flex; flex-wrap: wrap; + border-radius: 4px 0; position: relative; - padding-bottom: 10px; border-bottom: silver solid 1px; gap: 16px; @@ -150,7 +150,7 @@ font-weight: 700; line-height: 20px; font-size: 14px; - margin: 10px 10px 0; + padding: 10px 16px; cursor: pointer; white-space: nowrap; position: relative; @@ -158,6 +158,7 @@ .challenge-selected-view { font-weight: 700 !important; + background: #bae1f9; color: #2a2a2a; &::after { @@ -169,8 +170,8 @@ z-index: 100; display: block; position: absolute; - top: 30px; - left: 20%; + top: 40px; + left: 30%; } } diff --git a/src/shared/components/challenge-detail/Header/index.jsx b/src/shared/components/challenge-detail/Header/index.jsx index 9898b37059..3cd0d3328f 100644 --- a/src/shared/components/challenge-detail/Header/index.jsx +++ b/src/shared/components/challenge-detail/Header/index.jsx @@ -17,6 +17,9 @@ import { PrimaryButton } from 'topcoder-react-ui-kit'; import { Link } from 'topcoder-react-utils'; import { COMPETITION_TRACKS } from 'utils/tc'; import { phaseEndDate } from 'utils/challenge-listing/helper'; +import { + getTimeLeft, +} from 'utils/challenge-detail/helper'; import LeftArrow from 'assets/images/arrow-prev.svg'; @@ -30,6 +33,10 @@ import TabSelector from './TabSelector'; import style from './style.scss'; +/* Holds day and hour range in ms. */ +const HOUR_MS = 60 * 60 * 1000; +const DAY_MS = 24 * HOUR_MS; + export default function ChallengeHeader(props) { const { isLoggedIn, @@ -108,6 +115,10 @@ export default function ChallengeHeader(props) { registrationEnded = !regPhase.isOpen; } + const currentPhases = challenge.phases + .filter(p => p.name !== 'Registration' && p.isOpen) + .sort((a, b) => moment(a.scheduledEndDate).diff(b.scheduledEndDate))[0]; + const trackLower = track ? track.replace(' ', '-').toLowerCase() : 'design'; const eventNames = (events || []).map((event => (event.eventName || '').toUpperCase())); @@ -128,6 +139,31 @@ export default function ChallengeHeader(props) { */ const hasSubmissions = !_.isEmpty(mySubmissions); + const openPhases = sortedAllPhases.filter(p => p.isOpen); + let nextPhase = openPhases[0]; + if (hasRegistered && openPhases[0] && openPhases[0].name === 'Registration') { + nextPhase = openPhases[1] || {}; + } + + const deadlineEnd = moment(nextPhase && phaseEndDate(nextPhase)); + const currentTime = moment(); + + const timeDiff = getTimeLeft(currentPhases, 'to go'); + + if (!timeDiff.late) { + timeDiff.text = timeDiff.text.replace('to go', ''); + } + + let timeLeft = deadlineEnd.isAfter(currentTime) + ? deadlineEnd.diff(currentTime) : 0; + + let format; + if (timeLeft > DAY_MS) format = 'D[d] H[h]'; + else if (timeLeft > HOUR_MS) format = 'H[h] m[min]'; + else format = 'm[min] s[s]'; + + timeLeft = moment.duration(timeLeft).format(format); + let relevantPhases = []; if (showDeadlineDetail) { @@ -177,31 +213,52 @@ export default function ChallengeHeader(props) { || phaseEndDate(p).getTime() < endPhaseDate)); relevantPhases.push({ id: -1, - name: 'Winners', + name: 'Winners Announced', isOpen: false, actualEndDate: endPhaseDate, scheduledEndDate: endPhaseDate, }); } else if (relevantPhases.length > 1) { - const lastPhase = relevantPhases[relevantPhases.length - 1]; - const lastPhaseTime = phaseEndDate(lastPhase).getTime(); - + // const lastPhase = relevantPhases[relevantPhases.length - 1]; + // const lastPhaseTime = phaseEndDate(lastPhase).getTime(); const appealsEndDate = phaseEndDate(sortedAllPhases[sortedAllPhases.length - 1]); - const appealsEnd = appealsEndDate.getTime(); - if (lastPhaseTime < appealsEnd) { - relevantPhases.push({ - id: -1, - name: 'Winners', - isOpen: false, - actualEndDate: appealsEndDate, - scheduledEndDate: appealsEndDate, - }); - } + // const appealsEnd = appealsEndDate.getTime(); + relevantPhases.push({ + id: -1, + name: 'Winners Announced', + isOpen: false, + actualEndDate: appealsEndDate, + scheduledEndDate: appealsEndDate, + }); } } const checkpointCount = checkpoints && checkpoints.numberOfPassedScreeningSubmissions; + let nextDeadlineMsg; + switch ((status || '').toLowerCase()) { + case 'completed': + nextDeadlineMsg = ( +
+ The challenge is finished. +
+ ); + break; + case 'active': + break; + default: + nextDeadlineMsg = ( +
+ Status: + ‌ + + {_.upperFirst(_.lowerCase(status))} + +
+ ); + break; + } + // Legacy MMs have a roundId field, but new MMs do not. // This is used to disable registration/submission for legacy MMs. const isLegacyMM = isMM(challenge) && Boolean(challenge.roundId); @@ -340,7 +397,7 @@ export default function ChallengeHeader(props) { onClick={unregisterFromChallenge} theme={{ button: unregisterButtonDisabled - ? style.submitButtonDisabled + ? style.unregisterButtonDisabled : style.unregisterButton, }} > @@ -366,23 +423,34 @@ export default function ChallengeHeader(props) { Submit { - track === COMPETITION_TRACKS.DES && hasRegistered && !unregistering + track === COMPETITION_TRACKS.DES && hasRegistered && !unregistering && hasSubmissions && ( - - View Submissions - - ) - } + + View Submissions + + ) + }
- Competition Timeline + {nextDeadlineMsg} + { + (status || '').toLowerCase() === 'active' + && ( +
+ {currentPhases && `${currentPhases.name} Ends: `} + + {timeDiff.text} + +
+ ) + }
- - - clearError()} /> ) : undefined } @@ -42,6 +43,7 @@ ErrorMessageContainer.propTypes = { error: PT.shape({ title: PT.string.isRequired, details: PT.string.isRequired, + support: PT.string.isRequired, }), }; diff --git a/src/shared/containers/challenge-detail/index.jsx b/src/shared/containers/challenge-detail/index.jsx index fc026000f8..3347cb0216 100644 --- a/src/shared/containers/challenge-detail/index.jsx +++ b/src/shared/containers/challenge-detail/index.jsx @@ -192,6 +192,8 @@ class ChallengeDetailPageContainer extends React.Component { reviewTypes, getAllCountries, getReviewTypes, + setCommunityId, + communityId, } = this.props; if ( @@ -220,6 +222,10 @@ class ChallengeDetailPageContainer extends React.Component { loadChallengeDetails(auth, challengeId); } + if (communityId) { + setCommunityId(communityId); + } + fetchChallengeStatistics(auth, challengeId); if (!allCountries.length) { @@ -746,6 +752,7 @@ ChallengeDetailPageContainer.propTypes = { loadChallengeDetails: PT.func.isRequired, fetchChallengeStatistics: PT.func.isRequired, getAllCountries: PT.func.isRequired, + setCommunityId: PT.func.isRequired, getReviewTypes: PT.func.isRequired, // loadResults: PT.func.isRequired, // loadingCheckpointResults: PT.bool, @@ -919,6 +926,10 @@ const mapDispatchToProps = (dispatch) => { dispatch(ca.getListInit(uuid)); dispatch(ca.getListDone(uuid, auth)); }, + setCommunityId: (communityId) => { + const a = actions.challenge; + dispatch(a.setCommunityId(communityId)); + }, getAllCountries: (tokenV3) => { dispatch(lookupActions.getAllCountriesInit()); dispatch(lookupActions.getAllCountriesDone(tokenV3));