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

My gigs Release on 7/26 #162

Merged
merged 24 commits into from
Jul 23, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@
"dev-https": "cross-env APPMODE=development webpack-dev-server --https --port 8008",
"build": "webpack --mode=${APPMODE:-development} --env.config=${APPENV:-dev}",
"analyze": "webpack --mode=production --env.analyze=true",
"lint": "eslint src --ext js,jsx",
"lint": "eslint src --ext js,jsx --fix",
"format": "prettier --write \"./**\"",
"test": "cross-env BABEL_ENV=test jest",
"watch-tests": "cross-env BABEL_ENV=test jest --watch",
45 changes: 41 additions & 4 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -5,14 +5,15 @@ import React, { useState, useLayoutEffect, useEffect, useRef } from "react";
import { Router, useLocation, Redirect } from "@reach/router";
import Challenges from "./containers/Challenges";
import Filter from "./containers/Filter";
import MyGigsFilter from "./containers/MyGigsFilter";
import MyGigs from "./containers/MyGigs";
import Menu from "./components/Menu";
import { disableSidebarForRoute } from "@topcoder/micro-frontends-navbar-app";
import * as constants from "./constants";
import actions from "./actions";
import * as utils from "./utils";
import store from "./store";
import { initialChallengeFilter } from "./reducers/filter";
import { initialChallengeFilter, initialGigFilter } from "./reducers/filter";
import _ from "lodash";
import { usePreviousLocation } from "./utils/hooks";
import { useSelector } from "react-redux";
@@ -38,6 +39,9 @@ const App = () => {
selected={selectedMenuItem}
onSelect={(item) => {
setSelectedMenuItem(item);
if (item == "Gigs") {
window.location.href = `${process.env.URL.BASE}/gigs`;
}
}}
isLoggedIn={isLoggedIn}
/>
@@ -46,7 +50,7 @@ const App = () => {
const location = useLocation();
const previousLocation = usePreviousLocation();

const getChallengesDebounced = useRef(_.debounce((f) => f(), 500));
const getDataDebounced = useRef(_.debounce((f) => f(), 500));

useEffect(() => {
store.dispatch(actions.lookup.checkIsLoggedIn());
@@ -74,12 +78,44 @@ const App = () => {
if (diff) {
store.dispatch(actions.filter.updateFilter(updatedFilter));
}
getChallengesDebounced.current(() =>
getDataDebounced.current(() =>
store.dispatch(actions.challenges.getChallenges(updatedFilter))
);
}
}, [location]);

useEffect(() => {
if (location.pathname === "/earn/my-gigs" && isLoggedIn) {
if (!location.search) {
store.dispatch(
actions.myGigs.getMyGigs(
constants.GIGS_FILTER_STATUSES_PARAM[initialGigFilter.status]
)
);
return;
}
const params = utils.url.parseUrlQuery(location.search);
if (_.keys(params).length == 1 && params.externalId) {
return;
}
const updatedGigFilter = {
status: params.status || "Open Applications",
};
const currentGig = store.getState().filter.gig;
const diff = !_.isEqual(updatedGigFilter, currentGig);
if (diff) {
store.dispatch(actions.filter.updateGigFilter(updatedGigFilter));
}
getDataDebounced.current(() =>
store.dispatch(
actions.myGigs.getMyGigs(
constants.GIGS_FILTER_STATUSES_PARAM[updatedGigFilter.status]
)
)
);
}
}, [location, isLoggedIn]);

const varsRef = useRef();
varsRef.current = { previousLocation };

@@ -108,7 +144,8 @@ const App = () => {
<div className="sidebar-content">
{menu}
<hr />
<Filter />
{location.pathname === "/earn/find/challenges" && <Filter />}
{location.pathname === "/earn/my-gigs" && <MyGigsFilter />}
</div>
<div className="sidebar-footer">
<a
12 changes: 12 additions & 0 deletions src/actions/filter.js
Original file line number Diff line number Diff line change
@@ -5,6 +5,10 @@ function updateFilter(partialUpdate) {
return partialUpdate;
}

function updateGigFilter(partialUpdate) {
return partialUpdate;
}

function clearChallengeFilter(defaultFilter) {
return defaultFilter;
}
@@ -15,8 +19,16 @@ function updateChallengeQuery(filter) {
return params;
}

function updateGigQuery(filter) {
const params = utils.myGig.createGigParams(filter);
utils.url.updateQuery(params);
return params;
}

export default createActions({
UPDATE_FILTER: updateFilter,
CLEAR_CHALLENGE_FILTER: clearChallengeFilter,
UPDATE_CHALLENGE_QUERY: updateChallengeQuery,
UPDATE_GIG_FILTER: updateGigFilter,
UPDATE_GIG_QUERY: updateGigQuery,
});
8 changes: 4 additions & 4 deletions src/actions/myGigs.js
Original file line number Diff line number Diff line change
@@ -12,8 +12,8 @@ import service from "../services/myGigs";
* @param {number} perPage items per page. by default is 10.
* @returns
*/
async function getMyGigs(page = 1, perPage = PER_PAGE) {
return service.getMyGigs(page, perPage);
async function getMyGigs(status = "open_jobs", page = 1, perPage = PER_PAGE) {
return service.getMyGigs(status, page, perPage);
}

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

async function getProfile() {
54 changes: 54 additions & 0 deletions src/api/app-constants.js
Original file line number Diff line number Diff line change
@@ -11,6 +11,60 @@ const Scopes = {
ALL_PROFILE: "all:earn-profile",
};

const MY_GIGS_JOB_STATUS = {
APPLIED: "applied",
SKILLS_TEST: "skills-test",
PHONE_SCREEN: "phone-screen",
SCREEN_PASS: "open",
INTERVIEW: "interview",
SELECTED: "selected",
OFFERED: "offered",
PLACED: "placed",
REJECTED_OTHER: "rejected - other",
REJECTED_PRE_SCREEN: "rejected-pre-screen",
CLIENT_REJECTED_INTERVIEW: "client rejected - interview",
CLIENT_REJECTED_SCREENING: "client rejected - screening",
JOB_CLOSED: "job-closed",
WITHDRAWN: "withdrawn",
WITHDRAWN_PRESCREEN: "withdrawn-prescreen",
};

const JOB_APPLICATION_STATUS_MAPPER = {
active_jobs: {
statuses: [MY_GIGS_JOB_STATUS.PLACED],
},
open_jobs: {
statuses: [
MY_GIGS_JOB_STATUS.APPLIED,
MY_GIGS_JOB_STATUS.SKILLS_TEST,
MY_GIGS_JOB_STATUS.PHONE_SCREEN,
MY_GIGS_JOB_STATUS.SCREEN_PASS,
MY_GIGS_JOB_STATUS.INTERVIEW,
MY_GIGS_JOB_STATUS.SELECTED,
MY_GIGS_JOB_STATUS.OFFERED,
],
},
completed_jobs: {
statuses: [MY_GIGS_JOB_STATUS.PLACED],
},
archived_jobs: {
statuses: [
MY_GIGS_JOB_STATUS.JOB_CLOSED,
MY_GIGS_JOB_STATUS.REJECTED_OTHER,
MY_GIGS_JOB_STATUS.REJECTED_PRE_SCREEN,
MY_GIGS_JOB_STATUS.CLIENT_REJECTED_INTERVIEW,
MY_GIGS_JOB_STATUS.CLIENT_REJECTED_SCREENING,
MY_GIGS_JOB_STATUS.WITHDRAWN,
MY_GIGS_JOB_STATUS.WITHDRAWN_PRESCREEN,
],
},
};

const MIN_HOUR_PER_WEEK_TO_WITHDRAW = 20;

module.exports = {
Scopes,
MY_GIGS_JOB_STATUS,
JOB_APPLICATION_STATUS_MAPPER,
MIN_HOUR_PER_WEEK_TO_WITHDRAW,
};
70 changes: 70 additions & 0 deletions src/api/common/helper.js
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@

const _ = require("lodash");
const config = require("config");
const constants = require("../app-constants");
const logger = require("./logger");
const httpStatus = require("http-status");
const Interceptor = require("express-interceptor");
@@ -265,10 +266,18 @@ async function getCurrentUserDetails(token) {
*/
async function getJobCandidates(criteria) {
const token = await getM2MToken();
let body = { statuses: [] };
if (criteria.status) {
body = constants.JOB_APPLICATION_STATUS_MAPPER[criteria.status] || {
statuses: [],
};
}
delete criteria.status;
const url = `${config.API.V5}/jobCandidates`;
const res = await request
.get(url)
.query(criteria)
.send(body)
.set("Authorization", `Bearer ${token}`)
.set("Accept", "application/json");
localLogger.debug({
@@ -283,6 +292,66 @@ async function getJobCandidates(criteria) {
};
}

/**
* Process placed job candidate to calculate the completed status
*
* @param {*} jobCandidates
* @param {*} userId
* @returns
*/
async function handlePlacedJobCandidates(jobCandidates, userId) {
if (!jobCandidates || jobCandidates.length == 0 || !userId) {
return;
}
const placedJobs = jobCandidates.filter(
(item) => item.status == constants.MY_GIGS_JOB_STATUS.PLACED
);
if (placedJobs.length == 0) {
return;
}
const token = await getM2MToken();
const url = `${config.API.V5}/resourceBookings`;
const criteria = {
userId: userId,
page: 1,
perPage: placedJobs.length,
};
const body = {
jobIds: _.map(placedJobs, "jobId"),
};
const res = await request
.get(url)
.query(criteria)
.send(body)
.set("Authorization", `Bearer ${token}`)
.set("Accept", "application/json");
localLogger.debug({
context: "handlePlacedJobCandidates",
message: `response body: ${JSON.stringify(res.body)}`,
});
if (res.body && res.body.length == 0) {
return;
}
// Handle placed job status with RB result
const rbRes = res.body;
_.each(rbRes, (rb) => {
const jc = jobCandidates.find(
(item) => item.userId == rb.userId && item.jobId == rb.jobId
);
if (jc) {
if (rb.endDate) {
if (
new Date(rb.endDate) < new Date() &&
new Date(rb.endDate).toDateString() != new Date().toDateString()
) {
jc.status = "completed";
}
}
}
});
return;
}

/**
* Return jobs by given criteria
* @param {string} criteria the search criteria
@@ -467,6 +536,7 @@ module.exports = {
getM2MToken,
getCurrentUserDetails,
getJobCandidates,
handlePlacedJobCandidates,
getJobs,
getMember,
getMemberTraits,
11 changes: 11 additions & 0 deletions src/api/docs/swagger.yaml
Original file line number Diff line number Diff line change
@@ -49,6 +49,12 @@ paths:
type: string
default: desc
enum: ["desc", "asc"]
- in: query
name: status
required: false
schema:
type: string
enum: ["active_jobs", "open_jobs", "completed_jobs", "archived_jobs"]
responses:
"200":
description: OK
@@ -260,6 +266,7 @@ components:
- status
- remark
- interview
- jobExternalId
properties:
title:
type: string
@@ -305,6 +312,10 @@ components:
description: "The remark of candidate"
interview:
$ref: "#/components/schemas/Interview"
jobExternalId:
type: string
example: "51313517"
description: "The corresponding gig ID on community app"
Payment:
required:
- min
Loading