diff --git a/Dockerfile b/Dockerfile index b82836be68..de3ab09f79 100644 --- a/Dockerfile +++ b/Dockerfile @@ -171,4 +171,3 @@ RUN npm run build EXPOSE 3000 CMD ["npm", "start"] - diff --git a/__tests__/shared/components/Header/__snapshots__/index.jsx.snap b/__tests__/shared/components/Header/__snapshots__/index.jsx.snap index 2b1282b6d4..bc199bd8cf 100644 --- a/__tests__/shared/components/Header/__snapshots__/index.jsx.snap +++ b/__tests__/shared/components/Header/__snapshots__/index.jsx.snap @@ -62,12 +62,12 @@ exports[`Default render 1`] = ` "title": "All Challenges", }, Object { - "href": "/community/arena", - "title": "Competitive Programming", + "href": "/engagements", + "title": "Engagements", }, Object { - "href": "/gigs", - "title": "Gig Work", + "href": "/community/arena", + "title": "Competitive Programming", }, Object { "href": "/community/practice", diff --git a/__tests__/shared/components/ProfilePage/Stats/SubTrackChallengeView/__mocks__/develop.json b/__tests__/shared/components/ProfilePage/Stats/SubTrackChallengeView/__mocks__/develop.json index bde784385f..2248ba05f6 100644 --- a/__tests__/shared/components/ProfilePage/Stats/SubTrackChallengeView/__mocks__/develop.json +++ b/__tests__/shared/components/ProfilePage/Stats/SubTrackChallengeView/__mocks__/develop.json @@ -16,7 +16,7 @@ "submissions": [{ "id": 520456, "submittedAt": "2017-12-02T11:57Z", - "status": "Active", + "status": "ACTIVE", "score": 100.0, "placement": 1, "challengeId": 30060905, @@ -172,7 +172,7 @@ }, { "id": 520452, "submittedAt": "2017-12-02T11:11Z", - "status": "Active", + "status": "ACTIVE", "score": 100.0, "placement": 1, "challengeId": 30060903, @@ -370,7 +370,7 @@ }, { "id": 246093, "submittedAt": "2017-11-25T13:36Z", - "status": "Active", + "status": "ACTIVE", "score": 100.0, "placement": 1, "challengeId": 30060687, @@ -700,7 +700,7 @@ }, { "id": 245457, "submittedAt": "2017-11-13T22:25Z", - "status": "Active", + "status": "ACTIVE", "score": 100.0, "placement": 1, "challengeId": 30060425, diff --git a/__tests__/shared/components/ProfilePage/Stats/SubTrackChallengeView/__snapshots__/index.jsx.snap b/__tests__/shared/components/ProfilePage/Stats/SubTrackChallengeView/__snapshots__/index.jsx.snap index 272205bb7c..efa5ae911a 100644 --- a/__tests__/shared/components/ProfilePage/Stats/SubTrackChallengeView/__snapshots__/index.jsx.snap +++ b/__tests__/shared/components/ProfilePage/Stats/SubTrackChallengeView/__snapshots__/index.jsx.snap @@ -153,7 +153,7 @@ exports[`SubtrackChallengeView matches shallow snapshot 1`] = ` "id": 520456, "placement": 1, "score": 100, - "status": "Active", + "status": "ACTIVE", "submissionImage": null, "submittedAt": "2017-12-02T11:57Z", "type": "Contest Submission", @@ -343,7 +343,7 @@ exports[`SubtrackChallengeView matches shallow snapshot 1`] = ` "id": 520452, "placement": 1, "score": 100, - "status": "Active", + "status": "ACTIVE", "submissionImage": null, "submittedAt": "2017-12-02T11:11Z", "type": "Contest Submission", @@ -723,7 +723,7 @@ exports[`SubtrackChallengeView matches shallow snapshot 1`] = ` "id": 246093, "placement": 1, "score": 100, - "status": "Active", + "status": "ACTIVE", "submissionImage": null, "submittedAt": "2017-11-25T13:36Z", "type": "Contest Submission", @@ -939,7 +939,7 @@ exports[`SubtrackChallengeView matches shallow snapshot 1`] = ` "id": 245457, "placement": 1, "score": 100, - "status": "Active", + "status": "ACTIVE", "submissionImage": null, "submittedAt": "2017-11-13T22:25Z", "type": "Contest Submission", diff --git a/__tests__/shared/components/__snapshots__/Content.jsx.snap b/__tests__/shared/components/__snapshots__/Content.jsx.snap index b40694e901..6b8c698f66 100644 --- a/__tests__/shared/components/__snapshots__/Content.jsx.snap +++ b/__tests__/shared/components/__snapshots__/Content.jsx.snap @@ -1036,19 +1036,11 @@ exports[`Matches shallow shapshot 1`] = ` Thrive Articles Feed - - Demo of Thrive Articles Feed component - - Gigs Feed - - - - Demo of Gigs Feed component - + - Demo of Thrive Articles Feed component + TCO Leaderboards diff --git a/__tests__/shared/components/challenge-listing/__snapshots__/index.jsx.snap b/__tests__/shared/components/challenge-listing/__snapshots__/index.jsx.snap index e1664e107c..c51617d4c5 100644 --- a/__tests__/shared/components/challenge-listing/__snapshots__/index.jsx.snap +++ b/__tests__/shared/components/challenge-listing/__snapshots__/index.jsx.snap @@ -8,6 +8,7 @@ exports[`Matches shallow shapshot 1 shapshot 1 1`] = ` { + const signer = crypto.createSign(algorithm); + signer.update(data); + signer.end(); + return signer.sign(key); + }; +} +if (typeof crypto.verify !== 'function') { + crypto.verify = (algorithm, data, key, signature) => { + const verifier = crypto.createVerify(algorithm); + verifier.update(data); + verifier.end(); + return verifier.verify(key, signature); + }; +} + /* Provide TextEncoder/TextDecoder in older Node runtimes (e.g. Node 10). */ const { TextDecoder, TextEncoder } = require('util'); if (typeof global.TextEncoder === 'undefined') { diff --git a/config/backup-default.js b/config/backup-default.js index 4bacfa2b87..d36b8fc4c0 100644 --- a/config/backup-default.js +++ b/config/backup-default.js @@ -338,10 +338,6 @@ module.exports = { title: 'Competitive Programming', href: '/community/arena', }, - { - title: 'Gig Work', - href: '/gigs', - }, { title: 'Practice', href: '/community/practice', @@ -444,8 +440,6 @@ module.exports = { TC_EDU_SEARCH_PATH: '/search', TC_EDU_SEARCH_BAR_MAX_RESULTS_EACH_GROUP: 3, POLICY_PAGES_PATH: '/privacy', - GIGS_PAGES_PATH: '/gigs', - GIGS_LISTING_CACHE_TIME: 300, // in seconds START_PAGE_PATH: '/start', TC_ACADEMY_BASE_PATH: '/learn', GUIKIT: { diff --git a/config/default.js b/config/default.js index 7ea072a6cc..d3ea0dadf8 100644 --- a/config/default.js +++ b/config/default.js @@ -9,6 +9,7 @@ module.exports = { V4: 'https://api.topcoder-dev.com/v4', V5: 'https://api.topcoder-dev.com/v5', V6: 'https://api.topcoder-dev.com/v6', + ENGAGEMENTS: 'https://api.topcoder-dev.com/v6/engagements/engagements', MM_BROKER: '/api', }, @@ -106,6 +107,7 @@ module.exports = { COMMUNITY_APP: 'https://community-app.topcoder-dev.com', CHALLENGES_URL: 'https://www.topcoder-dev.com/challenges', COPILOTS_URL: 'https://copilots.topcoder-dev.com', + ENGAGEMENTS_APP: 'https://engagements.topcoder-dev.com', TCO_OPEN_URL: 'https://www.topcoder-dev.com/community/member-programs/topcoder-open', ARENA: 'https://arena.topcoder-dev.com', AUTH: 'https://accounts-auth0.topcoder-dev.com', @@ -337,12 +339,12 @@ module.exports = { href: '/challenges', }, { - title: 'Competitive Programming', - href: '/community/arena', + title: 'Engagements', + href: '/engagements', }, { - title: 'Gig Work', - href: '/gigs', + title: 'Competitive Programming', + href: '/community/arena', }, { title: 'Practice', @@ -446,8 +448,6 @@ module.exports = { TC_EDU_SEARCH_PATH: '/search', TC_EDU_SEARCH_BAR_MAX_RESULTS_EACH_GROUP: 3, POLICY_PAGES_PATH: '/privacy', - GIGS_PAGES_PATH: '/gigs', - GIGS_LISTING_CACHE_TIME: 300, // in seconds START_PAGE_PATH: '/start', TC_ACADEMY_BASE_PATH: '/learn', GUIKIT: { diff --git a/config/development.js b/config/development.js index 1310698c94..81baff8cb0 100644 --- a/config/development.js +++ b/config/development.js @@ -1,8 +1,12 @@ module.exports = { SEGMENT_IO_API_KEY: 'QBtLgV8vCiuRX1lDikbMjcoe9aCHkF6n', SERVER_API_KEY: '79b2d5eb-c1fd-42c4-9391-6b2c9780d591', + API: { + ENGAGEMENTS: 'https://api.topcoder-dev.com/v6/engagements/engagements', + }, URL: { USER_SETTINGS: '', /* No dev server is available for saved searches */ + ENGAGEMENTS_APP: 'https://engagements.topcoder-dev.com', REVIEW_API_URL: '/reviewTypes', }, PLATFORM_SITE_URL: 'https://platform.topcoder-dev.com', diff --git a/config/production.js b/config/production.js index da1d0d0a8a..62ce27dc85 100644 --- a/config/production.js +++ b/config/production.js @@ -5,6 +5,7 @@ module.exports = { V4: 'https://api.topcoder.com/v4', V5: 'https://api.topcoder.com/v5', V6: 'https://api.topcoder.com/v6', + ENGAGEMENTS: 'https://api.topcoder.com/v6/engagements/engagements', }, AUTH0: { DOMAIN: 'topcoder.auth0.com', @@ -28,6 +29,7 @@ module.exports = { COMMUNITY_APP: 'https://community-app.topcoder.com', CHALLENGES_URL: 'https://www.topcoder.com/challenges', COPILOTS_URL: 'https://copilots.topcoder.com', + ENGAGEMENTS_APP: 'https://engagements.topcoder.com', TCO_OPEN_URL: 'https://www.topcoder.com/community/member-programs/topcoder-open', AUTH: 'https://accounts-auth0.topcoder.com', @@ -128,12 +130,12 @@ module.exports = { href: '/challenges?ref=nav', }, { - title: 'Competitive Programming', - href: '/community/arena?ref=nav', + title: 'Engagements', + href: '/engagements?ref=nav', }, { - title: 'Gig Work', - href: '/gigs?ref=nav', + title: 'Competitive Programming', + href: '/community/arena?ref=nav', }, { title: 'Practice', diff --git a/config/qa.js b/config/qa.js index d709837e5d..7ac7b5291b 100644 --- a/config/qa.js +++ b/config/qa.js @@ -1,8 +1,12 @@ module.exports = { SEGMENT_IO_API_KEY: 'QBtLgV8vCiuRX1lDikbMjcoe9aCHkF6n', SERVER_API_KEY: '79b2d5eb-c1fd-42c4-9391-6b2c9780d591', + API: { + ENGAGEMENTS: 'https://api.topcoder-qa.com/v6/engagements/engagements', + }, URL: { USER_SETTINGS: '', /* No qa server is available for saved searches */ + ENGAGEMENTS_APP: 'https://engagements.topcoder-qa.com', }, PLATFORM_SITE_URL: 'https://platform.topcoder-qa.com', PLATFORMUI_SITE_URL: 'https://platform-ui.topcoder-qa.com', diff --git a/config/test.js b/config/test.js index 3197fa2bb6..c65bd35beb 100644 --- a/config/test.js +++ b/config/test.js @@ -1,3 +1,9 @@ module.exports = { SERVER_API_KEY: '79b2d5eb-c1fd-42c4-9391-6b2c9780d591', + API: { + ENGAGEMENTS: 'https://api.topcoder-dev.com/v6/engagements/engagements', + }, + URL: { + ENGAGEMENTS_APP: 'https://engagements.topcoder-dev.com', + }, }; diff --git a/package.json b/package.json index 7022593479..1a55fedcb4 100644 --- a/package.json +++ b/package.json @@ -164,7 +164,7 @@ "supertest": "^3.1.0", "tc-core-library-js": "github:topcoder-platform/tc-core-library-js#master", "tc-ui": "^1.0.12", - "topcoder-react-lib": "github:topcoder-platform/topcoder-react-lib#develop", + "topcoder-react-lib": "github:topcoder-platform/topcoder-react-lib#v6", "topcoder-react-ui-kit": "2.0.1", "topcoder-react-utils": "github:topcoder-platform/topcoder-react-utils#v6", "turndown": "^4.0.2", diff --git a/src/assets/images/back-arrow-gig-apply.svg b/src/assets/images/back-arrow-gig-apply.svg deleted file mode 100644 index ab3b9f0bbb..0000000000 --- a/src/assets/images/back-arrow-gig-apply.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - -BD4BD0F4-C75C-4897-A58C-B092051E2535 -Created with sketchtool. - - - - - - - - diff --git a/src/assets/images/gig-blob.svg b/src/assets/images/gig-blob.svg deleted file mode 100644 index 4b086d2cc5..0000000000 --- a/src/assets/images/gig-blob.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - EA5B2FA5-2439-489A-B86C-B537FC455656 - Created with sketchtool. - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/assets/images/gig-work/tag-dolars.png b/src/assets/images/gig-work/tag-dolars.png deleted file mode 100644 index ec9ca00301..0000000000 Binary files a/src/assets/images/gig-work/tag-dolars.png and /dev/null differ diff --git a/src/assets/images/gig-work/tag-hot.png b/src/assets/images/gig-work/tag-hot.png deleted file mode 100644 index 5bc7676d60..0000000000 Binary files a/src/assets/images/gig-work/tag-hot.png and /dev/null differ diff --git a/src/assets/images/gig-work/tag-new.png b/src/assets/images/gig-work/tag-new.png deleted file mode 100644 index 819c49cc10..0000000000 Binary files a/src/assets/images/gig-work/tag-new.png and /dev/null differ diff --git a/src/assets/images/icon-calendar-gig.svg b/src/assets/images/icon-calendar-gig.svg deleted file mode 100644 index 14b2544617..0000000000 --- a/src/assets/images/icon-calendar-gig.svg +++ /dev/null @@ -1,30 +0,0 @@ - - - - 7AC9DEAC-3F2A-487E-B616-A085E6B4D7FA - Created with sketchtool. - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/assets/images/img-gig-work.png b/src/assets/images/img-gig-work.png deleted file mode 100644 index 0b10ff6612..0000000000 Binary files a/src/assets/images/img-gig-work.png and /dev/null differ diff --git a/src/assets/mock-data/member-path-selector-data.json b/src/assets/mock-data/member-path-selector-data.json index 7091a0dab1..58c8f8faab 100644 --- a/src/assets/mock-data/member-path-selector-data.json +++ b/src/assets/mock-data/member-path-selector-data.json @@ -5,7 +5,7 @@ "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", + "contentText": "A competition-based alternative to a typical 9-5 job", "btnText": "Topcoder Challenges", "btnURL": "https://www.topcoder.com/challenges", "btnNewTab": true @@ -26,15 +26,6 @@ "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/server/index.js b/src/server/index.js index 795b51d485..d0fb675387 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -26,7 +26,6 @@ import { promisify } from 'util'; import cdnRouter from './routes/cdn'; import mockDocuSignFactory from './__mocks__/docu-sign-mock'; -import recruitCRMRouter from './routes/recruitCRM'; import mmLeaderboardRouter from './routes/mmLeaderboard'; import feedsRouter from './routes/feeds'; @@ -275,7 +274,6 @@ async function onExpressJsSetup(server) { }); server.use('/api/cdn', cdnRouter); - server.use('/api/recruit', recruitCRMRouter); server.use('/api/mml', mmLeaderboardRouter); server.use('/api/feeds', feedsRouter); diff --git a/src/server/routes/recruitCRM.js b/src/server/routes/recruitCRM.js deleted file mode 100644 index d523744a3f..0000000000 --- a/src/server/routes/recruitCRM.js +++ /dev/null @@ -1,65 +0,0 @@ -/** - * The routes related to RecruitCRM.io integration - */ - -import express from 'express'; -import { middleware } from 'tc-core-library-js'; -import config from 'config'; -import _ from 'lodash'; -import RecruitCRMService from '../services/recruitCRM'; - -const authenticator = middleware.jwtAuthenticator; -const authenticatorOptions = _.pick(config.SECRET.JWT_AUTH, ['AUTH_SECRET', 'VALID_ISSUERS']); -const cors = require('cors'); -const multer = require('multer'); - -const storage = multer.memoryStorage(); -const upload = multer({ - storage, - limits: { - fileSize: 8000000, - }, -}); -const routes = express.Router(); - -// Enables CORS on those routes according config above -// ToDo configure CORS for set of our trusted domains -// routes.use(cors()); -// routes.options('*', cors()); - -routes.options('/jobs', cors()); -routes.get('/jobs', cors(), (req, res, next) => new RecruitCRMService().getAllJobs(req, res, next)); - -routes.options('/jobs/cache', cors()); -routes.get('/jobs/cache', cors(), (req, res, next) => new RecruitCRMService().getJobsCacheStats(req, res, next)); - -routes.options('/jobs/cache/flush', cors()); -routes.get('/jobs/cache/flush', cors(), (req, res, next) => authenticator(authenticatorOptions)(req, res, next), (req, res, next) => new RecruitCRMService().getJobsCacheFlush(req, res, next)); - -routes.options('/jobs/search', cors()); -routes.get('/jobs/search', cors(), (req, res, next) => new RecruitCRMService().getJobs(req, res, next)); - -routes.options('/jobs/:id', cors()); -routes.get('/jobs/:id', cors(), (req, res, next) => new RecruitCRMService().getJob(req, res, next)); - -const applyOptions = { - origin: true, - methods: ['POST'], - credentials: true, - maxAge: 3600, - allowedHeaders: ['Content-Type', 'Authorization'], -}; -routes.options('/jobs/:id/apply', cors(applyOptions)); -routes.post('/jobs/:id/apply', cors(applyOptions), (req, res, next) => authenticator(authenticatorOptions)(req, res, next), upload.single('resume'), (req, res, next) => new RecruitCRMService().applyForJob(req, res, next)); - -routes.options('/candidates/search', cors()); -routes.get('/candidates/search', cors(), (req, res, next) => new RecruitCRMService().searchCandidates(req, res, next)); -// new router added -routes.options('/profile', cors()); -routes.get('/profile', cors(), (req, res, next) => authenticator(authenticatorOptions)(req, res, next), (req, res, next) => new RecruitCRMService().getProfile(req, res, next)); -routes.post('/profile', cors(), (req, res, next) => authenticator(authenticatorOptions)(req, res, next), upload.single('resume'), (req, res, next) => new RecruitCRMService().updateProfile(req, res, next)); - -routes.options('/taasjobs', cors()); -routes.get('/taasjobs', cors(), (req, res, next) => new RecruitCRMService().getJobsFromTaas(req, res, next)); - -export default routes; diff --git a/src/server/services/recruitCRM.js b/src/server/services/recruitCRM.js deleted file mode 100644 index c2d24bc46b..0000000000 --- a/src/server/services/recruitCRM.js +++ /dev/null @@ -1,786 +0,0 @@ -/** - * Server-side functions necessary for effective integration with recruitCRM - */ -import fetch from 'isomorphic-fetch'; -import config from 'config'; -import qs from 'qs'; -import _ from 'lodash'; -import { logger, services } from 'topcoder-react-lib'; -import Joi from 'joi'; -import xss from 'xss'; -import { sendEmailDirect } from './sendGrid'; -// import GSheetService from './gSheet'; - - -const { api } = services; - -const FormData = require('form-data'); -const NodeCache = require('node-cache'); - -// gigs list caching -const CACHE_KEY = 'jobs'; -const gigsCache = new NodeCache({ stdTTL: config.GIGS_LISTING_CACHE_TIME, checkperiod: 10 }); - -const JOB_FIELDS_RESPONSE = [ - 'id', - 'slug', - 'country', - 'locality', - 'city', - 'name', - 'custom_fields', - 'enable_job_application_form', - 'created_on', - 'updated_on', - 'min_annual_salary', - 'salary_type', - 'max_annual_salary', - 'job_description_text', - 'job_status', -]; -const CANDIDATE_FIELDS_RESPONSE = [ - 'id', - 'slug', - 'first_name', - 'last_name', - 'email', - 'contact_number', - 'skill', - 'resume', - 'locality', - 'salary_expectation', - 'custom_fields', -]; -const OMIT_CUSTOM_FIELDS = [ - 'Candidates Link', - 'Account Executive', - 'Wipro Centralization SPOC', -]; - -/** - * Send email to Kiril/Nick for debuging gig application errors - * @param {Object} error the error - */ -function notifyKirilAndNick(error) { - logger.error(error); - sendEmailDirect({ - personalizations: [ - { - to: [{ email: 'kiril.kartunov@gmail.com' }, { email: 'ncastillo@topcoder.com' }], - subject: 'Gig application error alert', - }, - ], - from: { email: 'noreply@topcoder.com' }, - content: [{ - type: 'text/plain', value: `The error occured as JSON string:\n\n ${JSON.stringify(error)}`, - }], - }); -} - -/** - * Sanitize Job before return - * @param {Object} job data from recuitcrm api - */ -function sanitizeJob(job) { - const sJob = _.pick(job, JOB_FIELDS_RESPONSE); - sJob.custom_fields = _.filter( - sJob.custom_fields, f => !_.includes(OMIT_CUSTOM_FIELDS, f.field_name), - ); - return sJob; -} - -const updateProfileSchema = Joi.object().keys({ - phone: Joi.string().required(), - availability: Joi.boolean().required(), - city: Joi.string().required(), - countryName: Joi.string().required(), -}).required(); - -/** - * Auxiliary class that handles communication with recruitCRM - */ -export default class RecruitCRMService { - /** - * Creates a new service instance. - * @param {String} baseUrl The base API endpoint. - */ - constructor(baseUrl = 'https://api.recruitcrm.io') { - this.private = { - baseUrl, - apiKey: config.SECRET.RECRUITCRM_API_KEY, - authorization: `Bearer ${config.SECRET.RECRUITCRM_API_KEY}`, - }; - } - - // eslint-disable-next-line class-methods-use-this - getJobsCacheStats(req, res) { - return res.send(gigsCache.getStats()); - } - - // eslint-disable-next-line class-methods-use-this - getJobsCacheFlush(req, res) { - gigsCache.flushAll(); - return res.send(gigsCache.getStats()); - } - - /** - * getJobsFromTaas endpoint. - * @return {Promise} - * @param {Object} the request. - */ - // eslint-disable-next-line class-methods-use-this - async getJobsFromTaas(req, res, next) { - try { - const m2mToken = await api.getTcM2mToken(); - const v5api = api.getApiV5(m2mToken); - const jobs = await v5api.get(`/jobs?${qs.stringify(req.query)}`); - return res.send({ - jobs: await jobs.json(), - }); - } catch (err) { - return next(err); - } - } - - /** - * Gets jobs endpoint. - * @return {Promise} - * @param {Object} the request. - */ - async getJobs(req, res, next) { - try { - const response = await fetch(`${this.private.baseUrl}/v1/jobs/search?${qs.stringify(req.query)}`, { - method: 'GET', - headers: { - 'Content-Type': req.headers['content-type'], - Authorization: this.private.authorization, - }, - }); - if (response.status === 429) { - await new Promise(resolve => setTimeout(resolve, 30000)); // wait 30sec - return this.getJobs(req, res, next); - } - if (response.status >= 400) { - const error = { - error: true, - status: response.status, - url: `${this.private.baseUrl}/v1/jobs/search?${qs.stringify(req.query)}`, - errObj: await response.json(), - }; - logger.error(error); - return res.send(error); - } - const data = await response.json(); - - data.data = _.map(data.data, j => sanitizeJob(j)); - - return res.send(data); - } catch (err) { - return next(err); - } - } - - /** - * Gets job by id endpoint. - * @return {Promise} - * @param {Object} the request. - */ - async getJob(req, res, next) { - try { - const sanitizedId = xss(req.params.id); - - if (!/^[a-zA-Z0-9-_]{8,23}$/.test(sanitizedId)) { - return res.status(400).json({ error: 'Invalid job ID format.' }); - } - const response = await fetch(`${this.private.baseUrl}/v1/jobs/${sanitizedId}`, { - method: 'GET', - headers: { - 'Content-Type': req.headers['content-type'], - Authorization: this.private.authorization, - }, - }); - if (response.status === 429) { - await new Promise(resolve => setTimeout(resolve, 30000)); // wait 30sec - return this.getJob(req, res, next); - } - if (response.status >= 400) { - const error = { - error: true, - status: response.status, - url: `${this.private.baseUrl}/v1/jobs/${sanitizedId}`, - errObj: await response.json(), - }; - logger.error(error); - return res.send(error); - } - const data = await response.json(); - - // If job or form not open return just job status - if ((data.job_status && data.job_status.id !== 1) - || data.enable_job_application_form !== 1) { - return res.send({ - job_status: data.job_status, - enable_job_application_form: data.enable_job_application_form, - }); - } - - return res.send(sanitizeJob(data)); - } catch (err) { - return next(err); - } - } - - /** - * Gets all jobs method. - * @return {Promise} - * @param {Object} query the request query. - */ - async getAll(query) { - try { - const response = await fetch(`${this.private.baseUrl}/v1/jobs/search?${qs.stringify(query)}`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: this.private.authorization, - }, - }); - if (response.status === 429) { - await new Promise(resolve => setTimeout(resolve, 30000)); // wait 30sec - return this.getAll(query); - } - if (response.status >= 400) { - const error = { - error: true, - status: response.status, - url: `${this.private.baseUrl}/v1/jobs/search?${qs.stringify(query)}`, - errObj: await response.json(), - }; - return error; - } - const data = await response.json(); - if (data.current_page < data.last_page) { - const pages = _.range(2, data.last_page + 1); - return Promise.all( - pages.map(page => fetch(`${this.private.baseUrl}/v1/jobs/search?${qs.stringify(query)}&page=${page}`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: this.private.authorization, - }, - })), - ) - .then(async (allPages) => { - // eslint-disable-next-line no-restricted-syntax - for (const pageDataRsp of allPages) { - // eslint-disable-next-line no-await-in-loop - const pageData = await pageDataRsp.json(); - data.data = _.flatten(data.data.concat(pageData.data)); - } - - // Filter by Job Application active - data.data = _.filter(data.data, job => job.enable_job_application_form === 1); - - const toSend = _.map(data.data, j => sanitizeJob(j)); - return toSend; - }); - } - - // Filter by Job Application active - data.data = _.filter(data.data, job => job.enable_job_application_form === 1); - - const toSend = _.map(data.data, j => sanitizeJob(j)); - return toSend; - } catch (err) { - return err; - } - } - - /** - * Gets all jobs endpoint. - * @return {Promise} - * @param {Object} the request. - */ - async getAllJobs(req, res, next) { - if (gigsCache.has(CACHE_KEY)) { - return res.send(gigsCache.get(CACHE_KEY)); - } - try { - const response = await fetch(`${this.private.baseUrl}/v1/jobs/search?${qs.stringify(req.query)}`, { - method: 'GET', - headers: { - 'Content-Type': req.headers['content-type'], - Authorization: this.private.authorization, - }, - }); - if (response.status === 429) { - await new Promise(resolve => setTimeout(resolve, 30000)); // wait 30sec - return this.getJobs(req, res, next); - } - if (response.status >= 400) { - const error = { - error: true, - status: response.status, - url: `${this.private.baseUrl}/v1/jobs/search?${qs.stringify(req.query)}`, - errObj: await response.json(), - }; - logger.error(error); - return res.send(error); - } - const data = await response.json(); - if (data.current_page < data.last_page) { - const pages = _.range(2, data.last_page + 1); - return Promise.all( - pages.map(page => fetch(`${this.private.baseUrl}/v1/jobs/search?${qs.stringify(req.query)}&page=${page}`, { - method: 'GET', - headers: { - 'Content-Type': req.headers['content-type'], - Authorization: this.private.authorization, - }, - })), - ) - .then(async (allPages) => { - // eslint-disable-next-line no-restricted-syntax - for (const pageDataRsp of allPages) { - // eslint-disable-next-line no-await-in-loop - const pageData = await pageDataRsp.json(); - data.data = _.flatten(data.data.concat(pageData.data)); - } - - // Filter by Job Application active - data.data = _.filter(data.data, job => job.enable_job_application_form === 1); - - const toSend = _.map(data.data, j => sanitizeJob(j)); - gigsCache.set(CACHE_KEY, toSend); - return res.send(toSend); - }) - .catch(e => res.send({ - error: e, - })); - } - - // Filter by Job Application active - data.data = _.filter(data.data, job => job.enable_job_application_form === 1); - - const toSend = _.map(data.data, j => sanitizeJob(j)); - gigsCache.set(CACHE_KEY, toSend); - return res.send(toSend); - } catch (err) { - return next(err); - } - } - - /** - * Search for candidate by email endpoint. - * @return {Promise} - * @param {Object} the request. - */ - async searchCandidates(req, res, next) { - try { - const response = await fetch(`${this.private.baseUrl}/v1/candidates/search?${qs.stringify(req.query)}`, { - method: 'GET', - headers: { - 'Content-Type': req.headers['content-type'], - Authorization: this.private.authorization, - }, - }); - if (response.status === 429) { - await new Promise(resolve => setTimeout(resolve, 30000)); // wait 30sec - return this.searchCandidates(req, res, next); - } - if (response.status >= 400) { - const error = { - error: true, - status: response.status, - url: `${this.private.baseUrl}/v1/candidates/search?${qs.stringify(req.query)}`, - errObj: await response.json(), - }; - logger.error(error); - return res.send(error); - } - const data = await response.json(); - data.data = _.map(data.data, j => _.pick(j, CANDIDATE_FIELDS_RESPONSE)); - return res.send(data); - } catch (err) { - return next(err); - } - } - - /** - * Apply for candidate for job endpoint. - * @return {Promise} - * @param {Object} the request. - */ - async applyForJob(req, res, next) { - const { id } = req.params; - const { body, file } = req; - const form = JSON.parse(body.form); - const fileData = new FormData(); - if (file) { - fileData.append('resume', file.buffer, file.originalname); - } - let candidateSlug; - let isNewCandidate = true; - const isReferred = false; - let growRes; - try { - // Check if candidate exists in the system? - const candidateResponse = await fetch(`${this.private.baseUrl}/v1/candidates/search?email=${form.email}`, { - method: 'GET', - headers: { - 'Content-Type': req.headers['content-type'], - Authorization: this.private.authorization, - }, - }); - if (candidateResponse.status >= 300) { - const error = { - error: true, - status: candidateResponse.status, - url: `${this.private.baseUrl}/v1/candidates/search?email=${form.email}`, - errorObj: await candidateResponse.json(), - }; - notifyKirilAndNick(error); - return res.send(error); - } - let candidateData = await candidateResponse.json(); - if (candidateData.data) { - // Candidate exists in recruitCRM - // We will update profile fields, otherwise we create new candidate below - // Check if candidate is placed in gig currently - isNewCandidate = false; - const candStatusIndex = _.findIndex( - candidateData.data[0].custom_fields, { field_id: 12 }, - ); - if (candStatusIndex !== -1 && candidateData.data[0].custom_fields[candStatusIndex].value === 'Placed') { - // reject application - return res.send({ - error: true, - errorObj: { - notAllowed: true, - message: 'Apologies, you are not allowed to apply to gigs if you are already placed on a gig.', - }, - }); - } - candidateSlug = candidateData.data[0].slug; - const fieldIndexProfile = _.findIndex( - candidateData.data[0].custom_fields, { field_id: 14 }, - ); - const fieldIndexForm = _.findIndex(form.custom_fields, { field_id: 14 }); - if (fieldIndexProfile !== -1 && fieldIndexForm !== -1) { - form.custom_fields[fieldIndexForm].value += `;${candidateData.data[0].custom_fields[fieldIndexProfile].value}`; - if (form.custom_fields[fieldIndexForm].value.length > 2000) { - form.custom_fields[fieldIndexForm].value = form.custom_fields[ - fieldIndexForm].value.slice(0, 2000); - } - } - } - // Create/update candidate profile - const workCandidateResponse = await fetch(`${this.private.baseUrl}/v1/candidates${candidateSlug ? `/${candidateSlug}` : ''}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: this.private.authorization, - }, - body: JSON.stringify(form), - }); - if (workCandidateResponse.status >= 300) { - const error = { - error: true, - status: workCandidateResponse.status, - url: `${this.private.baseUrl}/v1/candidates${candidateSlug ? `/${candidateSlug}` : ''}`, - form, - errorObj: await workCandidateResponse.json(), - }; - notifyKirilAndNick(error); - return res.send(error); - } - candidateData = await workCandidateResponse.json(); - // Attach resume to candidate if uploaded - if (file) { - const formHeaders = fileData.getHeaders(); - const fileCandidateResponse = await fetch(`${this.private.baseUrl}/v1/candidates/${candidateData.slug}`, { - method: 'POST', - headers: { - Authorization: this.private.authorization, - ...formHeaders, - }, - body: fileData, - }); - if (fileCandidateResponse.status >= 300) { - const error = { - error: true, - status: fileCandidateResponse.status, - url: `${this.private.baseUrl}/v1/candidates/${candidateData.slug}`, - form, - fileData, - file, - formHeaders, - errorObj: await fileCandidateResponse.json(), - }; - notifyKirilAndNick(error); - return res.send(error); - } - candidateData = await fileCandidateResponse.json(); - } - // Candidate ready to apply for job - const applyResponse = await fetch(`${this.private.baseUrl}/v1/candidates/${candidateData.slug}/assign?job_slug=${id}`, { - method: 'POST', - headers: { - 'Content-Type': req.headers['content-type'], - Authorization: this.private.authorization, - }, - }); - if (applyResponse.status >= 300) { - const errObj = await applyResponse.json(); - if (errObj.errorCode === 422 && errObj.errorMessage === 'Candidate is already assigned to this job') { - return res.send({ - success: true, - }); - } - const error = { - error: true, - status: applyResponse.status, - url: `${this.private.baseUrl}/v1/candidates/${candidateData.slug}/assign?job_slug=${id}`, - form, - candidateData, - errorObj: errObj, - }; - notifyKirilAndNick(error); - return res.send(error); - } - // Set hired-stage - const hireStageResponse = await fetch(`${this.private.baseUrl}/v1/candidates/${candidateData.slug}/hiring-stages/${id}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: this.private.authorization, - }, - body: JSON.stringify({ - candidate_slug: candidateData.slug, - job_slug: id, - status_id: '10', - }), - }); - if (hireStageResponse.status >= 300) { - const error = { - error: true, - status: hireStageResponse.status, - url: `$${this.private.baseUrl}/v1/candidates/${candidateData.slug}/hiring-stages/${id}`, - form, - errorObj: await hireStageResponse.json(), - }; - notifyKirilAndNick(error); - return res.send(error); - } - // For new candidates that apply via referral link - // aka triggered referral state step 1 - notify and etc. housekeeping tasks - if (isNewCandidate && isReferred && !growRes.error) { - // update the tracking sheet - // enable that code when issue with service account key structure is resolved - // const gs = new GSheetService(); - // await gs.addToSheet(config.GIG_REFERRALS_SHEET, [[ - // `${form.first_name} ${form.last_name}`, - // form.email, - // `https://app.recruitcrm.io/candidate/${candidateData.slug}`, - // `https://topcoder.com/members/${form.custom_fields[tcHandle].value}`, - // `${growRes.referrer.firstName} ${growRes.referrer.lastName}`, - // growRes.referrer.email, - // `https://topcoder.com/members/${growRes.referrer.metadata.tcHandle}`, - // `https://app.recruitcrm.io/job/${id}`, - // ]]); - // Notify the person who referred - sendEmailDirect({ - personalizations: [ - { - to: [{ email: growRes.referrer.email }], - subject: 'Thanks for your Topcoder referral!', - }, - ], - from: { email: 'noreply@topcoder.com', name: 'The Topcoder Community Team' }, - content: [{ - type: 'text/html', value: `

Hello ${growRes.referrer.metadata.tcHandle},

You just made our day! Sharing a Topcoder Gig Work opportunity is the BEST compliment you can give us. So pat yourself on the back, give yourself a hi-five, or just stand up and dance like no one is watching. You deserve it!

Did you know many of our Topcoder members consider earning through Topcoder to be life changing? There must be something in the air. You have just taken the first step into helping someone you know change their life for the better.

Because of that, we are excited to reward you. $500 is earned for every referral you send us that gets the gig. Learn more here.

Thank you for sharing Topcoder Gigs and hope to see you around here again soon!

- The Topcoder Community Team

`, - }], - }); - } - // respond to API call - const data = await applyResponse.json(); - return res.send(data); - } catch (err) { - return next(err); - } - } - - /** - * Get user profile endpoint. - * @return {Promise} - * @param {Object} the request. - */ - async getProfile(req, res, next) { - try { - // get candidate by email - const candidate = await this.getCandidateByEmail(req.authUser.email); - // return error if getCandidateByEmail operation failed - if (candidate.error) { - const error = candidate; - logger.error(error); - const responseNoProfileMapping = { - hasProfile: false, - }; - return res.send(responseNoProfileMapping); - } - // apply desired response format - const responseMapping = { - hasProfile: true, - phone: candidate.contact_number, - salaryExpectation: candidate.salary_expectation, - skill: candidate.skill, - resume: candidate.resume, - availability: _.isNil(candidate.available_from) ? true - : new Date(candidate.available_from) <= new Date(), - }; - return res.send(responseMapping); - } catch (err) { - return next(err); - } - } - - /** - * Update user profile endpoint. - * @return {Promise} - * @param {Object} the request. - */ - async updateProfile(req, res, next) { - const { body, file } = req; - // validate provided data - const validationResult = updateProfileSchema.validate(body); - if (validationResult.error) { - return res.status(400).send({ message: validationResult.error.message }); - } - const fileData = new FormData(); - if (file) { - fileData.append('resume', file.buffer, file.originalname); - } - try { - // get candidate by email - const candidate = await this.getCandidateByEmail(req.authUser.email); - // return error if getCandidateByEmail operation failed - if (candidate.error) { - const error = candidate; - logger.error(error); - return res.status(error.status).send(error); - } - const candidateSlug = candidate.slug; - const form = { - city: body.city, - locality: body.countryName, - contact_number: body.phone, - available_from: body.availability === 'true' ? new Date().toISOString() : new Date('2100-01-01').toISOString(), - }; - // update candidate profile - const response = await fetch(`${this.private.baseUrl}/v1/candidates/${candidateSlug}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: this.private.authorization, - }, - body: JSON.stringify(form), - }); - if (response.status >= 300) { - const error = { - error: true, - status: response.status, - url: `${this.private.baseUrl}/v1/candidates${candidateSlug}`, - form, - errorObj: await response.json(), - }; - logger.error(error); - return res.status(error.status).send(error); - } - // Attach resume to candidate if uploaded - if (file) { - const formHeaders = fileData.getHeaders(); - const fileResponse = await fetch(`${this.private.baseUrl}/v1/candidates/${candidateSlug}`, { - method: 'POST', - headers: { - Authorization: this.private.authorization, - ...formHeaders, - }, - body: fileData, - }); - if (fileResponse.status >= 300) { - const error = { - error: true, - status: fileResponse.status, - url: `${this.private.baseUrl}/v1/candidates/${candidateSlug}`, - file, - formHeaders, - errorObj: await fileResponse.json(), - }; - logger.error(error); - return res.status(error.status).send(error); - } - } - return res.status(204).end(); - } catch (err) { - return next(err); - } - } - - /** - * Get candidate by email - * @return {object} result of the search operation - * @param {string} email email address of the user. - */ - async getCandidateByEmail(email) { - const query = { - email, - }; - const url = `${this.private.baseUrl}/v1/candidates/search?${qs.stringify(query)}`; - const response = await fetch(url, { - method: 'GET', - headers: { - Authorization: this.private.authorization, - }, - }); - if (response.status === 429) { - await new Promise(resolve => setTimeout(resolve, 30000)); // wait 30sec - return this.getCandidateByEmail(email); - } - if (response.status >= 300) { - const error = { - error: true, - status: response.status, - url, - errObj: await response.json(), - }; - return error; - } - const data = await response.json(); - // return error object if candidate with provided email not found - if ((_.isArray(data) && data.length === 0) || data.data.length === 0) { - const error = { - error: true, - status: 404, - url, - errObj: { - message: `No candidate was found with email: ${email}`, - }, - }; - return error; - } - // return first candidate - return data.data[0]; - } -} - -// Self update cache on expire to keep it fresh -gigsCache.on('expired', async (key) => { - if (key === CACHE_KEY) { - const ss = new RecruitCRMService(); - const gigs = await ss.getAll({ - job_status: 1, - }); - if (!gigs.error) { - gigsCache.set(CACHE_KEY, gigs); - } - } -}); diff --git a/src/shared/actions/engagements.js b/src/shared/actions/engagements.js new file mode 100644 index 0000000000..16dbc8cd64 --- /dev/null +++ b/src/shared/actions/engagements.js @@ -0,0 +1,47 @@ +import _ from 'lodash'; +import { createActions } from 'redux-actions'; +import { errors } from 'topcoder-react-lib'; +import getEngagements from 'services/engagements'; + +const { fireErrorMessage } = errors; + +const PAGE_SIZE = 10; + +function getEngagementsInit(uuid, page, filters) { + return { uuid, page, filters }; +} + +async function getEngagementsDone(uuid, page, filters, tokenV3) { + try { + const { engagements, meta } = await getEngagements(page, PAGE_SIZE, filters, tokenV3); + + return { + uuid, + engagements, + meta, + page, + }; + } catch (error) { + const { message: errorMessage } = error || {}; + let message = 'Unknown error'; + if (errorMessage) { + message = errorMessage; + } else if (typeof error === 'string') { + message = error; + } + fireErrorMessage('Error Loading Engagements', message); + const rejection = error instanceof Error ? error : new Error(message); + rejection.uuid = uuid; + rejection.originalError = error; + return Promise.reject(rejection); + } +} + +export default createActions({ + ENGAGEMENTS: { + GET_ENGAGEMENTS_INIT: getEngagementsInit, + GET_ENGAGEMENTS_DONE: getEngagementsDone, + DROP_ENGAGEMENTS: _.noop, + SET_FILTER: _.identity, + }, +}); diff --git a/src/shared/actions/recruitCRM.js b/src/shared/actions/recruitCRM.js deleted file mode 100644 index 1597b45fa7..0000000000 --- a/src/shared/actions/recruitCRM.js +++ /dev/null @@ -1,202 +0,0 @@ -import { redux } from 'topcoder-react-utils'; -import Service from 'services/recruitCRM'; -import _ from 'lodash'; -import { getCustomField } from 'utils/gigs'; - -/** - * Jobs page fetch init - */ -function getJobsInit() { - return {}; -} - -/** - * Jobs page fetch done - */ -async function getJobsDone(query) { - const ss = new Service(); - const res = await ss.getAllJobs(query); - - return { - data: res, - }; -} - -function getJobApplicationsInit() { - return {}; -} - -async function getJobApplicationsDone(tokenV3) { - const ss = new Service(); - const res = await ss.getJobApplications(tokenV3); - return { - data: res, - }; -} - -/** - * Job fetch init - */ -function getJobInit(id) { - return { id }; -} - -/** - * Job fetch done - */ -async function getJobDone(id) { - const ss = new Service(); - const res = await ss.getJob(id); - - return { - id, - data: res, - }; -} - -/** - * Apply for Job init - */ -function applyForJobInit(job, payload) { - return { id: job.slug, payload }; -} - -/** - * Helper utitlity - * @param {object} joib The job object - * @param {object} payload The apply form payload - */ -function normalizeRecruitPayload(job, payload) { - const perJob = [ - `${job.name} ->`, - `Pay Expectation: ${payload.payExpectation}`, - `Able to work during timezone? ${payload.timezoneConfirm.filter(s => s.value).map(() => getCustomField(job.custom_fields, 'Timezone')).join(',')}`, - `Am I ok to work the duration? ${payload.durationConfirm.filter(s => s.value).map(() => getCustomField(job.custom_fields, 'Duration')).join(',')}`, - ]; - const referral = _.find(payload.reffereal, { selected: true }); - const normalized = { - last_name: payload.lname, - first_name: payload.fname, - email: payload.email, - contact_number: payload.phone, - city: payload.city, - locality: _.find(payload.country, { selected: true }).label, - salary_expectation: _.trim(payload.payExpectation), - skill: payload.skills.filter(s => s.selected).map(s => s.label).join(','), - custom_fields: [ - { - field_id: 1, - value: payload.tcProfileLink || (payload.handle ? `https://topcoder.com/members/${payload.handle}` : ''), - }, - { - field_id: 2, - value: payload.handle || '', - }, - { - field_id: 14, - value: perJob.join(','), - }, - ], - }; - if (referral) { - normalized.custom_fields.push({ - field_id: 13, - value: referral.label, - }); - } - if (payload.fileCV) { - normalized.resume = payload.fileCV; - } - - return normalized; -} - -/** - * Apply for Job done - */ -async function applyForJobDone(job, payload, tokenV3) { - const ss = new Service(); - try { - const res = await ss.applyForJob(job.slug, normalizeRecruitPayload(job, payload), tokenV3); - - return { - id: job.slug, - data: res, - }; - } catch (error) { - return { - id: job.slug, - data: { - error: true, - errorObj: error, - }, - }; - } -} - -/** - * Search for cnadidate in recruit - */ -function searchCandidatesInit(email) { - return { email }; -} - -/** - * Search for cnadidate in recruit and get profile if available - * @param {string} email the email to search - */ -async function searchCandidatesDone(email) { - const ss = new Service(); - try { - const res = await ss.searchCandidates(email); - - return { - email, - data: res, - }; - } catch (error) { - return { - email, - data: { - error: true, - errorObj: error, - }, - }; - } -} - -/** - * Gigs fetch init - */ -function getGigsInit() { - return {}; -} - -/** - * Gigs fetch done - */ -async function getGigsDone(query) { - const ss = new Service(); - const res = await ss.getTaasJobs(query); - - return { - data: res.jobs, - }; -} - -export default redux.createActions({ - RECRUIT: { - GET_JOBS_INIT: getJobsInit, - GET_JOBS_DONE: getJobsDone, - GET_JOB_INIT: getJobInit, - GET_JOB_DONE: getJobDone, - APPLY_FOR_JOB_INIT: applyForJobInit, - APPLY_FOR_JOB_DONE: applyForJobDone, - SEARCH_CANDIDATES_INIT: searchCandidatesInit, - SEARCH_CANDIDATES_DONE: searchCandidatesDone, - GET_JOB_APPLICATIONS_INIT: getJobApplicationsInit, - GET_JOB_APPLICATIONS_DONE: getJobApplicationsDone, - GET_GIGS_INIT: getGigsInit, - GET_GIGS_DONE: getGigsDone, - }, -}); diff --git a/src/shared/components/Content/index.jsx b/src/shared/components/Content/index.jsx index a2e2d46815..cb683fa6bd 100644 --- a/src/shared/components/Content/index.jsx +++ b/src/shared/components/Content/index.jsx @@ -836,11 +836,6 @@ export default function Content() { {' '} - Demo of Thrive Articles Feed component - - Gigs Feed - - {' '} - - Demo of Gigs Feed component TCO Leaderboards diff --git a/src/shared/components/Contentful/AppComponent/index.jsx b/src/shared/components/Contentful/AppComponent/index.jsx index 22ba9de307..90b08f88ee 100644 --- a/src/shared/components/Contentful/AppComponent/index.jsx +++ b/src/shared/components/Contentful/AppComponent/index.jsx @@ -9,7 +9,6 @@ import PT from 'prop-types'; import React from 'react'; import { errors } from 'topcoder-react-lib'; import Leaderboard from 'containers/tco/Leaderboard'; -import RecruitCRMJobs from 'containers/Gigs/RecruitCRMJobs'; import GSheet from 'containers/GSheet'; import PathSelector from 'components/MemberPath/PathSelector'; @@ -52,9 +51,6 @@ export function AppComponentSwitch(appComponent) { /> ); } - if (type === 'RecruitCRM-Jobs') { - return ; - } if (type === 'GSheet') { return ; } diff --git a/src/shared/components/Dashboard/GigsFeed/index.jsx b/src/shared/components/Dashboard/GigsFeed/index.jsx deleted file mode 100644 index ecd0e0f3bd..0000000000 --- a/src/shared/components/Dashboard/GigsFeed/index.jsx +++ /dev/null @@ -1,63 +0,0 @@ -/* eslint-disable no-nested-ternary */ -/** - * Gigs Feed component - */ -import LoadingIndicator from 'components/LoadingIndicator'; -import PT from 'prop-types'; -import React from 'react'; -import './styles.scss'; -import { config } from 'topcoder-react-utils'; - -export default function GigsFeed({ - gigs, - loading, - theme, -}) { - const formatRateType = rateType => `/${rateType === 'weekly' ? 'week' : rateType}`; - return ( -
-
- GIGS - View all gigs - -
-
- {loading ?
- : gigs.message ? {gigs.message} : gigs.map(gig => ( -
- {gig.title} - -
- - ${`${(gig.minSalary || 0).toLocaleString()} - $${ - (gig.maxSalary || 0).toLocaleString()}` - } - - {formatRateType(gig.rateType)} -
-
- ))} -
-
- ); -} - -GigsFeed.defaultProps = { - gigs: [], - theme: 'light', -}; - -GigsFeed.propTypes = { - gigs: PT.arrayOf(PT.shape()), - loading: PT.bool.isRequired, - theme: PT.oneOf(['dark', 'light']), -}; diff --git a/src/shared/components/Dashboard/GigsFeed/styles.scss b/src/shared/components/Dashboard/GigsFeed/styles.scss deleted file mode 100644 index 060b9e0dbe..0000000000 --- a/src/shared/components/Dashboard/GigsFeed/styles.scss +++ /dev/null @@ -1,130 +0,0 @@ -@import "~styles/mixins"; - -.loading { - height: 195px; - width: 100%; - display: flex; - align-items: center; - justify-content: center; - - @include xs-to-sm { - height: 285px; - } -} - -.container { - @include roboto-regular; - - color: $tc-gray-90; - margin-bottom: 30px; -} - -.header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 12px 0; - - .title { - @include barlow-semi-bold; - - font-size: 16px; - } - - .allLink { - color: $dashboard-teal; - font-size: 13px; - line-height: 20px; - - &:hover { - text-decoration: none !important; - } - - span { - @include xs-to-sm { - display: none; - } - } - } -} - -.gigs { - padding: 0 14px; - background-color: $tc-white; - border-radius: 8px; - height: 195px !important; - width: 100%; - - @include xs-to-sm { - height: 285px !important; - } - - .row { - display: flex; - font-size: 14px; - line-height: 18px; - align-items: center; - justify-content: space-between; - padding: 10px 0; - border-top: 1px solid $tc-gray-05; - - a { - margin-right: 8px; - } - - .salary { - display: flex; - flex-wrap: nowrap; - align-items: flex-end; - justify-content: center; - } - - .amount { - text-align: right; - } - - .rateType { - margin-left: 7px; - text-align: right; - } - - &:first-child { - border: none; - } - - @include xs-to-sm { - .salary { - display: flex; - flex: 1 0 90px; - flex-direction: column; - } - - .amount { - white-space: nowrap; - } - } - } -} - -.light { - background: transparent; -} - -.container.dark { - color: $tc-white; - - .header { - .allLink { - color: $dashboard-dark-link; - text-decoration: underline; - } - } - - .gigs { - background: $dashboard-dark-card-bg; - - .row { - border-top: 1px solid $dashboard-dark-bg; - } - } -} diff --git a/src/shared/components/GUIKit/DropdownSingleSkills/index.jsx b/src/shared/components/GUIKit/DropdownSingleSkills/index.jsx index 2c43181844..8c6a08080d 100644 --- a/src/shared/components/GUIKit/DropdownSingleSkills/index.jsx +++ b/src/shared/components/GUIKit/DropdownSingleSkills/index.jsx @@ -21,6 +21,12 @@ function DropdownSingleSkills({ loadOptions, createText, }) { + let normalizedTerms = null; + if (terms && typeof terms === 'object') { + normalizedTerms = terms; + } else if (terms) { + normalizedTerms = { value: terms, label: terms }; + } const containerRef = useRef(null); useEffect(() => { const selectInput = containerRef.current.getElementsByClassName('Select-input'); @@ -79,17 +85,11 @@ function DropdownSingleSkills({ { - onChange(value ? (value.value || '') : ''); + onChange(value || null); }} - defaultValue={terms ? { - value: terms, - label: terms, - } : null} + defaultValue={normalizedTerms} promptTextCreator={value => `${createText} "${value}"`} placeholder={`${placeholder}${placeholder && required ? ' *' : ''}`} cacheOptions={cacheOptions} @@ -120,7 +120,13 @@ DropdownSingleSkills.defaultProps = { }; DropdownSingleSkills.propTypes = { - terms: PT.string, + terms: PT.oneOfType([ + PT.string, + PT.shape({ + label: PT.string, + value: PT.string, + }), + ]), placeholder: PT.string, label: PT.string, required: PT.bool, diff --git a/src/shared/components/GUIKit/JobListCard/index.jsx b/src/shared/components/GUIKit/JobListCard/index.jsx deleted file mode 100644 index 26510b0c10..0000000000 --- a/src/shared/components/GUIKit/JobListCard/index.jsx +++ /dev/null @@ -1,79 +0,0 @@ -/* eslint-disable max-len */ -/** - * Job card component. - */ -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-calendar.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'; - -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'); - if (skills !== 'n/a') { - skills = skills.split(','); - if (skills.length > 2) { - skills = `${skills.slice(0, 2).join(', ')},...`; - } else { - skills = skills.join(', '); - } - } - const tag = getCustomField(job.custom_fields, 'Job Tag'); - const onHotlistApply = () => { - optimizely.track('View Details Click'); - }; - - return ( -
- { - tag !== 'n/a' && gig-job-tag - } - {job.name} -
-
- skills-icon {skills} -
-
- {job.country} -
-
- ${job.min_annual_salary} - {job.max_annual_salary} (USD) / {getSalaryType(job.salary_type || {})} -
-
- {/^\d+$/.test(duration) ? `${duration} Weeks` : duration} -
-
- VIEW DETAILS -
-
-
- ); -} - -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 deleted file mode 100644 index cd3b04f59a..0000000000 --- a/src/shared/components/GUIKit/JobListCard/style.scss +++ /dev/null @@ -1,140 +0,0 @@ -@import "~components/GUIKit/Assets/Styles/default"; -@import "~components/Contentful/default"; - -.container { - border: 1px solid #e9e9e9; - border-radius: 10px; - display: flex; - flex-direction: column; - color: #2a2a2a; - 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, - .gig-name:hover { - color: #1e94a3; - margin-top: 0; - margin-bottom: 12px; - text-decoration: none; - font-family: Barlow, sans-serif; - font-size: 20px; - font-weight: 600; - line-height: 24px; - text-transform: uppercase; - - @include xs-to-sm { - margin-bottom: 20px; - } - } - - .job-infos { - display: flex; - - @include xs-to-sm { - flex-direction: column; - } - - .icon-val { - display: flex; - align-items: center; - - @include xs-to-sm { - margin-bottom: 20px; - } - - &: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: 345px; - - @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 { - margin-right: 0; - width: 141px; - } - - svg, - img { - margin-right: 7px; - width: 20px; - height: 20px; - } - } - - .row-btn { - display: flex; - justify-content: flex-end; - flex: 1; - - @include xs-to-sm { - justify-content: flex-start; - } - - button.primary-green-md { - outline: none; - - @include primary-green; - @include md; - - &:hover { - @include primary-green; - } - - &:disabled, - &:hover:disabled { - background-color: #e9e9e9 !important; - border: none !important; - text-decoration: none !important; - color: #fafafb !important; - box-shadow: none !important; - } - } - } - } -} diff --git a/src/shared/components/GUIKit/SearchCombo/index.jsx b/src/shared/components/GUIKit/SearchCombo/index.jsx index 3be805bf38..e07288f5ee 100644 --- a/src/shared/components/GUIKit/SearchCombo/index.jsx +++ b/src/shared/components/GUIKit/SearchCombo/index.jsx @@ -22,10 +22,14 @@ function SearchCombo({ } else { getService(auth.tokenV3).getSkills(inputValue).then( (response) => { - const suggestedOptions = (response || []).map(skillItem => ({ - label: skillItem.name, - value: skillItem.name, - })); + const suggestedOptions = (response || []) + .map((skillItem) => { + const label = skillItem && (skillItem.name || skillItem.label || skillItem.title); + if (!label) return null; + const value = skillItem.id || skillItem.skillId || skillItem.value || label; + return { label, value }; + }) + .filter(Boolean); return callback(null, { options: suggestedOptions, }); @@ -40,8 +44,9 @@ function SearchCombo({ terms={skills} placeholder={placeholder} onChange={(newSkill) => { - setSkills(newSkill); - onSearch(newSkill); + const nextSkill = newSkill || ''; + setSkills(nextSkill); + onSearch(nextSkill); }} cacheOptions loadOptions={fetchSkills} @@ -58,7 +63,13 @@ SearchCombo.defaultProps = { }; SearchCombo.propTypes = { - term: PT.string, + term: PT.oneOfType([ + PT.string, + PT.shape({ + label: PT.string, + value: PT.string, + }), + ]), placeholder: PT.string, onSearch: PT.func.isRequired, auth: PT.object, diff --git a/src/shared/components/Gigs/GigApply/index.jsx b/src/shared/components/Gigs/GigApply/index.jsx deleted file mode 100644 index f3ea35051f..0000000000 --- a/src/shared/components/Gigs/GigApply/index.jsx +++ /dev/null @@ -1,384 +0,0 @@ -/* eslint-disable max-len */ -/** - * The Gig apply page. - */ - -import _ from 'lodash'; -import React, { useMemo } from 'react'; -import PT from 'prop-types'; -import { Link, config } from 'topcoder-react-utils'; -import TextInput from 'components/GUIKit/TextInput'; -import DropdownSkills from 'components/GUIKit/DropdownSkills'; -import RadioButton from 'components/GUIKit/RadioButton'; -import Checkbox from 'components/GUIKit/Checkbox'; -import { getCustomField } from 'utils/gigs'; -import Modal from 'components/Contentful/Modal'; -import FilestackFilePicker from 'components/GUIKit/FilePicker'; -import Dropdown from 'components/GUIKit/Dropdown'; -import LoadingIndicator from 'components/LoadingIndicator'; -import './style.scss'; -import bigCheckmark from 'assets/images/big-checkmark.png'; -import SadFace from 'assets/images/sad-face-icon.svg'; -import BackArrowGig from 'assets/images/back-arrow-gig-apply.svg'; -import CheckmarkGreen from 'assets/images/checkmark-green.svg'; -import { getService } from 'services/skills'; - -export default function GigApply(props) { - const { - job, - onFormInputChange, - formData, - formErrors, - onApplyClick, - applying, - application, - user, - recruitProfile, - auth, - } = props; - const retUrl = encodeURIComponent(`${window.location.origin}${window.location.pathname}`); - const duration = getCustomField(job.custom_fields, 'Duration'); - const isPlaced = _.find(_.isEmpty(recruitProfile) ? [] : recruitProfile.custom_fields, { field_id: 12 }); - const fetchSkills = useMemo(() => _.debounce((inputValue, callback) => { - if (!inputValue) { - callback(null); - } else { - getService(auth.tokenV3).getSkills(inputValue).then( - (response) => { - const skills = response || []; - const suggestedOptions = skills.map(skillItem => ({ - label: skillItem.name, - value: skillItem.name, - })); - return callback(null, { - options: suggestedOptions, - }); - }, - ).catch(() => callback(null)); - } - }, 150), [auth.tokenV3]); - - return user ? ( -
- { - job.error || job.enable_job_application_form !== 1 ? ( -
-

Gig does not exist.

-
- VIEW OTHER GIGS -
-
- ) : ( -
-

{job.name}

- GIG DETAILS -
- { - isPlaced && isPlaced.value === 'Placed' ? ( -
- -

One Gig Limit!

- -

Apologies, you are not allowed to apply to gigs if you are already placed on a gig.

You can however refer a friend to this gig and receive $500 if they get placed in that gig. To do that, you can enter their email on the right side of the Gigs Description page.

If you have any questions or feel this is an error, please email talent.taas@wipro.com.

-
-
- Back To Gig -
-
- ) : null - } - { - application ? ( -
- { application.error ? : bigCheckmark OK} -

{application.error ? 'OOPS!' : 'APPLICATION SUBMITTED'}

- { - application.error ? ( - - { - application.errorObj ? ( -

{application.errorObj.message || JSON.stringify(application.errorObj)}

- ) : null - } - { - application.errorObj && application.errorObj.notAllowed ? ( -

If you have any questions or feel this is an error, please email talent.taas@wipro.com.

- ) : ( - -

Looks like there is a problem on our end. Please try again.
If this persists please contact support@topcoder.com.

-

Please send us an email at talent.taas@wipro.com with the subject ‘Gig Error’
and paste the URL for the gig you are attempting to apply for so that we know of your interest.

-
- ) - } -
- ) : ( -

We will contact you via email if it seems like a fit!

- ) - } -
- { - application.error ? ( - - { - !application.errorObj.notAllowed ? ( - { - e.preventDefault(); - window.location.reload(); - }} - >APPLY AGAIN - - ) : null - } - VIEW OTHER GIGS - - ) : ( - - GO TO GIGS LIST - CHECK GIG APPLICATION STATUS - - ) - } -
-
- ) : null - } - { - applying ? ( -
- -

Processing your application…

-
- ) : null - } - { - !application && !applying && (!isPlaced || isPlaced.value !== 'Placed') ? ( -
- {!_.isEmpty(recruitProfile) - && ( -
-
It looks like you have applied to a gig previously. Perfect!
-

We have most of your information. Is there anything you would like to update to your Gig Work Profile?

-
- )} -

PERSONAL INFORMATION

- {_.isEmpty(recruitProfile) - &&

Welcome to Topcoder Gigs! We’d like to get to know you.

} -
-
- onFormInputChange('fname', val)} - errorMsg={formErrors.fname} - value={formData.fname} - required - readonly - /> - onFormInputChange('lname', val)} - errorMsg={formErrors.lname} - value={formData.lname} - required - readonly - /> -
-
- onFormInputChange('email', val)} - errorMsg={formErrors.email} - value={formData.email} - required - readonly - /> - onFormInputChange('phone', val)} - errorMsg={formErrors.phone} - value={formData.phone} - required - /> -
-
- onFormInputChange('city', val)} - errorMsg={formErrors.city} - value={formData.city} - required - /> - onFormInputChange('country', val)} - errorMsg={formErrors.country} - options={formData.country} - required - /> -
-
- {_.isEmpty(recruitProfile) &&

TOPCODER INFORMATION

} - {_.isEmpty(recruitProfile) && ( -
-
- onFormInputChange('handle', val)} - errorMsg={formErrors.handle} - value={formData.handle} - readonly - /> - onFormInputChange('tcProfileLink', val)} - errorMsg={formErrors.tcProfileLink} - value={formData.handle ? `https://topcoder.com/members/${formData.handle}` : null} - readonly - /> -
-
- )} -

SHARE YOUR WEEKLY PAY EXPECTATIONS

-
-
- onFormInputChange('payExpectation', val)} - errorMsg={formErrors.payExpectation} - value={formData.payExpectation} - required - /> -
-
-

RESUME & SKILLS

- { - recruitProfile.resume ? ( -

Please upload your resume/CV. Double-check that all of your tech skills are listed in your resume/CV and add them to the tech skills section below, {recruitProfile.resume.filename}

- ) : ( -

Please upload your resume/CV. Double-check that all of your tech skills are listed in your resume/CV and add them to the tech skills section below.

- ) - } -
- onFormInputChange('fileCV', files[0])} - inputOptions={{ - accept: '.pdf,.docx', - }} - infoText="Drag & drop your resume or CV here - please omit contact information *" - errorMsg={formErrors.fileCV} - /> -
- onFormInputChange('skills', val)} - errorMsg={formErrors.skills} - addNewOptionPlaceholder="Type to add another skill..." - required - cacheOptions - loadOptions={fetchSkills} - /> -
-

FINAL QUESTIONS

-
- {_.isEmpty(recruitProfile) && ( - onFormInputChange('reffereal', val)} - errorMsg={formErrors.reffereal} - options={formData.reffereal} - required - /> - )} -
-

Are you able to work during the specified timezone? ({`${getCustomField(job.custom_fields, 'Timezone')}`}) *

- onFormInputChange('timezoneConfirm', val)} - errorMsg={formErrors.timezoneConfirm} - options={formData.timezoneConfirm} - size="lg" - /> -
-

Are you ok to work with the duration of the gig? ({/^\d+$/.test(duration) ? `${duration} Weeks` : duration}) *

- onFormInputChange('durationConfirm', val)} - errorMsg={formErrors.durationConfirm} - options={formData.durationConfirm} - size="lg" - /> -
-
-
-
-
-
- onFormInputChange('agreedTerms', val)} - checked={formData.agreedTerms} - errorMsg={formErrors.agreedTerms} - size="lg" - /> - I agree to Candidate Terms * -
-
- View Our Equal Employment Opportunity Policy -
- -
- ) : null - } -
- ) - } -
- ) : ( -
-
-
-

You must be a Topcoder member to apply!

-
- Login -
-

Not a member? Register here.

-
-
-
- ); -} - -GigApply.defaultProps = { - formErrors: {}, - applying: false, - application: null, - user: null, - auth: {}, -}; - -GigApply.propTypes = { - job: PT.shape().isRequired, - formErrors: PT.shape(), - formData: PT.shape().isRequired, - onFormInputChange: PT.func.isRequired, - onApplyClick: PT.func.isRequired, - applying: PT.bool, - application: PT.shape(), - user: PT.shape(), - recruitProfile: PT.shape().isRequired, - auth: PT.object, -}; diff --git a/src/shared/components/Gigs/GigApply/style.scss b/src/shared/components/Gigs/GigApply/style.scss deleted file mode 100644 index 2c1a1e603e..0000000000 --- a/src/shared/components/Gigs/GigApply/style.scss +++ /dev/null @@ -1,322 +0,0 @@ -@import '~styles/mixins'; -@import "~components/Contentful/default"; - -.loading-wrap { - margin-top: 35px; - - .loading-text { - font-family: Roboto, sans-serif; - font-size: 24px; - line-height: 26px; - color: #2a2a2a; - text-align: center; - margin-top: 26px; - } -} - -.container { - max-width: $screen-lg; - min-height: 80vh; - margin: auto; - color: #2a2a2a; - - @include gui-kit-headers; - @include gui-kit-content; - @include roboto-regular; - - @include xs-to-md { - padding: 0 15px; - } - - .wrap .error { - display: flex; - flex-direction: column; - align-items: center; - margin-top: 27px; - height: 80vh; - - h3 { - text-align: center; - } - - .cta-buttons a.primaryBtn { - margin-bottom: 0 !important; - } - - .regTxt { - font-size: 14px; - margin: 10px 0 0; - } - } - - .checkboxes-row { - display: flex; - - .checkbox { - display: flex; - align-items: center; - margin-right: 28px; - - &:last-child { - margin-right: 0; - } - - .label { - font-size: 14px; - margin-left: 8px; - } - } - } - - .wrap { - h2 { - color: #26b3c5; - text-align: center; - margin-top: 47px; - margin-bottom: 31px; - - @include xs-to-md { - text-align: left; - } - } - - .back-link { - color: #229174; - font-weight: bold; - font-size: 14px; - letter-spacing: 0.8px; - text-decoration: none; - line-height: 40px; - margin-bottom: 12px; - display: flex; - align-items: center; - - svg { - width: 8px; - margin-right: 6px; - } - } - - .separator { - border-bottom: 1px solid #e9e9e9; - } - - .apply-state { - display: flex; - flex-direction: column; - align-items: center; - margin-bottom: 162px; - margin-top: 50px; - - h2 { - color: #2a2a2a; - margin-top: 31px; - margin-bottom: 20px; - } - - p { - font-size: 24px; - line-height: 36px; - text-align: center; - - a { - font-size: 24px; - line-height: 36px; - } - } - - .cta-buttons { - margin-top: 30px; - } - } - - .form-wrap { - max-width: 880px; - margin: auto; - - h4 { - margin-top: 35px; - margin-bottom: 5px; - } - - p { - font-size: 14px; - line-height: 22px; - - a { - font-size: 14px; - } - } - - .info-text { - font-size: 16px; - margin-top: 55px; - - h6 { - font-family: Barlow, sans-serif; - font-size: 16px; - line-height: 20px; - font-weight: 600; - margin-bottom: 8px; - display: flex; - align-items: center; - - svg { - margin-left: 5px; - } - } - } - - .form-section { - margin: 13px 0 50px; - - .form-row { - display: grid; - gap: 20px; - grid-template-columns: 1fr 1fr; - margin-bottom: 8px; - - @include xs-to-md { - display: flex; - flex-direction: column; - } - } - - p { - margin-top: 30px; - margin-bottom: 6px; - line-height: 30px; - font-size: 16px; - } - - :global { - .radioButtonContainer { - display: flex; - flex-direction: row; - - .radioButton { - margin-right: 28px; - - &:last-child { - margin-right: 0; - } - } - } - } - - .last-input { - margin-top: 34px; - margin-bottom: 81px; - } - - .input-bot-margin { - margin-bottom: 10px; - } - } - } - - .bottom-section { - display: flex; - justify-content: space-between; - font-size: 14px; - margin-top: 23px; - margin-bottom: 80px; - - @include xs-to-md { - flex-direction: column; - - .checkboxes-row, - .checkbox { - margin-bottom: 20px; - } - } - } - - .primaryBtn { - background-color: #137d60; - border-radius: 20px; - color: #fff; - font-size: 14px; - font-weight: bolder; - text-decoration: none; - text-transform: uppercase; - line-height: 40px; - padding: 0 20px; - border: none; - outline: none; - margin: 0 auto 150px auto !important; - display: flex; - - &:hover { - box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.2); - background-color: #0ab88a; - } - - @include xs-to-sm { - margin-bottom: 20px; - } - - &:disabled { - background-color: #e9e9e9 !important; - border: none !important; - text-decoration: none !important; - color: #fafafb !important; - box-shadow: none !important; - } - } - - .moldal-link { - text-decoration: underline; - color: #0d61bf; - - &:hover { - text-decoration: none; - } - } - - .error-text { - color: #ef476f; - } - } - /* stylelint-disable */ - .cta-buttons { - display: flex; - justify-content: center; - align-items: flex-start; - margin-top: 47px; - - @include xs-to-sm { - flex-direction: column; - } - - a { - background-color: #fff; - border: 1px solid #137d60; - border-radius: 20px; - color: #229174; - font-size: 14px; - font-weight: bolder; - text-decoration: none; - text-transform: uppercase; - line-height: 40px; - padding: 0 20px; - - &:hover { - box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.2); - } - - @include xs-to-sm { - text-align: center; - } - - &:last-child { - margin-left: 20px; - } - } - - .gig-list-btn { - margin-right: 12px; - } - } - /* stylelint-enable */ -} diff --git a/src/shared/components/Gigs/GigDetails/index.jsx b/src/shared/components/Gigs/GigDetails/index.jsx deleted file mode 100644 index 91601f521b..0000000000 --- a/src/shared/components/Gigs/GigDetails/index.jsx +++ /dev/null @@ -1,183 +0,0 @@ -/* eslint-disable no-return-assign */ -/* eslint-disable max-len */ -/** - * The Gig details component. - */ - -import { isEmpty } from 'lodash'; -import React, { useState } from 'react'; -import PT from 'prop-types'; -import { connect } from 'react-redux'; -import { Link, config, isomorphy } from 'topcoder-react-utils'; -import ReactHtmlParser from 'react-html-parser'; -import { getSalaryType, getCustomField } from 'utils/gigs'; -import './style.scss'; -import IconLocation from 'assets/images/icon-location.svg'; -import IconMoney from 'assets/images/icon-payment.svg'; -import IconDuration from 'assets/images/icon-calendar-gig.svg'; -import IconHours from 'assets/images/icon-duration.svg'; -import IconTimezone from 'assets/images/icon-timezone.svg'; -import iconSkills from 'assets/images/icon-skills-blue.png'; -import iconLabel1 from 'assets/images/l1.png'; -import iconLabel2 from 'assets/images/l2.png'; -import iconLabel3 from 'assets/images/l3.png'; -import SadFace from 'assets/images/sad-face-icon.svg'; -import LoginModal from '../LoginModal'; - -// Cleanup HTML from style tags -// so it won't affect other parts of the UI -const ReactHtmlParserOptions = { - // eslint-disable-next-line consistent-return - transform: (node) => { - if (node.type === 'style' && node.name === 'style') { - return null; - } - }, -}; - -function GigDetails(props) { - const { - job, application, profile, - } = props; - // eslint-disable-next-line no-restricted-globals - const retUrl = isomorphy.isClientSide() ? location.href : null; - let skills = getCustomField(job.custom_fields, 'Technologies Required'); - if (skills !== 'n/a') skills = skills.split(',').join(', '); - const hPerW = getCustomField(job.custom_fields, 'Hours per week'); - const compens = job.min_annual_salary === job.max_annual_salary ? job.max_annual_salary : `${job.min_annual_salary} - ${job.max_annual_salary} (USD)`; - - const [isLoginModalOpen, setLoginModalOpen] = useState(false); - const duration = getCustomField(job.custom_fields, 'Duration'); - - return ( -
- { - 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'}

-
- VIEW OTHER GIGS -
-
- ) : ( -
-

{job.name}

-
-
- -
- Location - {job.country} -
-
-
- -
- Compensation - ${compens} / {getSalaryType(job.salary_type)} -
-
-
- -
- Duration - {/^\d+$/.test(duration) ? `${duration} Weeks` : duration} -
-
-
- -
- Hours - {hPerW === 'n/a' ? hPerW : `${hPerW} hours / week`} -
-
-
- -
- Working Hours - {getCustomField(job.custom_fields, 'Timezone')} -
-
-
-
-
-

Required Skills

-

skills-icon {skills}

-

Description

-

{ReactHtmlParser(job.job_description_text, ReactHtmlParserOptions)} -

-

Notes

-
- - * Topcoder does not provide visa sponsorship nor will we work with Staffing Agencies. - - - ** USA Visa Holders - Please consult an attorney before applying to any Topcoder Gig. Some visa statuses will or will not allow you to conduct freelance work with Topcoder. - - - *** Topcoder and Wipro employees are not eligible for Gig work opportunities. Do not apply and send questions to talent.taas@wipro.com. - -
-
- { - !application || !application.success ? ( - { - if (isEmpty(profile)) { - e.preventDefault(); - setLoginModalOpen(true); - } - }} - >APPLY TO THIS JOB - - ) : null - } - VIEW OTHER JOBS -
-
-
-
-

At Topcoder, we pride ourselves in bringing our customers the very best candidates to help fill their needs. Want to improve your chances? You can do a few things:

-
    -
  • - label 1 -
    Make sure your Topcoder profile says it all. Fill out your profile to the best of your ability. Your skills, your location, your devices, etc, all help you improve your chances of being selected for a gig.
    -
  • -
  • - label 2 -
    Let us know you’re here! Check in on our Gig Work forum and tell us you’re looking for a gig. It’s great visibility for the Gig team.
    -
  • -
  • - label 3 -
    Check out our Topcoder challenges and participate. Challenges showing your technology skills make you a “qualified” candidate so we know you’re good. The proof is in the pudding!
    -
  • -
-
-
If you have any questions or doubts, don’t hesitate to email talent.taas@wipro.com.
- { - isLoginModalOpen && setLoginModalOpen(false)} /> - } -
-
-
- ) - } -
- ); -} - -GigDetails.defaultProps = { - application: null, - profile: {}, -}; - -GigDetails.propTypes = { - job: PT.shape().isRequired, - application: PT.shape(), - profile: PT.shape(), -}; - -export default connect()(GigDetails); diff --git a/src/shared/components/Gigs/GigDetails/style.scss b/src/shared/components/Gigs/GigDetails/style.scss deleted file mode 100644 index 145ecf9e39..0000000000 --- a/src/shared/components/Gigs/GigDetails/style.scss +++ /dev/null @@ -1,441 +0,0 @@ -/* stylelint-disable no-descending-specificity */ -@import '~styles/mixins'; -@import "~components/Contentful/default"; -@import "~components/buttons/themed/tc"; - -@mixin primaryBtn { - background-color: #137d60; - border-radius: 20px; - color: #fff !important; - font-size: 14px !important; - font-weight: bolder !important; - text-decoration: none !important; - text-transform: uppercase !important; - line-height: 40px !important; - padding: 0 20px !important; - border: none; - outline: none; - - &:hover { - box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.2); - background-color: #0ab88a; - } - - @include xs-to-sm { - margin-bottom: 20px; - margin-right: 0; - } -} - -.primaryBtn { - @include primaryBtn; -} - -.container { - max-width: $screen-lg; - min-height: 50vh; - margin: auto; - color: #2a2a2a; - overflow: hidden; - - @include gui-kit-headers; - @include gui-kit-content; - @include roboto-regular; - - @include xs-to-md { - padding: 0 15px; - } - - .error { - display: flex; - flex-direction: column; - align-items: center; - margin-top: 27px; - height: 80vh; - } - - .wrap .content .right .support a { - font-weight: 600; - text-decoration: none; - } - - .wrap { - h2 { - color: #26b3c5; - text-align: center; - margin-top: 47px; - margin-bottom: 40px; - - @include xs-to-md { - text-align: left; - } - } - - .infos { - display: flex; - justify-content: center; - border-bottom: 1px solid #e9e9e9; - padding-bottom: 52px; - - @include xs-to-md { - flex-direction: column; - } - - .infos-item { - display: flex; - margin-right: 70px; - - @include xs-to-md { - margin-bottom: 15px; - } - - &:last-child { - margin-right: 0; - } - - svg { - margin-right: 8px; - margin-top: 6px; - } - - .infos-data { - display: flex; - flex-direction: column; - - strong { - font-weight: bold; - line-height: 30px; - } - } - } - } - - .content { - display: flex; - margin-top: 44px; - margin-bottom: 192px; - - @include xs-to-md { - flex-direction: column; - } - - .right { - min-width: 408px; - display: flex; - flex-direction: column; - flex: 1; - - @include xs-to-md { - margin-top: 47px; - min-width: auto; - } - - .shareButtons { - display: flex; - align-items: center; - margin: 20px 0 12px; - color: #fff; - - a { - margin-right: 5px; - - &:last-child { - margin-right: 0; - } - } - - svg > path { - fill: #fff; - } - } - - .referr-area { - background-image: linear-gradient(135.29deg, #2c95d7 0%, #06d6a0 100%); - border-radius: 10px; - padding: 25px 20px 30px 20px; - margin-bottom: 20px; - - h6 { - margin: 0 0 6px; - color: #fff; - } - - p { - color: #fff; - margin-bottom: 20px; - } - - .referralLinkTitile { - font-weight: 500; - color: #fff; - display: block; - margin: 15px 0 5px; - } - - .referralLink { - border: 1px solid rgba(255, 255, 255, 0.4); - border-radius: 2px; - color: #fff; - width: 100%; - overflow: auto; - padding: 7px; - background: transparent; - margin: 0; - } - - .copyAndShare { - display: flex; - align-items: center; - justify-content: space-between; - margin: 15px 0 4px; - - @media screen and (max-width: 375px) { - flex-direction: column; - align-items: flex-start; - } - - button { - @include primary-borderless; - @include md; - - &:hover { - @include primary-borderless; - } - } - - .shareButtons { - margin: 0; - } - } - - .sepWrap { - display: flex; - align-items: center; - margin: 15px 0; - - .sepLine { - height: 1px; - background-color: #000; - opacity: 0.13; - flex: 2; - } - - span { - margin: 0 9px; - display: inline-block; - color: #fff; - font-weight: 500; - } - } - - .referr-form { - display: flex; - justify-content: center; - - input { - background-color: #fff !important; - border: 1px solid #aaa !important; - border-radius: 6px !important; - margin-right: 10px; - margin-bottom: 0 !important; - color: #2a2a2a; - font-family: Roboto, sans-serif; - font-size: 14px; - line-height: 22px; - } - - input::-webkit-input-placeholder, - input::placeholder { - color: #aaa !important; - font-family: Roboto, sans-serif !important; - font-size: 14px !important; - line-height: 40px !important; - text-transform: none; - } - - input:focus { - box-shadow: none !important; - } - - button { - background-color: #fff; - border-radius: 20px; - color: #229174 !important; - font-size: 14px; - font-weight: bolder; - text-decoration: none; - text-transform: uppercase; - line-height: 40px; - padding: 0 20px; - border: none; - outline: none; - max-width: 122px; - - &:hover { - box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.2); - } - - @include xs-to-sm { - margin-bottom: 20px; - } - - &:disabled { - background-color: #e9e9e9 !important; - border: none !important; - text-decoration: none !important; - color: #fafafb !important; - box-shadow: none !important; - } - } - } - } - - .subscribe-area { - background-color: #f4f4f4; - border-radius: 10px; - padding: 25px 32px 30px 20px; - - h6 { - margin: 0 0 19px; - } - } - - .info-area { - background-color: #f4f4f4; - padding: 20px; - border-radius: 10px; - margin-top: 20px; - - /* stylelint-disable */ - p, - a, - li { - font-size: 14px; - line-height: 26px; - - strong { - font-weight: bold; - } - } - - /* stylelint-enable */ - - ul { - margin-bottom: 0; - list-style: none; - padding: 0; - - li { - margin-bottom: 15px; - display: flex; - align-items: flex-start; - - img { - margin-right: 16px; - min-width: 40px; - } - - &:last-child { - margin-bottom: 0; - } - } - } - } - - .gig-skills { - display: flex; - } - - .support { - background-color: #eaf6fd; - border-radius: 10px; - padding: 20px; - text-transform: uppercase; - font-weight: 600; - font-family: Barlow, sans-serif; - margin-top: 20px; - line-height: 20px; - } - } - - .left { - flex: 2; - margin-right: 80px; - - /* stylelint-disable */ - div strong { - font-weight: bold; - line-height: 30px; - display: inline-block; - } - - /* stylelint-enable */ - - h4 { - margin-top: 35px; - - &:first-child { - margin-top: 0; - } - } - - .skills { - display: flex; - align-items: center; - line-height: 21px; - - /* stylelint-disable */ - img { - margin-right: 8px; - } - - /* stylelint-enable */ - } - } - } - - .how-it-works { - color: #fff; - } - } - - .cta-buttons { - display: flex; - justify-content: center; - margin-top: 47px; - - @include xs-to-sm { - flex-direction: column; - } - - /* stylelint-disable */ - a { - background-color: #fff; - border: 1px solid #137d60; - border-radius: 20px; - color: #229174; - font-size: 14px; - font-weight: bolder; - text-decoration: none; - text-transform: uppercase; - line-height: 38px; - padding: 0 20px; - - &:hover { - box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.2); - } - - @include xs-to-sm { - text-align: center; - } - } - - /* stylelint-enable */ - - .primaryBtn { - margin-right: 20px; - - @include primaryBtn; - } - } -} diff --git a/src/shared/components/Gigs/GigHeader/index.jsx b/src/shared/components/Gigs/GigHeader/index.jsx deleted file mode 100644 index 519415c88b..0000000000 --- a/src/shared/components/Gigs/GigHeader/index.jsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import { config, Link } from 'topcoder-react-utils'; -import PT from 'prop-types'; -import BannerInfoIcon from 'assets/images/banner-info.svg'; -import './style.scss'; - -const GigHeader = ({ appNum }) => ( -
-
-
- -
- - You have {appNum} applied Gigs in the system - -
- -
- CHECK GIG APPLICATION STATUS -
-
-); - -GigHeader.defaultProps = { - appNum: 0, -}; - -GigHeader.propTypes = { - appNum: PT.number, -}; -export default GigHeader; diff --git a/src/shared/components/Gigs/GigHeader/style.scss b/src/shared/components/Gigs/GigHeader/style.scss deleted file mode 100644 index 8c51a60079..0000000000 --- a/src/shared/components/Gigs/GigHeader/style.scss +++ /dev/null @@ -1,83 +0,0 @@ -@import "~styles/mixins"; -@import "~components/GUIKit/Assets/Styles/default"; -@import "~components/Contentful/default"; - -.gig-header { - @include roboto-medium; - - @include xs-to-sm { - height: 81px; - line-height: 22px; - padding-left: 20px; - } - - display: flex; - justify-content: space-between; - flex-direction: row; - align-items: center; - margin-top: 15px; - height: 70px; - background: linear-gradient(90deg, #6f308b 0%, #1470ac 100%); - border-radius: 10px; - color: #fff; - font-size: 16px; - - .content { - display: flex; - justify-content: space-between; - flex-direction: row; - align-items: center; - color: #fff; - - .banner-info { - width: 24px; - height: 24px; - margin-left: 20px; - margin-right: 10px; - - @include xs-to-sm { - display: none; - } - } - - @include xs-to-sm { - width: 80%; - } - } - - .row-btn { - display: flex; - justify-content: flex-end; - flex: 1; - padding-right: 20px; - - @include xs-to-sm { - justify-content: flex-start; - } - - button.primary-white-md { - outline: none; - - @include primary-white; - @include md; - - &:hover { - @include primary-white; - } - } - } - - .banner-close { - width: 14px; - height: 14px; - margin-right: 30px; - - &:hover { - cursor: pointer; - } - - @include xs-to-sm { - margin-bottom: 20px; - } - } -} diff --git a/src/shared/components/Gigs/LoginModal/index.jsx b/src/shared/components/Gigs/LoginModal/index.jsx deleted file mode 100644 index 79317e3897..0000000000 --- a/src/shared/components/Gigs/LoginModal/index.jsx +++ /dev/null @@ -1,85 +0,0 @@ -/* eslint-disable max-len */ -/** - * The modal used for login enforcing - */ - -/* global window */ - -import PT from 'prop-types'; -import React from 'react'; -import { Modal, PrimaryButton } from 'topcoder-react-ui-kit'; -import { config } from 'topcoder-react-utils'; -import SVG from 'react-inlinesvg'; -import MediaQuery from 'react-responsive'; -import tc from 'components/buttons/themed/tc.scss'; -import ThinkingFaceMobile from 'assets/images/thinking-face-mobile.svg'; -import ThinkingFace from 'assets/images/thinking-face-laptop-tablet.svg'; -import modalStyle from './modal.scss'; - -/** Themes for buttons - * those overwrite PrimaryButton style to match achieve various styles. - * Should implement pattern of classes. - */ -const buttonThemes = { - tc, -}; - -const blobYellow = 'https://images.ctfassets.net/b5f1djy59z3a/3wYRUnrUj3v765abrGRwlM/b0f9b49b4f49dc163e6913559a19b9e3/blob-yellow.svg'; -const progressBar = 'https://images.ctfassets.net/b5f1djy59z3a/2BX7LOrvVzKEarIJ8boCFm/bba0dd3e0180d2dc355809e6c1954631/progress-bar.svg'; -const progressBarMid = 'https://images.ctfassets.net/b5f1djy59z3a/517ZRt9geweW3QTtzlUqJu/11e33e876426f97e0725ba5fff9755f8/progress-bar-mid.svg'; -const progressBarXS = 'https://images.ctfassets.net/b5f1djy59z3a/6QxH7uVKCngtzBaXDn3Od1/3e0222a1ce773cead3f3a45f291f43a6/progress-bar-mobile.svg'; -const blobPurple = 'https://images.ctfassets.net/b5f1djy59z3a/1ZRCwp1uoShcES16lQmeu/ba084734120ffedebcb92b4e3fa2d667/blob-purple.svg'; - -function LoginModal({ retUrl, onCancel, utmSource }) { - return ( - -
- -

YAY! You are almost done!

-

- Looks like you're not a Topcoder member yet. Or maybe - you're not logged in? - It's quick to register and it's free! -

- - - - - - - - - -
- { - window.location = `${config.URL.AUTH}/?retUrl=${encodeURIComponent(retUrl)}&mode=signUp&utmSource=${utmSource}®Source=gigs`; - }} - theme={{ - button: buttonThemes.tc['primary-green-md'], - }} - > - REGISTER NOW - -
-

Already a member? Login here

- -
-
- ); -} - -LoginModal.defaultProps = { - utmSource: 'gig_listing', -}; - -LoginModal.propTypes = { - retUrl: PT.string.isRequired, - onCancel: PT.func.isRequired, - utmSource: PT.string, -}; - -export default LoginModal; diff --git a/src/shared/components/Gigs/LoginModal/modal.scss b/src/shared/components/Gigs/LoginModal/modal.scss deleted file mode 100644 index aa641aec6a..0000000000 --- a/src/shared/components/Gigs/LoginModal/modal.scss +++ /dev/null @@ -1,178 +0,0 @@ -@import "~styles/mixins"; -@import "~components/Contentful/default"; - -.container { - padding: 0; - width: auto; - max-width: 727px; - height: auto; - max-height: none; - border-radius: 10px; - display: flex; - flex-direction: column; - justify-content: center; - - @include gui-kit-headers; - @include gui-kit-content; - - @media (min-width: 757px) { - width: 727px; - } - - @media (min-width: 630px) and (max-width: 757px) { - width: 600px; - } - - @media (max-width: 630px) { - width: 290px; - max-height: none; - top: 42%; - } - - .title { - color: #9d41c9; - font-family: BarlowCondensed, sans-serif; - font-size: 60px; - line-height: 58px; - font-weight: 500; - margin: 0; - margin-bottom: 30px; - - @include xs-to-sm { - max-width: 400px; - margin: auto; - font-size: 60px !important; - line-height: 58px !important; - margin-bottom: 30px; - } - - @media (max-width: 425px) { - max-width: 230px; - margin: auto; - font-size: 36px !important; - line-height: 34px !important; - margin-bottom: 30px; - } - } - - .loginMsg { - color: #2a2a2a; - font-size: 24px; - line-height: 36px; - margin-bottom: 40px; - - @media (max-width: 425px) { - text-align: left !important; - font-size: 20px; - line-height: 30px; - margin-bottom: 30px; - } - } - - .ctaButtons { - display: flex; - align-content: center; - justify-content: center; - - & > button:first-child { - margin-right: 10px !important; - } - - & > a:first-child { - margin-right: 10px !important; - } - } - - .referrals { - display: flex; - overflow: auto; - - .sucessMsg { - font-size: 24px; - line-height: 36px; - margin-bottom: 40px; - } - - .rightAlign { - justify-content: flex-end; - } - } - - .loginRequired { - display: flex; - flex-direction: column; - padding: 80px 55px 40px 60px; - text-align: center; - position: relative; - - @include xs-to-sm { - padding: 50px 35px 40px; - } - - @media (max-width: 425px) { - padding: 50px 15px 70px; - } - - .progressBar { - width: 100%; - - @include xs { - margin-bottom: 40px; - } - - svg { - max-width: 100%; - } - } - - .blobYellow, - .blobPurple { - position: absolute; - z-index: -1; - - svg { - max-width: 100%; - } - } - - .blobYellow { - top: 0; - right: 0; - - @media (max-width: 425px) { - max-width: 62px; - } - } - - .blobPurple { - bottom: -5px; - left: 0; - - @media (max-width: 425px) { - max-width: 84px; - bottom: -40px; - } - } - - .thinkingFace { - width: 23px; - margin: 0 5px; - transform: translateY(4px); - - @media (max-width: 425px) { - width: 21px; - transform: translateY(2px); - } - } - - .regTxt { - font-size: 16px; - margin: 30px 0 0; - } - } -} - -.overlay { - background-color: #2a2a2a; - opacity: 0.95; -} diff --git a/src/shared/components/challenge-listing/ChallengeTab/index.jsx b/src/shared/components/challenge-listing/ChallengeTab/index.jsx index 212a8152b7..8d131b6e47 100644 --- a/src/shared/components/challenge-listing/ChallengeTab/index.jsx +++ b/src/shared/components/challenge-listing/ChallengeTab/index.jsx @@ -3,16 +3,24 @@ import _ from 'lodash'; import { BUCKETS, isPastBucket } from 'utils/challenge-listing/buckets'; import cn from 'classnames'; import { useMediaQuery } from 'react-responsive'; +import { Link } from 'topcoder-react-utils'; import ArrowIcon from 'assets/images/ico-arrow-down.svg'; import PT from 'prop-types'; import './style.scss'; const TAB_NAME = { - PAST_CHALLENGES: 'Past', - ACTIVE_CHALLENGES: 'Active', + PAST_CHALLENGES: 'Past Challenges', + ACTIVE_CHALLENGES: 'Active Challenges', }; +const TAB_LINKS = [ + { + label: 'Engagements', + to: '/engagements', + }, +]; + const ChallengeTab = ({ activeBucket, setPreviousBucketOfActiveTab, @@ -21,17 +29,25 @@ const ChallengeTab = ({ previousBucketOfActiveTab, selectBucket, location, + history, setFilterState, filterState, }) => { const past = isPastBucket(activeBucket); const [currentSelected, setCurrentSelected] = useState(past); const [isTabClosed, setIsTabClosed] = useState(true); + const pathname = _.get(location, 'pathname', ''); + const activeExternalTab = useMemo( + () => TAB_LINKS.find(link => pathname.startsWith(link.to)), + [pathname], + ); + const externalTabLabel = activeExternalTab ? activeExternalTab.label : null; const currentTabName = useMemo( () => ( - currentSelected ? TAB_NAME.PAST_CHALLENGES : TAB_NAME.ACTIVE_CHALLENGES + externalTabLabel + || (currentSelected ? TAB_NAME.PAST_CHALLENGES : TAB_NAME.ACTIVE_CHALLENGES) ), - [location, currentSelected, filterState], + [currentSelected, externalTabLabel], ); useEffect(() => { @@ -39,6 +55,12 @@ const ChallengeTab = ({ }, [activeBucket]); const onActiveClick = () => { + if (externalTabLabel) { + if (history && history.push) { + history.push(`/challenges?bucket=${BUCKETS.OPEN_FOR_REGISTRATION}`); + } + return; + } if (currentTabName === TAB_NAME.ACTIVE_CHALLENGES) { return; } @@ -63,6 +85,12 @@ const ChallengeTab = ({ }; const onPastChallengesClick = () => { + if (externalTabLabel) { + if (history && history.push) { + history.push(`/challenges?bucket=${BUCKETS.ALL_PAST}`); + } + return; + } if (currentTabName === TAB_NAME.PAST_CHALLENGES) { return; } @@ -118,6 +146,16 @@ const ChallengeTab = ({ > {TAB_NAME.PAST_CHALLENGES} + {TAB_LINKS.map(link => ( +
  • + + {link.label} + +
  • + ))} ); @@ -154,6 +192,15 @@ const ChallengeTab = ({ >

    {TAB_NAME.PAST_CHALLENGES}

    + {TAB_LINKS.map(link => ( + +

    {link.label}

    + + ))}
    ) } @@ -177,6 +224,7 @@ ChallengeTab.defaultProps = { setPreviousBucketOfPastChallengesTab: () => {}, previousBucketOfActiveTab: null, previousBucketOfPastChallengesTab: null, + history: null, }; ChallengeTab.propTypes = { @@ -184,6 +232,9 @@ ChallengeTab.propTypes = { search: PT.string, pathname: PT.string, }).isRequired, + history: PT.shape({ + push: PT.func, + }), activeBucket: PT.string, setPreviousBucketOfActiveTab: PT.func, setPreviousBucketOfPastChallengesTab: PT.func, diff --git a/src/shared/components/challenge-listing/ChallengeTab/style.scss b/src/shared/components/challenge-listing/ChallengeTab/style.scss index 78db42ab26..aa311699af 100644 --- a/src/shared/components/challenge-listing/ChallengeTab/style.scss +++ b/src/shared/components/challenge-listing/ChallengeTab/style.scss @@ -1,5 +1,10 @@ @import "~styles/mixins"; +.item-link { + color: inherit; + text-decoration: none; +} + .mobile-tab-expanded { margin: 0 16px; background-color: $listing-light-blue; @@ -29,6 +34,11 @@ font-weight: 700; } } + + .item-link { + display: block; + width: 100%; + } } .challenge-tab { @@ -58,6 +68,14 @@ } } + .item-link { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + width: 100%; + } + .active { color: $color-blue-140; border-bottom-color: $color-blue-140; diff --git a/src/shared/components/engagement-listing/EngagementCard/index.jsx b/src/shared/components/engagement-listing/EngagementCard/index.jsx new file mode 100644 index 0000000000..3d68bd80df --- /dev/null +++ b/src/shared/components/engagement-listing/EngagementCard/index.jsx @@ -0,0 +1,333 @@ +import React from 'react'; +import PT from 'prop-types'; +import moment from 'moment-timezone'; +import { config } from 'topcoder-react-utils'; +import IconBlackDuration from 'assets/images/icon-black-calendar.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 './style.scss'; + +const ROLE_LABELS = { + DESIGNER: 'Designer', + SOFTWARE_DEVELOPER: 'Software Developer', + DATA_SCIENTIST: 'Data Scientist', + DATA_ENGINEER: 'Data Engineer', +}; + +const WORKLOAD_LABELS = { + FULL_TIME: 'Full Time', + FRACTIONAL: 'Fractional', +}; + +const STATUS_LABELS = { + OPEN: 'Open', + PENDING_ASSIGNMENT: 'Pending Assignment', + ACTIVE: 'Active', + CANCELLED: 'Cancelled', + CLOSED: 'Closed', +}; + +const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; +const UNKNOWN_SKILL_LABEL = 'Unknown skill'; +const DEFAULT_LOCALE = 'en-US'; + +function asArray(value) { + if (!value) return []; + return Array.isArray(value) ? value : [value]; +} + +function isUuid(value) { + return typeof value === 'string' && UUID_PATTERN.test(value); +} + +function toTitleCase(value) { + return value + .toLowerCase() + .split(' ') + .map(part => (part ? `${part[0].toUpperCase()}${part.slice(1)}` : '')) + .join(' '); +} + +function normalizeLabel(value, normalizedMap) { + if (typeof value === 'object' && value !== null) { + const label = value.name || value.title; + if (label) return String(label); + } + + if (!value) return 'Not Specified'; + + const raw = String(value).trim(); + if (!raw) return 'Not Specified'; + + const normalized = raw.toUpperCase().replace(/[\s-]+/g, '_'); + if (normalizedMap && normalizedMap[normalized]) { + return normalizedMap[normalized]; + } + + const hasSeparators = /[_-]+/.test(raw); + const isAllCaps = raw === raw.toUpperCase(); + const spaced = raw.replace(/[_-]+/g, ' ').trim(); + + if (hasSeparators || isAllCaps) { + return spaced ? toTitleCase(spaced) : raw; + } + + return spaced || raw; +} + +function normalizeSkillLabel(skill) { + if (!skill) return null; + + if (typeof skill === 'object' && skill !== null) { + const label = skill.name || skill.title; + if (label) return String(label); + const skillId = skill.id || skill.value; + if (isUuid(skillId)) return UNKNOWN_SKILL_LABEL; + return skillId ? String(skillId) : null; + } + + if (isUuid(skill)) return UNKNOWN_SKILL_LABEL; + return String(skill); +} + +function getIntlTimeZoneName(timeZone, style) { + if (typeof Intl === 'undefined' || typeof Intl.DateTimeFormat !== 'function') return null; + try { + const formatter = new Intl.DateTimeFormat(DEFAULT_LOCALE, { + timeZone, + timeZoneName: style, + }); + if (typeof formatter.formatToParts !== 'function') return null; + const parts = formatter.formatToParts(new Date()); + const namePart = parts.find(part => part.type === 'timeZoneName'); + return namePart && namePart.value ? namePart.value : null; + } catch (error) { + return null; + } +} + +function getMomentTimeZoneName(timeZone) { + if (!moment || !moment.tz || !moment.tz.zone) return null; + if (!moment.tz.zone(timeZone)) return null; + try { + return moment.tz(new Date(), timeZone).format('z'); + } catch (error) { + return null; + } +} + +function formatTimeZoneLabel(timeZone) { + if (!timeZone) return ''; + const normalized = String(timeZone).trim(); + if (!normalized) return ''; + if (normalized === 'Any') return 'Any'; + + const shortName = getMomentTimeZoneName(normalized) || getIntlTimeZoneName(normalized, 'short'); + const longName = getIntlTimeZoneName(normalized, 'long'); + + if (shortName && longName) { + if (shortName === longName) return shortName; + return `${shortName} - ${longName}`; + } + + return shortName || longName || normalized; +} + +function normalizeLocationValue(value) { + if (!value) return null; + const normalized = (value && value.name) || value; + if (!normalized) return null; + if (typeof normalized === 'string' && moment && moment.tz && moment.tz.zone && moment.tz.zone(normalized)) { + return formatTimeZoneLabel(normalized); + } + return normalized; +} + +function formatDuration(value, unitLabel) { + if (value === null || value === undefined || value === '') return null; + const numericValue = Number(value); + if (Number.isNaN(numericValue) || numericValue <= 0) return null; + return `${numericValue} ${unitLabel}${numericValue === 1 ? '' : 's'}`; +} + +function getDuration(startDate, endDate, durationWeeks, durationMonths) { + const weekDuration = formatDuration(durationWeeks, 'Week'); + if (weekDuration) return weekDuration; + const monthDuration = formatDuration(durationMonths, 'Month'); + if (monthDuration) return monthDuration; + if (!startDate || !endDate) return 'TBD'; + const start = moment(startDate); + const end = moment(endDate); + if (!start.isValid() || !end.isValid()) return 'TBD'; + + const diffDays = end.diff(start, 'days'); + if (diffDays < 0) return 'TBD'; + const weeks = Math.max(1, Math.ceil(diffDays / 7)); + return `${weeks} Week${weeks === 1 ? '' : 's'}`; +} + +function formatDeadline(dateValue) { + if (!dateValue) return 'TBD'; + const deadline = moment(dateValue); + if (!deadline.isValid()) return 'TBD'; + return deadline.format('MMM DD, YYYY'); +} + +function getRoleDisplay(role) { + return normalizeLabel(role, ROLE_LABELS); +} + +function getWorkloadDisplay(workload) { + return normalizeLabel(workload, WORKLOAD_LABELS); +} + +function getCompensationDisplay(compensationRange) { + if (typeof compensationRange === 'object' && compensationRange !== null) { + const label = compensationRange.name || compensationRange.title; + return label ? String(label) : 'Not Specified'; + } + return compensationRange ? String(compensationRange) : 'Not Specified'; +} + +function getStatusDisplay(status) { + if (typeof status === 'object' && status !== null) { + const label = status.name || status.title; + if (label) return String(label); + } + + if (!status) return 'Not Specified'; + + const normalized = String(status).trim().toUpperCase().replace(/[\s-]+/g, '_'); + if (!normalized) return 'Not Specified'; + + return STATUS_LABELS[normalized] || 'Not Specified'; +} + +function EngagementCard({ engagement }) { + const { + title, + name, + startDate, + start, + endDate, + end, + durationStartDate, + durationEndDate, + durationWeeks, + durationMonths, + role, + workload, + compensationRange, + skills: engagementSkills, + requiredSkills, + skillsets, + location, + locations: engagementLocations, + timezone, + timezones, + timeZones, + countries, + status, + applicationDeadline, + nanoId, + id, + engagementId, + } = engagement; + + const displayTitle = title || name || 'Engagement'; + const normalizedStartDate = startDate || start || durationStartDate; + const normalizedEndDate = endDate || end || durationEndDate; + const durationText = getDuration( + normalizedStartDate, + normalizedEndDate, + durationWeeks, + durationMonths, + ); + const deadlineText = formatDeadline(applicationDeadline); + + const skillsSource = [engagementSkills, requiredSkills, skillsets] + .find(value => Array.isArray(value) && value.length) + || engagementSkills + || requiredSkills + || skillsets; + const skills = Array.from(new Set( + asArray(skillsSource) + .map(normalizeSkillLabel) + .filter(Boolean), + )); + const skillsText = skills.length + ? skills.slice(0, 2).join(', ') + : 'Not Specified'; + const limitedSkillsText = skills.length > 2 + ? `${skillsText},...` + : skillsText; + + const locations = [ + ...asArray(location), + ...asArray(engagementLocations), + ...asArray(timezone), + ...asArray(timezones), + ...asArray(timeZones), + ...asArray(countries), + ] + .map(normalizeLocationValue) + .filter(Boolean); + const locationText = locations.length ? locations.join(', ') : 'Remote'; + + const resolvedEngagementId = nanoId || id || engagementId; + const engagementLink = resolvedEngagementId + ? `${config.URL.ENGAGEMENTS_APP}/${resolvedEngagementId}` + : config.URL.ENGAGEMENTS_APP; + const statusText = getStatusDisplay(status); + + return ( +
    +
    + + {displayTitle} + + {statusText} +
    +
    +
    + role-icon {getRoleDisplay(role)} +
    +
    + skills-icon {limitedSkillsText} +
    +
    + {locationText} +
    +
    + {getWorkloadDisplay(workload)} +
    +
    + {getCompensationDisplay(compensationRange)} +
    +
    + {durationText} +
    +
    + {`Apply by ${deadlineText}`} +
    + +
    +
    + ); +} + +EngagementCard.defaultProps = { + engagement: {}, +}; + +EngagementCard.propTypes = { + engagement: PT.shape(), +}; + +export default EngagementCard; diff --git a/src/shared/components/engagement-listing/EngagementCard/style.scss b/src/shared/components/engagement-listing/EngagementCard/style.scss new file mode 100644 index 0000000000..33df70eb26 --- /dev/null +++ b/src/shared/components/engagement-listing/EngagementCard/style.scss @@ -0,0 +1,132 @@ +@import "~styles/mixins"; +@import "~components/GUIKit/Assets/Styles/default"; +@import "~components/Contentful/default"; +@import "~components/buttons/themed/tc"; + +.container { + border: 1px solid #e9e9e9; + border-radius: 10px; + display: flex; + flex-direction: column; + color: #2a2a2a; + padding: 25px 35px 25px 44px; + margin-bottom: 15px; + + @include gui-kit-headers; + @include gui-kit-content; + @include roboto-regular; + + .gig-name, + .gig-name:visited, + .gig-name:active, + .gig-name:hover { + color: #1e94a3; + display: inline-block; + font-family: Barlow, sans-serif; + font-size: 20px; + font-weight: 600; + line-height: 24px; + margin-bottom: 12px; + margin-top: 0; + min-width: 0; + text-decoration: none; + text-transform: uppercase; + + @include xs-to-sm { + margin-bottom: 20px; + } + } +} + +.header { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 12px; + margin-bottom: 12px; + + @include xs-to-sm { + margin-bottom: 20px; + } +} + +.status-badge { + display: inline-flex; + align-items: center; + padding: 3px 10px; + border-radius: 999px; + border: 1px solid #e1e1e1; + background-color: #f7f7f7; + color: #2a2a2a; + font-size: 12px; + font-weight: 500; + line-height: 18px; + white-space: nowrap; +} + +.job-infos { + display: grid; + grid-template-columns: 1.2fr 1fr 1.5fr 1fr 1.3fr 0.9fr 1fr 141px; + column-gap: 20px; + align-items: center; + + @include xs-to-sm { + display: flex; + flex-direction: column; + align-items: flex-start; + } +} + +.icon-val { + display: flex; + align-items: center; + min-width: 0; + + @include xs-to-sm { + margin-bottom: 20px; + width: 100%; + } + + svg, + img { + margin-right: 7px; + width: 20px; + height: 20px; + } +} + +.row-btn { + display: flex; + justify-content: flex-end; + align-items: center; + width: 141px; + + @include xs-to-sm { + justify-content: flex-start; + width: auto; + margin-bottom: 10px; + } +} + +.primary-green-md { + outline: none; + display: inline-flex; + align-items: center; + justify-content: center; + + @include primary-green; + @include md; + + &:hover { + @include primary-green; + } + + &:disabled, + &:hover:disabled { + background-color: #e9e9e9 !important; + border: none !important; + text-decoration: none !important; + color: #fafafb !important; + box-shadow: none !important; + } +} diff --git a/src/shared/components/engagement-listing/index.jsx b/src/shared/components/engagement-listing/index.jsx new file mode 100644 index 0000000000..b040fff9c2 --- /dev/null +++ b/src/shared/components/engagement-listing/index.jsx @@ -0,0 +1,235 @@ +import React, { + useEffect, + useMemo, + useRef, + useState, +} from 'react'; +import PT from 'prop-types'; +import LoadingIndicator from 'components/LoadingIndicator'; +import SearchCombo from 'components/GUIKit/SearchCombo'; +import Dropdown from 'components/GUIKit/Dropdown'; +import ChallengeTab from 'components/challenge-listing/ChallengeTab'; + +import EngagementCard from './EngagementCard'; + +import './style.scss'; + +const SORT_OPTIONS = [ + { label: 'Latest Added Descending', value: 'createdAt' }, + { label: 'Latest Updated Descending', value: 'updatedAt' }, +]; + +const CREATED_DATE_FIELDS = ['createdAt', 'created_at', 'createdOn', 'created']; +const UPDATED_DATE_FIELDS = ['updatedAt', 'updated_at', 'updatedOn', 'updated']; +const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + +function getTimestamp(engagement, fields) { + if (!engagement) return 0; + for (let i = 0; i < fields.length; i += 1) { + const value = engagement[fields[i]]; + if (value) { + const timestamp = Date.parse(value); + if (!Number.isNaN(timestamp)) return timestamp; + } + } + return 0; +} + +export default function EngagementListing({ + engagements, + loading, + loadMore, + filter, + setFilter, + allEngagementsLoaded, + auth, + history, + location, +}) { + const [search, setSearch] = useState(filter.search || ''); + const [sortBy, setSortBy] = useState(filter.sortBy || 'createdAt'); + const hasEngagements = engagements && engagements.length > 0; + const [filtersReady, setFiltersReady] = useState( + hasEngagements || allEngagementsLoaded, + ); + const hasStartedLoadingRef = useRef(false); + + useEffect(() => { + setSearch(filter.search || ''); + setSortBy(filter.sortBy || 'createdAt'); + }, [filter]); + + useEffect(() => { + if (loading) { + hasStartedLoadingRef.current = true; + return; + } + + if ( + !filtersReady + && (hasEngagements || allEngagementsLoaded || hasStartedLoadingRef.current) + ) { + setFiltersReady(true); + } + }, [allEngagementsLoaded, filtersReady, hasEngagements, loading]); + + const handleSearch = (nextSearch) => { + const nextOption = nextSearch && typeof nextSearch === 'object' + ? nextSearch + : { label: nextSearch, value: nextSearch }; + const label = nextOption + ? String(nextOption.label || nextOption.value || '').trim() + : ''; + const value = nextOption && nextOption.value + ? String(nextOption.value).trim() + : ''; + const hasSkillId = Boolean(value) && UUID_PATTERN.test(value); + const normalizedSearch = label; + + setSearch(normalizedSearch); + setFilter({ + ...filter, + search: normalizedSearch, + skills: hasSkillId ? [value] : [], + }); + }; + + const handleSortChange = (nextOptions) => { + const selected = (nextOptions || []).find(option => option.selected); + const nextSortBy = selected && selected.label === SORT_OPTIONS[1].label + ? SORT_OPTIONS[1].value + : SORT_OPTIONS[0].value; + + setSortBy(nextSortBy); + setFilter({ + ...filter, + sortBy: nextSortBy, + }); + }; + + const sortOptions = SORT_OPTIONS.map(option => ({ + label: option.label, + selected: option.value === sortBy, + })); + + const sortedEngagements = useMemo(() => { + if (!Array.isArray(engagements) || engagements.length <= 1) return engagements || []; + + const primaryFields = sortBy === 'updatedAt' ? UPDATED_DATE_FIELDS : CREATED_DATE_FIELDS; + const fallbackFields = sortBy === 'updatedAt' ? CREATED_DATE_FIELDS : []; + + return engagements + .map((engagement, index) => { + const primaryTimestamp = getTimestamp(engagement, primaryFields); + const fallbackTimestamp = primaryTimestamp ? 0 : getTimestamp(engagement, fallbackFields); + return { + engagement, + index, + timestamp: primaryTimestamp || fallbackTimestamp, + }; + }) + .sort((a, b) => { + if (a.timestamp === b.timestamp) return a.index - b.index; + return b.timestamp - a.timestamp; + }) + .map(item => item.engagement); + }, [engagements, sortBy]); + + return ( +
    + +
    + {filtersReady ? ( + + + + + ) : null} +
    + + {loading && !hasEngagements ? ( +
    + +
    + ) : null} + + {!loading && !hasEngagements ? ( +
    + No engagements match your filters yet. +
    + ) : null} + + {hasEngagements ? ( +
    + {sortedEngagements.map((engagement, index) => ( + + ))} +
    + ) : null} + + {hasEngagements && !allEngagementsLoaded ? ( +
    + +
    + ) : null} +
    + ); +} + +EngagementListing.defaultProps = { + engagements: [], + loading: false, + loadMore: () => {}, + allEngagementsLoaded: false, + auth: {}, + history: null, + location: {}, +}; + +EngagementListing.propTypes = { + engagements: PT.arrayOf(PT.shape()), + loading: PT.bool, + loadMore: PT.func, + filter: PT.shape({ + status: PT.string, + skills: PT.arrayOf(PT.string), + location: PT.string, + search: PT.string, + sortBy: PT.string, + }).isRequired, + setFilter: PT.func.isRequired, + allEngagementsLoaded: PT.bool, + auth: PT.shape({ + tokenV3: PT.string, + }), + history: PT.shape({ + push: PT.func, + }), + location: PT.shape({ + pathname: PT.string, + search: PT.string, + }), +}; diff --git a/src/shared/components/engagement-listing/style.scss b/src/shared/components/engagement-listing/style.scss new file mode 100644 index 0000000000..0328889c8e --- /dev/null +++ b/src/shared/components/engagement-listing/style.scss @@ -0,0 +1,81 @@ +@import '~styles/mixins'; + +.engagementListing { + @include roboto-regular; + + width: 100%; + max-width: $screen-lg; + margin: 0 auto; + padding: 24px 0; + + @media (max-width: 1280px) { + padding: 24px 15px; + } +} + +.filters { + display: flex; + align-items: center; + min-height: 52px; + gap: 30px; + margin-bottom: 20px; + + @include xs-to-sm { + flex-direction: column; + gap: 15px; + } + + > div { + flex: 1; + + &:first-child { + flex: 3; + } + } +} + +.filters.loading { + pointer-events: none; +} + +.cards { + display: flex; + flex-direction: column; +} + +.loading-state { + display: flex; + justify-content: center; + padding: 40px 0; +} + +.empty-state { + text-align: center; + padding: 40px 16px; + background: $tc-white; + border: 1px solid $tc-gray-10; + border-radius: $corner-radius; + color: $tc-gray-60; +} + +.load-more { + text-align: center; + margin-top: 24px; +} + +.load-more-button { + @include roboto-medium; + + border-radius: $corner-radius; + border: 1px solid $tc-dark-blue-70; + padding: 10px 18px; + font-size: 14px; + background: transparent; + color: $tc-dark-blue-110; + cursor: pointer; + + &:disabled { + opacity: 0.6; + cursor: not-allowed; + } +} diff --git a/src/shared/components/examples/GigsFeed/index.jsx b/src/shared/components/examples/GigsFeed/index.jsx deleted file mode 100644 index b54fe8200d..0000000000 --- a/src/shared/components/examples/GigsFeed/index.jsx +++ /dev/null @@ -1,35 +0,0 @@ -import GigsFeed from 'containers/Dashboard/GigsFeed'; -import React from 'react'; - -import './style.scss'; -import { PrimaryButton } from 'topcoder-react-ui-kit/src/shared/components/buttons'; - -export default class GigsFeedExample extends React.Component { - constructor() { - super(); - this.state = { theme: 'light' }; - this.toggleTheme = this.toggleTheme.bind(this); - } - - toggleTheme() { - const { theme } = this.state; - this.setState({ theme: theme === 'light' ? 'dark' : 'light' }); - } - - render() { - const { theme } = this.state; - - return ( -
    -
    -

    - Gigs Feed Preview -

    -
    Theme: {theme}
    - Toggle Theme - -
    -
    - ); - } -} diff --git a/src/shared/components/examples/GigsFeed/style.scss b/src/shared/components/examples/GigsFeed/style.scss deleted file mode 100644 index 211374bab5..0000000000 --- a/src/shared/components/examples/GigsFeed/style.scss +++ /dev/null @@ -1,25 +0,0 @@ -@import "~styles/mixins"; - -.page { - width: 100%; - min-height: 100vh; -} - -.container { - margin: 0 auto; - padding: 25px 50px; - max-width: 670px; - - @include xs-to-sm { - padding: 25px 15px; - } -} - -.light { - background: #f4f4f4; -} - -.dark { - background: $dashboard-dark-bg; - color: $tc-white; -} diff --git a/src/shared/containers/Dashboard/GigsFeed.jsx b/src/shared/containers/Dashboard/GigsFeed.jsx deleted file mode 100644 index 5ad294e568..0000000000 --- a/src/shared/containers/Dashboard/GigsFeed.jsx +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Container for dashboard announcement. - */ - -import GigsFeed from 'components/Dashboard/GigsFeed'; -import PT from 'prop-types'; -import React from 'react'; -import actions from 'actions/recruitCRM'; -import { connect } from 'react-redux'; - - -class GigsFeedContainer extends React.Component { - componentDidMount() { - const { - getGigs, - gigs, - itemCount, - } = this.props; - - // This gets all jobs. - if (!gigs || gigs.length === 0) { - getGigs({ - perPage: itemCount, - sortBy: 'createdAt', - sortOrder: 'desc', - status: 'sourcing', - isApplicationPageActive: true, - }); - } - } - - render() { - const { - gigs, - theme, - loading, - } = this.props; - - return ( - - ); - } -} - -GigsFeedContainer.defaultProps = { - itemCount: 10, - gigs: [], - loading: true, - theme: 'light', -}; - -GigsFeedContainer.propTypes = { - gigs: PT.arrayOf(PT.shape()), - itemCount: PT.number, - getGigs: PT.func.isRequired, - loading: PT.bool, - theme: PT.oneOf(['dark', 'light']), -}; - -function mapStateToProps(state) { - const data = state.recruitCRM; - return { - gigs: data ? data.gigs : [], - loading: data ? data.gigsLoading : true, - }; -} - -function mapDispatchToProps(dispatch) { - const a = actions.recruit; - return { - getGigs: (ownProps) => { - dispatch(a.getGigsInit(ownProps)); - dispatch(a.getGigsDone(ownProps)); - }, - }; -} - -export default connect( - mapStateToProps, - mapDispatchToProps, -)(GigsFeedContainer); diff --git a/src/shared/containers/Dashboard/index.jsx b/src/shared/containers/Dashboard/index.jsx index f7af5fb2b6..83071038b6 100644 --- a/src/shared/containers/Dashboard/index.jsx +++ b/src/shared/containers/Dashboard/index.jsx @@ -13,8 +13,6 @@ import { isTokenExpired } from '@topcoder-platform/tc-auth-lib'; import { config } from 'topcoder-react-utils'; import Viewport from 'components/Contentful/Viewport'; import TopcoderTime from 'components/Dashboard/TCTime'; -// deprecated with https://topcoder.atlassian.net/browse/CORE-346 -// import GigsFeed from 'containers/Dashboard/GigsFeed'; import ChallengesFeed from 'containers/Dashboard/ChallengesFeed'; import MetaTags from 'components/MetaTags'; // deprecated with https://topcoder.atlassian.net/browse/TOP-1390 @@ -55,8 +53,6 @@ function SlashTCContainer(props) { theme="light" excludeTags={[INNOVATION_CHALLENGES_TAG]} /> - {/* deprected with https://topcoder.atlassian.net/browse/CORE-346 */} - {/* */} {/* deprecated with https://topcoder.atlassian.net/browse/TOP-1390 */} {/* */} { isDevEnv ? : } @@ -77,8 +73,6 @@ function SlashTCContainer(props) { theme="light" excludeTags={[INNOVATION_CHALLENGES_TAG]} /> - {/* deprected with https://topcoder.atlassian.net/browse/CORE-346 */} - {/* */} {/* deprecated with https://topcoder.atlassian.net/browse/TOP-1390 */} {/* */}
    diff --git a/src/shared/containers/EDU/Home.jsx b/src/shared/containers/EDU/Home.jsx index c832380760..45cd2ca2ff 100644 --- a/src/shared/containers/EDU/Home.jsx +++ b/src/shared/containers/EDU/Home.jsx @@ -14,7 +14,6 @@ import DesignTrackIcon from 'assets/images/track-design.svg'; import MMTrackIcon from 'assets/images/track-MM.svg'; import AlgoTrackIcon from 'assets/images/track-ALGO.svg'; import TCTrackIcon from 'assets/images/tc-logo-icon.svg'; -import GigWorkTrackIcon from 'assets/images/gig-blob.svg'; // CSS import homeTheme from './styles/home.scss'; @@ -107,13 +106,6 @@ export default class EDUHome extends React.Component {
    -
    -
    -
    - -
    - -
    diff --git a/src/shared/containers/EDU/Tracks.jsx b/src/shared/containers/EDU/Tracks.jsx index e2190d0ca1..43b3df5643 100644 --- a/src/shared/containers/EDU/Tracks.jsx +++ b/src/shared/containers/EDU/Tracks.jsx @@ -24,7 +24,6 @@ import DS from 'assets/images/img-data-science.png'; import Algo from 'assets/images/img-algorithm.png'; import QA from 'assets/images/img-QA.png'; import Topcoder from 'assets/images/img-Topcoder.png'; -import GigWork from 'assets/images/img-gig-work.png'; import iconFilterArrow from 'assets/images/tc-edu/icon-filter-arrow.png'; // Partials import ResultTabs from './partials/ResultTabs'; @@ -38,7 +37,6 @@ const TRACK_BANNER_BACK_COLORS = { 'Competitive Programming': '#FFA45D', QA: '#8AFB8A', Topcoder: '#2A2A2A', - 'Gig Work': '#ef476f', }; const TRACK_IMAGES = { Development: Dev, @@ -47,7 +45,6 @@ const TRACK_IMAGES = { 'Competitive Programming': Algo, QA, Topcoder, - 'Gig Work': GigWork, }; const SORT_BY_OPTIONS = [ { label: 'Content Publish Date', selected: true }, diff --git a/src/shared/containers/EDU/styles/home.scss b/src/shared/containers/EDU/styles/home.scss index 1dc0c6c6dd..94a1b7bb33 100644 --- a/src/shared/containers/EDU/styles/home.scss +++ b/src/shared/containers/EDU/styles/home.scss @@ -199,14 +199,6 @@ height: 71px; } - .trackIconGigs { - display: flex; - justify-content: center; - align-items: center; - width: 74px; - height: 75px; - } - .trackIconTC { background-repeat: no-repeat; background-size: cover; diff --git a/src/shared/containers/Gigs/RecruitCRMJobApply.jsx b/src/shared/containers/Gigs/RecruitCRMJobApply.jsx deleted file mode 100644 index dd75721b06..0000000000 --- a/src/shared/containers/Gigs/RecruitCRMJobApply.jsx +++ /dev/null @@ -1,357 +0,0 @@ -/** - * Apply for a job page - */ - -import _ from 'lodash'; -import actions from 'actions/recruitCRM'; -import GigApply from 'components/Gigs/GigApply'; -import LoadingIndicator from 'components/LoadingIndicator'; -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'; - - -const cookies = require('browser-cookies'); -const countries = require('i18n-iso-countries'); -countries.registerLocale(require('i18n-iso-countries/langs/en.json')); - -class RecruitCRMJobApplyContainer extends React.Component { - constructor(props) { - super(props); - // initial state - this.state = { - formErrors: {}, - formData: { - skills: [], - durationConfirm: [{ label: 'Yes', value: false }, { label: 'No', value: false }], - timezoneConfirm: [{ label: 'Yes', value: false }, { label: 'No', value: false }], - agreedTerms: false, - country: _.map(countries.getNames('en'), val => ({ label: val, selected: false })), - reffereal: [ - { label: 'Google', selected: false }, - { 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 }, - ], - // eslint-disable-next-line react/destructuring-assignment - }, - }; - - // binds - this.onFormInputChange = this.onFormInputChange.bind(this); - this.onApplyClick = this.onApplyClick.bind(this); - this.validateForm = this.validateForm.bind(this); - } - - // eslint-disable-next-line consistent-return - componentDidMount() { - const { formData } = this.state; - const { user, recruitProfile, searchCandidates } = this.props; - if (user) { - if (!recruitProfile) searchCandidates(user.email); - else { - const { country, skills } = formData; - const recruitSkills = recruitProfile.skill.split(','); - let skillsValues = (skills || []).map(s => s.label); - skillsValues = _.uniq([ - ...skillsValues, - ...recruitSkills, - ]); - - return this.setState({ - formData: _.merge(formData, user, { - phone: recruitProfile.contact_number, - country: _.map( - country, - c => ({ - label: c.label, - selected: c.label.toLowerCase() === recruitProfile.locality.toLowerCase(), - }), - ), - skills: skillsValues.map(s => ({ - label: s, - selected: true, - })), - payExpectation: recruitProfile.salary_expectation, - }), - }); - } - this.setState({ - formData: _.merge(formData, user), - }); - } - } - - componentDidUpdate(prevProps) { - const { recruitProfile, user } = this.props; - if (recruitProfile !== prevProps.recruitProfile && !_.isEmpty(recruitProfile)) { - // when recruit profile loaded - const { formData } = this.state; - const { country, skills } = formData; - const recruitSkills = recruitProfile.skill.split(','); - - let skillsValues = (skills || []).map(s => s.label); - skillsValues = _.uniq([ - ...skillsValues, - ...recruitSkills, - ]); - - const updatedForm = { - formData: _.merge(formData, user, { - phone: recruitProfile.contact_number, - country: _.map( - country, - c => ({ - label: c.label, - selected: c.label.toLowerCase() === recruitProfile.locality.toLowerCase(), - }), - ), - skills: skillsValues.map(s => ({ - label: s, - selected: true, - })), - payExpectation: recruitProfile.salary_expectation, - }), - }; - // eslint-disable-next-line react/no-did-update-set-state - this.setState(updatedForm); - } - } - - onFormInputChange(key, value) { - // update the state - this.setState(state => ({ - ...state, - formData: { - ...state.formData, - [key]: value, - }, - })); - this.validateForm(key); - } - - onApplyClick() { - const { - applyForJob, job, optimizely, auth, - } = this.props; - const { formData } = this.state; - this.validateForm(); - this.setState((state) => { - if (_.isEmpty(state.formErrors)) { - applyForJob(job, formData, auth.tokenV3); - optimizely.track('Submit Application Form'); - const isFeatured = _.find(job.custom_fields, ['field_name', 'Featured']); - const jobTags = _.find(job.custom_fields, ['field_name', 'Job Tag']); - let hotListCookie = cookies.get('_tc.hcl'); - if (isFeatured && isFeatured.value) { - optimizely.track('Submit to Featured Gigs'); - } - if (jobTags && jobTags.value) { - optimizely.track('Submit to Tagged Gigs'); - } - if (hotListCookie) { - hotListCookie = JSON.parse(hotListCookie); - if (hotListCookie.slug === job.slug) { - optimizely.track('Submit to Hotlist Gigs'); - cookies.erase('_tc.hcl'); - } - } - } - }); - } - - validateForm(prop) { - this.setState((state) => { - const { formData, formErrors } = state; - const { recruitProfile } = this.props; - // Form validation happens here - const requiredTextFields = [ - 'fname', 'lname', 'city', 'phone', 'email', - ]; - // check required text fields for value - // check min/max lengths - _.each(requiredTextFields, (key) => { - // validate only modified prop if set - // and do not touch the others - if (prop && prop !== key) return; - if (!formData[key] || !_.trim(formData[key])) formErrors[key] = 'Required field'; - else if (formData[key] && _.trim(formData[key]).length < 2) formErrors[key] = 'Must be at least 2 characters'; - else if (formData[key] && _.trim(formData[key]).length > 2) { - switch (key) { - case 'city': - case 'phone': - if (_.trim(formData[key]).length > 50) formErrors[key] = 'Must be max 50 characters'; - else delete formErrors[key]; - break; - default: - if (_.trim(formData[key]).length > 40) formErrors[key] = 'Must be max 40 characters'; - else delete formErrors[key]; - break; - } - } else delete formErrors[key]; - }); - // check for selected country - if (!prop || prop === 'country') { - if (!_.find(formData.country, { selected: true })) formErrors.country = 'Please, select your country'; - else delete formErrors.country; - } - // check for selected reffereal - if (!prop || prop === 'reffereal') { - if (_.isEmpty(recruitProfile)) { - if (!_.find(formData.reffereal, { selected: true })) formErrors.reffereal = 'Please, select your reffereal'; - else delete formErrors.reffereal; - } - } - // check payExpectation to be a number - if (!prop || prop === 'payExpectation') { - if (formData.payExpectation && _.trim(formData.payExpectation)) { - if (!_.isInteger(_.toNumber(formData.payExpectation))) formErrors.payExpectation = 'Must be integer value in $'; - else delete formErrors.payExpectation; - } else formErrors.payExpectation = 'Required field'; - } - // check for valid email - if (!prop || prop === 'email') { - if (formData.email && _.trim(formData.email)) { - if (!(isValidEmail(formData.email))) formErrors.email = 'Invalid email'; - else delete formErrors.email; - } - } - // require atleast 1 skill - if (!prop || prop === 'skills') { - const skills = _.filter(formData.skills, ['selected', true]); - if (!skills.length) formErrors.skills = 'Please, add technical skills'; - else if (skills.map(skill => skill.label).join(',').length >= 100) formErrors.skills = 'Sum of all skill characters may not be greater than 100'; - else delete formErrors.skills; - } - // have accepted terms - if (!prop || prop === 'agreedTerms') { - if (!formData.agreedTerms) formErrors.agreedTerms = 'Please, accept our terms'; - else delete formErrors.agreedTerms; - } - // has CV file ready for upload - if (!prop || prop === 'fileCV') { - if (!formData.fileCV && _.isEmpty(recruitProfile)) formErrors.fileCV = 'Please, pick your CV file for uploading'; - else if (formData.fileCV) { - const sizeInMB = (formData.fileCV.size / (1024 * 1024)).toFixed(2); - if (sizeInMB > 8) { - formErrors.fileCV = 'Max file size is limited to 8 MB'; - delete formData.fileCV; - } else if (_.endsWith(formData.fileCV.name, '.pdf') || _.endsWith(formData.fileCV.name, '.docx')) { - delete formErrors.fileCV; - } else { - formErrors.fileCV = 'Only .pdf and .docx files are allowed'; - } - } - } - // timezone - if (!prop || prop === 'timezoneConfirm') { - const a = _.find(formData[prop], { value: true }); - if (a) { - if (a.label === 'No') formErrors[prop] = 'Sorry, we are only looking for candidates that can work the hours and duration listed'; - else delete formErrors[prop]; - } else if (prop) formErrors[prop] = 'Required field'; - else if (!prop && !_.find(formData.timezoneConfirm, { value: true })) formErrors.timezoneConfirm = 'Required field'; - } - // duration - if (!prop || prop === 'durationConfirm') { - const a = _.find(formData[prop], { value: true }); - if (a) { - if (a.label === 'No') formErrors[prop] = 'Sorry, we are only looking for candidates that can work the hours and duration listed'; - else delete formErrors[prop]; - } else if (prop) formErrors[prop] = 'Required field'; - else if (!prop && !_.find(formData.durationConfirm, { value: true })) formErrors.durationConfirm = 'Required field'; - } - // updated state - return { - ...state, - formErrors, - }; - }); - } - - render() { - const { formErrors, formData } = this.state; - const { recruitProfile, user } = this.props; - return !recruitProfile && user ? : ( - - ); - } -} - -RecruitCRMJobApplyContainer.defaultProps = { - user: null, - applying: false, - application: null, - recruitProfile: null, - auth: {}, -}; - -RecruitCRMJobApplyContainer.propTypes = { - job: PT.shape().isRequired, - user: PT.shape(), - applyForJob: PT.func.isRequired, - applying: PT.bool, - application: PT.shape(), - searchCandidates: PT.func.isRequired, - recruitProfile: PT.shape(), - optimizely: PT.shape().isRequired, - auth: PT.object, -}; - -function mapStateToProps(state, ownProps) { - const { profile } = state.auth; - const { job } = ownProps; - let userData = null; - if (profile && profile.email) { - userData = { - fname: profile.firstName, - lname: profile.lastName, - email: profile.email, - city: profile.addresses && profile.addresses[0] ? profile.addresses[0].city : '', - handle: profile.handle, - }; - } - return { - user: userData, - applying: state.recruitCRM && state.recruitCRM[job.slug] - ? state.recruitCRM[job.slug].applying : false, - application: state.recruitCRM && state.recruitCRM[job.slug] - ? state.recruitCRM[job.slug].application : null, - recruitProfile: state.recruitCRM && profile && state.recruitCRM[profile.email] - ? state.recruitCRM[profile.email].profile : null, - auth: { - ...state.auth, - }, - }; -} - -function mapDispatchToActions(dispatch) { - const a = actions.recruit; - return { - applyForJob: (job, payload, tokenV3) => { - dispatch(a.applyForJobInit(job, payload)); - dispatch(a.applyForJobDone(job, payload, tokenV3)); - }, - searchCandidates: (email) => { - dispatch(a.searchCandidatesInit(email)); - dispatch(a.searchCandidatesDone(email)); - }, - }; -} - -export default connect( - mapStateToProps, - mapDispatchToActions, -)(withOptimizely(RecruitCRMJobApplyContainer)); diff --git a/src/shared/containers/Gigs/RecruitCRMJobDetails.jsx b/src/shared/containers/Gigs/RecruitCRMJobDetails.jsx deleted file mode 100644 index 130c84073e..0000000000 --- a/src/shared/containers/Gigs/RecruitCRMJobDetails.jsx +++ /dev/null @@ -1,93 +0,0 @@ -/** - * A block that fetches and renders a job details page - * driven by recruitCRM - */ - -import { isEmpty } from 'lodash'; -import actions from 'actions/recruitCRM'; -import LoadingIndicator from 'components/LoadingIndicator'; -import GigDetails from 'components/Gigs/GigDetails'; -import PT from 'prop-types'; -import React from 'react'; -import { connect } from 'react-redux'; -import RecruitCRMJobApply from './RecruitCRMJobApply'; - -class RecruitCRMJobDetailsContainer extends React.Component { - componentDidMount() { - const { - getJob, - id, - job, - } = this.props; - - if (isEmpty(job)) { - getJob(id); - } - } - - render() { - const { - loading, - job, - isApply, - application, - profile, - } = this.props; - - if (loading) { - return ; - } - - return isApply - ? - : ( - - ); - } -} - -RecruitCRMJobDetailsContainer.defaultProps = { - job: {}, - application: null, - profile: {}, -}; - -RecruitCRMJobDetailsContainer.propTypes = { - getJob: PT.func.isRequired, - loading: PT.bool.isRequired, - job: PT.shape(), - id: PT.string.isRequired, - isApply: PT.bool.isRequired, - application: PT.shape(), - profile: PT.shape(), -}; - -function mapStateToProps(state, ownProps) { - const data = state.recruitCRM[ownProps.id]; - const profile = state.auth && state.auth.profile ? { ...state.auth.profile } : {}; - return { - job: data ? data.job : {}, - loading: data ? data.loading : true, - application: data ? data.application : null, - profile, - }; -} - -function mapDispatchToActions(dispatch) { - const a = actions.recruit; - return { - getJob: (id) => { - dispatch(a.getJobInit(id)); - dispatch(a.getJobDone(id)); - }, - }; -} - -export default connect( - mapStateToProps, - mapDispatchToActions, -)(RecruitCRMJobDetailsContainer); diff --git a/src/shared/containers/Gigs/RecruitCRMJobs.jsx b/src/shared/containers/Gigs/RecruitCRMJobs.jsx deleted file mode 100644 index 6d146e1338..0000000000 --- a/src/shared/containers/Gigs/RecruitCRMJobs.jsx +++ /dev/null @@ -1,348 +0,0 @@ -/** - * A block that fetches and renders a job listing page - * driven by recruitCRM - */ -import _ from 'lodash'; -import actions from 'actions/recruitCRM'; -import LoadingIndicator from 'components/LoadingIndicator'; -import SearchCombo from 'components/GUIKit/SearchCombo'; -import Paginate from 'components/GUIKit/Paginate'; -import JobListCard from 'components/GUIKit/JobListCard'; -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 GigHeader from 'components/Gigs/GigHeader'; -import './jobLisingStyles.scss'; - -const cookies = require('browser-cookies'); - -const GIGS_PER_PAGE = 10; -// Sort by dropdown -const sortByOptions = [ - { label: 'Latest Added Descending', selected: true }, - { label: 'Latest Updated Descending', selected: false }, -]; -// Locations -let locations = [{ - label: 'All', selected: true, -}]; - -class RecruitCRMJobsContainer extends React.Component { - constructor(props) { - super(props); - // Filter initial state - this.state = { - term: '', - page: 0, - sortBy: 'created_on', - location: 'All', - }; - // binds - this.onSearch = this.onSearch.bind(this); - this.onPaginate = this.onPaginate.bind(this); - this.onFilter = this.onFilter.bind(this); - this.onLocation = this.onLocation.bind(this); - this.onSort = this.onSort.bind(this); - this.onHotlistClick = this.onHotlistClick.bind(this); - } - - componentDidMount() { - const { - getJobs, - jobs, - getJobApplications, - auth, - } = this.props; - const { state } = this; - const q = getQuery(); - // This gets all jobs. - // Pagination and filtering on front-side - if (!jobs.length) { - getJobs({ - job_status: 1, // Open jobs only - }); - } - // handle URL query if present - if (q && q.search) { - const stateUpdate = { - ...state, - term: q.search, - }; - this.setState(stateUpdate); - } - if (auth.tokenV3) { - getJobApplications(auth.tokenV3); - } - } - - /** - * Wraps all calls to setState - * @param {Object} newState the state update - */ - onFilter(newState) { - // Do updates - // update the state - this.setState(newState); - } - - onSearch(newTerm) { - this.onFilter({ - term: newTerm, - page: 0, - }); - // update the URL query - updateQuery({ - search: newTerm, - }); - } - - onPaginate(newPage) { - this.onFilter({ - page: newPage.selected, - }); - window.scrollTo({ - top: 0, - behavior: 'smooth', - }); - } - - onLocation(newLocation) { - const selected = _.find(newLocation, { selected: true }); - this.onFilter({ - location: selected.label, - page: 0, - }); - } - - onSort(newSort) { - const selected = _.find(newSort, { selected: true }); - this.onFilter({ - sortBy: selected.label === 'Latest Updated Descending' ? 'updated_on' : 'created_on', - page: 0, - }); - } - - onHotlistClick(job) { - const { optimizely } = this.props; - optimizely.track('Hotlist ads click'); - cookies.set('_tc.hcl', JSON.stringify({ - slug: job.slug, - }), { - expires: 0, - }); - } - - render() { - const { - loading, - jobs, - optimizely, - applications, - auth, - } = this.props; - const { - term, - page, - sortBy, - location, - } = this.state; - - if (loading) { - return ( - - -

    Searching our database for the best gigs…

    -
    - ); - } - - // optimizely decide - let decision = { enabled: true }; - if (isomorphy.isClientSide()) { - // decision = optimizely.decide('gig_listing_hotlist'); - decision = optimizely.decide('gig_listing_hotlist_center'); - } - 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) => { - const country = _.trim(!job.country || job.country === 'Anywhere' || job.country === 'Any' ? 'All' : job.country); - // build dropdown - const found = _.findIndex(locations, l => l.label.toLowerCase() === country.toLowerCase()); - if (found === -1) { - locations.push({ - label: country, selected: location.toLowerCase() === country.toLowerCase(), - }); - } else { - locations[found].selected = location.toLowerCase() === country.toLowerCase(); - } - locations[0].selected = location === 'All'; - // filter - if (location === 'Anywhere' || location === 'Any' || location === 'All') return true; - return location.toLowerCase() === (job.country || '').toLowerCase(); - }); - // sort location dropdown - locations = _.sortBy(locations, ['label']); - // Filter by term - if (term) { - jobsToDisplay = _.filter(jobsToDisplay, (job) => { - // eslint-disable-next-line no-underscore-dangle - const _term = term.toLowerCase(); - // name search - if (job && job.name && job.name.toLowerCase().includes(_term)) return true; - // skills search - const skills = _.find(job.custom_fields, ['field_name', 'Technologies Required']); - if (skills && skills.value && skills.value.toLowerCase().includes(_term)) return true; - // location - if ((job.country || '').toLowerCase().includes(_term)) return true; - // duration - const duration = _.find(job.custom_fields, ['field_name', 'Duration']); - if (duration && duration.value && duration.value.toLowerCase().includes(_term)) return true; - // no match - return false; - }); - } - // Sort controlled by sortBy state - jobsToDisplay = jobsToDisplay.sort((a, b) => { - // sort featured gigs first no matter the sortBy - const featuredA = getCustomField(a.custom_fields, 'Featured'); - const featuredB = getCustomField(b.custom_fields, 'Featured'); - if (featuredB !== 'n/a' && featuredA === 'n/a') return Number.MAX_VALUE; - if (featuredB === 'n/a' && featuredA !== '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 - jobsToDisplay = _.slice( - jobsToDisplay, - page * GIGS_PER_PAGE, (page * GIGS_PER_PAGE) + GIGS_PER_PAGE, - ); - // hot list of gigs - let isHotlistRendered = false; - const hotlist = () => ( -
    -
    - { - hotlistJobs.map((hjob, indx) => ( - this.onHotlistClick(hjob)}> -
    {hjob.country}
    -
    {hjob.name}
    -
    ${hjob.min_annual_salary} - {hjob.max_annual_salary} / {getSalaryType(hjob.salary_type)}
    - - )) - } -
    -
    - ); - - return ( -
    -
    -
    - - - -
    - {auth.tokenV3 && applications > 0 && } -
    - { - jobsToDisplay.length - ? jobsToDisplay.map((job, indx) => { - const featured = getCustomField(job.custom_fields, 'Featured'); - if ((featured === 'n/a' || indx === 2) && !isHotlistRendered && hotlistJobs.length && decision.enabled) { - isHotlistRendered = true; - return ( - - {hotlist()} - - - ); - } - return ; - }) - : ( - - { - hotlistJobs.length && decision.enabled && hotlist() - } - No Results - - ) - } -
    - { - jobsToDisplay.length - ? : null - } -
    -
    - ); - } -} - -RecruitCRMJobsContainer.defaultProps = { - jobs: [], - loading: true, - applications: 0, - auth: {}, -}; - -RecruitCRMJobsContainer.propTypes = { - getJobs: PT.func.isRequired, - loading: PT.bool, - jobs: PT.arrayOf(PT.shape), - optimizely: PT.shape().isRequired, - getJobApplications: PT.func.isRequired, - applications: PT.number, - auth: PT.object, -}; - -function mapStateToProps(state) { - const data = state.recruitCRM; - return { - jobs: data ? data.jobs : [], - loading: data ? data.loading : true, - applications: data.applications, - auth: { - ...state.auth, - }, - }; -} - -function mapDispatchToActions(dispatch) { - const a = actions.recruit; - return { - getJobs: (ownProps) => { - dispatch(a.getJobsInit(ownProps)); - dispatch(a.getJobsDone(ownProps)); - }, - getJobApplications: (tokenV3) => { - dispatch(a.getJobApplicationsInit()); - dispatch(a.getJobApplicationsDone(tokenV3)); - }, - }; -} - -export default connect( - mapStateToProps, - mapDispatchToActions, -)(withOptimizely(RecruitCRMJobsContainer)); diff --git a/src/shared/containers/Gigs/_RecruitCRMJobs_ab-v1.jsx b/src/shared/containers/Gigs/_RecruitCRMJobs_ab-v1.jsx deleted file mode 100644 index 9c5f9bfa7c..0000000000 --- a/src/shared/containers/Gigs/_RecruitCRMJobs_ab-v1.jsx +++ /dev/null @@ -1,329 +0,0 @@ -/** - * A block that fetches and renders a job listing page - * driven by recruitCRM - */ -import _ from 'lodash'; -import actions from 'actions/recruitCRM'; -import LoadingIndicator from 'components/LoadingIndicator'; -import SearchCombo from 'components/GUIKit/SearchCombo'; -import Paginate from 'components/GUIKit/Paginate'; -import JobListCard from 'components/GUIKit/JobListCard'; -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 cookies = require('browser-cookies'); - -const CONTENT_PREVIEW_LENGTH = 175; -const GIGS_PER_PAGE = 10; -// Sort by dropdown -const sortByOptions = [ - { label: 'Latest Added Descending', selected: true }, - { label: 'Latest Updated Descending', selected: false }, -]; -// Locations -let locations = [{ - label: 'All', selected: true, -}]; - -class RecruitCRMJobsContainer extends React.Component { - constructor(props) { - super(props); - // Filter initial state - this.state = { - term: '', - page: 0, - sortBy: 'created_on', - location: 'All', - }; - // binds - this.onSearch = this.onSearch.bind(this); - this.onPaginate = this.onPaginate.bind(this); - this.onFilter = this.onFilter.bind(this); - this.onLocation = this.onLocation.bind(this); - this.onSort = this.onSort.bind(this); - this.onHotlistClick = this.onHotlistClick.bind(this); - } - - componentDidMount() { - const { - getJobs, - jobs, - } = this.props; - const { state } = this; - const q = getQuery(); - // This gets all jobs. - // Pagination and filtering on front-side - if (!jobs.length) { - getJobs({ - job_status: 1, // Open jobs only - }); - } - // handle URL query if present - if (q && q.search) { - const stateUpdate = { - ...state, - term: q.search, - }; - this.setState(stateUpdate); - } - } - - /** - * Wraps all calls to setState - * @param {Object} newState the state update - */ - onFilter(newState) { - // Do updates - // update the state - this.setState(newState); - } - - onSearch(newTerm) { - this.onFilter({ - term: newTerm, - page: 0, - }); - // update the URL query - updateQuery({ - search: newTerm, - }); - } - - onPaginate(newPage) { - this.onFilter({ - page: newPage.selected, - }); - window.scrollTo({ - top: 0, - behavior: 'smooth', - }); - } - - onLocation(newLocation) { - const selected = _.find(newLocation, { selected: true }); - this.onFilter({ - location: selected.label, - page: 0, - }); - } - - onSort(newSort) { - const selected = _.find(newSort, { selected: true }); - this.onFilter({ - sortBy: selected.label === 'Latest Updated Descending' ? 'updated_on' : 'created_on', - page: 0, - }); - } - - onHotlistClick(job) { - const { optimizely } = this.props; - optimizely.track('Hotlist ads click'); - cookies.set('_tc.hcl', JSON.stringify({ - slug: job.slug, - }), { - expires: 0, - }); - } - - render() { - const { - loading, - jobs, - optimizely, - } = this.props; - const { - term, - page, - sortBy, - location, - } = this.state; - - if (loading) { - return ( - - ; -

    Searching our database for the best gigs…

    -
    - ); - } - - // optimizely decide - let decision = { enabled: true }; - if (isomorphy.isClientSide()) { - // decision = optimizely.decide('gig_listing_hotlist'); - decision = optimizely.decide('gig_listing_hotlist_center'); - } - 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) => { - const country = job.country === 'Anywhere' || job.country === 'Any' ? 'All' : job.country; - // build dropdown - const found = _.findIndex(locations, { label: country }); - if (found === -1) { - locations.push({ - label: country, selected: location.toLowerCase() === country.toLowerCase(), - }); - } else { - locations[found].selected = location.toLowerCase() === country.toLowerCase(); - } - locations[0].selected = location === 'All'; - // filter - if (location === 'Anywhere' || location === 'Any' || location === 'All') return true; - return location.toLowerCase() === job.country.toLowerCase(); - }); - // sort location dropdown - locations = _.sortBy(locations, ['label']); - // Filter by term - if (term) { - jobsToDisplay = _.filter(jobsToDisplay, (job) => { - // eslint-disable-next-line no-underscore-dangle - const _term = term.toLowerCase(); - // name search - if (job.name.toLowerCase().includes(_term)) return true; - // skills search - const skills = _.find(job.custom_fields, ['field_name', 'Technologies Required']); - if (skills && skills.value && skills.value.toLowerCase().includes(_term)) return true; - // location - if (job.country.toLowerCase().includes(_term)) return true; - // duration - const duration = _.find(job.custom_fields, ['field_name', 'Duration']); - if (duration && duration.value && duration.value.toLowerCase().includes(_term)) return true; - // no match - return false; - }); - } - // Sort controlled by sortBy state - jobsToDisplay = jobsToDisplay.sort((a, b) => { - // sort featured gigs first no matter the sortBy - const featuredA = getCustomField(a.custom_fields, 'Featured'); - const featuredB = getCustomField(b.custom_fields, 'Featured'); - if (featuredB !== 'n/a' && featuredA === 'n/a') return Number.MAX_VALUE; - if (featuredB === 'n/a' && featuredA !== '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 - jobsToDisplay = _.slice( - jobsToDisplay, - page * GIGS_PER_PAGE, (page * GIGS_PER_PAGE) + GIGS_PER_PAGE, - ); - - return ( -
    -
    -
    - - - -
    -
    - { - jobsToDisplay.length - ? jobsToDisplay.map(job => ) - : No Results - } -
    - { - jobsToDisplay.length - ? : null - } -
    - { - hotlistJobs.length && decision.enabled && ( -
    -
    HOT THIS WEEK
    -
    - { - hotlistJobs.map((hjob, indx) => (indx <= 1 ? ( - this.onHotlistClick(hjob)}> -
    {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 - } - - ) : ( -
    -
    {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 - } - this.onHotlistClick(hjob)}>Apply Now -
    - ))) - } -
    -
    - ) - } -
    - ); - } -} - -RecruitCRMJobsContainer.defaultProps = { - jobs: [], - loading: true, -}; - -RecruitCRMJobsContainer.propTypes = { - getJobs: PT.func.isRequired, - loading: PT.bool, - jobs: PT.arrayOf(PT.shape), - optimizely: PT.shape().isRequired, -}; - -function mapStateToProps(state) { - const data = state.recruitCRM; - return { - jobs: data ? data.jobs : [], - loading: data ? data.loading : true, - }; -} - -function mapDispatchToActions(dispatch) { - const a = actions.recruit; - return { - getJobs: (ownProps) => { - dispatch(a.getJobsInit(ownProps)); - dispatch(a.getJobsDone(ownProps)); - }, - }; -} - -export default connect( - mapStateToProps, - mapDispatchToActions, -)(withOptimizely(RecruitCRMJobsContainer)); diff --git a/src/shared/containers/Gigs/_jobLisingStyles_ab-v1.scss b/src/shared/containers/Gigs/_jobLisingStyles_ab-v1.scss deleted file mode 100644 index 535a92cb16..0000000000 --- a/src/shared/containers/Gigs/_jobLisingStyles_ab-v1.scss +++ /dev/null @@ -1,215 +0,0 @@ -/* stylelint-disable no-descending-specificity */ -@import "~styles/mixins"; - -.loading-text { - font-family: Roboto, sans-serif; - font-size: 24px; - line-height: 26px; - color: #2a2a2a; - text-align: center; -} - -.container, -.container-with-hotlist { - max-width: $screen-lg; - margin: auto; - - @media (max-width: 1280px) { - padding: 0 15px; - } - - .gigs { - display: block; - } - - .filters { - display: flex; - align-items: flex-end; - - @include xs-to-sm { - flex-direction: column; - } - - > div { - margin-right: 30px; - flex: 1; - - @include xs-to-sm { - margin-right: 0; - margin-bottom: 15px; - } - - &:first-child { - flex: 3; - } - - &:last-child { - margin-right: 0; - } - } - } - - .jobs-list-container { - display: flex; - flex-direction: column; - margin: 20px 0 50px 0; - - .no-results { - display: flex; - justify-content: center; - } - } -} - -.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/Gigs/jobLisingStyles.scss b/src/shared/containers/Gigs/jobLisingStyles.scss deleted file mode 100644 index 3c294a2fd5..0000000000 --- a/src/shared/containers/Gigs/jobLisingStyles.scss +++ /dev/null @@ -1,194 +0,0 @@ -/* stylelint-disable no-descending-specificity */ -@import "~styles/mixins"; - -.loading-text { - font-family: Roboto, sans-serif; - font-size: 24px; - line-height: 26px; - color: #2a2a2a; - text-align: center; -} - -.container, -.container-with-hotlist { - max-width: $screen-lg; - margin: auto; - - @media (max-width: 1280px) { - padding: 0 15px; - } - - .gigs { - display: block; - flex: 1; - } - - .filters { - display: flex; - align-items: flex-end; - - @include xs-to-sm { - flex-direction: column; - } - - > div { - margin-right: 30px; - flex: 1; - - @include xs-to-sm { - margin-right: 0; - margin-bottom: 15px; - } - - &:first-child { - flex: 3; - } - - &:last-child { - margin-right: 0; - } - } - } - - .jobs-list-container { - display: flex; - flex-direction: column; - margin: 20px 0 50px 0; - - .no-results { - display: flex; - justify-content: center; - } - } -} - -.container-with-hotlist { - display: flex; - - .hotlist { - display: flex; - flex: 1; - margin: 5px 0; - - 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 { - display: grid; - align-items: stretch; - gap: 0 20px; - width: 100%; - grid-template-columns: 1fr 1fr 1fr 1fr; - - @include md-to-lg { - grid-template-columns: 1fr 1fr; - } - - @include xs-to-sm { - grid-template-columns: 1fr; - } - - .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; - line-height: 30px; - - 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(125.57deg, #2c95d7 0%, #83c5ee 100%); - } - - .hotlist-item-3 { - background-image: linear-gradient(309.43deg, #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/index.jsx b/src/shared/containers/GigsPages/index.jsx deleted file mode 100644 index 5e5bd0431c..0000000000 --- a/src/shared/containers/GigsPages/index.jsx +++ /dev/null @@ -1,143 +0,0 @@ -/** - * 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 ChallengeTab from 'components/challenge-listing/ChallengeTab'; - -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, - 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; - } 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 = '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, -}; - -GigsPagesContainer.propTypes = { - location: PT.shape({ - search: PT.string, - pathname: PT.string, - }).isRequired, - history: PT.shape().isRequired, - match: PT.shape().isRequired, - profile: PT.shape(), -}; - -function mapStateToProps(state) { - const profile = state.auth && state.auth.profile ? { ...state.auth.profile } : {}; - return { - profile, - }; -} - -export default connect( - mapStateToProps, -)(GigsPagesContainer); diff --git a/src/shared/containers/GigsPages/style.scss b/src/shared/containers/GigsPages/style.scss deleted file mode 100644 index a2a0b1bf75..0000000000 --- a/src/shared/containers/GigsPages/style.scss +++ /dev/null @@ -1,6 +0,0 @@ -@import "~styles/mixins"; - -.ChallengeFiltersExample { - margin: 0 auto; - max-width: $screen-lg; -} diff --git a/src/shared/containers/engagement-listing/index.jsx b/src/shared/containers/engagement-listing/index.jsx new file mode 100644 index 0000000000..e2bc2bc306 --- /dev/null +++ b/src/shared/containers/engagement-listing/index.jsx @@ -0,0 +1,184 @@ +import _ from 'lodash'; +import actions from 'actions/engagements'; +import headerActions from 'actions/topcoder_header'; +import React from 'react'; +import PT from 'prop-types'; +import shortId from 'shortid'; +import { connect } from 'react-redux'; +import EngagementListing from 'components/engagement-listing'; +import MetaTags from 'components/MetaTags'; + +import ogImage from '../../../assets/images/social.png'; + +class EngagementListingContainer extends React.Component { + componentDidMount() { + const { + auth, + filter, + getEngagements, + markHeaderMenu, + } = this.props; + + markHeaderMenu(); + getEngagements(0, filter, auth.tokenV3); + } + + componentDidUpdate(prevProps) { + const { + auth, + dropEngagements, + filter, + getEngagements, + } = this.props; + + const filterChanged = !_.isEqual(prevProps.filter, filter); + const tokenChanged = prevProps.auth.tokenV3 !== auth.tokenV3; + + if (filterChanged || tokenChanged) { + dropEngagements(); + getEngagements(0, filter, auth.tokenV3); + } + } + + componentWillUnmount() { + const { dropEngagements } = this.props; + dropEngagements(); + } + + loadMore = () => { + const { + allEngagementsLoaded, + auth, + filter, + getEngagements, + lastRequestedPage, + loadingEngagementsUUID, + } = this.props; + + if (loadingEngagementsUUID || allEngagementsLoaded) return; + + const nextPage = lastRequestedPage + 1; + getEngagements(nextPage, filter, auth.tokenV3); + }; + + render() { + const { + engagements, + filter, + auth, + history, + location, + loadingEngagementsUUID, + setFilter, + allEngagementsLoaded, + } = this.props; + + return ( + + + + + ); + } +} + +EngagementListingContainer.defaultProps = { + engagements: [], + loadingEngagementsUUID: '', + lastRequestedPage: -1, + allEngagementsLoaded: false, + meta: { + totalCount: 0, + }, +}; + +EngagementListingContainer.propTypes = { + auth: PT.shape({ + tokenV3: PT.string, + }).isRequired, + engagements: PT.arrayOf(PT.shape()), + loadingEngagementsUUID: PT.string, + lastRequestedPage: PT.number, + allEngagementsLoaded: PT.bool, + meta: PT.shape({ + totalCount: PT.number, + }), + filter: PT.shape({ + status: PT.string, + skills: PT.arrayOf(PT.string), + location: PT.string, + search: PT.string, + sortBy: PT.string, + }).isRequired, + getEngagements: PT.func.isRequired, + dropEngagements: PT.func.isRequired, + setFilter: PT.func.isRequired, + markHeaderMenu: PT.func.isRequired, + history: PT.shape({ + push: PT.func, + }).isRequired, + location: PT.shape({ + pathname: PT.string, + search: PT.string, + }).isRequired, +}; + +const mapStateToProps = (state) => { + const engagementsState = state.engagements || {}; + + return { + engagements: engagementsState.engagements || [], + loadingEngagementsUUID: engagementsState.loadingEngagementsUUID || '', + lastRequestedPage: Number.isFinite(engagementsState.lastRequestedPage) + ? engagementsState.lastRequestedPage + : -1, + allEngagementsLoaded: engagementsState.allEngagementsLoaded || false, + filter: engagementsState.filter || { + status: 'open', + skills: [], + location: '', + search: '', + sortBy: 'createdAt', + }, + meta: engagementsState.meta || { + totalCount: 0, + }, + auth: state.auth, + }; +}; + +const mapDispatchToProps = (dispatch) => { + const a = actions.engagements; + const ah = headerActions.topcoderHeader; + + return { + getEngagements: (page, filters, tokenV3) => { + const uuid = shortId(); + dispatch(a.getEngagementsInit(uuid, page, filters)); + dispatch(a.getEngagementsDone(uuid, page, filters, tokenV3)); + }, + dropEngagements: () => dispatch(a.dropEngagements()), + setFilter: filter => dispatch(a.setFilter(filter)), + markHeaderMenu: () => dispatch(ah.setCurrentNav('Compete', 'Engagements')), + }; +}; + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(EngagementListingContainer); diff --git a/src/shared/reducers/engagements.js b/src/shared/reducers/engagements.js new file mode 100644 index 0000000000..ee443ad19d --- /dev/null +++ b/src/shared/reducers/engagements.js @@ -0,0 +1,99 @@ +import { handleActions } from 'redux-actions'; +import actions from 'actions/engagements'; + +const initialState = { + engagements: [], + loadingEngagementsUUID: '', + lastRequestedPage: -1, + allEngagementsLoaded: false, + filter: { + status: 'open', + skills: [], + location: '', + search: '', + sortBy: 'createdAt', + }, + meta: { + totalCount: 0, + }, +}; + +function onGetEngagementsInit(state, { payload }) { + return { + ...state, + loadingEngagementsUUID: payload.uuid, + lastRequestedPage: payload.page, + allEngagementsLoaded: payload.page === 0 ? false : state.allEngagementsLoaded, + }; +} + +function onGetEngagementsDone(state, { error, payload }) { + if (!payload || payload.uuid !== state.loadingEngagementsUUID) return state; + + if (error) { + return { + ...state, + loadingEngagementsUUID: '', + }; + } + + const page = typeof payload.page === 'number' ? payload.page : state.lastRequestedPage; + const engagements = payload.engagements || []; + const nextEngagements = page > 0 + ? state.engagements.concat(engagements) + : engagements; + const nextMeta = { + ...state.meta, + ...(payload.meta || {}), + }; + const totalCount = typeof nextMeta.totalCount === 'number' ? nextMeta.totalCount : null; + const allEngagementsLoaded = totalCount !== null + ? nextEngagements.length >= totalCount + : engagements.length === 0; + + return { + ...state, + engagements: nextEngagements, + loadingEngagementsUUID: '', + allEngagementsLoaded, + meta: nextMeta, + }; +} + +function onDropEngagements(state) { + return { + ...state, + engagements: [], + loadingEngagementsUUID: '', + lastRequestedPage: -1, + allEngagementsLoaded: false, + meta: { + totalCount: 0, + }, + }; +} + +function onSetFilter(state, { payload }) { + return { + ...state, + filter: { + ...state.filter, + ...payload, + }, + }; +} + +function create(initial) { + return handleActions({ + [actions.engagements.getEngagementsInit]: onGetEngagementsInit, + [actions.engagements.getEngagementsDone]: onGetEngagementsDone, + [actions.engagements.dropEngagements]: onDropEngagements, + [actions.engagements.setFilter]: onSetFilter, + }, initial || initialState); +} + +export function factory() { + return Promise.resolve(create()); +} + +export default create(); diff --git a/src/shared/reducers/index.js b/src/shared/reducers/index.js index 34f4da0a55..1900c157fa 100644 --- a/src/shared/reducers/index.js +++ b/src/shared/reducers/index.js @@ -38,13 +38,13 @@ import { factory as termsFactory } from './terms'; import { factory as mfaFactory } from './mfa'; import mmLeaderboard from './mmLeaderboard'; import tcoLeaderboards from './tco/leaderboards'; -import recruitCRM from './recruitCRM'; import gSheet from './gSheet'; import timelineWall from './timelineWall'; import thrive from './contentful/thrive'; import dashboard from './dashboard'; import blog from './blog'; import identity from './identity'; +import engagements from './engagements'; /** * Given HTTP request, generates options for SSR by topcoder-react-lib's reducer @@ -173,7 +173,6 @@ export function factory(req) { menuNavigation, challengesBlock, policyPages, - recruitCRM, mmLeaderboard, gSheet, thrive, @@ -182,6 +181,7 @@ export function factory(req) { blog, timelineWall, identity, + engagements, })); } diff --git a/src/shared/reducers/recruitCRM.js b/src/shared/reducers/recruitCRM.js deleted file mode 100644 index 4ed3a878e3..0000000000 --- a/src/shared/reducers/recruitCRM.js +++ /dev/null @@ -1,178 +0,0 @@ -/** - * Reducer for state.recruit - */ -import _ from 'lodash'; -import actions from 'actions/recruitCRM'; -import { handleActions } from 'redux-actions'; - -/** - * Handles recruit.getJobsInit action. - * @param {Object} state Previous state. - */ -function onInit(state) { - return { - ...state, - jobs: {}, - loading: true, - }; -} - -/** - * Handles recruit.getJobsDone action. - * @param {Object} state Previous state. - * @param {Object} action The action. - */ -function onDone(state, { payload }) { - return { - ...state, - loading: false, - jobs: payload.data, - }; -} - -/** - * Handles recruit.getJobInit action. - * @param {Object} state Previous state. - */ -function onJobInit(state, { payload }) { - return { - ...state, - [payload.id]: { - loading: true, - }, - }; -} - -/** - * Handles recruit.getJobDone action. - * @param {Object} state Previous state. - * @param {Object} action The action. - */ -function onJobDone(state, { payload }) { - return { - ...state, - [payload.id]: { - loading: false, - job: payload.data, - }, - }; -} - -/** - * Handles recruit.applyForJobInit action. - * @param {Object} state Previous state. - */ -function onApplyForJobInit(state, { payload }) { - const r = { - ...state, - }; - r[payload.id].applying = true; - return r; -} - -/** - * Handles recruit.applyForJobDone action. - * @param {Object} state Previous state. - * @param {Object} action The action. - */ -function onApplyForJobDone(state, action) { - const r = { - ...state, - }; - if (!action.error) { - r[action.payload.id].applying = false; - r[action.payload.id].application = action.payload.data; - } - return r; -} - -/** - * Handles recruit.applyForJobInit action. - * @param {Object} state Previous state. - */ -function onSearchCandidatesInit(state, { payload }) { - const r = { - ...state, - }; - r[payload.email] = {}; - return r; -} - -/** - * Handles recruit.applyForJobDone action. - * @param {Object} state Previous state. - * @param {Object} action The action. - */ -function onSearchCandidatesDone(state, { payload }) { - const r = { - ...state, - }; - const profile = _.isArray(payload.data) ? {} : payload.data.data[0]; - r[payload.email].profile = profile; - return r; -} - -function onGetJobApplicationsInit(state) { - return { - ...state, - applications: 0, - }; -} - -function onGetJobApplicationsDone(state, { payload }) { - return { - ...state, - applications: payload.data, - }; -} - -/** - * Handles recruit.getGigsInit action. - * @param {Object} state Previous state. - */ -function onGigsInit(state) { - return { - ...state, - gigs: [], - gigsLoading: true, - }; -} - -/** - * Handles recruit.getGigsDone action. - * @param {Object} state Previous state. - * @param {Object} action The action. - */ -function onGigsDone(state, { payload }) { - return { - ...state, - gigsLoading: false, - gigs: payload.data, - }; -} - -/** - * Creates recruitCRM reducer with the specified initial state. - * @param {Object} state Optional. If not given, the default one is - * generated automatically. - * @return {Function} Reducer. - */ -function create(state = {}) { - return handleActions({ - [actions.recruit.getJobsInit]: onInit, - [actions.recruit.getJobsDone]: onDone, - [actions.recruit.getJobInit]: onJobInit, - [actions.recruit.getJobDone]: onJobDone, - [actions.recruit.applyForJobInit]: onApplyForJobInit, - [actions.recruit.applyForJobDone]: onApplyForJobDone, - [actions.recruit.searchCandidatesInit]: onSearchCandidatesInit, - [actions.recruit.searchCandidatesDone]: onSearchCandidatesDone, - [actions.recruit.getJobApplicationsInit]: onGetJobApplicationsInit, - [actions.recruit.getJobApplicationsDone]: onGetJobApplicationsDone, - [actions.recruit.getGigsInit]: onGigsInit, - [actions.recruit.getGigsDone]: onGigsDone, - }, state); -} - -/* Reducer with the default initial state. */ -export default create(); diff --git a/src/shared/routes/Examples/Examples.jsx b/src/shared/routes/Examples/Examples.jsx index 1e1bf55d4a..6b58a245a1 100644 --- a/src/shared/routes/Examples/Examples.jsx +++ b/src/shared/routes/Examples/Examples.jsx @@ -33,7 +33,6 @@ import SearchPageFilterExample from 'components/examples/SearchPageFilter'; import BlogFeedExample from 'components/examples/BlogFeed'; import GUIKit from 'components/examples/GUIKit'; import ThriveArticlesFeedExample from 'components/examples/ThriveArticlesFeed'; -import GigsFeedExample from 'components/examples/GigsFeed'; import ChallengesFeed from 'components/examples/ChallengesFeed'; import MemberPathSelectorExample from 'components/examples/MemberPathSelector'; @@ -102,7 +101,6 @@ export default function Examples({ - diff --git a/src/shared/routes/GigsPages.jsx b/src/shared/routes/GigsPages.jsx deleted file mode 100644 index c165fadcc7..0000000000 --- a/src/shared/routes/GigsPages.jsx +++ /dev/null @@ -1,26 +0,0 @@ -/** - * The loader of Gigs page webpack chunks. - */ -import path from 'path'; -import React from 'react'; -import LoadingPagePlaceholder from 'components/LoadingPagePlaceholder'; -import { AppChunk, webpack } from 'topcoder-react-utils'; - -export default function GigsPagesRoute(props) { - return ( - import(/* webpackChunkName: "gigsPages/chunk" */ 'containers/GigsPages') - .then(({ default: GigsPagesContainer }) => ( - - )) - } - renderPlaceholder={() => } - renderServer={(renderProps) => { - const p = webpack.resolveWeak('containers/GigsPages'); - const GigsPagesContainer = webpack.requireWeak(path.resolve(__dirname, p)); - return ; - }} - /> - ); -} diff --git a/src/shared/routes/StartPage.jsx b/src/shared/routes/StartPage.jsx deleted file mode 100644 index dca924c079..0000000000 --- a/src/shared/routes/StartPage.jsx +++ /dev/null @@ -1,21 +0,0 @@ -/** - * The loader of Gigs page webpack chunks. - */ -import React from 'react'; - -import LoadingPagePlaceholder from 'components/LoadingPagePlaceholder'; -import { AppChunk } from 'topcoder-react-utils'; - -export default function GigsPagesRoute(props) { - return ( - import(/* webpackChunkName: "gigsPages/chunk" */ 'containers/GigsPages') - .then(({ default: GigsPagesContainer }) => ( - - )) - } - renderPlaceholder={() => } - /> - ); -} diff --git a/src/shared/routes/Topcoder/EngagementListing.jsx b/src/shared/routes/Topcoder/EngagementListing.jsx new file mode 100644 index 0000000000..6fa30c7498 --- /dev/null +++ b/src/shared/routes/Topcoder/EngagementListing.jsx @@ -0,0 +1,32 @@ +import LoadingIndicator from 'components/LoadingIndicator'; +import path from 'path'; +import React from 'react'; +import { StaticRouter } from 'react-router-dom'; +import { AppChunk, webpack } from 'topcoder-react-utils'; + +export default function EngagementListingRoute() { + return ( + import(/* webpackChunkName: "engagement-listing/chunk" */ 'containers/engagement-listing') + .then(({ default: EngagementListing }) => ( + + )) + } + renderPlaceholder={() => } + renderServer={(renderProps) => { + const p = webpack.resolveWeak('containers/engagement-listing'); + const EngagementListing = webpack.requireWeak(path.resolve(__dirname, p)); + return ( + + + + ); + }} + /> + ); +} diff --git a/src/shared/routes/Topcoder/Routes.jsx b/src/shared/routes/Topcoder/Routes.jsx index 7e040cd60d..18301002b6 100644 --- a/src/shared/routes/Topcoder/Routes.jsx +++ b/src/shared/routes/Topcoder/Routes.jsx @@ -27,6 +27,7 @@ import EDUHome from '../EDUHome'; import EDUTracks from '../EDUTracks'; import EDUSearch from '../EDUSearch'; import ChallengeListing from './ChallengeListing'; +import EngagementListing from './EngagementListing'; import Dashboard from './Dashboard'; import Notifications from './Notifications'; import HallOfFame from '../HallOfFame'; @@ -60,6 +61,7 @@ export default function Topcoder() { path="/challenges/:challengeId([\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12}|\d{5,8})" /> + } path="/community/(competitive-programming|data-science|design|development|qa)/how-to-compete" /> - - ( -
    -
    - -
    -
    - )} - path={`${config.GIGS_PAGES_PATH}/roles`} - /> - - (
    diff --git a/src/shared/services/contentful.js b/src/shared/services/contentful.js index 2f1531253a..1577a73871 100644 --- a/src/shared/services/contentful.js +++ b/src/shared/services/contentful.js @@ -25,7 +25,7 @@ if (isomorphy.isServerSide()) { const EDU_TAXONOMY_ID = '15caxocitaxyK65K9oSd91'; // The keys for subcategory lists/references // If need to add new track add its fieldID here to be autopickuped -const EDU_TRACK_KEYS = ['dataScience', 'competitiveProgramming', 'design', 'development', 'qualityAssurance', 'topcoder', 'gigWork']; +const EDU_TRACK_KEYS = ['dataScience', 'competitiveProgramming', 'design', 'development', 'qualityAssurance', 'topcoder']; const EDU_ARTICLE_TYPES = ['Article', 'Video', 'Forum post']; diff --git a/src/shared/services/engagements.js b/src/shared/services/engagements.js new file mode 100644 index 0000000000..8de9936f07 --- /dev/null +++ b/src/shared/services/engagements.js @@ -0,0 +1,266 @@ +import { config } from 'topcoder-react-utils'; + +const engagementsApiUrl = config.API.ENGAGEMENTS || `${config.API.V6}/engagements/engagements`; +const skillsApiUrl = `${config.API.V5}/standardized-skills/skills`; +const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; +const UNKNOWN_SKILL_LABEL = 'Unknown skill'; + +function isUuid(value) { + return typeof value === 'string' && UUID_PATTERN.test(value); +} + +function asArray(value) { + if (!value) return []; + return Array.isArray(value) ? value : [value]; +} + +function buildEngagementsUrl(page, pageSize, filters = {}) { + const normalizedPage = Number.isFinite(page) ? Math.max(1, page + 1) : 1; + const url = new URL(engagementsApiUrl); + + url.searchParams.append('page', normalizedPage.toString()); + if (Number.isFinite(pageSize)) { + url.searchParams.append('perPage', pageSize.toString()); + } + + if (filters.status) { + const normalizedStatus = String(filters.status).trim().toUpperCase(); + if (normalizedStatus) { + url.searchParams.append('status', normalizedStatus); + } + } + + const hasSkillFilters = filters.skills && filters.skills.length; + if (filters.search && !hasSkillFilters) { + url.searchParams.append('search', String(filters.search).trim()); + } + + if (filters.location) { + const countries = String(filters.location) + .split(',') + .map(entry => entry.trim()) + .filter(Boolean); + if (countries.length) { + url.searchParams.append('countries', countries.join(',')); + } + } + + if (filters.skills && filters.skills.length) { + const skills = Array.isArray(filters.skills) + ? filters.skills + : [filters.skills]; + const normalizedSkills = skills + .map(skill => String(skill).trim()) + .filter(Boolean); + if (normalizedSkills.length) { + url.searchParams.append('requiredSkills', normalizedSkills.join(',')); + } + } + + if (filters.sortBy === 'createdAt' || filters.sortBy === 'updatedAt') { + url.searchParams.append('sortBy', filters.sortBy); + url.searchParams.append('sortOrder', 'desc'); + } + + return url; +} + +function getAuthHeaders(tokenV3) { + if (!tokenV3) return undefined; + return { + Authorization: `Bearer ${tokenV3}`, + }; +} + +function extractEngagements(data) { + if (Array.isArray(data)) return data; + if (data && Array.isArray(data.data)) return data.data; + if (data && data.result && Array.isArray(data.result.content)) return data.result.content; + if (data && data.result && Array.isArray(data.result.data)) return data.result.data; + if (data && Array.isArray(data.items)) return data.items; + return []; +} + +function extractSkills(data) { + if (Array.isArray(data)) return data; + if (data && Array.isArray(data.data)) return data.data; + if (data && data.result && Array.isArray(data.result.content)) return data.result.content; + if (data && data.result && Array.isArray(data.result.data)) return data.result.data; + if (data && Array.isArray(data.items)) return data.items; + return []; +} + +function extractMeta(data, engagementsCount) { + if (!data || Array.isArray(data)) return { totalCount: engagementsCount }; + if (data.meta) return data.meta; + if (data.result && data.result.metadata) return data.result.metadata; + if (typeof data.totalCount === 'number') return { totalCount: data.totalCount }; + if (typeof data.total === 'number') return { totalCount: data.total }; + if (typeof data.count === 'number') return { totalCount: data.count }; + return { totalCount: engagementsCount }; +} + +async function fetchSkillsByIds(skillIds, tokenV3) { + const ids = Array.isArray(skillIds) ? skillIds : [skillIds]; + const uniqueIds = Array.from(new Set(ids.filter(Boolean))); + if (!uniqueIds.length) return []; + + const params = new URLSearchParams(); + uniqueIds.forEach(skillId => params.append('skillId', skillId)); + params.set('disablePagination', 'true'); + + const headers = getAuthHeaders(tokenV3); + const res = await fetch(`${skillsApiUrl}?${params.toString()}`, { + method: 'GET', + headers, + }); + + if (!res.ok) { + throw new Error(res.statusText); + } + + const data = await res.json(); + return extractSkills(data); +} + +function resolveSkillLabel(skill, skillNameById) { + if (!skill) return null; + + if (typeof skill === 'object') { + const label = skill.name || skill.title; + if (label) return label; + + const skillId = skill.id || skill.value; + if (isUuid(skillId)) { + return skillNameById.get(skillId) || UNKNOWN_SKILL_LABEL; + } + return skillId ? String(skillId) : null; + } + + if (isUuid(skill)) { + return skillNameById.get(skill) || UNKNOWN_SKILL_LABEL; + } + + return String(skill); +} + +function normalizeEngagementSkills(engagement, skillNameById) { + if (!engagement || typeof engagement !== 'object') return engagement; + + return { + ...engagement, + skills: asArray(engagement.skills) + .map(skill => resolveSkillLabel(skill, skillNameById)) + .filter(Boolean), + requiredSkills: asArray(engagement.requiredSkills) + .map(skill => resolveSkillLabel(skill, skillNameById)) + .filter(Boolean), + skillsets: asArray(engagement.skillsets) + .map(skill => resolveSkillLabel(skill, skillNameById)) + .filter(Boolean), + }; +} + +async function hydrateEngagementSkills(engagements, tokenV3) { + if (!Array.isArray(engagements) || !engagements.length) { + return engagements; + } + + const skillIds = new Set(); + engagements.forEach((engagement) => { + const skillValues = [ + ...asArray(engagement.skills), + ...asArray(engagement.requiredSkills), + ...asArray(engagement.skillsets), + ]; + skillValues.forEach((skill) => { + if (typeof skill === 'object' && skill !== null) { + const skillId = skill.id || skill.value; + if (isUuid(skillId)) { + skillIds.add(skillId); + } + } else if (isUuid(skill)) { + skillIds.add(skill); + } + }); + }); + + let skillNameById = new Map(); + if (skillIds.size) { + try { + const skills = await fetchSkillsByIds(Array.from(skillIds), tokenV3); + skillNameById = new Map( + skills + .map(skill => [skill.id, skill.name || skill.title || skill.label]) + .filter(([id, label]) => Boolean(id) && Boolean(label)), + ); + } catch (error) { + skillNameById = new Map(); + } + } + + return engagements.map(engagement => normalizeEngagementSkills(engagement, skillNameById)); +} + +/** + * Fetches engagements. + * + * @param {number} page - Page number (0-based). + * @param {number} pageSize - Number of items per page. + * @param {Object} filters - Filters for engagements. + * @param {string} tokenV3 - Optional auth token. + * @returns {Promise<{engagements: Object[], meta: Object}>} The fetched data. + */ +export default async function getEngagements(page, pageSize, filters = {}, tokenV3) { + const url = buildEngagementsUrl(page, pageSize, filters); + const headers = getAuthHeaders(tokenV3); + + try { + const res = await fetch(url.toString(), { + method: 'GET', + headers, + }); + + if (!res.ok) { + throw new Error(res.statusText); + } + + const data = await res.json(); + const engagements = extractEngagements(data); + const meta = extractMeta(data, engagements.length); + const hydratedEngagements = await hydrateEngagementSkills(engagements, tokenV3); + + return { engagements: hydratedEngagements, meta }; + } catch (error) { + return Promise.reject(error); + } +} + +/** + * Fetches a single engagement by ID. + * + * @param {string} engagementId - The engagement ID. + * @param {string} tokenV3 - Optional auth token. + * @returns {Promise} The engagement details. + */ +export async function getEngagementDetails(engagementId, tokenV3) { + const url = new URL(`${engagementsApiUrl}/${encodeURIComponent(engagementId)}`); + const headers = getAuthHeaders(tokenV3); + + try { + const res = await fetch(url.toString(), { + method: 'GET', + headers, + }); + + if (!res.ok) { + throw new Error(res.statusText); + } + + const data = await res.json(); + const [hydrated] = await hydrateEngagementSkills([data], tokenV3); + return hydrated || data; + } catch (error) { + return Promise.reject(error); + } +} diff --git a/src/shared/services/recruitCRM.js b/src/shared/services/recruitCRM.js deleted file mode 100644 index ff54725a54..0000000000 --- a/src/shared/services/recruitCRM.js +++ /dev/null @@ -1,125 +0,0 @@ -import fetch from 'isomorphic-fetch'; -import { logger } from 'topcoder-react-lib'; -import { config } from 'topcoder-react-utils'; -import qs from 'qs'; -import _ from 'lodash'; - -const PROXY_ENDPOINT = '/api/recruit'; - -export default class Service { - baseUrl = PROXY_ENDPOINT; - - /** - * Get jobs by query - * @param {object} query The request query - */ - async getJobs(query) { - const res = await fetch(`${this.baseUrl}/jobs/search?${qs.stringify(query)}`); - if (!res.ok) { - const error = new Error('Failed to get jobs'); - logger.error(error, res); - } - return res.json(); - } - - /** - * Get job by id - * @param {*} id The request id - */ - async getJob(id) { - const res = await fetch(`${this.baseUrl}/jobs/${id}`); - if (!res.ok) { - const error = new Error(`Failed to get job ${id}`); - logger.error(error, res); - } - return res.json(); - } - - /** - * Get all jobs - * @param {object} query The request query - */ - async getAllJobs(query) { - const res = await fetch(`${this.baseUrl}/jobs?${qs.stringify(query)}`); - if (!res.ok) { - const error = new Error('Failed to get jobs'); - logger.error(error, res); - } - return res.json(); - } - - /** - * get member applications - * @param {*} tokenV3 - * @returns - */ - /* eslint-disable class-methods-use-this */ - async getJobApplications(tokenV3) { - const res = await fetch( - `${config.PLATFORM_SITE_URL}/gigs-app/api/my-gigs/myJobApplications?page=1&perPage=1`, - { - method: 'GET', - headers: new Headers({ - Authorization: `Bearer ${tokenV3}`, - }), - }, - ); - if (!res.ok) { - const error = new Error('Failed to get job applications'); - logger.error(error, res); - } - return parseInt(res.headers.get('x-total'), 10) || 0; - } - - /** - * applyForJob for candidate - * @param {string} id The job id to apply to - * @param {object} payload The apply payload - * @param {string} tokenV3 User token - */ - async applyForJob(id, payload, tokenV3) { - const { resume } = payload; - const data = new FormData(); - data.append('resume', resume); - data.append('form', JSON.stringify(_.omit(payload, 'resume'))); - const res = await fetch(`${this.baseUrl}/jobs/${id}/apply`, { - method: 'POST', - body: data, - headers: new Headers({ - Authorization: `Bearer ${tokenV3}`, - }), - credentials: 'omit', - }); - if (!res.ok) { - const error = new Error('Failed to apply for job'); - logger.error(error, res); - } - return res.json(); - } - - /** - * Search for candidate - * @param {object} email The email to search with - */ - async searchCandidates(email) { - const res = await fetch(`${this.baseUrl}/candidates/search?email=${email}`); - if (!res.ok) { - const error = new Error('Failed to search for candidates'); - logger.error(error, res); - } - return res.json(); - } - - /** - * Get TAAS jobs - * @param {object} query The request query - */ - async getTaasJobs(query) { - const res = await fetch(`${this.baseUrl}/taasjobs?${qs.stringify(query)}`); - if (!res.ok) { - const error = new Error('Failed to get taas jobs'); - logger.error(error, res); - } - return res.json(); - } -} diff --git a/src/shared/utils/gigs.js b/src/shared/utils/gigs.js deleted file mode 100644 index 3c9ba53a8c..0000000000 --- a/src/shared/utils/gigs.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Gig work utils - */ - -import _ from 'lodash'; - -/** - * Salary Type mapper - * @param {Object} data the data - */ -export function getSalaryType(data) { - switch (data.id) { - case 1: return 'monthly'; - case 2: return 'annual'; - case 3: return 'week'; - case 4: return 'daily'; - case 5: return 'hourly'; - default: return 'n/a'; - } -} - -/** - * Custom Field mapper - * @param {Array} data the data - */ -export function getCustomField(data, key) { - const val = _.find(data, { - field_name: key, - }); - return val && val.value ? val.value : 'n/a'; -} diff --git a/src/shared/utils/url.js b/src/shared/utils/url.js index 495de94486..c7e93027d7 100644 --- a/src/shared/utils/url.js +++ b/src/shared/utils/url.js @@ -196,14 +196,6 @@ export const getSubPageConfiguration = (location, loginUserHandle) => { const url = (location || window.location).pathname; - if (url.includes('/gigs')) { - toolName = 'Gigs'; - toolRoot = '/gigs'; - loginRedirect = '/gigs'; - type = 'tool'; - fullFooter = false; - } - if (url.includes('/thrive')) { toolName = 'Articles'; toolRoot = '/thrive'; @@ -228,6 +220,14 @@ export const getSubPageConfiguration = (location, loginUserHandle) => { fullFooter = false; } + if (url.includes('/engagements')) { + toolName = 'Opportunities'; + toolRoot = '/challenges'; + loginRedirect = '/engagements'; + type = 'tool'; + fullFooter = false; + } + if (url.includes('/members')) { const handle = url.substring(url.lastIndexOf('/') + 1); toolName = loginUserHandle && handle && loginUserHandle.toLowerCase() === handle.toLocaleLowerCase() ? 'My Profile' : 'Profiles'; @@ -253,14 +253,6 @@ export const getSubPageConfiguration = (location, loginUserHandle) => { fullFooter = true; } - if (url.includes('/community/gig-resources')) { - toolName = 'Gigs'; - toolRoot = '/community/gig-resources'; - loginRedirect = '/community/gig-resources'; - type = 'marketing'; - fullFooter = true; - } - if (url.includes('/community/practice')) { toolName = 'Challenge Practice'; toolRoot = '/community/practice'; diff --git a/src/test/jmeter/Community-25UV.jmx b/src/test/jmeter/Community-25UV.jmx index 184149b02d..b867126807 100644 --- a/src/test/jmeter/Community-25UV.jmx +++ b/src/test/jmeter/Community-25UV.jmx @@ -409,7 +409,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=${pgn}&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=${pgn}&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -451,7 +451,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=5&sortBy=name&sortOrder=asc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=5&sortBy=name&sortOrder=asc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -481,7 +481,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=5&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=5&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -511,7 +511,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -571,7 +571,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -601,7 +601,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -631,7 +631,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=CH + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=CH GET true false @@ -661,7 +661,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=CH + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=CH GET true false @@ -691,7 +691,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=CH + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=CH GET true false @@ -721,7 +721,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=F2F + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=F2F GET true false @@ -751,7 +751,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=F2F + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=F2F GET true false @@ -781,7 +781,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=F2F + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=F2F GET true false @@ -811,7 +811,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=TSK GET true false @@ -841,7 +841,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=TSK + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=TSK GET true false @@ -871,7 +871,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=TSK + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=TSK GET true false @@ -901,7 +901,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=CH + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=CH GET true false @@ -931,7 +931,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=CH + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=CH GET true false @@ -961,7 +961,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=CH + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=CH GET true false @@ -991,7 +991,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=F2F + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=F2F GET true false @@ -1021,7 +1021,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=F2F + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=F2F GET true false @@ -1051,7 +1051,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=F2F + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=F2F GET true false @@ -1081,7 +1081,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=TSK GET true false @@ -1111,7 +1111,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=TSK + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=TSK GET true false @@ -1141,7 +1141,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=TSK + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=TSK GET true false @@ -1171,7 +1171,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=CH + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=CH GET true false @@ -1201,7 +1201,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=CH + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=CH GET true false @@ -1231,7 +1231,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=CH + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=CH GET true false @@ -1261,7 +1261,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=F2F + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=F2F GET true false @@ -1291,7 +1291,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=F2F + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=F2F GET true false @@ -1321,7 +1321,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=F2F + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=F2F GET true false @@ -1351,7 +1351,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=TSK GET true false @@ -1381,7 +1381,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=TSK + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=TSK GET true false @@ -1411,7 +1411,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=TSK + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=TSK GET true false @@ -1441,7 +1441,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=CH + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=CH GET true false @@ -1471,7 +1471,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=CH + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=CH GET true false @@ -1501,7 +1501,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=CH + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=CH GET true false @@ -1531,7 +1531,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=F2F + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=F2F GET true false @@ -1561,7 +1561,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=F2F + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=F2F GET true false @@ -1591,7 +1591,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=F2F + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=F2F GET true false @@ -1621,7 +1621,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=TSK GET true false @@ -1651,7 +1651,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=TSK + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=TSK GET true false @@ -1681,7 +1681,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=TSK + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=TSK GET true false @@ -1711,7 +1711,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&tracks[]=Dev&types[]=CH&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&tracks[]=Dev&types[]=CH&types[]=TSK GET true false @@ -1741,7 +1741,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?startDateEnd=2020-01-31T18%3A29%3A59.999Z&endDateStart=2019-12-31T18%3A30%3A00.000Z&status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?startDateEnd=2020-01-31T18%3A29%3A59.999Z&endDateStart=2019-12-31T18%3A30%3A00.000Z&status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -1939,7 +1939,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?search=${tags}&tab=details&status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?search=${tags}&tab=details&status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -1981,7 +1981,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?search=topcoder&status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?search=topcoder&status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -2011,7 +2011,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?search=topcoder&status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?search=topcoder&status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -2041,7 +2041,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?search=topcoder&status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?search=topcoder&status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -2331,7 +2331,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=${pgn}&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=${pgn}&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -2373,7 +2373,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=5&sortBy=name&sortOrder=asc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=5&sortBy=name&sortOrder=asc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -2403,7 +2403,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=5&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=5&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -2433,7 +2433,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -2493,7 +2493,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -2523,7 +2523,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -2553,7 +2553,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=CH + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=CH GET true false @@ -2583,7 +2583,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=CH + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=CH GET true false @@ -2613,7 +2613,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=CH + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=CH GET true false @@ -2643,7 +2643,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=F2F + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=F2F GET true false @@ -2673,7 +2673,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=F2F + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=F2F GET true false @@ -2703,7 +2703,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=F2F + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=F2F GET true false @@ -2733,7 +2733,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=TSK GET true false @@ -2763,7 +2763,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=TSK + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=TSK GET true false @@ -2793,7 +2793,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=TSK + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=TSK GET true false @@ -2823,7 +2823,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=CH + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=CH GET true false @@ -2853,7 +2853,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=CH + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=CH GET true false @@ -2883,7 +2883,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=CH + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=CH GET true false @@ -2913,7 +2913,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=F2F + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=F2F GET true false @@ -2943,7 +2943,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=F2F + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=F2F GET true false @@ -2973,7 +2973,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=F2F + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=F2F GET true false @@ -3003,7 +3003,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=TSK GET true false @@ -3033,7 +3033,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=TSK + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=TSK GET true false @@ -3063,7 +3063,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=TSK + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=TSK GET true false @@ -3093,7 +3093,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=CH + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=CH GET true false @@ -3123,7 +3123,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=CH + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=CH GET true false @@ -3153,7 +3153,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=CH + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=CH GET true false @@ -3183,7 +3183,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=F2F + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=F2F GET true false @@ -3213,7 +3213,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=F2F + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=F2F GET true false @@ -3243,7 +3243,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=F2F + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=F2F GET true false @@ -3273,7 +3273,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=TSK GET true false @@ -3303,7 +3303,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=TSK + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=TSK GET true false @@ -3333,7 +3333,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=TSK + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=TSK GET true false @@ -3363,7 +3363,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=CH + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=CH GET true false @@ -3393,7 +3393,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=CH + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=CH GET true false @@ -3423,7 +3423,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=CH + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=CH GET true false @@ -3453,7 +3453,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=F2F + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=F2F GET true false @@ -3483,7 +3483,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=F2F + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=F2F GET true false @@ -3513,7 +3513,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=F2F + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=F2F GET true false @@ -3543,7 +3543,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=TSK GET true false @@ -3573,7 +3573,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=TSK + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=TSK GET true false @@ -3603,7 +3603,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=TSK + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=TSK GET true false @@ -3633,7 +3633,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&tracks[]=Dev&types[]=CH&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&tracks[]=Dev&types[]=CH&types[]=TSK GET true false @@ -3663,7 +3663,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?startDateEnd=2020-01-31T18%3A29%3A59.999Z&endDateStart=2019-12-31T18%3A30%3A00.000Z&status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?startDateEnd=2020-01-31T18%3A29%3A59.999Z&endDateStart=2019-12-31T18%3A30%3A00.000Z&status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -3861,7 +3861,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?search=${tags}&tab=details&status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?search=${tags}&tab=details&status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -3903,7 +3903,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?search=topcoder&status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?search=topcoder&status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -3933,7 +3933,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?search=topcoder&status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?search=topcoder&status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -3963,7 +3963,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?search=topcoder&status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?search=topcoder&status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -4183,7 +4183,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=${pgn}&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=${pgn}&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -4225,7 +4225,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=5&sortBy=name&sortOrder=asc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=5&sortBy=name&sortOrder=asc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -4255,7 +4255,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=5&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=5&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -4285,7 +4285,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -4345,7 +4345,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -4375,7 +4375,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -4405,7 +4405,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=CH + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=CH GET true false @@ -4435,7 +4435,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=CH + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=CH GET true false @@ -4465,7 +4465,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=CH + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=CH GET true false @@ -4495,7 +4495,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=F2F + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=F2F GET true false @@ -4525,7 +4525,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=F2F + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=F2F GET true false @@ -4555,7 +4555,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=F2F + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=F2F GET true false @@ -4585,7 +4585,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=TSK GET true false @@ -4615,7 +4615,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=TSK + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=TSK GET true false @@ -4645,7 +4645,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=TSK + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=TSK GET true false @@ -4675,7 +4675,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=CH + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=CH GET true false @@ -4705,7 +4705,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=CH + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=CH GET true false @@ -4735,7 +4735,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=CH + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=CH GET true false @@ -4765,7 +4765,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=F2F + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=F2F GET true false @@ -4795,7 +4795,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=F2F + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=F2F GET true false @@ -4825,7 +4825,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=F2F + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=F2F GET true false @@ -4855,7 +4855,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=TSK GET true false @@ -4885,7 +4885,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=TSK + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=TSK GET true false @@ -4915,7 +4915,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=TSK + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=TSK GET true false @@ -4945,7 +4945,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=CH + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=CH GET true false @@ -4975,7 +4975,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=CH + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=CH GET true false @@ -5005,7 +5005,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=CH + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=CH GET true false @@ -5035,7 +5035,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=F2F + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=F2F GET true false @@ -5065,7 +5065,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=F2F + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=F2F GET true false @@ -5095,7 +5095,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=F2F + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=F2F GET true false @@ -5125,7 +5125,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=TSK GET true false @@ -5155,7 +5155,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=TSK + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=TSK GET true false @@ -5185,7 +5185,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=TSK + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=TSK GET true false @@ -5215,7 +5215,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=CH + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=CH GET true false @@ -5245,7 +5245,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=CH + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=CH GET true false @@ -5275,7 +5275,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=CH + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=CH GET true false @@ -5305,7 +5305,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=F2F + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=F2F GET true false @@ -5335,7 +5335,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=F2F + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=F2F GET true false @@ -5365,7 +5365,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=F2F + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=F2F GET true false @@ -5395,7 +5395,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=TSK GET true false @@ -5425,7 +5425,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=TSK + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=TSK GET true false @@ -5455,7 +5455,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=TSK + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=TSK GET true false @@ -5485,7 +5485,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&tracks[]=Dev&types[]=CH&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&tracks[]=Dev&types[]=CH&types[]=TSK GET true false @@ -5515,7 +5515,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?startDateEnd=2020-01-31T18%3A29%3A59.999Z&endDateStart=2019-12-31T18%3A30%3A00.000Z&status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?startDateEnd=2020-01-31T18%3A29%3A59.999Z&endDateStart=2019-12-31T18%3A30%3A00.000Z&status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -5713,7 +5713,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?search=${tags}&tab=details&status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?search=${tags}&tab=details&status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -5755,7 +5755,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?search=topcoder&status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?search=topcoder&status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -5785,7 +5785,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?search=topcoder&status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?search=topcoder&status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -5815,7 +5815,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?search=topcoder&status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?search=topcoder&status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -5979,7 +5979,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=${pgn}&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=${pgn}&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -6021,7 +6021,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=5&sortBy=name&sortOrder=asc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=5&sortBy=name&sortOrder=asc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -6051,7 +6051,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=5&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=5&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -6081,7 +6081,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -6141,7 +6141,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -6171,7 +6171,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -6201,7 +6201,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=CH + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=CH GET true false @@ -6231,7 +6231,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=CH + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=CH GET true false @@ -6261,7 +6261,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=CH + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=CH GET true false @@ -6291,7 +6291,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=F2F + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=F2F GET true false @@ -6321,7 +6321,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=F2F + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=F2F GET true false @@ -6351,7 +6351,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=F2F + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=F2F GET true false @@ -6381,7 +6381,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=TSK GET true false @@ -6411,7 +6411,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=TSK + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=TSK GET true false @@ -6441,7 +6441,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=TSK + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=TSK GET true false @@ -6471,7 +6471,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=CH + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=CH GET true false @@ -6501,7 +6501,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=CH + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=CH GET true false @@ -6531,7 +6531,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=CH + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=CH GET true false @@ -6561,7 +6561,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=F2F + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=F2F GET true false @@ -6591,7 +6591,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=F2F + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=F2F GET true false @@ -6621,7 +6621,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=F2F + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=F2F GET true false @@ -6651,7 +6651,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=TSK GET true false @@ -6681,7 +6681,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=TSK + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=TSK GET true false @@ -6711,7 +6711,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=TSK + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=TSK GET true false @@ -6741,7 +6741,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=CH + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=CH GET true false @@ -6771,7 +6771,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=CH + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=CH GET true false @@ -6801,7 +6801,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=CH + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=CH GET true false @@ -6831,7 +6831,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=F2F + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=F2F GET true false @@ -6861,7 +6861,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=F2F + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=F2F GET true false @@ -6891,7 +6891,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=F2F + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=F2F GET true false @@ -6921,7 +6921,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=TSK GET true false @@ -6951,7 +6951,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=TSK + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=TSK GET true false @@ -6981,7 +6981,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=TSK + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=TSK GET true false @@ -7011,7 +7011,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=CH + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=CH GET true false @@ -7041,7 +7041,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=CH + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=CH GET true false @@ -7071,7 +7071,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=CH + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=CH GET true false @@ -7101,7 +7101,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=F2F + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=F2F GET true false @@ -7131,7 +7131,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=F2F + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=F2F GET true false @@ -7161,7 +7161,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=F2F + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=F2F GET true false @@ -7191,7 +7191,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=TSK GET true false @@ -7221,7 +7221,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=TSK + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=TSK GET true false @@ -7251,7 +7251,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=TSK + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=TSK GET true false @@ -7281,7 +7281,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&tracks[]=Dev&types[]=CH&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&tracks[]=Dev&types[]=CH&types[]=TSK GET true false @@ -7311,7 +7311,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?startDateEnd=2020-01-31T18%3A29%3A59.999Z&endDateStart=2019-12-31T18%3A30%3A00.000Z&status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?startDateEnd=2020-01-31T18%3A29%3A59.999Z&endDateStart=2019-12-31T18%3A30%3A00.000Z&status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -7509,7 +7509,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?search=${tags}&tab=details&status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?search=${tags}&tab=details&status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -7551,7 +7551,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?search=topcoder&status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?search=topcoder&status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -7581,7 +7581,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?search=topcoder&status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?search=topcoder&status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -7611,7 +7611,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?search=topcoder&status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?search=topcoder&status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -7768,7 +7768,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=${pgn}&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=${pgn}&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -7810,7 +7810,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=5&sortBy=name&sortOrder=asc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=5&sortBy=name&sortOrder=asc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -7840,7 +7840,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=5&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=5&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -7870,7 +7870,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -7930,7 +7930,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -7960,7 +7960,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&tracks[]=Des&tracks[]=Dev&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -7990,7 +7990,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=CH + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=CH GET true false @@ -8020,7 +8020,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=CH + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=CH GET true false @@ -8050,7 +8050,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=CH + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=CH GET true false @@ -8080,7 +8080,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=F2F + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=F2F GET true false @@ -8110,7 +8110,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=F2F + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=F2F GET true false @@ -8140,7 +8140,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=F2F + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=F2F GET true false @@ -8170,7 +8170,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=TSK GET true false @@ -8200,7 +8200,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=TSK + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=TSK GET true false @@ -8230,7 +8230,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=TSK + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Des&types[]=TSK GET true false @@ -8260,7 +8260,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=CH + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=CH GET true false @@ -8290,7 +8290,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=CH + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=CH GET true false @@ -8320,7 +8320,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=CH + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=CH GET true false @@ -8350,7 +8350,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=F2F + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=F2F GET true false @@ -8380,7 +8380,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=F2F + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=F2F GET true false @@ -8410,7 +8410,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=F2F + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=F2F GET true false @@ -8440,7 +8440,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=TSK GET true false @@ -8470,7 +8470,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=TSK + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=TSK GET true false @@ -8500,7 +8500,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=TSK + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&types[]=TSK GET true false @@ -8530,7 +8530,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=CH + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=CH GET true false @@ -8560,7 +8560,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=CH + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=CH GET true false @@ -8590,7 +8590,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=CH + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=CH GET true false @@ -8620,7 +8620,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=F2F + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=F2F GET true false @@ -8650,7 +8650,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=F2F + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=F2F GET true false @@ -8680,7 +8680,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=F2F + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=F2F GET true false @@ -8710,7 +8710,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=TSK GET true false @@ -8740,7 +8740,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=TSK + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=TSK GET true false @@ -8770,7 +8770,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=TSK + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=DS&types[]=TSK GET true false @@ -8800,7 +8800,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=CH + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=CH GET true false @@ -8830,7 +8830,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=CH + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=CH GET true false @@ -8860,7 +8860,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=CH + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=CH GET true false @@ -8890,7 +8890,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=F2F + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=F2F GET true false @@ -8920,7 +8920,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=F2F + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=F2F GET true false @@ -8950,7 +8950,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=F2F + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=F2F GET true false @@ -8980,7 +8980,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=TSK GET true false @@ -9010,7 +9010,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=TSK + /v5/challenges/?status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=TSK GET true false @@ -9040,7 +9040,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=TSK + /v5/challenges/?status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&types[]=TSK GET true false @@ -9070,7 +9070,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&tracks[]=Dev&types[]=CH&types[]=TSK + /v5/challenges/?status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=QA&tracks[]=Dev&types[]=CH&types[]=TSK GET true false @@ -9100,7 +9100,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?startDateEnd=2020-01-31T18%3A29%3A59.999Z&endDateStart=2019-12-31T18%3A30%3A00.000Z&status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?startDateEnd=2020-01-31T18%3A29%3A59.999Z&endDateStart=2019-12-31T18%3A30%3A00.000Z&status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -9298,7 +9298,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?search=${tags}&tab=details&status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?search=${tags}&tab=details&status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -9340,7 +9340,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?search=topcoder&status=Active&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?search=topcoder&status=ACTIVE&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -9370,7 +9370,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?search=topcoder&status=Active&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?search=topcoder&status=ACTIVE&currentPhaseName=Registration&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false @@ -9400,7 +9400,7 @@ start up time/ramp-up time = Shutdown time/ramp-down time of the previous record https - /v5/challenges/?search=topcoder&status=Completed&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK + /v5/challenges/?search=topcoder&status=COMPLETED&perPage=10&page=1&sortBy=startDate&sortOrder=desc&tracks[]=Dev&tracks[]=Des&tracks[]=DS&tracks[]=QA&types[]=CH&types[]=F2F&types[]=TSK GET true false diff --git a/src/test/jmeter/path.csv b/src/test/jmeter/path.csv index 1a045c243e..47c5980622 100644 --- a/src/test/jmeter/path.csv +++ b/src/test/jmeter/path.csv @@ -3,7 +3,6 @@ path /members/codejam /challenges /community/arena -/gigs /community/practice /thrive/tracks?track=Competitive%20Programming /thrive/tracks?track=Data%20Science&tax= @@ -30,4 +29,4 @@ path /settings/account /settings/account#my-account /settings/account#linked-accounts -/settings/preferences \ No newline at end of file +/settings/preferences