diff --git a/client/src/components/Donation/donation.css b/client/src/components/Donation/Donation.css similarity index 55% rename from client/src/components/Donation/donation.css rename to client/src/components/Donation/Donation.css index 8d1b4229b8..3477da2134 100644 --- a/client/src/components/Donation/donation.css +++ b/client/src/components/Donation/Donation.css @@ -1,5 +1,5 @@ .donation-modal { - font-size: 1.2rem; + font-size: 0.8rem; } .donation-modal p { @@ -18,43 +18,12 @@ width:80%; justify-content: center; margin: 0 auto; - height: 300px; -} - -#donate-amount-panel ul { - list-style: none; - display: flex; - margin-left: 0px; -} - -#donate-amount-panel li { - flex: 0.20; - display: flex; - justify-content: center; - align-items: center; -} - -#donate-amount-panel a { - width: calc(100% - 20px); - height: 75px; - display: flex; - justify-content: center; - align-items: center; - border: 2px solid #006400; -} - -#donate-amount-panel a:hover, #donate-amount-panel a:focus, #donate-amount-panel a.active { - text-decoration: none; - color: white; - background-color: #006400; - font-weight: bold; } .donation-elements { display: flex; - height: 100%; - flex-direction: column; - justify-content: space-evenly; + flex-direction: row; + justify-content: space-between; } #donation-completion-body { @@ -72,11 +41,6 @@ margin: 0 10px; } -.donation-email-container { - width: 80%; - margin: 0 auto; -} - .donation-email-container label { display: flex; flex-direction: column; @@ -85,12 +49,9 @@ .donation-email-container input { color: #006400; font-weight: normal; - border: none; } .maybe-later-container { - width: 80%; - margin: 50px auto 0; display: flex; justify-content: center; } @@ -104,3 +65,30 @@ font-size: 18px; cursor: pointer; } + +.donate-input-element { + padding-top: 8px; +} + +.donate-card-element { + min-width: 280px; + flex: 1 1 auto; +} + +.donate-expiry-element { + min-width: 120px; + flex: 0 0 auto; +} + +.donate-cvc-element { + min-width: 80px; + flex: 0 0 auto; +} + +.StripeElement--focus { + border-color: #66afe9; + outline: 0; + box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6); +} + + diff --git a/client/src/components/Donation/components/CardForm.js b/client/src/components/Donation/components/CardForm.js deleted file mode 100644 index 64910c5895..0000000000 --- a/client/src/components/Donation/components/CardForm.js +++ /dev/null @@ -1,59 +0,0 @@ -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; -import { Button } from '@freecodecamp/react-bootstrap'; - -import StripCardForm from './StripeCardForm'; - -const propTypes = { - amount: PropTypes.number.isRequired, - handleSubmit: PropTypes.func.isRequired -}; - -class CardForm extends PureComponent { - constructor(...props) { - super(...props); - - this.state = { - isFormValid: false - }; - - this.getValidationState = this.getValidationState.bind(this); - this.submit = this.submit.bind(this); - } - - submit(e) { - e.preventDefault(); - this.props.handleSubmit(); - } - - getValidationState(isFormValid) { - this.setState(state => ({ - ...state, - isFormValid - })); - } - - render() { - const { amount } = this.props; - const { isFormValid } = this.state; - return ( -
- - - - ); - } -} -CardForm.displayName = 'CardForm'; -CardForm.propTypes = propTypes; - -export default CardForm; diff --git a/client/src/components/Donation/components/DonateCompletion.js b/client/src/components/Donation/components/DonateCompletion.js index 615ed06c1d..9a7f774444 100644 --- a/client/src/components/Donation/components/DonateCompletion.js +++ b/client/src/components/Donation/components/DonateCompletion.js @@ -11,7 +11,7 @@ const propTypes = { success: PropTypes.bool }; -function DonateCompletion({ processing, reset, success, error = null }) { +function DonateCompletion({ close, processing, reset, success, error = null }) { /* eslint-disable no-nested-ternary */ const style = processing ? 'info' : success ? 'success' : 'danger'; const heading = processing @@ -43,6 +43,7 @@ function DonateCompletion({ processing, reset, success, error = null }) { )} + {!processing && }

); diff --git a/client/src/components/Donation/components/DonateForm.js b/client/src/components/Donation/components/DonateForm.js index 2c683d2d9d..ca6189cb77 100644 --- a/client/src/components/Donation/components/DonateForm.js +++ b/client/src/components/Donation/components/DonateForm.js @@ -1,13 +1,22 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import isEmail from 'validator/lib/isEmail'; - -import CardForm from './CardForm'; +import { + Button, + ControlLabel, + Form, + FormControl, + FormGroup +} from '@freecodecamp/react-bootstrap'; import { injectStripe } from 'react-stripe-elements'; + +import Spacer from '../../../components/helpers/Spacer'; +import StripeCardForm from './StripeCardForm'; import { postJSON$ } from '../../../templates/Challenges/utils/ajax-stream.js'; const propTypes = { email: PropTypes.string, + maybeButton: PropTypes.func.isRequired, renderCompletion: PropTypes.func.isRequired, stripe: PropTypes.shape({ createToken: PropTypes.func.isRequired @@ -28,14 +37,31 @@ class DonateForm extends Component { this.state = { ...initialSate, - email: null + email: null, + isFormValid: false }; this.getUserEmail = this.getUserEmail.bind(this); + this.getValidationState = this.getValidationState.bind(this); this.handleEmailChange = this.handleEmailChange.bind(this); this.handleSubmit = this.handleSubmit.bind(this); this.postDonation = this.postDonation.bind(this); this.resetDonation = this.resetDonation.bind(this); + this.submit = this.submit.bind(this); + } + + + getUserEmail() { + const { email: stateEmail } = this.state; + const { email: propsEmail } = this.props; + return stateEmail || propsEmail || ''; + } + + getValidationState(isFormValid) { + this.setState(state => ({ + ...state, + isFormValid + })); } handleEmailChange(e) { @@ -75,12 +101,6 @@ class DonateForm extends Component { }); } - getUserEmail() { - const { email: stateEmail } = this.state; - const { email: propsEmail } = this.props; - return stateEmail || propsEmail || ''; - } - postDonation(token) { const { donationAmount: amount } = this.state; this.setState(state => ({ @@ -118,50 +138,50 @@ class DonateForm extends Component { ); } - renderDonateForm() { - return ( -
-
-

- freeCodeCamp.org is a tiny nonprofit that's helping millions of - people learn to code for free. -

-

- Join 4,180 supporters. -

-

- Your $5 / month donation will help keep tech education free and - open. -

-
-
- {this.renderEmailInput()} - -
- ); - } - - renderEmailInput() { - return ( -
- -
- ); + submit(e) { + e.preventDefault(); + this.handleSubmit(); } resetDonation() { return this.setState(() => initialSate); } + renderDonateForm() { + const { isFormValid } = this.state; + return ( +
+
+ + + Email (we'll send you a tax-deductible donation receipt): + + + + + + + + {this.props.maybeButton()} +
+ ); + } + render() { const { donationState: { processing, success, error } diff --git a/client/src/components/Donation/Donation.js b/client/src/components/Donation/components/DonateModal.js similarity index 57% rename from client/src/components/Donation/Donation.js rename to client/src/components/Donation/components/DonateModal.js index d5e70772d5..b688bf2d7c 100644 --- a/client/src/components/Donation/Donation.js +++ b/client/src/components/Donation/components/DonateModal.js @@ -3,20 +3,24 @@ import PropTypes from 'prop-types'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; -import { Modal } from '@freecodecamp/react-bootstrap'; +import { Modal, Button } from '@freecodecamp/react-bootstrap'; import { StripeProvider, Elements } from 'react-stripe-elements'; -import ga from '../../analytics'; -import DonateForm from './components/DonateForm'; -import DonateCompletion from './components/DonateCompletion'; +import { stripePublicKey } from '../../../../config/env.json'; + +import ga from '../../../analytics'; +import DonateForm from './DonateForm'; +import DonateCompletion from './DonateCompletion'; import { userSelector, closeDonationModal, isDonationModalOpenSelector -} from '../../redux'; +} from '../../../redux'; -import './donation.css'; -import poweredByStripe from '../../images/powered_by_stripe.svg'; +import PoweredByStripe from './poweredByStripe'; +import DonateText from './DonateText'; + +import '../Donation.css'; const mapStateToProps = createSelector( userSelector, @@ -38,9 +42,7 @@ const propTypes = { show: PropTypes.bool }; -const stripeKey = 'pk_live_E6Z6xPM8pEsJziHW905zpAvF'; - -class DonationModal extends Component { +class DonateModal extends Component { constructor(...props) { super(...props); this.state = { @@ -54,7 +56,7 @@ class DonationModal extends Component { /* eslint-disable react/no-did-mount-set-state */ this.setState(state => ({ ...state, - stripe: window.Stripe(stripeKey) + stripe: window.Stripe(stripePublicKey) })); } else { document.querySelector('#stripe-js').addEventListener('load', () => { @@ -62,7 +64,7 @@ class DonationModal extends Component { console.info('stripe has loaded'); this.setState(state => ({ ...state, - stripe: window.Stripe(stripeKey) + stripe: window.Stripe(stripePublicKey) })); }); } @@ -77,12 +79,13 @@ class DonationModal extends Component { renderMaybe() { const { closeDonationModal } = this.props; const handleClick = e => { + console.log(e.target); e.preventDefault(); return closeDonationModal(); }; return (
- +
); } @@ -93,36 +96,34 @@ class DonationModal extends Component { ga.modalview('/donation-modal'); } return ( - - - - - - Support Our NonProfit - - - - - - - powered by stripe - - - - + + + + + + Support Our NonProfit + + + + + + + + + + + + ); } } -DonationModal.displayName = 'DonationModal'; -DonationModal.propTypes = propTypes; +DonateModal.displayName = 'DonateModal'; +DonateModal.propTypes = propTypes; export default connect( mapStateToProps, @@ -131,4 +132,4 @@ export default connect( { pure: false } -)(DonationModal); +)(DonateModal); diff --git a/client/src/components/Donation/components/DonateText.js b/client/src/components/Donation/components/DonateText.js new file mode 100644 index 0000000000..90e9221b73 --- /dev/null +++ b/client/src/components/Donation/components/DonateText.js @@ -0,0 +1,23 @@ +import React, { Component } from 'react'; + +class DonateText extends Component { + render() { + return ( +
+

+ freeCodeCamp.org is a tiny nonprofit that's helping millions of + people learn to code for free. +

+

+ Join 4,180 supporters. +

+

+ Your $5 / month donation will help keep tech education free and + open. +

+
+ ); + } +} + +export default DonateText; diff --git a/client/src/components/Donation/components/StripeCardForm.js b/client/src/components/Donation/components/StripeCardForm.js index f538a84019..2033c3fc8c 100644 --- a/client/src/components/Donation/components/StripeCardForm.js +++ b/client/src/components/Donation/components/StripeCardForm.js @@ -1,4 +1,4 @@ -import React, { PureComponent } from 'react'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { CardNumberElement, @@ -7,17 +7,20 @@ import { } from 'react-stripe-elements'; import { ControlLabel, FormGroup } from '@freecodecamp/react-bootstrap'; +import '../Donation.css'; + const propTypes = { getValidationState: PropTypes.func.isRequired }; const style = { base: { - color: '#006400' + color: '#006400', + fontSize: '18px' } }; -class StripCardForm extends PureComponent { +class StripeCardForm extends Component { constructor(...props) { super(...props); @@ -74,23 +77,35 @@ class StripCardForm extends PureComponent { return (
- Your Card Number: - + Card Number: + - Your Card Expiration Month: - + Expiry: + - Your Card CVC (3-digit security number): - + CVC: +
); } } -StripCardForm.displayName = 'StripCardForm'; -StripCardForm.propTypes = propTypes; +StripeCardForm.displayName = 'StripeCardForm'; +StripeCardForm.propTypes = propTypes; -export default StripCardForm; +export default StripeCardForm; diff --git a/client/src/components/icons/poweredByStripe.js b/client/src/components/Donation/components/poweredByStripe.js similarity index 100% rename from client/src/components/icons/poweredByStripe.js rename to client/src/components/Donation/components/poweredByStripe.js diff --git a/client/src/components/Donation/index.js b/client/src/components/Donation/index.js index ed478f8eb8..39cf10aca2 100644 --- a/client/src/components/Donation/index.js +++ b/client/src/components/Donation/index.js @@ -1 +1 @@ -export { default } from './Donation'; +export { default } from './components/DonateModal'; diff --git a/client/src/components/layouts/Learn.js b/client/src/components/layouts/Learn.js index b6bda95557..2ff4ecce37 100644 --- a/client/src/components/layouts/Learn.js +++ b/client/src/components/layouts/Learn.js @@ -1,7 +1,7 @@ import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; -import DonationModal from '../Donation'; +import DonateModal from '../Donation'; import 'prismjs/themes/prism.css'; import 'react-reflex/styles.css'; @@ -11,7 +11,7 @@ function LearnLayout({ children }) { return (
{children}
- +
); } diff --git a/client/src/images/powered_by_stripe.svg b/client/src/images/powered_by_stripe.svg deleted file mode 100644 index 43a92fa8ea..0000000000 --- a/client/src/images/powered_by_stripe.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - \ No newline at end of file diff --git a/client/src/pages/donate.js b/client/src/pages/donate.js index b79961aa66..8ae738e856 100644 --- a/client/src/pages/donate.js +++ b/client/src/pages/donate.js @@ -5,13 +5,16 @@ 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'; import DonateCompletion from '../components/Donation/components/DonateCompletion'; -import PoweredByStripe from '../components/icons/poweredByStripe'; +import DonateText from '../components/Donation/components/DonateText'; +import PoweredByStripe from '../components/Donation/components/poweredByStripe'; import './index.css'; @@ -24,16 +27,12 @@ const mapStateToProps = createSelector(userSelector, ({ email = '' }) => ({ email })); -// Stripe public key -const stripeKey = 'pk_live_E6Z6xPM8pEsJziHW905zpAvF'; - class IndexPage extends Component { constructor(...props) { super(...props); this.state = { stripe: null }; - this.handleStripeLoad = this.handleStripeLoad.bind(this); } componentDidMount() { @@ -41,7 +40,7 @@ class IndexPage extends Component { /* eslint-disable react/no-did-mount-set-state */ this.setState(state => ({ ...state, - stripe: window.Stripe(stripeKey) + stripe: window.Stripe(stripePublicKey) })); } else { document @@ -63,41 +62,47 @@ class IndexPage extends Component { console.info('stripe has loaded'); this.setState(state => ({ ...state, - stripe: window.Stripe(stripeKey) + stripe: window.Stripe(stripePublicKey) })); } renderCompletion(props) { return {}} {...props} />; } + render() { const { email = '' } = this.props; return ( -
- - + - -

Become a supporter

- - - - - - - - - - Other ways to donate. - - - - -
+ + +

+ Become a Supporter +

+ + + +
+ + + null} + renderCompletion={this.renderCompletion} + /> + + + + + +
+ ); } } diff --git a/client/src/redux/index.js b/client/src/redux/index.js index 7956348051..1e71d6dbf9 100644 --- a/client/src/redux/index.js +++ b/client/src/redux/index.js @@ -255,6 +255,10 @@ export const reducer = handleActions( ...state, showDonationModal: true }), + [types.closeDonationModal]: state => ({ + ...state, + showDonationModal: false + }), [types.showCert]: state => ({ ...state, showCert: {}, diff --git a/config/env.js b/config/env.js index 9d240e1e74..d258bbd70b 100644 --- a/config/env.js +++ b/config/env.js @@ -11,6 +11,7 @@ const { FORUM_LOCATION: forum, NEWS_LOCATION: news, LOCALE: locale, + STRIPE_PUBLIC: stripePublicKey, } = process.env; const locations = { @@ -20,4 +21,4 @@ const locations = { newsLocation: news }; -module.exports = Object.assign(locations, { locale }); +module.exports = Object.assign(locations, { locale, stripePublicKey });