Skip to content

Commit e5cf98f

Browse files
author
himaniraghav3
committed
feat(challenge-feed): pm-1188 copilot opportunities on challenge feed
add support for fetching and displaying copilot opportunities within the challenge feed, including dynamic sorting and pagination handling. refs pm-1188
1 parent 9f432e5 commit e5cf98f

File tree

19 files changed

+899
-68
lines changed

19 files changed

+899
-68
lines changed

config/backup-default.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ module.exports = {
102102
/* This is the same value as above, but it is used by topcoder-react-lib,
103103
* as a more verbose name for the param. */
104104
COMMUNITY_APP: 'https://community-app.topcoder-dev.com',
105+
COPILOTS_URL: 'https://copilots.topcoder-dev.com',
105106
CHALLENGES_URL: 'https://www.topcoder-dev.com/challenges',
106107
TCO_OPEN_URL: 'https://www.topcoder-dev.com/community/member-programs/topcoder-open',
107108
ARENA: 'https://arena.topcoder-dev.com',

config/default.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ module.exports = {
104104
* as a more verbose name for the param. */
105105
COMMUNITY_APP: 'https://community-app.topcoder-dev.com',
106106
CHALLENGES_URL: 'https://www.topcoder-dev.com/challenges',
107+
COPILOTS_URL: 'https://copilots.topcoder-dev.com',
107108
TCO_OPEN_URL: 'https://www.topcoder-dev.com/community/member-programs/topcoder-open',
108109
ARENA: 'https://arena.topcoder-dev.com',
109110
AUTH: 'https://accounts-auth0.topcoder-dev.com',

src/shared/actions/challenge-listing/index.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { processSRM } from 'utils/tc';
1010
import { errors, services } from 'topcoder-react-lib';
1111
import { BUCKETS } from 'utils/challenge-listing/buckets';
1212
import SORT from 'utils/challenge-listing/sort';
13+
import getCopilotOpportunities from '../../services/copilotOpportunities';
1314

1415
const { fireErrorMessage } = errors;
1516
const { getService } = services.challenge;
@@ -25,6 +26,8 @@ const PAGE_SIZE = 10;
2526
*/
2627
const REVIEW_OPPORTUNITY_PAGE_SIZE = 1000;
2728

29+
const COPILOT_OPPORTUNITY_PAGE_SIZE = 20;
30+
2831
/**
2932
* Private. Loads from the backend all challenges matching some conditions.
3033
* @param {Function} getter Given params object of shape { limit, offset }
@@ -496,6 +499,21 @@ function getReviewOpportunitiesDone(uuid, page, tokenV3) {
496499
});
497500
}
498501

502+
/**
503+
* Action to get a list of currently open Copilot Opportunities using V5 API
504+
* @param {String} uuid Unique identifier for init/done instance from shortid module
505+
* @param {Number} page Page of copilot opportunities to fetch (1-based)
506+
* @return {Promise<{uuid: string, loaded: object}>} Action result
507+
*/
508+
function getCopilotOpportunitiesDone(uuid, page) {
509+
return getCopilotOpportunities(page, COPILOT_OPPORTUNITY_PAGE_SIZE)
510+
.then(loaded => ({ uuid, loaded }))
511+
.catch((error) => {
512+
fireErrorMessage('Error Getting Copilot Opportunities', error.content || error);
513+
return Promise.reject(error);
514+
});
515+
}
516+
499517
/**
500518
* Payload creator for the action that inits the loading of SRMs.
501519
* @param {String} uuid
@@ -610,6 +628,9 @@ export default createActions({
610628
GET_REVIEW_OPPORTUNITIES_INIT: (uuid, page) => ({ uuid, page }),
611629
GET_REVIEW_OPPORTUNITIES_DONE: getReviewOpportunitiesDone,
612630

631+
GET_COPILOT_OPPORTUNITIES_INIT: (uuid, page) => ({ uuid, page }),
632+
GET_COPILOT_OPPORTUNITIES_DONE: getCopilotOpportunitiesDone,
633+
613634
GET_SRMS_INIT: getSrmsInit,
614635
GET_SRMS_DONE: getSrmsDone,
615636

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/**
2+
* Component for rendering a Copilot Opportunity and associated Challenge
3+
* information. Will be contained within a Bucket.
4+
*/
5+
import _ from 'lodash';
6+
import { config } from 'topcoder-react-utils';
7+
import moment from 'moment';
8+
import React, { useMemo } from 'react';
9+
import PT from 'prop-types';
10+
11+
import Tags from '../Tags';
12+
13+
import './style.scss';
14+
15+
const PROJECT_TYPE_LABELS = {
16+
dev: 'Development',
17+
ai: 'AI (Artificial Intelligence)',
18+
design: 'Design',
19+
datascience: 'DataScience',
20+
qa: 'Quality Assurance',
21+
};
22+
23+
function CopilotOpportunityCard({
24+
opportunity,
25+
}) {
26+
const skills = useMemo(() => _.uniq((opportunity.skills || []).map(skill => skill.name)), [
27+
opportunity.skills,
28+
]);
29+
const start = moment(opportunity.startDate);
30+
31+
return (
32+
<div styleName="copilotOpportunityCard">
33+
<div styleName="left-panel">
34+
35+
<div styleName="challenge-details">
36+
<a
37+
href={`${config.URL.COPILOTS_URL}/opportunity/${opportunity.id}`}
38+
target="_blank"
39+
rel="noopener noreferrer"
40+
>
41+
{opportunity.project.name}
42+
</a>
43+
44+
<div styleName="details-footer">
45+
<span styleName="date">
46+
Starts {start.format('MMM DD')}
47+
</span>
48+
{ skills.length > 0
49+
&& (
50+
<Tags
51+
skills={skills}
52+
/>
53+
) }
54+
</div>
55+
</div>
56+
</div>
57+
58+
<div styleName="right-panel">
59+
<div styleName="type">
60+
<span>{PROJECT_TYPE_LABELS[opportunity.type]}</span>
61+
</div>
62+
<div styleName={`status ${opportunity.status === 'completed' ? 'completed' : ''}`}>
63+
<span>{opportunity.status}</span>
64+
</div>
65+
<div styleName="numHours">
66+
<span>{opportunity.numHoursPerWeek} hours/week</span>
67+
</div>
68+
</div>
69+
</div>
70+
);
71+
}
72+
73+
CopilotOpportunityCard.propTypes = {
74+
opportunity: PT.shape().isRequired,
75+
};
76+
77+
export default CopilotOpportunityCard;
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
@import '~styles/mixins';
2+
3+
$challenge-space-10: $base-unit * 2;
4+
$challenge-space-15: $base-unit * 3;
5+
$challenge-space-20: $base-unit * 4;
6+
$challenge-space-30: $base-unit * 6;
7+
$challenge-space-40: $base-unit * 8;
8+
$challenge-space-45: $base-unit * 9;
9+
$challenge-space-50: $base-unit * 10;
10+
$challenge-space-90: $base-unit * 18;
11+
$status-space-10: $base-unit * 2;
12+
$status-space-15: $base-unit * 3;
13+
$status-space-20: $base-unit * 4;
14+
$status-space-25: $base-unit * 5;
15+
$status-space-30: $base-unit * 6;
16+
$status-space-40: $base-unit * 8;
17+
$status-space-50: $base-unit * 10;
18+
$status-radius-1: $corner-radius / 2;
19+
$status-radius-4: $corner-radius * 2;
20+
21+
.copilotOpportunityCard {
22+
@include roboto-medium;
23+
24+
display: flex;
25+
justify-content: flex-start;
26+
position: relative;
27+
background: $tc-white;
28+
padding: $challenge-space-20 0;
29+
border-top: 1px solid $tc-gray-10;
30+
color: $tc-black;
31+
font-size: 15px;
32+
margin-left: 24px;
33+
34+
&:last-child {
35+
border-bottom: 1px solid $tc-gray-10;
36+
}
37+
38+
@include xs-to-md {
39+
flex-wrap: wrap;
40+
padding: $base-unit * 3 0;
41+
margin-left: 0;
42+
flex-direction: column;
43+
}
44+
45+
@include xs-to-sm {
46+
position: relative;
47+
}
48+
49+
a,
50+
a:visited {
51+
color: $tc-black;
52+
}
53+
54+
a:hover {
55+
color: $tc-dark-blue-110;
56+
}
57+
58+
.left-panel {
59+
display: flex;
60+
justify-content: flex-start;
61+
width: 45.5%;
62+
63+
@include xs-to-md {
64+
width: 100%;
65+
padding: 0 16px;
66+
}
67+
}
68+
69+
.right-panel {
70+
display: flex;
71+
justify-content: space-between;
72+
width: 50%;
73+
74+
@include xs-to-md {
75+
width: 100%;
76+
display: flex;
77+
}
78+
79+
@include xs-to-sm {
80+
display: flex;
81+
}
82+
}
83+
84+
// Challenge title, end date & technologies
85+
.challenge-details {
86+
display: inline-block;
87+
vertical-align: baseline;
88+
width: 82%;
89+
margin-right: $challenge-space-30;
90+
91+
@include md {
92+
margin-right: 180px;
93+
}
94+
95+
@include xs-to-sm {
96+
margin-right: 0;
97+
}
98+
99+
a {
100+
line-height: $challenge-space-20;
101+
102+
@include xs-to-sm {
103+
display: inline-block;
104+
}
105+
}
106+
}
107+
108+
.details-footer {
109+
width: 100%;
110+
margin-top: 16px;
111+
display: flex;
112+
113+
.date {
114+
font-size: 12px;
115+
color: $tc-gray-60;
116+
margin-right: $challenge-space-10;
117+
line-height: ($challenge-space-10) + 2;
118+
font-weight: normal;
119+
margin-top: 2px;
120+
}
121+
}
122+
123+
// Review payment
124+
.status {
125+
@include roboto-medium;
126+
127+
display: inline-block;
128+
font-size: 13px;
129+
font-weight: 500;
130+
color: green;
131+
line-height: $challenge-space-20;
132+
margin-right: $challenge-space-20;
133+
min-width: $challenge-space-50 + 2;
134+
width: 30%;
135+
136+
&.completed {
137+
color: $tc-orange;
138+
}
139+
140+
@include xs-to-md {
141+
position: absolute;
142+
right: 0;
143+
top: 20px;
144+
margin-right: $challenge-space-20;
145+
margin-bottom: $challenge-space-20;
146+
}
147+
148+
@include xs-to-sm {
149+
position: relative;
150+
display: block;
151+
margin-top: $challenge-space-30;
152+
margin-bottom: $challenge-space-45;
153+
margin-left: $challenge-space-15;
154+
top: 0;
155+
}
156+
157+
@include md {
158+
right: 108px;
159+
}
160+
161+
// $ Symbol
162+
span {
163+
font-weight: 500;
164+
font-size: 14px;
165+
line-height: 16px;
166+
text-transform: capitalize;
167+
}
168+
}
169+
170+
.type {
171+
@include roboto-medium;
172+
173+
width: 40%;
174+
175+
@include xs-to-md {
176+
position: absolute;
177+
right: 0;
178+
top: 20px;
179+
margin-right: $challenge-space-20;
180+
margin-bottom: $challenge-space-20;
181+
}
182+
183+
@include xs-to-sm {
184+
position: relative;
185+
display: block;
186+
margin-top: $challenge-space-30;
187+
margin-bottom: $challenge-space-45;
188+
margin-left: $challenge-space-15;
189+
top: 0;
190+
}
191+
192+
@include md {
193+
right: 108px;
194+
}
195+
196+
span {
197+
color: $tc-black;
198+
font-weight: 500;
199+
font-size: 14px;
200+
line-height: 16px;
201+
text-transform: capitalize;
202+
}
203+
}
204+
205+
.numHours {
206+
207+
width: 30%;
208+
209+
@include xs-to-md {
210+
position: absolute;
211+
right: 0;
212+
top: 20px;
213+
margin-right: $challenge-space-20;
214+
margin-bottom: $challenge-space-20;
215+
}
216+
217+
@include xs-to-sm {
218+
position: relative;
219+
display: block;
220+
margin-top: $challenge-space-30;
221+
margin-bottom: $challenge-space-45;
222+
margin-left: $challenge-space-15;
223+
top: 0;
224+
}
225+
226+
@include md {
227+
right: 108px;
228+
}
229+
230+
span {
231+
color: $tc-black;
232+
font-weight: 500;
233+
font-size: 14px;
234+
line-height: 16px;
235+
}
236+
}
237+
238+
239+
}
240+

0 commit comments

Comments
 (0)