Skip to content

Commit eb04bd7

Browse files
committed
Fix for #5724
1 parent fd596b3 commit eb04bd7

File tree

5 files changed

+68
-15
lines changed

5 files changed

+68
-15
lines changed

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: 42 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,7 @@ import {
2223
config, Link, isomorphy,
2324
} from 'topcoder-react-utils';
2425
import qs from 'qs';
26+
import LoginModal from 'components/Gigs/LoginModal';
2527
// SVGs and assets
2628
import GestureIcon from 'assets/images/icon-gesture.svg';
2729
import ReadMoreArrow from 'assets/images/read-more-arrow.svg';
@@ -41,21 +43,30 @@ const DEFAULT_BANNER_IMAGE = 'https://images.ctfassets.net/piwi0eufbb2g/7v2hlDsV
4143
const RANDOM_BANNERS = ['6G8mjiTC1mzeSQ2YoUG1gB', '1DnDD02xX1liHfSTf5Vsn8', 'HQZ3mN0rR92CbNTkKTHJ5', '1OLoX8ZsvjAnn4TdGbZESD', '77jn01UGoQe2gqA7x0coQD'];
4244
const RANDOM_BANNER = RANDOM_BANNERS[_.random(0, 4)];
4345

44-
export default class Article extends React.Component {
46+
class Article extends React.Component {
4547
componentDidMount() {
4648
const { fields } = this.props;
4749
this.setState({
4850
upvotes: fields.upvotes || 0,
4951
downvotes: fields.downvotes || 0,
52+
showLogin: false,
53+
voting: false,
5054
});
5155
}
5256

57+
// eslint-disable-next-line consistent-return
5358
updateVote(type) {
54-
let userVotes = localStorage.getItem(LOCAL_STORAGE_KEY);
55-
userVotes = userVotes ? JSON.parse(userVotes) : {};
5659
const {
57-
id, spaceName, environment, preview,
60+
id, spaceName, environment, preview, auth,
5861
} = this.props;
62+
// check for auth?
63+
if (!auth) {
64+
return this.setState({
65+
showLogin: true,
66+
});
67+
}
68+
let userVotes = localStorage.getItem(LOCAL_STORAGE_KEY);
69+
userVotes = userVotes ? JSON.parse(userVotes) : {};
5970
const articleVote = userVotes[id];
6071
let { upvotes, downvotes } = this.state;
6172
// Check if user alredy voted on this article?
@@ -93,17 +104,21 @@ export default class Article extends React.Component {
93104
}
94105
}
95106
// Store user action
107+
this.setState({
108+
voting: true,
109+
});
96110
getService({ spaceName, environment, preview }).articleVote(id, {
97111
upvotes,
98112
downvotes,
99-
})
113+
}, auth.tokenV3)
100114
.then(() => {
101115
// Only when Contentful enntry was succesfully updated
102116
// then we update the local store and the state
103117
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(userVotes));
104118
this.setState({
105119
upvotes,
106120
downvotes,
121+
voting: false,
107122
});
108123
});
109124
}
@@ -115,7 +130,9 @@ export default class Article extends React.Component {
115130
const contentfulConfig = {
116131
spaceName, environment, preview,
117132
};
118-
const { upvotes, downvotes } = this.state || {};
133+
const {
134+
upvotes, downvotes, showLogin, voting,
135+
} = this.state || {};
119136
let shareUrl;
120137
if (isomorphy.isClientSide()) {
121138
shareUrl = encodeURIComponent(window.location.href);
@@ -283,7 +300,7 @@ export default class Article extends React.Component {
283300
{/* Voting */}
284301
<div className={theme.actionContainer}>
285302
<div className={theme.action}>
286-
<div tabIndex={0} role="button" className={theme.circleGreenIcon} onClick={() => this.updateVote('up')} onKeyPress={() => this.updateVote('up')}>
303+
<div tabIndex={0} role="button" className={voting ? theme.circleGreenIconDisabled : theme.circleGreenIcon} onClick={() => this.updateVote('up')} onKeyPress={() => this.updateVote('up')}>
287304
<GestureIcon />
288305
</div>
289306
<span>
@@ -293,7 +310,7 @@ export default class Article extends React.Component {
293310
</span>
294311
</div>
295312
<div className={theme.action}>
296-
<div tabIndex={0} role="button" className={theme.circleRedIcon} onClick={() => this.updateVote('down')} onKeyPress={() => this.updateVote('down')}>
313+
<div tabIndex={0} role="button" className={voting ? theme.circleRedIconDisabled : theme.circleRedIcon} onClick={() => this.updateVote('down')} onKeyPress={() => this.updateVote('down')}>
297314
<GestureIcon />
298315
</div>
299316
<span>{downvotes}</span>
@@ -380,6 +397,10 @@ export default class Article extends React.Component {
380397
) : null
381398
}
382399
</div>
400+
{
401+
// eslint-disable-next-line no-restricted-globals
402+
showLogin && <LoginModal retUrl={isomorphy.isClientSide() ? location.href : null} onCancel={() => this.setState({ showLogin: false })} utmSource="thrive_article" />
403+
}
383404
</React.Fragment>
384405
);
385406
}
@@ -388,6 +409,7 @@ export default class Article extends React.Component {
388409
Article.defaultProps = {
389410
spaceName: null,
390411
environment: null,
412+
auth: null,
391413
};
392414

393415
Article.propTypes = {
@@ -398,4 +420,16 @@ Article.propTypes = {
398420
preview: PT.bool.isRequired,
399421
spaceName: PT.string,
400422
environment: PT.string,
423+
auth: PT.shape(),
401424
};
425+
426+
function mapStateToProps(state) {
427+
const auth = state.auth && state.auth.profile ? { ...state.auth } : null;
428+
return {
429+
auth,
430+
};
431+
}
432+
433+
export default connect(
434+
mapStateToProps,
435+
)(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/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;

src/shared/services/contentful.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,15 +258,17 @@ class Service {
258258
* Vote on article
259259
* @param {String} id Entry ID.
260260
* @param {Array} data The updated data array
261+
* @param {String} tokenV3 user's auth token
261262
* @returns {Promise<void>}
262263
*/
263-
async articleVote(id, votes) {
264+
async articleVote(id, votes, tokenV3) {
264265
// eslint-disable-next-line prefer-template
265266
const url = this.private.baseUrl + '/votes';
266267
const res = await fetch(url, {
267268
method: 'POST',
268269
headers: {
269270
'Content-Type': 'application/json',
271+
Authorization: `Bearer ${tokenV3}`,
270272
},
271273
body: JSON.stringify({
272274
id, votes,

0 commit comments

Comments
 (0)