feat: add minimal form to modal
This commit is contained in:
committed by
mrugesh
parent
85d3587e59
commit
01d1315835
@ -18,15 +18,16 @@ import { postChargeStripe } from '../../../utils/ajax';
|
|||||||
import { userSelector } from '../../../redux';
|
import { userSelector } from '../../../redux';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
|
changeCloseBtnLabel: PropTypes.func,
|
||||||
donationAmount: PropTypes.number.isRequired,
|
donationAmount: PropTypes.number.isRequired,
|
||||||
donationDuration: PropTypes.string.isRequired,
|
donationDuration: PropTypes.string.isRequired,
|
||||||
email: PropTypes.string,
|
email: PropTypes.string,
|
||||||
getDonationButtonLabel: PropTypes.func.isRequired,
|
getDonationButtonLabel: PropTypes.func.isRequired,
|
||||||
hideAmountOptionsCB: PropTypes.func.isRequired,
|
|
||||||
isSignedIn: PropTypes.bool,
|
isSignedIn: PropTypes.bool,
|
||||||
stripe: PropTypes.shape({
|
stripe: PropTypes.shape({
|
||||||
createToken: PropTypes.func.isRequired
|
createToken: PropTypes.func.isRequired
|
||||||
})
|
}),
|
||||||
|
theme: PropTypes.string
|
||||||
};
|
};
|
||||||
const initialState = {
|
const initialState = {
|
||||||
donationState: {
|
donationState: {
|
||||||
@ -38,7 +39,7 @@ const initialState = {
|
|||||||
|
|
||||||
const mapStateToProps = createSelector(
|
const mapStateToProps = createSelector(
|
||||||
userSelector,
|
userSelector,
|
||||||
({ email }) => ({ email })
|
({ email, theme }) => ({ email, theme })
|
||||||
);
|
);
|
||||||
|
|
||||||
class DonateFormChildViewForHOC extends Component {
|
class DonateFormChildViewForHOC extends Component {
|
||||||
@ -59,7 +60,6 @@ class DonateFormChildViewForHOC extends Component {
|
|||||||
this.handleSubmit = this.handleSubmit.bind(this);
|
this.handleSubmit = this.handleSubmit.bind(this);
|
||||||
this.postDonation = this.postDonation.bind(this);
|
this.postDonation = this.postDonation.bind(this);
|
||||||
this.resetDonation = this.resetDonation.bind(this);
|
this.resetDonation = this.resetDonation.bind(this);
|
||||||
this.hideAmountOptions(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getUserEmail() {
|
getUserEmail() {
|
||||||
@ -113,11 +113,6 @@ class DonateFormChildViewForHOC extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
hideAmountOptions(hide) {
|
|
||||||
const { hideAmountOptionsCB } = this.props;
|
|
||||||
hideAmountOptionsCB(hide);
|
|
||||||
}
|
|
||||||
|
|
||||||
postDonation(token) {
|
postDonation(token) {
|
||||||
const { donationAmount: amount, donationDuration: duration } = this.state;
|
const { donationAmount: amount, donationDuration: duration } = this.state;
|
||||||
this.setState(state => ({
|
this.setState(state => ({
|
||||||
@ -128,10 +123,12 @@ class DonateFormChildViewForHOC extends Component {
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// hide the donation options on the parent and scroll to top
|
// scroll to top
|
||||||
this.hideAmountOptions(true);
|
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
|
|
||||||
|
// change the donation modal button to close
|
||||||
|
this.props.changeCloseBtnLabel();
|
||||||
|
|
||||||
return postChargeStripe({
|
return postChargeStripe({
|
||||||
token,
|
token,
|
||||||
amount,
|
amount,
|
||||||
@ -179,7 +176,7 @@ class DonateFormChildViewForHOC extends Component {
|
|||||||
|
|
||||||
renderDonateForm() {
|
renderDonateForm() {
|
||||||
const { isFormValid } = this.state;
|
const { isFormValid } = this.state;
|
||||||
const { getDonationButtonLabel } = this.props;
|
const { getDonationButtonLabel, theme } = this.props;
|
||||||
return (
|
return (
|
||||||
<Form className='donation-form' onSubmit={this.handleSubmit}>
|
<Form className='donation-form' onSubmit={this.handleSubmit}>
|
||||||
<FormGroup className='donation-email-container'>
|
<FormGroup className='donation-email-container'>
|
||||||
@ -194,7 +191,10 @@ class DonateFormChildViewForHOC extends Component {
|
|||||||
value={this.getUserEmail()}
|
value={this.getUserEmail()}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<StripeCardForm getValidationState={this.getValidationState} />
|
<StripeCardForm
|
||||||
|
getValidationState={this.getValidationState}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
<Button
|
<Button
|
||||||
block={true}
|
block={true}
|
||||||
bsStyle='primary'
|
bsStyle='primary'
|
||||||
|
@ -4,18 +4,18 @@ import PropTypes from 'prop-types';
|
|||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { Modal, Button } from '@freecodecamp/react-bootstrap';
|
import { Modal, Button, Col, Row } from '@freecodecamp/react-bootstrap';
|
||||||
import { Link } from '../../../components/helpers';
|
import { Spacer } from '../../../components/helpers';
|
||||||
import { blockNameify } from '../../../../utils/blockNameify';
|
import { blockNameify } from '../../../../utils/blockNameify';
|
||||||
import Heart from '../../../assets/icons/Heart';
|
import Heart from '../../../assets/icons/Heart';
|
||||||
import Cup from '../../../assets/icons/Cup';
|
import Cup from '../../../assets/icons/Cup';
|
||||||
|
import MinimalDonateForm from './MinimalDonateForm';
|
||||||
|
|
||||||
import ga from '../../../analytics';
|
import ga from '../../../analytics';
|
||||||
import {
|
import {
|
||||||
closeDonationModal,
|
closeDonationModal,
|
||||||
isDonationModalOpenSelector,
|
isDonationModalOpenSelector,
|
||||||
isBlockDonationModalSelector,
|
isBlockDonationModalSelector
|
||||||
activeDonationsSelector
|
|
||||||
} from '../../../redux';
|
} from '../../../redux';
|
||||||
|
|
||||||
import { challengeMetaSelector } from '../../../templates/Challenges/redux';
|
import { challengeMetaSelector } from '../../../templates/Challenges/redux';
|
||||||
@ -26,12 +26,10 @@ const mapStateToProps = createSelector(
|
|||||||
isDonationModalOpenSelector,
|
isDonationModalOpenSelector,
|
||||||
challengeMetaSelector,
|
challengeMetaSelector,
|
||||||
isBlockDonationModalSelector,
|
isBlockDonationModalSelector,
|
||||||
activeDonationsSelector,
|
(show, { block }, isBlockDonation) => ({
|
||||||
(show, { block }, isBlockDonation, activeDonors) => ({
|
|
||||||
show,
|
show,
|
||||||
block,
|
block,
|
||||||
isBlockDonation,
|
isBlockDonation
|
||||||
activeDonors
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -51,13 +49,12 @@ const propTypes = {
|
|||||||
show: PropTypes.bool
|
show: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
function DonateModal({
|
function DonateModal({ show, block, isBlockDonation, closeDonationModal }) {
|
||||||
show,
|
const [showCloseLabel, setCloseLabel] = React.useState(false);
|
||||||
block,
|
const changeCloseBtnLabel = () => {
|
||||||
activeDonors,
|
setCloseLabel(true);
|
||||||
isBlockDonation,
|
};
|
||||||
closeDonationModal
|
|
||||||
}) {
|
|
||||||
if (show) {
|
if (show) {
|
||||||
ga.modalview('/donation-modal');
|
ga.modalview('/donation-modal');
|
||||||
}
|
}
|
||||||
@ -66,12 +63,12 @@ function DonateModal({
|
|||||||
<div className='donation-icon-container'>
|
<div className='donation-icon-container'>
|
||||||
<Cup className='donation-icon' />
|
<Cup className='donation-icon' />
|
||||||
</div>
|
</div>
|
||||||
<p className='text-center'>
|
<Row>
|
||||||
Nicely done. You just completed {blockNameify(block)}.
|
<Col sm={10} smOffset={1} xs={12}>
|
||||||
</p>
|
<p>Nicely done. You just completed {blockNameify(block)}.</p>
|
||||||
<p className='text-center'>
|
<p>Help us create even more learning resources like this.</p>
|
||||||
Help us create even more learning resources like this.
|
</Col>
|
||||||
</p>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -80,14 +77,13 @@ function DonateModal({
|
|||||||
<div className='donation-icon-container'>
|
<div className='donation-icon-container'>
|
||||||
<Heart className='donation-icon' />
|
<Heart className='donation-icon' />
|
||||||
</div>
|
</div>
|
||||||
<p>
|
<Row>
|
||||||
freeCodeCamp.org is a tiny nonprofit that's helping millions of people
|
<Col sm={10} smOffset={1} xs={12}>
|
||||||
learn to code for free.
|
<p>
|
||||||
</p>
|
Help us create even more learning resources for you and your family.
|
||||||
<p>
|
</p>
|
||||||
Join <strong>{activeDonors}</strong> supporters.
|
</Col>
|
||||||
</p>
|
</Row>
|
||||||
<p>Your donation will help keep tech education free and open.</p>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -95,30 +91,28 @@ function DonateModal({
|
|||||||
<Modal bsSize='lg' className='donation-modal' show={show}>
|
<Modal bsSize='lg' className='donation-modal' show={show}>
|
||||||
<Modal.Header className='fcc-modal'>
|
<Modal.Header className='fcc-modal'>
|
||||||
<Modal.Title className='modal-title text-center'>
|
<Modal.Title className='modal-title text-center'>
|
||||||
<strong>Support freeCodeCamp.org</strong>
|
<strong>Become a Supporter</strong>
|
||||||
</Modal.Title>
|
</Modal.Title>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
{isBlockDonation ? blockDonationText : progressDonationText}
|
{isBlockDonation ? blockDonationText : progressDonationText}
|
||||||
|
<Spacer />
|
||||||
|
<MinimalDonateForm changeCloseBtnLabel={changeCloseBtnLabel} />
|
||||||
|
<Spacer />
|
||||||
|
<Row>
|
||||||
|
<Col sm={10} smOffset={1} xs={12}>
|
||||||
|
<Button
|
||||||
|
block={true}
|
||||||
|
bsSize='sm'
|
||||||
|
bsStyle='primary'
|
||||||
|
className='btn-link'
|
||||||
|
onClick={closeDonationModal}
|
||||||
|
>
|
||||||
|
{showCloseLabel ? 'close' : 'Please ask me later.'}
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
</Modal.Body>
|
</Modal.Body>
|
||||||
<Modal.Footer>
|
|
||||||
<Link
|
|
||||||
className='btn-invert btn btn-lg btn-primary btn-block btn-cta'
|
|
||||||
onClick={closeDonationModal}
|
|
||||||
to={`/donate`}
|
|
||||||
>
|
|
||||||
Support our nonprofit
|
|
||||||
</Link>
|
|
||||||
<Button
|
|
||||||
block={true}
|
|
||||||
bsSize='lg'
|
|
||||||
bsStyle='primary'
|
|
||||||
className='btn-invert'
|
|
||||||
onClick={closeDonationModal}
|
|
||||||
>
|
|
||||||
Ask me later
|
|
||||||
</Button>
|
|
||||||
</Modal.Footer>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
203
client/src/components/Donation/components/MinimalDonateForm.js
Normal file
203
client/src/components/Donation/components/MinimalDonateForm.js
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import { Button, Row, Col } from '@freecodecamp/react-bootstrap';
|
||||||
|
import { StripeProvider, Elements } from 'react-stripe-elements';
|
||||||
|
|
||||||
|
import {
|
||||||
|
amountsConfig,
|
||||||
|
durationsConfig,
|
||||||
|
defaultStateConfig
|
||||||
|
} from '../../../../../config/donation-settings';
|
||||||
|
import { apiLocation } from '../../../../config/env.json';
|
||||||
|
import DonateFormChildViewForHOC from './DonateFormChildViewForHOC';
|
||||||
|
import {
|
||||||
|
userSelector,
|
||||||
|
isSignedInSelector,
|
||||||
|
signInLoadingSelector,
|
||||||
|
hardGoTo as navigate
|
||||||
|
} 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,');
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
changeCloseBtnLabel: PropTypes.func,
|
||||||
|
isDonating: PropTypes.bool,
|
||||||
|
isSignedIn: PropTypes.bool,
|
||||||
|
navigate: PropTypes.func.isRequired,
|
||||||
|
showLoading: PropTypes.bool.isRequired,
|
||||||
|
stripe: PropTypes.shape({
|
||||||
|
createToken: PropTypes.func.isRequired
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = createSelector(
|
||||||
|
userSelector,
|
||||||
|
signInLoadingSelector,
|
||||||
|
isSignedInSelector,
|
||||||
|
({ isDonating }, showLoading, isSignedIn) => ({
|
||||||
|
isDonating,
|
||||||
|
isSignedIn,
|
||||||
|
showLoading
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
navigate
|
||||||
|
};
|
||||||
|
|
||||||
|
const createOnClick = navigate => e => {
|
||||||
|
e.preventDefault();
|
||||||
|
return navigate(`${apiLocation}/signin?returnTo=donate`);
|
||||||
|
};
|
||||||
|
|
||||||
|
class ModalDonateForm extends Component {
|
||||||
|
constructor(...args) {
|
||||||
|
super(...args);
|
||||||
|
|
||||||
|
this.durations = durationsConfig;
|
||||||
|
this.amounts = amountsConfig;
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
...defaultStateConfig,
|
||||||
|
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() {
|
||||||
|
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
|
||||||
|
if (stripePublicKey) {
|
||||||
|
this.setState(state => ({
|
||||||
|
...state,
|
||||||
|
stripe: window.Stripe(stripePublicKey)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
const { changeCloseBtnLabel } = this.props;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{paymentType === 'Card' ? (
|
||||||
|
<StripeProvider stripe={stripe}>
|
||||||
|
<Elements>
|
||||||
|
<DonateFormChildViewForHOC
|
||||||
|
changeCloseBtnLabel={changeCloseBtnLabel}
|
||||||
|
donationAmount={donationAmount}
|
||||||
|
donationDuration={donationDuration}
|
||||||
|
getDonationButtonLabel={this.getDonationButtonLabel}
|
||||||
|
/>
|
||||||
|
</Elements>
|
||||||
|
</StripeProvider>
|
||||||
|
) : (
|
||||||
|
<p>
|
||||||
|
PayPal is currently unavailable. Please use a Credit/Debit card
|
||||||
|
instead.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { isSignedIn, navigate, showLoading, 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}>
|
||||||
|
{!showLoading && !isSignedIn ? (
|
||||||
|
<Button
|
||||||
|
bsStyle='default'
|
||||||
|
className='btn btn-block'
|
||||||
|
onClick={createOnClick(navigate)}
|
||||||
|
>
|
||||||
|
Become a supporter
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
this.renderDonationOptions()
|
||||||
|
)}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ModalDonateForm.displayName = 'ModalDonateForm';
|
||||||
|
ModalDonateForm.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(ModalDonateForm);
|
@ -1,10 +1,6 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {
|
import { CardNumberElement, CardExpiryElement } from 'react-stripe-elements';
|
||||||
CardNumberElement,
|
|
||||||
CardExpiryElement,
|
|
||||||
CardCVCElement
|
|
||||||
} from 'react-stripe-elements';
|
|
||||||
import { ControlLabel, FormGroup } from '@freecodecamp/react-bootstrap';
|
import { ControlLabel, FormGroup } from '@freecodecamp/react-bootstrap';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
@ -31,10 +27,6 @@ class StripeCardForm extends Component {
|
|||||||
cardExpiry: {
|
cardExpiry: {
|
||||||
complete: false,
|
complete: false,
|
||||||
error: null
|
error: null
|
||||||
},
|
|
||||||
cardCvc: {
|
|
||||||
complete: false,
|
|
||||||
error: null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -92,14 +84,6 @@ class StripeCardForm extends Component {
|
|||||||
style={style}
|
style={style}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormGroup>
|
|
||||||
<ControlLabel>Your Card CVC (3-digit security number):</ControlLabel>
|
|
||||||
<CardCVCElement
|
|
||||||
className='form-control donate-input-element'
|
|
||||||
onChange={this.handleInputChange}
|
|
||||||
style={style}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ const amountsConfig = {
|
|||||||
};
|
};
|
||||||
const defaultAmount = {
|
const defaultAmount = {
|
||||||
year: 25000,
|
year: 25000,
|
||||||
month: 3500,
|
month: 500,
|
||||||
onetime: 25000
|
onetime: 25000
|
||||||
};
|
};
|
||||||
const defaultStateConfig = {
|
const defaultStateConfig = {
|
||||||
|
Reference in New Issue
Block a user