-
Notifications
You must be signed in to change notification settings - Fork 212
PM-1188 copilot opportunities on challenge feed #7094
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
e5cf98f
9a8d287
f83c121
524a86c
f1e3d3e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ import { processSRM } from 'utils/tc'; | |
import { errors, services } from 'topcoder-react-lib'; | ||
import { BUCKETS } from 'utils/challenge-listing/buckets'; | ||
import SORT from 'utils/challenge-listing/sort'; | ||
import getCopilotOpportunities from '../../services/copilotOpportunities'; | ||
|
||
const { fireErrorMessage } = errors; | ||
const { getService } = services.challenge; | ||
|
@@ -25,6 +26,8 @@ const PAGE_SIZE = 10; | |
*/ | ||
const REVIEW_OPPORTUNITY_PAGE_SIZE = 1000; | ||
|
||
const COPILOT_OPPORTUNITY_PAGE_SIZE = 20; | ||
|
||
/** | ||
* Private. Loads from the backend all challenges matching some conditions. | ||
* @param {Function} getter Given params object of shape { limit, offset } | ||
|
@@ -496,6 +499,21 @@ function getReviewOpportunitiesDone(uuid, page, tokenV3) { | |
}); | ||
} | ||
|
||
/** | ||
* Action to get a list of currently open Copilot Opportunities using V5 API | ||
* @param {String} uuid Unique identifier for init/done instance from shortid module | ||
* @param {Number} page Page of copilot opportunities to fetch (1-based) | ||
* @return {Promise<{uuid: string, loaded: object}>} Action result | ||
*/ | ||
function getCopilotOpportunitiesDone(uuid, page) { | ||
return getCopilotOpportunities(page, COPILOT_OPPORTUNITY_PAGE_SIZE) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider handling the case where |
||
.then(loaded => ({ uuid, loaded })) | ||
.catch((error) => { | ||
fireErrorMessage('Error Getting Copilot Opportunities', error.content || error); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
return Promise.reject(error); | ||
}); | ||
} | ||
|
||
/** | ||
* Payload creator for the action that inits the loading of SRMs. | ||
* @param {String} uuid | ||
|
@@ -610,6 +628,9 @@ export default createActions({ | |
GET_REVIEW_OPPORTUNITIES_INIT: (uuid, page) => ({ uuid, page }), | ||
GET_REVIEW_OPPORTUNITIES_DONE: getReviewOpportunitiesDone, | ||
|
||
GET_COPILOT_OPPORTUNITIES_INIT: (uuid, page) => ({ uuid, page }), | ||
GET_COPILOT_OPPORTUNITIES_DONE: getCopilotOpportunitiesDone, | ||
|
||
GET_SRMS_INIT: getSrmsInit, | ||
GET_SRMS_DONE: getSrmsDone, | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
/** | ||
* Component for rendering a Copilot Opportunity and associated Challenge | ||
* information. Will be contained within a Bucket. | ||
*/ | ||
import _ from 'lodash'; | ||
import { config } from 'topcoder-react-utils'; | ||
import moment from 'moment'; | ||
import React, { useMemo } from 'react'; | ||
import PT from 'prop-types'; | ||
|
||
import Tags from '../Tags'; | ||
|
||
import './style.scss'; | ||
|
||
const PROJECT_TYPE_LABELS = { | ||
dev: 'Development', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider adding a default label for project types that are not listed in |
||
ai: 'AI (Artificial Intelligence)', | ||
design: 'Design', | ||
datascience: 'DataScience', | ||
qa: 'Quality Assurance', | ||
}; | ||
|
||
function CopilotOpportunityCard({ | ||
opportunity, | ||
}) { | ||
const skills = useMemo(() => _.uniq((opportunity.skills || []).map(skill => skill.name)), [ | ||
opportunity.skills, | ||
himaniraghav3 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
]); | ||
const start = moment(opportunity.startDate); | ||
|
||
return ( | ||
<div styleName="copilotOpportunityCard"> | ||
<div styleName="left-panel"> | ||
|
||
<div styleName="challenge-details"> | ||
<a | ||
href={`${config.URL.COPILOTS_URL}/opportunity/${opportunity.id}`} | ||
target="_blank" | ||
rel="noopener noreferrer" | ||
> | ||
{opportunity.project.name} | ||
</a> | ||
|
||
<div styleName="details-footer"> | ||
<span styleName="date"> | ||
Starts {start.format('MMM DD')} | ||
</span> | ||
{ skills.length > 0 | ||
&& ( | ||
<Tags | ||
skills={skills} | ||
/> | ||
) } | ||
</div> | ||
</div> | ||
</div> | ||
|
||
<div styleName="right-panel"> | ||
<div styleName="type"> | ||
<span>{PROJECT_TYPE_LABELS[opportunity.type]}</span> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider adding a fallback for |
||
</div> | ||
<div styleName={`status ${opportunity.status === 'completed' ? 'completed' : ''}`}> | ||
<span>{opportunity.status}</span> | ||
</div> | ||
<div styleName="numHours"> | ||
<span>{opportunity.numHoursPerWeek} hours/week</span> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
CopilotOpportunityCard.propTypes = { | ||
opportunity: PT.shape().isRequired, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
}; | ||
|
||
export default CopilotOpportunityCard; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,240 @@ | ||
@import '~styles/mixins'; | ||
|
||
$challenge-space-10: $base-unit * 2; | ||
$challenge-space-15: $base-unit * 3; | ||
$challenge-space-20: $base-unit * 4; | ||
$challenge-space-30: $base-unit * 6; | ||
$challenge-space-40: $base-unit * 8; | ||
$challenge-space-45: $base-unit * 9; | ||
$challenge-space-50: $base-unit * 10; | ||
$challenge-space-90: $base-unit * 18; | ||
$status-space-10: $base-unit * 2; | ||
$status-space-15: $base-unit * 3; | ||
$status-space-20: $base-unit * 4; | ||
$status-space-25: $base-unit * 5; | ||
$status-space-30: $base-unit * 6; | ||
$status-space-40: $base-unit * 8; | ||
$status-space-50: $base-unit * 10; | ||
$status-radius-1: $corner-radius / 2; | ||
$status-radius-4: $corner-radius * 2; | ||
|
||
.copilotOpportunityCard { | ||
@include roboto-medium; | ||
|
||
display: flex; | ||
justify-content: flex-start; | ||
position: relative; | ||
background: $tc-white; | ||
padding: $challenge-space-20 0; | ||
border-top: 1px solid $tc-gray-10; | ||
color: $tc-black; | ||
font-size: 15px; | ||
margin-left: 24px; | ||
|
||
&:last-child { | ||
border-bottom: 1px solid $tc-gray-10; | ||
} | ||
|
||
@include xs-to-md { | ||
flex-wrap: wrap; | ||
padding: $base-unit * 3 0; | ||
margin-left: 0; | ||
flex-direction: column; | ||
} | ||
|
||
@include xs-to-sm { | ||
position: relative; | ||
} | ||
|
||
a, | ||
a:visited { | ||
color: $tc-black; | ||
} | ||
|
||
a:hover { | ||
color: $tc-dark-blue-110; | ||
} | ||
|
||
.left-panel { | ||
display: flex; | ||
justify-content: flex-start; | ||
width: 45.5%; | ||
|
||
@include xs-to-md { | ||
width: 100%; | ||
padding: 0 16px; | ||
} | ||
} | ||
|
||
.right-panel { | ||
display: flex; | ||
justify-content: space-between; | ||
width: 50%; | ||
|
||
@include xs-to-md { | ||
width: 100%; | ||
display: flex; | ||
} | ||
|
||
@include xs-to-sm { | ||
display: flex; | ||
} | ||
} | ||
|
||
// Challenge title, end date & technologies | ||
.challenge-details { | ||
display: inline-block; | ||
vertical-align: baseline; | ||
width: 82%; | ||
margin-right: $challenge-space-30; | ||
|
||
@include md { | ||
margin-right: 180px; | ||
} | ||
|
||
@include xs-to-sm { | ||
margin-right: 0; | ||
} | ||
|
||
a { | ||
line-height: $challenge-space-20; | ||
|
||
@include xs-to-sm { | ||
display: inline-block; | ||
} | ||
} | ||
} | ||
|
||
.details-footer { | ||
width: 100%; | ||
margin-top: 16px; | ||
display: flex; | ||
|
||
.date { | ||
font-size: 12px; | ||
color: $tc-gray-60; | ||
margin-right: $challenge-space-10; | ||
line-height: ($challenge-space-10) + 2; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The line-height calculation |
||
font-weight: normal; | ||
margin-top: 2px; | ||
} | ||
} | ||
|
||
// Review payment | ||
.status { | ||
@include roboto-medium; | ||
|
||
display: inline-block; | ||
font-size: 13px; | ||
font-weight: 500; | ||
color: green; | ||
line-height: $challenge-space-20; | ||
margin-right: $challenge-space-20; | ||
min-width: $challenge-space-50 + 2; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
width: 30%; | ||
|
||
&.completed { | ||
color: $tc-orange; | ||
} | ||
|
||
@include xs-to-md { | ||
position: absolute; | ||
right: 0; | ||
top: 20px; | ||
margin-right: $challenge-space-20; | ||
margin-bottom: $challenge-space-20; | ||
} | ||
|
||
@include xs-to-sm { | ||
position: relative; | ||
display: block; | ||
margin-top: $challenge-space-30; | ||
margin-bottom: $challenge-space-45; | ||
margin-left: $challenge-space-15; | ||
top: 0; | ||
} | ||
|
||
@include md { | ||
right: 108px; | ||
} | ||
|
||
// $ Symbol | ||
span { | ||
font-weight: 500; | ||
font-size: 14px; | ||
line-height: 16px; | ||
text-transform: capitalize; | ||
} | ||
} | ||
|
||
.type { | ||
@include roboto-medium; | ||
|
||
width: 40%; | ||
|
||
@include xs-to-md { | ||
position: absolute; | ||
right: 0; | ||
top: 20px; | ||
margin-right: $challenge-space-20; | ||
margin-bottom: $challenge-space-20; | ||
} | ||
|
||
@include xs-to-sm { | ||
position: relative; | ||
display: block; | ||
margin-top: $challenge-space-30; | ||
margin-bottom: $challenge-space-45; | ||
margin-left: $challenge-space-15; | ||
top: 0; | ||
} | ||
|
||
@include md { | ||
right: 108px; | ||
} | ||
|
||
span { | ||
color: $tc-black; | ||
font-weight: 500; | ||
font-size: 14px; | ||
line-height: 16px; | ||
text-transform: capitalize; | ||
} | ||
} | ||
|
||
.numHours { | ||
|
||
width: 30%; | ||
|
||
@include xs-to-md { | ||
position: absolute; | ||
right: 0; | ||
top: 20px; | ||
margin-right: $challenge-space-20; | ||
margin-bottom: $challenge-space-20; | ||
} | ||
|
||
@include xs-to-sm { | ||
position: relative; | ||
display: block; | ||
margin-top: $challenge-space-30; | ||
margin-bottom: $challenge-space-45; | ||
margin-left: $challenge-space-15; | ||
top: 0; | ||
} | ||
|
||
@include md { | ||
right: 108px; | ||
} | ||
|
||
span { | ||
color: $tc-black; | ||
font-weight: 500; | ||
font-size: 14px; | ||
line-height: 16px; | ||
} | ||
} | ||
|
||
|
||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As this is hard-coded value we need to add it also in https://github.com/topcoder-platform/community-app/blob/develop/config/production.js