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 {
|
.donation-form {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 80%;
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ function DonateCompletion({ processing, reset, success, error = null }) {
|
|||||||
? 'We are processing your donation.'
|
? 'We are processing your donation.'
|
||||||
: success
|
: success
|
||||||
? 'Your donation was successful.'
|
? 'Your donation was successful.'
|
||||||
: 'Something went wrong with your donation';
|
: 'Something went wrong with your donation.';
|
||||||
return (
|
return (
|
||||||
<Alert bsStyle={style} className='donation-completion'>
|
<Alert bsStyle={style} className='donation-completion'>
|
||||||
<h4>
|
<h4>
|
||||||
@ -35,7 +35,16 @@ function DonateCompletion({ processing, reset, success, error = null }) {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{success && (
|
{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>}
|
{error && <p>{error}</p>}
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,227 +2,116 @@ import React, { Component } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
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 {
|
import {
|
||||||
Button,
|
userSelector,
|
||||||
ControlLabel,
|
isSignedInSelector,
|
||||||
Form,
|
signInLoadingSelector,
|
||||||
FormControl,
|
hardGoTo as navigate
|
||||||
FormGroup,
|
} from '../../../redux';
|
||||||
Row,
|
|
||||||
Col
|
|
||||||
} from '@freecodecamp/react-bootstrap';
|
|
||||||
import { injectStripe } from 'react-stripe-elements';
|
|
||||||
|
|
||||||
import Spacer from '../../../components/helpers/Spacer';
|
import '../Donation.css';
|
||||||
import StripeCardForm from './StripeCardForm';
|
|
||||||
import DonateCompletion from './DonateCompletion';
|
|
||||||
import { postChargeStripe } from '../../../utils/ajax';
|
|
||||||
import { userSelector, isSignedInSelector } from '../../../redux';
|
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
email: PropTypes.string,
|
|
||||||
isSignedIn: PropTypes.bool,
|
isSignedIn: PropTypes.bool,
|
||||||
|
navigate: PropTypes.func.isRequired,
|
||||||
|
showLoading: PropTypes.bool.isRequired,
|
||||||
stripe: PropTypes.shape({
|
stripe: PropTypes.shape({
|
||||||
createToken: PropTypes.func.isRequired
|
createToken: PropTypes.func.isRequired
|
||||||
}),
|
})
|
||||||
theme: PropTypes.string
|
|
||||||
};
|
|
||||||
const initialState = {
|
|
||||||
donationAmount: 500,
|
|
||||||
donationState: {
|
|
||||||
processing: false,
|
|
||||||
success: false,
|
|
||||||
error: ''
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = createSelector(
|
const mapStateToProps = createSelector(
|
||||||
userSelector,
|
userSelector,
|
||||||
|
signInLoadingSelector,
|
||||||
isSignedInSelector,
|
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 {
|
class DonateForm extends Component {
|
||||||
constructor(...args) {
|
constructor(...args) {
|
||||||
super(...args);
|
super(...args);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
...initialState,
|
donationAmount: 500
|
||||||
email: null,
|
|
||||||
isFormValid: false
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getUserEmail = this.getUserEmail.bind(this);
|
this.buttonSingleAmounts = [2500, 5000, 10000, 25000];
|
||||||
this.getValidationState = this.getValidationState.bind(this);
|
this.buttonMonthlyAmounts = [500, 1000, 2000];
|
||||||
this.handleEmailChange = this.handleEmailChange.bind(this);
|
this.buttonAnnualAmounts = [6000, 10000, 25000, 50000];
|
||||||
this.handleSubmit = this.handleSubmit.bind(this);
|
|
||||||
this.postDonation = this.postDonation.bind(this);
|
this.isActive = this.isActive.bind(this);
|
||||||
this.resetDonation = this.resetDonation.bind(this);
|
this.renderAmountButtons = this.renderAmountButtons.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
getUserEmail() {
|
isActive(amount) {
|
||||||
const { email: stateEmail } = this.state;
|
return this.state.donationAmount === amount;
|
||||||
const { email: propsEmail } = this.props;
|
|
||||||
return stateEmail || propsEmail || '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getValidationState(isFormValid) {
|
renderAmountButtons() {
|
||||||
this.setState(state => ({
|
return this.buttonAnnualAmounts.map(amount => (
|
||||||
...state,
|
<Button
|
||||||
isFormValid
|
className={`amount-value ${this.isActive(amount) ? 'active' : ''}`}
|
||||||
}));
|
href=''
|
||||||
}
|
id={amount}
|
||||||
|
key={'amount-' + amount}
|
||||||
handleEmailChange(e) {
|
onClick={this.handleAmountClick}
|
||||||
const newValue = e.target.value;
|
tabIndex='-1'
|
||||||
return this.setState(state => ({
|
>
|
||||||
...state,
|
{`$${amount / 100}`}
|
||||||
email: newValue
|
</Button>
|
||||||
}));
|
));
|
||||||
}
|
|
||||||
|
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { isSignedIn, navigate, showLoading, stripe } = this.props;
|
||||||
donationState: { processing, success, error }
|
|
||||||
} = this.state;
|
if (!showLoading && !isSignedIn) {
|
||||||
if (processing || success || error) {
|
return (
|
||||||
return this.renderCompletion({
|
<div>
|
||||||
processing,
|
<Button
|
||||||
success,
|
bsStyle='default'
|
||||||
error,
|
className='btn btn-block'
|
||||||
reset: this.resetDonation
|
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.displayName = 'DonateForm';
|
||||||
DonateForm.propTypes = propTypes;
|
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 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 DonateText = () => {
|
||||||
|
|
||||||
const propTypes = {
|
|
||||||
activeDonations: PropTypes.number
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = createSelector(
|
|
||||||
activeDonationsSelector,
|
|
||||||
activeDonations => ({ activeDonations })
|
|
||||||
);
|
|
||||||
|
|
||||||
const DonateText = ({ activeDonations }) => {
|
|
||||||
const donationsLocale = activeDonations.toLocaleString();
|
|
||||||
return (
|
return (
|
||||||
<Row>
|
<>
|
||||||
<Col sm={10} smOffset={1} xs={12}>
|
<p>freeCodeCamp is a highly efficient education nonprofit.</p>
|
||||||
<p>
|
<p>
|
||||||
freeCodeCamp.org is a tiny nonprofit that's helping millions of people
|
In 2019 alone, we provided 1,100,000,000 minutes of free education to
|
||||||
learn to code for free.
|
people around the world.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Join <strong>{donationsLocale}</strong> supporters.
|
Since freeCodeCamp's total budget is only $373,000, that means every
|
||||||
</p>
|
dollar you donate to freeCodeCamp translates into 50 hours worth of
|
||||||
<p>
|
technology education.
|
||||||
Your $5 / month donation will help keep tech education free and open.
|
</p>
|
||||||
</p>
|
<p>
|
||||||
</Col>
|
When you donate to freeCodeCamp, you help people learn new skills and
|
||||||
</Row>
|
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.displayName = 'DonateText';
|
||||||
DonateText.propTypes = propTypes;
|
|
||||||
|
|
||||||
export default connect(mapStateToProps)(DonateText);
|
export default DonateText;
|
||||||
|
@ -7,8 +7,6 @@ import {
|
|||||||
} from 'react-stripe-elements';
|
} from 'react-stripe-elements';
|
||||||
import { ControlLabel, FormGroup } from '@freecodecamp/react-bootstrap';
|
import { ControlLabel, FormGroup } from '@freecodecamp/react-bootstrap';
|
||||||
|
|
||||||
import '../Donation.css';
|
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
getValidationState: PropTypes.func.isRequired,
|
getValidationState: PropTypes.func.isRequired,
|
||||||
theme: PropTypes.string
|
theme: PropTypes.string
|
||||||
|
@ -1,40 +1,25 @@
|
|||||||
import React, { Component, Fragment } from 'react';
|
import React, { Component, Fragment } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
import { StripeProvider, Elements } from 'react-stripe-elements';
|
import PropTypes from 'prop-types';
|
||||||
import { Grid, Row, Col, Button } from '@freecodecamp/react-bootstrap';
|
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
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 { Spacer, Loader } from '../components/helpers';
|
||||||
import DonateOther from '../components/Donation/components/DonateOther';
|
|
||||||
import DonateForm from '../components/Donation/components/DonateForm';
|
import DonateForm from '../components/Donation/components/DonateForm';
|
||||||
import DonateText from '../components/Donation/components/DonateText';
|
import DonateText from '../components/Donation/components/DonateText';
|
||||||
import PoweredByStripe from '../components/Donation/components/poweredByStripe';
|
import { signInLoadingSelector } from '../redux';
|
||||||
import {
|
|
||||||
signInLoadingSelector,
|
|
||||||
isSignedInSelector,
|
|
||||||
hardGoTo as navigate
|
|
||||||
} from '../redux';
|
|
||||||
import { stripeScriptLoader } from '../utils/scriptLoaders';
|
import { stripeScriptLoader } from '../utils/scriptLoaders';
|
||||||
|
|
||||||
const mapStateToProps = createSelector(
|
const mapStateToProps = createSelector(
|
||||||
signInLoadingSelector,
|
signInLoadingSelector,
|
||||||
isSignedInSelector,
|
showLoading => ({
|
||||||
(showLoading, isSignedIn) => ({
|
showLoading
|
||||||
showLoading,
|
|
||||||
isSignedIn
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
|
||||||
navigate
|
|
||||||
};
|
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
isSignedIn: PropTypes.bool.isRequired,
|
|
||||||
navigate: PropTypes.func.isRequired,
|
|
||||||
showLoading: PropTypes.bool.isRequired
|
showLoading: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -42,11 +27,9 @@ export class DonatePage extends Component {
|
|||||||
constructor(...props) {
|
constructor(...props) {
|
||||||
super(...props);
|
super(...props);
|
||||||
this.state = {
|
this.state = {
|
||||||
stripe: null,
|
stripe: null
|
||||||
showOtherOptions: false
|
|
||||||
};
|
};
|
||||||
this.handleStripeLoad = this.handleStripeLoad.bind(this);
|
this.handleStripeLoad = this.handleStripeLoad.bind(this);
|
||||||
this.toggleOtherOptions = this.toggleOtherOptions.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -77,54 +60,33 @@ export class DonatePage extends Component {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleOtherOptions() {
|
|
||||||
this.setState(({ showOtherOptions }) => ({
|
|
||||||
showOtherOptions: !showOtherOptions
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { showOtherOptions, stripe } = this.state;
|
const { stripe } = this.state;
|
||||||
const { showLoading, isSignedIn, navigate } = this.props;
|
const { showLoading } = this.props;
|
||||||
|
|
||||||
if (showLoading) {
|
if (showLoading) {
|
||||||
return <Loader fullScreen={true} />;
|
return <Loader fullScreen={true} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!showLoading && !isSignedIn) {
|
|
||||||
navigate(`${apiLocation}/signin?returnTo=donate`);
|
|
||||||
return <Loader fullScreen={true} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Helmet title='Support our nonprofit | freeCodeCamp.org' />
|
<Helmet title='Support our nonprofit | freeCodeCamp.org' />
|
||||||
<Spacer />
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<Row>
|
<Row>
|
||||||
<Col sm={10} smOffset={1} xs={12}>
|
<Col sm={10} smOffset={1} xs={12}>
|
||||||
<h2 className='text-center'>Become a Supporter</h2>
|
<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 />
|
<Spacer />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<Col md={6}>
|
||||||
|
<DonateText />
|
||||||
|
</Col>
|
||||||
|
<Col md={6}>
|
||||||
|
<DonateForm stripe={stripe} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
</Grid>
|
</Grid>
|
||||||
{showOtherOptions && <DonateOther />}
|
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -133,7 +95,4 @@ export class DonatePage extends Component {
|
|||||||
DonatePage.displayName = 'DonatePage';
|
DonatePage.displayName = 'DonatePage';
|
||||||
DonatePage.propTypes = propTypes;
|
DonatePage.propTypes = propTypes;
|
||||||
|
|
||||||
export default connect(
|
export default connect(mapStateToProps)(DonatePage);
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(DonatePage);
|
|
||||||
|
Reference in New Issue
Block a user