feat: update donation plans on donate page. (#40102)

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
Ahmad Abdolsaheb
2020-11-06 14:30:14 +03:00
committed by GitHub
parent 7ce85a8f03
commit ca369b8585
10 changed files with 218 additions and 195 deletions

View File

@ -12,9 +12,6 @@ import {
ToggleButton, ToggleButton,
ToggleButtonGroup ToggleButtonGroup
} from '@freecodecamp/react-bootstrap'; } from '@freecodecamp/react-bootstrap';
import ApplePay from './assets/ApplePay';
import GooglePay from './assets/GooglePay';
import acceptedCards from './assets/accepted-cards.png';
import { import {
amountsConfig, amountsConfig,
durationsConfig, durationsConfig,
@ -241,7 +238,7 @@ class DonateForm extends Component {
const { donationAmount, donationDuration, processing } = this.state; const { donationAmount, donationDuration, processing } = this.state;
return !processing ? ( return !processing ? (
<div> <div>
<h3>Duration and amount:</h3> <h3>Select gift frequency:</h3>
<Tabs <Tabs
activeKey={donationDuration} activeKey={donationDuration}
animation={false} animation={false}
@ -257,6 +254,7 @@ class DonateForm extends Component {
title={this.durations[duration]} title={this.durations[duration]}
> >
<Spacer /> <Spacer />
<h3>Select gift amount:</h3>
<div> <div>
<ToggleButtonGroup <ToggleButtonGroup
animation={`false`} animation={`false`}
@ -306,55 +304,25 @@ class DonateForm extends Component {
</b> </b>
)} )}
<Spacer /> <Spacer />
<Button <div className='donate-btn-group'>
block={true} <Button
bsStyle='primary' block={true}
className='btn-cta' bsStyle='primary'
id='confirm-donation-btn' id='confirm-donation-btn'
onClick={e => this.handleStripeCheckoutRedirect(e, 'apple pay')} onClick={e => this.handleStripeCheckoutRedirect(e, 'credit card')}
> >
<span>Donate with Apple Pay</span> <b>Credit Card</b>
</Button>
<ApplePay className='apple-pay-logo' /> <PaypalButton
</Button> addDonation={addDonation}
<Spacer /> donationAmount={donationAmount}
<Button donationDuration={donationDuration}
block={true} handleProcessing={handleProcessing}
bsStyle='primary' isSubscription={isOneTime ? false : true}
className='btn-cta' onDonationStateChange={this.onDonationStateChange}
id='confirm-donation-btn' skipAddDonation={!isSignedIn}
onClick={e => this.handleStripeCheckoutRedirect(e, 'google pay')}
>
<span>Donate with Google Pay</span>
<GooglePay className='google-pay-logo' />
</Button>
<Spacer />
<Button
block={true}
bsStyle='primary'
className='btn-cta'
id='confirm-donation-btn'
onClick={e => this.handleStripeCheckoutRedirect(e, 'credit card')}
>
<span>Donate with Card</span>
<img
alt='accepted cards'
className='accepted-cards'
src={acceptedCards}
/> />
</Button> </div>
<Spacer />
<PaypalButton
addDonation={addDonation}
donationAmount={donationAmount}
donationDuration={donationDuration}
handleProcessing={handleProcessing}
isSubscription={isOneTime ? false : true}
onDonationStateChange={this.onDonationStateChange}
skipAddDonation={!isSignedIn}
/>
<Spacer size={2} />
</div> </div>
); );
} }
@ -415,12 +383,8 @@ class DonateForm extends Component {
renderPageForm() { renderPageForm() {
return ( return (
<Row> <Row>
<Col sm={10} smOffset={1} xs={12}> <Col xs={12}>{this.renderDurationAmountOptions()}</Col>
{this.renderDurationAmountOptions()} <Col xs={12}>{this.renderDonationOptions()}</Col>
</Col>
<Col sm={10} smOffset={1} xs={12}>
{this.renderDonationOptions()}
</Col>
</Row> </Row>
); );
} }

View File

@ -0,0 +1,19 @@
import React from 'react';
import { Row, Col } from '@freecodecamp/react-bootstrap';
const DonateSupportText = () => (
<Row className='donate-text'>
<Col xs={12}>
<hr />
<h4>
<b>Need help with your current or past donations?</b>
</h4>
<p>
Forward a copy of your donation receipt to donors@freecodecamp.org and
tell us how we can help.
</p>
</Col>
</Row>
);
DonateSupportText.displayName = 'DonateText';
export default DonateSupportText;

View File

@ -4,17 +4,8 @@ import { Row, Col } from '@freecodecamp/react-bootstrap';
const DonateText = () => { const DonateText = () => {
return ( return (
<Row className='donate-text'> <Row className='donate-text'>
<Col sm={10} smOffset={1} xs={12}> <Col xs={12}>
<p>freeCodeCamp is a highly efficient education nonprofit.</p> <p>freeCodeCamp is a highly efficient education nonprofit.</p>
<p>
In 2019 alone, we provided 18 million hours of free education to
people around the world.
</p>
<p>
Since freeCodeCamp's total budget is only $373,000, that means every
dollar you donate to freeCodeCamp translates into 50 hours worth of
technology education.
</p>
<p> <p>
When you donate to freeCodeCamp, you help people learn new skills and When you donate to freeCodeCamp, you help people learn new skills and
provide for their families. provide for their families.
@ -23,14 +14,6 @@ const DonateText = () => {
You also help us create new resources for you to use to expand your You also help us create new resources for you to use to expand your
own technology skills. own technology skills.
</p> </p>
<hr />
<h4>
<b>Need help with your current or past donations?</b>
</h4>
<p>
Forward a copy of your donation receipt to donors@freecodecamp.org and
tell us how we can help.
</p>
</Col> </Col>
</Row> </Row>
); );

View File

@ -92,7 +92,7 @@
.donate-tabs > .nav-pills > li > a { .donate-tabs > .nav-pills > li > a {
text-transform: capitalize; text-transform: capitalize;
text-decoration: none; text-decoration: none;
border: 2px solid var(--yellow-light); border: 3px solid var(--yellow-light);
border-radius: 0px; border-radius: 0px;
color: var(--gray-85); color: var(--gray-85);
margin: 0 1px; margin: 0 1px;
@ -109,7 +109,7 @@
.donate-tabs > .nav-pills > li.active > a:focus { .donate-tabs > .nav-pills > li.active > a:focus {
color: var(--gray-85); color: var(--gray-85);
background-color: var(--yellow-light); background-color: var(--yellow-light);
border: 2px solid var(--yellow-light); border: 3px solid var(--yellow-light);
text-decoration: none; text-decoration: none;
border-radius: 0px; border-radius: 0px;
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
@ -125,13 +125,17 @@
} }
.amount-values > label { .amount-values > label {
margin: 0 2px !important; margin: 0 2px;
color: var(--gray-85); color: var(--gray-85);
border: 2px solid var(--yellow-light); border: 3px solid var(--yellow-light);
border-radius: 0px; border-radius: 0px;
background-color: transparent; background-color: transparent;
} }
.amount-values.btn-group .btn + .btn {
margin: 0 2px;
}
.amount-values > label:hover, .amount-values > label:hover,
.amount-values > label:focus, .amount-values > label:focus,
.amount-values > label:active:hover { .amount-values > label:active:hover {
@ -155,17 +159,36 @@
.amount-values > label.active:focus { .amount-values > label.active:focus {
color: var(--gray-85); color: var(--gray-85);
background-color: var(--yellow-light); background-color: var(--yellow-light);
border: 2px solid var(--yellow-light); border: 3px solid var(--yellow-light);
border-radius: 0px; border-radius: 0px;
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
} }
@media (max-width: 500px) {
.amount-values {
display: flex;
flex-wrap: wrap;
}
.amount-values > label {
width: 31.3%;
margin-bottom: 3px;
}
.amount-values.btn-group > .btn:first-child {
margin-left: 2px;
margin-bottom: 3px;
}
.amount-values.btn-group .btn + .btn {
/* margin: 0 2px; */
margin-bottom: 3px;
}
}
li.disabled { li.disabled {
cursor: not-allowed; cursor: not-allowed;
} }
li.disabled > a { li.disabled > a {
border: 2px solid var(--gray-15) !important; border: 3px solid var(--gray-15) !important;
color: var(--gray-15) !important; color: var(--gray-15) !important;
} }
@ -178,12 +201,6 @@ li.disabled > a {
font-family: 'Lato', sans-serif; font-family: 'Lato', sans-serif;
} }
@media (max-width: 991px) {
.donate-text {
margin-top: 30px;
}
}
@media (max-width: 400px) { @media (max-width: 400px) {
.donate-tabs > .nav-pills > li > a { .donate-tabs > .nav-pills > li > a {
font-size: 0.8rem; font-size: 0.8rem;
@ -342,13 +359,51 @@ li.disabled > a {
align-self: center; align-self: center;
} }
#confirm-donation-btn { button#confirm-donation-btn {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: center; justify-content: center;
align-content: center; align-content: center;
border-radius: 5px;
background-color: var(--yellow-light);
border-color: var(--yellow-light);
}
button#confirm-donation-btn:active,
button#confirm-donation-btn:active:focus,
button#confirm-donation-btn:hover {
color: var(--secondary-color);
background-color: #f2ba38;
border-color: #f2ba38;
} }
.hide { .hide {
display: none; display: none;
} }
.donate-btn-group {
display: flex;
flex-direction: column;
}
.donate-btn-group > * {
width: 100%;
height: 43px;
}
.donate-btn-group button:first-child {
margin-bottom: 10px;
}
@media (min-width: 500px) {
.donate-btn-group {
flex-direction: row;
}
.donate-btn-group > * {
width: 49%;
}
.donate-btn-group button:first-child {
margin-bottom: 0px;
margin-right: auto;
}
}

View File

@ -34,7 +34,7 @@ export class PayPalButtonScriptLoader extends Component {
loadScript(subscription, deleteScript) { loadScript(subscription, deleteScript) {
if (deleteScript) scriptRemover('paypal-sdk'); if (deleteScript) scriptRemover('paypal-sdk');
let queries = `?client-id=${this.props.clientId}&disable-funding=credit,card`; let queries = `?client-id=${this.props.clientId}&disable-funding=credit,card,bancontact,blik,eps,giropay,ideal,mybank,p24,sepa,sofort,venmo`;
if (subscription) queries += '&vault=true'; if (subscription) queries += '&vault=true';
scriptLoader( scriptLoader(

View File

@ -0,0 +1,47 @@
import React from 'react';
import PropTypes from 'prop-types';
import Media from 'react-responsive';
import { Spacer, ImageLoader } from '../../helpers';
import wideImg from '../../../assets/images/landing/wide-image.png';
const propTypes = {
page: PropTypes.string
};
const LARGE_SCREEN_SIZE = 1200;
const imageConfig = {
donate: {
spacerSize: 0,
height: 345,
width: 585
},
landing: {
spacerSize: 2,
height: 442,
width: 750
}
};
function CampersImage({ page }) {
const { spacerSize, height, width } = imageConfig[page];
return (
<Media minWidth={LARGE_SCREEN_SIZE}>
<Spacer size={spacerSize} />
<ImageLoader
alt='freeCodeCamp students at a local study group in South Korea.'
className='landing-page-image'
height={height}
src={wideImg}
width={width}
/>
<p className='text-center caption'>
freeCodeCamp students at a local study group in South Korea.
</p>
</Media>
);
}
CampersImage.displayName = 'CampersImage';
CampersImage.propTypes = propTypes;
export default CampersImage;

View File

@ -1,9 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Media from 'react-responsive';
import { Col, Row } from '@freecodecamp/react-bootstrap'; import { Col, Row } from '@freecodecamp/react-bootstrap';
import { Spacer, ImageLoader } from '../../helpers'; import { Spacer } from '../../helpers';
import wideImg from '../../../assets/images/landing/wide-image.png';
import Login from '../../Header/components/Login'; import Login from '../../Header/components/Login';
import { import {
AmazonLogo, AmazonLogo,
@ -12,29 +10,13 @@ import {
SpotifyLogo, SpotifyLogo,
GoogleLogo GoogleLogo
} from '../../../assets/images/components'; } from '../../../assets/images/components';
import CampersImage from './CampersImage';
const propTypes = { const propTypes = {
page: PropTypes.string page: PropTypes.string
}; };
const LARGE_SCREEN_SIZE = 1200;
function landingTop({ page }) { function landingTop({ page }) {
const landingImageSection = (
<Media minWidth={LARGE_SCREEN_SIZE}>
<Spacer size={2} />
<ImageLoader
alt='Freecodecamp students at a local study'
className='landing-page-image'
height={442}
src={wideImg}
width={750}
/>
<p className='text-center caption'>
freeCodeCamp students at a local study group in South Korea.
</p>
</Media>
);
const BigCallToAction = ( const BigCallToAction = (
<Login block={true} data-test-label={`${page}-big-cta`}> <Login block={true} data-test-label={`${page}-big-cta`}>
{page === 'landing' {page === 'landing'
@ -65,7 +47,7 @@ function landingTop({ page }) {
</div> </div>
<Spacer /> <Spacer />
{BigCallToAction} {BigCallToAction}
{landingImageSection} <CampersImage page={page} />
<Spacer /> <Spacer />
</Col> </Col>
</Row> </Row>

View File

@ -12,7 +12,7 @@
--gray-90: #0a0a23; --gray-90: #0a0a23;
--purple-light: #dbb8ff; --purple-light: #dbb8ff;
--purple-dark: #5a01a7; --purple-dark: #5a01a7;
--yellow-light: #f1be32; --yellow-light: #ffc300;
--yellow-dark: #4d3800; --yellow-dark: #4d3800;
--blue-light: #99c9ff; --blue-light: #99c9ff;
--blue-dark: #002ead; --blue-dark: #002ead;

View File

@ -9,7 +9,9 @@ import { Grid, Row, Col, Alert } from '@freecodecamp/react-bootstrap';
import { Spacer, Loader } from '../components/helpers'; import { Spacer, Loader } from '../components/helpers';
import DonateForm from '../components/Donation/DonateForm'; import DonateForm from '../components/Donation/DonateForm';
import DonateText from '../components/Donation/DonateText'; import DonateText from '../components/Donation/DonateText';
import DonateSupportText from '../components/Donation/DonateSupportText';
import { signInLoadingSelector, userSelector, executeGA } from '../redux'; import { signInLoadingSelector, userSelector, executeGA } from '../redux';
import CampersImage from '../components/landing/components/CampersImage';
const propTypes = { const propTypes = {
executeGA: PropTypes.func, executeGA: PropTypes.func,
@ -78,43 +80,42 @@ export class DonatePage extends Component {
<Helmet title='Support our nonprofit | freeCodeCamp.org' /> <Helmet title='Support our nonprofit | freeCodeCamp.org' />
<Grid className='donate-page-wrapper'> <Grid className='donate-page-wrapper'>
<Spacer /> <Spacer />
<Row>
<Col sm={10} smOffset={1} xs={12}>
<h1 className='text-center'>
{isDonating
? 'Thank You for Your Support'
: 'Become a Supporter'}
</h1>
<Spacer />
</Col>
</Row>
<Row> <Row>
<Fragment> <Fragment>
<Col md={6}> <Col lg={6} lgOffset={0} md={8} mdOffset={2} sm={10} smOffset={1}>
<Row> <Row className='donate-text'>
<Col sm={10} smOffset={1} xs={12}> <Col className={'text-center'} xs={12}>
{isDonating ? ( {isDonating ? (
<Alert> <h2>Thank you for your support</h2>
<p> ) : (
Thank you for being a supporter of freeCodeCamp. You <h2>Help us do more</h2>
currently have a recurring donation. )}
</p> <Spacer />
<br />
<p>
If you would like to make additional donations, those
will help our nonprofit and our mission, too.
</p>
</Alert>
) : null}
</Col> </Col>
</Row> </Row>
{isDonating ? (
<Alert>
<p>
Thank you for being a supporter of freeCodeCamp. You
currently have a recurring donation.
</p>
<br />
<p>
If you would like to make additional donations, those will
help our nonprofit and our mission, too.
</p>
</Alert>
) : null}
<DonateText isDonating={isDonating} />
<Spacer />
<DonateForm <DonateForm
enableDonationSettingsPage={this.enableDonationSettingsPage} enableDonationSettingsPage={this.enableDonationSettingsPage}
handleProcessing={this.handleProcessing} handleProcessing={this.handleProcessing}
/> />
<DonateSupportText />
</Col> </Col>
<Col md={6}> <Col lg={6}>
<DonateText /> <CampersImage page='donate' />
</Col> </Col>
</Fragment> </Fragment>
</Row> </Row>

View File

@ -1,18 +1,15 @@
// Configuration for client side // Configuration for client side
const durationsConfig = { const durationsConfig = {
year: 'yearly',
month: 'monthly', month: 'monthly',
onetime: 'one-time' onetime: 'one-time'
}; };
const amountsConfig = { const amountsConfig = {
year: [100000, 25000, 6000], month: [1000, 2000, 3000, 4000, 5000],
month: [25000, 3500, 500], onetime: [2500, 5000, 7500, 10000, 15000]
onetime: [100000, 25000, 6000]
}; };
const defaultAmount = { const defaultAmount = {
year: 25000, month: 1000,
month: 500, onetime: 2500
onetime: 25000
}; };
const defaultDonation = { const defaultDonation = {
donationAmount: defaultAmount['month'], donationAmount: defaultAmount['month'],
@ -25,79 +22,54 @@ const modalDefaultDonation = {
const onetimeSKUConfig = { const onetimeSKUConfig = {
live: [ live: [
{ amount: '100000', id: 'sku_GwHogRRJrCYGms' }, { amount: '15000', id: 'sku_IElisJHup0nojP' },
{ amount: '25000', id: 'sku_GwHnCde23uDH5R' }, { amount: '10000', id: 'sku_IEliodY88lglPk' },
{ amount: '6000', id: 'sku_H5mjFgpayAzJzT' } { amount: '7500', id: 'sku_IEli9AXW8DwNtT' },
{ amount: '5000', id: 'sku_IElhJxkNh9UgDp' },
{ amount: '2500', id: 'sku_IElhQtqLgKZC8y' }
], ],
staging: [ staging: [
{ amount: '100000', id: 'sku_GvAeUdWLsmGO9O' }, { amount: '15000', id: 'sku_IEPNpHACYJmUwz' },
{ amount: '25000', id: 'sku_GvAdXbsotjFi7G' }, { amount: '10000', id: 'sku_IEPMY1OXxnY4WU' },
{ amount: '6000', id: 'sku_GvAeJDgwjnGAdy' } { amount: '7500', id: 'sku_IEPLOotEqlMOWC' },
{ amount: '5000', id: 'sku_IEPKAxxAxfMnUI' },
{ amount: '2500', id: 'sku_IEPIgLRzViwq5z' }
] ]
}; };
// Configuration for server side // Configuration for server side
const durationKeysConfig = ['year', 'month', 'onetime']; const durationKeysConfig = ['month', 'onetime'];
const donationOneTimeConfig = [100000, 25000, 6000]; const donationOneTimeConfig = [100000, 25000, 6000];
const donationSubscriptionConfig = { const donationSubscriptionConfig = {
duration: { duration: {
year: 'Yearly',
month: 'Monthly' month: 'Monthly'
}, },
plans: { plans: {
year: [100000, 25000, 6000],
month: [25000, 3500, 500] month: [25000, 3500, 500]
} }
}; };
// Shared paypal configuration // Shared paypal configuration
// keep the 5 dollars for the modal
const paypalConfigTypes = { const paypalConfigTypes = {
live: { live: {
month: { month: {
'500': { '500': { planId: 'P-1L11422374370240ULZKX3PA' },
planId: 'P-1L11422374370240ULZKX3PA' '1000': { planId: 'P-61K21421WY874920PL6E36YI' },
}, '2000': { planId: 'P-31999436LF709112VL6E374A' },
'3500': { '3000': { planId: 'P-1KY930839N8045117L6E4BKY' },
planId: 'P-81U00703FF076883HLZ2PWMI' '4000': { planId: 'P-0JW4843250567551AL6E4CAI' },
}, '5000': { planId: 'P-0WR49877YD949401BL6E4CTA' }
'25000': {
planId: 'P-7M045671FN915794KLZ2PW6I'
}
},
year: {
'6000': {
planId: 'P-9Y661558DW462253NLZZ2IMQ'
},
'25000': {
planId: 'P-3NN39392MK1889318LZZ2KQY'
},
'100000': {
planId: 'P-7YN43286C4599382LLZZ2JUI'
}
} }
}, },
staging: { staging: {
month: { month: {
'500': { '500': { planId: 'P-37N14480BW163382FLZYPVMA' },
planId: 'P-37N14480BW163382FLZYPVMA' '1000': { planId: 'P-28B62039J8092810UL6E3FXA' },
}, '2000': { planId: 'P-7HR706961M9170433L6HI5VI' },
'3500': { '3000': { planId: 'P-35V33574BU596924JL6HI6XY' },
planId: 'P-3E678937P5715503NLZZTRVY' '4000': { planId: 'P-45M45060289267734L6HJSXA' },
}, '5000': { planId: 'P-0MD70861FY4172444L6HJTUQ' }
'25000': {
planId: 'P-97K80194AU368022JLZ2Q27Y'
}
},
year: {
'6000': {
planId: 'P-0UY77185EM3077131LZYP6VY'
},
'25000': {
planId: 'P-7K1585908S634694XLZZTHUQ'
},
'100000': {
planId: 'P-0J5231134H608574XLZZTDLQ'
}
} }
} }
}; };