diff --git a/.circleci/config.yml b/.circleci/config.yml index b47bfc646e..ab8fcc9a87 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -349,21 +349,21 @@ workflows: filters: branches: only: - - free + - gig-apply-new-options-dropdown # This is alternate dev env for parallel testing - "build-qa": context : org-global filters: branches: only: - - gig-cache-layer + - member-path-component # This is beta env for production soft releases - "build-prod-beta": context : org-global filters: branches: only: - - slash-tc + - slash-home-hotfix # This is stage env for production QA releases - "build-prod-staging": context : org-global diff --git a/__tests__/shared/components/__snapshots__/Content.jsx.snap b/__tests__/shared/components/__snapshots__/Content.jsx.snap index 36cc437614..f9d9c7553a 100644 --- a/__tests__/shared/components/__snapshots__/Content.jsx.snap +++ b/__tests__/shared/components/__snapshots__/Content.jsx.snap @@ -1075,6 +1075,16 @@ exports[`Matches shallow shapshot 1`] = ` - Demo of Blog Feed component +
  • + + Member Path - Path Selector + + + — Demo for path selector component on member path page +
  • `; diff --git a/config/default.js b/config/default.js index 81f79405df..75d0d7fedd 100644 --- a/config/default.js +++ b/config/default.js @@ -102,7 +102,7 @@ module.exports = { /* This is the same value as above, but it is used by topcoder-react-lib, * as a more verbose name for the param. */ COMMUNITY_APP: 'https://community-app.topcoder-dev.com', - CHALLENGES_URL: 'https://platform.topcoder.com/earn/find/challenges', + CHALLENGES_URL: 'https://www.topcoder-dev.com/challenges', ARENA: 'https://arena.topcoder-dev.com', AUTH: 'https://accounts-auth0.topcoder-dev.com', diff --git a/config/production.js b/config/production.js index cc92c85444..7a62d55e7f 100644 --- a/config/production.js +++ b/config/production.js @@ -25,6 +25,7 @@ module.exports = { /* This is the same value as above, but it is used by topcoder-react-lib, * as a more verbose name for the param. */ COMMUNITY_APP: 'https://community-app.topcoder.com', + CHALLENGES_URL: 'https://www.topcoder.com/challenges', AUTH: 'https://accounts-auth0.topcoder.com', BASE: 'https://www.topcoder.com', diff --git a/src/assets/images/member-path/icon-left.svg b/src/assets/images/member-path/icon-left.svg new file mode 100644 index 0000000000..8261f5640c --- /dev/null +++ b/src/assets/images/member-path/icon-left.svg @@ -0,0 +1,19 @@ + + + 1EF4CE06-1B7B-47E8-B32B-876E51A166B9 + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/images/member-path/icon-right.svg b/src/assets/images/member-path/icon-right.svg new file mode 100644 index 0000000000..8f44a1718c --- /dev/null +++ b/src/assets/images/member-path/icon-right.svg @@ -0,0 +1,13 @@ + + + 2EEB4162-14EC-4DD7-A445-058D8E648D53 + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/mock-data/member-path-selector-data.json b/src/assets/mock-data/member-path-selector-data.json new file mode 100644 index 0000000000..7091a0dab1 --- /dev/null +++ b/src/assets/mock-data/member-path-selector-data.json @@ -0,0 +1,40 @@ +{ + "title": "WHAT DO YOU WANT TO DO TODAY?", + "items": [ + { + "title": "COMPETE", + "iconURL": "https://media-bucket-s3.s3.eu-central-1.amazonaws.com/sx/icon-compete.svg", + "activeIconURL": "https://s3.eu-central-1.amazonaws.com/media-bucket-s3/sx/icon-compete-active.svg", + "contentText": "A competition based, gig economy, a typical 9-5 job for you", + "btnText": "Topcoder Challenges", + "btnURL": "https://www.topcoder.com/challenges", + "btnNewTab": true + }, { + "title": "BUILD SKILLS", + "iconURL": "https://media-bucket-s3.s3.eu-central-1.amazonaws.com/sx/icon-build-skills.svg", + "activeIconURL": "https://s3.eu-central-1.amazonaws.com/media-bucket-s3/sx/icon-build-skills-active.svg", + "contentText": "Get stronger with Skill Builder Competitions", + "btnText": "Find a Competition", + "btnURL": "https://www.topcoder.com/challenges?bucket=openForRegistration&search=Topcoder%20Skill%20Builder%20Competition&tracks[DS]=true&tracks[Des]=true&tracks[Dev]=true&tracks[QA]=true&types[]=CH&types[]=F2F&types[]=TSK", + "btnNewTab": true + }, + { + "title": "LEARN", + "iconURL": "https://s3.eu-central-1.amazonaws.com/media-bucket-s3/sx/icon-learn.svg", + "activeIconURL": "https://s3.eu-central-1.amazonaws.com/media-bucket-s3/sx/icon-learn-active.svg", + "contentText": "Tutorials and workshops that matter", + "btnText": "Thrive", + "btnURL": "https://www.topcoder.com/thrive", + "btnNewTab": true + }, + { + "title": "GET A GIG", + "iconURL": "https://s3.eu-central-1.amazonaws.com/media-bucket-s3/sx/icon-gig.svg", + "activeIconURL": "https://s3.eu-central-1.amazonaws.com/media-bucket-s3/sx/icon-get-a-gig-active.svg", + "contentText": "Find a freelance job with our help", + "btnText": "Gig Work", + "btnURL": "https://www.topcoder.com/gigs", + "btnNewTab": true + } + ] +} diff --git a/src/shared/components/Content/index.jsx b/src/shared/components/Content/index.jsx index 60b76cd665..293cb35600 100644 --- a/src/shared/components/Content/index.jsx +++ b/src/shared/components/Content/index.jsx @@ -861,6 +861,16 @@ export default function Content() { {' '} - Demo of Blog Feed component +
  • + + Member Path - Path Selector + + {' '} + — + Demo for path selector component on member path page +
  • ); diff --git a/src/shared/components/Contentful/AppComponent/index.jsx b/src/shared/components/Contentful/AppComponent/index.jsx index 5d19f81fee..a012fb82e9 100644 --- a/src/shared/components/Contentful/AppComponent/index.jsx +++ b/src/shared/components/Contentful/AppComponent/index.jsx @@ -12,6 +12,7 @@ import Leaderboard from 'containers/tco/Leaderboard'; import RecruitCRMJobs from 'containers/Gigs/RecruitCRMJobs'; import EmailSubscribeForm from 'containers/EmailSubscribeForm'; import GSheet from 'containers/GSheet'; +import PathSelector from 'components/MemberPath/PathSelector'; const { fireErrorMessage } = errors; @@ -43,6 +44,14 @@ export function AppComponentSwitch(appComponent) { if (appComponent.fields.type === 'GSheet') { return ; } + if (appComponent.fields.type === 'MemberPath') { + return ( + + ); + } fireErrorMessage(`Unsupported app component type ${appComponent.fields.type}`, ''); return null; } diff --git a/src/shared/components/Dashboard/GigsFeed/index.jsx b/src/shared/components/Dashboard/GigsFeed/index.jsx index 3750a100b6..ecd0e0f3bd 100644 --- a/src/shared/components/Dashboard/GigsFeed/index.jsx +++ b/src/shared/components/Dashboard/GigsFeed/index.jsx @@ -20,7 +20,7 @@ export default function GigsFeed({ GIGS View all gigs @@ -31,7 +31,7 @@ export default function GigsFeed({ : gigs.message ? {gigs.message} : gigs.map(gig => (
    {gig.title} diff --git a/src/shared/components/MemberPath/PathSelector/index.jsx b/src/shared/components/MemberPath/PathSelector/index.jsx new file mode 100644 index 0000000000..ae16a7c202 --- /dev/null +++ b/src/shared/components/MemberPath/PathSelector/index.jsx @@ -0,0 +1,133 @@ +import PT from 'prop-types'; +import React, { useEffect, useState } from 'react'; +import cn from 'classnames'; +import ArrowLeftIcon from 'assets/images/member-path/icon-left.svg'; +import ArrowRightIcon from 'assets/images/member-path/icon-right.svg'; +import styles from './styles.scss'; + +const colorStyles = [styles.purple, styles.blue, styles.teal, styles.orange]; + +export default function PathSelector({ + data, + animationTime, +}) { + const [activeItemIndex, setActiveItemIndex] = useState(0); + const [timer, setTimer] = useState(null); + + useEffect( + () => { + setTimer(setInterval(() => { + setActiveItemIndex(itemIndex => (itemIndex + 1) % data.items.length); + }, + animationTime)); + return () => { + if (timer) { + clearInterval(timer); + } + }; + }, + [], + ); + const stopTimer = () => { + if (timer) { + clearInterval(timer); + setTimer(null); + } + }; + + const handleOptionSelect = index => () => { + setActiveItemIndex(index); + // Stop the animation if user selects an item + stopTimer(); + }; + + const handlePrevButton = () => { + setActiveItemIndex(activeItemIndex - 1 < 0 ? data.items.length - 1 : activeItemIndex - 1); + // Stop the animation if user selects an item + stopTimer(); + }; + + const handleNextButton = () => { + setActiveItemIndex((activeItemIndex + 1) % data.items.length); + // Stop the animation if user selects an item + stopTimer(); + }; + + const items = data.items.map((item, index) => ( + + )); + + return ( +
    +
    +

    {data.title}

    +
    + + {items} + +
    +
    +
    +
    + {data.items[activeItemIndex].contentText} +
    +
    + {data.items[activeItemIndex].btnText} + +
    +
    + ); +} + +PathSelector.defaultProps = { + animationTime: 3000, +}; + +PathSelector.propTypes = { + data: PT.shape({ + items: PT.arrayOf(PT.shape({ + title: PT.string, + iconURL: PT.string, + activeIconURL: PT.string, + contentText: PT.string, + btnText: PT.string, + btnURL: PT.string, + btnNewTab: PT.bool, + })), + title: PT.string, + }).isRequired, + animationTime: PT.number, +}; diff --git a/src/shared/components/MemberPath/PathSelector/styles.scss b/src/shared/components/MemberPath/PathSelector/styles.scss new file mode 100644 index 0000000000..c59e1008cd --- /dev/null +++ b/src/shared/components/MemberPath/PathSelector/styles.scss @@ -0,0 +1,242 @@ +@import "~styles/mixins"; +@import "~components/buttons/themed/tc"; + +.container { + width: 100%; + height: 618px; + + @include xs-to-sm { + height: 504px; + } +} + +.header { + background-color: #011423; + border-top-left-radius: 10px; + border-top-right-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + padding: 50px 32px; + height: 380px; + + @include xs-to-sm { + padding: 20px 32px 28px 32px; + height: 298px; + } +} + +.title { + @include barlow-condensed-medium; + + color: $tc-white; + margin-bottom: 61px; + font-size: 48px; + text-align: center; + + @include xs-to-sm { + margin-bottom: 20px; + font-size: 31px; + line-height: 33px; + } +} + +.options { + display: flex; + align-items: center; + justify-content: center; + + .arrowIcon { + display: none; + } + + @include xs-to-sm { + .arrowIcon { + display: block; + margin: 16px; + } + } +} + +.option { + @include barlow-condensed-semi-bold; + + font-size: 24px; + width: 168px; + height: 168px; + border-radius: 12px; + margin: 0 8px; + border: none; + background: $tc-white; + color: $tc-gray-70; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + position: relative; + + @include xs-to-sm { + &:not(.active) { + display: none; + } + } + + .icon, + .activeIcon { + display: block; + height: 64px; + width: 64px; + margin-bottom: 19px; + content: unset; + } + + .activeIcon { + display: none; + } + + &.active { + color: $tc-white; + display: flex; + + .activeIcon { + display: block; + } + + .icon { + display: none; + } + + @include md-to-xl { + &.purple { + background: linear-gradient(305deg, #9d41c9 0%, #ef476f 100%); + + &::before { + background: linear-gradient(305.22deg, #e6477a 0.01%, #e3467c 100%); + } + } + + &.blue { + background: linear-gradient(90deg, #26b3c5 0%, #2984bd 100%); + + &::before { + background: linear-gradient(90deg, #27a2c2 0%, #27a1c1 100%); + } + } + + &.teal { + background: linear-gradient(270deg, #26b3c5 0%, #06d6a0 100%); + + &::before { + background: linear-gradient(270deg, #1ac0b7 0%, #1ac0b8 100%); + } + } + + &.orange { + background: linear-gradient(90deg, #ffc43d 0%, #f37593 100%); + + &::before { + background: linear-gradient(86.76deg, #f58186 0%, #f47e89 100%); + } + } + } + + &::after { + content: ''; + display: block; + position: absolute; + bottom: -58px; + width: 32px; + height: 16px; + border-left: 16px solid #011423; + border-right: 16px solid #011423; + border-top: 16px solid #011423; + border-bottom: 16px solid transparent; + } + + &::before { + content: ''; + display: block; + position: absolute; + bottom: -59px; + width: 31px; + height: 17px; + } + + @include xs-to-sm { + background: $tc-white; + color: $tc-gray-70; + + &::before, + &::after { + display: none; + } + + .icon { + display: block; + } + + .activeIcon { + display: none; + } + } + } +} + +.content { + background: linear-gradient(305deg, #9d41c9 0%, #ef476f 100%); + width: 100%; + display: flex; + align-items: center; + justify-content: flex-end; + flex-direction: column; + border-bottom-left-radius: 10px; + border-bottom-right-radius: 10px; + padding: 41px 0 49px; + + &.purple { + background: linear-gradient(305deg, #9d41c9 0%, #ef476f 100%); + } + + &.blue { + background: linear-gradient(90deg, #26b3c5 0%, #2984bd 100%); + } + + &.teal { + background: linear-gradient(270deg, #26b3c5 0%, #06d6a0 100%); + } + + &.orange { + background: linear-gradient(90deg, #ffc43d 0%, #f37593 100%); + } + + @include xs-to-sm { + padding: 30px 0; + } +} + +.contentText { + @include roboto-regular; + + color: $tc-white; + font-size: 24px; + line-height: 29px; + margin-bottom: 26px; + padding: 0 40px; + text-align: center; + flex: 1; + display: flex; + align-items: center; + justify-content: center; + + @include xs-to-sm { + margin-bottom: 15px; + font-size: 20px; + line-height: 30px; + } +} + +.contentButton { + @include primary-borderless; + @include md; +} diff --git a/src/shared/components/examples/MemberPathSelector/index.jsx b/src/shared/components/examples/MemberPathSelector/index.jsx new file mode 100644 index 0000000000..cb9d614720 --- /dev/null +++ b/src/shared/components/examples/MemberPathSelector/index.jsx @@ -0,0 +1,15 @@ +import PathSelector from 'components/MemberPath/PathSelector'; +import React from 'react'; +import mockData from 'assets/mock-data/member-path-selector-data.json'; +import './styles.scss'; + +export default function MemberPathSelectorExample() { + return ( +
    +

    + Member Path - Path Selector Component Preview +

    + +
    + ); +} diff --git a/src/shared/components/examples/MemberPathSelector/styles.scss b/src/shared/components/examples/MemberPathSelector/styles.scss new file mode 100644 index 0000000000..7ee3b6eee8 --- /dev/null +++ b/src/shared/components/examples/MemberPathSelector/styles.scss @@ -0,0 +1,8 @@ +@import "~styles/mixins"; + +.container { + background: white; + margin: 0 auto; + max-width: 800px; + padding: 8px; +} diff --git a/src/shared/containers/Gigs/RecruitCRMJobApply.jsx b/src/shared/containers/Gigs/RecruitCRMJobApply.jsx index 4c086cf6ab..422ae8a474 100644 --- a/src/shared/containers/Gigs/RecruitCRMJobApply.jsx +++ b/src/shared/containers/Gigs/RecruitCRMJobApply.jsx @@ -35,6 +35,8 @@ class RecruitCRMJobApplyContainer extends React.Component { { label: 'LinkedIn', selected: false }, { label: 'Other Ad or Promotion', selected: false }, { label: 'Quora', selected: false }, + { label: 'Referral', selected: false }, + { label: 'Topcoder Newsletter', selected: false }, { label: 'Uprisor Podcast', selected: false }, { label: 'YouTube or Video Ad', selected: false }, ], diff --git a/src/shared/routes/Examples/Examples.jsx b/src/shared/routes/Examples/Examples.jsx index 4c23246982..25d9dca746 100644 --- a/src/shared/routes/Examples/Examples.jsx +++ b/src/shared/routes/Examples/Examples.jsx @@ -36,6 +36,7 @@ import ThriveArticlesFeedExample from 'components/examples/ThriveArticlesFeed'; import GigsFeedExample from 'components/examples/GigsFeed'; import TCOLeaderboardsExample from 'components/examples/TCOLeaderboards'; import ChallengesFeed from 'components/examples/ChallengesFeed'; +import MemberPathSelectorExample from 'components/examples/MemberPathSelector'; import { Switch, @@ -106,6 +107,7 @@ export default function Examples({ + ); diff --git a/src/shared/routes/index.jsx b/src/shared/routes/index.jsx index 3468ce0f0b..7ffc2b2642 100644 --- a/src/shared/routes/index.jsx +++ b/src/shared/routes/index.jsx @@ -134,13 +134,14 @@ function Routes({ communityId }) { /> ( - +
    +
    )} exact path={config.START_PAGE_PATH}