feat(donate): two col layout checkout page

This commit is contained in:
Mrugesh Mohapatra
2019-11-05 19:23:36 +05:30
committed by mrugesh
parent 7a4e84d163
commit 4cd5542489
8 changed files with 353 additions and 547 deletions

View File

@ -19,7 +19,6 @@
.donation-form {
display: flex;
flex-direction: column;
width: 80%;
justify-content: center;
margin: 0 auto;
}

View File

@ -19,7 +19,7 @@ function DonateCompletion({ processing, reset, success, error = null }) {
? 'We are processing your donation.'
: success
? 'Your donation was successful.'
: 'Something went wrong with your donation';
: 'Something went wrong with your donation.';
return (
<Alert bsStyle={style} className='donation-completion'>
<h4>
@ -35,7 +35,16 @@ function DonateCompletion({ processing, reset, success, error = null }) {
/>
)}
{success && (
<p>Thank you for supporting the freeCodeCamp.org community.</p>
<div>
<p>
You can update your supporter status at any time from your account
settings.
</p>
<p>
Thank you for supporting free technology education for people all
over the world.
</p>
</div>
)}
{error && <p>{error}</p>}
</div>

View File

@ -2,227 +2,116 @@ 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 } from '@freecodecamp/react-bootstrap';
import { StripeProvider, Elements } from 'react-stripe-elements';
import { apiLocation } from '../../../../config/env.json';
import DonateFormChildViewForHOC from './DonateFormChildViewForHOC';
import {
Button,
ControlLabel,
Form,
FormControl,
FormGroup,
Row,
Col
} from '@freecodecamp/react-bootstrap';
import { injectStripe } from 'react-stripe-elements';
userSelector,
isSignedInSelector,
signInLoadingSelector,
hardGoTo as navigate
} from '../../../redux';
import Spacer from '../../../components/helpers/Spacer';
import StripeCardForm from './StripeCardForm';
import DonateCompletion from './DonateCompletion';
import { postChargeStripe } from '../../../utils/ajax';
import { userSelector, isSignedInSelector } from '../../../redux';
import '../Donation.css';
const propTypes = {
email: PropTypes.string,
isSignedIn: PropTypes.bool,
navigate: PropTypes.func.isRequired,
showLoading: PropTypes.bool.isRequired,
stripe: PropTypes.shape({
createToken: PropTypes.func.isRequired
}),
theme: PropTypes.string
};
const initialState = {
donationAmount: 500,
donationState: {
processing: false,
success: false,
error: ''
}
})
};
const mapStateToProps = createSelector(
userSelector,
signInLoadingSelector,
isSignedInSelector,
({ email, theme }, isSignedIn) => ({ email, theme, isSignedIn })
({ email, theme }, showLoading, isSignedIn) => ({
email,
theme,
showLoading,
isSignedIn
})
);
const mapDispatchToProps = {
navigate
};
const createOnClick = navigate => e => {
e.preventDefault();
return navigate(`${apiLocation}/signin?returnTo=donate`);
};
class DonateForm extends Component {
constructor(...args) {
super(...args);
this.state = {
...initialState,
email: null,
isFormValid: false
donationAmount: 500
};
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.buttonSingleAmounts = [2500, 5000, 10000, 25000];
this.buttonMonthlyAmounts = [500, 1000, 2000];
this.buttonAnnualAmounts = [6000, 10000, 25000, 50000];
this.isActive = this.isActive.bind(this);
this.renderAmountButtons = this.renderAmountButtons.bind(this);
}
getUserEmail() {
const { email: stateEmail } = this.state;
const { email: propsEmail } = this.props;
return stateEmail || propsEmail || '';
isActive(amount) {
return this.state.donationAmount === amount;
}
getValidationState(isFormValid) {
this.setState(state => ({
...state,
isFormValid
}));
}
handleEmailChange(e) {
const newValue = e.target.value;
return this.setState(state => ({
...state,
email: newValue
}));
}
handleSubmit(e) {
e.preventDefault();
const email = this.getUserEmail();
if (!email || !isEmail(email)) {
return this.setState(state => ({
...state,
donationState: {
...state.donationState,
error:
'We need a valid email address to which we can send your' +
' donation tax receipt.'
}
}));
}
return this.props.stripe.createToken({ email }).then(({ error, token }) => {
if (error) {
return this.setState(state => ({
...state,
donationState: {
...state.donationState,
error:
'Something went wrong processing your donation. Your card' +
' has not been charged.'
}
}));
}
return this.postDonation(token);
});
}
postDonation(token) {
const { donationAmount: amount } = this.state;
const { isSignedIn } = this.props;
this.setState(state => ({
...state,
donationState: {
...state.donationState,
processing: true
}
}));
return postChargeStripe(isSignedIn, {
token,
amount
})
.then(response => {
const data = response && response.data;
this.setState(state => ({
...state,
donationState: {
...state.donationState,
processing: false,
success: true,
error: data.error ? data.error : null
}
}));
})
.catch(error => {
const data =
error.response && error.response.data
? error.response.data
: {
error:
'Something is not right. Please contact team@freecodecamp.org'
};
this.setState(state => ({
...state,
donationState: {
...state.donationState,
processing: false,
success: false,
error: data.error
}
}));
});
}
resetDonation() {
return this.setState({ ...initialState });
}
renderCompletion(props) {
return <DonateCompletion {...props} />;
}
renderDonateForm() {
const { isFormValid } = this.state;
const { theme } = this.props;
return (
<Row>
<Col sm={10} smOffset={1} xs={12}>
<Form className='donation-form' onSubmit={this.handleSubmit}>
<FormGroup className='donation-email-container'>
<ControlLabel>
Email (we'll send you a tax-deductible donation receipt):
</ControlLabel>
<FormControl
onChange={this.handleEmailChange}
placeholder='me@example.com'
required={true}
type='text'
value={this.getUserEmail()}
/>
</FormGroup>
<StripeCardForm
getValidationState={this.getValidationState}
theme={theme}
/>
<Button
block={true}
bsStyle='primary'
disabled={!isFormValid}
id='confirm-donation-btn'
type='submit'
>
Confirm your donation of $5 / month
</Button>
<Spacer />
</Form>
</Col>
</Row>
);
renderAmountButtons() {
return this.buttonAnnualAmounts.map(amount => (
<Button
className={`amount-value ${this.isActive(amount) ? 'active' : ''}`}
href=''
id={amount}
key={'amount-' + amount}
onClick={this.handleAmountClick}
tabIndex='-1'
>
{`$${amount / 100}`}
</Button>
));
}
render() {
const {
donationState: { processing, success, error }
} = this.state;
if (processing || success || error) {
return this.renderCompletion({
processing,
success,
error,
reset: this.resetDonation
});
const { isSignedIn, navigate, showLoading, stripe } = this.props;
if (!showLoading && !isSignedIn) {
return (
<div>
<Button
bsStyle='default'
className='btn btn-block'
onClick={createOnClick(navigate)}
>
Become a supporter
</Button>
</div>
);
}
return this.renderDonateForm();
return (
<div>
<StripeProvider stripe={stripe}>
<Elements>
<DonateFormChildViewForHOC />
</Elements>
</StripeProvider>
</div>
);
}
}
DonateForm.displayName = 'DonateForm';
DonateForm.propTypes = propTypes;
export default injectStripe(connect(mapStateToProps)(DonateForm));
export default connect(
mapStateToProps,
mapDispatchToProps
)(DonateForm);

View File

@ -0,0 +1,224 @@
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,
ControlLabel,
Form,
FormControl,
FormGroup
} from '@freecodecamp/react-bootstrap';
import { injectStripe } from 'react-stripe-elements';
import Spacer from '../../helpers/Spacer';
import StripeCardForm from './StripeCardForm';
import DonateCompletion from './DonateCompletion';
import { postChargeStripe } from '../../../utils/ajax';
import { userSelector, isSignedInSelector } from '../../../redux';
const propTypes = {
email: PropTypes.string,
isSignedIn: PropTypes.bool,
stripe: PropTypes.shape({
createToken: PropTypes.func.isRequired
}),
theme: PropTypes.string
};
const initialState = {
donationAmount: 500,
donationState: {
processing: false,
success: false,
error: ''
}
};
const mapStateToProps = createSelector(
userSelector,
isSignedInSelector,
({ email, theme }, isSignedIn) => ({ email, theme, isSignedIn })
);
class DonateFormChildViewForHOC extends Component {
constructor(...args) {
super(...args);
this.state = {
...initialState,
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);
}
getUserEmail() {
const { email: stateEmail } = this.state;
const { email: propsEmail } = this.props;
return stateEmail || propsEmail || '';
}
getValidationState(isFormValid) {
this.setState(state => ({
...state,
isFormValid
}));
}
handleEmailChange(e) {
const newValue = e.target.value;
return this.setState(state => ({
...state,
email: newValue
}));
}
handleSubmit(e) {
e.preventDefault();
const email = this.getUserEmail();
if (!email || !isEmail(email)) {
return this.setState(state => ({
...state,
donationState: {
...state.donationState,
error:
'We need a valid email address to which we can send your' +
' donation tax receipt.'
}
}));
}
return this.props.stripe.createToken({ email }).then(({ error, token }) => {
if (error) {
return this.setState(state => ({
...state,
donationState: {
...state.donationState,
error:
'Something went wrong processing your donation. Your card' +
' has not been charged.'
}
}));
}
return this.postDonation(token);
});
}
postDonation(token) {
const { donationAmount: amount } = this.state;
const { isSignedIn } = this.props;
this.setState(state => ({
...state,
donationState: {
...state.donationState,
processing: true
}
}));
return postChargeStripe(isSignedIn, {
token,
amount
})
.then(response => {
const data = response && response.data;
this.setState(state => ({
...state,
donationState: {
...state.donationState,
processing: false,
success: true,
error: data.error ? data.error : null
}
}));
})
.catch(error => {
const data =
error.response && error.response.data
? error.response.data
: {
error:
'Something is not right. Please contact team@freecodecamp.org'
};
this.setState(state => ({
...state,
donationState: {
...state.donationState,
processing: false,
success: false,
error: data.error
}
}));
});
}
resetDonation() {
return this.setState({ ...initialState });
}
renderCompletion(props) {
return <DonateCompletion {...props} />;
}
renderDonateForm() {
const { isFormValid } = this.state;
const { theme } = this.props;
return (
<Form className='donation-form' onSubmit={this.handleSubmit}>
<FormGroup className='donation-email-container'>
<ControlLabel>
Email (we'll send you a tax-deductible donation receipt):
</ControlLabel>
<FormControl
onChange={this.handleEmailChange}
placeholder='me@example.com'
required={true}
type='text'
value={this.getUserEmail()}
/>
</FormGroup>
<StripeCardForm
getValidationState={this.getValidationState}
theme={theme}
/>
<Button
block={true}
bsStyle='primary'
disabled={!isFormValid}
id='confirm-donation-btn'
type='submit'
>
Confirm your donation of $5 / month
</Button>
<Spacer />
</Form>
);
}
render() {
const {
donationState: { processing, success, error }
} = this.state;
if (processing || success || error) {
return this.renderCompletion({
processing,
success,
error,
reset: this.resetDonation
});
}
return this.renderDonateForm();
}
}
DonateFormChildViewForHOC.displayName = 'DonateFormChildViewForHOC';
DonateFormChildViewForHOC.propTypes = propTypes;
export default injectStripe(
connect(mapStateToProps)(DonateFormChildViewForHOC)
);

View File

@ -1,261 +0,0 @@
import React, { Component, Fragment } from 'react';
import { Grid, Col, Row } from '@freecodecamp/react-bootstrap';
import ReactGA from '../../../analytics/index.js';
import { Link, Spacer } from '../../helpers';
const paypalMonthlyDonations = [
{
eventLabel: 'paypal',
eventValue: 5,
defaultValueHash: 'KTAXU8B4MYAT8',
defaultValue: 'Donate $5 each month'
},
{
eventLabel: 'paypal',
eventValue: 10,
defaultValueHash: 'BYEBQEHS5LHNC',
defaultValue: 'Donate $10 each month'
},
{
eventLabel: 'paypal',
eventValue: 35,
defaultValueHash: '57ZPMKJ8G893Y',
defaultValue: 'Donate $35 each month'
},
{
eventLabel: 'paypal',
eventValue: 50,
defaultValueHash: '2ZVYTHK6Q7AFW',
defaultValue: 'Donate $50 each month'
},
{
eventLabel: 'paypal',
eventValue: 100,
defaultValueHash: 'C7PUT3LMJHKK2',
defaultValue: 'Donate $100 each month'
},
{
eventLabel: 'paypal',
eventValue: 250,
defaultValueHash: '69JGTY4DHSTEN',
defaultValue: 'Donate $250 each month'
}
];
const paypalOneTimeDonation = {
eventLabel: 'paypal one time donation',
eventValue: 0,
defaultValueHash: 'B256JC6ZCWD3J',
defaultValue: 'Make a one-time donation'
};
class DonateOther extends Component {
renderForm(item) {
return (
<form
action='https://www.paypal.com/cgi-bin/webscr'
key={item.defaultValueHash}
method='post'
onSubmit={() =>
ReactGA.event({
category: 'donation',
action: 'click',
label: item.eventLabel,
value: item.eventValue
})
}
target='_blank'
>
<input name='cmd' type='hidden' value='_s-xclick' />{' '}
<input
name='hosted_button_id'
type='hidden'
value={item.defaultValueHash}
/>{' '}
<input
className='btn btn-cta signup-btn btn-block'
name='submit'
type='submit'
value={item.defaultValue}
/>
</form>
);
}
/* eslint-disable max-len */
render() {
return (
<Fragment>
<Spacer />
<Grid className='donate-other'>
<Row>
<Col lg={8} lgOffset={2} sm={10} smOffset={1} xs={12}>
<h2 className='text-center'>
Other ways you can support our nonprofit
</h2>
<p>
freeCodeCamp is a small donor-supported 501(c)(3) public
charity. We are tax-exempt, so you can deduct donations you make
to our nonprofit from your taxes. You can{' '}
<a href='https://s3.amazonaws.com/freecodecamp/Free+Code+Camp+Inc+IRS+Determination+Letter.pdf'>
download our IRS Determination Letter here
</a>
.
</p>
<hr />
<h3>Set up a monthly donation using PayPal</h3>
<p>
You can set up a monthly donation to freeCodeCamp by clicking
one of the links below and following the instructions on PayPal.
You can easily stop your donations at any time in the future.
</p>
<Row>
<Col
lg={6}
lgOffset={3}
md={6}
mdOffset={3}
sm={8}
smOffset={2}
xs={12}
>
{paypalMonthlyDonations.map(item => {
return this.renderForm(item);
})}
</Col>
</Row>
<hr />
<h3>Make a one-time donation using PayPal</h3>
<p>
You can make a one-time monthly donation to freeCodeCamp for any
amount of money by clicking one of the links below and following
the instructions on PayPal:
</p>
<Row>
<Col
lg={6}
lgOffset={3}
md={6}
mdOffset={3}
sm={8}
smOffset={2}
xs={12}
>
{this.renderForm(paypalOneTimeDonation)}
</Col>
</Row>
<hr />
<h3>Get your employer to match your donation</h3>
<p>
Many freeCodeCamp supporters are able to get their employers to
match their donations to freeCodeCamp. Our Tax-exempt number
(EIN) is 82-0779546. If we can help you with setting this up,
please email{' '}
<a href='mailto:team@freecodecamp.org'>team@freecodecamp.org</a>
.
</p>
<hr />
<h3>Donate through a payroll deduction</h3>
<p>
In the US and Canada, some employers have a convenient way to
give to freeCodeCamp through a payroll deduction. Ask your
employer if they can do this, and have them send any necessary
paperwork to:
</p>
<p>
Free Code Camp, Inc.
<br />
7800 W Hefner Rd, PO BOX 721024
<br />
Oklahoma City, Oklahoma 73172
<br />
United States
</p>
<hr />
<h3>Donate cryptocurrency to freeCodeCamp</h3>
<p>
Below are our wallets where we can receive cryptocurrency
donations.
</p>
<h4>Make a one-time Bitcoin donation</h4>
<p>
Our Bitcoin wallet is{' '}
<code className='wallet'>
12skYi7aMCjDUdrVdoB3JjZ77ug8gxJfbL
</code>
</p>
<h4>Make a one-time Ethereum donation</h4>
<p>
Our Ethereum wallet is{' '}
<code className='wallet'>
0x0ADbEf2471416BD8732cf0f3944294eE393CcAF5
</code>
</p>
<h4>Make a one-time Litecoin donation</h4>
<p>
Our Litecoin wallet is{' '}
<code className='wallet'>
LKu8UG8Z1nbTxnq9Do96PsC3FwbNtycf3X
</code>
</p>
<h4>Make a one-time Bitcoin Cash donation</h4>
<p>
Our Bitcoin Cash wallet is{' '}
<code className='wallet'>
1EBxPEJWrGZWxe2UayyAsnd5VsRg5H9xfu
</code>
</p>
<hr />
<h3>Donate to freeCodeCamp by mailing us a check</h3>
<p>Our mailing address is:</p>
<p>
Free Code Camp, Inc.
<br />
7800 W Hefner Rd, PO BOX 721024
<br />
Oklahoma City, Oklahoma 73172
<br />
United States
</p>
<hr />
<h3>Donate Stock to freeCodeCamp</h3>
<p>
If you want to donate stocks to freeCodeCamp, please email us at{' '}
<a href='mailto:team@freecodecamp.org'>team@freecodecamp.org</a>
.
</p>
<hr />
<h3>Legacy Gift</h3>
<p>
You can help future generations of learners by listing
freeCodeCamp in your will or living trust. If you're interested
in doing this, email Quincy directly and we can discuss this:{' '}
<a href='mailto:quincy@freecodecamp.org'>
quincy@freecodecamp.org
</a>
.
</p>
<hr />
<h3>
Thank you for supporting our nonprofit and the freeCodeCamp
community.
</h3>
<Spacer />
<div className='text-center'>
<Link to='/donate'>Donate using a Credit or Debit card</Link>
</div>
<Spacer />
</Col>
</Row>
</Grid>
</Fragment>
);
}
/* eslint-enable max-len */
}
DonateOther.displayName = 'DonateOther';
export default DonateOther;

View File

@ -1,41 +1,30 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { Row, Col } from '@freecodecamp/react-bootstrap';
import { activeDonationsSelector } from '../../../redux';
const propTypes = {
activeDonations: PropTypes.number
};
const mapStateToProps = createSelector(
activeDonationsSelector,
activeDonations => ({ activeDonations })
);
const DonateText = ({ activeDonations }) => {
const donationsLocale = activeDonations.toLocaleString();
const DonateText = () => {
return (
<Row>
<Col sm={10} smOffset={1} xs={12}>
<p>
freeCodeCamp.org is a tiny nonprofit that's helping millions of people
learn to code for free.
</p>
<p>
Join <strong>{donationsLocale}</strong> supporters.
</p>
<p>
Your $5 / month donation will help keep tech education free and open.
</p>
</Col>
</Row>
<>
<p>freeCodeCamp is a highly efficient education nonprofit.</p>
<p>
In 2019 alone, we provided 1,100,000,000 minutes 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>
</>
);
};
DonateText.displayName = 'DonateText';
DonateText.propTypes = propTypes;
export default connect(mapStateToProps)(DonateText);
export default DonateText;

View File

@ -7,8 +7,6 @@ import {
} from 'react-stripe-elements';
import { ControlLabel, FormGroup } from '@freecodecamp/react-bootstrap';
import '../Donation.css';
const propTypes = {
getValidationState: PropTypes.func.isRequired,
theme: PropTypes.string

View File

@ -1,40 +1,25 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import Helmet from 'react-helmet';
import { StripeProvider, Elements } from 'react-stripe-elements';
import { Grid, Row, Col, Button } 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 { stripePublicKey, apiLocation } from '../../config/env.json';
import { stripePublicKey } from '../../config/env.json';
import { Spacer, Loader } from '../components/helpers';
import DonateOther from '../components/Donation/components/DonateOther';
import DonateForm from '../components/Donation/components/DonateForm';
import DonateText from '../components/Donation/components/DonateText';
import PoweredByStripe from '../components/Donation/components/poweredByStripe';
import {
signInLoadingSelector,
isSignedInSelector,
hardGoTo as navigate
} from '../redux';
import { signInLoadingSelector } from '../redux';
import { stripeScriptLoader } from '../utils/scriptLoaders';
const mapStateToProps = createSelector(
signInLoadingSelector,
isSignedInSelector,
(showLoading, isSignedIn) => ({
showLoading,
isSignedIn
showLoading => ({
showLoading
})
);
const mapDispatchToProps = {
navigate
};
const propTypes = {
isSignedIn: PropTypes.bool.isRequired,
navigate: PropTypes.func.isRequired,
showLoading: PropTypes.bool.isRequired
};
@ -42,11 +27,9 @@ export class DonatePage extends Component {
constructor(...props) {
super(...props);
this.state = {
stripe: null,
showOtherOptions: false
stripe: null
};
this.handleStripeLoad = this.handleStripeLoad.bind(this);
this.toggleOtherOptions = this.toggleOtherOptions.bind(this);
}
componentDidMount() {
@ -77,54 +60,33 @@ export class DonatePage extends Component {
}));
}
toggleOtherOptions() {
this.setState(({ showOtherOptions }) => ({
showOtherOptions: !showOtherOptions
}));
}
render() {
const { showOtherOptions, stripe } = this.state;
const { showLoading, isSignedIn, navigate } = this.props;
const { stripe } = this.state;
const { showLoading } = this.props;
if (showLoading) {
return <Loader fullScreen={true} />;
}
if (!showLoading && !isSignedIn) {
navigate(`${apiLocation}/signin?returnTo=donate`);
return <Loader fullScreen={true} />;
}
return (
<Fragment>
<Helmet title='Support our nonprofit | freeCodeCamp.org' />
<Spacer />
<Grid>
<Row>
<Col sm={10} smOffset={1} xs={12}>
<h2 className='text-center'>Become a Supporter</h2>
<DonateText />
</Col>
<Col md={8} mdOffset={2} sm={10} smOffset={1} xs={12}>
<hr />
<StripeProvider stripe={stripe}>
<Elements>
<DonateForm />
</Elements>
</StripeProvider>
<div className='text-center'>
<PoweredByStripe />
<Spacer />
<Button bsStyle='link' onClick={this.toggleOtherOptions}>
{`${showOtherOptions ? 'Hide' : 'Show'} other ways to donate`}
</Button>
</div>
<Spacer />
</Col>
</Row>
<Row>
<Col md={6}>
<DonateText />
</Col>
<Col md={6}>
<DonateForm stripe={stripe} />
</Col>
</Row>
</Grid>
{showOtherOptions && <DonateOther />}
</Fragment>
);
}
@ -133,7 +95,4 @@ export class DonatePage extends Component {
DonatePage.displayName = 'DonatePage';
DonatePage.propTypes = propTypes;
export default connect(
mapStateToProps,
mapDispatchToProps
)(DonatePage);
export default connect(mapStateToProps)(DonatePage);