diff --git a/.circleci/config.yml b/.circleci/config.yml index 1c0455e68c..c44c1dd181 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -202,7 +202,7 @@ workflows: filters: branches: only: - - hot-fix-hall-of-fame + - develop # This is alternate dev env for parallel testing - "build-test": context : org-global @@ -210,6 +210,7 @@ workflows: branches: only: - develop + - feature-contentful # This is beta env for production soft releases - "build-prod-beta": context : org-global diff --git a/__tests__/shared/components/__snapshots__/TopcoderFooter.jsx.snap b/__tests__/shared/components/__snapshots__/TopcoderFooter.jsx.snap index 10310b5e75..9fdfd6c76a 100644 --- a/__tests__/shared/components/__snapshots__/TopcoderFooter.jsx.snap +++ b/__tests__/shared/components/__snapshots__/TopcoderFooter.jsx.snap @@ -321,6 +321,15 @@ exports[`Matches shallow shapshot 1`] = ` Talk to Sales +
  • + + Terms + +
  • ) : ( - + {subData.entries.items[rec.sys.id].fields.title} ) @@ -278,7 +278,7 @@ export default class Article extends React.Component { Read More ) : ( - + Read More ) diff --git a/src/shared/components/Contentful/ArticleCard/ArticleCard.jsx b/src/shared/components/Contentful/ArticleCard/ArticleCard.jsx index 59a6e794bd..d50230a139 100644 --- a/src/shared/components/Contentful/ArticleCard/ArticleCard.jsx +++ b/src/shared/components/Contentful/ArticleCard/ArticleCard.jsx @@ -67,7 +67,7 @@ class ArticleCard extends React.Component { // determine if article cards will redirect to external link or article details page const articlePageUrl = article.externalArticle && article.contentUrl ? article.contentUrl - : `${config.TC_EDU_BASE_PATH}${config.TC_EDU_ARTICLES_PATH}/${article.title}`; + : `${config.TC_EDU_BASE_PATH}${config.TC_EDU_ARTICLES_PATH}/${article.slug || article.title}`; const articlePageTarget = article.externalArticle && article.contentUrl ? '_blank' : '_self'; diff --git a/src/shared/components/Contentful/ContentBlock/themes/TCO19.scss b/src/shared/components/Contentful/ContentBlock/themes/TCO19.scss index a912edb3ec..6d1dba8192 100644 --- a/src/shared/components/Contentful/ContentBlock/themes/TCO19.scss +++ b/src/shared/components/Contentful/ContentBlock/themes/TCO19.scss @@ -13,6 +13,10 @@ } } +strong a { + font-weight: 600 !important; +} + .container { align-content: center; background: white; diff --git a/src/shared/components/Contentful/ContentBlock/themes/TCO20.scss b/src/shared/components/Contentful/ContentBlock/themes/TCO20.scss index 414da6152e..7dca7a7556 100644 --- a/src/shared/components/Contentful/ContentBlock/themes/TCO20.scss +++ b/src/shared/components/Contentful/ContentBlock/themes/TCO20.scss @@ -27,6 +27,10 @@ } } +strong a { + font-weight: 600 !important; +} + .content { flex: 1; padding: 0; @@ -48,6 +52,24 @@ @include gui-kit-headers; + a { + @include roboto-regular; + + font-size: 16px; + line-height: 24px; + color: #0d61bf; + text-decoration: underline; + + &:hover { + text-decoration: none; + color: #0d61bf; + } + + &:visited { + color: #8231a9; + } + } + p { @include tc-body-md; @@ -163,24 +185,6 @@ sub { bottom: -0.25em; } - - a { - @include roboto-regular; - - font-size: 16px; - line-height: 24px; - color: #0d61bf; - text-decoration: underline; - - &:hover { - text-decoration: none; - color: #0d61bf; - } - - &:visited { - color: #8231a9; - } - } } .image { diff --git a/src/shared/components/Contentful/ContentBlock/themes/default.scss b/src/shared/components/Contentful/ContentBlock/themes/default.scss index fb46c12439..95ff90be48 100644 --- a/src/shared/components/Contentful/ContentBlock/themes/default.scss +++ b/src/shared/components/Contentful/ContentBlock/themes/default.scss @@ -63,3 +63,7 @@ pre { white-space: pre; } } + +strong a { + font-weight: 600 !important; +} diff --git a/src/shared/components/Contentful/ContentBlock/themes/general.scss b/src/shared/components/Contentful/ContentBlock/themes/general.scss index e3bee47b74..ad01f09abc 100644 --- a/src/shared/components/Contentful/ContentBlock/themes/general.scss +++ b/src/shared/components/Contentful/ContentBlock/themes/general.scss @@ -26,6 +26,10 @@ } } +strong a { + font-weight: 600 !important; +} + .content { flex: 1; padding: 0; diff --git a/src/shared/components/Contentful/SearchBar/SearchBar.jsx b/src/shared/components/Contentful/SearchBar/SearchBar.jsx index 3e3c53b58a..9444c56777 100644 --- a/src/shared/components/Contentful/SearchBar/SearchBar.jsx +++ b/src/shared/components/Contentful/SearchBar/SearchBar.jsx @@ -55,6 +55,7 @@ export class SearchBarInner extends Component { this.getDropdownPopup = this.getDropdownPopup.bind(this); this.getSuggestionList = this.getSuggestionList.bind(this); this.handleSearchChange = this.handleSearchChange.bind(this); + this.handleClickOutside = this.handleClickOutside.bind(this); // using debounce to avoid processing or requesting too much this.updateSuggestionListWithNewSearch = _.debounce( this.updateSuggestionListWithNewSearch.bind(this), 400, @@ -66,6 +67,10 @@ export class SearchBarInner extends Component { this.apiService = getService({ spaceName: 'EDU' }); } + componentWillUnmount() { + document.removeEventListener('mousedown', this.handleClickOutside); + } + /** * Set the search field ref */ @@ -164,13 +169,13 @@ export class SearchBarInner extends Component { className={theme['group-cell']} onClick={() => { window.location.href = (item.externalArticle && item.contentUrl) - ? item.contentUrl : `${config.TC_EDU_BASE_PATH}${config.TC_EDU_ARTICLES_PATH}/${item.title}`; + ? item.contentUrl : `${config.TC_EDU_BASE_PATH}${config.TC_EDU_ARTICLES_PATH}/${item.slug || item.title}`; }} onKeyPress={_.noop} > { e.nativeEvent.stopImmediatePropagation(); @@ -215,13 +220,13 @@ export class SearchBarInner extends Component { className={theme['group-cell']} onClick={() => { window.location.href = (item.externalArticle && item.contentUrl) - ? item.contentUrl : `${config.TC_EDU_BASE_PATH}${config.TC_EDU_ARTICLES_PATH}/${item.title}`; + ? item.contentUrl : `${config.TC_EDU_BASE_PATH}${config.TC_EDU_ARTICLES_PATH}/${item.slug || item.title}`; }} onKeyPress={_.noop} > { e.nativeEvent.stopImmediatePropagation(); @@ -267,7 +272,7 @@ export class SearchBarInner extends Component { > @@ -317,6 +322,13 @@ export class SearchBarInner extends Component { )); } + handleClickOutside(e) { + if (this.popupSearchResultRef && !this.popupSearchResultRef.contains(e.target)) { + this.setState({ isShowSuggestion: false }); + document.removeEventListener('mousedown', this.handleClickOutside); + } + } + /** * Update size of popup search result */ @@ -453,14 +465,10 @@ export class SearchBarInner extends Component { ref={this.setSearchFieldRef} type="text" placeholder="Search..." - onBlur={() => { - _.delay(() => { - this.setState({ isShowSuggestion: false }); - }, 100); - }} onFocus={(e) => { this.updateSuggestionListWithNewSearch(e.target.value); this.setState({ isShowSuggestion: true, isShowFilterPopup: false }); + document.addEventListener('mousedown', this.handleClickOutside); }} onChange={this.handleSearchChange} /> diff --git a/src/shared/components/Leaderboard/ChallengeHistoryModal/index.jsx b/src/shared/components/Leaderboard/ChallengeHistoryModal/index.jsx index fd8f7deb99..883ebf67ad 100644 --- a/src/shared/components/Leaderboard/ChallengeHistoryModal/index.jsx +++ b/src/shared/components/Leaderboard/ChallengeHistoryModal/index.jsx @@ -1,77 +1,147 @@ -import React from 'react'; -import { Modal, PrimaryButton } from 'topcoder-react-ui-kit'; +import React, { Component } from 'react'; +import { Modal } from 'topcoder-react-ui-kit'; import PT from 'prop-types'; import LoadingIndicator from 'components/LoadingIndicator'; import { config } from 'topcoder-react-utils'; +import cn from 'classnames'; +import _ from 'lodash'; import theme from './styles.scss'; import PodiumSpot from '../PodiumSpot'; +class ChallengeHistoryModal extends Component { + constructor(props) { + super(props); -function ChallengeHistoryModal({ - challenges, - competitor, - onCancel, - loading, - isCopilot, - isAlgo, -}) { - return ( - -

    - Completed Challenges History -

    -
    - -
    -
    -
    - Challenge Name -
    - { - !isCopilot ? ( -
    Placement
    - ) : null - } -
    - TCO Points + this.state = { + sortParam: { + order: '', + field: '', + }, + }; + } + + render() { + const { + challenges, + competitor, + onCancel, + loading, + isCopilot, + isAlgo, + } = this.props; + const { sortParam } = this.state; + const challengesOrdered = _.orderBy(challenges, [sortParam.field], [sortParam.order]); + + return ( + +

    + Completed Challenges History +

    +
    +
    -
    -
    - { - challenges.map(challenge => ( -
    - + + + + { !isCopilot ? ( -
    {challenge.place}
    + ) : null } -
    - {challenge.points} -
    - - )) + + + + + { + challengesOrdered.map(challenge => ( + + + { + !isCopilot ? ( + + ) : null + } + + + )) + } + +
    Challenge Name +
    + Placement + +
    +
    +
    + Points + +
    +
    + + {challenge.challenge_name || challenge.challenge_id} + + {challenge.place} + {challenge.points} +
    + { + loading ? : null } -
    - { - loading ? : null - } -
    - - Close - -
    - - ); +
    + +
    + + ); + } } const CHALLENGES_TYPE = PT.arrayOf(PT.shape({ diff --git a/src/shared/components/Leaderboard/ChallengeHistoryModal/styles.scss b/src/shared/components/Leaderboard/ChallengeHistoryModal/styles.scss index 6ef72af9aa..a0eb6e36df 100644 --- a/src/shared/components/Leaderboard/ChallengeHistoryModal/styles.scss +++ b/src/shared/components/Leaderboard/ChallengeHistoryModal/styles.scss @@ -1,75 +1,60 @@ @import "~styles/mixins"; -.col-1 { - padding: 0 30px; - width: 55%; - - .challenge-name { - color: $tc-gray-90; - font-weight: 700; - } - - @media (max-width: 768px) { - padding-left: 2 * $base-unit; - } -} - -.col-2 { - width: 22.5%; - text-align: center; -} - -.col-3 { - color: $tc-gray-40; - width: 22.5%; - text-align: center; -} - -.body { - border: $tc-gray-10 1px solid; -} - -.buttons { - margin-top: 10px; - display: flex; - flex-direction: row; - justify-content: center; -} +$light-gray: #d4d4d4; .container { @include roboto-regular; color: $tc-gray-50; background: $tc-white; - border-radius: 3 * $corner-radius 3 * $corner-radius; + border-radius: 10px; top: 50%; width: 70%; max-height: 90%; overflow-y: auto; + padding: 80px 78px; @media (max-width: 768px) { width: 80%; } - h1 { - color: $tc-gray-80; + h3 { + color: #1e94a3; + font-family: BarlowCondensed, sans-serif; + font-size: 34px; + font-weight: 500; + line-height: 38px; text-align: center; - font-size: 22px; - font-weight: 400; - margin-bottom: 10px; + margin-bottom: 60px; + text-transform: uppercase; @media (max-width: 768px) { - font-size: 4vw; + font-size: 31px !important; + font-weight: 500 !important; + line-height: 33px !important; + margin-bottom: 30px; } } } +.overlay { + background-color: #2a2a2a; + opacity: 0.95; + border: none; + height: 100%; + left: 0; + outline: none; + position: fixed; + top: 0; + width: 100%; + z-index: 998; +} + .podium-spot-wrapper { text-align: center; display: flex; justify-content: center; - margin-top: 15px; - margin-bottom: 1em; + margin-bottom: 50px; > div > span { height: 128px; @@ -86,51 +71,119 @@ } } -.head { - background: $tc-gray-neutral-light; - border: $tc-gray-10 1px solid; - border-bottom: none; - color: $tc-gray-40; - font-weight: 400; - font-size: 13px; - line-height: 15px; - padding: 12.5px 0; +.history-table { + width: 100%; + margin-bottom: 62px; + + thead { + th { + color: #2a2a2a; + font-family: Roboto, sans-serif; + font-size: 14px; + font-weight: 500; + letter-spacing: 0.5px; + line-height: 18px; + text-align: left; + text-transform: uppercase; + border-bottom: 1px solid #d4d4d4; + padding-bottom: 15px; + } + } + + .row { + border-bottom: 1px solid #d4d4d4; + + .name { + .link { + color: #0d61bf; + font-size: 14px; + font-weight: 400; + line-height: 51px; + text-decoration: underline; + + &:hover { + text-decoration: none; + } + } + } + + .placement { + color: #2a2a2a; + font-size: 14px; + font-weight: 500; + line-height: 51px; + } + + .points { + color: #2a2a2a; + font-size: 14px; + font-weight: 400; + line-height: 51px; + } + } +} + +.buttons { + margin-top: 10px; display: flex; + flex-direction: row; + justify-content: center; - @media (max-width: 768px) { - font-size: 3vw; - padding: 2 * $base-unit 0; + .close-btn { + color: #fafafb; + font-family: Roboto, sans-serif; + font-size: 14px; + font-weight: 700; + letter-spacing: 0.8px; + line-height: 40px; + background-color: #137d60; + border-radius: 20px; + border: none; + text-transform: uppercase; + padding: 0 20px; + + &:hover { + background-color: #0ab88a !important; + box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.2); + } } } -.row { +.header-table-content { display: flex; align-items: center; - line-height: 40px; - color: $tc-gray-70; - font-size: 15px; - border-bottom: 1px solid $tc-gray-10; +} - @media (max-width: 768px) { - font-size: 3vw; - } +.sort-container { + display: flex; + flex-direction: column; + margin-left: 5px; + padding: 0; + border: none; + outline: none; + background: transparent; } -.title { - color: $tc-gray-50; - font-size: 13px; - line-height: 15px; - font-weight: 500; +.sort-container > div { + width: 0; + height: 0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; +} + +.sort-up { + border-bottom: 4px solid $light-gray; + margin-bottom: 2px; + + &.active { + border-bottom: 4px solid $tc-black; + } } -.link { - color: #0681ff; - display: block; - line-height: 1.5; +.sort-down { + border-top: 4px solid $light-gray; - &:visited, - &:hover, - &:active { - color: #0681ff; + &.active { + border-top: 4px solid $tc-black; } } diff --git a/src/shared/components/NewsletterArchive/index.jsx b/src/shared/components/NewsletterArchive/index.jsx index f38a745761..7e4c9cc089 100644 --- a/src/shared/components/NewsletterArchive/index.jsx +++ b/src/shared/components/NewsletterArchive/index.jsx @@ -1,36 +1,118 @@ import _ from 'lodash'; import moment from 'moment'; -import React, { Fragment } from 'react'; +import React, { Component } from 'react'; import PT from 'prop-types'; import { themr } from 'react-css-super-themr'; +import cn from 'classnames'; import defaultStyle from './style.scss'; /* Date/time format to use in the link. */ -const FORMAT = 'MMM DD, HH:mm'; +const FORMAT = 'MMM DD, YYYY'; -function NewsletterArchive({ - archive, -}) { -// console.log(archive) - return _.map( - archive.campaigns, - (link, indx) => ( - - - {link.settings.title} - - Sent: {moment(link.send_time).format(FORMAT)} - - ), - ); -} +class NewsletterArchive extends Component { + constructor(props) { + super(props); -NewsletterArchive.defaultProps = { - token: null, -}; + this.state = { + sortParam: { + order: '', + field: '', + }, + }; + } + + render() { + const { + archive, + } = this.props; + const { sortParam } = this.state; + const archiveOrdered = _.orderBy(archive.campaigns, [sortParam.field], [sortParam.order]); + + return ( + + + + + + + + + + { + archiveOrdered.map((archiveItem, indx) => ( + + + + + + )) + } + +
    Item +
    + NEWSLETTER + +
    +
    +
    + SEND DATE + +
    +
    {indx + 1} + + {archiveItem.settings.title} + + + {moment(archiveItem.send_time).format(FORMAT)} +
    + ); + } +} NewsletterArchive.propTypes = { - archive: PT.shape().isRequired, + archive: PT.arrayOf().isRequired, }; export default themr('NewsletterArchive', defaultStyle)(NewsletterArchive); diff --git a/src/shared/components/NewsletterArchive/style.scss b/src/shared/components/NewsletterArchive/style.scss index e55b8bff9d..6c0292052c 100644 --- a/src/shared/components/NewsletterArchive/style.scss +++ b/src/shared/components/NewsletterArchive/style.scss @@ -1,5 +1,7 @@ @import "~styles/mixins"; +$light-gray: #d4d4d4; + .archive-link { display: block; font-size: 15px; @@ -20,3 +22,93 @@ margin-bottom: 10px; display: block; } + +.history-table { + width: 100%; + margin-bottom: 62px; + + thead { + th { + color: #2a2a2a; + font-family: Roboto, sans-serif; + font-size: 14px; + font-weight: 500; + letter-spacing: 0.5px; + line-height: 18px; + text-align: left; + text-transform: uppercase; + border-bottom: 1px solid #d4d4d4; + padding-bottom: 15px; + } + } + + .row { + border-bottom: 1px solid #d4d4d4; + + td { + padding: 0; + border-bottom: 1px solid #d4d4d4; + } + + .name { + .archive-link { + color: #0d61bf; + font-size: 14px; + font-weight: 500; + line-height: 51px; + text-decoration: underline; + + &:hover { + text-decoration: none; + } + } + } + + .index, + .sent-date { + color: #2a2a2a; + font-size: 14px; + font-weight: 400; + line-height: 51px; + } + } +} + +.header-table-content { + display: flex; + align-items: center; +} + +.sort-container { + display: flex; + flex-direction: column; + margin-left: 5px; + padding: 0; + border: none; + outline: none; + background: transparent; +} + +.sort-container > div { + width: 0; + height: 0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; +} + +.sort-up { + border-bottom: 4px solid $light-gray; + margin-bottom: 2px; + + &.active { + border-bottom: 4px solid $tc-black; + } +} + +.sort-down { + border-top: 4px solid $light-gray; + + &.active { + border-top: 4px solid $tc-black; + } +} diff --git a/src/shared/components/TopcoderFooter/index.jsx b/src/shared/components/TopcoderFooter/index.jsx index 6f97a5c674..e079531f08 100644 --- a/src/shared/components/TopcoderFooter/index.jsx +++ b/src/shared/components/TopcoderFooter/index.jsx @@ -91,6 +91,7 @@ export default function TopcoderFooter() { About Community Changelog Talk to Sales + Terms
    diff --git a/src/shared/components/challenge-detail/Specification/index.jsx b/src/shared/components/challenge-detail/Specification/index.jsx index 3f425545c8..5c9af5d04a 100644 --- a/src/shared/components/challenge-detail/Specification/index.jsx +++ b/src/shared/components/challenge-detail/Specification/index.jsx @@ -1,3 +1,4 @@ +/* eslint-disable max-len */ /* Component renders challenge details and specifications */ @@ -468,41 +469,21 @@ export default function ChallengeDetailsView(props) {
    ) : (

    - Topcoder will compensate members in accordance with our - standard payment policies, unless otherwise specified in this - challenge. For information on payment policies, setting up your - profile to receive payments, and general payment questions, - please refer to + Topcoder will compensate members in accordance with our standard payment policies, unless + otherwise specified in this challenge. For information on payment policies, setting up your profile to + receive payments, and general payment questions, please refer to ‌ - https://www.topcoder.com/thrive/articles/Payment%20Policies%20and%20Instructions - + Payment Policies and Instructions + .

    ) } -
    -

    - Reliability Rating and Bonus -

    -

    - For challenges that have a reliability bonus, the bonus depends - on the reliability rating at the moment of registration for that - project. A participant with no previous projects is considered to - have no reliability rating, and therefore gets no bonus. - Reliability bonus does not apply to Digital Run winnings. Since - reliability rating is based on the past 15 projects, it can only - have 15 discrete values. -
    - - Read more. - -

    -
    (article.externalArticle && article.contentUrl ? article.contentUrl - : `${config.TC_EDU_BASE_PATH}${config.TC_EDU_ARTICLES_PATH}/${article.title}`); + : `${config.TC_EDU_BASE_PATH}${config.TC_EDU_ARTICLES_PATH}/${article.slug || article.title}`); const items = map(articles, (a, idx) => (
    diff --git a/src/shared/routes/Topcoder/Routes.jsx b/src/shared/routes/Topcoder/Routes.jsx index eca52ee957..46fa2b6331 100644 --- a/src/shared/routes/Topcoder/Routes.jsx +++ b/src/shared/routes/Topcoder/Routes.jsx @@ -117,11 +117,52 @@ export default function Topcoder() { { - if (_.isEmpty(data.entries.items)) return ; + if (_.isEmpty(data.entries.items)) { + // try search by title match + // this legacy support should be deprecated when all + // Thrive links switched to hypens, someday + return ( + { + if (_.isEmpty(dataTitle.entries.items)) return ; + let id = dataTitle.entries.matches[0].items[0]; + if (dataTitle.entries.matches[0].total !== 1) { + // more than 1 match. we need to try find best + const mId = _.findKey( + dataTitle.entries.items, + // eslint-disable-next-line max-len + o => o.fields.title.toLocaleLowerCase() === articleTitle.toLocaleLowerCase(), + ); + id = mId || id; + } + const { + externalArticle, + contentUrl, + } = dataTitle.entries.items[id].fields; + if (externalArticle && contentUrl && isomorphy.isClientSide()) { + window.location.href = contentUrl; + return null; + } + return ( +
    + ); + }} + renderPlaceholder={LoadingIndicator} + /> + ); + } const id = data.entries.matches[0].items[0]; const { externalArticle, contentUrl } = data.entries.items[id].fields; if (externalArticle && contentUrl && isomorphy.isClientSide()) {