1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-26 12:29:06 +01:00
metamask-extension/ui/pages/onboarding-flow/create-password/create-password.js
Howard Braham 9acd4b4ea1
feat(srp): add a quiz to the SRP reveal (#19283)
* feat(srp): add a quiz to the SRP reveal

* fixed the popover header centering

* lint fixes

* converted from `ui/components/ui/popover` to `ui/components/component-library/modal`

* responded to @darkwing review

* added unit tests

* renamed the folder to 'srp-quiz-modal'

* responded to Monte's review

* using i18n-helper in the test suite

* small improvement to JSXDict comments

* wrote a new webdriver.holdMouseDownOnElement() to assist with testing the "Hold to reveal SRP" button

* Updating layout and some storybook naming and migrating to tsx

* Apply suggestions from @georgewrmarshall

Co-authored-by: George Marshall <george.marshall@consensys.net>

* Unit test searches by data-testid instead of by text

* new layout and copy for the Settings->Security page

* now with 100% test coverage for /ui/pages/settings/security-tab
fixes #16871
fixes #18140

* e2e tests to reveal SRP after quiz

* e2e- Fix lint, remove unneeded extras

* @coreyjanssen and @georgewrmarshall compromise

Co-authored-by: George Marshall <george.marshall@consensys.net>
Co-authored-by: Corey Janssen <corey.janssen@consensys.net>

* trying isRequired again

* transparent background on PNG

* [e2e] moving functions to helpers and adding testid for SRP reveal quiz (#19481)

* moving functions to helpers and adding testid

* fix lint error

* took out the IPFS gateway fixes

* lint fix

* translations of SRP Reveal Quiz

* new Spanish translation from Guto

* Update describe for e2e tests

* Apply suggestion from @georgewrmarshall

Co-authored-by: George Marshall <george.marshall@consensys.net>

* fixed the Tab key problem

---------

Co-authored-by: georgewrmarshall <george.marshall@consensys.net>
Co-authored-by: Plasma Corral <32695229+plasmacorral@users.noreply.github.com>
Co-authored-by: Corey Janssen <corey.janssen@consensys.net>
2023-06-20 14:27:10 -04:00

301 lines
9.8 KiB
JavaScript

import React, { useState, useMemo, useContext, useEffect } from 'react';
import PropTypes from 'prop-types';
import { useHistory } from 'react-router-dom';
import zxcvbn from 'zxcvbn';
import { useSelector } from 'react-redux';
import { useI18nContext } from '../../../hooks/useI18nContext';
import Button from '../../../components/ui/button';
import Typography from '../../../components/ui/typography';
import {
TEXT_ALIGN,
TypographyVariant,
JustifyContent,
FONT_WEIGHT,
AlignItems,
} from '../../../helpers/constants/design-system';
import {
ONBOARDING_COMPLETION_ROUTE,
ONBOARDING_SECURE_YOUR_WALLET_ROUTE,
} from '../../../helpers/constants/routes';
import FormField from '../../../components/ui/form-field';
import Box from '../../../components/ui/box';
import CheckBox from '../../../components/ui/check-box';
import {
ThreeStepProgressBar,
threeStepStages,
TwoStepProgressBar,
twoStepStages,
} from '../../../components/app/step-progress-bar';
import { PASSWORD_MIN_LENGTH } from '../../../helpers/constants/common';
import ZENDESK_URLS from '../../../helpers/constants/zendesk-url';
import { getFirstTimeFlowType, getCurrentKeyring } from '../../../selectors';
import { FIRST_TIME_FLOW_TYPES } from '../../../helpers/constants/onboarding';
import { MetaMetricsContext } from '../../../contexts/metametrics';
import {
MetaMetricsEventCategory,
MetaMetricsEventName,
} from '../../../../shared/constants/metametrics';
import { Icon, IconName } from '../../../components/component-library';
export default function CreatePassword({
createNewAccount,
importWithRecoveryPhrase,
secretRecoveryPhrase,
}) {
const t = useI18nContext();
const [confirmPassword, setConfirmPassword] = useState('');
const [password, setPassword] = useState('');
const [passwordError, setPasswordError] = useState('');
const [passwordStrength, setPasswordStrength] = useState('');
const [passwordStrengthText, setPasswordStrengthText] = useState('');
const [confirmPasswordError, setConfirmPasswordError] = useState('');
const [termsChecked, setTermsChecked] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const history = useHistory();
const firstTimeFlowType = useSelector(getFirstTimeFlowType);
const trackEvent = useContext(MetaMetricsContext);
const currentKeyring = useSelector(getCurrentKeyring);
useEffect(() => {
if (currentKeyring) {
if (firstTimeFlowType === FIRST_TIME_FLOW_TYPES.IMPORT) {
history.replace(ONBOARDING_COMPLETION_ROUTE);
} else {
history.replace(ONBOARDING_SECURE_YOUR_WALLET_ROUTE);
}
}
}, [currentKeyring, history, firstTimeFlowType]);
const isValid = useMemo(() => {
if (!password || !confirmPassword || password !== confirmPassword) {
return false;
}
if (password.length < PASSWORD_MIN_LENGTH) {
return false;
}
return !passwordError && !confirmPasswordError;
}, [password, confirmPassword, passwordError, confirmPasswordError]);
const getPasswordStrengthLabel = (isTooShort, score) => {
if (isTooShort) {
return {
className: 'create-password__weak',
dataTestId: 'short-password-error',
text: t('passwordNotLongEnough'),
description: '',
};
}
if (score >= 4) {
return {
className: 'create-password__strong',
dataTestId: 'strong-password',
text: t('strong'),
description: '',
};
}
if (score === 3) {
return {
className: 'create-password__average',
dataTestId: 'average-password',
text: t('average'),
description: t('passwordStrengthDescription'),
};
}
return {
className: 'create-password__weak',
dataTestId: 'weak-password',
text: t('weak'),
description: t('passwordStrengthDescription'),
};
};
const handlePasswordChange = (passwordInput) => {
const isTooShort =
passwordInput.length && passwordInput.length < PASSWORD_MIN_LENGTH;
const { score } = zxcvbn(passwordInput);
const passwordStrengthLabel = getPasswordStrengthLabel(isTooShort, score);
const passwordStrengthComponent = t('passwordStrength', [
<span
key={score}
data-testid={passwordStrengthLabel.dataTestId}
className={passwordStrengthLabel.className}
>
{passwordStrengthLabel.text}
</span>,
]);
const confirmError =
!confirmPassword || passwordInput === confirmPassword
? ''
: t('passwordsDontMatch');
setPassword(passwordInput);
setPasswordStrength(passwordStrengthComponent);
setPasswordStrengthText(passwordStrengthLabel.description);
setConfirmPasswordError(confirmError);
};
const handleConfirmPasswordChange = (confirmPasswordInput) => {
const error =
password === confirmPasswordInput ? '' : t('passwordsDontMatch');
setConfirmPassword(confirmPasswordInput);
setConfirmPasswordError(error);
};
const handleCreate = async (event) => {
event?.preventDefault();
if (!isValid) {
return;
}
trackEvent({
category: MetaMetricsEventCategory.Onboarding,
event: MetaMetricsEventName.OnboardingWalletCreationAttempted,
});
// If secretRecoveryPhrase is defined we are in import wallet flow
if (
secretRecoveryPhrase &&
firstTimeFlowType === FIRST_TIME_FLOW_TYPES.IMPORT
) {
await importWithRecoveryPhrase(password, secretRecoveryPhrase);
history.push(ONBOARDING_COMPLETION_ROUTE);
} else {
// Otherwise we are in create new wallet flow
try {
if (createNewAccount) {
await createNewAccount(password);
}
history.push(ONBOARDING_SECURE_YOUR_WALLET_ROUTE);
} catch (error) {
setPasswordError(error.message);
}
}
};
return (
<div className="create-password__wrapper" data-testid="create-password">
{secretRecoveryPhrase &&
firstTimeFlowType === FIRST_TIME_FLOW_TYPES.IMPORT ? (
<TwoStepProgressBar
stage={twoStepStages.PASSWORD_CREATE}
marginBottom={4}
/>
) : (
<ThreeStepProgressBar
stage={threeStepStages.PASSWORD_CREATE}
marginBottom={4}
/>
)}
<Typography variant={TypographyVariant.H2} fontWeight={FONT_WEIGHT.BOLD}>
{t('createPassword')}
</Typography>
<Typography variant={TypographyVariant.H4} align={TEXT_ALIGN.CENTER}>
{t('passwordSetupDetails')}
</Typography>
<Box justifyContent={JustifyContent.center} marginTop={3}>
<form className="create-password__form" onSubmit={handleCreate}>
<FormField
dataTestId="create-password-new"
autoFocus
passwordStrength={passwordStrength}
passwordStrengthText={passwordStrengthText}
onChange={handlePasswordChange}
password={!showPassword}
titleText={t('newPassword')}
value={password}
titleDetail={
<Typography variant={TypographyVariant.H7}>
<a
href=""
data-testid="show-password"
className="create-password__form--password-button"
onClick={(e) => {
e.preventDefault();
setShowPassword(!showPassword);
}}
>
{showPassword ? t('hide') : t('show')}
</a>
</Typography>
}
/>
<FormField
dataTestId="create-password-confirm"
onChange={handleConfirmPasswordChange}
password={!showPassword}
error={confirmPasswordError}
titleText={t('confirmPassword')}
value={confirmPassword}
titleDetail={
isValid && (
<div className="create-password__form--checkmark">
<Icon name={IconName.Check} />
</div>
)
}
/>
<Box
alignItems={AlignItems.center}
justifyContent={JustifyContent.spaceBetween}
marginBottom={4}
>
<label className="create-password__form__terms-label">
<CheckBox
dataTestId="create-password-terms"
onClick={() => setTermsChecked(!termsChecked)}
checked={termsChecked}
/>
<Typography
variant={TypographyVariant.H5}
boxProps={{ marginLeft: 3 }}
>
{t('passwordTermsWarning', [
<a
onClick={(e) => e.stopPropagation()}
key="create-password__link-text"
href={ZENDESK_URLS.PASSWORD_AND_SRP_ARTICLE}
target="_blank"
rel="noopener noreferrer"
>
<span className="create-password__link-text">
{t('learnMoreUpperCase')}
</span>
</a>,
])}
</Typography>
</label>
</Box>
<Button
data-testid={
secretRecoveryPhrase &&
firstTimeFlowType === FIRST_TIME_FLOW_TYPES.IMPORT
? 'create-password-import'
: 'create-password-wallet'
}
type="primary"
large
className="create-password__form--submit-button"
disabled={!isValid || !termsChecked}
onClick={handleCreate}
>
{secretRecoveryPhrase &&
firstTimeFlowType === FIRST_TIME_FLOW_TYPES.IMPORT
? t('importMyWallet')
: t('createNewWallet')}
</Button>
</form>
</Box>
</div>
);
}
CreatePassword.propTypes = {
createNewAccount: PropTypes.func,
importWithRecoveryPhrase: PropTypes.func,
secretRecoveryPhrase: PropTypes.string,
};