Skip to content

Commit

Permalink
feat(donate):add donation modal and certification message (freeCodeCa…
Browse files Browse the repository at this point in the history
…mp#37822)

Co-Authored-By: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
  • Loading branch information
2 people authored and abbathaw committed Jul 24, 2020
1 parent e356557 commit c75b216
Show file tree
Hide file tree
Showing 17 changed files with 503 additions and 89 deletions.
38 changes: 38 additions & 0 deletions client/src/assets/icons/Heart.js
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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);
Loading

0 comments on commit c75b216

Please sign in to comment.