feat: A/B test firing a confetti on block donation modal (#45393)

This commit is contained in:
Ahmad Abdolsaheb
2022-03-10 12:13:10 +03:00
committed by GitHub
parent d3304276ca
commit 2e9b37a02b
6 changed files with 92 additions and 17 deletions

View File

@ -332,7 +332,6 @@
"duration-2": "Become a monthly supporter of our nonprofit.", "duration-2": "Become a monthly supporter of our nonprofit.",
"duration-3": "Become an annual supporter of our nonprofit", "duration-3": "Become an annual supporter of our nonprofit",
"duration-4": "Become a supporter of our nonprofit", "duration-4": "Become a supporter of our nonprofit",
"duration-5": "Support our nonprofit and earn a supporter profile badge.",
"nicely-done": "Nicely done. You just completed {{block}}.", "nicely-done": "Nicely done. You just completed {{block}}.",
"credit-card": "Credit Card", "credit-card": "Credit Card",
"credit-card-2": "Or donate with a credit card:", "credit-card-2": "Or donate with a credit card:",

View File

@ -52,6 +52,7 @@
"@reach/router": "1.3.4", "@reach/router": "1.3.4",
"@stripe/react-stripe-js": "1.7.0", "@stripe/react-stripe-js": "1.7.0",
"@stripe/stripe-js": "1.24.0", "@stripe/stripe-js": "1.24.0",
"@types/canvas-confetti": "1.4.2",
"@types/react-scrollable-anchor": "0.6.1", "@types/react-scrollable-anchor": "0.6.1",
"algoliasearch": "4.12.2", "algoliasearch": "4.12.2",
"assert": "2.0.0", "assert": "2.0.0",
@ -60,6 +61,7 @@
"bezier-easing": "2.1.0", "bezier-easing": "2.1.0",
"browser-cookies": "1.2.0", "browser-cookies": "1.2.0",
"buffer": "6.0.3", "buffer": "6.0.3",
"canvas-confetti": "1.5.1",
"chai": "4.3.6", "chai": "4.3.6",
"crypto-browserify": "3.12.0", "crypto-browserify": "3.12.0",
"date-fns": "2.27.0", "date-fns": "2.27.0",

View File

@ -14,8 +14,7 @@ import {
closeDonationModal, closeDonationModal,
isDonationModalOpenSelector, isDonationModalOpenSelector,
recentlyClaimedBlockSelector, recentlyClaimedBlockSelector,
executeGA, executeGA
isAVariantSelector
} from '../../redux'; } from '../../redux';
import { isLocationSuperBlock } from '../../utils/path-parsers'; import { isLocationSuperBlock } from '../../utils/path-parsers';
import { playTone } from '../../utils/tone'; import { playTone } from '../../utils/tone';
@ -25,11 +24,9 @@ import DonateForm from './donate-form';
const mapStateToProps = createSelector( const mapStateToProps = createSelector(
isDonationModalOpenSelector, isDonationModalOpenSelector,
recentlyClaimedBlockSelector, recentlyClaimedBlockSelector,
isAVariantSelector, (show: boolean, recentlyClaimedBlock: string) => ({
(show: boolean, recentlyClaimedBlock: string, isAVariant: boolean) => ({
show, show,
recentlyClaimedBlock, recentlyClaimedBlock
isAVariant
}) })
); );
@ -49,7 +46,6 @@ type DonateModalProps = {
location: WindowLocation | undefined; location: WindowLocation | undefined;
recentlyClaimedBlock: string; recentlyClaimedBlock: string;
show: boolean; show: boolean;
isAVariant: boolean;
}; };
function DonateModal({ function DonateModal({
@ -57,8 +53,7 @@ function DonateModal({
closeDonationModal, closeDonationModal,
executeGA, executeGA,
location, location,
recentlyClaimedBlock, recentlyClaimedBlock
isAVariant
}: DonateModalProps): JSX.Element { }: DonateModalProps): JSX.Element {
const [closeLabel, setCloseLabel] = React.useState(false); const [closeLabel, setCloseLabel] = React.useState(false);
const { t } = useTranslation(); const { t } = useTranslation();
@ -134,11 +129,6 @@ function DonateModal({
</div> </div>
); );
const renderABtestProgressText = () => {
if (isAVariant) return getDonationText();
else <b>{t('donate.duration-5')}</b>;
};
const progressDonationText = ( const progressDonationText = (
<div className='text-center progress-modal-text'> <div className='text-center progress-modal-text'>
<div className='donation-icon-container'> <div className='donation-icon-container'>
@ -147,7 +137,7 @@ function DonateModal({
<Row> <Row>
{!closeLabel && ( {!closeLabel && (
<Col sm={10} smOffset={1} xs={12}> <Col sm={10} smOffset={1} xs={12}>
{renderABtestProgressText()} {getDonationText()}
</Col> </Col>
)} )}
</Row> </Row>

View File

@ -7,6 +7,7 @@ import {
call, call,
take take
} from 'redux-saga/effects'; } from 'redux-saga/effects';
import { fireConfetti } from '../utils/fire-confetti';
import { import {
addDonation, addDonation,
@ -26,7 +27,8 @@ import {
postChargeStripeComplete, postChargeStripeComplete,
postChargeStripeError, postChargeStripeError,
postChargeStripeCardComplete, postChargeStripeCardComplete,
postChargeStripeCardError postChargeStripeCardError,
isAVariantSelector
} from './'; } from './';
const defaultDonationErrorMessage = `Something is not right. Please contact donors@freecodecamp.org`; const defaultDonationErrorMessage = `Something is not right. Please contact donors@freecodecamp.org`;
@ -37,6 +39,12 @@ function* showDonateModalSaga() {
yield delay(200); yield delay(200);
const recentlyClaimedBlock = yield select(recentlyClaimedBlockSelector); const recentlyClaimedBlock = yield select(recentlyClaimedBlockSelector);
yield put(openDonationModal()); yield put(openDonationModal());
if (recentlyClaimedBlock) {
const isAVariant = yield select(isAVariantSelector);
if (isAVariant === false) {
fireConfetti();
}
}
yield take(appTypes.closeDonationModal); yield take(appTypes.closeDonationModal);
if (recentlyClaimedBlock) { if (recentlyClaimedBlock) {
yield put(preventBlockDonationRequests()); yield put(preventBlockDonationRequests());

View File

@ -0,0 +1,48 @@
import confetti from 'canvas-confetti';
export const fireConfetti = () => {
const count = 200;
const defaults = {
origin: { y: 0.7 },
zIndex: 10000
};
function fire(
particleRatio: number,
opts: {
spread?: number;
startVelocity?: number;
decay?: number;
scalar?: number;
}
) {
confetti(
Object.assign({}, defaults, opts, {
particleCount: Math.floor(count * particleRatio)
})
)?.catch(err => console.log(err));
}
fire(0.25, {
spread: 26,
startVelocity: 55
});
fire(0.2, {
spread: 60
});
fire(0.35, {
spread: 100,
decay: 0.91,
scalar: 0.8
});
fire(0.1, {
spread: 120,
startVelocity: 25,
decay: 0.92,
scalar: 1.2
});
fire(0.1, {
spread: 120,
startVelocity: 45
});
};

28
package-lock.json generated
View File

@ -985,6 +985,7 @@
"@reach/router": "1.3.4", "@reach/router": "1.3.4",
"@stripe/react-stripe-js": "1.7.0", "@stripe/react-stripe-js": "1.7.0",
"@stripe/stripe-js": "1.24.0", "@stripe/stripe-js": "1.24.0",
"@types/canvas-confetti": "1.4.2",
"@types/react-scrollable-anchor": "0.6.1", "@types/react-scrollable-anchor": "0.6.1",
"algoliasearch": "4.12.2", "algoliasearch": "4.12.2",
"assert": "2.0.0", "assert": "2.0.0",
@ -993,6 +994,7 @@
"bezier-easing": "2.1.0", "bezier-easing": "2.1.0",
"browser-cookies": "1.2.0", "browser-cookies": "1.2.0",
"buffer": "6.0.3", "buffer": "6.0.3",
"canvas-confetti": "1.5.1",
"chai": "4.3.6", "chai": "4.3.6",
"crypto-browserify": "3.12.0", "crypto-browserify": "3.12.0",
"date-fns": "2.27.0", "date-fns": "2.27.0",
@ -15171,6 +15173,11 @@
"@types/responselike": "*" "@types/responselike": "*"
} }
}, },
"node_modules/@types/canvas-confetti": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/@types/canvas-confetti/-/canvas-confetti-1.4.2.tgz",
"integrity": "sha512-t45KUDHlwrD9PJVRHc5z1SlXhO82BQEgMKUXGEV1KnWLFMPA6Y5LfUsLTHHzH9KcKDHZLEiYYH5nIDcjRKWNTg=="
},
"node_modules/@types/chai": { "node_modules/@types/chai": {
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.0.tgz", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.0.tgz",
@ -20396,6 +20403,15 @@
"resolved": "https://registry.npmjs.org/canonical-json/-/canonical-json-0.0.4.tgz", "resolved": "https://registry.npmjs.org/canonical-json/-/canonical-json-0.0.4.tgz",
"integrity": "sha1-ZXnAcsPbXEd+xB3JePvyuPQQdKM=" "integrity": "sha1-ZXnAcsPbXEd+xB3JePvyuPQQdKM="
}, },
"node_modules/canvas-confetti": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/canvas-confetti/-/canvas-confetti-1.5.1.tgz",
"integrity": "sha512-Ncz+oZJP6OvY7ti4E1slxVlyAV/3g7H7oQtcCDXgwGgARxPnwYY9PW5Oe+I8uvspYNtuHviAdgA0LfcKFWJfpg==",
"funding": {
"type": "donate",
"url": "https://www.paypal.me/kirilvatev"
}
},
"node_modules/capture-exit": { "node_modules/capture-exit": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz",
@ -56676,6 +56692,7 @@
"@stripe/stripe-js": "1.24.0", "@stripe/stripe-js": "1.24.0",
"@testing-library/jest-dom": "5.16.2", "@testing-library/jest-dom": "5.16.2",
"@testing-library/react": "12.1.3", "@testing-library/react": "12.1.3",
"@types/canvas-confetti": "*",
"@types/react-scrollable-anchor": "0.6.1", "@types/react-scrollable-anchor": "0.6.1",
"algoliasearch": "4.12.2", "algoliasearch": "4.12.2",
"assert": "2.0.0", "assert": "2.0.0",
@ -56686,6 +56703,7 @@
"bezier-easing": "2.1.0", "bezier-easing": "2.1.0",
"browser-cookies": "1.2.0", "browser-cookies": "1.2.0",
"buffer": "6.0.3", "buffer": "6.0.3",
"canvas-confetti": "1.5.1",
"chai": "4.3.6", "chai": "4.3.6",
"chokidar": "3.5.3", "chokidar": "3.5.3",
"copy-webpack-plugin": "9.1.0", "copy-webpack-plugin": "9.1.0",
@ -65756,6 +65774,11 @@
"@types/responselike": "*" "@types/responselike": "*"
} }
}, },
"@types/canvas-confetti": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/@types/canvas-confetti/-/canvas-confetti-1.4.2.tgz",
"integrity": "sha512-t45KUDHlwrD9PJVRHc5z1SlXhO82BQEgMKUXGEV1KnWLFMPA6Y5LfUsLTHHzH9KcKDHZLEiYYH5nIDcjRKWNTg=="
},
"@types/chai": { "@types/chai": {
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.0.tgz", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.0.tgz",
@ -70219,6 +70242,11 @@
"resolved": "https://registry.npmjs.org/canonical-json/-/canonical-json-0.0.4.tgz", "resolved": "https://registry.npmjs.org/canonical-json/-/canonical-json-0.0.4.tgz",
"integrity": "sha1-ZXnAcsPbXEd+xB3JePvyuPQQdKM=" "integrity": "sha1-ZXnAcsPbXEd+xB3JePvyuPQQdKM="
}, },
"canvas-confetti": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/canvas-confetti/-/canvas-confetti-1.5.1.tgz",
"integrity": "sha512-Ncz+oZJP6OvY7ti4E1slxVlyAV/3g7H7oQtcCDXgwGgARxPnwYY9PW5Oe+I8uvspYNtuHviAdgA0LfcKFWJfpg=="
},
"capture-exit": { "capture-exit": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz",