feat(donate):add donation modal and certification message (#37822)
Co-Authored-By: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
@@ -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
client/src/components/Donation/components/DonationModal.js
Normal file
98
client/src/components/Donation/components/DonationModal.js
Normal 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);
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
@@ -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 {
|
||||
|
@@ -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);
|
||||
}
|
||||
|
Reference in New Issue
Block a user