Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(donate):add donation modal and certification message #37822

Merged
merged 37 commits into from Dec 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
a2b62cb
feat: add donation modal on block completion and donation message on …
ahmaxed Nov 27, 2019
2e15a1f
fix: make completion percentage use props
ahmaxed Nov 27, 2019
1c97678
fix: wait for completion modal to close and one second to pass before…
ahmaxed Nov 27, 2019
4bda3ff
fix: close Donation modal on /donate redirection
ahmaxed Nov 27, 2019
f4798b6
fix: hide certificate message for the session if user clicks on /dona…
ahmaxed Nov 27, 2019
c045131
Update client/src/client-only-routes/ShowCertification.js
ahmaxed Nov 28, 2019
9e29936
WIP: trigger donation modal on all learn pages
ojeytonwilliams Nov 29, 2019
c53b62b
fix: only show modal if block has been finished
ojeytonwilliams Nov 29, 2019
6a642d9
fix: show donate message when cert is reloaded
ojeytonwilliams Nov 29, 2019
bb65992
fix: change styles and add heart svg
ahmaxed Nov 30, 2019
54a1ab7
feat: add heart anim
ahmaxed Dec 1, 2019
ff22e03
feat: add example test
ahmaxed Dec 1, 2019
1566045
feat: adjust syles
ahmaxed Dec 1, 2019
136c12b
Update client/src/assets/icons/Heart.js
ahmaxed Dec 2, 2019
4b91382
Update client/src/assets/icons/Heart.js
ahmaxed Dec 2, 2019
83342a0
Update client/src/assets/icons/Heart.js
ahmaxed Dec 2, 2019
1c7f19d
Update client/src/assets/icons/Heart.js
ahmaxed Dec 2, 2019
5f92379
Update client/src/components/Donation/components/DonationModal.js
ahmaxed Dec 2, 2019
0ff431e
Update client/src/templates/Challenges/redux/current-challenge-saga.t…
ahmaxed Dec 2, 2019
a4596b9
Update client/src/templates/Challenges/redux/current-challenge-saga.t…
ahmaxed Dec 2, 2019
9b24778
Update client/src/templates/Challenges/redux/current-challenge-saga.t…
ahmaxed Dec 2, 2019
c8f4bd3
Update client/src/templates/Challenges/redux/current-challenge-saga.t…
ahmaxed Dec 2, 2019
cd53ba9
Update client/src/templates/Challenges/redux/current-challenge-saga.t…
ahmaxed Dec 2, 2019
ca3abfa
Update client/src/components/Donation/Donation.css
ahmaxed Dec 2, 2019
a0f5402
Update client/src/components/Donation/Donation.css
ahmaxed Dec 2, 2019
3791520
Update client/src/templates/Challenges/redux/current-challenge-saga.js
ahmaxed Dec 2, 2019
78c4e03
Update client/src/templates/Challenges/redux/current-challenge-saga.js
ahmaxed Dec 2, 2019
26b766d
Update client/src/redux/index.js
ahmaxed Dec 2, 2019
aeef0f5
Update client/src/redux/index.js
ahmaxed Dec 2, 2019
e93c6fa
Update client/src/redux/index.js
ahmaxed Dec 2, 2019
8f65322
Update client/src/templates/Challenges/redux/current-challenge-saga.js
ahmaxed Dec 2, 2019
4fa07fb
Update client/src/templates/Challenges/redux/current-challenge-saga.js
ahmaxed Dec 2, 2019
01b7c14
refactor: make action name more descriptive
ojeytonwilliams Dec 2, 2019
593024b
Update client/src/components/Donation/components/DonationModal.js
ahmaxed Dec 2, 2019
4470850
Update client/src/components/Donation/components/DonationModal.js
ahmaxed Dec 2, 2019
e3bb6c6
Update client/src/components/Donation/components/DonationModal.js
ahmaxed Dec 2, 2019
aec0feb
Update client/src/components/Donation/components/DonationModal.js
ahmaxed Dec 2, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
38 changes: 38 additions & 0 deletions client/src/assets/icons/Heart.js
@@ -0,0 +1,38 @@
import React, { Fragment } from 'react';

const propTypes = {};

function Heart(props) {
return (
<Fragment>
<span className='sr-only'>Heart</span>
<svg
height={184}
version='1.1'
viewBox='0 0 200 184'
width={200}
xmlns='http://www.w3.org/2000/svg'
{...props}
>
<g fill='none' fillRule='evenodd'>
<g fill='var(--love-color)'>
<ellipse cx='140.5' cy={59} id='a' rx='59.5' ry={59} />
<circle cx={59} cy={59} r={59} />
<rect
height={118}
transform='translate(100 100) rotate(-45) translate(-100 -100)'
width={118}
x={41}
y={41}
/>
</g>
</g>
</svg>
</Fragment>
);
}

Heart.displayName = 'Heart';
Heart.propTypes = propTypes;

export default Heart;
80 changes: 74 additions & 6 deletions client/src/client-only-routes/ShowCertification.js
Expand Up @@ -8,15 +8,20 @@ import { Grid, Row, Col, Image } from '@freecodecamp/react-bootstrap';
import {
showCertSelector,
showCertFetchStateSelector,
showCert
showCert,
userFetchStateSelector,
usernameSelector,
isDonatingSelector,
isDonationRequestedSelector,
preventDonationRequests
} 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 @@ -35,8 +40,15 @@ const propTypes = {
complete: PropTypes.bool,
errored: PropTypes.bool
}),
isDonating: PropTypes.bool,
isDonationRequested: PropTypes.bool,
issueDate: PropTypes.string,
preventDonationRequests: PropTypes.func,
showCert: PropTypes.func.isRequired,
signedInUserName: PropTypes.string,
userFetchState: PropTypes.shape({
complete: PropTypes.bool
}),
userFullName: PropTypes.string,
username: PropTypes.string,
validCertName: PropTypes.bool
Expand All @@ -47,16 +59,34 @@ const mapStateToProps = (state, { certName }) => {
return createSelector(
showCertSelector,
showCertFetchStateSelector,
(cert, fetchState) => ({
usernameSelector,
userFetchStateSelector,
isDonatingSelector,
isDonationRequestedSelector,
(
cert,
fetchState,
signedInUserName,
userFetchState,
isDonating,
isDonationRequested
) => ({
cert,
fetchState,
validCertName
validCertName,
signedInUserName,
userFetchState,
isDonating,
isDonationRequested
})
);
};

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

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

if (!validCertName) {
Expand All @@ -81,6 +116,7 @@ class ShowCertification extends Component {
}

const { pending, complete, errored } = fetchState;
const { complete: userComplete } = userFetchState;

if (pending) {
return <Loader fullScreen={true} />;
Expand All @@ -103,8 +139,40 @@ class ShowCertification extends Component {
certTitle,
completionTime
} = cert;

let conditionalDonationMessage = '';

if (
userComplete &&
signedInUserName === username &&
!isDonating &&
!isDonationRequested
) {
conditionalDonationMessage = (
<Grid>
<Row className='certification-donation text-center'>
<p>
Only you can see this message. Congratulations on 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
className={'btn'}
onClick={preventDonationRequests}
to={'/donate'}
>
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
56 changes: 56 additions & 0 deletions client/src/components/Donation/Donation.css
Expand Up @@ -162,3 +162,59 @@ li.disabled > a {
.servicebot-embed-panel .panel {
padding: 20px;
}

.heart-icon-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 40px;
}

.heart-icon {
width: 150px;
height: auto;
transform: scale(1.5);
opacity: 0;
animation: heart-icon-animation 1s linear 100ms forwards;
}

@keyframes heart-icon-animation {
33% {
transform: scale(1.2);
}
66% {
transform: scale(1.25);
}
100% {
opacity: 1;
transform: scale(1);
}
}

.donation-modal p {
margin: 0;
text-align: center;
font-weight: 700;
font-size: 1.2rem;
}

.donation-modal .modal-title {
text-align: center;
font-weight: 700;
font-size: 1.5rem;
}

@media screen and (max-width: 991px) {
.heart-icon-container {
margin: 30px;
}
.donation-modal p {
font-weight: 400;
font-size: 1rem;
}
.donation-modal .modal-title {
font-weight: 600;
font-size: 1.2rem;
}
}
98 changes: 98 additions & 0 deletions client/src/components/Donation/components/DonationModal.js
@@ -0,0 +1,98 @@
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 { Link } from '../../../components/helpers';
import { blockNameify } from '../../../../utils/blockNameify';
import Heart from '../../../assets/icons/Heart';

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 {
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='modal-title text-center'>
<strong>Support freeCodeCamp.org</strong>
</Modal.Title>
</Modal.Header>
<Modal.Body>
<p className='text-center'>
Nicely done. You just completed {blockNameify(block)}.
</p>
<div className='heart-icon-container'>
<Heart className='heart-icon' />
</div>
<p className='text-center'>
Help us create even more learning resources like this.
</p>
</Modal.Body>
<Modal.Footer>
<Link
className='btn-invert btn btn-lg btn-primary btn-block btn-cta'
onClick={this.props.closeDonationModal}
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);
41 changes: 36 additions & 5 deletions client/src/components/layouts/Certification.js
@@ -1,11 +1,42 @@
import React, { Fragment } from 'react';
import React, { Fragment, Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';

function CertificationLayout({ children }) {
return <Fragment>{children}</Fragment>;
import ga from '../../analytics';
import { fetchUser, isSignedInSelector } from '../../redux';
import { createSelector } from 'reselect';

const mapStateToProps = createSelector(
isSignedInSelector,
isSignedIn => ({
isSignedIn
})
);

const mapDispatchToProps = { fetchUser };

class CertificationLayout extends Component {
componentDidMount() {
const { isSignedIn, fetchUser, pathname } = this.props;
if (!isSignedIn) {
fetchUser();
}
ga.pageview(pathname);
}
render() {
return <Fragment>{this.props.children}</Fragment>;
}
}

CertificationLayout.displayName = 'CertificationLayout';
CertificationLayout.propTypes = { children: PropTypes.any };
CertificationLayout.propTypes = {
children: PropTypes.any,
fetchUser: PropTypes.func.isRequired,
isSignedIn: PropTypes.bool,
pathname: PropTypes.string.isRequired
};

export default CertificationLayout;
export default connect(
mapStateToProps,
mapDispatchToProps
)(CertificationLayout);