Skip to content

Commit

Permalink
Merge pull request #5743 from topcoder-platform/develop
Browse files Browse the repository at this point in the history
Release v1.13.5
  • Loading branch information
luizrrodrigues authored Oct 14, 2021
2 parents fd596b3 + 6e38292 commit 37e8a94
Show file tree
Hide file tree
Showing 9 changed files with 240 additions and 16 deletions.
3 changes: 2 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ workflows:
branches:
only:
- develop
- fix/issue-5739
# This is alternate dev env for parallel testing
- "build-test":
context : org-global
Expand All @@ -363,7 +364,7 @@ workflows:
filters:
branches:
only:
- free
- thrive-vulnerability-1
# This is stage env for production QA releases
- "build-prod-staging":
context : org-global
Expand Down
8 changes: 6 additions & 2 deletions src/server/routes/contentful.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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);
});
Expand Down
60 changes: 52 additions & 8 deletions src/shared/components/Contentful/Article/Article.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand All @@ -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?
Expand Down Expand Up @@ -93,17 +105,21 @@ 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
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(userVotes));
this.setState({
upvotes,
downvotes,
voting: false,
});
});
}
Expand All @@ -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);
Expand Down Expand Up @@ -283,7 +301,7 @@ export default class Article extends React.Component {
{/* Voting */}
<div className={theme.actionContainer}>
<div className={theme.action}>
<div tabIndex={0} role="button" className={theme.circleGreenIcon} onClick={() => this.updateVote('up')} onKeyPress={() => this.updateVote('up')}>
<div tabIndex={0} role="button" className={voting ? theme.circleGreenIconDisabled : theme.circleGreenIcon} onClick={() => this.updateVote('up')} onKeyPress={() => this.updateVote('up')}>
<GestureIcon />
</div>
<span>
Expand All @@ -293,7 +311,7 @@ export default class Article extends React.Component {
</span>
</div>
<div className={theme.action}>
<div tabIndex={0} role="button" className={theme.circleRedIcon} onClick={() => this.updateVote('down')} onKeyPress={() => this.updateVote('down')}>
<div tabIndex={0} role="button" className={voting ? theme.circleRedIconDisabled : theme.circleRedIcon} onClick={() => this.updateVote('down')} onKeyPress={() => this.updateVote('down')}>
<GestureIcon />
</div>
<span>{downvotes}</span>
Expand Down Expand Up @@ -380,6 +398,19 @@ export default class Article extends React.Component {
) : null
}
</div>
{
showLogin && (
<LoginModal
// eslint-disable-next-line no-restricted-globals
retUrl={isomorphy.isClientSide() ? location.href : null}
onCancel={() => this.setState({ showLogin: false })}
modalTitle="Want to vote?"
modalText="You must be a Topcoder member to do that."
utmSource="thrive_article"
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>}
/>
)
}
</React.Fragment>
);
}
Expand All @@ -388,6 +419,7 @@ export default class Article extends React.Component {
Article.defaultProps = {
spaceName: null,
environment: null,
auth: null,
};

Article.propTypes = {
Expand All @@ -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);
12 changes: 10 additions & 2 deletions src/shared/components/Contentful/Article/themes/default.scss
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,8 @@
padding: 8px 9px;
margin-right: 10px;

.circleGreenIcon {
.circleGreenIcon,
.circleGreenIconDisabled {
border-radius: 100%;
width: 42px;
height: 42px;
Expand All @@ -431,7 +432,8 @@
background-color: #12c188;
}

.circleRedIcon {
.circleRedIcon,
.circleRedIconDisabled {
border-radius: 100%;
width: 42px;
height: 42px;
Expand All @@ -444,6 +446,12 @@
transform: rotateX(-180deg);
}

.circleGreenIconDisabled,
.circleRedIconDisabled {
pointer-events: none;
opacity: 0.5;
}

span {
@include barlow-bold;

Expand Down
1 change: 1 addition & 0 deletions src/shared/components/Dashboard/Challenges/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export default function ChallengesFeed({
<div styleName="prize">
<span styleName="amount">
{`$${_.sum(challenge.prizeSets
.filter(set => set.type === 'placement')
.map(item => _.sum(item.prizes.map(prize => prize.value)))).toLocaleString()}`}
</span>
</div>
Expand Down
9 changes: 7 additions & 2 deletions src/shared/components/Gigs/LoginModal/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<Modal
theme={modalStyle}
Expand All @@ -56,7 +56,7 @@ function LoginModal({ retUrl, onCancel }) {
<div className={modalStyle.ctaButtons}>
<PrimaryButton
onClick={() => {
window.location = `${config.URL.AUTH}/member/registration?retUrl=${encodeURIComponent(retUrl)}&mode=signUp&utm_source=gig_listing`;
window.location = `${config.URL.AUTH}/member/registration?retUrl=${encodeURIComponent(retUrl)}&mode=signUp&utm_source=${utmSource}`;
}}
theme={{
button: buttonThemes.tc['primary-green-md'],
Expand All @@ -72,9 +72,14 @@ function LoginModal({ retUrl, onCancel }) {
);
}

LoginModal.defaultProps = {
utmSource: 'gig_listing',
};

LoginModal.propTypes = {
retUrl: PT.string.isRequired,
onCancel: PT.func.isRequired,
utmSource: PT.string,
};

export default LoginModal;
70 changes: 70 additions & 0 deletions src/shared/components/LoginModal/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* Generic Login Modal Dialog
*/
/* global window */

import PT from 'prop-types';
import React from 'react';
import { Modal, PrimaryButton } from 'topcoder-react-ui-kit';
import { config, Link } from 'topcoder-react-utils';
import tc from 'components/buttons/themed/tc.scss';
import modalStyle from './modal.scss';

/** Themes for buttons
* those overwrite PrimaryButton style to match achieve various styles.
* Should implement pattern of classes.
*/
const buttonThemes = {
tc,
};

function LoginModal({
onCancel,
retUrl,
utmSource,
modalTitle,
modalText,
infoNode,
}) {
return (
<Modal
onCancel={onCancel}
theme={modalStyle}
>
<div className={modalStyle.loginRequired}>
<h3 className={modalStyle.title}>{modalTitle}</h3>
<p className={modalStyle.loginMsg}>{modalText}</p>
<div className={modalStyle.ctaButtons}>
<PrimaryButton
onClick={() => {
window.location = `${config.URL.AUTH}/member?retUrl=${encodeURIComponent(retUrl)}`;
}}
theme={{
button: buttonThemes.tc['primary-green-md'],
}}
>
LOGIN
</PrimaryButton>
<Link to={`${config.URL.AUTH}/member/registration?retUrl=${encodeURIComponent(retUrl)}&mode=signUp${utmSource ? `&utm_source=${utmSource}` : ''}`} className={buttonThemes.tc['primary-white-md']}>REGISTER</Link>
</div>
{infoNode}
</div>
</Modal>
);
}

LoginModal.defaultProps = {
utmSource: null,
infoNode: null,
};

LoginModal.propTypes = {
onCancel: PT.func.isRequired,
retUrl: PT.string.isRequired,
utmSource: PT.string,
modalTitle: PT.string.isRequired,
modalText: PT.string.isRequired,
infoNode: PT.node,
};

export default LoginModal;
Loading

0 comments on commit 37e8a94

Please sign in to comment.