diff --git a/client/i18n/locales/english/translations.json b/client/i18n/locales/english/translations.json
index c06784727d..892149051c 100644
--- a/client/i18n/locales/english/translations.json
+++ b/client/i18n/locales/english/translations.json
@@ -332,7 +332,6 @@
"duration-2": "Become a monthly supporter of our nonprofit.",
"duration-3": "Become an annual 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}}.",
"credit-card": "Credit Card",
"credit-card-2": "Or donate with a credit card:",
diff --git a/client/package.json b/client/package.json
index 5909e1c5ce..bf83d46ab9 100644
--- a/client/package.json
+++ b/client/package.json
@@ -52,6 +52,7 @@
"@reach/router": "1.3.4",
"@stripe/react-stripe-js": "1.7.0",
"@stripe/stripe-js": "1.24.0",
+ "@types/canvas-confetti": "1.4.2",
"@types/react-scrollable-anchor": "0.6.1",
"algoliasearch": "4.12.2",
"assert": "2.0.0",
@@ -60,6 +61,7 @@
"bezier-easing": "2.1.0",
"browser-cookies": "1.2.0",
"buffer": "6.0.3",
+ "canvas-confetti": "1.5.1",
"chai": "4.3.6",
"crypto-browserify": "3.12.0",
"date-fns": "2.27.0",
diff --git a/client/src/components/Donation/donation-modal.tsx b/client/src/components/Donation/donation-modal.tsx
index 27ffa552c7..1829e7f435 100644
--- a/client/src/components/Donation/donation-modal.tsx
+++ b/client/src/components/Donation/donation-modal.tsx
@@ -14,8 +14,7 @@ import {
closeDonationModal,
isDonationModalOpenSelector,
recentlyClaimedBlockSelector,
- executeGA,
- isAVariantSelector
+ executeGA
} from '../../redux';
import { isLocationSuperBlock } from '../../utils/path-parsers';
import { playTone } from '../../utils/tone';
@@ -25,11 +24,9 @@ import DonateForm from './donate-form';
const mapStateToProps = createSelector(
isDonationModalOpenSelector,
recentlyClaimedBlockSelector,
- isAVariantSelector,
- (show: boolean, recentlyClaimedBlock: string, isAVariant: boolean) => ({
+ (show: boolean, recentlyClaimedBlock: string) => ({
show,
- recentlyClaimedBlock,
- isAVariant
+ recentlyClaimedBlock
})
);
@@ -49,7 +46,6 @@ type DonateModalProps = {
location: WindowLocation | undefined;
recentlyClaimedBlock: string;
show: boolean;
- isAVariant: boolean;
};
function DonateModal({
@@ -57,8 +53,7 @@ function DonateModal({
closeDonationModal,
executeGA,
location,
- recentlyClaimedBlock,
- isAVariant
+ recentlyClaimedBlock
}: DonateModalProps): JSX.Element {
const [closeLabel, setCloseLabel] = React.useState(false);
const { t } = useTranslation();
@@ -134,11 +129,6 @@ function DonateModal({
);
- const renderABtestProgressText = () => {
- if (isAVariant) return getDonationText();
- else {t('donate.duration-5')};
- };
-
const progressDonationText = (
@@ -147,7 +137,7 @@ function DonateModal({
{!closeLabel && (
- {renderABtestProgressText()}
+ {getDonationText()}
)}
diff --git a/client/src/redux/donation-saga.js b/client/src/redux/donation-saga.js
index d24fa9038c..4ff0539162 100644
--- a/client/src/redux/donation-saga.js
+++ b/client/src/redux/donation-saga.js
@@ -7,6 +7,7 @@ import {
call,
take
} from 'redux-saga/effects';
+import { fireConfetti } from '../utils/fire-confetti';
import {
addDonation,
@@ -26,7 +27,8 @@ import {
postChargeStripeComplete,
postChargeStripeError,
postChargeStripeCardComplete,
- postChargeStripeCardError
+ postChargeStripeCardError,
+ isAVariantSelector
} from './';
const defaultDonationErrorMessage = `Something is not right. Please contact donors@freecodecamp.org`;
@@ -37,6 +39,12 @@ function* showDonateModalSaga() {
yield delay(200);
const recentlyClaimedBlock = yield select(recentlyClaimedBlockSelector);
yield put(openDonationModal());
+ if (recentlyClaimedBlock) {
+ const isAVariant = yield select(isAVariantSelector);
+ if (isAVariant === false) {
+ fireConfetti();
+ }
+ }
yield take(appTypes.closeDonationModal);
if (recentlyClaimedBlock) {
yield put(preventBlockDonationRequests());
diff --git a/client/src/utils/fire-confetti.ts b/client/src/utils/fire-confetti.ts
new file mode 100644
index 0000000000..2b36c2e3e6
--- /dev/null
+++ b/client/src/utils/fire-confetti.ts
@@ -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
+ });
+};
diff --git a/package-lock.json b/package-lock.json
index 2bb15dc510..dd2fea95d0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -985,6 +985,7 @@
"@reach/router": "1.3.4",
"@stripe/react-stripe-js": "1.7.0",
"@stripe/stripe-js": "1.24.0",
+ "@types/canvas-confetti": "1.4.2",
"@types/react-scrollable-anchor": "0.6.1",
"algoliasearch": "4.12.2",
"assert": "2.0.0",
@@ -993,6 +994,7 @@
"bezier-easing": "2.1.0",
"browser-cookies": "1.2.0",
"buffer": "6.0.3",
+ "canvas-confetti": "1.5.1",
"chai": "4.3.6",
"crypto-browserify": "3.12.0",
"date-fns": "2.27.0",
@@ -15171,6 +15173,11 @@
"@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": {
"version": "4.3.0",
"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",
"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": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz",
@@ -56676,6 +56692,7 @@
"@stripe/stripe-js": "1.24.0",
"@testing-library/jest-dom": "5.16.2",
"@testing-library/react": "12.1.3",
+ "@types/canvas-confetti": "*",
"@types/react-scrollable-anchor": "0.6.1",
"algoliasearch": "4.12.2",
"assert": "2.0.0",
@@ -56686,6 +56703,7 @@
"bezier-easing": "2.1.0",
"browser-cookies": "1.2.0",
"buffer": "6.0.3",
+ "canvas-confetti": "1.5.1",
"chai": "4.3.6",
"chokidar": "3.5.3",
"copy-webpack-plugin": "9.1.0",
@@ -65756,6 +65774,11 @@
"@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": {
"version": "4.3.0",
"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",
"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": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz",