From 7207782a6a2e35e2f354c34165866fd6d0f71cd5 Mon Sep 17 00:00:00 2001 From: dat Date: Thu, 4 May 2023 17:48:55 +0700 Subject: [PATCH] feat: add link to gigs on challenge listings page on ca --- .../__snapshots__/index.jsx.snap | 18 +- .../components/challenge-listing/index.jsx | 2 + .../challenge-listing/ChallengeTab/index.jsx | 100 ++++++++-- .../challenge-listing/ChallengeTab/style.scss | 29 ++- .../components/challenge-listing/index.jsx | 12 +- .../components/challenge-listing/style.scss | 26 --- src/shared/containers/GigsPages/index.jsx | 184 ++++++++++++++++++ src/shared/containers/GigsPages/style.scss | 6 + .../challenge-listing/Listing/index.jsx | 8 + src/shared/routes/GigsPages.jsx | 8 +- src/shared/routes/index.jsx | 2 +- src/shared/utils/url.js | 21 +- 12 files changed, 347 insertions(+), 69 deletions(-) create mode 100644 src/shared/containers/GigsPages/index.jsx create mode 100644 src/shared/containers/GigsPages/style.scss diff --git a/__tests__/shared/components/challenge-listing/__snapshots__/index.jsx.snap b/__tests__/shared/components/challenge-listing/__snapshots__/index.jsx.snap index 455a808b8..5fd5c91cb 100644 --- a/__tests__/shared/components/challenge-listing/__snapshots__/index.jsx.snap +++ b/__tests__/shared/components/challenge-listing/__snapshots__/index.jsx.snap @@ -5,16 +5,9 @@ exports[`Matches shallow shapshot 1 shapshot 1 1`] = ` className="src-shared-components-challenge-listing-___style__ChallengeFiltersExample___3IjeI" id="challengeFilterContainer" > -

- CHALLENGES -

-
-

- CHALLENGES -

-
{ diff --git a/src/shared/components/challenge-listing/ChallengeTab/index.jsx b/src/shared/components/challenge-listing/ChallengeTab/index.jsx index a9a4ba195..97d88956f 100644 --- a/src/shared/components/challenge-listing/ChallengeTab/index.jsx +++ b/src/shared/components/challenge-listing/ChallengeTab/index.jsx @@ -1,11 +1,13 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useMemo } from 'react'; import { BUCKETS, isPastBucket } from 'utils/challenge-listing/buckets'; import cn from 'classnames'; import { useMediaQuery } from 'react-responsive'; import ArrowIcon from 'assets/images/ico-arrow-down.svg'; +import { config } from 'topcoder-react-utils'; import PT from 'prop-types'; import './style.scss'; +import { getUpdateQuery } from 'utils/url'; const ChallengeTab = ({ activeBucket, @@ -14,41 +16,75 @@ const ChallengeTab = ({ previousBucketOfPastChallengesTab, previousBucketOfActiveTab, selectBucket, + location, + history, }) => { const past = isPastBucket(activeBucket); const [currentSelected, setCurrentSelected] = useState(past); const [isTabClosed, setIsTabClosed] = useState(true); + const currentTabName = useMemo(() => { + if (location.pathname === config.GIGS_PAGES_PATH) { + return 'GIGS'; + } + return currentSelected ? 'PAST CHALLENGES' : 'ACTIVE CHALLENGES'; + }, [location, currentSelected]); + const pageTitle = useMemo(() => { + if (location.pathname === config.GIGS_PAGES_PATH) { + return 'GIG WORK OPPORTUNITIES'; + } + return 'CHALLENGES'; + }, [location]); useEffect(() => { setCurrentSelected(isPastBucket(activeBucket)); }, [activeBucket]); + const moveToChallengesPage = (selectedBucket) => { + if (currentTabName === 'GIGS') { + const queryParams = getUpdateQuery({ bucket: selectedBucket }); + history.push(`/challenges${queryParams || ''}`); + } + }; + const onActiveClick = () => { - if (!past) { + if (!past && currentTabName !== 'GIGS') { return; } setPreviousBucketOfPastChallengesTab(activeBucket); setCurrentSelected(0); setIsTabClosed(true); + let selectedBucket = ''; if (previousBucketOfActiveTab) { - selectBucket(previousBucketOfActiveTab); + selectedBucket = previousBucketOfActiveTab; } else { - selectBucket(BUCKETS.OPEN_FOR_REGISTRATION); + selectedBucket = BUCKETS.OPEN_FOR_REGISTRATION; } + moveToChallengesPage(selectedBucket); + selectBucket(selectedBucket); }; const onPastChallengesClick = () => { - if (past) { + if (past && currentTabName !== 'GIGS') { return; } setPreviousBucketOfActiveTab(activeBucket); setCurrentSelected(1); setIsTabClosed(true); + let selectedBucket = ''; if (previousBucketOfPastChallengesTab) { - selectBucket(previousBucketOfPastChallengesTab); + selectedBucket = previousBucketOfPastChallengesTab; } else { - selectBucket(BUCKETS.ALL_PAST); + selectedBucket = BUCKETS.ALL_PAST; } + moveToChallengesPage(selectedBucket); + selectBucket(selectedBucket); + }; + + const onGigsClick = () => { + if (typeof window === 'undefined') { + return; + } + history.push(`${config.GIGS_PAGES_PATH}${window.location.search || ''}`); }; const desktop = useMediaQuery({ minWidth: 1024 }); @@ -57,7 +93,7 @@ const ChallengeTab = ({
  • { if (e.key !== 'Enter') { @@ -71,7 +107,7 @@ const ChallengeTab = ({
  • { if (e.key !== 'Enter') { @@ -83,6 +119,20 @@ const ChallengeTab = ({ > PAST CHALLENGES
  • +
  • { + if (e.key !== 'Enter') { + return; + } + onGigsClick(); + }} + role="presentation" + > + GIGS +
); @@ -93,7 +143,7 @@ const ChallengeTab = ({ role="presentation" onClick={() => setIsTabClosed(!isTabClosed)} > -

{currentSelected ? 'PAST CHALLENGES' : 'ACTIVE CHALLENGES'}

+

{currentTabName}

ACTIVE CHALLENGES

PAST CHALLENGES

+
+

GIGS

+
) } ); - return desktop ? desktopTab : mobileTab; + return ( + +

{pageTitle}

+
+ {desktop ? desktopTab : mobileTab} +
+ ); }; ChallengeTab.defaultProps = { + activeBucket: null, + selectBucket: () => {}, setPreviousBucketOfActiveTab: () => {}, setPreviousBucketOfPastChallengesTab: () => {}, previousBucketOfActiveTab: null, @@ -136,11 +201,16 @@ ChallengeTab.defaultProps = { }; ChallengeTab.propTypes = { - activeBucket: PT.string.isRequired, + location: PT.shape({ + search: PT.string, + pathname: PT.string, + }).isRequired, + history: PT.shape().isRequired, + activeBucket: PT.string, setPreviousBucketOfActiveTab: PT.func, setPreviousBucketOfPastChallengesTab: PT.func, previousBucketOfActiveTab: PT.string, - selectBucket: PT.func.isRequired, + selectBucket: PT.func, previousBucketOfPastChallengesTab: PT.string, }; diff --git a/src/shared/components/challenge-listing/ChallengeTab/style.scss b/src/shared/components/challenge-listing/ChallengeTab/style.scss index 575e052d7..85817857d 100644 --- a/src/shared/components/challenge-listing/ChallengeTab/style.scss +++ b/src/shared/components/challenge-listing/ChallengeTab/style.scss @@ -59,6 +59,7 @@ .active { color: $tc-black; font-weight: 700; + position: relative; &::after { content: ""; @@ -70,7 +71,7 @@ z-index: 100; display: block; position: absolute; - top: 145px; + top: 31px; } } } @@ -117,3 +118,29 @@ margin: 0 16px; } } + +.tc-title { + @include barlow-condensed; + + color: $tco-black; + font-size: 32px; + margin: 32px 0 24px 0; + line-height: 32px; + font-weight: 600; + text-transform: uppercase; + + @include xs-to-md { + margin: 16px; + } +} + +.tc-seperator { + background-color: $listing-gray; + height: 2px; + opacity: 0.5; + margin: 0; + + @include xs-to-md { + margin: 0 16px; + } +} diff --git a/src/shared/components/challenge-listing/index.jsx b/src/shared/components/challenge-listing/index.jsx index 00b5c564e..85d65b32a 100644 --- a/src/shared/components/challenge-listing/index.jsx +++ b/src/shared/components/challenge-listing/index.jsx @@ -54,6 +54,8 @@ export default function ChallengeListing(props) { setPreviousBucketOfPastChallengesTab, previousBucketOfPastChallengesTab, previousBucketOfActiveTab, + location, + history, } = props; // const { challenges } = props; @@ -157,17 +159,15 @@ export default function ChallengeListing(props) { return (
- -

CHALLENGES

-
-
@@ -221,6 +221,10 @@ ChallengeListing.defaultProps = { }; ChallengeListing.propTypes = { + location: PT.shape({ + search: PT.string, + }).isRequired, + history: PT.shape().isRequired, activeBucket: PT.string.isRequired, expanding: PT.bool, challenges: PT.arrayOf(PT.shape()).isRequired, diff --git a/src/shared/components/challenge-listing/style.scss b/src/shared/components/challenge-listing/style.scss index a8b290e56..518bc5e05 100644 --- a/src/shared/components/challenge-listing/style.scss +++ b/src/shared/components/challenge-listing/style.scss @@ -22,32 +22,6 @@ $challenge-radius-4: $corner-radius * 2; flex: 1; margin: 0; - .tc-title { - @include barlow-condensed; - - color: $tco-black; - font-size: 32px; - margin: 32px 0 24px 0; - line-height: 32px; - font-weight: 600; - text-transform: uppercase; - - @include xs-to-md { - margin: 16px; - } - } - - .tc-seperator { - background-color: $listing-gray; - height: 2px; - opacity: 0.5; - margin: 0; - - @include xs-to-md { - margin: 0 16px; - } - } - .tc-content-wrapper { display: flex; padding: 0; diff --git a/src/shared/containers/GigsPages/index.jsx b/src/shared/containers/GigsPages/index.jsx new file mode 100644 index 000000000..95319a386 --- /dev/null +++ b/src/shared/containers/GigsPages/index.jsx @@ -0,0 +1,184 @@ +/** + * Connects the Redux store to the GigsPages component. + */ +import React from 'react'; +import PT from 'prop-types'; +import Header from 'containers/TopcoderHeader'; +import Footer from 'components/TopcoderFooter'; +import Viewport from 'components/Contentful/Viewport'; +import { config, isomorphy } from 'topcoder-react-utils'; +import RecruitCRMJobDetails from 'containers/Gigs/RecruitCRMJobDetails'; +import { Helmet } from 'react-helmet'; +import MetaTags from 'components/MetaTags'; +import { OptimizelyProvider, createInstance } from '@optimizely/react-sdk'; +import { connect } from 'react-redux'; +import _ from 'lodash'; +import { v4 as uuidv4 } from 'uuid'; +import { getQuery } from 'utils/url'; +import ReferralCode from 'components/Gigs/ReferralCode'; +import ChallengeTab from 'components/challenge-listing/ChallengeTab'; +import actions from 'actions/growSurf'; + +import './style.scss'; + +const optimizelyClient = createInstance({ + sdkKey: config.OPTIMIZELY.SDK_KEY, +}); +const cookies = require('browser-cookies'); + +const GIGS_SOCIAL_SHARE_IMAGE = 'https://images.ctfassets.net/b5f1djy59z3a/4XlYNZgq5Kfa4XdwQ6pDfV/769ea7be756a88145b88ce685f050ebc/10_Freelance_Gig.png'; + +function GigsPagesContainer(props) { + const { + match, + profile, + growSurf, + getReferralId, + tokenV3, + history, + location, + } = props; + const optProfile = { + attributes: {}, + }; + if (!_.isEmpty(profile)) { + optProfile.id = String(profile.userId); + optProfile.attributes.TC_Handle = profile.handle; + optProfile.attributes.HomeCountryCode = profile.homeCountryCode; + optProfile.attributes.email = profile.email; + // trigger referral id fetching when profile is loaded + if (isomorphy.isClientSide()) { + if (_.isEmpty(growSurf) || (!growSurf.loading && !growSurf.data && !growSurf.error)) { + getReferralId(profile, tokenV3); + } + } + } else if (isomorphy.isClientSide()) { + const idCookie = cookies.get('_tc.aid'); + if (idCookie) { + optProfile.id = JSON.parse(idCookie).aid; + } else { + optProfile.id = uuidv4(); + cookies.set('_tc.aid', JSON.stringify({ + aid: optProfile.id, + }), { + secure: true, + domain: '', + expires: 365, // days + }); + } + } + // check for referral code in the URL and set it to cookie + if (isomorphy.isClientSide()) { + const query = getQuery(); + if (query.referralId) { + cookies.set(config.GROWSURF_COOKIE, JSON.stringify({ + referralId: query.referralId, + }), config.GROWSURF_COOKIE_SETTINGS); + } + } + const { id, type } = match.params; + const isApply = `${config.GIGS_PAGES_PATH}/${id}/apply` === match.url; + const title = 'Find Freelance Work | Gigs | Topcoder'; + const description = 'Compete and build up your profiles and skills! Topcoder members become eligible to work on Gig Work projects by first proving themselves in various skill sets through Topcoder competitions.'; + const inner = ( +
+ + + + +
+ { + id ? ( + + ) : null + } +
+ +
+ { + !id && !type ? ( + + + + + ) : null + } +
+
+ ); + + return ( + + {inner} + + ); +} + +GigsPagesContainer.defaultProps = { + profile: null, + growSurf: null, + tokenV3: null, +}; + +GigsPagesContainer.propTypes = { + location: PT.shape({ + search: PT.string, + pathname: PT.string, + }).isRequired, + history: PT.shape().isRequired, + match: PT.shape().isRequired, + profile: PT.shape(), + growSurf: PT.shape(), + getReferralId: PT.func.isRequired, + tokenV3: PT.string, +}; + +function mapStateToProps(state) { + const profile = state.auth && state.auth.profile ? { ...state.auth.profile } : {}; + const { growSurf } = state; + return { + profile, + growSurf, + tokenV3: state.auth ? state.auth.tokenV3 : null, + }; +} + +function mapDispatchToActions(dispatch) { + const a = actions.growsurf; + return { + getReferralId: (profile, tokenV3) => { + dispatch(a.getReferralidInit()); + dispatch(a.getReferralidDone(profile, tokenV3)); + }, + }; +} + +export default connect( + mapStateToProps, + mapDispatchToActions, +)(GigsPagesContainer); diff --git a/src/shared/containers/GigsPages/style.scss b/src/shared/containers/GigsPages/style.scss new file mode 100644 index 000000000..a2a0b1bf7 --- /dev/null +++ b/src/shared/containers/GigsPages/style.scss @@ -0,0 +1,6 @@ +@import "~styles/mixins"; + +.ChallengeFiltersExample { + margin: 0 auto; + max-width: $screen-lg; +} diff --git a/src/shared/containers/challenge-listing/Listing/index.jsx b/src/shared/containers/challenge-listing/Listing/index.jsx index d92ec6cd2..c89dc1eaf 100644 --- a/src/shared/containers/challenge-listing/Listing/index.jsx +++ b/src/shared/containers/challenge-listing/Listing/index.jsx @@ -518,6 +518,8 @@ export class ListingContainer extends React.Component { meta, setSearchText, filterState, + location, + history, } = this.props; const { @@ -634,6 +636,8 @@ export class ListingContainer extends React.Component { /> {banner} import(/* webpackChunkName: "gigsPages/chunk" */ 'containers/GigsPages') + renderClientAsync={renderProps => import(/* webpackChunkName: "gigsPages/chunk" */ 'containers/GigsPages') .then(({ default: GigsPagesContainer }) => ( - + )) } renderPlaceholder={() => } - renderServer={() => { + renderServer={(renderProps) => { const p = webpack.resolveWeak('containers/GigsPages'); const GigsPagesContainer = webpack.requireWeak(path.resolve(__dirname, p)); - return ; + return ; }} /> ); diff --git a/src/shared/routes/index.jsx b/src/shared/routes/index.jsx index a723bae36..9f76f31b6 100644 --- a/src/shared/routes/index.jsx +++ b/src/shared/routes/index.jsx @@ -107,7 +107,7 @@ function Routes({ communityId }) {