Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 5b509c2

Browse files
authoredJul 7, 2021
Merge pull request #155 from topcoder-platform/dev
Gigs Optimization Update Release
2 parents 7a865ea + 70dd05d commit 5b509c2

File tree

21 files changed

+399
-36
lines changed

21 files changed

+399
-36
lines changed
 

‎package-lock.json

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
"react-date-range": "^1.1.3",
8282
"react-dom": "^16.12.0",
8383
"react-dropzone": "^11.3.2",
84+
"react-loading": "^2.0.3",
8485
"react-redux": "^7.2.3",
8586
"react-responsive-modal": "^6.1.0",
8687
"react-select": "^1.3.0",

‎src/App.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,8 @@ const App = () => {
112112
</div>
113113
<div className="sidebar-footer">
114114
<a
115-
className="button button-primary"
116-
href="https://github.com/topcoder-platform/micro-frontends-earn-app/issues/new?assignees=&labels=&template=bug_report.md&title="
115+
className="button"
116+
href="https://discussions.topcoder.com/discussion/8870/new-beta-site-discuss?new=1"
117117
target="_blank"
118118
>
119119
GIVE APPLICATION FEEDBACK

‎src/actions/myGigs.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { createActions } from "redux-actions";
2-
import { PER_PAGE } from "../constants";
2+
import {
3+
PER_PAGE,
4+
CHECKING_GIG_TIMES,
5+
DELAY_CHECK_GIG_TIME,
6+
} from "../constants";
37
import service from "../services/myGigs";
48

59
/**
@@ -30,9 +34,29 @@ async function updateProfile(profile) {
3034
return service.updateProfile(profile);
3135
}
3236

37+
async function startCheckingGigs(externalId) {
38+
let i = 0;
39+
while (i < CHECKING_GIG_TIMES) {
40+
const res = await service.startCheckingGigs(externalId);
41+
if (res && !res.synced) {
42+
await new Promise((resolve) => {
43+
setTimeout(() => {
44+
resolve();
45+
}, DELAY_CHECK_GIG_TIME);
46+
});
47+
i++;
48+
continue;
49+
} else {
50+
return {};
51+
}
52+
}
53+
return {};
54+
}
55+
3356
export default createActions({
3457
GET_MY_GIGS: getMyGigs,
3558
LOAD_MORE_MY_GIGS: loadMoreMyGigs,
3659
GET_PROFILE: getProfile,
3760
UPDATE_PROFILE: updateProfile,
61+
START_CHECKING_GIGS: startCheckingGigs,
3862
});

‎src/api/app-constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
const Scopes = {
66
// JobApplication
77
READ_JOBAPPLICATION: "read:earn-jobApplications",
8+
READ_JOB: "read:earn-job",
89
READ_PROFILE: "read:earn-profile",
910
WRITE_PROFILE: "write:earn-profile",
1011
ALL_PROFILE: "all:earn-profile",

‎src/api/controllers/JobApplicationController.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ async function getMyJobApplications(req, res) {
1515
res.send(result.result);
1616
}
1717

18+
async function getJob(req, res) {
19+
const result = await service.getJob(req.authUser, req.query);
20+
res.send(result);
21+
}
22+
1823
module.exports = {
1924
getMyJobApplications,
25+
getJob,
2026
};

‎src/api/docs/swagger.yaml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,60 @@ paths:
8888
application/json:
8989
schema:
9090
$ref: "#/components/schemas/Error"
91+
/job:
92+
get:
93+
tags:
94+
- Job
95+
description: |
96+
Check whether the Job Application info has been synced.
97+
98+
**Authorization** All topcoder members are allowed.
99+
security:
100+
- bearerAuth: []
101+
parameters:
102+
- in: query
103+
name: externalId
104+
required: true
105+
schema:
106+
type: string
107+
description: The Job External Id
108+
responses:
109+
"200":
110+
description: OK
111+
content:
112+
application/json:
113+
schema:
114+
$ref: "#/components/schemas/JobSynced"
115+
"400":
116+
description: Bad request
117+
content:
118+
application/json:
119+
schema:
120+
$ref: "#/components/schemas/Error"
121+
"401":
122+
description: Not authenticated
123+
content:
124+
application/json:
125+
schema:
126+
$ref: "#/components/schemas/Error"
127+
"403":
128+
description: Forbidden
129+
content:
130+
application/json:
131+
schema:
132+
$ref: "#/components/schemas/Error"
133+
"404":
134+
description: Not Found
135+
content:
136+
application/json:
137+
schema:
138+
$ref: "#/components/schemas/Error"
139+
"500":
140+
description: Internal Server Error
141+
content:
142+
application/json:
143+
schema:
144+
$ref: "#/components/schemas/Error"
91145
/profile:
92146
get:
93147
tags:
@@ -187,6 +241,14 @@ components:
187241
scheme: bearer
188242
bearerFormat: JWT
189243
schemas:
244+
JobSynced:
245+
required:
246+
- synced
247+
properties:
248+
synced:
249+
type: boolean
250+
description: "Whether the job application has been synced"
251+
example: true
190252
JobApplication:
191253
required:
192254
- title

‎src/api/routes/JobApplicationRoutes.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,12 @@ module.exports = {
1212
scopes: [constants.Scopes.READ_JOBAPPLICATION],
1313
},
1414
},
15+
"/job": {
16+
get: {
17+
controller: "JobApplicationController",
18+
method: "getJob",
19+
auth: "jwt",
20+
scopes: [constants.Scopes.READ_JOB],
21+
},
22+
},
1523
};

‎src/api/services/JobApplicationService.js

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ async function getMyJobApplications(currentUser, criteria) {
6161
min: job.minSalary,
6262
max: job.maxSalary,
6363
frequency: job.rateType,
64-
currency: job.currency,
64+
// currency: job.currency,
65+
currency: "$",
6566
},
6667
hoursPerWeek: job.hoursPerWeek,
6768
location: job.jobLocation,
@@ -96,6 +97,51 @@ getMyJobApplications.schema = Joi.object()
9697
})
9798
.required();
9899

100+
async function getJob(currentUser, criteria) {
101+
const emptyResult = {
102+
synced: false,
103+
};
104+
// we expect logged-in users
105+
if (currentUser.isMachine) {
106+
return emptyResult;
107+
}
108+
// get user id by calling taas-api with current user's token
109+
const { id: userId } = await helper.getCurrentUserDetails(
110+
currentUser.jwtToken
111+
);
112+
if (!userId) {
113+
throw new errors.NotFoundError(
114+
`Id for user: ${currentUser.userId} not found`
115+
);
116+
}
117+
// get job based on the jobExternalId
118+
const { result: jobs } = await helper.getJobs(criteria);
119+
if (jobs && jobs.length) {
120+
const candidates = jobs[0].candidates || [];
121+
const newJob = candidates.find((item) => item.userId == userId);
122+
if (newJob) {
123+
return {
124+
synced: true,
125+
};
126+
}
127+
}
128+
return {
129+
synced: false,
130+
};
131+
}
132+
133+
getJob.schema = Joi.object()
134+
.keys({
135+
currentUser: Joi.object().required(),
136+
criteria: Joi.object()
137+
.keys({
138+
externalId: Joi.string(),
139+
})
140+
.required(),
141+
})
142+
.required();
143+
99144
module.exports = {
100145
getMyJobApplications,
146+
getJob,
101147
};

‎src/components/Banner/index.jsx

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,7 @@ const Banner = () => {
3737
please let us know! This is what the Beta phase is intended for, and
3838
your feedback will enable us to greatly improve the new site.{" "}
3939
</p>
40-
<p>
41-
You can click on the Feedback button on page or file a github ticket
42-
at{" "}
43-
<a
44-
href="https://github.com/topcoder-platform/micro-frontends-earn-app/issues/new?assignees=&labels=&template=bug_report.md"
45-
target="_blank"
46-
>
47-
https://github.com/topcoder-platform/micro-frontends-earn-app/issues/new?template=bug_report.md
48-
</a>
49-
.
50-
</p>
40+
<p>You can click on the Feedback button on page.</p>
5141
<p>Thank you!</p>
5242
</div>
5343
)}

‎src/components/Empty/index.jsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import React from "react";
2+
import { EMPTY_GIGS_TEXT } from "../../constants";
3+
import Button from "../Button";
4+
import "./styles.scss";
5+
6+
const Empty = () => {
7+
return (
8+
<div styleName="empty-wrapper">
9+
<div styleName="empty-inner">
10+
<h6>{EMPTY_GIGS_TEXT}</h6>
11+
<span>Interested in getting a gig?</span>
12+
<Button
13+
isPrimary
14+
size="lg"
15+
onClick={() => {
16+
window.location.href = `${process.env.URL.BASE}/gigs`;
17+
}}
18+
>
19+
VIEW GIGS
20+
</Button>
21+
</div>
22+
</div>
23+
);
24+
};
25+
26+
export default Empty;

‎src/components/Empty/styles.scss

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
@import "styles/variables";
2+
@import "styles/mixins";
3+
@import "styles/animations";
4+
5+
.empty-wrapper {
6+
width: 100%;
7+
border-radius: $border-radius-lg;
8+
background-color: #ffffff;
9+
padding: 81px 0px 330px 0px;
10+
min-width: 650px;
11+
.empty-inner {
12+
display: flex;
13+
flex-direction: column;
14+
justify-content: center;
15+
align-items: center;
16+
& > h6 {
17+
font-size: 20px;
18+
color: $tc-black;
19+
line-height: 24px;
20+
@include barlow-semibold;
21+
}
22+
& > span {
23+
font-size: 16px;
24+
color: $tc-black;
25+
line-height: 26px;
26+
margin-top: 30px;
27+
@include roboto-regular;
28+
}
29+
& > button {
30+
margin-top: 20px;
31+
letter-spacing: 0.8px;
32+
}
33+
}
34+
}

‎src/components/Loading/index.jsx

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import React from "react";
2+
import ReactLoading from "react-loading";
3+
import PT from "prop-types";
4+
import "./styles.scss";
5+
6+
const Loading = (props) => {
7+
const { color, type, height, width } = props;
8+
return (
9+
<div styleName="loading-wrapper">
10+
<div styleName="loading-inner">
11+
<div>
12+
<ReactLoading
13+
type={type}
14+
color={color}
15+
height={height}
16+
width={width}
17+
/>
18+
</div>
19+
<h6>LOADING</h6>
20+
<span>We are processing your gigs data</span>
21+
</div>
22+
</div>
23+
);
24+
};
25+
26+
Loading.defaultProps = {
27+
color: "#0ab88a",
28+
type: "spin",
29+
width: 35,
30+
height: 35,
31+
};
32+
33+
Loading.propTypes = {
34+
color: PT.string,
35+
type: PT.string,
36+
width: PT.number,
37+
height: PT.number,
38+
};
39+
40+
export default Loading;

‎src/components/Loading/styles.scss

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
@import "styles/variables";
2+
@import "styles/mixins";
3+
@import "styles/animations";
4+
5+
.loading-wrapper {
6+
width: 100%;
7+
border-radius: $border-radius-lg;
8+
background-color: rgba(42,42,42, 0.07);
9+
padding: 223px 0px;
10+
.loading-inner {
11+
display: flex;
12+
flex-direction: column;
13+
justify-content: center;
14+
align-items: center;
15+
& > h6 {
16+
margin-top: 15px;
17+
font-size: 20px;
18+
color: $tc-turquoise-dark1;
19+
line-height: 24px;
20+
@include barlow-bold;
21+
}
22+
& > span {
23+
font-size: 16px;
24+
color: $tc-gray1;
25+
line-height: 26px;
26+
margin-top: 5px;
27+
@include roboto-regular;
28+
}
29+
svg {
30+
path:first-child {
31+
fill: $tc-gray4;
32+
opacity: 1;
33+
}
34+
path:last-child {
35+
animation: rotate 0.8s linear infinite;
36+
transform-origin: 50% 50%;
37+
}
38+
}
39+
}
40+
}

‎src/constants/index.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,4 +351,8 @@ export const GIG_STATUS_TOOLTIP = {
351351
};
352352

353353
export const EMPTY_GIGS_TEXT =
354-
"Looks like you haven't applied to any gig opportunities yet.";
354+
"LOOKS LIKE YOU HAVEN'T APPLIED TO ANY GIG OPPORTUNITIES YET.";
355+
356+
export const CHECKING_GIG_TIMES = 3;
357+
358+
export const DELAY_CHECK_GIG_TIME = 2000;

‎src/containers/MyGigs/JobListing/JobCard/index.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,11 @@ const JobCard = ({ job }) => {
6565
job.paymentRangeTo &&
6666
job.currency && (
6767
<>
68-
{job.currency}{" "}
68+
{job.currency}
6969
{formatMoneyValue(job.paymentRangeFrom, "")}
7070
{" - "}
7171
{formatMoneyValue(job.paymentRangeTo, "")}
72+
{" (USD)"}
7273
{" / "}
7374
{job.paymentRangeRateType}
7475
</>

‎src/containers/MyGigs/index.jsx

Lines changed: 58 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import React, { useEffect, useRef, useState } from "react";
2+
import { useLocation } from "@reach/router";
23
import PT from "prop-types";
34
import { connect } from "react-redux";
45
import Modal from "../../components/Modal";
56
import Button from "../../components/Button";
7+
import Loading from "../../components/Loading";
8+
import Empty from "../../components/Empty";
69
import JobListing from "./JobListing";
710
import actions from "../../actions";
8-
import { EMPTY_GIGS_TEXT } from "../../constants";
11+
import * as utils from "../../utils";
912

1013
import UpdateGigProfile from "./modals/UpdateGigProfile";
1114
import UpdateSuccess from "./modals/UpdateSuccess";
@@ -23,16 +26,41 @@ const MyGigs = ({
2326
updateProfile,
2427
updateProfileSuccess,
2528
getAllCountries,
29+
checkingGigs,
30+
startCheckingGigs,
2631
}) => {
32+
const location = useLocation();
33+
const params = utils.url.parseUrlQuery(location.search);
2734
const propsRef = useRef();
28-
propsRef.current = { getMyGigs, getProfile, getAllCountries };
35+
propsRef.current = {
36+
getMyGigs,
37+
getProfile,
38+
getAllCountries,
39+
startCheckingGigs,
40+
params,
41+
};
2942

3043
useEffect(() => {
31-
propsRef.current.getMyGigs();
3244
propsRef.current.getProfile();
3345
propsRef.current.getAllCountries();
46+
if (propsRef.current.params.externalId) {
47+
propsRef.current.startCheckingGigs(propsRef.current.params.externalId);
48+
} else {
49+
propsRef.current.getMyGigs();
50+
}
3451
}, []);
3552

53+
const isInitialMount = useRef(true);
54+
useEffect(() => {
55+
if (isInitialMount.current) {
56+
isInitialMount.current = false;
57+
return;
58+
}
59+
if (!checkingGigs) {
60+
propsRef.current.getMyGigs();
61+
}
62+
}, [checkingGigs]);
63+
3664
const [openUpdateProfile, setOpenUpdateProfile] = useState(false);
3765
const [openUpdateSuccess, setOpenUpdateSuccess] = useState(false);
3866

@@ -49,28 +77,37 @@ const MyGigs = ({
4977
<div styleName="page">
5078
<h1 styleName="title">
5179
<span styleName="text">MY GIGS</span>
52-
<Button
53-
isPrimary
54-
size="lg"
55-
disabled={!(profile && profile.hasProfile)}
56-
onClick={() => {
57-
setOpenUpdateProfile(true);
58-
}}
59-
>
60-
UPDATE GIG WORK PROFILE
61-
</Button>
80+
<div styleName="operation">
81+
<Button
82+
isPrimary
83+
size="lg"
84+
disabled={!(profile && profile.hasProfile)}
85+
onClick={() => {
86+
setOpenUpdateProfile(true);
87+
}}
88+
>
89+
UPDATE GIG WORK PROFILE
90+
</Button>
91+
<Button
92+
size="lg"
93+
onClick={() => {
94+
window.location.href = `${process.env.URL.BASE}/gigs`;
95+
}}
96+
>
97+
VIEW GIGS
98+
</Button>
99+
</div>
62100
</h1>
63-
{myGigs && myGigs.length == 0 && (
64-
<h3 styleName="empty-label">{EMPTY_GIGS_TEXT}</h3>
65-
)}
66-
{myGigs && myGigs.length > 0 && (
101+
{!checkingGigs && myGigs && myGigs.length == 0 && <Empty />}
102+
{!checkingGigs && myGigs && myGigs.length > 0 && (
67103
<JobListing
68104
jobs={myGigs}
69105
loadMore={loadMore}
70106
total={total}
71107
numLoaded={numLoaded}
72108
/>
73109
)}
110+
{checkingGigs && <Loading />}
74111
</div>
75112
<Modal open={openUpdateProfile}>
76113
<UpdateGigProfile
@@ -106,9 +143,12 @@ MyGigs.propTypes = {
106143
updateProfile: PT.func,
107144
updateProfileSuccess: PT.bool,
108145
getAllCountries: PT.func,
146+
checkingGigs: PT.bool,
147+
startCheckingGigs: PT.func,
109148
};
110149

111150
const mapStateToProps = (state) => ({
151+
checkingGigs: state.myGigs.checkingGigs,
112152
myGigs: state.myGigs.myGigs,
113153
total: state.myGigs.total,
114154
numLoaded: state.myGigs.numLoaded,
@@ -122,6 +162,7 @@ const mapDispatchToProps = {
122162
getProfile: actions.myGigs.getProfile,
123163
updateProfile: actions.myGigs.updateProfile,
124164
getAllCountries: actions.lookup.getAllCountries,
165+
startCheckingGigs: actions.myGigs.startCheckingGigs,
125166
};
126167

127168
export default connect(mapStateToProps, mapDispatchToProps)(MyGigs);

‎src/containers/MyGigs/styles.scss

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,17 @@
1111
display: flex;
1212
align-items: center;
1313
margin-bottom: 22px;
14+
justify-content: space-between;
1415

1516
.text {
1617
align-self: flex-start;
1718
}
1819

19-
button {
20-
margin-left: auto;
20+
.operation {
21+
width: 375px;
22+
display: flex;
23+
justify-content: space-between;
24+
flex-direction: row-reverse;
2125
}
2226
}
2327

‎src/reducers/myGigs.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const defaultState = {
1515
updatingProfile: false,
1616
updatingProfileError: null,
1717
updatingProfileSucess: null,
18+
checkingGigs: false,
1819
};
1920

2021
function onGetMyGigsInit(state) {
@@ -111,6 +112,20 @@ function onUpdateProfileFailure(state, { payload }) {
111112
};
112113
}
113114

115+
function onCheckingGigsInit(state) {
116+
return {
117+
...state,
118+
checkingGigs: true,
119+
};
120+
}
121+
122+
function onCheckingGigsDone(state) {
123+
return {
124+
...state,
125+
checkingGigs: false,
126+
};
127+
}
128+
114129
export default handleActions(
115130
{
116131
GET_MY_GIGS_INIT: onGetMyGigsInit,
@@ -125,6 +140,8 @@ export default handleActions(
125140
UPDATE_PROFILE_INIT: onUpdateProfileInit,
126141
UPDATE_PROFILE_DONE: onUpdateProfileDone,
127142
UPDATE_PROFILE_FAILURE: onUpdateProfileFailure,
143+
START_CHECKING_GIGS_INIT: onCheckingGigsInit,
144+
START_CHECKING_GIGS_DONE: onCheckingGigsDone,
128145
},
129146
defaultState
130147
);

‎src/services/myGigs.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,17 @@ async function updateProfile(profile) {
157157
return response;
158158
}
159159

160+
async function startCheckingGigs(externalId) {
161+
const res = await api.get(
162+
`/earn-app/api/my-gigs/job?externalId=${externalId}`,
163+
process.env.URL.PLATFORM_WEBSITE_URL
164+
);
165+
return res;
166+
}
167+
160168
export default {
161169
getMyGigs,
162170
getProfile,
163171
updateProfile,
172+
startCheckingGigs,
164173
};

‎src/styles/animations.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@keyframes rotate {
2+
from { transform: rotate(0deg);}
3+
to { transform: rotate(360deg); }
4+
}

0 commit comments

Comments
 (0)
This repository has been archived.