mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-04-30 16:01:14 -04:00
feat(client): AB test adding mutitier donation modal (#51539)
Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
@@ -474,6 +474,8 @@
|
||||
"confirm-one-time": "Confirm your one-time donation of ${{usd}}:",
|
||||
"confirm-monthly": "Confirm your donation of ${{usd}} / month:",
|
||||
"confirm-yearly": "Confirm your donation of ${{usd}} / year:",
|
||||
"confirm-multitier": "Donating ${{usd}} / month:",
|
||||
"edit-amount": "edit amount",
|
||||
"wallet-label": "${{usd}} donation to freeCodeCamp",
|
||||
"wallet-label-1": "${{usd}} / month donation to freeCodeCamp",
|
||||
"your-donation": "Your ${{usd}} donation will provide {{hours}} hours of learning to people around the world.",
|
||||
@@ -490,6 +492,7 @@
|
||||
"progress-modal-cta-8": "Donate now to help us develop new courses on emerging tools and programming concepts.",
|
||||
"progress-modal-cta-9": "Donate now to support our math for developers curriculum.",
|
||||
"progress-modal-cta-10": "Donate now to help us develop free professional programming certifications for all.",
|
||||
"help-us-develop": "Help us develop free professional programming certifications for all.",
|
||||
"nicely-done": "Nicely done. You just completed {{block}}.",
|
||||
"credit-card": "Credit Card",
|
||||
"credit-card-2": "Or donate with a credit card:",
|
||||
|
||||
@@ -267,6 +267,7 @@ const ShowCertification = (props: ShowCertificationProps): JSX.Element => {
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Spacer size='medium' />
|
||||
<Row>
|
||||
<Col sm={4} smOffset={4} xs={6} xsOffset={3}>
|
||||
{isDonationSubmitted && donationCloseBtn}
|
||||
|
||||
@@ -6,12 +6,11 @@ import { connect } from 'react-redux';
|
||||
import Spinner from 'react-spinkit';
|
||||
import { createSelector } from 'reselect';
|
||||
import type { TFunction } from 'i18next';
|
||||
import { Button } from '@freecodecamp/react-bootstrap';
|
||||
|
||||
import {
|
||||
amountsConfig,
|
||||
durationsConfig,
|
||||
defaultDonation,
|
||||
modalDefaultDonation,
|
||||
DonationAmount,
|
||||
type DonationConfig
|
||||
} from '../../../../shared/config/donation-settings';
|
||||
import { defaultDonationFormState } from '../../redux';
|
||||
@@ -25,6 +24,12 @@ import {
|
||||
} from '../../redux/selectors';
|
||||
import Spacer from '../helpers/spacer';
|
||||
import { Themes } from '../settings/theme';
|
||||
import { DonateFormState } from '../../redux/types';
|
||||
import {
|
||||
CENTS_IN_DOLLAR,
|
||||
convertToTimeContributed,
|
||||
formattedAmountLabel
|
||||
} from './utils';
|
||||
import DonateCompletion from './donate-completion';
|
||||
import PatreonButton from './patreon-button';
|
||||
import PaypalButton from './paypal-button';
|
||||
@@ -41,28 +46,6 @@ import {
|
||||
|
||||
import './donation.css';
|
||||
|
||||
const numToCommas = (num: number) =>
|
||||
num.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
|
||||
|
||||
// the number is used to indicate to the doner about how much hours of free education their dontation will provide.
|
||||
const contributedHoursOfFreeEduction = 50;
|
||||
const convertAmountToUSD = 100;
|
||||
const convertToTimeContributed = (amount: number) =>
|
||||
numToCommas((amount / convertAmountToUSD) * contributedHoursOfFreeEduction);
|
||||
const formattedAmountLabel = (amount: number) =>
|
||||
numToCommas(amount / convertAmountToUSD);
|
||||
|
||||
type DonateFormState = {
|
||||
processing: boolean;
|
||||
redirecting: boolean;
|
||||
success: boolean;
|
||||
error: string;
|
||||
loading: {
|
||||
stripe: boolean;
|
||||
paypal: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
type DonateFormComponentState = DonationConfig;
|
||||
|
||||
type PostCharge = (data: {
|
||||
@@ -83,6 +66,8 @@ type DonateFormProps = {
|
||||
defaultTheme?: Themes;
|
||||
email: string;
|
||||
handleProcessing?: () => void;
|
||||
editAmount?: () => void;
|
||||
selectedDonationAmount?: DonationAmount;
|
||||
donationFormState: DonateFormState;
|
||||
isMinimalForm?: boolean;
|
||||
isSignedIn: boolean;
|
||||
@@ -135,17 +120,10 @@ const PaymentButtonsLoader = () => {
|
||||
|
||||
class DonateForm extends Component<DonateFormProps, DonateFormComponentState> {
|
||||
static displayName = 'DonateForm';
|
||||
durations: { month: 'monthly'; onetime: 'one-time' };
|
||||
amounts: { month: number[]; onetime: number[] };
|
||||
constructor(props: DonateFormProps) {
|
||||
super(props);
|
||||
|
||||
this.durations = durationsConfig;
|
||||
this.amounts = amountsConfig;
|
||||
|
||||
const initialAmountAndDuration: DonationConfig = this.props.isMinimalForm
|
||||
? modalDefaultDonation
|
||||
: defaultDonation;
|
||||
const initialAmountAndDuration: DonationConfig = defaultDonation;
|
||||
|
||||
this.state = { ...initialAmountAndDuration };
|
||||
|
||||
@@ -187,8 +165,9 @@ class DonateForm extends Component<DonateFormProps, DonateFormComponentState> {
|
||||
paymentMethodId,
|
||||
handleAuthentication
|
||||
}: PostPayment): void => {
|
||||
const { donationAmount: amount, donationDuration: duration } = this.state;
|
||||
const { paymentContext, email } = this.props;
|
||||
const { donationAmount, donationDuration: duration } = this.state;
|
||||
const { paymentContext, email, selectedDonationAmount } = this.props;
|
||||
const amount = selectedDonationAmount || donationAmount;
|
||||
|
||||
this.props.postCharge({
|
||||
paymentProvider,
|
||||
@@ -210,7 +189,7 @@ class DonateForm extends Component<DonateFormProps, DonateFormComponentState> {
|
||||
}
|
||||
|
||||
renderButtonGroup() {
|
||||
const { donationAmount, donationDuration } = this.state;
|
||||
const { donationAmount: defaultAmount, donationDuration } = this.state;
|
||||
const {
|
||||
donationFormState: { loading, processing },
|
||||
defaultTheme,
|
||||
@@ -218,24 +197,44 @@ class DonateForm extends Component<DonateFormProps, DonateFormComponentState> {
|
||||
t,
|
||||
isMinimalForm,
|
||||
isSignedIn,
|
||||
isDonating
|
||||
isDonating,
|
||||
editAmount,
|
||||
selectedDonationAmount
|
||||
} = this.props;
|
||||
const donationAmount: DonationAmount =
|
||||
selectedDonationAmount || defaultAmount;
|
||||
const priorityTheme = defaultTheme ? defaultTheme : theme;
|
||||
const isOneTime = donationDuration === 'one-time';
|
||||
const walletlabel = `${t(
|
||||
isOneTime ? 'donate.wallet-label' : 'donate.wallet-label-1',
|
||||
{ usd: donationAmount / convertAmountToUSD }
|
||||
)}:`;
|
||||
const walletlabel = `${t('donate.wallet-label-1', {
|
||||
usd: donationAmount / CENTS_IN_DOLLAR
|
||||
})}:`;
|
||||
console.log(formattedAmountLabel(donationAmount));
|
||||
const showMinimalPayments = isSignedIn && (isMinimalForm || !isDonating);
|
||||
const confirmationMessage = t('donate.confirm-monthly', {
|
||||
usd: formattedAmountLabel(donationAmount)
|
||||
});
|
||||
const confirmationWithEditAmount = (
|
||||
<>
|
||||
{t('donate.confirm-multitier', {
|
||||
usd: formattedAmountLabel(donationAmount)
|
||||
})}
|
||||
|
||||
<Button bsStyle='primary' className='btn-link' onClick={editAmount}>
|
||||
{t('donate.edit-amount')}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
|
||||
const confirmationClass = () => {
|
||||
if (editAmount) return 'edit-amount-confirmation';
|
||||
if (isMinimalForm) return 'donation-label-modal';
|
||||
return '';
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<b className={isMinimalForm ? 'donation-label-modal' : ''}>
|
||||
{t('donate.confirm-monthly', {
|
||||
usd: formattedAmountLabel(donationAmount)
|
||||
})}
|
||||
<b className={confirmationClass()}>
|
||||
{editAmount ? confirmationWithEditAmount : confirmationMessage}
|
||||
</b>
|
||||
<Spacer size='medium' />
|
||||
<Spacer size={editAmount ? 'small' : 'medium'} />
|
||||
<fieldset className={'donate-btn-group security-legend'}>
|
||||
<legend>
|
||||
<SecurityLockIcon />
|
||||
@@ -263,7 +262,10 @@ class DonateForm extends Component<DonateFormProps, DonateFormComponentState> {
|
||||
theme={priorityTheme}
|
||||
/>
|
||||
{(!loading.stripe || !loading.paypal) && (
|
||||
<PatreonButton postPayment={this.postPayment} />
|
||||
<PatreonButton
|
||||
postPayment={this.postPayment}
|
||||
donationAmount={donationAmount}
|
||||
/>
|
||||
)}
|
||||
{showMinimalPayments && (
|
||||
<>
|
||||
@@ -283,18 +285,12 @@ class DonateForm extends Component<DonateFormProps, DonateFormComponentState> {
|
||||
}
|
||||
|
||||
renderPageForm() {
|
||||
const { donationAmount, donationDuration } = this.state;
|
||||
const { donationAmount } = this.state;
|
||||
const { t } = this.props;
|
||||
const usd = formattedAmountLabel(donationAmount);
|
||||
const hours = convertToTimeContributed(donationAmount);
|
||||
const donationDescription = t('donate.your-donation-2', { usd, hours });
|
||||
|
||||
let donationDescription = t('donate.your-donation-3', { usd, hours });
|
||||
|
||||
if (donationDuration === 'one-time') {
|
||||
donationDescription = t('donate.your-donation', { usd, hours });
|
||||
} else if (donationDuration === 'month') {
|
||||
donationDescription = t('donate.your-donation-2', { usd, hours });
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<p className='donation-description'>{donationDescription}</p>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Modal, Button, Col, Row } from '@freecodecamp/react-bootstrap';
|
||||
import { Tabs, TabsContent, TabsTrigger, TabsList } from '@freecodecamp/ui';
|
||||
import { WindowLocation } from '@reach/router';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -7,10 +8,14 @@ import { useFeature } from '@growthbook/growthbook-react';
|
||||
import { goToAnchor } from 'react-scrollable-anchor';
|
||||
import { bindActionCreators, Dispatch, AnyAction } from 'redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { PaymentContext } from '../../../../shared/config/donation-settings';
|
||||
import {
|
||||
PaymentContext,
|
||||
subscriptionAmounts,
|
||||
defaultDonation,
|
||||
defaultTierAmount
|
||||
} from '../../../../shared/config/donation-settings';
|
||||
import BearProgressModal from '../../assets/images/components/bear-progress-modal';
|
||||
import BearBlockCompletion from '../../assets/images/components/bear-block-completion-modal';
|
||||
|
||||
import { closeDonationModal, executeGA } from '../../redux/actions';
|
||||
import {
|
||||
isDonationModalOpenSelector,
|
||||
@@ -20,6 +25,7 @@ import { isLocationSuperBlock } from '../../utils/path-parsers';
|
||||
import { playTone } from '../../utils/tone';
|
||||
import { Spacer } from '../helpers';
|
||||
import DonateForm from './donate-form';
|
||||
import { formattedAmountLabel, convertToTimeContributed } from './utils';
|
||||
|
||||
type RecentlyClaimedBlock = null | { block: string; superBlock: string };
|
||||
|
||||
@@ -79,7 +85,12 @@ function DonateModal({
|
||||
const [ctaNumber, setCtaNumber] = useState(0);
|
||||
const [isDisabled, setIsDisabled] = useState(true);
|
||||
const [showSkipButton, setShowSkipButton] = useState(false);
|
||||
const [showDonateForm, setShowDonateForm] = useState(true);
|
||||
const [donationAmount, setDonationAmount] = useState(
|
||||
defaultDonation.donationAmount
|
||||
);
|
||||
const loadElementsIndividually = useFeature('load_elements_individually').on;
|
||||
const showMultiTier = useFeature('multi-tier').on;
|
||||
const { t } = useTranslation();
|
||||
|
||||
// test wheather the conversions are being distributed properly
|
||||
@@ -119,6 +130,13 @@ function DonateModal({
|
||||
if (show) setCtaNumber(getctaNumberBetween1To10());
|
||||
}, [show]);
|
||||
|
||||
useEffect(() => {
|
||||
if (showMultiTier) {
|
||||
setShowDonateForm(false);
|
||||
setDonationAmount(defaultTierAmount);
|
||||
}
|
||||
}, [showMultiTier]);
|
||||
|
||||
const handleModalHide = () => {
|
||||
// If modal is open on a SuperBlock page
|
||||
if (isLocationSuperBlock(location)) {
|
||||
@@ -126,11 +144,8 @@ function DonateModal({
|
||||
}
|
||||
};
|
||||
|
||||
const donationText = (
|
||||
const modalHeader = (
|
||||
<div className=' text-center block-modal-text'>
|
||||
<div className='donation-icon-container'>
|
||||
<RenderIlustration recentlyClaimedBlock={recentlyClaimedBlock} />
|
||||
</div>
|
||||
<Row>
|
||||
{!closeLabel && (
|
||||
<Col sm={10} smOffset={1} xs={12}>
|
||||
@@ -143,13 +158,146 @@ function DonateModal({
|
||||
})}
|
||||
</b>
|
||||
)}
|
||||
<b>{t(`donate.progress-modal-cta-${ctaNumber}`)}</b>
|
||||
{showMultiTier ? (
|
||||
<h1>{t('donate.help-us-develop')}</h1>
|
||||
) : (
|
||||
<b>{t(`donate.progress-modal-cta-${ctaNumber}`)}</b>
|
||||
)}
|
||||
</Col>
|
||||
)}
|
||||
</Row>
|
||||
<Spacer size='small' />
|
||||
</div>
|
||||
);
|
||||
|
||||
const closeButtonRow = (
|
||||
<>
|
||||
<Row>
|
||||
<Col
|
||||
sm={4}
|
||||
smOffset={4}
|
||||
xs={8}
|
||||
xsOffset={2}
|
||||
className={showSkipButton ? 'no-delay-fade-in' : 'no-opacity'}
|
||||
>
|
||||
<Button
|
||||
bsSize='sm'
|
||||
bsStyle='primary'
|
||||
className='btn-link close-button'
|
||||
onClick={closeDonationModal}
|
||||
tabIndex='0'
|
||||
disabled={isDisabled}
|
||||
>
|
||||
{closeLabel ? t('buttons.close') : t('buttons.ask-later')}
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</>
|
||||
);
|
||||
|
||||
const selectionTabs = (
|
||||
<Row className={'donate-btn-group'}>
|
||||
<Col
|
||||
xs={12}
|
||||
className={loadElementsIndividually && 'two-seconds-delay-fade-in'}
|
||||
>
|
||||
<b>
|
||||
{t('donate.confirm-monthly', {
|
||||
usd: formattedAmountLabel(donationAmount)
|
||||
})}
|
||||
</b>
|
||||
<Spacer size='small' />
|
||||
<Tabs
|
||||
className={'donate-btn-group'}
|
||||
defaultValue={donationAmount.toString()}
|
||||
>
|
||||
<TabsList className='nav-lists'>
|
||||
{subscriptionAmounts.map(value => (
|
||||
<TabsTrigger
|
||||
key={value}
|
||||
value={value.toString()}
|
||||
onClick={() => setDonationAmount(value)}
|
||||
>
|
||||
${formattedAmountLabel(value)}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
<Spacer size='small' />
|
||||
{subscriptionAmounts.map(value => {
|
||||
const usd = formattedAmountLabel(donationAmount);
|
||||
const hours = convertToTimeContributed(donationAmount);
|
||||
const donationDescription = t('donate.your-donation-2', {
|
||||
usd,
|
||||
hours
|
||||
});
|
||||
|
||||
return (
|
||||
<TabsContent
|
||||
key={value}
|
||||
className='tab-content'
|
||||
value={value.toString()}
|
||||
>
|
||||
<p>{donationDescription}</p>
|
||||
</TabsContent>
|
||||
);
|
||||
})}
|
||||
</Tabs>
|
||||
<Button
|
||||
block={true}
|
||||
bsStyle='primary'
|
||||
className='text-center confirm-donation-btn donate-btn-group'
|
||||
type='submit'
|
||||
onClick={() => setShowDonateForm(true)}
|
||||
>
|
||||
{t('buttons.donate')}
|
||||
</Button>
|
||||
<Spacer size='medium' />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
|
||||
const donationFormRow = (
|
||||
<Row>
|
||||
<Col
|
||||
xs={12}
|
||||
className={loadElementsIndividually && 'two-seconds-delay-fade-in'}
|
||||
>
|
||||
<DonateForm
|
||||
handleProcessing={handleProcessing}
|
||||
isMinimalForm={true}
|
||||
paymentContext={PaymentContext.Modal}
|
||||
editAmount={
|
||||
showMultiTier ? () => setShowDonateForm(false) : undefined
|
||||
}
|
||||
selectedDonationAmount={donationAmount}
|
||||
/>
|
||||
<Spacer size='medium' />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
|
||||
const multiTierModalBody = (
|
||||
<>
|
||||
<div className={showDonateForm ? 'hide' : ''}>
|
||||
{modalHeader}
|
||||
{selectionTabs}
|
||||
{closeButtonRow}
|
||||
</div>
|
||||
<div className={!showDonateForm ? 'hide' : ''}>
|
||||
{donationFormRow}
|
||||
{closeLabel && closeButtonRow}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
const defaultModalBody = (
|
||||
<>
|
||||
{modalHeader}
|
||||
{donationFormRow}
|
||||
{closeButtonRow}
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
bsSize='lg'
|
||||
@@ -158,42 +306,10 @@ function DonateModal({
|
||||
show={show}
|
||||
>
|
||||
<Modal.Body className={'no-delay-fade-in'}>
|
||||
{donationText}
|
||||
<Spacer size='medium' />
|
||||
<Row>
|
||||
<Col
|
||||
xs={12}
|
||||
className={loadElementsIndividually && 'two-seconds-delay-fade-in'}
|
||||
>
|
||||
<DonateForm
|
||||
handleProcessing={handleProcessing}
|
||||
isMinimalForm={true}
|
||||
paymentContext={PaymentContext.Modal}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Spacer size='medium' />
|
||||
<Row>
|
||||
<Col
|
||||
sm={4}
|
||||
smOffset={4}
|
||||
xs={8}
|
||||
xsOffset={2}
|
||||
className={showSkipButton ? 'no-delay-fade-in' : 'no-opacity'}
|
||||
>
|
||||
<Button
|
||||
block={true}
|
||||
bsSize='sm'
|
||||
bsStyle='primary'
|
||||
className='btn-link'
|
||||
onClick={closeDonationModal}
|
||||
tabIndex='0'
|
||||
disabled={isDisabled}
|
||||
>
|
||||
{closeLabel ? t('buttons.close') : t('buttons.ask-later')}
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
<div className='donation-icon-container'>
|
||||
<RenderIlustration recentlyClaimedBlock={recentlyClaimedBlock} />
|
||||
</div>
|
||||
{showMultiTier ? multiTierModalBody : defaultModalBody}
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -286,15 +286,48 @@ li.disabled > a {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.donation-modal p,
|
||||
.donation-modal b {
|
||||
text-align: center;
|
||||
font-size: 1rem;
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.donation-modal p {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.donation-label-modal {
|
||||
font-weight: normal;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.edit-amount-confirmation {
|
||||
width: 350px !important;
|
||||
margin: 0 auto;
|
||||
display: flex !important;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
text-align: center;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.donation-modal h1 {
|
||||
font-family: var(--font-family-sans-serif);
|
||||
}
|
||||
|
||||
.donation-modal [role='tablist'] button {
|
||||
background-color: transparent;
|
||||
}
|
||||
.donation-modal [role='tablist'] button:hover:not([data-state='active']) {
|
||||
background-color: var(--quaternary-background);
|
||||
color: var(--quaternary-color);
|
||||
}
|
||||
|
||||
.donation-modal [role='tablist'] button[data-state='active'] {
|
||||
background-color: var(--quaternary-color);
|
||||
}
|
||||
|
||||
.donation-icon-container {
|
||||
@@ -350,10 +383,6 @@ li.disabled > a {
|
||||
margin: 40px;
|
||||
}
|
||||
|
||||
.donation-modal p {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.donation-modal .modal-title {
|
||||
font-weight: 700;
|
||||
font-size: 1.5rem;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
DonationAmount,
|
||||
donationUrls,
|
||||
patreonDefaultPledgeAmount,
|
||||
PaymentProvider
|
||||
} from '../../../../shared/config/donation-settings';
|
||||
import envData from '../../../config/env.json';
|
||||
@@ -14,21 +14,21 @@ const { patreonClientId }: { patreonClientId: string | null } = envData as {
|
||||
|
||||
interface PatreonButtonProps {
|
||||
postPayment: (arg0: PostPayment) => void;
|
||||
donationAmount: DonationAmount;
|
||||
}
|
||||
|
||||
const PatreonButton = ({
|
||||
postPayment
|
||||
postPayment,
|
||||
donationAmount
|
||||
}: PatreonButtonProps): JSX.Element | null => {
|
||||
if (
|
||||
!patreonClientId ||
|
||||
!patreonDefaultPledgeAmount ||
|
||||
!donationUrls.successUrl
|
||||
) {
|
||||
if (!patreonClientId || !donationAmount || !donationUrls.successUrl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const clientId = `&client_id=${patreonClientId}`;
|
||||
const pledgeLevel = `$&min_cents=${patreonDefaultPledgeAmount}`;
|
||||
|
||||
// current Patreon pledge flow does not support custom amounts, it must be a tier
|
||||
const pledgeLevel = `$&min_cents=${donationAmount}`;
|
||||
const v2Params = '&scope=identity%20identity[email]';
|
||||
const redirectUri = `&redirect_uri=${donationUrls.successUrl}`;
|
||||
const href = `https://www.patreon.com/oauth2/become-patron?response_type=code${pledgeLevel}${clientId}${redirectUri}${v2Params}`;
|
||||
|
||||
7
client/src/components/Donation/utils.ts
Normal file
7
client/src/components/Donation/utils.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
const numToCommas = (num: number) => Intl.NumberFormat('en-US').format(num);
|
||||
const EDUCATION_HOURS_PER_DOLLAR = 50;
|
||||
export const CENTS_IN_DOLLAR = 100;
|
||||
export const convertToTimeContributed = (amount: number) =>
|
||||
numToCommas((amount / CENTS_IN_DOLLAR) * EDUCATION_HOURS_PER_DOLLAR);
|
||||
export const formattedAmountLabel = (amount: number) =>
|
||||
numToCommas(amount / CENTS_IN_DOLLAR);
|
||||
@@ -47,3 +47,14 @@ interface DefaultDonationFormState {
|
||||
success: boolean;
|
||||
error: null | string;
|
||||
}
|
||||
|
||||
export interface DonateFormState {
|
||||
processing: boolean;
|
||||
redirecting: boolean;
|
||||
success: boolean;
|
||||
error: string;
|
||||
loading: {
|
||||
stripe: boolean;
|
||||
paypal: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,37 +1,21 @@
|
||||
// Configuration for client side
|
||||
|
||||
export type DonationAmount = 500 | 1000 | 2000 | 3000 | 4000 | 5000;
|
||||
export type DonationAmount = 500 | 1000 | 2000 | 4000;
|
||||
export type DonationDuration = 'one-time' | 'month';
|
||||
export interface DonationConfig {
|
||||
donationAmount: DonationAmount;
|
||||
donationDuration: DonationDuration;
|
||||
}
|
||||
|
||||
export const durationsConfig: {
|
||||
month: 'monthly';
|
||||
onetime: 'one-time';
|
||||
} = {
|
||||
month: 'monthly',
|
||||
onetime: 'one-time'
|
||||
};
|
||||
export const subscriptionAmounts: DonationAmount[] = [500, 1000, 2000, 4000];
|
||||
|
||||
export const amountsConfig = {
|
||||
month: [1000, 2000, 3000, 4000, 5000],
|
||||
onetime: [2500, 5000, 7500, 10000, 15000]
|
||||
};
|
||||
export const defaultAmount: { month: 500; onetime: 7500 } = {
|
||||
month: 500,
|
||||
onetime: 7500
|
||||
};
|
||||
export const defaultDonation: DonationConfig = {
|
||||
donationAmount: defaultAmount.month,
|
||||
donationDuration: 'month'
|
||||
};
|
||||
export const modalDefaultDonation: DonationConfig = {
|
||||
donationAmount: 500,
|
||||
donationDuration: 'month'
|
||||
};
|
||||
|
||||
export const defaultTierAmount = 2000;
|
||||
|
||||
export const onetimeSKUConfig = {
|
||||
live: [
|
||||
{ amount: '15000', id: 'sku_IElisJHup0nojP' },
|
||||
@@ -127,13 +111,6 @@ export const donationUrls = {
|
||||
cancelUrl: 'https://freecodecamp.org/donate'
|
||||
};
|
||||
|
||||
export const patreonDefaultPledgeAmount = 500;
|
||||
|
||||
export const aBTestConfig = {
|
||||
isTesting: true,
|
||||
type: 'secureIconButtonOnly'
|
||||
};
|
||||
|
||||
export enum PaymentContext {
|
||||
Modal = 'modal',
|
||||
DonatePage = 'donate page',
|
||||
|
||||
Reference in New Issue
Block a user