Skip to content

Commit 37e8a94

Browse files
Merge pull request #5743 from topcoder-platform/develop
Release v1.13.5
2 parents fd596b3 + 6e38292 commit 37e8a94

File tree

9 files changed

+240
-16
lines changed

9 files changed

+240
-16
lines changed

.circleci/config.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@ workflows:
343343
branches:
344344
only:
345345
- develop
346+
- fix/issue-5739
346347
# This is alternate dev env for parallel testing
347348
- "build-test":
348349
context : org-global
@@ -363,7 +364,7 @@ workflows:
363364
filters:
364365
branches:
365366
only:
366-
- free
367+
- thrive-vulnerability-1
367368
# This is stage env for production QA releases
368369
- "build-prod-staging":
369370
context : org-global

src/server/routes/contentful.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
*/
44

55
import express from 'express';
6-
6+
import { middleware } from 'tc-core-library-js';
7+
import config from 'config';
8+
import _ from 'lodash';
79
import {
810
ASSETS_DOMAIN,
911
IMAGES_DOMAIN,
@@ -14,6 +16,8 @@ import {
1416

1517
const cors = require('cors');
1618

19+
const authenticator = middleware.jwtAuthenticator;
20+
const authenticatorOptions = _.pick(config.SECRET.JWT_AUTH, ['AUTH_SECRET', 'VALID_ISSUERS']);
1721
const routes = express.Router();
1822

1923
// Enables CORS on those routes according config above
@@ -124,7 +128,7 @@ routes.use('/:spaceName/:environment/published/entries', (req, res, next) => {
124128
});
125129

126130
/* Update votes on article. */
127-
routes.use('/:spaceName/:environment/votes', (req, res, next) => {
131+
routes.use('/:spaceName/:environment/votes', (req, res, next) => authenticator(authenticatorOptions)(req, res, next), (req, res, next) => {
128132
articleVote(req.body)
129133
.then(res.send.bind(res), next);
130134
});

src/shared/components/Contentful/Article/Article.jsx

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66
import _ from 'lodash';
77
import React from 'react';
8+
import { connect } from 'react-redux';
89
import PT from 'prop-types';
910
import { fixStyle } from 'utils/contentful';
1011
import { getService } from 'services/contentful';
@@ -22,6 +23,8 @@ import {
2223
config, Link, isomorphy,
2324
} from 'topcoder-react-utils';
2425
import qs from 'qs';
26+
import LoginModal from 'components/LoginModal';
27+
import modalStyle from 'components/LoginModal/modal.scss';
2528
// SVGs and assets
2629
import GestureIcon from 'assets/images/icon-gesture.svg';
2730
import ReadMoreArrow from 'assets/images/read-more-arrow.svg';
@@ -41,21 +44,30 @@ const DEFAULT_BANNER_IMAGE = 'https://images.ctfassets.net/piwi0eufbb2g/7v2hlDsV
4144
const RANDOM_BANNERS = ['6G8mjiTC1mzeSQ2YoUG1gB', '1DnDD02xX1liHfSTf5Vsn8', 'HQZ3mN0rR92CbNTkKTHJ5', '1OLoX8ZsvjAnn4TdGbZESD', '77jn01UGoQe2gqA7x0coQD'];
4245
const RANDOM_BANNER = RANDOM_BANNERS[_.random(0, 4)];
4346

44-
export default class Article extends React.Component {
47+
class Article extends React.Component {
4548
componentDidMount() {
4649
const { fields } = this.props;
4750
this.setState({
4851
upvotes: fields.upvotes || 0,
4952
downvotes: fields.downvotes || 0,
53+
showLogin: false,
54+
voting: false,
5055
});
5156
}
5257

58+
// eslint-disable-next-line consistent-return
5359
updateVote(type) {
54-
let userVotes = localStorage.getItem(LOCAL_STORAGE_KEY);
55-
userVotes = userVotes ? JSON.parse(userVotes) : {};
5660
const {
57-
id, spaceName, environment, preview,
61+
id, spaceName, environment, preview, auth,
5862
} = this.props;
63+
// check for auth?
64+
if (!auth) {
65+
return this.setState({
66+
showLogin: true,
67+
});
68+
}
69+
let userVotes = localStorage.getItem(LOCAL_STORAGE_KEY);
70+
userVotes = userVotes ? JSON.parse(userVotes) : {};
5971
const articleVote = userVotes[id];
6072
let { upvotes, downvotes } = this.state;
6173
// Check if user alredy voted on this article?
@@ -93,17 +105,21 @@ export default class Article extends React.Component {
93105
}
94106
}
95107
// Store user action
108+
this.setState({
109+
voting: true,
110+
});
96111
getService({ spaceName, environment, preview }).articleVote(id, {
97112
upvotes,
98113
downvotes,
99-
})
114+
}, auth.tokenV3)
100115
.then(() => {
101116
// Only when Contentful enntry was succesfully updated
102117
// then we update the local store and the state
103118
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(userVotes));
104119
this.setState({
105120
upvotes,
106121
downvotes,
122+
voting: false,
107123
});
108124
});
109125
}
@@ -115,7 +131,9 @@ export default class Article extends React.Component {
115131
const contentfulConfig = {
116132
spaceName, environment, preview,
117133
};
118-
const { upvotes, downvotes } = this.state || {};
134+
const {
135+
upvotes, downvotes, showLogin, voting,
136+
} = this.state || {};
119137
let shareUrl;
120138
if (isomorphy.isClientSide()) {
121139
shareUrl = encodeURIComponent(window.location.href);
@@ -283,7 +301,7 @@ export default class Article extends React.Component {
283301
{/* Voting */}
284302
<div className={theme.actionContainer}>
285303
<div className={theme.action}>
286-
<div tabIndex={0} role="button" className={theme.circleGreenIcon} onClick={() => this.updateVote('up')} onKeyPress={() => this.updateVote('up')}>
304+
<div tabIndex={0} role="button" className={voting ? theme.circleGreenIconDisabled : theme.circleGreenIcon} onClick={() => this.updateVote('up')} onKeyPress={() => this.updateVote('up')}>
287305
<GestureIcon />
288306
</div>
289307
<span>
@@ -293,7 +311,7 @@ export default class Article extends React.Component {
293311
</span>
294312
</div>
295313
<div className={theme.action}>
296-
<div tabIndex={0} role="button" className={theme.circleRedIcon} onClick={() => this.updateVote('down')} onKeyPress={() => this.updateVote('down')}>
314+
<div tabIndex={0} role="button" className={voting ? theme.circleRedIconDisabled : theme.circleRedIcon} onClick={() => this.updateVote('down')} onKeyPress={() => this.updateVote('down')}>
297315
<GestureIcon />
298316
</div>
299317
<span>{downvotes}</span>
@@ -380,6 +398,19 @@ export default class Article extends React.Component {
380398
) : null
381399
}
382400
</div>
401+
{
402+
showLogin && (
403+
<LoginModal
404+
// eslint-disable-next-line no-restricted-globals
405+
retUrl={isomorphy.isClientSide() ? location.href : null}
406+
onCancel={() => this.setState({ showLogin: false })}
407+
modalTitle="Want to vote?"
408+
modalText="You must be a Topcoder member to do that."
409+
utmSource="thrive_article"
410+
infoNode={<p className={modalStyle.regTxt}>Discover <a href="/community/learn" target="_blank" rel="noreferrer">other features</a> you can access by becoming a member.</p>}
411+
/>
412+
)
413+
}
383414
</React.Fragment>
384415
);
385416
}
@@ -388,6 +419,7 @@ export default class Article extends React.Component {
388419
Article.defaultProps = {
389420
spaceName: null,
390421
environment: null,
422+
auth: null,
391423
};
392424

393425
Article.propTypes = {
@@ -398,4 +430,16 @@ Article.propTypes = {
398430
preview: PT.bool.isRequired,
399431
spaceName: PT.string,
400432
environment: PT.string,
433+
auth: PT.shape(),
401434
};
435+
436+
function mapStateToProps(state) {
437+
const auth = state.auth && state.auth.profile ? { ...state.auth } : null;
438+
return {
439+
auth,
440+
};
441+
}
442+
443+
export default connect(
444+
mapStateToProps,
445+
)(Article);

src/shared/components/Contentful/Article/themes/default.scss

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,8 @@
419419
padding: 8px 9px;
420420
margin-right: 10px;
421421

422-
.circleGreenIcon {
422+
.circleGreenIcon,
423+
.circleGreenIconDisabled {
423424
border-radius: 100%;
424425
width: 42px;
425426
height: 42px;
@@ -431,7 +432,8 @@
431432
background-color: #12c188;
432433
}
433434

434-
.circleRedIcon {
435+
.circleRedIcon,
436+
.circleRedIconDisabled {
435437
border-radius: 100%;
436438
width: 42px;
437439
height: 42px;
@@ -444,6 +446,12 @@
444446
transform: rotateX(-180deg);
445447
}
446448

449+
.circleGreenIconDisabled,
450+
.circleRedIconDisabled {
451+
pointer-events: none;
452+
opacity: 0.5;
453+
}
454+
447455
span {
448456
@include barlow-bold;
449457

src/shared/components/Dashboard/Challenges/index.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export default function ChallengesFeed({
3838
<div styleName="prize">
3939
<span styleName="amount">
4040
{`$${_.sum(challenge.prizeSets
41+
.filter(set => set.type === 'placement')
4142
.map(item => _.sum(item.prizes.map(prize => prize.value)))).toLocaleString()}`}
4243
</span>
4344
</div>

src/shared/components/Gigs/LoginModal/index.jsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const progressBarMid = 'https://images.ctfassets.net/b5f1djy59z3a/517ZRt9geweW3Q
3030
const progressBarXS = 'https://images.ctfassets.net/b5f1djy59z3a/6QxH7uVKCngtzBaXDn3Od1/3e0222a1ce773cead3f3a45f291f43a6/progress-bar-mobile.svg';
3131
const blobPurple = 'https://images.ctfassets.net/b5f1djy59z3a/1ZRCwp1uoShcES16lQmeu/ba084734120ffedebcb92b4e3fa2d667/blob-purple.svg';
3232

33-
function LoginModal({ retUrl, onCancel }) {
33+
function LoginModal({ retUrl, onCancel, utmSource }) {
3434
return (
3535
<Modal
3636
theme={modalStyle}
@@ -56,7 +56,7 @@ function LoginModal({ retUrl, onCancel }) {
5656
<div className={modalStyle.ctaButtons}>
5757
<PrimaryButton
5858
onClick={() => {
59-
window.location = `${config.URL.AUTH}/member/registration?retUrl=${encodeURIComponent(retUrl)}&mode=signUp&utm_source=gig_listing`;
59+
window.location = `${config.URL.AUTH}/member/registration?retUrl=${encodeURIComponent(retUrl)}&mode=signUp&utm_source=${utmSource}`;
6060
}}
6161
theme={{
6262
button: buttonThemes.tc['primary-green-md'],
@@ -72,9 +72,14 @@ function LoginModal({ retUrl, onCancel }) {
7272
);
7373
}
7474

75+
LoginModal.defaultProps = {
76+
utmSource: 'gig_listing',
77+
};
78+
7579
LoginModal.propTypes = {
7680
retUrl: PT.string.isRequired,
7781
onCancel: PT.func.isRequired,
82+
utmSource: PT.string,
7883
};
7984

8085
export default LoginModal;
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**
2+
* Generic Login Modal Dialog
3+
*/
4+
/* global window */
5+
6+
import PT from 'prop-types';
7+
import React from 'react';
8+
import { Modal, PrimaryButton } from 'topcoder-react-ui-kit';
9+
import { config, Link } from 'topcoder-react-utils';
10+
import tc from 'components/buttons/themed/tc.scss';
11+
import modalStyle from './modal.scss';
12+
13+
/** Themes for buttons
14+
* those overwrite PrimaryButton style to match achieve various styles.
15+
* Should implement pattern of classes.
16+
*/
17+
const buttonThemes = {
18+
tc,
19+
};
20+
21+
function LoginModal({
22+
onCancel,
23+
retUrl,
24+
utmSource,
25+
modalTitle,
26+
modalText,
27+
infoNode,
28+
}) {
29+
return (
30+
<Modal
31+
onCancel={onCancel}
32+
theme={modalStyle}
33+
>
34+
<div className={modalStyle.loginRequired}>
35+
<h3 className={modalStyle.title}>{modalTitle}</h3>
36+
<p className={modalStyle.loginMsg}>{modalText}</p>
37+
<div className={modalStyle.ctaButtons}>
38+
<PrimaryButton
39+
onClick={() => {
40+
window.location = `${config.URL.AUTH}/member?retUrl=${encodeURIComponent(retUrl)}`;
41+
}}
42+
theme={{
43+
button: buttonThemes.tc['primary-green-md'],
44+
}}
45+
>
46+
LOGIN
47+
</PrimaryButton>
48+
<Link to={`${config.URL.AUTH}/member/registration?retUrl=${encodeURIComponent(retUrl)}&mode=signUp${utmSource ? `&utm_source=${utmSource}` : ''}`} className={buttonThemes.tc['primary-white-md']}>REGISTER</Link>
49+
</div>
50+
{infoNode}
51+
</div>
52+
</Modal>
53+
);
54+
}
55+
56+
LoginModal.defaultProps = {
57+
utmSource: null,
58+
infoNode: null,
59+
};
60+
61+
LoginModal.propTypes = {
62+
onCancel: PT.func.isRequired,
63+
retUrl: PT.string.isRequired,
64+
utmSource: PT.string,
65+
modalTitle: PT.string.isRequired,
66+
modalText: PT.string.isRequired,
67+
infoNode: PT.node,
68+
};
69+
70+
export default LoginModal;

0 commit comments

Comments
 (0)