feat: A/B test firing a confetti on block donation modal (#45393)
This commit is contained in:
@ -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:",
|
||||||
|
@ -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",
|
||||||
|
@ -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>
|
||||||
|
@ -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());
|
||||||
|
48
client/src/utils/fire-confetti.ts
Normal file
48
client/src/utils/fire-confetti.ts
Normal 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
28
package-lock.json
generated
@ -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",
|
||||||
|
Reference in New Issue
Block a user