diff --git a/.circleci/config.yml b/.circleci/config.yml index 5e180c60ef..d723a4d47b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -276,15 +276,13 @@ workflows: branches: only: - develop - - remove-outage-banner # This is alternate dev env for parallel testing - "build-test": context : org-global filters: branches: - only: + only: - free - # This is alternate dev env for parallel testing - "build-qa": context : org-global @@ -306,7 +304,6 @@ workflows: branches: only: - develop - - remove-outage-banner - "approve-smoke-test-on-staging": type: approval requires: diff --git a/Dockerfile b/Dockerfile index 97e99ee223..902b754e09 100644 --- a/Dockerfile +++ b/Dockerfile @@ -52,6 +52,7 @@ ARG MAILCHIMP_BASE_URL ARG NODE_CONFIG_ENV ARG OPEN_EXCHANGE_RATES_KEY ARG SEGMENT_IO_API_KEY +ARG CHAMELEON_VERIFICATION_SECRET ARG SERVER_API_KEY # TC M2M credentials for Community App server @@ -72,6 +73,9 @@ ARG SENDGRID_API_KEY ARG GROWSURF_API_KEY ARG GROWSURF_CAMPAIGN_ID +# Optimizely +ARG OPTIMIZELY_SDK_KEY + ################################################################################ # Setting of environment variables in the Docker image. @@ -108,6 +112,7 @@ ENV MAILCHIMP_BASE_URL=$MAILCHIMP_BASE_URL ENV NODE_CONFIG_ENV=$NODE_CONFIG_ENV ENV OPEN_EXCHANGE_RATES_KEY=$OPEN_EXCHANGE_RATES_KEY ENV SEGMENT_IO_API_KEY=$SEGMENT_IO_API_KEY +ENV CHAMELEON_VERIFICATION_SECRET=$CHAMELEON_VERIFICATION_SECRET ENV SERVER_API_KEY=$SERVER_API_KEY # TC M2M credentials for Community App server @@ -131,6 +136,9 @@ ENV GROWSURF_API_KEY=$GROWSURF_API_KEY ENV GROWSURF_CAMPAIGN_ID=$GROWSURF_CAMPAIGN_ID ENV GSHEETS_API_KEY=$GSHEETS_API_KEY +# Optimizely +ENV OPTIMIZELY_SDK_KEY=$OPTIMIZELY_SDK_KEY + ################################################################################ # Testing and build of the application inside the container. diff --git a/build.sh b/build.sh index 32b4414968..56bd78bb06 100755 --- a/build.sh +++ b/build.sh @@ -32,6 +32,7 @@ docker build -t $TAG \ --build-arg NODE_CONFIG_ENV=$NODE_CONFIG_ENV \ --build-arg OPEN_EXCHANGE_RATES_KEY=$OPEN_EXCHANGE_RATES_KEY \ --build-arg SEGMENT_IO_API_KEY=$SEGMENT_IO_API_KEY \ + --build-arg CHAMELEON_VERIFICATION_SECRET=$CHAMELEON_VERIFICATION_SECRET \ --build-arg SERVER_API_KEY=$SERVER_API_KEY \ --build-arg TC_M2M_CLIENT_ID=$TC_M2M_CLIENT_ID \ --build-arg TC_M2M_CLIENT_SECRET=$TC_M2M_CLIENT_SECRET \ @@ -48,6 +49,7 @@ docker build -t $TAG \ --build-arg GROWSURF_API_KEY=$GROWSURF_API_KEY \ --build-arg GROWSURF_CAMPAIGN_ID=$GROWSURF_CAMPAIGN_ID \ --build-arg GSHEETS_API_KEY=$GSHEETS_API_KEY \ + --build-arg OPTIMIZELY_SDK_KEY=$OPTIMIZELY_SDK_KEY \ --build-arg COMMUNITY_APP_URL=$COMMUNITY_APP_URL . # Copies "node_modules" from the created image, if necessary for caching. diff --git a/config/custom-environment-variables.js b/config/custom-environment-variables.js index 34ffc3cb89..75e275d959 100644 --- a/config/custom-environment-variables.js +++ b/config/custom-environment-variables.js @@ -99,6 +99,7 @@ module.exports = { RECRUITCRM_API_KEY: 'RECRUITCRM_API_KEY', GROWSURF_API_KEY: 'GROWSURF_API_KEY', SENDGRID_API_KEY: 'SENDGRID_API_KEY', + CHAMELEON_VERIFICATION_SECRET: 'CHAMELEON_VERIFICATION_SECRET', }, GROWSURF_CAMPAIGN_ID: 'GROWSURF_CAMPAIGN_ID', AUTH_CONFIG: { @@ -108,4 +109,7 @@ module.exports = { TOKEN_CACHE_TIME: 'TOKEN_CACHE_TIME', }, GSHEETS_API_KEY: 'GSHEETS_API_KEY', + OPTIMIZELY: { + SDK_KEY: 'OPTIMIZELY_SDK_KEY', + }, }; diff --git a/config/default.js b/config/default.js index 161136e9e2..ec258f40b2 100644 --- a/config/default.js +++ b/config/default.js @@ -426,4 +426,7 @@ module.exports = { DEBOUNCE_ON_CHANGE_TIME: 150, }, ENABLE_RECOMMENDER: true, + OPTIMIZELY: { + SDK_KEY: '7V4CJhurXT3Y3bnzv1hv1', + }, }; diff --git a/docs/secure-identity-verification.md b/docs/secure-identity-verification.md new file mode 100644 index 0000000000..896bc450ad --- /dev/null +++ b/docs/secure-identity-verification.md @@ -0,0 +1,13 @@ +## Setup +1. Make sure you have a Chameleon Account and segment.com account +2. Integrate Chameleon Account with Segment. https://help.trychameleon.com/en/articles/1161770-installing-using-segment +3. Set Environment secret variable retrieved here https://app.trychameleon.com/settings/integrations/segment. Run the following command +`export CHAMELEON_VERIFICATION_SECRET=` +4. Run community app + +## Verification +1. Log in to topcoder-dev account +2. Access http://local.topcoder-dev.com/challenges +3. You will notice in the network tab there will be 2 requests POST to https://api.segment.io/v1/i, one will send it to segment and one will send only to chameleon (with request payload `{ integrations: { All: false, Chameleon: true }}`) + +Repeat the proses and log in to different account and make sure the `uid_hash` is different for each different user. diff --git a/package.json b/package.json index 28e53ff3ad..4c43b84f24 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ }, "dependencies": { "@hapi/joi": "^16.1.4", + "@optimizely/react-sdk": "^2.5.0", "@topcoder-platform/tc-auth-lib": "topcoder-platform/tc-auth-lib#1.0.4", "aos": "^2.3.4", "atob": "^2.1.1", diff --git a/src/assets/images/gig-work/tag-dolars.png b/src/assets/images/gig-work/tag-dolars.png new file mode 100644 index 0000000000..ec9ca00301 Binary files /dev/null and b/src/assets/images/gig-work/tag-dolars.png differ diff --git a/src/assets/images/gig-work/tag-hot.png b/src/assets/images/gig-work/tag-hot.png new file mode 100644 index 0000000000..5bc7676d60 Binary files /dev/null and b/src/assets/images/gig-work/tag-hot.png differ diff --git a/src/assets/images/gig-work/tag-new.png b/src/assets/images/gig-work/tag-new.png new file mode 100644 index 0000000000..819c49cc10 Binary files /dev/null and b/src/assets/images/gig-work/tag-new.png differ diff --git a/src/client/index.jsx b/src/client/index.jsx index b44745e76e..b879711ac2 100644 --- a/src/client/index.jsx +++ b/src/client/index.jsx @@ -23,9 +23,10 @@ const { setErrorsStore } = errors; * Performs AnalyticsJS identification of the user. * @param {Object} profile TC user profile. * @param {Array} roles User roles. + * @param {String} userIdHash Unique Hash per user. */ -function identify(profile, roles) { - analytics.identify(profile.userId, { +function identify(profile, roles, userIdHash) { + const payload = { avatar: profile.photoURL, createdAt: profile.createdAt, email: profile.email, @@ -39,7 +40,21 @@ function identify(profile, roles) { })), tracks: profile.tracks || [], username: profile.handle, - }); + }; + analytics.identify( + profile.userId, + payload, + { + integrations: { Chameleon: false }, + }, + ); + analytics.identify( + profile.userId, + { uid_hash: userIdHash, ...payload }, + { + integrations: { All: false, Chameleon: true }, + }, + ); } /** @@ -74,7 +89,7 @@ function authenticate(store) { }).then(({ tctV2, tctV3 }) => { const { auth } = store.getState(); if (auth.profile && !analyticsIdentitySet) { - identify(auth.profile, _.get(auth, 'user.roles')); + identify(auth.profile, _.get(auth, 'user.roles'), auth.userIdHash); analyticsIdentitySet = true; } if (auth.tokenV3 !== (tctV3 || null)) { @@ -85,7 +100,7 @@ function authenticate(store) { const userId = profile && profile.userId; const prevUserId = _.get(store.getState(), 'auth.profile.userId'); if (userId && userId !== prevUserId) { - identify(profile, _.get(auth, user.roles)); + identify(profile, _.get(auth, user.roles), auth.userIdHash); analyticsIdentitySet = true; } }); diff --git a/src/server/services/recruitCRM.js b/src/server/services/recruitCRM.js index 036271d110..10c9c1f97e 100644 --- a/src/server/services/recruitCRM.js +++ b/src/server/services/recruitCRM.js @@ -26,6 +26,7 @@ const JOB_FIELDS_RESPONSE = [ 'salary_type', 'max_annual_salary', 'job_description_text', + 'job_status', ]; const CANDIDATE_FIELDS_RESPONSE = [ 'id', diff --git a/src/shared/components/GUIKit/JobListCard/index.jsx b/src/shared/components/GUIKit/JobListCard/index.jsx index adfdfe4f48..536a4f647e 100644 --- a/src/shared/components/GUIKit/JobListCard/index.jsx +++ b/src/shared/components/GUIKit/JobListCard/index.jsx @@ -6,14 +6,24 @@ import React from 'react'; import PT from 'prop-types'; import { config, Link } from 'topcoder-react-utils'; import { getSalaryType, getCustomField } from 'utils/gigs'; +import { withOptimizely } from '@optimizely/react-sdk'; import './style.scss'; import IconBlackDuration from 'assets/images/icon-black-duration.svg'; import IconBlackLocation from 'assets/images/icon-black-location.svg'; import IconBlackPayment from 'assets/images/icon-black-payment.svg'; import iconBlackSkills from 'assets/images/icon-skills.png'; +import newTag from 'assets/images/gig-work/tag-new.png'; +import hotTag from 'assets/images/gig-work/tag-hot.png'; +import dolarsTag from 'assets/images/gig-work/tag-dolars.png'; -export default function JobListCard({ +const TAGS = { + New: newTag, + Hot: hotTag, + $$$: dolarsTag, +}; +function JobListCard({ job, + optimizely, }) { const duration = getCustomField(job.custom_fields, 'Duration'); let skills = getCustomField(job.custom_fields, 'Technologies Required'); @@ -25,10 +35,17 @@ export default function JobListCard({ skills = skills.join(', '); } } + const tag = getCustomField(job.custom_fields, 'Job Tag'); + const onHotlistApply = () => { + optimizely.track('View Details Click'); + }; return (
- {job.name} + { + tag !== 'n/a' && gig-job-tag + } + {job.name}
skills-icon {skills} @@ -43,7 +60,7 @@ export default function JobListCard({ {/^\d+$/.test(duration) ? `${duration} Weeks` : duration}
- VIEW DETAILS + VIEW DETAILS
@@ -56,4 +73,7 @@ JobListCard.defaultProps = { JobListCard.propTypes = { job: PT.shape().isRequired, + optimizely: PT.shape().isRequired, }; + +export default withOptimizely(JobListCard); diff --git a/src/shared/components/GUIKit/JobListCard/style.scss b/src/shared/components/GUIKit/JobListCard/style.scss index 1252ab2fd4..9cc3ca808a 100644 --- a/src/shared/components/GUIKit/JobListCard/style.scss +++ b/src/shared/components/GUIKit/JobListCard/style.scss @@ -7,13 +7,23 @@ display: flex; flex-direction: column; color: #2a2a2a; - padding: 25px 35px; + padding: 25px 35px 25px 44px; margin-bottom: 15px; + position: relative; @include gui-kit-headers; @include gui-kit-content; @include roboto-regular; + .gig-tag { + position: absolute; + top: -1px; + left: 10px; + width: 24px; + height: 56px; + border-radius: 0; + } + .gig-name, .gig-name:visited, .gig-name:active, @@ -50,18 +60,38 @@ &:first-child { width: 250px; + + @media (max-width: 1280px) { + width: auto; + margin-right: 20px; + } } &:nth-child(2) { width: 204px; + + @media (max-width: 1280px) { + width: auto; + margin-right: 20px; + } } &:nth-child(3) { width: 263px; + + @media (max-width: 1280px) { + width: auto; + margin-right: 20px; + } } &:nth-child(4) { width: 255px; + + @media (max-width: 1280px) { + width: auto; + margin-right: 20px; + } } &:last-child { diff --git a/src/shared/components/Gigs/GigDetails/index.jsx b/src/shared/components/Gigs/GigDetails/index.jsx index a28eb790af..0da0bca08d 100644 --- a/src/shared/components/Gigs/GigDetails/index.jsx +++ b/src/shared/components/Gigs/GigDetails/index.jsx @@ -66,7 +66,7 @@ export default function GigDetails(props) { return (
{ - job.error || job.enable_job_application_form !== 1 ? ( + job.error || job.job_status.id !== 1 || job.enable_job_application_form !== 1 ? (
{ job.error ? : null }

{ job.error ? 'Gig does not exist' : 'This Gig has been Fulfilled'}

diff --git a/src/shared/containers/Gigs/RecruitCRMJobApply.jsx b/src/shared/containers/Gigs/RecruitCRMJobApply.jsx index f418265630..cba026118b 100644 --- a/src/shared/containers/Gigs/RecruitCRMJobApply.jsx +++ b/src/shared/containers/Gigs/RecruitCRMJobApply.jsx @@ -10,8 +10,10 @@ import PT from 'prop-types'; import React from 'react'; import { connect } from 'react-redux'; import { isValidEmail } from 'utils/tc'; +import { withOptimizely } from '@optimizely/react-sdk'; import techSkills from './techSkills'; + const countries = require('i18n-iso-countries'); countries.registerLocale(require('i18n-iso-countries/langs/en.json')); @@ -120,12 +122,13 @@ class RecruitCRMJobApplyContainer extends React.Component { } onApplyClick() { - const { applyForJob, job } = this.props; + const { applyForJob, job, optimizely } = this.props; const { formData } = this.state; this.validateForm(); this.setState((state) => { if (_.isEmpty(state.formErrors)) { applyForJob(job, formData); + optimizely.track('Submit Application Form'); } }); } @@ -269,6 +272,7 @@ RecruitCRMJobApplyContainer.propTypes = { application: PT.shape(), searchCandidates: PT.func.isRequired, recruitProfile: PT.shape(), + optimizely: PT.shape().isRequired, }; function mapStateToProps(state, ownProps) { @@ -312,4 +316,4 @@ function mapDispatchToActions(dispatch) { export default connect( mapStateToProps, mapDispatchToActions, -)(RecruitCRMJobApplyContainer); +)(withOptimizely(RecruitCRMJobApplyContainer)); diff --git a/src/shared/containers/Gigs/RecruitCRMJobs.jsx b/src/shared/containers/Gigs/RecruitCRMJobs.jsx index 77c4770fed..cfb3f2aadf 100644 --- a/src/shared/containers/Gigs/RecruitCRMJobs.jsx +++ b/src/shared/containers/Gigs/RecruitCRMJobs.jsx @@ -12,9 +12,14 @@ import Dropdown from 'components/GUIKit/Dropdown'; import PT from 'prop-types'; import React from 'react'; import { connect } from 'react-redux'; +import { getSalaryType, getCustomField } from 'utils/gigs'; +import IconBlackLocation from 'assets/images/icon-black-location.svg'; +import { config, Link, isomorphy } from 'topcoder-react-utils'; import { getQuery, updateQuery } from 'utils/url'; +import { withOptimizely } from '@optimizely/react-sdk'; import './jobLisingStyles.scss'; +const CONTENT_PREVIEW_LENGTH = 175; const GIGS_PER_PAGE = 10; // Sort by dropdown const sortByOptions = [ @@ -42,6 +47,7 @@ class RecruitCRMJobsContainer extends React.Component { this.onFilter = this.onFilter.bind(this); this.onLocation = this.onLocation.bind(this); this.onSort = this.onSort.bind(this); + this.onHotlistApply = this.onHotlistApply.bind(this); } componentDidMount() { @@ -115,10 +121,16 @@ class RecruitCRMJobsContainer extends React.Component { }); } + onHotlistApply() { + const { optimizely } = this.props; + optimizely.track('Hotlist ads click'); + } + render() { const { loading, jobs, + optimizely, } = this.props; const { term, @@ -136,7 +148,19 @@ class RecruitCRMJobsContainer extends React.Component { ); } + // optimizely decide + let decision = { enabled: true }; + if (isomorphy.isClientSide()) { + decision = optimizely.decide('gig_listing_hotlist'); + } let jobsToDisplay = jobs; + // build hotlist of jobs if present + let hotlistJobs = _.filter(jobs, (job) => { + const showInHotlist = _.find(job.custom_fields, ['field_name', 'Show in Hotlist']); + return showInHotlist && showInHotlist.value; + }); + hotlistJobs = hotlistJobs.sort((a, b) => new Date(b.updated_on) - new Date(a.updated_on)); + hotlistJobs = _.slice(hotlistJobs, 0, 4); // build current locations dropdown based on all data // and filter by selected location jobsToDisplay = _.filter(jobs, (job) => { @@ -177,7 +201,14 @@ class RecruitCRMJobsContainer extends React.Component { }); } // Sort controlled by sortBy state - jobsToDisplay = jobsToDisplay.sort((a, b) => new Date(b[sortBy]) - new Date(a[sortBy])); + jobsToDisplay = jobsToDisplay.sort((a, b) => { + // sort tags first no matter the sortBy + const tagA = getCustomField(a.custom_fields, 'Job Tag'); + const tagB = getCustomField(b.custom_fields, 'Job Tag'); + if (tagB !== 'n/a' && tagA === 'n/a') return Number.MAX_VALUE; + if (tagB === 'n/a' && tagA !== 'n/a') return -Number.MIN_VALUE; + return new Date(b[sortBy]) - new Date(a[sortBy]); + }); // Calc pages const pages = Math.ceil(jobsToDisplay.length / GIGS_PER_PAGE); // Paginate the results @@ -187,22 +218,58 @@ class RecruitCRMJobsContainer extends React.Component { ); return ( -
-
- - - -
-
+
+
+
+ + + +
+
+ { + jobsToDisplay.length + ? jobsToDisplay.map(job => ) + : No Results + } +
{ jobsToDisplay.length - ? jobsToDisplay.map(job => ) - : No Results + ? : null }
{ - jobsToDisplay.length - ? : null + hotlistJobs.length && decision.enabled && ( +
+
HOT THIS WEEK
+
+ { + hotlistJobs.map((hjob, indx) => (indx <= 1 ? ( + +
{hjob.country}
+
{hjob.name}
+
${hjob.min_annual_salary} - {hjob.max_annual_salary} / {getSalaryType(hjob.salary_type)}
+ + ) : ( +
+
{hjob.country}
+
{hjob.name}
+
${hjob.min_annual_salary} - {hjob.max_annual_salary} / {getSalaryType(hjob.salary_type)}
+ { + getCustomField(hjob.custom_fields, 'Hotlist excerpt') !== 'n/a' ? ( +
+ { + `${getCustomField(hjob.custom_fields, 'Hotlist excerpt').substring(0, CONTENT_PREVIEW_LENGTH)}${getCustomField(hjob.custom_fields, 'Hotlist excerpt').length > CONTENT_PREVIEW_LENGTH ? '...' : ''}` + } +
+ ) : null + } + Apply Now +
+ ))) + } +
+
+ ) }
); @@ -218,6 +285,7 @@ RecruitCRMJobsContainer.propTypes = { getJobs: PT.func.isRequired, loading: PT.bool, jobs: PT.arrayOf(PT.shape), + optimizely: PT.shape().isRequired, }; function mapStateToProps(state) { @@ -241,4 +309,4 @@ function mapDispatchToActions(dispatch) { export default connect( mapStateToProps, mapDispatchToActions, -)(RecruitCRMJobsContainer); +)(withOptimizely(RecruitCRMJobsContainer)); diff --git a/src/shared/containers/Gigs/jobLisingStyles.scss b/src/shared/containers/Gigs/jobLisingStyles.scss index d1997b2f7c..535a92cb16 100644 --- a/src/shared/containers/Gigs/jobLisingStyles.scss +++ b/src/shared/containers/Gigs/jobLisingStyles.scss @@ -1,3 +1,4 @@ +/* stylelint-disable no-descending-specificity */ @import "~styles/mixins"; .loading-text { @@ -8,14 +9,19 @@ text-align: center; } -.container { +.container, +.container-with-hotlist { max-width: $screen-lg; margin: auto; - @include xs-to-sm { + @media (max-width: 1280px) { padding: 0 15px; } + .gigs { + display: block; + } + .filters { display: flex; align-items: flex-end; @@ -54,3 +60,156 @@ } } } + +.container-with-hotlist { + display: flex; + + .gigs { + width: 956px; + + @media (max-width: 1280px) { + max-width: none; + flex: 1; + } + } + + .filters { + > div { + margin-right: 20px; + + @include xs-to-sm { + margin-right: 0; + } + + &:nth-child(2) { + min-width: 194px; + } + + &:last-child { + flex: 2; + max-width: 223px; + + @include xs-to-sm { + max-width: none; + } + } + } + } + + .hotlist { + display: flex; + flex-direction: column; + margin-left: 28px; + flex: 1; + + @media (max-width: 1280px) { + display: none; + } + + h5 { + font-family: Barlow, sans-serif; + font-size: 20px; + line-height: 24px; + text-transform: uppercase; + font-weight: 600; + margin: 7px 0 31px 6px; + color: #2a2a2a; + } + + .hotlist-items { + .hotlist-item-1, + .hotlist-item-2, + .hotlist-item-3, + .hotlist-item-4 { + display: flex; + flex-direction: column; + border-radius: 10px; + padding: 20px 20px 12px; + font-family: Roboto, sans-serif; + margin-bottom: 16px; + color: #2a2a2a; + + .location { + font-size: 14px; + display: flex; + align-items: center; + + svg { + margin-right: 5px; + width: 15px; + height: 17px; + } + } + + .job-title { + margin: 0; + } + + .job-money { + line-height: 30px; + } + + .job-desc { + font-family: Roboto, sans-serif; + line-height: 24px; + margin-top: 13px; + } + } + + .hotlist-item-1, + .hotlist-item-2, + .hotlist-item-4 { + color: #fff; + + .location svg g { + stroke: #fff; + } + + .job-title, + .job-desc { + color: #fff; + } + } + + .hotlist-item-1 { + background-image: linear-gradient(305.22deg, #9d41c9 0.01%, #ef476f 100%); + } + + .hotlist-item-2 { + background-image: linear-gradient(140.77deg, #9d41c9 0%, #50ade8 100%); + } + + .hotlist-item-3 { + background-image: linear-gradient(133.83deg, #f4f4f4 0%, #d4d4d4 100%); + } + + .hotlist-item-4 { + background-image: linear-gradient(359.14deg, #555 0%, #2a2a2a 100%); + } + + .hotlist-item-button-3, + .hotlist-item-button-4 { + font-family: Roboto, sans-serif; + font-size: 12px; + letter-spacing: 0.8px; + line-height: 30px; + padding: 0 15px; + text-transform: uppercase; + font-weight: bold; + margin: 22px 0 9px; + max-width: 104px; + border-radius: 15px; + } + + .hotlist-item-button-3 { + background-color: #137d60; + color: #fff; + } + + .hotlist-item-button-4 { + background-color: #fff; + color: #229174; + } + } + } +} diff --git a/src/shared/containers/GigsPages.jsx b/src/shared/containers/GigsPages.jsx index 9945ba4962..44db1f7058 100644 --- a/src/shared/containers/GigsPages.jsx +++ b/src/shared/containers/GigsPages.jsx @@ -6,19 +6,50 @@ import PT from 'prop-types'; import Header from 'containers/TopcoderHeader'; import Footer from 'components/TopcoderFooter'; import Viewport from 'components/Contentful/Viewport'; -import { config } from 'topcoder-react-utils'; +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'; +const optimizelyClient = createInstance({ + sdkKey: config.OPTIMIZELY.SDK_KEY, +}); +const cookies = require('browser-cookies'); -export default function GigsPagesContainer(props) { - const { match } = props; - const { id } = match.params; +function GigsPagesContainer(props) { + const { match, profile } = 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; + } 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 + }); + } + } + const { id, type } = match.params; const isApply = `${config.GIGS_PAGES_PATH}/${id}/apply` === match.url; const title = 'Gig Work | Topcoder Community | 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.'; - return ( + const inner = (