Skip to content

Commit

Permalink
feat: add donation modal and certification donation text
Browse files Browse the repository at this point in the history
  • Loading branch information
ahmaxed committed Nov 26, 2019
1 parent dc1b590 commit 2f3b5a1
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 23 deletions.
54 changes: 48 additions & 6 deletions client/src/client-only-routes/ShowCertification.js
Expand Up @@ -8,15 +8,19 @@ import { Grid, Row, Col, Image } from '@freecodecamp/react-bootstrap';
import {
showCertSelector,
showCertFetchStateSelector,
showCert
showCert,
usernameSelector,
isDonatingSelector,
isDonationRequestedSelector,
donationRequested
} from '../redux';
import validCertNames from '../../utils/validCertNames';
import { createFlashMessage } from '../components/Flash/redux';
import standardErrorMessage from '../utils/standardErrorMessage';
import reallyWeirdErrorMessage from '../utils/reallyWeirdErrorMessage';

import RedirectHome from '../components/RedirectHome';
import { Loader } from '../components/helpers';
import { Loader, Link } from '../components/helpers';

const propTypes = {
cert: PropTypes.shape({
Expand All @@ -30,13 +34,17 @@ const propTypes = {
certDashedName: PropTypes.string,
certName: PropTypes.string,
createFlashMessage: PropTypes.func.isRequired,
donationRequested: PropTypes.func,
fetchState: PropTypes.shape({
pending: PropTypes.bool,
complete: PropTypes.bool,
errored: PropTypes.bool
}),
isDonating: PropTypes.bool,
isDonationRequested: PropTypes.bool,
issueDate: PropTypes.string,
showCert: PropTypes.func.isRequired,
signedInUserName: PropTypes.string,
userFullName: PropTypes.string,
username: PropTypes.string,
validCertName: PropTypes.bool
Expand All @@ -47,16 +55,25 @@ const mapStateToProps = (state, { certName }) => {
return createSelector(
showCertSelector,
showCertFetchStateSelector,
(cert, fetchState) => ({
usernameSelector,
isDonatingSelector,
isDonationRequestedSelector,
(cert, fetchState, signedInUserName, isDonating, isDonationRequested) => ({
cert,
fetchState,
validCertName
validCertName,
signedInUserName,
isDonating,
isDonationRequested
})
);
};

const mapDispatchToProps = dispatch =>
bindActionCreators({ createFlashMessage, showCert }, dispatch);
bindActionCreators(
{ createFlashMessage, showCert, donationRequested },
dispatch
);

class ShowCertification extends Component {
componentDidMount() {
Expand All @@ -72,7 +89,11 @@ class ShowCertification extends Component {
fetchState,
validCertName,
createFlashMessage,
certName
certName,
donationRequested,
signedInUserName,
isDonating,
isDonationRequested
} = this.props;

if (!validCertName) {
Expand Down Expand Up @@ -103,8 +124,29 @@ class ShowCertification extends Component {
certTitle,
completionTime
} = cert;

let conditionalDonationMessage = '';
if (signedInUserName === username && !isDonating && !isDonationRequested) {
donationRequested();
conditionalDonationMessage = (
<Grid>
<Row className='certification-donation text-center'>
<p>
Only you can see this message. Congratulationson earning this
certification. It’s no easy task. Running freeCodeCamp isn’t 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>
<Link>Check out our donation dashboard</Link>
</Row>
</Grid>
);
}

return (
<div className='certificate-outer-wrapper'>
{conditionalDonationMessage}
<Grid className='certificate-wrapper certification-namespace'>
<Row>
<header>
Expand Down
95 changes: 95 additions & 0 deletions client/src/components/Donation/components/DonationModal.js
@@ -0,0 +1,95 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { Modal, Button } from '@freecodecamp/react-bootstrap';
import { Spacer, Link } from '../../../components/helpers';
import { blockNameify } from '../../../../utils/blockNameify';

import ga from '../../../analytics';
import {
closeDonationModal,
isDonationModalOpenSelector
} from '../../../redux';

import { challengeMetaSelector } from '../../../templates/Challenges/redux';

import '../Donation.css';

const mapStateToProps = createSelector(
isDonationModalOpenSelector,
challengeMetaSelector,
(show, { block }) => ({
show,
block
})
);

const mapDispatchToProps = dispatch =>
bindActionCreators(
{
closeDonationModal
},
dispatch
);

const propTypes = {
block: PropTypes.string,
closeDonationModal: PropTypes.func.isRequired,
show: PropTypes.bool
};

class DonateModal extends Component {
constructor(...props) {
super(...props);
}

render() {
const { show, block } = this.props;
if (show) {
ga.modalview('/donation-modal');
}

return (
<Modal bsSize='lg' className='donation-modal' show={show}>
<Modal.Header className='fcc-modal'>
<Modal.Title className='text-center'>
<strong>Support freeCodeCamp.org</strong>
</Modal.Title>
</Modal.Header>
<Modal.Body>
<Spacer />
<p>Nicely done. You just completed {blockNameify(block)}.</p>
<p>Help us create even more learning resources like this.</p>
<Spacer />
</Modal.Body>
<Modal.Footer>
<Link
className='btn-invert btn btn-lg btn-primary btn-block'
to={`/donate`}
>
Support our nonprofit
</Link>
<Button
block={true}
bsSize='lg'
bsStyle='primary'
className='btn-invert'
onClick={this.props.closeDonationModal}
>
Ask me later
</Button>
</Modal.Footer>
</Modal>
);
}
}

DonateModal.displayName = 'DonateModal';
DonateModal.propTypes = propTypes;

export default connect(
mapStateToProps,
mapDispatchToProps
)(DonateModal);
2 changes: 2 additions & 0 deletions client/src/components/layouts/Learn.js
Expand Up @@ -10,6 +10,7 @@ import {
isSignedInSelector
} from '../../redux';
import createRedirect from '../../components/createRedirect';
import DonateModal from '../Donation/components/DonationModal';

import 'prismjs/themes/prism.css';
import './prism.css';
Expand Down Expand Up @@ -47,6 +48,7 @@ function LearnLayout({
return (
<Fragment>
<main id='learn-app-wrapper'>{children}</main>
<DonateModal />
</Fragment>
);
}
Expand Down
14 changes: 14 additions & 0 deletions client/src/pages/certification.css
Expand Up @@ -35,6 +35,20 @@
display: flex;
align-items: center;
min-height: 100vh;
flex-direction: column;
}

.certificate-outer-wrapper .certification-donation {
padding: 15px 0px;
}
.certificate-outer-wrapper .certification-donation a {
padding: 5px 10px;
}

.certificate-outer-wrapper .certification-donation a:hover {
cursor: pointer;
background: var(--theme-color);
color: white;
}

.certification-namespace header {
Expand Down
20 changes: 5 additions & 15 deletions client/src/redux/index.js
Expand Up @@ -134,7 +134,8 @@ export const completedChallengesSelector = state =>
userSelector(state).completedChallenges || [];
export const completionCountSelector = state => state[ns].completionCount;
export const currentChallengeIdSelector = state => state[ns].currentChallengeId;
export const donationRequestedSelector = state => state[ns].donationRequested;
export const isDonationRequestedSelector = state => state[ns].donationRequested;
export const isDonatingSelector = state => userSelector(state).isDonating;

export const isOnlineSelector = state => state[ns].isOnline;
export const isSignedInSelector = state => !!state[ns].appUsername;
Expand All @@ -146,20 +147,9 @@ export const showCertSelector = state => state[ns].showCert;
export const showCertFetchStateSelector = state => state[ns].showCertFetchState;

export const showDonationSelector = state => {
const completedChallenges = completedChallengesSelector(state);
const completionCount = completionCountSelector(state);
const currentCompletedLength = completedChallenges.length;
const donationRequested = donationRequestedSelector(state);
// the user has not completed 9 challenges in total yet
if (currentCompletedLength < 9) {
return false;
}
// this will mean we are on the 10th submission in total for the user
if (completedChallenges.length === 9 && donationRequested === false) {
return true;
}
// this will mean we are on the 3rd submission for this browser session
if (completionCount === 2 && donationRequested === false) {
const isDonationRequested = isDonationRequestedSelector(state);
const isDonating = isDonatingSelector(state);
if (isDonationRequested === false && isDonating === false) {
return true;
}
return false;
Expand Down
22 changes: 20 additions & 2 deletions client/src/templates/Challenges/redux/completion-epic.js
@@ -1,6 +1,8 @@
import { of, empty } from 'rxjs';
import {
switchMap,
concatMap,
delay,
retry,
catchError,
concat,
Expand All @@ -25,7 +27,10 @@ import {
isSignedInSelector,
submitComplete,
updateComplete,
updateFailed
updateFailed,
openDonationModal,
showDonationSelector,
donationRequested
} from '../../../redux';

import postUpdate$ from '../utils/postUpdate$';
Expand Down Expand Up @@ -132,6 +137,10 @@ export default function completionEpic(action$, state$) {
const meta = challengeMetaSelector(state);
const { nextChallengePath, introPath, challengeType } = meta;
const closeChallengeModal = of(closeModal('completion'));
const openDonationModals = of(openDonationModal());
const requestDonation = of(donationRequested());
const shouldShowDonationModal = showDonationSelector(state);

let submitter = () => of({ type: 'no-user-signed-in' });
if (
!(challengeType in submitTypes) ||
Expand All @@ -149,7 +158,16 @@ export default function completionEpic(action$, state$) {
return submitter(type, state).pipe(
tap(() => navigate(introPath ? introPath : nextChallengePath)),
concat(closeChallengeModal),
filter(Boolean)
filter(Boolean),
concatMap(() => {
if (shouldShowDonationModal) {
return requestDonation.pipe(
delay(700),
concat(openDonationModals)
);
}
return empty();
})
);
})
);
Expand Down

0 comments on commit 2f3b5a1

Please sign in to comment.