feat(client): unify GA and add to donation (#37984)

This commit is contained in:
Ahmad Abdolsaheb
2019-12-31 20:59:32 +03:00
committed by mrugesh
parent d07c85151b
commit 78df306707
18 changed files with 304 additions and 109 deletions

View File

@ -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,43 +206,36 @@ class ShowCertification extends Component {
</div>
);
if (
userComplete &&
signedInUserName === username &&
!isDonating &&
!donationClosed
) {
conditionalDonationSection = (
<Grid className='donation-section'>
{!closeBtn && (
<Row>
<Col sm={10} smOffset={1} xs={12}>
<p>
Only you can see this message. Congratulations on earning this
certification. Its no easy task. Running freeCodeCamp isnt
easy either. Nor is it cheap. Help us help you and many other
people around the world. Make a tax-deductible supporting
donation to our nonprofit today.
</p>
</Col>
</Row>
)}
<MinimalDonateForm
showCloseBtn={this.showDonationCloseBtn}
defaultTheme='light'
/>
let donationSection = (
<Grid className='donation-section'>
{!isDonationSubmitted && (
<Row>
<Col sm={4} smOffset={4} xs={6} xsOffset={3}>
{closeBtn ? donationCloseBtn : ''}
<Col sm={10} smOffset={1} xs={12}>
<p>
Only you can see this message. Congratulations on earning this
certification. Its no easy task. Running freeCodeCamp isnt
easy either. Nor is it cheap. Help us help you and many other
people around the world. Make a tax-deductible supporting
donation to our nonprofit today.
</p>
</Col>
</Row>
</Grid>
);
}
)}
<MinimalDonateForm
handleProcessing={this.handleProcessing}
defaultTheme='light'
/>
<Row>
<Col sm={4} smOffset={4} xs={6} xsOffset={3}>
{isDonationSubmitted && donationCloseBtn}
</Col>
</Row>
</Grid>
);
return (
<div className='certificate-outer-wrapper'>
{conditionalDonationSection}
{isDonationDisplayed && !isDonationClosed ? donationSection : ''}
<Grid className='certificate-wrapper certification-namespace'>
<Row>
<header>

View File

@ -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>

View File

@ -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, {

View File

@ -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}>

View File

@ -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>

View File

@ -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({
category: 'Map Block Click',
action: blockDashedName
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({
category: 'Map Challenge Click',
action: slug
return this.props.executeGA({
type: 'event',
data: {
category: 'Map Challenge Click',
action: slug
}
});
};
}

View File

@ -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
};

View File

@ -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' />

View File

@ -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

View File

@ -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 });
}
}

View File

@ -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);

View File

@ -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);

View 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)];
}

View File

@ -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
);

View File

@ -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 (

View File

@ -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}>

View File

@ -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

View File

@ -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