fix(client): modernize stripe form (#41359)
Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
30658
client/package-lock.json
generated
30658
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -19,6 +19,7 @@
|
|||||||
"@freecodecamp/react-calendar-heatmap": "^1.0.0",
|
"@freecodecamp/react-calendar-heatmap": "^1.0.0",
|
||||||
"@loadable/component": "^5.14.1",
|
"@loadable/component": "^5.14.1",
|
||||||
"@reach/router": "^1.3.4",
|
"@reach/router": "^1.3.4",
|
||||||
|
"@stripe/react-stripe-js": "^1.4.0",
|
||||||
"algoliasearch": "^3.35.1",
|
"algoliasearch": "^3.35.1",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"babel-plugin-prismjs": "^2.0.1",
|
"babel-plugin-prismjs": "^2.0.1",
|
||||||
@ -63,7 +64,6 @@
|
|||||||
"react-responsive": "^6.1.1",
|
"react-responsive": "^6.1.1",
|
||||||
"react-scrollable-anchor": "^0.6.1",
|
"react-scrollable-anchor": "^0.6.1",
|
||||||
"react-spinkit": "^3.0.0",
|
"react-spinkit": "^3.0.0",
|
||||||
"react-stripe-elements": "^2.0.3",
|
|
||||||
"react-tooltip": "^4.2.13",
|
"react-tooltip": "^4.2.13",
|
||||||
"react-transition-group": "^4.4.1",
|
"react-transition-group": "^4.4.1",
|
||||||
"react-youtube": "^7.13.1",
|
"react-youtube": "^7.13.1",
|
||||||
|
@ -276,7 +276,7 @@ const ShowCertification = props => {
|
|||||||
<Col md={8} mdOffset={2} xs={12}>
|
<Col md={8} mdOffset={2} xs={12}>
|
||||||
<DonateForm
|
<DonateForm
|
||||||
handleProcessing={handleProcessing}
|
handleProcessing={handleProcessing}
|
||||||
defaultTheme='light'
|
defaultTheme='default'
|
||||||
isMinimalForm={true}
|
isMinimalForm={true}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -3,7 +3,7 @@ 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 { StripeProvider, Elements } from 'react-stripe-elements';
|
import { Elements } from '@stripe/react-stripe-js';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Col,
|
Col,
|
||||||
@ -26,10 +26,10 @@ import {
|
|||||||
} from '../../../../config/donation-settings';
|
} from '../../../../config/donation-settings';
|
||||||
import { stripePublicKey, deploymentEnv } from '../../../../config/env.json';
|
import { stripePublicKey, deploymentEnv } from '../../../../config/env.json';
|
||||||
import { stripeScriptLoader } from '../../utils/scriptLoaders';
|
import { stripeScriptLoader } from '../../utils/scriptLoaders';
|
||||||
import DonateFormChildViewForHOC from './DonateFormChildViewForHOC';
|
|
||||||
import Spacer from '../helpers/Spacer';
|
import Spacer from '../helpers/Spacer';
|
||||||
import PaypalButton from './PaypalButton';
|
import PaypalButton from './PaypalButton';
|
||||||
import DonateCompletion from './DonateCompletion';
|
import DonateCompletion from './DonateCompletion';
|
||||||
|
import StripeCardForm from './StripeCardForm';
|
||||||
import {
|
import {
|
||||||
isSignedInSelector,
|
isSignedInSelector,
|
||||||
signInLoadingSelector,
|
signInLoadingSelector,
|
||||||
@ -38,7 +38,8 @@ import {
|
|||||||
addDonation,
|
addDonation,
|
||||||
postChargeStripe,
|
postChargeStripe,
|
||||||
updateDonationFormState,
|
updateDonationFormState,
|
||||||
defaultDonationFormState
|
defaultDonationFormState,
|
||||||
|
userSelector
|
||||||
} from '../../redux';
|
} from '../../redux';
|
||||||
|
|
||||||
import './Donation.css';
|
import './Donation.css';
|
||||||
@ -50,6 +51,7 @@ const propTypes = {
|
|||||||
addDonation: PropTypes.func,
|
addDonation: PropTypes.func,
|
||||||
defaultTheme: PropTypes.string,
|
defaultTheme: PropTypes.string,
|
||||||
donationFormState: PropTypes.object,
|
donationFormState: PropTypes.object,
|
||||||
|
email: PropTypes.string,
|
||||||
handleProcessing: PropTypes.func,
|
handleProcessing: PropTypes.func,
|
||||||
isDonating: PropTypes.bool,
|
isDonating: PropTypes.bool,
|
||||||
isMinimalForm: PropTypes.bool,
|
isMinimalForm: PropTypes.bool,
|
||||||
@ -58,6 +60,7 @@ const propTypes = {
|
|||||||
postChargeStripe: PropTypes.func.isRequired,
|
postChargeStripe: PropTypes.func.isRequired,
|
||||||
showLoading: PropTypes.bool.isRequired,
|
showLoading: PropTypes.bool.isRequired,
|
||||||
t: PropTypes.func.isRequired,
|
t: PropTypes.func.isRequired,
|
||||||
|
theme: PropTypes.string,
|
||||||
updateDonationFormState: PropTypes.func
|
updateDonationFormState: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -65,10 +68,13 @@ const mapStateToProps = createSelector(
|
|||||||
signInLoadingSelector,
|
signInLoadingSelector,
|
||||||
isSignedInSelector,
|
isSignedInSelector,
|
||||||
donationFormStateSelector,
|
donationFormStateSelector,
|
||||||
(showLoading, isSignedIn, donationFormState) => ({
|
userSelector,
|
||||||
|
(showLoading, isSignedIn, donationFormState, { email, theme }) => ({
|
||||||
isSignedIn,
|
isSignedIn,
|
||||||
showLoading,
|
showLoading,
|
||||||
donationFormState
|
donationFormState,
|
||||||
|
email,
|
||||||
|
theme
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -107,6 +113,7 @@ class DonateForm extends Component {
|
|||||||
);
|
);
|
||||||
this.hideAmountOptionsCB = this.hideAmountOptionsCB.bind(this);
|
this.hideAmountOptionsCB = this.hideAmountOptionsCB.bind(this);
|
||||||
this.resetDonation = this.resetDonation.bind(this);
|
this.resetDonation = this.resetDonation.bind(this);
|
||||||
|
this.postStripeDonation = this.postStripeDonation.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -193,6 +200,18 @@ class DonateForm extends Component {
|
|||||||
this.setState({ donationAmount });
|
this.setState({ donationAmount });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
postStripeDonation(token) {
|
||||||
|
const { donationAmount: amount, donationDuration: duration } = this.state;
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
|
||||||
|
// change the donation modal button label to close
|
||||||
|
// or display the close button for the cert donation section
|
||||||
|
if (this.props.handleProcessing) {
|
||||||
|
this.props.handleProcessing(duration, amount);
|
||||||
|
}
|
||||||
|
this.props.postChargeStripe({ token, amount, duration });
|
||||||
|
}
|
||||||
|
|
||||||
async handleStripeCheckoutRedirect(e, paymentMethod) {
|
async handleStripeCheckoutRedirect(e, paymentMethod) {
|
||||||
const { stripe } = this.state;
|
const { stripe } = this.state;
|
||||||
const { donationAmount, donationDuration } = this.state;
|
const { donationAmount, donationDuration } = this.state;
|
||||||
@ -358,12 +377,12 @@ class DonateForm extends Component {
|
|||||||
const { donationAmount, donationDuration, stripe } = this.state;
|
const { donationAmount, donationDuration, stripe } = this.state;
|
||||||
const {
|
const {
|
||||||
handleProcessing,
|
handleProcessing,
|
||||||
defaultTheme,
|
|
||||||
addDonation,
|
addDonation,
|
||||||
postChargeStripe,
|
email,
|
||||||
t
|
theme,
|
||||||
|
t,
|
||||||
|
defaultTheme
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row>
|
<Row>
|
||||||
<Col lg={8} lgOffset={2} sm={10} smOffset={1} xs={12}>
|
<Col lg={8} lgOffset={2} sm={10} smOffset={1} xs={12}>
|
||||||
@ -384,19 +403,15 @@ class DonateForm extends Component {
|
|||||||
<Spacer />
|
<Spacer />
|
||||||
<b>{t('donate.credit-card-2')}</b>
|
<b>{t('donate.credit-card-2')}</b>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<StripeProvider stripe={stripe}>
|
<Elements stripe={stripe}>
|
||||||
<Elements>
|
<StripeCardForm
|
||||||
<DonateFormChildViewForHOC
|
|
||||||
defaultTheme={defaultTheme}
|
|
||||||
donationAmount={donationAmount}
|
|
||||||
donationDuration={donationDuration}
|
|
||||||
getDonationButtonLabel={this.getDonationButtonLabel}
|
getDonationButtonLabel={this.getDonationButtonLabel}
|
||||||
handleProcessing={handleProcessing}
|
|
||||||
onDonationStateChange={this.onDonationStateChange}
|
onDonationStateChange={this.onDonationStateChange}
|
||||||
postChargeStripe={postChargeStripe}
|
postStripeDonation={this.postStripeDonation}
|
||||||
|
theme={defaultTheme ? defaultTheme : theme}
|
||||||
|
userEmail={email}
|
||||||
/>
|
/>
|
||||||
</Elements>
|
</Elements>
|
||||||
</StripeProvider>
|
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
);
|
);
|
||||||
|
@ -1,211 +0,0 @@
|
|||||||
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,
|
|
||||||
Alert
|
|
||||||
} from '@freecodecamp/react-bootstrap';
|
|
||||||
import { injectStripe } from 'react-stripe-elements';
|
|
||||||
|
|
||||||
import StripeCardForm from './StripeCardForm';
|
|
||||||
import DonateCompletion from './DonateCompletion';
|
|
||||||
import { userSelector } from '../../redux';
|
|
||||||
import { withTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
const propTypes = {
|
|
||||||
defaultTheme: PropTypes.string,
|
|
||||||
donationAmount: PropTypes.number.isRequired,
|
|
||||||
donationDuration: PropTypes.string.isRequired,
|
|
||||||
email: PropTypes.string,
|
|
||||||
getDonationButtonLabel: PropTypes.func.isRequired,
|
|
||||||
handleProcessing: PropTypes.func,
|
|
||||||
isSignedIn: PropTypes.bool,
|
|
||||||
onDonationStateChange: PropTypes.func,
|
|
||||||
postChargeStripe: PropTypes.func,
|
|
||||||
showCloseBtn: PropTypes.func,
|
|
||||||
stripe: PropTypes.shape({
|
|
||||||
createToken: PropTypes.func.isRequired
|
|
||||||
}),
|
|
||||||
t: PropTypes.func.isRequired,
|
|
||||||
theme: PropTypes.string
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = createSelector(
|
|
||||||
userSelector,
|
|
||||||
({ email, theme }) => ({ email, theme })
|
|
||||||
);
|
|
||||||
|
|
||||||
class DonateFormChildViewForHOC extends Component {
|
|
||||||
constructor(...args) {
|
|
||||||
super(...args);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
isSubmissionValid: null,
|
|
||||||
email: null,
|
|
||||||
isEmailValid: true,
|
|
||||||
isFormValid: false
|
|
||||||
};
|
|
||||||
|
|
||||||
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.handleEmailBlur = this.handleEmailBlur.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({
|
|
||||||
email: newValue,
|
|
||||||
// reset validation
|
|
||||||
isEmailValid: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSubmit(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const { isEmailValid, isFormValid } = this.state;
|
|
||||||
|
|
||||||
if ((!isEmailValid, !isFormValid)) {
|
|
||||||
return this.setState({
|
|
||||||
isSubmissionValid: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
isSubmissionValid: null
|
|
||||||
});
|
|
||||||
|
|
||||||
const { t } = this.props;
|
|
||||||
const email = this.getUserEmail();
|
|
||||||
if (!email || !isEmail(email)) {
|
|
||||||
return this.props.onDonationStateChange({
|
|
||||||
error: t('donate.need-email')
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return this.props.stripe.createToken({ email }).then(({ error, token }) => {
|
|
||||||
if (error) {
|
|
||||||
return this.props.onDonationStateChange({
|
|
||||||
error: t('donate.went-wrong')
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return this.postDonation(token);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
postDonation(token) {
|
|
||||||
const { donationAmount: amount, donationDuration: duration } = this.props;
|
|
||||||
|
|
||||||
// scroll to top
|
|
||||||
window.scrollTo(0, 0);
|
|
||||||
|
|
||||||
// change the donation modal button label to close
|
|
||||||
// or display the close button for the cert donation section
|
|
||||||
if (this.props.handleProcessing) {
|
|
||||||
this.props.handleProcessing(duration, amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.props.postChargeStripe({
|
|
||||||
token,
|
|
||||||
amount,
|
|
||||||
duration
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
renderCompletion(props) {
|
|
||||||
return <DonateCompletion {...props} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleEmailBlur() {
|
|
||||||
const emailValue = this.state.email;
|
|
||||||
const newValidation = isEmail(emailValue);
|
|
||||||
return this.setState({
|
|
||||||
isEmailValid: newValidation
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
renderErrorMessage() {
|
|
||||||
const { isEmailValid, isFormValid } = this.state;
|
|
||||||
const { t } = this.props;
|
|
||||||
let message = '';
|
|
||||||
if (!isEmailValid && !isFormValid)
|
|
||||||
message = <p>{t('donate.valid-info')}</p>;
|
|
||||||
else if (!isEmailValid) message = <p>{t('donate.valid-email')}</p>;
|
|
||||||
else message = <p>{t('donate.valid-card')}</p>;
|
|
||||||
|
|
||||||
return <Alert bsStyle='danger'>{message}</Alert>;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderDonateForm() {
|
|
||||||
const { isEmailValid, isSubmissionValid, email } = this.state;
|
|
||||||
const { getDonationButtonLabel, theme, defaultTheme, t } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form className='donation-form' onSubmit={this.handleSubmit}>
|
|
||||||
<div>{isSubmissionValid !== null ? this.renderErrorMessage() : ''}</div>
|
|
||||||
<FormGroup className='donation-email-container'>
|
|
||||||
<ControlLabel>{t('donate.email-receipt')}</ControlLabel>
|
|
||||||
<FormControl
|
|
||||||
className={!isEmailValid && email ? 'email--invalid' : ''}
|
|
||||||
key='3'
|
|
||||||
onBlur={this.handleEmailBlur}
|
|
||||||
onChange={this.handleEmailChange}
|
|
||||||
placeholder='me@example.com'
|
|
||||||
required={true}
|
|
||||||
type='text'
|
|
||||||
value={this.state.email || ''}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
<StripeCardForm
|
|
||||||
getValidationState={this.getValidationState}
|
|
||||||
theme={defaultTheme ? defaultTheme : theme}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
block={true}
|
|
||||||
bsStyle='primary'
|
|
||||||
id='confirm-donation-btn'
|
|
||||||
type='submit'
|
|
||||||
>
|
|
||||||
{getDonationButtonLabel()}
|
|
||||||
</Button>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillReceiveProps({ email }) {
|
|
||||||
if (this.state.email === null && email) {
|
|
||||||
this.setState({ email });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return this.renderDonateForm();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DonateFormChildViewForHOC.displayName = 'DonateFormChildViewForHOC';
|
|
||||||
DonateFormChildViewForHOC.propTypes = propTypes;
|
|
||||||
|
|
||||||
export default injectStripe(
|
|
||||||
connect(mapStateToProps)(withTranslation()(DonateFormChildViewForHOC))
|
|
||||||
);
|
|
@ -1,33 +1,27 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
CardNumberElement,
|
||||||
|
CardExpiryElement,
|
||||||
|
useStripe,
|
||||||
|
useElements
|
||||||
|
} from '@stripe/react-stripe-js';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { CardNumberElement, CardExpiryElement } from 'react-stripe-elements';
|
import isEmail from 'validator/lib/isEmail';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Row,
|
Row,
|
||||||
Col,
|
Col,
|
||||||
ControlLabel,
|
ControlLabel,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
Image
|
Image,
|
||||||
|
Button,
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
Alert
|
||||||
} from '@freecodecamp/react-bootstrap';
|
} from '@freecodecamp/react-bootstrap';
|
||||||
import { withTranslation } from 'react-i18next';
|
import { withTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const propTypes = {
|
const initialPaymentInfoValidityState = {
|
||||||
getValidationState: PropTypes.func.isRequired,
|
|
||||||
t: PropTypes.func.isRequired,
|
|
||||||
theme: PropTypes.string
|
|
||||||
};
|
|
||||||
|
|
||||||
const style = {
|
|
||||||
base: {
|
|
||||||
fontSize: '18px'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class StripeCardForm extends Component {
|
|
||||||
constructor(...props) {
|
|
||||||
super(...props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
validation: {
|
|
||||||
cardNumber: {
|
cardNumber: {
|
||||||
complete: false,
|
complete: false,
|
||||||
error: null
|
error: null
|
||||||
@ -36,53 +30,130 @@ class StripeCardForm extends Component {
|
|||||||
complete: false,
|
complete: false,
|
||||||
error: null
|
error: null
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.handleInputChange = this.handleInputChange.bind(this);
|
const propTypes = {
|
||||||
this.isValidInput = this.isValidInput.bind(this);
|
getDonationButtonLabel: PropTypes.func.isRequired,
|
||||||
}
|
onDonationStateChange: PropTypes.func,
|
||||||
|
postStripeDonation: PropTypes.func,
|
||||||
|
t: PropTypes.func.isRequired,
|
||||||
|
theme: PropTypes.string,
|
||||||
|
userEmail: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
const StripeCardForm = ({
|
||||||
this.props.getValidationState(this.isValidInput());
|
getDonationButtonLabel,
|
||||||
}
|
theme,
|
||||||
|
t,
|
||||||
|
onDonationStateChange,
|
||||||
|
postStripeDonation,
|
||||||
|
userEmail
|
||||||
|
}) => {
|
||||||
|
const [isSubmissionValid, setSubmissionValidity] = useState(true);
|
||||||
|
const [email, setEmail] = useState(userEmail);
|
||||||
|
const [isEmailValid, setEmailValidity] = useState(true);
|
||||||
|
const [paymentInfoValidation, setPaymentValidity] = useState(
|
||||||
|
initialPaymentInfoValidityState
|
||||||
|
);
|
||||||
|
|
||||||
handleInputChange(event) {
|
const stripe = useStripe();
|
||||||
|
const elements = useElements();
|
||||||
|
|
||||||
|
function handleInputChange(event) {
|
||||||
const { elementType, error, complete } = event;
|
const { elementType, error, complete } = event;
|
||||||
return this.setState(
|
setPaymentValidity({
|
||||||
state => ({
|
...paymentInfoValidation,
|
||||||
...state,
|
|
||||||
validation: {
|
|
||||||
...state.validation,
|
|
||||||
[elementType]: {
|
[elementType]: {
|
||||||
error,
|
error,
|
||||||
complete
|
complete
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}),
|
|
||||||
() => this.props.getValidationState(this.isValidInput())
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isValidInput() {
|
function isPaymentInfoValid() {
|
||||||
const { validation } = this.state;
|
return Object.keys(paymentInfoValidation)
|
||||||
return Object.keys(validation)
|
.map(key => paymentInfoValidation[key])
|
||||||
.map(key => validation[key])
|
|
||||||
.every(({ complete, error }) => complete && !error);
|
.every(({ complete, error }) => complete && !error);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
const options = {
|
||||||
const { t } = this.props;
|
style: {
|
||||||
// set color based on theme
|
base: {
|
||||||
style.base.color = this.props.theme === 'night' ? '#fff' : '#0a0a23';
|
fontSize: '18px',
|
||||||
|
color: `${theme === 'night' ? '#fff' : '#0a0a23'}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async event => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (!isEmailValid || !isPaymentInfoValid())
|
||||||
|
return setSubmissionValidity(false);
|
||||||
|
else setSubmissionValidity(true);
|
||||||
|
|
||||||
|
if (!isEmail(email)) {
|
||||||
|
return onDonationStateChange({
|
||||||
|
error: t('donate.need-email')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { error, token } = await stripe.createToken(
|
||||||
|
elements.getElement(CardNumberElement),
|
||||||
|
{ email }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return onDonationStateChange({
|
||||||
|
error: t('donate.went-wrong')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return postStripeDonation(token);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEmailChange = e => {
|
||||||
|
const newValue = e.target.value;
|
||||||
|
setEmail(newValue);
|
||||||
|
setEmailValidity(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEmailBlur = () => {
|
||||||
|
const newValidation = isEmail(email);
|
||||||
|
setEmailValidity(newValidation);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderErrorMessage = () => {
|
||||||
|
let message = '';
|
||||||
|
if (!isEmailValid && !isPaymentInfoValid())
|
||||||
|
message = <p>{t('donate.valid-info')}</p>;
|
||||||
|
else if (!isEmailValid) message = <p>{t('donate.valid-email')}</p>;
|
||||||
|
else message = <p>{t('donate.valid-card')}</p>;
|
||||||
|
return <Alert bsStyle='danger'>{message}</Alert>;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<Form className='donation-form' onSubmit={handleSubmit}>
|
||||||
|
<div>{!isSubmissionValid ? renderErrorMessage() : ''}</div>
|
||||||
|
<FormGroup className='donation-email-container'>
|
||||||
|
<ControlLabel>{t('donate.email-receipt')}</ControlLabel>
|
||||||
|
<FormControl
|
||||||
|
className={!isEmailValid && email ? 'email--invalid' : ''}
|
||||||
|
key='3'
|
||||||
|
onBlur={handleEmailBlur}
|
||||||
|
onChange={handleEmailChange}
|
||||||
|
placeholder='me@example.com'
|
||||||
|
required={true}
|
||||||
|
type='text'
|
||||||
|
value={email || ''}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
<div className='donation-elements'>
|
<div className='donation-elements'>
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<ControlLabel>{t('donate.card-number')}</ControlLabel>
|
<ControlLabel>{t('donate.card-number')}</ControlLabel>
|
||||||
<CardNumberElement
|
<CardNumberElement
|
||||||
className='form-control donate-input-element'
|
className='form-control donate-input-element'
|
||||||
onChange={this.handleInputChange}
|
onChange={handleInputChange}
|
||||||
style={style}
|
options={options}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
@ -91,8 +162,8 @@ class StripeCardForm extends Component {
|
|||||||
<Col md={5} xs={12}>
|
<Col md={5} xs={12}>
|
||||||
<CardExpiryElement
|
<CardExpiryElement
|
||||||
className='form-control donate-input-element'
|
className='form-control donate-input-element'
|
||||||
onChange={this.handleInputChange}
|
onChange={handleInputChange}
|
||||||
style={style}
|
options={options}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col className='form-payments-wrapper' md={7} xs={12}>
|
<Col className='form-payments-wrapper' md={7} xs={12}>
|
||||||
@ -108,9 +179,18 @@ class StripeCardForm extends Component {
|
|||||||
</Row>
|
</Row>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</div>
|
</div>
|
||||||
|
<Button
|
||||||
|
block={true}
|
||||||
|
bsStyle='primary'
|
||||||
|
disabled={!stripe}
|
||||||
|
id='confirm-donation-btn'
|
||||||
|
type='submit'
|
||||||
|
>
|
||||||
|
{getDonationButtonLabel()}
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
StripeCardForm.displayName = 'StripeCardForm';
|
StripeCardForm.displayName = 'StripeCardForm';
|
||||||
StripeCardForm.propTypes = propTypes;
|
StripeCardForm.propTypes = propTypes;
|
||||||
|
Reference in New Issue
Block a user