From 02e6e711cff6ba6c28b0b2639d550250efac35cb Mon Sep 17 00:00:00 2001 From: Mrugesh Mohapatra Date: Thu, 7 Feb 2019 19:03:18 +0530 Subject: [PATCH] fix(donate): refactor handlers for charges --- api-server/server/boot/donate.js | 21 +++++++++-- api-server/server/middlewares/csurf.js | 2 +- client/src/components/Donation/Donation.css | 27 ++++++++------ .../Donation/components/DonateCompletion.js | 27 +++++++++----- .../Donation/components/DonateForm.js | 35 ++++++++++++------- .../Donation/components/DonateModal.js | 19 ++++------ client/src/pages/donate.js | 24 ++----------- .../src/templates/Challenges/utils/index.js | 2 +- 8 files changed, 88 insertions(+), 69 deletions(-) diff --git a/api-server/server/boot/donate.js b/api-server/server/boot/donate.js index 0437ba7d14..c6fc7bad56 100644 --- a/api-server/server/boot/donate.js +++ b/api-server/server/boot/donate.js @@ -1,6 +1,10 @@ import Stripe from 'stripe'; +import debug from 'debug'; + import keys from '../../../config/secrets'; +const log = debug('fcc:boot:donate'); + export default function donateBoot(app, done) { let stripe = false; @@ -70,7 +74,19 @@ export default function donateBoot(app, done) { const fccUser = user ? Promise.resolve(user) : - User.create$({ email }).toPromise(); + new Promise((resolve, reject) => + User.findOrCreate( + { where: { email }}, + { email }, + (err, instance, isNew) => { + log('is new user instance: ', isNew); + if (err) { + return reject(err); + } + return resolve(instance); + } + ) + ); let donatingUser = {}; let donation = { @@ -133,7 +149,8 @@ export default function donateBoot(app, done) { api.post('/charge-stripe', createStripeDonation); donateRouter.use('/donate', api); app.use(donateRouter); - app.use('/external', donateRouter); + app.use('/internal', donateRouter); + app.use('/unauthenticated', donateRouter); connectToStripe().then(done); } } diff --git a/api-server/server/middlewares/csurf.js b/api-server/server/middlewares/csurf.js index 081a44f1ce..8ca0374ff4 100644 --- a/api-server/server/middlewares/csurf.js +++ b/api-server/server/middlewares/csurf.js @@ -11,7 +11,7 @@ export default function() { return function csrf(req, res, next) { const path = req.path.split('/')[1]; - if (/(^api$|^external$|^internal$|^p$)/.test(path)) { + if (/(^api$|^unauthenticated$|^internal$|^p$)/.test(path)) { return next(); } return protection(req, res, next); diff --git a/client/src/components/Donation/Donation.css b/client/src/components/Donation/Donation.css index 815d29e586..4ca8722fd8 100644 --- a/client/src/components/Donation/Donation.css +++ b/client/src/components/Donation/Donation.css @@ -3,15 +3,22 @@ } .donation-modal p { - width: 90%; margin-left: auto; margin-right: auto; } +.donation-modal .alert { + width: 60%; + margin: 0 auto 0; +} + .donation-modal .modal-title { font-size: 1.2rem; } +.donation-modal .donation-form { + width: 60%; +} .donation-form { display: flex; flex-direction: column; @@ -26,19 +33,19 @@ justify-content: space-between; } -#donation-completion-body { +.donation-completion, +.donation-completion-body { display: flex; + flex-direction: column; justify-content: center; align-items: center; + text-align: center; } .donation-completion-buttons { display: flex; - justify-content: flex-end; -} - -.donation-completion-buttons button { - margin: 0 10px; + justify-content: center; + align-items: center; } .donation-email-container label { @@ -51,16 +58,16 @@ font-weight: normal; } -.maybe-later-container { +.modal-close-btn-container { display: flex; justify-content: center; } -.maybe-later-container a { +.modal-close-btn-container a { font-size: 18px; } -.maybe-later-container a:hover { +.modal-close-btn-container a:hover { text-decoration: none; font-size: 18px; cursor: pointer; diff --git a/client/src/components/Donation/components/DonateCompletion.js b/client/src/components/Donation/components/DonateCompletion.js index cd48df33e8..d85ea8a805 100644 --- a/client/src/components/Donation/components/DonateCompletion.js +++ b/client/src/components/Donation/components/DonateCompletion.js @@ -1,8 +1,10 @@ -import React, { Fragment } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import { Alert, Button } from '@freecodecamp/react-bootstrap'; import Spinner from 'react-spinkit'; +import '../Donation.css'; + const propTypes = { error: PropTypes.string, processing: PropTypes.bool, @@ -16,13 +18,12 @@ function DonateCompletion({ processing, reset, success, error = null }) { const heading = processing ? 'We are processing your donation.' : success - ? 'Your donation was successful. Thank you for supporting the ' + - 'freeCodeCamp.org community.' + ? 'Your donation was successful.' : 'Something went wrong with your donation'; return ( - +

{heading}

-
+
{processing && ( )} - {error && error} + {success && ( +

+ Thank you for supporting the freeCodeCamp.org community. +

+ )} + {error && ( +

+ {error} +

+ )}

{error && ( - +

- - +
)}

diff --git a/client/src/components/Donation/components/DonateForm.js b/client/src/components/Donation/components/DonateForm.js index 50c44c50c7..18b3c9988e 100644 --- a/client/src/components/Donation/components/DonateForm.js +++ b/client/src/components/Donation/components/DonateForm.js @@ -1,5 +1,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; import isEmail from 'validator/lib/isEmail'; import { Button, @@ -10,14 +12,15 @@ import { } from '@freecodecamp/react-bootstrap'; import { injectStripe } from 'react-stripe-elements'; +import { apiLocation } from '../../../../config/env.json'; import Spacer from '../../../components/helpers/Spacer'; import StripeCardForm from './StripeCardForm'; import DonateCompletion from './DonateCompletion'; import { postJSON$ } from '../../../templates/Challenges/utils/ajax-stream.js'; +import { userSelector, isSignedInSelector } from '../../../redux'; const propTypes = { email: PropTypes.string, - maybeButton: PropTypes.func.isRequired, stripe: PropTypes.shape({ createToken: PropTypes.func.isRequired }) @@ -31,6 +34,12 @@ const initialSate = { } }; +const mapStateToProps = createSelector( + userSelector, + isSignedInSelector, + ({ email }, isSignedIn) => ({ email, isSignedIn }) +); + class DonateForm extends Component { constructor(...args) { super(...args); @@ -50,7 +59,6 @@ class DonateForm extends Component { this.submit = this.submit.bind(this); } - getUserEmail() { const { email: stateEmail } = this.state; const { email: propsEmail } = this.props; @@ -103,6 +111,7 @@ class DonateForm extends Component { postDonation(token) { const { donationAmount: amount } = this.state; + const { isSignedIn } = this.props; this.setState(state => ({ ...state, donationState: { @@ -110,7 +119,10 @@ class DonateForm extends Component { processing: true } })); - const chargeStripePath = '/unauthenticated/donate/charge-stripe'; + + const chargeStripePath = isSignedIn ? + '/internal/donate/charge-stripe' : + `${apiLocation}/unauthenticated/donate/charge-stripe`; return postJSON$(chargeStripePath, { token, amount @@ -148,9 +160,7 @@ class DonateForm extends Component { } renderCompletion(props) { - return ( - - ); + return ; } renderDonateForm() { @@ -163,11 +173,11 @@ class DonateForm extends Component { Email (we'll send you a tax-deductible donation receipt): @@ -183,7 +193,6 @@ class DonateForm extends Component { - {this.props.maybeButton()}
); } @@ -207,4 +216,4 @@ class DonateForm extends Component { DonateForm.displayName = 'DonateForm'; DonateForm.propTypes = propTypes; -export default injectStripe(DonateForm); +export default injectStripe(connect(mapStateToProps)(DonateForm)); diff --git a/client/src/components/Donation/components/DonateModal.js b/client/src/components/Donation/components/DonateModal.js index 18d9988b4c..33a646310e 100644 --- a/client/src/components/Donation/components/DonateModal.js +++ b/client/src/components/Donation/components/DonateModal.js @@ -11,7 +11,6 @@ import { stripePublicKey } from '../../../../config/env.json'; import ga from '../../../analytics'; import DonateForm from './DonateForm'; import { - userSelector, closeDonationModal, isDonationModalOpenSelector } from '../../../redux'; @@ -22,9 +21,8 @@ import DonateText from './DonateText'; import '../Donation.css'; const mapStateToProps = createSelector( - userSelector, isDonationModalOpenSelector, - ({ email }, show) => ({ email, show }) + show => ({ show }) ); const mapDispatchToProps = dispatch => @@ -37,7 +35,6 @@ const mapDispatchToProps = dispatch => const propTypes = { closeDonationModal: PropTypes.func.isRequired, - email: PropTypes.string, show: PropTypes.bool }; @@ -76,14 +73,14 @@ class DonateModal extends Component { return closeDonationModal(); }; return ( -
- +
+
); } render() { - const { email, show } = this.props; + const { show } = this.props; if (show) { ga.modalview('/donation-modal'); } @@ -97,11 +94,9 @@ class DonateModal extends Component { - - + + + {this.renderMaybe()} diff --git a/client/src/pages/donate.js b/client/src/pages/donate.js index 6e434955a8..a9f67e69d5 100644 --- a/client/src/pages/donate.js +++ b/client/src/pages/donate.js @@ -1,14 +1,10 @@ /* eslint-disable max-len */ import React, { Component, Fragment } from 'react'; -import PropTypes from 'prop-types'; import Helmet from 'react-helmet'; -import { connect } from 'react-redux'; import { StripeProvider, Elements } from 'react-stripe-elements'; -import { createSelector } from 'reselect'; import { Row, Col } from '@freecodecamp/react-bootstrap'; import { stripePublicKey } from '../../config/env.json'; -import { userSelector } from '../redux'; import Spacer from '../components/helpers/Spacer'; import DonateForm from '../components/Donation/components/DonateForm'; @@ -17,15 +13,6 @@ import PoweredByStripe from '../components/Donation/components/poweredByStripe'; import './index.css'; -const propTypes = { - email: PropTypes.string, - show: PropTypes.bool -}; - -const mapStateToProps = createSelector(userSelector, ({ email = '' }) => ({ - email -})); - class IndexPage extends Component { constructor(...props) { super(...props); @@ -66,7 +53,6 @@ class IndexPage extends Component { } render() { - const { email = '' } = this.props; return ( @@ -76,16 +62,13 @@ class IndexPage extends Component {

Become a Supporter

- +
- null} - /> +
@@ -102,6 +85,5 @@ class IndexPage extends Component { } IndexPage.displayName = 'IndexPage'; -IndexPage.propTypes = propTypes; -export default connect(mapStateToProps)(IndexPage); +export default IndexPage; diff --git a/client/src/templates/Challenges/utils/index.js b/client/src/templates/Challenges/utils/index.js index 8ec84a7ced..1c41ff9c54 100644 --- a/client/src/templates/Challenges/utils/index.js +++ b/client/src/templates/Challenges/utils/index.js @@ -6,5 +6,5 @@ export function createGuideUrl(slug = '') { export function isGoodXHRStatus(status) { const statusInt = parseInt(status, 10); - return statusInt >= 200 && statusInt < 400; + return (statusInt >= 200 && statusInt < 400) || statusInt === 402; }