Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit 47e22a0

Browse files
authored
Merge pull request #165 from topcoder-platform/dev
[PROD] Next Release
2 parents 5b509c2 + b32a626 commit 47e22a0

File tree

21 files changed

+489
-35
lines changed

21 files changed

+489
-35
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"dev-https": "cross-env APPMODE=development webpack-dev-server --https --port 8008",
77
"build": "webpack --mode=${APPMODE:-development} --env.config=${APPENV:-dev}",
88
"analyze": "webpack --mode=production --env.analyze=true",
9-
"lint": "eslint src --ext js,jsx",
9+
"lint": "eslint src --ext js,jsx --fix",
1010
"format": "prettier --write \"./**\"",
1111
"test": "cross-env BABEL_ENV=test jest",
1212
"watch-tests": "cross-env BABEL_ENV=test jest --watch",

src/App.jsx

+43-4
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@ import React, { useState, useLayoutEffect, useEffect, useRef } from "react";
55
import { Router, useLocation, Redirect } from "@reach/router";
66
import Challenges from "./containers/Challenges";
77
import Filter from "./containers/Filter";
8+
import MyGigsFilter from "./containers/MyGigsFilter";
89
import MyGigs from "./containers/MyGigs";
910
import Menu from "./components/Menu";
1011
import { disableSidebarForRoute } from "@topcoder/micro-frontends-navbar-app";
1112
import * as constants from "./constants";
1213
import actions from "./actions";
1314
import * as utils from "./utils";
1415
import store from "./store";
15-
import { initialChallengeFilter } from "./reducers/filter";
16+
import { initialChallengeFilter, initialGigFilter } from "./reducers/filter";
1617
import _ from "lodash";
1718
import { usePreviousLocation } from "./utils/hooks";
1819
import { useSelector } from "react-redux";
@@ -38,6 +39,9 @@ const App = () => {
3839
selected={selectedMenuItem}
3940
onSelect={(item) => {
4041
setSelectedMenuItem(item);
42+
if (item == "Gigs") {
43+
window.location.href = `${process.env.URL.BASE}/gigs`;
44+
}
4145
}}
4246
isLoggedIn={isLoggedIn}
4347
/>
@@ -46,7 +50,7 @@ const App = () => {
4650
const location = useLocation();
4751
const previousLocation = usePreviousLocation();
4852

49-
const getChallengesDebounced = useRef(_.debounce((f) => f(), 500));
53+
const getDataDebounced = useRef(_.debounce((f) => f(), 500));
5054

5155
useEffect(() => {
5256
store.dispatch(actions.lookup.checkIsLoggedIn());
@@ -74,12 +78,46 @@ const App = () => {
7478
if (diff) {
7579
store.dispatch(actions.filter.updateFilter(updatedFilter));
7680
}
77-
getChallengesDebounced.current(() =>
81+
getDataDebounced.current(() =>
7882
store.dispatch(actions.challenges.getChallenges(updatedFilter))
7983
);
8084
}
8185
}, [location]);
8286

87+
useEffect(() => {
88+
if (location.pathname === "/earn/my-gigs" && isLoggedIn) {
89+
if (!location.search) {
90+
store.dispatch(actions.filter.updateGigFilter(initialGigFilter));
91+
92+
store.dispatch(
93+
actions.myGigs.getMyGigs(
94+
constants.GIGS_FILTER_STATUSES_PARAM[initialGigFilter.status]
95+
)
96+
);
97+
return;
98+
}
99+
const params = utils.url.parseUrlQuery(location.search);
100+
if (_.keys(params).length == 1 && params.externalId) {
101+
return;
102+
}
103+
const updatedGigFilter = {
104+
status: params.status || "Open Applications",
105+
};
106+
const currentGig = store.getState().filter.gig;
107+
const diff = !_.isEqual(updatedGigFilter, currentGig);
108+
if (diff) {
109+
store.dispatch(actions.filter.updateGigFilter(updatedGigFilter));
110+
}
111+
getDataDebounced.current(() =>
112+
store.dispatch(
113+
actions.myGigs.getMyGigs(
114+
constants.GIGS_FILTER_STATUSES_PARAM[updatedGigFilter.status]
115+
)
116+
)
117+
);
118+
}
119+
}, [location, isLoggedIn]);
120+
83121
const varsRef = useRef();
84122
varsRef.current = { previousLocation };
85123

@@ -108,7 +146,8 @@ const App = () => {
108146
<div className="sidebar-content">
109147
{menu}
110148
<hr />
111-
<Filter />
149+
{location.pathname === "/earn/find/challenges" && <Filter />}
150+
{location.pathname === "/earn/my-gigs" && <MyGigsFilter />}
112151
</div>
113152
<div className="sidebar-footer">
114153
<a

src/actions/filter.js

+12
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ function updateFilter(partialUpdate) {
55
return partialUpdate;
66
}
77

8+
function updateGigFilter(partialUpdate) {
9+
return partialUpdate;
10+
}
11+
812
function clearChallengeFilter(defaultFilter) {
913
return defaultFilter;
1014
}
@@ -15,8 +19,16 @@ function updateChallengeQuery(filter) {
1519
return params;
1620
}
1721

22+
function updateGigQuery(filter) {
23+
const params = utils.myGig.createGigParams(filter);
24+
utils.url.updateQuery(params);
25+
return params;
26+
}
27+
1828
export default createActions({
1929
UPDATE_FILTER: updateFilter,
2030
CLEAR_CHALLENGE_FILTER: clearChallengeFilter,
2131
UPDATE_CHALLENGE_QUERY: updateChallengeQuery,
32+
UPDATE_GIG_FILTER: updateGigFilter,
33+
UPDATE_GIG_QUERY: updateGigQuery,
2234
});

src/actions/myGigs.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ import service from "../services/myGigs";
1212
* @param {number} perPage items per page. by default is 10.
1313
* @returns
1414
*/
15-
async function getMyGigs(page = 1, perPage = PER_PAGE) {
16-
return service.getMyGigs(page, perPage);
15+
async function getMyGigs(status = "open_jobs", page = 1, perPage = PER_PAGE) {
16+
return service.getMyGigs(status, page, perPage);
1717
}
1818

1919
/**
@@ -22,8 +22,8 @@ async function getMyGigs(page = 1, perPage = PER_PAGE) {
2222
* @param {*} perPage items per page. by default is 10
2323
* @returns
2424
*/
25-
async function loadMoreMyGigs(nextPage, perPage = PER_PAGE) {
26-
return service.getMyGigs(nextPage, perPage);
25+
async function loadMoreMyGigs(status, nextPage, perPage = PER_PAGE) {
26+
return service.getMyGigs(status, nextPage, perPage);
2727
}
2828

2929
async function getProfile() {

src/api/app-constants.js

+54
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,60 @@ const Scopes = {
1111
ALL_PROFILE: "all:earn-profile",
1212
};
1313

14+
const MY_GIGS_JOB_STATUS = {
15+
APPLIED: "applied",
16+
SKILLS_TEST: "skills-test",
17+
PHONE_SCREEN: "phone-screen",
18+
SCREEN_PASS: "open",
19+
INTERVIEW: "interview",
20+
SELECTED: "selected",
21+
OFFERED: "offered",
22+
PLACED: "placed",
23+
REJECTED_OTHER: "rejected - other",
24+
REJECTED_PRE_SCREEN: "rejected-pre-screen",
25+
CLIENT_REJECTED_INTERVIEW: "client rejected - interview",
26+
CLIENT_REJECTED_SCREENING: "client rejected - screening",
27+
JOB_CLOSED: "job-closed",
28+
WITHDRAWN: "withdrawn",
29+
WITHDRAWN_PRESCREEN: "withdrawn-prescreen",
30+
};
31+
32+
const JOB_APPLICATION_STATUS_MAPPER = {
33+
active_jobs: {
34+
statuses: [MY_GIGS_JOB_STATUS.PLACED],
35+
},
36+
open_jobs: {
37+
statuses: [
38+
MY_GIGS_JOB_STATUS.APPLIED,
39+
MY_GIGS_JOB_STATUS.SKILLS_TEST,
40+
MY_GIGS_JOB_STATUS.PHONE_SCREEN,
41+
MY_GIGS_JOB_STATUS.SCREEN_PASS,
42+
MY_GIGS_JOB_STATUS.INTERVIEW,
43+
MY_GIGS_JOB_STATUS.SELECTED,
44+
MY_GIGS_JOB_STATUS.OFFERED,
45+
],
46+
},
47+
completed_jobs: {
48+
statuses: [MY_GIGS_JOB_STATUS.PLACED],
49+
},
50+
archived_jobs: {
51+
statuses: [
52+
MY_GIGS_JOB_STATUS.JOB_CLOSED,
53+
MY_GIGS_JOB_STATUS.REJECTED_OTHER,
54+
MY_GIGS_JOB_STATUS.REJECTED_PRE_SCREEN,
55+
MY_GIGS_JOB_STATUS.CLIENT_REJECTED_INTERVIEW,
56+
MY_GIGS_JOB_STATUS.CLIENT_REJECTED_SCREENING,
57+
MY_GIGS_JOB_STATUS.WITHDRAWN,
58+
MY_GIGS_JOB_STATUS.WITHDRAWN_PRESCREEN,
59+
],
60+
},
61+
};
62+
63+
const MIN_HOUR_PER_WEEK_TO_WITHDRAW = 20;
64+
1465
module.exports = {
1566
Scopes,
67+
MY_GIGS_JOB_STATUS,
68+
JOB_APPLICATION_STATUS_MAPPER,
69+
MIN_HOUR_PER_WEEK_TO_WITHDRAW,
1670
};

src/api/common/helper.js

+70
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
const _ = require("lodash");
66
const config = require("config");
7+
const constants = require("../app-constants");
78
const logger = require("./logger");
89
const httpStatus = require("http-status");
910
const Interceptor = require("express-interceptor");
@@ -265,10 +266,18 @@ async function getCurrentUserDetails(token) {
265266
*/
266267
async function getJobCandidates(criteria) {
267268
const token = await getM2MToken();
269+
let body = { statuses: [] };
270+
if (criteria.status) {
271+
body = constants.JOB_APPLICATION_STATUS_MAPPER[criteria.status] || {
272+
statuses: [],
273+
};
274+
}
275+
delete criteria.status;
268276
const url = `${config.API.V5}/jobCandidates`;
269277
const res = await request
270278
.get(url)
271279
.query(criteria)
280+
.send(body)
272281
.set("Authorization", `Bearer ${token}`)
273282
.set("Accept", "application/json");
274283
localLogger.debug({
@@ -283,6 +292,66 @@ async function getJobCandidates(criteria) {
283292
};
284293
}
285294

295+
/**
296+
* Process placed job candidate to calculate the completed status
297+
*
298+
* @param {*} jobCandidates
299+
* @param {*} userId
300+
* @returns
301+
*/
302+
async function handlePlacedJobCandidates(jobCandidates, userId) {
303+
if (!jobCandidates || jobCandidates.length == 0 || !userId) {
304+
return;
305+
}
306+
const placedJobs = jobCandidates.filter(
307+
(item) => item.status == constants.MY_GIGS_JOB_STATUS.PLACED
308+
);
309+
if (placedJobs.length == 0) {
310+
return;
311+
}
312+
const token = await getM2MToken();
313+
const url = `${config.API.V5}/resourceBookings`;
314+
const criteria = {
315+
userId: userId,
316+
page: 1,
317+
perPage: placedJobs.length,
318+
};
319+
const body = {
320+
jobIds: _.map(placedJobs, "jobId"),
321+
};
322+
const res = await request
323+
.get(url)
324+
.query(criteria)
325+
.send(body)
326+
.set("Authorization", `Bearer ${token}`)
327+
.set("Accept", "application/json");
328+
localLogger.debug({
329+
context: "handlePlacedJobCandidates",
330+
message: `response body: ${JSON.stringify(res.body)}`,
331+
});
332+
if (res.body && res.body.length == 0) {
333+
return;
334+
}
335+
// Handle placed job status with RB result
336+
const rbRes = res.body;
337+
_.each(rbRes, (rb) => {
338+
const jc = jobCandidates.find(
339+
(item) => item.userId == rb.userId && item.jobId == rb.jobId
340+
);
341+
if (jc) {
342+
if (rb.endDate) {
343+
if (
344+
new Date(rb.endDate) < new Date() &&
345+
new Date(rb.endDate).toDateString() != new Date().toDateString()
346+
) {
347+
jc.status = "completed";
348+
}
349+
}
350+
}
351+
});
352+
return;
353+
}
354+
286355
/**
287356
* Return jobs by given criteria
288357
* @param {string} criteria the search criteria
@@ -467,6 +536,7 @@ module.exports = {
467536
getM2MToken,
468537
getCurrentUserDetails,
469538
getJobCandidates,
539+
handlePlacedJobCandidates,
470540
getJobs,
471541
getMember,
472542
getMemberTraits,

src/api/docs/swagger.yaml

+11
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ paths:
4949
type: string
5050
default: desc
5151
enum: ["desc", "asc"]
52+
- in: query
53+
name: status
54+
required: false
55+
schema:
56+
type: string
57+
enum: ["active_jobs", "open_jobs", "completed_jobs", "archived_jobs"]
5258
responses:
5359
"200":
5460
description: OK
@@ -260,6 +266,7 @@ components:
260266
- status
261267
- remark
262268
- interview
269+
- jobExternalId
263270
properties:
264271
title:
265272
type: string
@@ -305,6 +312,10 @@ components:
305312
description: "The remark of candidate"
306313
interview:
307314
$ref: "#/components/schemas/Interview"
315+
jobExternalId:
316+
type: string
317+
example: "51313517"
318+
description: "The corresponding gig ID on community app"
308319
Payment:
309320
required:
310321
- min

0 commit comments

Comments
 (0)