feat(donate): two col layout checkout page
This commit is contained in:
committed by
mrugesh
parent
7a4e84d163
commit
4cd5542489
@ -19,7 +19,6 @@
|
||||
.donation-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 80%;
|
||||
justify-content: center;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
);
|
@ -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;
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
Reference in New Issue
Block a user