feat(client): unify GA and add to donation (#37984)
This commit is contained in:
committed by
mrugesh
parent
d07c85151b
commit
78df306707
@ -15,7 +15,8 @@ import {
|
||||
showCert,
|
||||
userFetchStateSelector,
|
||||
usernameSelector,
|
||||
isDonatingSelector
|
||||
isDonatingSelector,
|
||||
executeGA
|
||||
} from '../redux';
|
||||
import validCertNames from '../../utils/validCertNames';
|
||||
import { createFlashMessage } from '../components/Flash/redux';
|
||||
@ -37,6 +38,7 @@ const propTypes = {
|
||||
certDashedName: PropTypes.string,
|
||||
certName: PropTypes.string,
|
||||
createFlashMessage: PropTypes.func.isRequired,
|
||||
executeGA: PropTypes.func,
|
||||
fetchState: PropTypes.shape({
|
||||
pending: PropTypes.bool,
|
||||
complete: PropTypes.bool,
|
||||
@ -74,19 +76,20 @@ const mapStateToProps = (state, { certName }) => {
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators({ createFlashMessage, showCert }, dispatch);
|
||||
bindActionCreators({ createFlashMessage, showCert, executeGA }, dispatch);
|
||||
|
||||
class ShowCertification extends Component {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
this.state = {
|
||||
closeBtn: false,
|
||||
donationClosed: false
|
||||
isDonationSubmitted: false,
|
||||
isDonationDisplayed: false,
|
||||
isDonationClosed: false
|
||||
};
|
||||
|
||||
this.hideDonationSection = this.hideDonationSection.bind(this);
|
||||
this.showDonationCloseBtn = this.showDonationCloseBtn.bind(this);
|
||||
this.handleProcessing = this.handleProcessing.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -97,12 +100,53 @@ class ShowCertification extends Component {
|
||||
return null;
|
||||
}
|
||||
|
||||
hideDonationSection() {
|
||||
this.setState({ donationClosed: true });
|
||||
shouldComponentUpdate(nextProps) {
|
||||
const {
|
||||
userFetchState: { complete: userComplete },
|
||||
signedInUserName,
|
||||
isDonating,
|
||||
cert: { username = '' },
|
||||
executeGA
|
||||
} = nextProps;
|
||||
const { isDonationDisplayed } = this.state;
|
||||
|
||||
if (
|
||||
!isDonationDisplayed &&
|
||||
userComplete &&
|
||||
signedInUserName === username &&
|
||||
!isDonating
|
||||
) {
|
||||
this.setState({
|
||||
isDonationDisplayed: true
|
||||
});
|
||||
|
||||
executeGA({
|
||||
type: 'event',
|
||||
data: {
|
||||
category: 'Donation',
|
||||
action: 'Displayed Certificate Donation',
|
||||
nonInteraction: true
|
||||
}
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
showDonationCloseBtn() {
|
||||
this.setState({ closeBtn: true });
|
||||
hideDonationSection() {
|
||||
this.setState({ isDonationDisplayed: false, isDonationClosed: true });
|
||||
}
|
||||
|
||||
handleProcessing(duration, amount) {
|
||||
this.props.executeGA({
|
||||
type: 'event',
|
||||
data: {
|
||||
category: 'donation',
|
||||
action: 'certificate stripe form submission',
|
||||
label: duration,
|
||||
value: amount
|
||||
}
|
||||
});
|
||||
this.setState({ isDonationSubmitted: true });
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -111,13 +155,14 @@ class ShowCertification extends Component {
|
||||
fetchState,
|
||||
validCertName,
|
||||
createFlashMessage,
|
||||
certName,
|
||||
signedInUserName,
|
||||
isDonating,
|
||||
userFetchState
|
||||
certName
|
||||
} = this.props;
|
||||
|
||||
const { donationClosed, closeBtn } = this.state;
|
||||
const {
|
||||
isDonationSubmitted,
|
||||
isDonationDisplayed,
|
||||
isDonationClosed
|
||||
} = this.state;
|
||||
|
||||
if (!validCertName) {
|
||||
createFlashMessage(standardErrorMessage);
|
||||
@ -125,7 +170,6 @@ class ShowCertification extends Component {
|
||||
}
|
||||
|
||||
const { pending, complete, errored } = fetchState;
|
||||
const { complete: userComplete } = userFetchState;
|
||||
|
||||
if (pending) {
|
||||
return <Loader fullScreen={true} />;
|
||||
@ -149,8 +193,6 @@ class ShowCertification extends Component {
|
||||
completionTime
|
||||
} = cert;
|
||||
|
||||
let conditionalDonationSection = '';
|
||||
|
||||
const donationCloseBtn = (
|
||||
<div>
|
||||
<Button
|
||||
@ -164,15 +206,9 @@ class ShowCertification extends Component {
|
||||
</div>
|
||||
);
|
||||
|
||||
if (
|
||||
userComplete &&
|
||||
signedInUserName === username &&
|
||||
!isDonating &&
|
||||
!donationClosed
|
||||
) {
|
||||
conditionalDonationSection = (
|
||||
let donationSection = (
|
||||
<Grid className='donation-section'>
|
||||
{!closeBtn && (
|
||||
{!isDonationSubmitted && (
|
||||
<Row>
|
||||
<Col sm={10} smOffset={1} xs={12}>
|
||||
<p>
|
||||
@ -186,21 +222,20 @@ class ShowCertification extends Component {
|
||||
</Row>
|
||||
)}
|
||||
<MinimalDonateForm
|
||||
showCloseBtn={this.showDonationCloseBtn}
|
||||
handleProcessing={this.handleProcessing}
|
||||
defaultTheme='light'
|
||||
/>
|
||||
<Row>
|
||||
<Col sm={4} smOffset={4} xs={6} xsOffset={3}>
|
||||
{closeBtn ? donationCloseBtn : ''}
|
||||
{isDonationSubmitted && donationCloseBtn}
|
||||
</Col>
|
||||
</Row>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='certificate-outer-wrapper'>
|
||||
{conditionalDonationSection}
|
||||
{isDonationDisplayed && !isDonationClosed ? donationSection : ''}
|
||||
<Grid className='certificate-wrapper certification-namespace'>
|
||||
<Row>
|
||||
<header>
|
||||
|
@ -34,6 +34,7 @@ const numToCommas = num =>
|
||||
num.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
|
||||
|
||||
const propTypes = {
|
||||
handleProcessing: PropTypes.func,
|
||||
isDonating: PropTypes.bool,
|
||||
isSignedIn: PropTypes.bool,
|
||||
navigate: PropTypes.func.isRequired,
|
||||
@ -191,7 +192,7 @@ class DonateForm extends Component {
|
||||
}
|
||||
|
||||
renderDonationOptions() {
|
||||
const { stripe } = this.props;
|
||||
const { stripe, handleProcessing } = this.props;
|
||||
const { donationAmount, donationDuration, paymentType } = this.state;
|
||||
return (
|
||||
<div>
|
||||
@ -203,6 +204,7 @@ class DonateForm extends Component {
|
||||
donationAmount={donationAmount}
|
||||
donationDuration={donationDuration}
|
||||
getDonationButtonLabel={this.getDonationButtonLabel}
|
||||
handleProcessing={handleProcessing}
|
||||
hideAmountOptionsCB={this.hideAmountOptionsCB}
|
||||
/>
|
||||
</Elements>
|
||||
|
@ -24,6 +24,7 @@ const propTypes = {
|
||||
donationDuration: PropTypes.string.isRequired,
|
||||
email: PropTypes.string,
|
||||
getDonationButtonLabel: PropTypes.func.isRequired,
|
||||
handleProcessing: PropTypes.func,
|
||||
isSignedIn: PropTypes.bool,
|
||||
showCloseBtn: PropTypes.func,
|
||||
stripe: PropTypes.shape({
|
||||
@ -148,8 +149,11 @@ class DonateFormChildViewForHOC extends Component {
|
||||
|
||||
// change the donation modal button label to close
|
||||
// or display the close button for the cert donation section
|
||||
if (this.props.showCloseBtn) {
|
||||
this.props.showCloseBtn();
|
||||
if (this.props.handleProcessing) {
|
||||
this.props.handleProcessing(
|
||||
this.state.donationDuration,
|
||||
Math.round(this.state.donationAmount / 100)
|
||||
);
|
||||
}
|
||||
|
||||
return postChargeStripe(yearEndGift, {
|
||||
|
@ -11,11 +11,11 @@ import Heart from '../../assets/icons/Heart';
|
||||
import Cup from '../../assets/icons/Cup';
|
||||
import MinimalDonateForm from './MinimalDonateForm';
|
||||
|
||||
import ga from '../../analytics';
|
||||
import {
|
||||
closeDonationModal,
|
||||
isDonationModalOpenSelector,
|
||||
isBlockDonationModalSelector
|
||||
isBlockDonationModalSelector,
|
||||
executeGA
|
||||
} from '../../redux';
|
||||
|
||||
import { challengeMetaSelector } from '../../templates/Challenges/redux';
|
||||
@ -36,7 +36,8 @@ const mapStateToProps = createSelector(
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{
|
||||
closeDonationModal
|
||||
closeDonationModal,
|
||||
executeGA
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
@ -45,18 +46,44 @@ const propTypes = {
|
||||
activeDonors: PropTypes.number,
|
||||
block: PropTypes.string,
|
||||
closeDonationModal: PropTypes.func.isRequired,
|
||||
executeGA: PropTypes.func,
|
||||
isBlockDonation: PropTypes.bool,
|
||||
show: PropTypes.bool
|
||||
};
|
||||
|
||||
function DonateModal({ show, block, isBlockDonation, closeDonationModal }) {
|
||||
function DonateModal({
|
||||
show,
|
||||
block,
|
||||
isBlockDonation,
|
||||
closeDonationModal,
|
||||
executeGA
|
||||
}) {
|
||||
const [closeLabel, setCloseLabel] = React.useState(false);
|
||||
const showCloseBtn = () => {
|
||||
const handleProcessing = (duration, amount) => {
|
||||
executeGA({
|
||||
type: 'event',
|
||||
data: {
|
||||
category: 'donation',
|
||||
action: 'Modal strip form submission',
|
||||
label: duration,
|
||||
value: amount
|
||||
}
|
||||
});
|
||||
setCloseLabel(true);
|
||||
};
|
||||
|
||||
if (show) {
|
||||
ga.modalview('/donation-modal');
|
||||
executeGA({ type: 'modal', data: '/donation-modal' });
|
||||
executeGA({
|
||||
type: 'event',
|
||||
data: {
|
||||
category: 'Donation',
|
||||
action: `Displayed ${
|
||||
isBlockDonation ? 'block' : 'progress'
|
||||
} donation modal`,
|
||||
nonInteraction: true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const donationText = (
|
||||
@ -101,7 +128,7 @@ function DonateModal({ show, block, isBlockDonation, closeDonationModal }) {
|
||||
<Modal.Body>
|
||||
{isBlockDonation ? blockDonationText : progressDonationText}
|
||||
<Spacer />
|
||||
<MinimalDonateForm showCloseBtn={showCloseBtn} />
|
||||
<MinimalDonateForm handleProcessing={handleProcessing} />
|
||||
<Spacer />
|
||||
<Row>
|
||||
<Col sm={4} smOffset={4} xs={8} xsOffset={2}>
|
||||
|
@ -19,8 +19,8 @@ import './Donation.css';
|
||||
|
||||
const propTypes = {
|
||||
defaultTheme: PropTypes.string,
|
||||
handleProcessing: PropTypes.func,
|
||||
isDonating: PropTypes.bool,
|
||||
showCloseBtn: PropTypes.func,
|
||||
stripe: PropTypes.shape({
|
||||
createToken: PropTypes.func.isRequired
|
||||
})
|
||||
@ -79,7 +79,7 @@ class MinimalDonateForm extends Component {
|
||||
|
||||
render() {
|
||||
const { donationAmount, donationDuration, stripe } = this.state;
|
||||
const { showCloseBtn, defaultTheme } = this.props;
|
||||
const { handleProcessing, defaultTheme } = this.props;
|
||||
|
||||
return (
|
||||
<Row>
|
||||
@ -93,7 +93,7 @@ class MinimalDonateForm extends Component {
|
||||
getDonationButtonLabel={() =>
|
||||
`Confirm your donation of $5 per month`
|
||||
}
|
||||
showCloseBtn={showCloseBtn}
|
||||
handleProcessing={handleProcessing}
|
||||
/>
|
||||
</Elements>
|
||||
</StripeProvider>
|
||||
|
@ -5,9 +5,8 @@ import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { Link } from 'gatsby';
|
||||
|
||||
import ga from '../../../analytics';
|
||||
import { makeExpandedBlockSelector, toggleBlock } from '../redux';
|
||||
import { completedChallengesSelector } from '../../../redux';
|
||||
import { completedChallengesSelector, executeGA } from '../../../redux';
|
||||
import Caret from '../../../assets/icons/Caret';
|
||||
import { blockNameify } from '../../../../utils/blockNameify';
|
||||
import GreenPass from '../../../assets/icons/GreenPass';
|
||||
@ -29,12 +28,13 @@ const mapStateToProps = (state, ownProps) => {
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators({ toggleBlock }, dispatch);
|
||||
bindActionCreators({ toggleBlock, executeGA }, dispatch);
|
||||
|
||||
const propTypes = {
|
||||
blockDashedName: PropTypes.string,
|
||||
challenges: PropTypes.array,
|
||||
completedChallenges: PropTypes.arrayOf(PropTypes.string),
|
||||
executeGA: PropTypes.func,
|
||||
intro: PropTypes.shape({
|
||||
fields: PropTypes.shape({ slug: PropTypes.string.isRequired }),
|
||||
frontmatter: PropTypes.shape({
|
||||
@ -58,19 +58,25 @@ export class Block extends Component {
|
||||
}
|
||||
|
||||
handleBlockClick() {
|
||||
const { blockDashedName, toggleBlock } = this.props;
|
||||
ga.event({
|
||||
const { blockDashedName, toggleBlock, executeGA } = this.props;
|
||||
executeGA({
|
||||
type: 'event',
|
||||
data: {
|
||||
category: 'Map Block Click',
|
||||
action: blockDashedName
|
||||
}
|
||||
});
|
||||
return toggleBlock(blockDashedName);
|
||||
}
|
||||
|
||||
handleChallengeClick(slug) {
|
||||
return () => {
|
||||
return ga.event({
|
||||
return this.props.executeGA({
|
||||
type: 'event',
|
||||
data: {
|
||||
category: 'Map Challenge Click',
|
||||
action: slug
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ test('<Block expanded snapshot', () => {
|
||||
test('<Block /> should handle toggle clicks correctly', async () => {
|
||||
const toggleSpy = jest.fn();
|
||||
const toggleMapSpy = jest.fn();
|
||||
const executeGA = jest.fn();
|
||||
|
||||
const props = {
|
||||
blockDashedName: 'block-a',
|
||||
@ -51,6 +52,7 @@ test('<Block /> should handle toggle clicks correctly', async () => {
|
||||
completedChallenges: mockCompleted,
|
||||
intro: mockIntroNodes[0],
|
||||
isExpanded: false,
|
||||
executeGA: executeGA,
|
||||
toggleBlock: toggleSpy,
|
||||
toggleMapModal: toggleMapSpy
|
||||
};
|
||||
|
@ -26,9 +26,10 @@ const numToCommas = num =>
|
||||
num.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
|
||||
|
||||
const propTypes = {
|
||||
showCloseBtn: PropTypes.func,
|
||||
handleProcessing: PropTypes.func,
|
||||
defaultTheme: PropTypes.string,
|
||||
isDonating: PropTypes.bool,
|
||||
executeGA: PropTypes.func,
|
||||
stripe: PropTypes.shape({
|
||||
createToken: PropTypes.func.isRequired
|
||||
})
|
||||
@ -47,6 +48,7 @@ class YearEndDonationForm extends Component {
|
||||
this.handleSelectAmount = this.handleSelectAmount.bind(this);
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
this.handlePaypalSubmission = this.handlePaypalSubmission.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -89,14 +91,13 @@ class YearEndDonationForm extends Component {
|
||||
|
||||
renderDonationOptions() {
|
||||
const { donationAmount, stripe } = this.state;
|
||||
|
||||
const { showCloseBtn, defaultTheme } = this.props;
|
||||
const { handleProcessing, defaultTheme } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<StripeProvider stripe={stripe}>
|
||||
<Elements>
|
||||
<DonateFormChildViewForHOC
|
||||
showCloseBtn={showCloseBtn}
|
||||
handleProcessing={handleProcessing}
|
||||
defaultTheme={defaultTheme}
|
||||
donationAmount={donationAmount}
|
||||
donationDuration='onetime'
|
||||
@ -179,12 +180,23 @@ class YearEndDonationForm extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
handlePaypalSubmission() {
|
||||
this.props.executeGA({
|
||||
type: 'event',
|
||||
data: {
|
||||
category: 'donation',
|
||||
action: 'year end gift paypal button click'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
renderPayPalDonations() {
|
||||
return (
|
||||
<form
|
||||
action='https://www.paypal.com/cgi-bin/webscr'
|
||||
method='post'
|
||||
target='_top'
|
||||
onSubmit={this.handlePaypalSubmission}
|
||||
>
|
||||
<input type='hidden' name='cmd' value='_s-xclick' />
|
||||
<input type='hidden' name='hosted_button_id' value='9C73W6CWSLNPW' />
|
||||
|
@ -2,8 +2,7 @@ import React, { Fragment, Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import ga from '../../analytics';
|
||||
import { fetchUser, isSignedInSelector } from '../../redux';
|
||||
import { fetchUser, isSignedInSelector, executeGA } from '../../redux';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
@ -13,7 +12,7 @@ const mapStateToProps = createSelector(
|
||||
})
|
||||
);
|
||||
|
||||
const mapDispatchToProps = { fetchUser };
|
||||
const mapDispatchToProps = { fetchUser, executeGA };
|
||||
|
||||
class CertificationLayout extends Component {
|
||||
componentDidMount() {
|
||||
@ -21,7 +20,7 @@ class CertificationLayout extends Component {
|
||||
if (!isSignedIn) {
|
||||
fetchUser();
|
||||
}
|
||||
ga.pageview(pathname);
|
||||
this.props.executeGA({ type: 'page', data: pathname });
|
||||
}
|
||||
render() {
|
||||
return <Fragment>{this.props.children}</Fragment>;
|
||||
@ -31,6 +30,7 @@ class CertificationLayout extends Component {
|
||||
CertificationLayout.displayName = 'CertificationLayout';
|
||||
CertificationLayout.propTypes = {
|
||||
children: PropTypes.any,
|
||||
executeGA: PropTypes.func,
|
||||
fetchUser: PropTypes.func.isRequired,
|
||||
isSignedIn: PropTypes.bool,
|
||||
pathname: PropTypes.string.isRequired
|
||||
|
@ -6,13 +6,13 @@ import { createSelector } from 'reselect';
|
||||
import Helmet from 'react-helmet';
|
||||
import fontawesome from '@fortawesome/fontawesome';
|
||||
|
||||
import ga from '../../analytics';
|
||||
import {
|
||||
fetchUser,
|
||||
isSignedInSelector,
|
||||
onlineStatusChange,
|
||||
isOnlineSelector,
|
||||
userSelector
|
||||
userSelector,
|
||||
executeGA
|
||||
} from '../../redux';
|
||||
import { flashMessageSelector, removeFlashMessage } from '../Flash/redux';
|
||||
|
||||
@ -68,6 +68,7 @@ const metaKeywords = [
|
||||
|
||||
const propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
executeGA: PropTypes.func,
|
||||
fetchUser: PropTypes.func.isRequired,
|
||||
flashMessage: PropTypes.shape({
|
||||
id: PropTypes.string,
|
||||
@ -101,27 +102,27 @@ const mapStateToProps = createSelector(
|
||||
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{ fetchUser, removeFlashMessage, onlineStatusChange },
|
||||
{ fetchUser, removeFlashMessage, onlineStatusChange, executeGA },
|
||||
dispatch
|
||||
);
|
||||
|
||||
class DefaultLayout extends Component {
|
||||
componentDidMount() {
|
||||
const { isSignedIn, fetchUser, pathname } = this.props;
|
||||
const { isSignedIn, fetchUser, pathname, executeGA } = this.props;
|
||||
if (!isSignedIn) {
|
||||
fetchUser();
|
||||
}
|
||||
ga.pageview(pathname);
|
||||
executeGA({ type: 'page', data: pathname });
|
||||
|
||||
window.addEventListener('online', this.updateOnlineStatus);
|
||||
window.addEventListener('offline', this.updateOnlineStatus);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { pathname } = this.props;
|
||||
const { pathname, executeGA } = this.props;
|
||||
const { pathname: prevPathname } = prevProps;
|
||||
if (pathname !== prevPathname) {
|
||||
ga.pageview(pathname);
|
||||
executeGA({ type: 'page', data: pathname });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import Helmet from 'react-helmet';
|
||||
import PropTypes from 'prop-types';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { Grid, Row, Col } from '@freecodecamp/react-bootstrap';
|
||||
@ -9,10 +10,11 @@ import { stripePublicKey } from '../../config/env.json';
|
||||
import { Spacer, Loader } from '../components/helpers';
|
||||
import DonateForm from '../components/Donation/DonateForm';
|
||||
import DonateText from '../components/Donation/DonateText';
|
||||
import { signInLoadingSelector, userSelector } from '../redux';
|
||||
import { signInLoadingSelector, userSelector, executeGA } from '../redux';
|
||||
import { stripeScriptLoader } from '../utils/scriptLoaders';
|
||||
|
||||
const propTypes = {
|
||||
executeGA: PropTypes.func,
|
||||
isDonating: PropTypes.bool,
|
||||
showLoading: PropTypes.bool.isRequired
|
||||
};
|
||||
@ -26,6 +28,14 @@ const mapStateToProps = createSelector(
|
||||
})
|
||||
);
|
||||
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{
|
||||
executeGA
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
||||
export class DonatePage extends Component {
|
||||
constructor(...props) {
|
||||
super(...props);
|
||||
@ -33,11 +43,19 @@ export class DonatePage extends Component {
|
||||
stripe: null,
|
||||
enableSettings: false
|
||||
};
|
||||
|
||||
this.handleProcessing = this.handleProcessing.bind(this);
|
||||
this.handleStripeLoad = this.handleStripeLoad.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.executeGA({
|
||||
type: 'event',
|
||||
data: {
|
||||
category: 'Donation',
|
||||
action: `Displayed donate page`,
|
||||
nonInteraction: true
|
||||
}
|
||||
});
|
||||
if (window.Stripe) {
|
||||
this.handleStripeLoad();
|
||||
} else if (document.querySelector('#stripe-js')) {
|
||||
@ -56,6 +74,18 @@ export class DonatePage extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleProcessing(duration, amount) {
|
||||
this.props.executeGA({
|
||||
type: 'event',
|
||||
data: {
|
||||
category: 'donation',
|
||||
action: 'donate page stripe form submission',
|
||||
label: duration,
|
||||
value: amount
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleStripeLoad() {
|
||||
// Create Stripe instance once Stripe.js loads
|
||||
console.info('stripe has loaded');
|
||||
@ -88,6 +118,7 @@ export class DonatePage extends Component {
|
||||
<Col md={6}>
|
||||
<DonateForm
|
||||
enableDonationSettingsPage={this.enableDonationSettingsPage}
|
||||
handleProcessing={this.handleProcessing}
|
||||
stripe={stripe}
|
||||
/>
|
||||
</Col>
|
||||
@ -105,4 +136,7 @@ export class DonatePage extends Component {
|
||||
DonatePage.displayName = 'DonatePage';
|
||||
DonatePage.propTypes = propTypes;
|
||||
|
||||
export default connect(mapStateToProps)(DonatePage);
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(DonatePage);
|
||||
|
@ -1,11 +1,48 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import Helmet from 'react-helmet';
|
||||
import { Grid } from '@freecodecamp/react-bootstrap';
|
||||
import { connect } from 'react-redux';
|
||||
import { executeGA } from '../redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Spacer, FullWidthRow } from '../components/helpers';
|
||||
import YearEndDonationForm from '../components/YearEndGift/YearEndDonationForm';
|
||||
|
||||
function YearEndGiftPage() {
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{
|
||||
executeGA
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
||||
const propTypes = {
|
||||
executeGA: PropTypes.func
|
||||
};
|
||||
|
||||
function YearEndGiftPage({ executeGA }) {
|
||||
useEffect(() => {
|
||||
executeGA({
|
||||
type: 'event',
|
||||
data: {
|
||||
category: 'Donation',
|
||||
action: `Displayed year end gift page`,
|
||||
nonInteraction: true
|
||||
}
|
||||
});
|
||||
}, [executeGA]);
|
||||
const handleProcessing = (duration, amount) => {
|
||||
executeGA({
|
||||
type: 'event',
|
||||
data: {
|
||||
category: 'donation',
|
||||
action: 'year-end-gift strip form submission',
|
||||
label: duration,
|
||||
value: amount
|
||||
}
|
||||
});
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Helmet title='Support our nonprofit | freeCodeCamp.org' />
|
||||
@ -13,7 +50,11 @@ function YearEndGiftPage() {
|
||||
<main>
|
||||
<Spacer />
|
||||
<FullWidthRow>
|
||||
<YearEndDonationForm defaultTheme='light' />
|
||||
<YearEndDonationForm
|
||||
defaultTheme='light'
|
||||
executeGA={executeGA}
|
||||
handleProcessing={handleProcessing}
|
||||
/>
|
||||
</FullWidthRow>
|
||||
<Spacer />
|
||||
<Spacer />
|
||||
@ -24,5 +65,9 @@ function YearEndGiftPage() {
|
||||
}
|
||||
|
||||
YearEndGiftPage.displayName = 'YearEndGiftPage';
|
||||
YearEndGiftPage.propTypes = propTypes;
|
||||
|
||||
export default YearEndGiftPage;
|
||||
export default connect(
|
||||
null,
|
||||
mapDispatchToProps
|
||||
)(YearEndGiftPage);
|
||||
|
11
client/src/redux/ga-saga.js
Normal file
11
client/src/redux/ga-saga.js
Normal file
@ -0,0 +1,11 @@
|
||||
import { takeEvery } from 'redux-saga/effects';
|
||||
import ga from '../analytics';
|
||||
|
||||
function* callGaType({ payload: { type, data } }) {
|
||||
const GaTypes = { event: ga.event, page: ga.pageview, modal: ga.modalview };
|
||||
GaTypes[type](data);
|
||||
}
|
||||
|
||||
export function createGaSaga(types) {
|
||||
return [takeEvery(types.executeGA, callGaType)];
|
||||
}
|
@ -11,6 +11,7 @@ import { createReportUserSaga } from './report-user-saga';
|
||||
import { createShowCertSaga } from './show-cert-saga';
|
||||
import { createNightModeSaga } from './night-mode-saga';
|
||||
import { createDonationSaga } from './donation-saga';
|
||||
import { createGaSaga } from './ga-saga';
|
||||
|
||||
import hardGoToEpic from './hard-go-to-epic';
|
||||
import failedUpdatesEpic from './failed-updates-epic';
|
||||
@ -65,6 +66,7 @@ export const types = createTypes(
|
||||
'onlineStatusChange',
|
||||
'resetUserData',
|
||||
'tryToShowDonationModal',
|
||||
'executeGA',
|
||||
'submitComplete',
|
||||
'updateComplete',
|
||||
'updateCurrentChallengeId',
|
||||
@ -84,6 +86,7 @@ export const sagas = [
|
||||
...createAcceptTermsSaga(types),
|
||||
...createAppMountSaga(types),
|
||||
...createDonationSaga(types),
|
||||
...createGaSaga(types),
|
||||
...createFetchUserSaga(types),
|
||||
...createShowCertSaga(types),
|
||||
...createReportUserSaga(types),
|
||||
@ -95,6 +98,9 @@ export const appMount = createAction(types.appMount);
|
||||
export const tryToShowDonationModal = createAction(
|
||||
types.tryToShowDonationModal
|
||||
);
|
||||
|
||||
export const executeGA = createAction(types.executeGA);
|
||||
|
||||
export const allowBlockDonationRequests = createAction(
|
||||
types.allowBlockDonationRequests
|
||||
);
|
||||
|
@ -6,10 +6,8 @@ import { createSelector } from 'reselect';
|
||||
import { Button, Modal } from '@freecodecamp/react-bootstrap';
|
||||
import { useStaticQuery, graphql } from 'gatsby';
|
||||
|
||||
import ga from '../../../analytics';
|
||||
import Login from '../../../components/Header/components/Login';
|
||||
import CompletionModalBody from './CompletionModalBody';
|
||||
|
||||
import { dasherize } from '../../../../../utils/slugs';
|
||||
|
||||
import './completion-modal.css';
|
||||
@ -25,7 +23,7 @@ import {
|
||||
lastBlockChalSubmitted
|
||||
} from '../redux';
|
||||
|
||||
import { isSignedInSelector } from '../../../redux';
|
||||
import { isSignedInSelector, executeGA } from '../../../redux';
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
challengeFilesSelector,
|
||||
@ -60,7 +58,8 @@ const mapDispatchToProps = function(dispatch) {
|
||||
},
|
||||
lastBlockChalSubmitted: () => {
|
||||
dispatch(lastBlockChalSubmitted());
|
||||
}
|
||||
},
|
||||
executeGA
|
||||
};
|
||||
return () => dispatchers;
|
||||
};
|
||||
@ -70,6 +69,7 @@ const propTypes = {
|
||||
close: PropTypes.func.isRequired,
|
||||
completedChallengesIds: PropTypes.array,
|
||||
currentBlockIds: PropTypes.array,
|
||||
executeGA: PropTypes.func,
|
||||
files: PropTypes.object.isRequired,
|
||||
id: PropTypes.string,
|
||||
isOpen: PropTypes.bool,
|
||||
@ -190,7 +190,7 @@ export class CompletionModalInner extends Component {
|
||||
const { completedPercent } = this.state;
|
||||
|
||||
if (isOpen) {
|
||||
ga.modalview('/completion-modal');
|
||||
executeGA({ type: 'modal', data: '/completion-modal' });
|
||||
}
|
||||
const dashedName = dasherize(title);
|
||||
return (
|
||||
|
@ -4,21 +4,22 @@ import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { Button, Modal } from '@freecodecamp/react-bootstrap';
|
||||
|
||||
import ga from '../../../analytics';
|
||||
import { createQuestion, closeModal, isHelpModalOpenSelector } from '../redux';
|
||||
import { executeGA } from '../../../redux';
|
||||
|
||||
import './help-modal.css';
|
||||
|
||||
const mapStateToProps = state => ({ isOpen: isHelpModalOpenSelector(state) });
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{ createQuestion, closeHelpModal: () => closeModal('help') },
|
||||
{ createQuestion, executeGA, closeHelpModal: () => closeModal('help') },
|
||||
dispatch
|
||||
);
|
||||
|
||||
const propTypes = {
|
||||
closeHelpModal: PropTypes.func.isRequired,
|
||||
createQuestion: PropTypes.func.isRequired,
|
||||
executeGA: PropTypes.func,
|
||||
isOpen: PropTypes.bool
|
||||
};
|
||||
|
||||
@ -27,9 +28,9 @@ const RSA =
|
||||
|
||||
export class HelpModal extends Component {
|
||||
render() {
|
||||
const { isOpen, closeHelpModal, createQuestion } = this.props;
|
||||
const { isOpen, closeHelpModal, createQuestion, executeGA } = this.props;
|
||||
if (isOpen) {
|
||||
ga.modalview('/help-modal');
|
||||
executeGA({ type: 'modal', data: '/help-modal' });
|
||||
}
|
||||
return (
|
||||
<Modal dialogClassName='help-modal' onHide={closeHelpModal} show={isOpen}>
|
||||
|
@ -5,13 +5,14 @@ import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { Button, Modal } from '@freecodecamp/react-bootstrap';
|
||||
|
||||
import ga from '../../../analytics';
|
||||
import { isResetModalOpenSelector, closeModal, resetChallenge } from '../redux';
|
||||
import { executeGA } from '../../../redux';
|
||||
|
||||
import './reset-modal.css';
|
||||
|
||||
const propTypes = {
|
||||
close: PropTypes.func.isRequired,
|
||||
executeGA: PropTypes.func,
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
reset: PropTypes.func.isRequired
|
||||
};
|
||||
@ -25,7 +26,11 @@ const mapStateToProps = createSelector(
|
||||
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{ close: () => closeModal('reset'), reset: () => resetChallenge() },
|
||||
{
|
||||
close: () => closeModal('reset'),
|
||||
executeGA,
|
||||
reset: () => resetChallenge()
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
||||
@ -35,7 +40,7 @@ function withActions(...fns) {
|
||||
|
||||
function ResetModal({ reset, close, isOpen }) {
|
||||
if (isOpen) {
|
||||
ga.modalview('/reset-modal');
|
||||
executeGA({ type: 'modal', data: '/reset-modal' });
|
||||
}
|
||||
return (
|
||||
<Modal
|
||||
|
@ -4,26 +4,30 @@ import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { Modal } from '@freecodecamp/react-bootstrap';
|
||||
|
||||
import ga from '../../../analytics';
|
||||
import { closeModal, isVideoModalOpenSelector } from '../redux';
|
||||
import { executeGA } from '../../../redux';
|
||||
|
||||
import './video-modal.css';
|
||||
|
||||
const mapStateToProps = state => ({ isOpen: isVideoModalOpenSelector(state) });
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators({ closeVideoModal: () => closeModal('video') }, dispatch);
|
||||
bindActionCreators(
|
||||
{ closeVideoModal: () => closeModal('video'), executeGA },
|
||||
dispatch
|
||||
);
|
||||
|
||||
const propTypes = {
|
||||
closeVideoModal: PropTypes.func.isRequired,
|
||||
executeGA: propTypes.func,
|
||||
isOpen: PropTypes.bool,
|
||||
videoUrl: PropTypes.string
|
||||
};
|
||||
|
||||
export class VideoModal extends Component {
|
||||
render() {
|
||||
const { isOpen, closeVideoModal, videoUrl } = this.props;
|
||||
const { isOpen, closeVideoModal, videoUrl, executeGA } = this.props;
|
||||
if (isOpen) {
|
||||
ga.modalview('/video-modal');
|
||||
executeGA({ type: 'modal', data: '/completion-modal' });
|
||||
}
|
||||
return (
|
||||
<Modal
|
||||
|
Reference in New Issue
Block a user