Skip to content

Commit 739abb5

Browse files
committed
PROD-2487 display TopcoderAcademy certificates on user profile
1 parent ee1f747 commit 739abb5

File tree

19 files changed

+464
-80
lines changed

19 files changed

+464
-80
lines changed

config/default.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,7 @@ module.exports = {
444444
GIGS_PAGES_PATH: '/gigs',
445445
GIGS_LISTING_CACHE_TIME: 300, // in seconds
446446
START_PAGE_PATH: '/start',
447+
TC_ACADEMY_BASE_PATH: '/learn',
447448
GUIKIT: {
448449
DEBOUNCE_ON_CHANGE_TIME: 150,
449450
},
Loading
Loading
Loading
Loading
Lines changed: 10 additions & 0 deletions
Loading
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import React from 'react';
2+
import PT from 'prop-types';
3+
import { noop } from 'lodash/noop';
4+
import { Modal } from 'topcoder-react-ui-kit';
5+
import cn from 'classnames';
6+
7+
import IconClose from 'assets/images/icon-close-green.svg';
8+
import styles from './styles.scss';
9+
10+
const ProfileModal = ({
11+
children,
12+
title,
13+
onCancel,
14+
containerClassName,
15+
}) => (
16+
<Modal
17+
theme={{
18+
container: cn(styles['modal-container'], containerClassName),
19+
overlay: styles['modal-overlay'],
20+
}}
21+
onCancel={onCancel}
22+
>
23+
<React.Fragment>
24+
<div styleName="header">
25+
<h2 styleName="title">
26+
{title}
27+
</h2>
28+
<div styleName="icon" role="presentation" onClick={onCancel}>
29+
<IconClose />
30+
</div>
31+
</div>
32+
{children}
33+
</React.Fragment>
34+
</Modal>
35+
);
36+
37+
ProfileModal.defaultProps = {
38+
title: null,
39+
onCancel: noop,
40+
containerClassName: '',
41+
};
42+
43+
ProfileModal.propTypes = {
44+
children: PT.node.isRequired,
45+
title: PT.node,
46+
onCancel: PT.func,
47+
containerClassName: PT.string,
48+
};
49+
50+
export default ProfileModal;
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
@import "~styles/mixins";
2+
3+
.modal-overlay {
4+
background: #000;
5+
}
6+
7+
.modal-container-copilot,
8+
.modal-container {
9+
width: 1232px;
10+
min-height: 700px;
11+
max-width: 1232px;
12+
border-radius: 8px;
13+
padding: 32px 32px 0 32px;
14+
gap: 24px;
15+
16+
&.modal-container-copilot {
17+
height: auto;
18+
}
19+
20+
@include xs-to-sm {
21+
width: 100%;
22+
max-width: 100%;
23+
height: 100% !important;
24+
max-height: 100% !important;
25+
padding: 24px 16px 48px 16px;
26+
}
27+
28+
.header {
29+
display: flex;
30+
justify-content: space-between;
31+
align-items: center;
32+
padding-bottom: 24px;
33+
34+
.title {
35+
@include barlow-medium;
36+
37+
font-weight: 600;
38+
font-size: 22px;
39+
line-height: 26px;
40+
text-transform: uppercase;
41+
}
42+
43+
.icon {
44+
cursor: pointer;
45+
}
46+
}
47+
}
48+
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import React from 'react';
2+
import PT from 'prop-types';
3+
4+
import DataScienceBadgeImg from 'assets/images/profile/tca-certificates/datascience-badge.png';
5+
import DesignBadgeImg from 'assets/images/profile/tca-certificates/design-badge.png';
6+
import DevelopBadgeImg from 'assets/images/profile/tca-certificates/develop-badge.png';
7+
import QaBadgeImg from 'assets/images/profile/tca-certificates/qa-badge.png';
8+
9+
import './styles.scss';
10+
11+
const badgesMap = {
12+
DATASCIENCE: DataScienceBadgeImg,
13+
DESIGN: DesignBadgeImg,
14+
DEV: DevelopBadgeImg,
15+
QA: QaBadgeImg,
16+
};
17+
18+
const CourseBadge = ({ type: badgeType, size }) => {
19+
const badgeImg = badgesMap[badgeType];
20+
21+
return (
22+
<div styleName={`tca-badge-wrap size-${size}`}>
23+
<img src={badgeImg} alt={badgeType} />
24+
</div>
25+
);
26+
};
27+
28+
CourseBadge.defaultProps = {
29+
size: 'md',
30+
};
31+
32+
CourseBadge.propTypes = {
33+
size: PT.oneOf(['md']),
34+
type: PT.oneOf(['DATASCIENCE', 'DESIGN', 'DEV', 'QA']).isRequired,
35+
};
36+
37+
38+
export default CourseBadge;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.tca-badge-wrap {
2+
&.size-md {
3+
width: 48px;
4+
height: 48px;
5+
}
6+
7+
img {
8+
display: block;
9+
width: 100%;
10+
height: 100%;
11+
object-fit: contain;
12+
}
13+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import React from 'react';
2+
import PT from 'prop-types';
3+
4+
import './styles.scss';
5+
import CourseBadge from '../CourseBadge';
6+
7+
const List = ({
8+
certificates,
9+
onClick,
10+
}) => (
11+
<div styleName="list">
12+
{certificates.map(certificate => (
13+
<div
14+
styleName="list-item"
15+
key={certificate.id}
16+
onClick={() => onClick(certificate)}
17+
onKeyPress={() => onClick(certificate)}
18+
role="button"
19+
tabIndex={-1}
20+
>
21+
<div styleName="list-item_badge">
22+
<CourseBadge type={certificate.certificationTrackType || 'DEV'} />
23+
</div>
24+
<div styleName="list-item_description">
25+
<div styleName="list-item_title">
26+
{certificate.certificationTitle}
27+
</div>
28+
<div styleName="list-item_sub">
29+
<a href={`//${certificate.providerUrl}`} target="blank" rel="noopener">
30+
by {certificate.provider}
31+
</a>
32+
</div>
33+
</div>
34+
</div>
35+
))}
36+
</div>
37+
);
38+
39+
List.propTypes = {
40+
certificates: PT.arrayOf(PT.shape()).isRequired,
41+
onClick: PT.func.isRequired,
42+
};
43+
44+
export default List;
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
@import "~styles/mixins";
2+
3+
.list {
4+
display: flex;
5+
gap: 16px;
6+
margin-top: 24px;
7+
flex-wrap: wrap;
8+
@include xs-to-sm {
9+
flex-direction: column;
10+
flex-wrap: nowrap;
11+
margin-top: 16px;
12+
}
13+
}
14+
15+
.list-item {
16+
background: $listing-white;
17+
border-radius: 8px;
18+
padding: 16px;
19+
20+
display: flex;
21+
align-items: center;
22+
gap: 16px;
23+
24+
width: 316px;
25+
transition: 0.25s ease-in-out;
26+
box-shadow: 0px 0px 0 rgba(0, 0, 0, 0);
27+
cursor: pointer;
28+
29+
&:hover {
30+
box-shadow: 0px 0px 16px rgba(22, 103, 154, 0.5);
31+
}
32+
@include xs-to-sm {
33+
width: 100%;
34+
}
35+
}
36+
37+
.list-item_badge {
38+
width: 48px;
39+
height: 48px;
40+
svg {
41+
display: block;
42+
width: 48px;
43+
height: 48px;
44+
}
45+
}
46+
47+
.list-item_description {
48+
49+
}
50+
51+
.list-item_title {
52+
@include roboto-sans-regular;
53+
font-size: 16px;
54+
line-height: 24px;
55+
font-weight: bold;
56+
}
57+
58+
.list-item_sub {
59+
@include roboto-sans-regular;
60+
font-style: italic;
61+
font-size: 14px;
62+
line-height: 22px;
63+
color: $listing-placeholder-gray;
64+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import React from 'react';
2+
import PT from 'prop-types';
3+
import { noop } from 'lodash/noop';
4+
import { config } from 'topcoder-react-utils';
5+
6+
import ProfileModal from '../../ProfileModal';
7+
import styles from './styles.scss';
8+
9+
const tcAcademyPath = `${config.PLATFORM_SITE_URL}${config.TC_ACADEMY_BASE_PATH}`;
10+
11+
const TcaCertificateModal = ({
12+
certificate,
13+
onCancel,
14+
memberHandle,
15+
}) => (
16+
<ProfileModal
17+
title="Topcoder Academy"
18+
onCancel={onCancel}
19+
containerClassName={styles['tca-certificate-modal']}
20+
>
21+
<iframe
22+
styleName="iframe"
23+
src={[
24+
tcAcademyPath,
25+
certificate.provider,
26+
certificate.certification,
27+
memberHandle,
28+
'certificate',
29+
].join('/')}
30+
title={certificate.certificationTitle}
31+
/>
32+
</ProfileModal>
33+
);
34+
35+
TcaCertificateModal.defaultProps = {
36+
onCancel: noop,
37+
};
38+
39+
TcaCertificateModal.propTypes = {
40+
certificate: PT.shape().isRequired,
41+
onCancel: PT.func,
42+
memberHandle: PT.string.isRequired,
43+
};
44+
45+
export default TcaCertificateModal;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
@import "~styles/mixins";
2+
3+
.tca-certificate-modal {
4+
display: flex;
5+
flex-direction: column;
6+
min-height: auto;
7+
padding: 32px;
8+
width: 980px;
9+
max-width: 96vw;
10+
@include xs-to-sm {
11+
display: flex;
12+
flex-direction: column;
13+
max-width: none;
14+
width: 100%;
15+
}
16+
}
17+
18+
.iframe {
19+
display: block;
20+
height: 600px;
21+
width: 100%;
22+
@include xs-to-sm {
23+
height: auto;
24+
flex: 1;
25+
}
26+
}

0 commit comments

Comments
 (0)