1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-10-23 20:05:27 +02:00
metamask-extension/ui/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js
2021-04-28 14:53:59 -05:00

348 lines
9.5 KiB
JavaScript

import { ethers } from 'ethers';
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import TextField from '../../../../components/ui/text-field';
import Button from '../../../../components/ui/button';
import {
INITIALIZE_SELECT_ACTION_ROUTE,
INITIALIZE_END_OF_FLOW_ROUTE,
} from '../../../../helpers/constants/routes';
const { isValidMnemonic } = ethers.utils;
export default class ImportWithSeedPhrase extends PureComponent {
static contextTypes = {
t: PropTypes.func,
metricsEvent: PropTypes.func,
};
static propTypes = {
history: PropTypes.object,
onSubmit: PropTypes.func.isRequired,
setSeedPhraseBackedUp: PropTypes.func,
initializeThreeBox: PropTypes.func,
};
state = {
seedPhrase: '',
showSeedPhrase: false,
password: '',
confirmPassword: '',
seedPhraseError: '',
passwordError: '',
confirmPasswordError: '',
termsChecked: false,
};
parseSeedPhrase = (seedPhrase) =>
(seedPhrase || '').trim().toLowerCase().match(/\w+/gu)?.join(' ') || '';
UNSAFE_componentWillMount() {
this._onBeforeUnload = () =>
this.context.metricsEvent({
eventOpts: {
category: 'Onboarding',
action: 'Import Seed Phrase',
name: 'Close window on import screen',
},
customVariables: {
errorLabel: 'Seed Phrase Error',
errorMessage: this.state.seedPhraseError,
},
});
window.addEventListener('beforeunload', this._onBeforeUnload);
}
componentWillUnmount() {
window.removeEventListener('beforeunload', this._onBeforeUnload);
}
handleSeedPhraseChange(seedPhrase) {
let seedPhraseError = '';
if (seedPhrase) {
const parsedSeedPhrase = this.parseSeedPhrase(seedPhrase);
const wordCount = parsedSeedPhrase.split(/\s/u).length;
if (wordCount % 3 !== 0 || wordCount > 24 || wordCount < 12) {
seedPhraseError = this.context.t('seedPhraseReq');
} else if (!isValidMnemonic(parsedSeedPhrase)) {
seedPhraseError = this.context.t('invalidSeedPhrase');
}
}
this.setState({ seedPhrase, seedPhraseError });
}
handlePasswordChange(password) {
const { t } = this.context;
this.setState((state) => {
const { confirmPassword } = state;
let confirmPasswordError = '';
let passwordError = '';
if (password && password.length < 8) {
passwordError = t('passwordNotLongEnough');
}
if (confirmPassword && password !== confirmPassword) {
confirmPasswordError = t('passwordsDontMatch');
}
return {
password,
passwordError,
confirmPasswordError,
};
});
}
handleConfirmPasswordChange(confirmPassword) {
const { t } = this.context;
this.setState((state) => {
const { password } = state;
let confirmPasswordError = '';
if (password !== confirmPassword) {
confirmPasswordError = t('passwordsDontMatch');
}
return {
confirmPassword,
confirmPasswordError,
};
});
}
handleImport = async (event) => {
event.preventDefault();
if (!this.isValid()) {
return;
}
const { password, seedPhrase } = this.state;
const {
history,
onSubmit,
setSeedPhraseBackedUp,
initializeThreeBox,
} = this.props;
try {
await onSubmit(password, this.parseSeedPhrase(seedPhrase));
this.context.metricsEvent({
eventOpts: {
category: 'Onboarding',
action: 'Import Seed Phrase',
name: 'Import Complete',
},
});
setSeedPhraseBackedUp(true).then(async () => {
initializeThreeBox();
history.push(INITIALIZE_END_OF_FLOW_ROUTE);
});
} catch (error) {
this.setState({ seedPhraseError: error.message });
}
};
isValid() {
const {
seedPhrase,
password,
confirmPassword,
passwordError,
confirmPasswordError,
seedPhraseError,
} = this.state;
if (
!password ||
!confirmPassword ||
!seedPhrase ||
password !== confirmPassword
) {
return false;
}
if (password.length < 8) {
return false;
}
return !passwordError && !confirmPasswordError && !seedPhraseError;
}
onTermsKeyPress = ({ key }) => {
if (key === ' ' || key === 'Enter') {
this.toggleTermsCheck();
}
};
toggleTermsCheck = () => {
this.context.metricsEvent({
eventOpts: {
category: 'Onboarding',
action: 'Import Seed Phrase',
name: 'Check ToS',
},
});
this.setState((prevState) => ({
termsChecked: !prevState.termsChecked,
}));
};
toggleShowSeedPhrase = () => {
this.setState(({ showSeedPhrase }) => ({
showSeedPhrase: !showSeedPhrase,
}));
};
render() {
const { t } = this.context;
const {
seedPhraseError,
showSeedPhrase,
passwordError,
confirmPasswordError,
termsChecked,
} = this.state;
return (
<form className="first-time-flow__form" onSubmit={this.handleImport}>
<div className="first-time-flow__create-back">
<a
onClick={(e) => {
e.preventDefault();
this.context.metricsEvent({
eventOpts: {
category: 'Onboarding',
action: 'Import Seed Phrase',
name: 'Go Back from Onboarding Import',
},
customVariables: {
errorLabel: 'Seed Phrase Error',
errorMessage: seedPhraseError,
},
});
this.props.history.push(INITIALIZE_SELECT_ACTION_ROUTE);
}}
href="#"
>
{`< Back`}
</a>
</div>
<div className="first-time-flow__header">
{t('importAccountSeedPhrase')}
</div>
<div className="first-time-flow__text-block">{t('secretPhrase')}</div>
<div className="first-time-flow__textarea-wrapper">
<label>{t('walletSeed')}</label>
{showSeedPhrase ? (
<textarea
className="first-time-flow__textarea"
onChange={(e) => this.handleSeedPhraseChange(e.target.value)}
value={this.state.seedPhrase}
placeholder={t('seedPhrasePlaceholder')}
/>
) : (
<TextField
className="first-time-flow__textarea first-time-flow__seedphrase"
type="password"
onChange={(e) => this.handleSeedPhraseChange(e.target.value)}
value={this.state.seedPhrase}
placeholder={t('seedPhrasePlaceholderPaste')}
/>
)}
{seedPhraseError && <span className="error">{seedPhraseError}</span>}
<div
className="first-time-flow__checkbox-container"
onClick={this.toggleShowSeedPhrase}
>
<div
className="first-time-flow__checkbox"
tabIndex="0"
role="checkbox"
onKeyPress={this.toggleShowSeedPhrase}
aria-checked={showSeedPhrase}
aria-labelledby="ftf-chk1-label"
>
{showSeedPhrase ? <i className="fa fa-check fa-2x" /> : null}
</div>
<span
id="ftf-chk1-label"
className="first-time-flow__checkbox-label"
>
{t('showSeedPhrase')}
</span>
</div>
</div>
<TextField
id="password"
label={t('newPassword')}
type="password"
className="first-time-flow__input"
value={this.state.password}
onChange={(event) => this.handlePasswordChange(event.target.value)}
error={passwordError}
autoComplete="new-password"
margin="normal"
largeLabel
/>
<TextField
id="confirm-password"
label={t('confirmPassword')}
type="password"
className="first-time-flow__input"
value={this.state.confirmPassword}
onChange={(event) =>
this.handleConfirmPasswordChange(event.target.value)
}
error={confirmPasswordError}
autoComplete="confirm-password"
margin="normal"
largeLabel
/>
<div
className="first-time-flow__checkbox-container"
onClick={this.toggleTermsCheck}
>
<div
className="first-time-flow__checkbox first-time-flow__terms"
tabIndex="0"
role="checkbox"
onKeyPress={this.onTermsKeyPress}
aria-checked={termsChecked}
aria-labelledby="ftf-chk1-label"
>
{termsChecked ? <i className="fa fa-check fa-2x" /> : null}
</div>
<span id="ftf-chk1-label" className="first-time-flow__checkbox-label">
{t('acceptTermsOfUse', [
<a
onClick={(e) => e.stopPropagation()}
key="first-time-flow__link-text"
href="https://metamask.io/terms.html"
target="_blank"
rel="noopener noreferrer"
>
<span className="first-time-flow__link-text">{t('terms')}</span>
</a>,
])}
</span>
</div>
<Button
type="primary"
submit
className="first-time-flow__button"
disabled={!this.isValid() || !termsChecked}
>
{t('import')}
</Button>
</form>
);
}
}