diff --git a/.circleci/config.yml b/.circleci/config.yml index c338b95eaf..12efd82ca1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -91,7 +91,35 @@ jobs: source awsenvconf source buildenvvar ./master_deploy.sh -d ECS -e DEV -t latest -s test_communityapp_taskvar -i communityapp - + + # Build & Deploy against testing backend + "build-qa": + <<: *defaults + steps: + # Initialization. + - checkout + - setup_remote_docker + - run: *install_dependency + - run: *install_deploysuite + # Restoration of node_modules from cache. + - restore_cache: *restore_cache_settings_for_build + - run: + name: "configuring environment" + command: | + ./awsconfiguration.sh DEV + ./buildenv.sh -e DEV -b qa_communityapp_buildvar,qa_communityapp_deployvar + # Build of Docker image. + - run: *build_docker_image + # Caching node modules. + - save_cache: *save_cache_settings + # Deployment. + - deploy: + name: Running MasterScript + command: | + source awsenvconf + source buildenvvar + ./master_deploy.sh -d ECS -e DEV -t latest -s qa_communityapp_taskvar -i communityapp + # Build & Deploy against prod api backend "build-prod-beta": <<: *defaults @@ -203,13 +231,22 @@ workflows: branches: only: - develop + - tco21 # This is alternate dev env for parallel testing - "build-test": + context : org-global + filters: + branches: + only: + - feature-contentful + # This is alternate dev env for parallel testing + - "build-qa": context : org-global filters: branches: only: - integration-v5-challenge-api + - develop # 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 + +
  • service.getEntry(cR3.sys.id).then( async (c3) => { - const sI3 = menuItemBuilder(url2, c3); + const url3 = urlTarget(url2, cR2); + const sI3 = menuItemBuilder(url3, c3); if (c3.fields.childRoutes) { sI3.subMenu = await Promise.all(_.map( c3.fields.childRoutes, cR4 => service.getEntry(cR4.sys.id).then( - c4 => menuItemBuilder(urlTarget(url2, c3), c4), + c4 => menuItemBuilder(url3, c4), ), )); } @@ -239,9 +240,10 @@ async function getMenuDone(menuProps) { } else { menu = menuData; } - // add the preconfigured secondary menus - menu[0].secondaryMenuForLoggedInUser = config.SECONDARY_MENU_FOR_LOGGED_USER; - menu[0].secondaryMenuForGuest = config.SECONDARY_MENU_FOR_GUEST; + // add the preconfigured secondary menus? + if (fields.showSecondaryNaviMenu) { + menu[0].secondaryMenu = config.HEADER_MENU[1].secondaryMenu; + } return { id: menuProps.id, diff --git a/src/shared/components/Contentful/Article/Article.jsx b/src/shared/components/Contentful/Article/Article.jsx index c0ef0eae71..809f7c909c 100644 --- a/src/shared/components/Contentful/Article/Article.jsx +++ b/src/shared/components/Contentful/Article/Article.jsx @@ -250,7 +250,7 @@ export default class Article extends React.Component { {subData.entries.items[rec.sys.id].fields.title} ) : ( - + {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..6dd6d736f7 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; @@ -74,13 +78,13 @@ th { @include roboto-regular; - color: #808080; + color: #2a2a2a; font-size: 15px; font-weight: bold; - line-height: 25px; + line-height: 18px; text-align: left; text-transform: uppercase; - padding: 7px 10px 7px 0; + padding: 18px 10px 14px 0; @include md-to-xl { white-space: nowrap; @@ -98,8 +102,8 @@ line-height: 25px; text-align: left; color: $tc-gray-80; - border-top: 1px solid #ededf2; - border-bottom: 1px solid #ededf2; + border-top: 1px solid #d4d4d4; + border-bottom: 1px solid #d4d4d4; padding: 20px 50px 20px 0; min-height: 51px; diff --git a/src/shared/components/Contentful/ContentBlock/themes/TCO20.scss b/src/shared/components/Contentful/ContentBlock/themes/TCO20.scss index 414da6152e..e6b4a5a3e7 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; @@ -72,13 +94,13 @@ th { @include roboto-regular; - color: #808080; + color: #2a2a2a; font-size: 15px; font-weight: bold; - line-height: 25px; + line-height: 18px; text-align: left; text-transform: uppercase; - padding: 7px 10px 7px 0; + padding: 18px 10px 14px 0; @include md-to-xl { white-space: nowrap; @@ -96,8 +118,8 @@ line-height: 25px; text-align: left; color: $tc-gray-80; - border-top: 1px solid #ededf2; - border-bottom: 1px solid #ededf2; + border-top: 1px solid #d4d4d4; + border-bottom: 1px solid #d4d4d4; padding: 20px 50px 20px 0; min-height: 51px; @@ -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..195069213b 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; @@ -118,13 +122,13 @@ th { @include roboto-regular; - color: #808080; + color: #2a2a2a; font-size: 15px; font-weight: bold; - line-height: 25px; + line-height: 18px; text-align: left; text-transform: uppercase; - padding: 7px 10px 7px 0; + padding: 18px 10px 14px 0; @include md-to-xl { white-space: nowrap; @@ -142,8 +146,8 @@ line-height: 25px; text-align: left; color: $tc-gray-80; - border-top: 1px solid #ededf2; - border-bottom: 1px solid #ededf2; + border-top: 1px solid #d4d4d4; + border-bottom: 1px solid #d4d4d4; padding: 20px 50px 20px 0; min-height: 51px; diff --git a/src/shared/components/Contentful/Countdown/index.jsx b/src/shared/components/Contentful/Countdown/index.jsx index 0ff5447e93..67ccb5b868 100644 --- a/src/shared/components/Contentful/Countdown/index.jsx +++ b/src/shared/components/Contentful/Countdown/index.jsx @@ -25,6 +25,7 @@ export default function CountdownLoader(props) { title={data.entries.items[id].fields.title} end={new Date(data.entries.items[id].fields.endDate)} extraStylesForContainer={data.entries.items[id].fields.extraStylesForContainer} + themeName={data.entries.items[id].fields.theme} /> )} renderPlaceholder={LoadingIndicator} 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/Countdown/index.jsx b/src/shared/components/Countdown/index.jsx index 7fd2a42dc8..4cccc74494 100644 --- a/src/shared/components/Countdown/index.jsx +++ b/src/shared/components/Countdown/index.jsx @@ -8,7 +8,13 @@ import PT from 'prop-types'; import React from 'react'; import { fixStyle } from 'utils/contentful'; -import './style.scss'; +import defaultTheme from './themes/style.scss'; +import TCO21 from './themes/TCO21.scss'; + +const THEMES = { + Default: defaultTheme, + TCO21, +}; /* We have to use state component, as we need to manipulate with DOM nodes to * access nuka-carousel state. */ @@ -34,6 +40,8 @@ export default class Countdown extends React.Component { } render() { + const { themeName } = this.props; + const theme = THEMES[themeName]; let { elapsed } = this.state; const oneDay = 24 * 60 * 60; const oneHour = 60 * 60; @@ -54,27 +62,26 @@ export default class Countdown extends React.Component { ); return (
    -
    {title}
    -
    :
    -
    +
    {title}
    +
    -
    {day}
    -
    days
    +
    {day}
    +
    days
    -
    {hour}
    -
    hours
    +
    {hour}
    +
    hours
    -
    {minute}
    -
    minutes
    +
    {minute}
    +
    minutes
    -
    -
    {second}
    -
    seconds
    +
    +
    {second}
    +
    seconds
    @@ -85,10 +92,12 @@ export default class Countdown extends React.Component { Countdown.defaultProps = { title: 'Countdown to TCO19 Final', extraStylesForContainer: {}, + themeName: 'Default', }; Countdown.propTypes = { title: PT.string, end: PT.instanceOf(Date).isRequired, extraStylesForContainer: PT.shape(), + themeName: PT.string, }; diff --git a/src/shared/components/Countdown/themes/TCO21.scss b/src/shared/components/Countdown/themes/TCO21.scss new file mode 100644 index 0000000000..01abb24ba4 --- /dev/null +++ b/src/shared/components/Countdown/themes/TCO21.scss @@ -0,0 +1,77 @@ +@import "~styles/mixins"; + +$text-color-gray: #37373b; +$container-background-yello: #fce217; + +.container { + @include roboto-regular; + + display: -webkit-flex; /* Safari */ + display: flex; + -webkit-flex-direction: row; /* Safari */ + flex-direction: row; + -webkit-justify-content: center; /* Safari */ + justify-content: center; + padding: 40px; + background: $container-background-yello; + + @media only screen and (max-width: 767px) { + -webkit-flex-direction: column; /* Safari */ + flex-direction: column; + } + + .title { + @include barlow-condensed-medium; + + color: #2a2a2a; + font-size: 48px; + line-height: 50px; + text-align: center; + margin: auto 41px auto 0; + text-transform: uppercase; + + @media only screen and (max-width: 767px) { + margin-bottom: 20px; + } + } + + .time-container { + display: -webkit-flex; /* Safari */ + display: flex; + -webkit-flex-direction: row; /* Safari */ + flex-direction: row; + -webkit-justify-content: center; /* Safari */ + justify-content: center; + } + + .time-value { + @include barlow-condensed; + + color: #9d41c9; + font-size: 80px; + line-height: 74px; + text-align: center; + } + + .time-label { + color: #2a2a2a; + font-size: 14px; + letter-spacing: 0.5px; + line-height: 18px; + text-align: center; + width: 100px; + text-transform: uppercase; + margin-top: 4px; + + @media only screen and (max-width: 767px) { + width: auto; + margin-left: 10px; + margin-right: 10px; + font-size: 18px; + } + } + + .time-second { + display: block; + } +} diff --git a/src/shared/components/Countdown/style.scss b/src/shared/components/Countdown/themes/style.scss similarity index 94% rename from src/shared/components/Countdown/style.scss rename to src/shared/components/Countdown/themes/style.scss index feec44e436..966b7500ee 100644 --- a/src/shared/components/Countdown/style.scss +++ b/src/shared/components/Countdown/themes/style.scss @@ -31,16 +31,8 @@ $container-background-yello: #fce217; margin-bottom: auto; text-transform: uppercase; - &.colon { - margin-right: 30px; - } - @media only screen and (max-width: 767px) { margin-bottom: 20px; - - &.colon { - display: none; - } } } 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/Leaderboard/LeaderboardTable/themes/tco20.scss b/src/shared/components/Leaderboard/LeaderboardTable/themes/tco20.scss index 9a787cf24f..fe4938c6b1 100644 --- a/src/shared/components/Leaderboard/LeaderboardTable/themes/tco20.scss +++ b/src/shared/components/Leaderboard/LeaderboardTable/themes/tco20.scss @@ -191,7 +191,7 @@ $table-bg-hover: #f5f5f5; .col-fulfillment, .col-challenges, .col-points { - color: #7f7f7f; + color: #2a2a2a; font-family: Roboto, sans-serif; font-size: 14px; font-weight: 500; 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/buttons/themed/tc.scss b/src/shared/components/buttons/themed/tc.scss index 265c60e689..b990bf4abc 100644 --- a/src/shared/components/buttons/themed/tc.scss +++ b/src/shared/components/buttons/themed/tc.scss @@ -6,6 +6,7 @@ font-weight: 700 !important; text-decoration: none !important; text-transform: uppercase !important; + margin: 0 !important; } @mixin primary-green { @@ -105,6 +106,7 @@ font-weight: 700 !important; text-decoration: none !important; text-transform: uppercase !important; + margin: 0 !important; } @mixin secondary-gray { @@ -130,6 +132,7 @@ font-weight: 700 !important; text-transform: uppercase !important; + margin: 0 !important; } @mixin warn-red { diff --git a/src/shared/components/challenge-detail/Specification/index.jsx b/src/shared/components/challenge-detail/Specification/index.jsx index 8d0a3d6a61..f8f27df96e 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 */ @@ -341,41 +342,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/containers/Contentful/MenuLoader/index.jsx b/src/shared/containers/Contentful/MenuLoader/index.jsx index 2132823f63..a8bb7b45d3 100644 --- a/src/shared/containers/Contentful/MenuLoader/index.jsx +++ b/src/shared/containers/Contentful/MenuLoader/index.jsx @@ -38,16 +38,19 @@ class MenuLoaderContainer extends React.Component { spaceName, environment, baseUrl, + menu, } = this.props; - // initiate loading the menu data - loadMenuData({ - id, - fields, - preview, - spaceName, - environment, - baseUrl, - }); + if (!menu.length) { + // initiate loading the menu data + loadMenuData({ + id, + fields, + preview, + spaceName, + environment, + baseUrl, + }); + } } handleChangeLevel1Id(menuId) { @@ -79,11 +82,6 @@ class MenuLoaderContainer extends React.Component { const { TopNav, LoginNav } = require('navigation-component'); const logoToUse = !_.isEmpty(menuLogo) ? menu logo : ; const menuTheme = fields.theme.split('- '); - const comboMenu = _.flatten(_.map(menu, menuItem => menuItem.subMenu)); - // This is a hack fix that should be removed when possible! - // Its orifing is in the https://github.com/topcoder-platform/navigation-component module - // which breaks if there is NOT an menu item with id = `community` - comboMenu[0].id = 'community'; let normalizedProfile = auth.profile && _.clone(auth.profile); if (auth.profile) { normalizedProfile.photoURL = (_.has(auth.profile, 'photoURL') && auth.profile.photoURL !== null) @@ -94,7 +92,7 @@ class MenuLoaderContainer extends React.Component { return (
    + { + meta.menuItems ? ( + + ) : null + } + + } + exact + path={`${base}/members/:handle([\\w\\-\\[\\].{}]{2,15})`} + /> + } + exact + path={`${base}/members/:handle([\\w\\-\\[\\].{}]{2,15})/details`} + /> + } + id="6wUJl6RRF6MxI3kR6DFq5t" + /> + +
    + ); +} + +TCO21.defaultProps = { + base: '', +}; + +TCO21.propTypes = { + base: PT.string, + meta: PT.shape().isRequired, +}; diff --git a/src/shared/routes/Communities/TCO21/index.jsx b/src/shared/routes/Communities/TCO21/index.jsx new file mode 100644 index 0000000000..a1880bb2c2 --- /dev/null +++ b/src/shared/routes/Communities/TCO21/index.jsx @@ -0,0 +1,32 @@ +/** + * Loader for the community's code chunks. + */ + +import LoadingIndicator from 'components/LoadingIndicator'; +import path from 'path'; +import PT from 'prop-types'; +import React from 'react'; +import { AppChunk, webpack } from 'topcoder-react-utils'; + +export default function ChunkLoader({ base, meta }) { + return ( + import(/* webpackChunkName: "tco21-community/chunk" */ './Routes') + .then(({ default: Routes }) => ( + + )) + } + renderPlaceholder={() => } + renderServer={() => { + const Routes = webpack.requireWeak(path.resolve(__dirname, './Routes')); + return ; + }} + /> + ); +} + +ChunkLoader.propTypes = { + base: PT.string.isRequired, + meta: PT.shape().isRequired, +}; diff --git a/src/shared/routes/Topcoder/Routes.jsx b/src/shared/routes/Topcoder/Routes.jsx index f861d9fe03..91904b8d53 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()) { diff --git a/src/shared/utils/contentful.js b/src/shared/utils/contentful.js index 17c7b5ff65..3c253aa36b 100644 --- a/src/shared/utils/contentful.js +++ b/src/shared/utils/contentful.js @@ -94,13 +94,13 @@ export function menuItemBuilder(baseUrl, item) { case 'route': return { title: item.fields.naviMenuLinkText || item.fields.name, - href: target(baseUrl, item), + href: item.fields.viewport ? target(baseUrl, item) : null, id: item.sys.id, }; case 'navigationMenuItem': return { title: item.fields.linkText || item.fields.name, - href: target(baseUrl, item), + href: item.fields.viewport ? target(baseUrl, item) : null, id: item.sys.id, }; default: return {};