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

Co-Authored-By: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
Ahmad Abdolsaheb
2019-12-02 15:48:53 +03:00
committed by GitHub
parent 9866d2f241
commit a9bbcda211
17 changed files with 503 additions and 89 deletions

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
import React, { Fragment } from 'react';
import React, { Fragment, Component } from 'react';
import PropTypes from 'prop-types';
import { createSelector } from 'reselect';
import { connect } from 'react-redux';
@@ -7,9 +7,11 @@ import { Loader } from '../../components/helpers';
import {
userSelector,
userFetchStateSelector,
isSignedInSelector
isSignedInSelector,
tryToShowDonationModal
} from '../../redux';
import createRedirect from '../../components/createRedirect';
import DonateModal from '../Donation/components/DonationModal';
import 'prismjs/themes/prism.css';
import './prism.css';
@@ -28,27 +30,40 @@ const mapStateToProps = createSelector(
})
);
const mapDispatchToProps = {
tryToShowDonationModal
};
const RedirectAcceptPrivacyTerm = createRedirect('/accept-privacy-terms');
function LearnLayout({
fetchState: { pending, complete },
isSignedIn,
user: { acceptedPrivacyTerms },
children
}) {
if (pending && !complete) {
return <Loader fullScreen={true} />;
class LearnLayout extends Component {
componentDidMount() {
this.props.tryToShowDonationModal();
}
if (isSignedIn && !acceptedPrivacyTerms) {
return <RedirectAcceptPrivacyTerm />;
}
render() {
const {
fetchState: { pending, complete },
isSignedIn,
user: { acceptedPrivacyTerms },
children
} = this.props;
return (
<Fragment>
<main id='learn-app-wrapper'>{children}</main>
</Fragment>
);
if (pending && !complete) {
return <Loader fullScreen={true} />;
}
if (isSignedIn && !acceptedPrivacyTerms) {
return <RedirectAcceptPrivacyTerm />;
}
return (
<Fragment>
<main id='learn-app-wrapper'>{children}</main>
<DonateModal />
</Fragment>
);
}
}
LearnLayout.displayName = 'LearnLayout';
@@ -60,9 +75,13 @@ LearnLayout.propTypes = {
errored: PropTypes.bool
}),
isSignedIn: PropTypes.bool,
tryToShowDonationModal: PropTypes.func.isRequired,
user: PropTypes.shape({
acceptedPrivacyTerms: PropTypes.bool
})
};
export default connect(mapStateToProps)(LearnLayout);
export default connect(
mapStateToProps,
mapDispatchToProps
)(LearnLayout);

View File

@@ -208,18 +208,19 @@ fieldset[disabled] .btn-primary.focus {
}
.btn-cta {
background-color: #ffac33;
background-image: linear-gradient(#ffcc4d, #ffac33);
border-color: #f1a02a;
background-color: #feac32;
background-image: linear-gradient(#fecc4c, #ffac33);
border-width: 3px;
border-color: #feac32;
color: #0a0a23 !important;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
border: none;
}
.btn-cta:hover,
.btn-cta:focus,
.btn-cta:active:hover {
background-color: #e99110;
background-image: linear-gradient(#ffcc4d, #e99110);
background-color: #fecc4c;
border-width: 3px;
border-color: #f1a02a;
background-image: none;
color: #0a0a23 !important;
}
.btn-cta:active {

View File

@@ -19,6 +19,8 @@
--green-dark: #00471b;
--red-light: #ffadad;
--red-dark: #850000;
--love-light: #f8577c;
--love-dark: #f82153;
}
.dark-palette {
@@ -36,6 +38,7 @@
--success-background: var(--green-dark);
--danger-color: var(--red-light);
--danger-background: var(--red-dark);
--love-color: var(--love-light);
}
.light-palette {
@@ -53,4 +56,5 @@
--success-background: var(--green-light);
--danger-color: var(--red-dark);
--danger-background: var(--red-light);
--love-color: var(--love-dark);
}