fix(donate): implement donate page
This commit is contained in:
committed by
mrugesh
parent
5609a1303e
commit
e4590fed5c
@ -170,16 +170,6 @@ export default function donateBoot(app, done) {
|
||||
};
|
||||
|
||||
return Promise.resolve(user)
|
||||
.then(nonDonatingUser => {
|
||||
const { isDonating } = nonDonatingUser;
|
||||
if (isDonating) {
|
||||
throw {
|
||||
message: `User already has active donation(s).`,
|
||||
type: 'AlreadyDonatingError'
|
||||
};
|
||||
}
|
||||
return nonDonatingUser;
|
||||
})
|
||||
.then(createCustomer)
|
||||
.then(customer => {
|
||||
return duration === 'onetime'
|
||||
@ -194,10 +184,7 @@ export default function donateBoot(app, done) {
|
||||
})
|
||||
.then(createAsyncUserDonation)
|
||||
.catch(err => {
|
||||
if (
|
||||
err.type === 'StripeCardError' ||
|
||||
err.type === 'AlreadyDonatingError'
|
||||
) {
|
||||
if (err.type === 'StripeCardError') {
|
||||
return res.status(402).send({ error: err.message });
|
||||
}
|
||||
return res
|
||||
|
@ -7,7 +7,7 @@ import { createSelector } from 'reselect';
|
||||
import { Grid, Row, Col, Image, Button } from '@freecodecamp/react-bootstrap';
|
||||
import FreeCodeCampLogo from '../assets/icons/freeCodeCampLogo';
|
||||
// eslint-disable-next-line max-len
|
||||
import MinimalDonateForm from '../components/Donation/components/MinimalDonateForm';
|
||||
import MinimalDonateForm from '../components/Donation/MinimalDonateForm';
|
||||
|
||||
import {
|
||||
showCertSelector,
|
||||
|
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import { Alert, Button } from '@freecodecamp/react-bootstrap';
|
||||
import Spinner from 'react-spinkit';
|
||||
|
||||
import '../Donation.css';
|
||||
import './Donation.css';
|
||||
|
||||
const propTypes = {
|
||||
error: PropTypes.string,
|
@ -18,19 +18,17 @@ import {
|
||||
durationsConfig,
|
||||
defaultAmount,
|
||||
defaultStateConfig
|
||||
} from '../../../../../config/donation-settings';
|
||||
} from '../../../../config/donation-settings';
|
||||
import { apiLocation } from '../../../../config/env.json';
|
||||
import Spacer from '../../helpers/Spacer';
|
||||
import Spacer from '../helpers/Spacer';
|
||||
import DonateFormChildViewForHOC from './DonateFormChildViewForHOC';
|
||||
import {
|
||||
userSelector,
|
||||
isSignedInSelector,
|
||||
signInLoadingSelector,
|
||||
hardGoTo as navigate
|
||||
} from '../../../redux';
|
||||
} from '../../redux';
|
||||
|
||||
import '../Donation.css';
|
||||
import DonateCompletion from './DonateCompletion.js';
|
||||
import './Donation.css';
|
||||
|
||||
const numToCommas = num =>
|
||||
num.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
|
||||
@ -46,11 +44,9 @@ const propTypes = {
|
||||
};
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
userSelector,
|
||||
signInLoadingSelector,
|
||||
isSignedInSelector,
|
||||
({ isDonating }, showLoading, isSignedIn) => ({
|
||||
isDonating,
|
||||
(showLoading, isSignedIn) => ({
|
||||
isSignedIn,
|
||||
showLoading
|
||||
})
|
||||
@ -74,8 +70,7 @@ class DonateForm extends Component {
|
||||
|
||||
this.state = {
|
||||
...defaultStateConfig,
|
||||
processing: false,
|
||||
isDonating: this.props.isDonating
|
||||
processing: false
|
||||
};
|
||||
|
||||
this.getActiveDonationAmount = this.getActiveDonationAmount.bind(this);
|
||||
@ -222,17 +217,7 @@ class DonateForm extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isSignedIn, navigate, showLoading, isDonating } = this.props;
|
||||
|
||||
if (isDonating) {
|
||||
return (
|
||||
<Row>
|
||||
<Col sm={10} smOffset={1} xs={12}>
|
||||
<DonateCompletion success={true} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
const { isSignedIn, navigate, showLoading } = this.props;
|
||||
|
||||
return (
|
||||
<Row>
|
@ -15,8 +15,8 @@ import { injectStripe } from 'react-stripe-elements';
|
||||
|
||||
import StripeCardForm from './StripeCardForm';
|
||||
import DonateCompletion from './DonateCompletion';
|
||||
import { postChargeStripe } from '../../../utils/ajax';
|
||||
import { userSelector } from '../../../redux';
|
||||
import { postChargeStripe } from '../../utils/ajax';
|
||||
import { userSelector } from '../../redux';
|
||||
|
||||
const propTypes = {
|
||||
showCloseBtn: PropTypes.func,
|
39
client/src/components/Donation/DonateText.js
Normal file
39
client/src/components/Donation/DonateText.js
Normal file
@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import { Row, Col } from '@freecodecamp/react-bootstrap';
|
||||
|
||||
const DonateText = () => {
|
||||
return (
|
||||
<Row className='donate-text'>
|
||||
<Col sm={10} smOffset={1} xs={12}>
|
||||
<p>freeCodeCamp is a highly efficient education nonprofit.</p>
|
||||
<p>
|
||||
In 2019 alone, we provided 18 million hours of free education to
|
||||
people around the world.
|
||||
</p>
|
||||
<p>
|
||||
Since freeCodeCamp's total budget is only $373,000, that means every
|
||||
dollar you donate to freeCodeCamp translates into 50 hours worth of
|
||||
technology education.
|
||||
</p>
|
||||
<p>
|
||||
When you donate to freeCodeCamp, you help people learn new skills and
|
||||
provide for their families.
|
||||
</p>
|
||||
<p>
|
||||
You also help us create new resources for you to use to expand your
|
||||
own technology skills.
|
||||
</p>
|
||||
<hr />
|
||||
<h4>
|
||||
<b>Need help with your existing or past donations?</b>
|
||||
</h4>
|
||||
<p>
|
||||
Send an email to team@freeCodeCamp.org with a copy of your donation
|
||||
receipt and we will be happy to resolve your query.
|
||||
</p>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
DonateText.displayName = 'DonateText';
|
||||
export default DonateText;
|
@ -5,22 +5,22 @@ import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { Modal, Button, Col, Row } from '@freecodecamp/react-bootstrap';
|
||||
import { Spacer } from '../../../components/helpers';
|
||||
import { blockNameify } from '../../../../utils/blockNameify';
|
||||
import Heart from '../../../assets/icons/Heart';
|
||||
import Cup from '../../../assets/icons/Cup';
|
||||
import { Spacer } from '../helpers';
|
||||
import { blockNameify } from '../../../utils/blockNameify';
|
||||
import Heart from '../../assets/icons/Heart';
|
||||
import Cup from '../../assets/icons/Cup';
|
||||
import MinimalDonateForm from './MinimalDonateForm';
|
||||
|
||||
import ga from '../../../analytics';
|
||||
import ga from '../../analytics';
|
||||
import {
|
||||
closeDonationModal,
|
||||
isDonationModalOpenSelector,
|
||||
isBlockDonationModalSelector
|
||||
} from '../../../redux';
|
||||
} from '../../redux';
|
||||
|
||||
import { challengeMetaSelector } from '../../../templates/Challenges/redux';
|
||||
import { challengeMetaSelector } from '../../templates/Challenges/redux';
|
||||
|
||||
import '../Donation.css';
|
||||
import './Donation.css';
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
isDonationModalOpenSelector,
|
@ -1,5 +1,3 @@
|
||||
/* eslint-disable react/sort-prop-types */
|
||||
/* eslint-disable react/jsx-sort-props */
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
@ -10,23 +8,19 @@ import { StripeProvider, Elements } from 'react-stripe-elements';
|
||||
import {
|
||||
amountsConfig,
|
||||
durationsConfig,
|
||||
defaultStateConfig
|
||||
} from '../../../../../config/donation-settings';
|
||||
modalDefaultStateConfig
|
||||
} from '../../../../config/donation-settings';
|
||||
import { stripePublicKey } from '../../../../config/env.json';
|
||||
import { stripeScriptLoader } from '../../utils/scriptLoaders';
|
||||
import DonateFormChildViewForHOC from './DonateFormChildViewForHOC';
|
||||
import { userSelector } from '../../../redux';
|
||||
import { userSelector } from '../../redux';
|
||||
|
||||
import '../Donation.css';
|
||||
import DonateCompletion from './DonateCompletion.js';
|
||||
import { stripePublicKey } from '../../../../../config/env.json';
|
||||
import { stripeScriptLoader } from '../../../utils/scriptLoaders';
|
||||
|
||||
const numToCommas = num =>
|
||||
num.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
|
||||
import './Donation.css';
|
||||
|
||||
const propTypes = {
|
||||
showCloseBtn: PropTypes.func,
|
||||
defaultTheme: PropTypes.string,
|
||||
isDonating: PropTypes.bool,
|
||||
showCloseBtn: PropTypes.func,
|
||||
stripe: PropTypes.shape({
|
||||
createToken: PropTypes.func.isRequired
|
||||
})
|
||||
@ -39,7 +33,7 @@ const mapStateToProps = createSelector(
|
||||
})
|
||||
);
|
||||
|
||||
class ModalDonateForm extends Component {
|
||||
class MinimalDonateForm extends Component {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
@ -47,13 +41,11 @@ class ModalDonateForm extends Component {
|
||||
this.amounts = amountsConfig;
|
||||
|
||||
this.state = {
|
||||
...defaultStateConfig,
|
||||
...modalDefaultStateConfig,
|
||||
isDonating: this.props.isDonating,
|
||||
stripe: null
|
||||
};
|
||||
this.handleSelectPaymentType = this.handleSelectPaymentType.bind(this);
|
||||
this.handleStripeLoad = this.handleStripeLoad.bind(this);
|
||||
this.getDonationButtonLabel = this.getDonationButtonLabel.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -85,91 +77,36 @@ class ModalDonateForm extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleSelectPaymentType(e) {
|
||||
this.setState({
|
||||
paymentType: e.currentTarget.value
|
||||
});
|
||||
}
|
||||
|
||||
getFormatedAmountLabel(amount) {
|
||||
return `$${numToCommas(amount / 100)}`;
|
||||
}
|
||||
|
||||
getDonationButtonLabel() {
|
||||
const { donationAmount, donationDuration } = this.state;
|
||||
let donationBtnLabel = `Confirm your donation`;
|
||||
if (donationDuration === 'onetime') {
|
||||
donationBtnLabel = `Confirm your one-time donation of ${this.getFormatedAmountLabel(
|
||||
donationAmount
|
||||
)}`;
|
||||
} else {
|
||||
donationBtnLabel = `Confirm your donation of ${this.getFormatedAmountLabel(
|
||||
donationAmount
|
||||
)} ${donationDuration === 'month' ? 'per month' : 'per year'}`;
|
||||
}
|
||||
return donationBtnLabel;
|
||||
}
|
||||
|
||||
renderDonationOptions() {
|
||||
const {
|
||||
donationAmount,
|
||||
donationDuration,
|
||||
paymentType,
|
||||
stripe
|
||||
} = this.state;
|
||||
|
||||
render() {
|
||||
const { donationAmount, donationDuration, stripe } = this.state;
|
||||
const { showCloseBtn, defaultTheme } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{paymentType === 'Card' ? (
|
||||
<Row>
|
||||
<Col sm={10} smOffset={1} xs={12}>
|
||||
<StripeProvider stripe={stripe}>
|
||||
<Elements>
|
||||
<DonateFormChildViewForHOC
|
||||
showCloseBtn={showCloseBtn}
|
||||
defaultTheme={defaultTheme}
|
||||
donationAmount={donationAmount}
|
||||
donationDuration={donationDuration}
|
||||
getDonationButtonLabel={this.getDonationButtonLabel}
|
||||
getDonationButtonLabel={() =>
|
||||
`Confirm your donation of $5 per month`
|
||||
}
|
||||
showCloseBtn={showCloseBtn}
|
||||
/>
|
||||
</Elements>
|
||||
</StripeProvider>
|
||||
) : (
|
||||
<p>
|
||||
PayPal is currently unavailable. Please use a Credit/Debit card
|
||||
instead.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isDonating } = this.props;
|
||||
|
||||
if (isDonating) {
|
||||
return (
|
||||
<Row>
|
||||
<Col sm={10} smOffset={1} xs={12}>
|
||||
<DonateCompletion success={true} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Row>
|
||||
<Col sm={10} smOffset={1} xs={12}>
|
||||
{this.renderDonationOptions()}
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ModalDonateForm.displayName = 'ModalDonateForm';
|
||||
ModalDonateForm.propTypes = propTypes;
|
||||
MinimalDonateForm.displayName = 'MinimalDonateForm';
|
||||
MinimalDonateForm.propTypes = propTypes;
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
null
|
||||
)(ModalDonateForm);
|
||||
)(MinimalDonateForm);
|
@ -1,66 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Spacer, Link } from '../../../components/helpers';
|
||||
|
||||
const DonateText = () => {
|
||||
return (
|
||||
<div className='donate-text'>
|
||||
<Spacer />
|
||||
<h3>How to donate to freeCodeCamp.org</h3>
|
||||
|
||||
<p>freeCodeCamp is a tax-exempt 501(c)(3) public charity.</p>
|
||||
|
||||
<p>
|
||||
We get almost all of our budget from our supporters, who donate $5 per
|
||||
month to our nonprofit.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To become a supporter, just{' '}
|
||||
<Link to='/learn'>start going through the curriculum</Link> and you will
|
||||
see a prompt to donate.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
If you want to make a larger one-time donation, set up employer
|
||||
matching, or support us in other ways, email team@freecodecamp.org and
|
||||
we can help make that happen.
|
||||
</p>
|
||||
<Spacer />
|
||||
|
||||
<h3> How does freeCodeCamp use these donations?</h3>
|
||||
|
||||
<p>
|
||||
100% of donations go to pay for servers, and to pay teachers and
|
||||
developers who help build our learning resources.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
We earned the 2019 Platinum Seal of Transparency from Guidestar.org. You
|
||||
can view all our nonprofit's details and download our accounting
|
||||
documents{' '}
|
||||
<Link to='https://www.guidestar.org/profile/82-0779546'>there.</Link>
|
||||
</p>
|
||||
<Spacer />
|
||||
|
||||
<h3> How do I stop my monthly recurring donation.</h3>
|
||||
|
||||
<p>
|
||||
Easy. Just forward a donation receipt to team@freecodecamp.org and we'll
|
||||
stop it.
|
||||
</p>
|
||||
|
||||
<Spacer />
|
||||
<h3>How do I restart my monthly recurring donation?</h3>
|
||||
|
||||
<p>
|
||||
Email one of your old donation receipts to team@freecodecamp.org and
|
||||
we'll restart it for you.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
DonateText.displayName = 'DonateText';
|
||||
|
||||
export default DonateText;
|
@ -14,7 +14,7 @@ import { StripeProvider, Elements } from 'react-stripe-elements';
|
||||
import { Spacer } from '../helpers';
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
import DonateFormChildViewForHOC from '../Donation/components/DonateFormChildViewForHOC';
|
||||
import DonateFormChildViewForHOC from '../Donation/DonateFormChildViewForHOC';
|
||||
|
||||
import './YearEndGift.css';
|
||||
import '../Donation/Donation.css';
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
tryToShowDonationModal
|
||||
} from '../../redux';
|
||||
import createRedirect from '../../components/createRedirect';
|
||||
import DonateModal from '../Donation/components/DonationModal';
|
||||
import DonateModal from '../Donation/DonationModal';
|
||||
|
||||
import 'prismjs/themes/prism.css';
|
||||
import './prism.css';
|
||||
|
@ -1,29 +1,108 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import Helmet from 'react-helmet';
|
||||
import { Grid } from '@freecodecamp/react-bootstrap';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { Grid, Row, Col } from '@freecodecamp/react-bootstrap';
|
||||
|
||||
import { Spacer, FullWidthRow } from '../components/helpers';
|
||||
import DonateText from '../components/Donation/components/DonateText';
|
||||
import { stripePublicKey } from '../../config/env.json';
|
||||
import { Spacer, Loader } from '../components/helpers';
|
||||
import DonateForm from '../components/Donation/DonateForm';
|
||||
import DonateText from '../components/Donation/DonateText';
|
||||
import { signInLoadingSelector, userSelector } from '../redux';
|
||||
import { stripeScriptLoader } from '../utils/scriptLoaders';
|
||||
|
||||
const propTypes = {
|
||||
isDonating: PropTypes.bool,
|
||||
showLoading: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
userSelector,
|
||||
signInLoadingSelector,
|
||||
({ isDonating }, showLoading) => ({
|
||||
isDonating,
|
||||
showLoading
|
||||
})
|
||||
);
|
||||
|
||||
export class DonatePage extends Component {
|
||||
constructor(...props) {
|
||||
super(...props);
|
||||
this.state = {
|
||||
stripe: null,
|
||||
enableSettings: false
|
||||
};
|
||||
|
||||
this.handleStripeLoad = this.handleStripeLoad.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (window.Stripe) {
|
||||
this.handleStripeLoad();
|
||||
} else if (document.querySelector('#stripe-js')) {
|
||||
document
|
||||
.querySelector('#stripe-js')
|
||||
.addEventListener('load', this.handleStripeLoad);
|
||||
} else {
|
||||
stripeScriptLoader(this.handleStripeLoad);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const stripeMountPoint = document.querySelector('#stripe-js');
|
||||
if (stripeMountPoint) {
|
||||
stripeMountPoint.removeEventListener('load', this.handleStripeLoad);
|
||||
}
|
||||
}
|
||||
|
||||
handleStripeLoad() {
|
||||
// Create Stripe instance once Stripe.js loads
|
||||
console.info('stripe has loaded');
|
||||
this.setState(state => ({
|
||||
...state,
|
||||
stripe: window.Stripe(stripePublicKey)
|
||||
}));
|
||||
}
|
||||
|
||||
render() {
|
||||
const { stripe } = this.state;
|
||||
const { showLoading } = this.props;
|
||||
|
||||
if (showLoading) {
|
||||
return <Loader fullScreen={true} />;
|
||||
}
|
||||
|
||||
function DonatePage() {
|
||||
return (
|
||||
<Fragment>
|
||||
<Helmet title='Support our nonprofit | freeCodeCamp.org' />
|
||||
<Grid>
|
||||
<main>
|
||||
<Spacer />
|
||||
<FullWidthRow>
|
||||
<Row>
|
||||
<Col sm={10} smOffset={1} xs={12}>
|
||||
<h1 className='text-center'>Become a Supporter</h1>
|
||||
<Spacer />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col md={6}>
|
||||
<DonateForm
|
||||
enableDonationSettingsPage={this.enableDonationSettingsPage}
|
||||
stripe={stripe}
|
||||
/>
|
||||
</Col>
|
||||
<Col md={6}>
|
||||
<DonateText />
|
||||
</FullWidthRow>
|
||||
</Col>
|
||||
</Row>
|
||||
<Spacer />
|
||||
<Spacer />
|
||||
</main>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DonatePage.displayName = 'DonatePage';
|
||||
DonatePage.propTypes = propTypes;
|
||||
|
||||
export default DonatePage;
|
||||
export default connect(mapStateToProps)(DonatePage);
|
||||
|
@ -11,7 +11,7 @@ const amountsConfig = {
|
||||
};
|
||||
const defaultAmount = {
|
||||
year: 25000,
|
||||
month: 500,
|
||||
month: 3500,
|
||||
onetime: 25000
|
||||
};
|
||||
const defaultStateConfig = {
|
||||
@ -19,6 +19,11 @@ const defaultStateConfig = {
|
||||
donationDuration: 'month',
|
||||
paymentType: 'Card'
|
||||
};
|
||||
const modalDefaultStateConfig = {
|
||||
donationAmount: 500,
|
||||
donationDuration: 'month',
|
||||
paymentType: 'Card'
|
||||
};
|
||||
|
||||
// Configuration for server side
|
||||
const durationKeysConfig = ['year', 'month', 'onetime'];
|
||||
@ -41,5 +46,6 @@ module.exports = {
|
||||
defaultStateConfig,
|
||||
durationKeysConfig,
|
||||
donationOneTimeConfig,
|
||||
donationSubscriptionConfig
|
||||
donationSubscriptionConfig,
|
||||
modalDefaultStateConfig
|
||||
};
|
||||
|
Reference in New Issue
Block a user