feat: a/b test lock icon (#45499)

* feat: a/b test

* Update client/i18n/locales/english/translations.json

* Apply suggestions from code review
This commit is contained in:
Ahmad Abdolsaheb
2022-03-22 19:28:41 +03:00
committed by GitHub
parent 4d620dea1a
commit 94650964c4
9 changed files with 39 additions and 94 deletions

View File

@ -52,7 +52,6 @@
"@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.25.0", "@stripe/stripe-js": "1.25.0",
"@types/canvas-confetti": "1.4.2",
"@types/react-scrollable-anchor": "0.6.1", "@types/react-scrollable-anchor": "0.6.1",
"algoliasearch": "4.13.0", "algoliasearch": "4.13.0",
"assert": "2.0.0", "assert": "2.0.0",
@ -61,7 +60,6 @@
"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

@ -23,7 +23,8 @@ import {
defaultDonationFormState, defaultDonationFormState,
userSelector, userSelector,
postChargeStripe, postChargeStripe,
postChargeStripeCard postChargeStripeCard,
isAVariantSelector
} from '../../redux'; } from '../../redux';
import Spacer from '../helpers/spacer'; import Spacer from '../helpers/spacer';
import { Themes } from '../settings/theme'; import { Themes } from '../settings/theme';
@ -33,6 +34,7 @@ import type { AddDonationData } from './paypal-button';
import PaypalButton from './paypal-button'; import PaypalButton from './paypal-button';
import StripeCardForm, { HandleAuthentication } from './stripe-card-form'; import StripeCardForm, { HandleAuthentication } from './stripe-card-form';
import WalletsWrapper from './walletsButton'; import WalletsWrapper from './walletsButton';
import SecurityLockIcon from './security-lock-icon';
import './donation.css'; import './donation.css';
@ -78,6 +80,7 @@ type DonateFormProps = {
) => string; ) => string;
theme: Themes; theme: Themes;
updateDonationFormState: (state: AddDonationData) => unknown; updateDonationFormState: (state: AddDonationData) => unknown;
isAVariant: boolean;
}; };
const mapStateToProps = createSelector( const mapStateToProps = createSelector(
@ -86,19 +89,22 @@ const mapStateToProps = createSelector(
isDonatingSelector, isDonatingSelector,
donationFormStateSelector, donationFormStateSelector,
userSelector, userSelector,
isAVariantSelector,
( (
showLoading: DonateFormProps['showLoading'], showLoading: DonateFormProps['showLoading'],
isSignedIn: DonateFormProps['isSignedIn'], isSignedIn: DonateFormProps['isSignedIn'],
isDonating: DonateFormProps['isDonating'], isDonating: DonateFormProps['isDonating'],
donationFormState: DonateFormState, donationFormState: DonateFormState,
{ email, theme }: { email: string; theme: Themes } { email, theme }: { email: string; theme: Themes },
isAVariant: boolean
) => ({ ) => ({
isSignedIn, isSignedIn,
isDonating, isDonating,
showLoading, showLoading,
donationFormState, donationFormState,
email, email,
theme theme,
isAVariant
}) })
); );
@ -315,7 +321,8 @@ class DonateForm extends Component<DonateFormProps, DonateFormComponentState> {
t, t,
isMinimalForm, isMinimalForm,
isSignedIn, isSignedIn,
isDonating isDonating,
isAVariant
} = this.props; } = this.props;
const priorityTheme = defaultTheme ? defaultTheme : theme; const priorityTheme = defaultTheme ? defaultTheme : theme;
const isOneTime = donationDuration === 'onetime'; const isOneTime = donationDuration === 'onetime';
@ -328,6 +335,7 @@ class DonateForm extends Component<DonateFormProps, DonateFormComponentState> {
return ( return (
<> <>
<b className={isMinimalForm ? 'donation-label-modal' : ''}> <b className={isMinimalForm ? 'donation-label-modal' : ''}>
{isAVariant === false && <SecurityLockIcon />}
{this.getDonationButtonLabel()}: {this.getDonationButtonLabel()}:
</b> </b>
<Spacer /> <Spacer />
@ -366,6 +374,7 @@ class DonateForm extends Component<DonateFormProps, DonateFormComponentState> {
processing={processing} processing={processing}
t={t} t={t}
theme={priorityTheme} theme={priorityTheme}
isAVariant={isAVariant}
/> />
</> </>
)} )}

View File

@ -334,6 +334,10 @@ li.disabled > a {
height: 22px; height: 22px;
} }
.confirm-donation-btn svg.svg-inline--fa.fa-lock {
margin-bottom: 2px;
}
@media screen and (min-width: 355px) { @media screen and (min-width: 355px) {
.form-payment-methods { .form-payment-methods {
height: 30px; height: 30px;

View File

@ -0,0 +1,14 @@
import React from 'react';
import { faLock } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
const SecurityLockIcon = (): JSX.Element => {
return (
<>
<FontAwesomeIcon icon={faLock} />
&nbsp;&nbsp;
</>
);
};
export default SecurityLockIcon;

View File

@ -17,6 +17,7 @@ import React, { useState } from 'react';
import envData from '../../../../config/env.json'; import envData from '../../../../config/env.json';
import { Themes } from '../settings/theme'; import { Themes } from '../settings/theme';
import { AddDonationData } from './paypal-button'; import { AddDonationData } from './paypal-button';
import SecurityLockIcon from './security-lock-icon';
const { stripePublicKey }: { stripePublicKey: string | null } = envData; const { stripePublicKey }: { stripePublicKey: string | null } = envData;
@ -34,6 +35,7 @@ interface FormPropTypes {
t: (label: string) => string; t: (label: string) => string;
theme: Themes; theme: Themes;
processing: boolean; processing: boolean;
isAVariant: boolean;
} }
interface Element { interface Element {
@ -49,7 +51,8 @@ const StripeCardForm = ({
t, t,
onDonationStateChange, onDonationStateChange,
postStripeCardDonation, postStripeCardDonation,
processing processing,
isAVariant
}: FormPropTypes): JSX.Element => { }: FormPropTypes): JSX.Element => {
const [isSubmissionValid, setSubmissionValidity] = useState(true); const [isSubmissionValid, setSubmissionValidity] = useState(true);
const [isTokenizing, setTokenizing] = useState(false); const [isTokenizing, setTokenizing] = useState(false);
@ -167,7 +170,8 @@ const StripeCardForm = ({
disabled={!stripe || !elements || isSubmitting} disabled={!stripe || !elements || isSubmitting}
type='submit' type='submit'
> >
Donate {isAVariant === false && <SecurityLockIcon />}
{t('buttons.donate')}
</Button> </Button>
</Form> </Form>
); );

View File

@ -8,7 +8,6 @@ import {
take take
} from 'redux-saga/effects'; } from 'redux-saga/effects';
import i18next from 'i18next'; import i18next from 'i18next';
import { fireConfetti } from '../utils/fire-confetti';
import { import {
addDonation, addDonation,
@ -28,8 +27,7 @@ import {
postChargeStripeComplete, postChargeStripeComplete,
postChargeStripeError, postChargeStripeError,
postChargeStripeCardComplete, postChargeStripeCardComplete,
postChargeStripeCardError, postChargeStripeCardError
isAVariantSelector
} from './'; } from './';
const defaultDonationErrorMessage = i18next.t('donate.error-2'); const defaultDonationErrorMessage = i18next.t('donate.error-2');
@ -40,12 +38,6 @@ 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

@ -1,48 +0,0 @@
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
});
};

View File

@ -94,7 +94,7 @@ const patreonDefaultPledgeAmount = 500;
const aBTestConfig = { const aBTestConfig = {
isTesting: true, isTesting: true,
type: 'badgeProgressModalCopy' type: 'addSecurityLock'
}; };
module.exports = { module.exports = {

28
package-lock.json generated
View File

@ -985,7 +985,6 @@
"@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.25.0", "@stripe/stripe-js": "1.25.0",
"@types/canvas-confetti": "1.4.2",
"@types/react-scrollable-anchor": "0.6.1", "@types/react-scrollable-anchor": "0.6.1",
"algoliasearch": "4.13.0", "algoliasearch": "4.13.0",
"assert": "2.0.0", "assert": "2.0.0",
@ -994,7 +993,6 @@
"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",
@ -15173,11 +15171,6 @@
"@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",
@ -20421,15 +20414,6 @@
"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",
@ -56530,7 +56514,6 @@
"@stripe/stripe-js": "1.25.0", "@stripe/stripe-js": "1.25.0",
"@testing-library/jest-dom": "5.16.2", "@testing-library/jest-dom": "5.16.2",
"@testing-library/react": "12.1.4", "@testing-library/react": "12.1.4",
"@types/canvas-confetti": "1.4.2",
"@types/react-scrollable-anchor": "0.6.1", "@types/react-scrollable-anchor": "0.6.1",
"algoliasearch": "4.13.0", "algoliasearch": "4.13.0",
"assert": "2.0.0", "assert": "2.0.0",
@ -56541,7 +56524,6 @@
"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",
@ -65612,11 +65594,6 @@
"@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",
@ -70080,11 +70057,6 @@
"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",