diff --git a/.circleci/config.yml b/.circleci/config.yml index 349c4c5d3c..e86c2aeada 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -363,7 +363,7 @@ workflows: filters: branches: only: - - free + - thrive-vulnerability-1 # This is stage env for production QA releases - "build-prod-staging": context : org-global diff --git a/src/server/routes/contentful.js b/src/server/routes/contentful.js index 638ff7bd02..1084af62ba 100644 --- a/src/server/routes/contentful.js +++ b/src/server/routes/contentful.js @@ -3,7 +3,9 @@ */ import express from 'express'; - +import { middleware } from 'tc-core-library-js'; +import config from 'config'; +import _ from 'lodash'; import { ASSETS_DOMAIN, IMAGES_DOMAIN, @@ -14,6 +16,8 @@ import { const cors = require('cors'); +const authenticator = middleware.jwtAuthenticator; +const authenticatorOptions = _.pick(config.SECRET.JWT_AUTH, ['AUTH_SECRET', 'VALID_ISSUERS']); const routes = express.Router(); // Enables CORS on those routes according config above @@ -124,7 +128,7 @@ routes.use('/:spaceName/:environment/published/entries', (req, res, next) => { }); /* Update votes on article. */ -routes.use('/:spaceName/:environment/votes', (req, res, next) => { +routes.use('/:spaceName/:environment/votes', (req, res, next) => authenticator(authenticatorOptions)(req, res, next), (req, res, next) => { articleVote(req.body) .then(res.send.bind(res), next); }); diff --git a/src/shared/components/Contentful/Article/Article.jsx b/src/shared/components/Contentful/Article/Article.jsx index 0017142c24..2e4476dab5 100644 --- a/src/shared/components/Contentful/Article/Article.jsx +++ b/src/shared/components/Contentful/Article/Article.jsx @@ -5,6 +5,7 @@ */ import _ from 'lodash'; import React from 'react'; +import { connect } from 'react-redux'; import PT from 'prop-types'; import { fixStyle } from 'utils/contentful'; import { getService } from 'services/contentful'; @@ -22,6 +23,8 @@ import { config, Link, isomorphy, } from 'topcoder-react-utils'; import qs from 'qs'; +import LoginModal from 'components/LoginModal'; +import modalStyle from 'components/LoginModal/modal.scss'; // SVGs and assets import GestureIcon from 'assets/images/icon-gesture.svg'; import ReadMoreArrow from 'assets/images/read-more-arrow.svg'; @@ -41,21 +44,30 @@ const DEFAULT_BANNER_IMAGE = 'https://images.ctfassets.net/piwi0eufbb2g/7v2hlDsV const RANDOM_BANNERS = ['6G8mjiTC1mzeSQ2YoUG1gB', '1DnDD02xX1liHfSTf5Vsn8', 'HQZ3mN0rR92CbNTkKTHJ5', '1OLoX8ZsvjAnn4TdGbZESD', '77jn01UGoQe2gqA7x0coQD']; const RANDOM_BANNER = RANDOM_BANNERS[_.random(0, 4)]; -export default class Article extends React.Component { +class Article extends React.Component { componentDidMount() { const { fields } = this.props; this.setState({ upvotes: fields.upvotes || 0, downvotes: fields.downvotes || 0, + showLogin: false, + voting: false, }); } + // eslint-disable-next-line consistent-return updateVote(type) { - let userVotes = localStorage.getItem(LOCAL_STORAGE_KEY); - userVotes = userVotes ? JSON.parse(userVotes) : {}; const { - id, spaceName, environment, preview, + id, spaceName, environment, preview, auth, } = this.props; + // check for auth? + if (!auth) { + return this.setState({ + showLogin: true, + }); + } + let userVotes = localStorage.getItem(LOCAL_STORAGE_KEY); + userVotes = userVotes ? JSON.parse(userVotes) : {}; const articleVote = userVotes[id]; let { upvotes, downvotes } = this.state; // Check if user alredy voted on this article? @@ -93,10 +105,13 @@ export default class Article extends React.Component { } } // Store user action + this.setState({ + voting: true, + }); getService({ spaceName, environment, preview }).articleVote(id, { upvotes, downvotes, - }) + }, auth.tokenV3) .then(() => { // Only when Contentful enntry was succesfully updated // then we update the local store and the state @@ -104,6 +119,7 @@ export default class Article extends React.Component { this.setState({ upvotes, downvotes, + voting: false, }); }); } @@ -115,7 +131,9 @@ export default class Article extends React.Component { const contentfulConfig = { spaceName, environment, preview, }; - const { upvotes, downvotes } = this.state || {}; + const { + upvotes, downvotes, showLogin, voting, + } = this.state || {}; let shareUrl; if (isomorphy.isClientSide()) { shareUrl = encodeURIComponent(window.location.href); @@ -283,7 +301,7 @@ export default class Article extends React.Component { {/* Voting */}
Discover other features you can access by becoming a member.
} + /> + ) + } ); } @@ -388,6 +419,7 @@ export default class Article extends React.Component { Article.defaultProps = { spaceName: null, environment: null, + auth: null, }; Article.propTypes = { @@ -398,4 +430,16 @@ Article.propTypes = { preview: PT.bool.isRequired, spaceName: PT.string, environment: PT.string, + auth: PT.shape(), }; + +function mapStateToProps(state) { + const auth = state.auth && state.auth.profile ? { ...state.auth } : null; + return { + auth, + }; +} + +export default connect( + mapStateToProps, +)(Article); diff --git a/src/shared/components/Contentful/Article/themes/default.scss b/src/shared/components/Contentful/Article/themes/default.scss index 599dc5f363..7874e05796 100644 --- a/src/shared/components/Contentful/Article/themes/default.scss +++ b/src/shared/components/Contentful/Article/themes/default.scss @@ -419,7 +419,8 @@ padding: 8px 9px; margin-right: 10px; - .circleGreenIcon { + .circleGreenIcon, + .circleGreenIconDisabled { border-radius: 100%; width: 42px; height: 42px; @@ -431,7 +432,8 @@ background-color: #12c188; } - .circleRedIcon { + .circleRedIcon, + .circleRedIconDisabled { border-radius: 100%; width: 42px; height: 42px; @@ -444,6 +446,12 @@ transform: rotateX(-180deg); } + .circleGreenIconDisabled, + .circleRedIconDisabled { + pointer-events: none; + opacity: 0.5; + } + span { @include barlow-bold; diff --git a/src/shared/components/Gigs/LoginModal/index.jsx b/src/shared/components/Gigs/LoginModal/index.jsx index 673a390a7f..47c1124533 100644 --- a/src/shared/components/Gigs/LoginModal/index.jsx +++ b/src/shared/components/Gigs/LoginModal/index.jsx @@ -30,7 +30,7 @@ const progressBarMid = 'https://images.ctfassets.net/b5f1djy59z3a/517ZRt9geweW3Q const progressBarXS = 'https://images.ctfassets.net/b5f1djy59z3a/6QxH7uVKCngtzBaXDn3Od1/3e0222a1ce773cead3f3a45f291f43a6/progress-bar-mobile.svg'; const blobPurple = 'https://images.ctfassets.net/b5f1djy59z3a/1ZRCwp1uoShcES16lQmeu/ba084734120ffedebcb92b4e3fa2d667/blob-purple.svg'; -function LoginModal({ retUrl, onCancel }) { +function LoginModal({ retUrl, onCancel, utmSource }) { return ({modalText}
+