diff --git a/common/assets/images/wallets/digital-bitbox.svg b/common/assets/images/wallets/digital-bitbox.svg new file mode 100644 index 00000000..dd382940 --- /dev/null +++ b/common/assets/images/wallets/digital-bitbox.svg @@ -0,0 +1,15 @@ + + + + digital-bitbox + Created with Sketch. + + + + + + + + + + diff --git a/common/assets/images/wallets/ledger.svg b/common/assets/images/wallets/ledger.svg new file mode 100644 index 00000000..7fe44bd7 --- /dev/null +++ b/common/assets/images/wallets/ledger.svg @@ -0,0 +1,18 @@ + + + + ledger + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/common/assets/images/wallets/metamask.svg b/common/assets/images/wallets/metamask.svg new file mode 100644 index 00000000..9000b8a1 --- /dev/null +++ b/common/assets/images/wallets/metamask.svg @@ -0,0 +1,81 @@ + + + + metamask + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/common/assets/images/wallets/mist.svg b/common/assets/images/wallets/mist.svg new file mode 100644 index 00000000..b52d8bc6 --- /dev/null +++ b/common/assets/images/wallets/mist.svg @@ -0,0 +1,20 @@ + + + + mist + Created with Sketch. + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/common/assets/images/wallets/trezor.svg b/common/assets/images/wallets/trezor.svg new file mode 100644 index 00000000..6f0e66d5 --- /dev/null +++ b/common/assets/images/wallets/trezor.svg @@ -0,0 +1,14 @@ + + + + trezor + Created with Sketch. + + + + + + + + + \ No newline at end of file diff --git a/common/components/OfflineAwareUnlockHeader.tsx b/common/components/OfflineAwareUnlockHeader.tsx index 5977a387..1790ae63 100644 --- a/common/components/OfflineAwareUnlockHeader.tsx +++ b/common/components/OfflineAwareUnlockHeader.tsx @@ -6,10 +6,10 @@ import { connect } from 'react-redux'; import { AppState } from 'reducers'; interface Props { - allowReadOnly: boolean; + disabledWallets?: string[]; } -export const OfflineAwareUnlockHeader: React.SFC = ({ allowReadOnly }) => ( - } allowReadOnly={allowReadOnly} /> +export const OfflineAwareUnlockHeader: React.SFC = ({ disabledWallets }) => ( + } disabledWallets={disabledWallets} /> ); interface StateProps { diff --git a/common/components/WalletDecrypt/Keystore.tsx b/common/components/WalletDecrypt/Keystore.tsx deleted file mode 100644 index 8b414dea..00000000 --- a/common/components/WalletDecrypt/Keystore.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import { isKeystorePassRequired } from 'libs/wallet'; -import React, { Component } from 'react'; -import translate, { translateRaw } from 'translations'; -import { TShowNotification } from 'actions/notifications'; - -export interface KeystoreValue { - file: string; - password: string; - valid: boolean; -} - -interface Props { - value: KeystoreValue; - onChange(value: KeystoreValue): void; - onUnlock(): void; - showNotification(level: string, message: string): TShowNotification; -} - -function isPassRequired(file: string): boolean { - let passReq = false; - try { - passReq = isKeystorePassRequired(file); - } catch (e) { - // TODO: communicate invalid file to user - } - return passReq; -} - -function isValidFile(rawFile: File): boolean { - const fileType = rawFile.type; - return fileType === '' || fileType === 'application/json'; -} - -export default class KeystoreDecrypt extends Component { - public render() { - const { file, password } = this.props.value; - const passReq = isPassRequired(file); - - return ( -
-
-

{translate('ADD_Radio_2_alt')}

- -
- - -
-

{translate('ADD_Label_3')}

- 0 ? 'is-valid' : 'is-invalid'}`} - value={password} - onChange={this.onPasswordChange} - onKeyDown={this.onKeyDown} - placeholder={translateRaw('x_Password')} - type="password" - /> -
-
-
-
- ); - } - - public onKeyDown = (e: any) => { - if (e.keyCode === 13) { - e.preventDefault(); - e.stopPropagation(); - this.props.onUnlock(); - } - }; - - public onPasswordChange = (e: any) => { - const valid = this.props.value.file.length && e.target.value.length; - this.props.onChange({ - ...this.props.value, - password: e.target.value, - valid - }); - }; - - public handleFileSelection = (e: any) => { - const fileReader = new FileReader(); - const target = e.target; - const inputFile = target.files[0]; - - fileReader.onload = () => { - const keystore = fileReader.result; - const passReq = isPassRequired(keystore); - - this.props.onChange({ - ...this.props.value, - file: keystore, - valid: keystore.length && !passReq - }); - }; - - if (isValidFile(inputFile)) { - fileReader.readAsText(inputFile, 'utf-8'); - } else { - this.props.showNotification('danger', translateRaw('ERROR_3')); - } - }; -} diff --git a/common/components/WalletDecrypt/ViewOnly.tsx b/common/components/WalletDecrypt/ViewOnly.tsx deleted file mode 100644 index 85e4b4ad..00000000 --- a/common/components/WalletDecrypt/ViewOnly.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import React, { Component } from 'react'; -import translate from 'translations'; -import { donationAddressMap } from 'config/data'; -import { isValidETHAddress } from 'libs/validators'; -import { AddressOnlyWallet } from 'libs/wallet'; - -interface Props { - onUnlock(param: any): void; -} - -interface State { - address: string; -} - -export default class ViewOnlyDecrypt extends Component { - public state = { - address: '' - }; - - public render() { - const { address } = this.state; - const isValid = isValidETHAddress(address); - - return ( -
-
-

{translate('MYWAL_Address')}

- -
- - - -
-
-
- ); - } - - private changeAddress = (ev: React.FormEvent) => { - this.setState({ address: ev.currentTarget.value }); - }; - - private openWallet = (ev: React.FormEvent) => { - const { address } = this.state; - ev.preventDefault(); - if (isValidETHAddress(address)) { - const wallet = new AddressOnlyWallet(address); - this.props.onUnlock(wallet); - } - }; -} diff --git a/common/components/WalletDecrypt/WalletDecrypt.scss b/common/components/WalletDecrypt/WalletDecrypt.scss new file mode 100644 index 00000000..9b5f7c2d --- /dev/null +++ b/common/components/WalletDecrypt/WalletDecrypt.scss @@ -0,0 +1,123 @@ +@import 'common/sass/variables'; +@import 'common/sass/mixins'; + +$speed: 500ms; + +@keyframes decrypt-enter { + 0% { + opacity: 0; + transform: translateY(8px); + } + 100% { + opacity: 1; + transform: translateY(0px); + } +} + +@mixin decrypt-title { + text-align: center; + line-height: 1; + margin: 0 0 30px; + font-weight: normal; + animation: decrypt-enter $speed ease 1; +} + +.WalletDecrypt { + position: relative; + + &-wallets { + &-title { + @include decrypt-title; + } + + &-row { + display: flex; + justify-content: center; + flex-wrap: wrap; + margin-bottom: 10px; + + @media screen and (max-width: $screen-xs) { + margin: 0; + } + + + &:last-child { + margin: 0; + } + } + } + + &-decrypt { + position: relative; + text-align: center; + padding-bottom: $space; + + &-back { + @include reset-button; + position: absolute; + top: 0; + left: 0; + line-height: $font-size-large; + opacity: 0.4; + transition: opacity 120ms ease, transform 120ms ease; + + @media (max-width: $screen-md) { + top: auto; + bottom: -10px; + left: 50%; + transform: translateX(-50%); + } + + &:hover, + &:focus { + opacity: 0.8; + } + + &:active { + outline: none; + opacity: 1; + } + + .fa { + position: relative; + top: -2px; + font-size: 11px; + } + } + + &-title { + @include decrypt-title; + } + + &-form { + max-width: 360px; + margin: 0 auto; + } + } +} + +// Animation between two slides +.DecryptContent { + &-enter { + opacity: 0; + transition: opacity $speed * .25 ease $speed * .125; + + &-active { + opacity: 1; + } + } + + &-exit { + position: absolute; + top: 0; + left: 0; + width: 100%; + opacity: 1; + transition: opacity $speed * .25 ease; + pointer-events: none; + + &-active { + opacity: 0; + } + } +} diff --git a/common/components/WalletDecrypt/WalletDecrypt.tsx b/common/components/WalletDecrypt/WalletDecrypt.tsx new file mode 100644 index 00000000..ca6d145b --- /dev/null +++ b/common/components/WalletDecrypt/WalletDecrypt.tsx @@ -0,0 +1,393 @@ +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import isEmpty from 'lodash/isEmpty'; +import { TransitionGroup, CSSTransition } from 'react-transition-group'; +import { + setWallet, + TSetWallet, + unlockKeystore, + TUnlockKeystore, + unlockMnemonic, + TUnlockMnemonic, + unlockPrivateKey, + TUnlockPrivateKey, + unlockWeb3, + TUnlockWeb3, + resetWallet, + TResetWallet +} from 'actions/wallet'; +import { reset, TReset } from 'actions/transaction'; +import translate from 'translations'; +import { + DigitalBitboxDecrypt, + KeystoreDecrypt, + LedgerNanoSDecrypt, + MnemonicDecrypt, + PrivateKeyDecrypt, + PrivateKeyValue, + TrezorDecrypt, + ViewOnlyDecrypt, + Web3Decrypt, + NavigationPrompt, + WalletButton +} from './components'; +import { AppState } from 'reducers'; +import { knowledgeBaseURL } from 'config/data'; +import { IWallet } from 'libs/wallet'; +import DigitalBitboxIcon from 'assets/images/wallets/digital-bitbox.svg'; +import LedgerIcon from 'assets/images/wallets/ledger.svg'; +import MetamaskIcon from 'assets/images/wallets/metamask.svg'; +import MistIcon from 'assets/images/wallets/mist.svg'; +import TrezorIcon from 'assets/images/wallets/trezor.svg'; +import './WalletDecrypt.scss'; + +type UnlockParams = {} | PrivateKeyValue; + +interface Props { + resetTransactionState: TReset; + unlockKeystore: TUnlockKeystore; + unlockMnemonic: TUnlockMnemonic; + unlockPrivateKey: TUnlockPrivateKey; + setWallet: TSetWallet; + unlockWeb3: TUnlockWeb3; + resetWallet: TResetWallet; + wallet: IWallet; + hidden?: boolean; + offline: boolean; + disabledWallets?: string[]; +} + +interface State { + selectedWalletKey: string | null; + value: UnlockParams | null; +} + +interface BaseWalletInfo { + lid: string; + component: any; + initialParams: object; + unlock: any; + helpLink?: string; + isReadOnly?: boolean; + attemptUnlock?: boolean; +} + +export interface SecureWalletInfo extends BaseWalletInfo { + icon?: string | null; + description: string; +} + +export interface InsecureWalletInfo extends BaseWalletInfo { + example: string; +} + +const WEB3_TYPES = { + MetamaskInpageProvider: { + lid: 'x_MetaMask', + icon: MetamaskIcon + }, + EthereumProvider: { + lid: 'x_Mist', + icon: MistIcon + } +}; +const WEB3_TYPE: string | false = + (window as any).web3 && (window as any).web3.currentProvider.constructor.name; + +const SECURE_WALLETS = ['web3', 'ledger-nano-s', 'trezor', 'digital-bitbox']; +const INSECURE_WALLETS = ['private-key', 'keystore-file', 'mnemonic-phrase']; + +export class WalletDecrypt extends Component { + public WALLETS: { [key: string]: SecureWalletInfo | InsecureWalletInfo } = { + web3: { + lid: WEB3_TYPE ? WEB3_TYPES[WEB3_TYPE].lid : 'x_Web3', + icon: WEB3_TYPE && WEB3_TYPES[WEB3_TYPE].icon, + description: 'ADD_Web3Desc', + component: Web3Decrypt, + initialParams: {}, + unlock: this.props.unlockWeb3, + attemptUnlock: true, + helpLink: `${knowledgeBaseURL}/migration/moving-from-private-key-to-metamask` + }, + 'ledger-nano-s': { + lid: 'x_Ledger', + icon: LedgerIcon, + description: 'ADD_HardwareDesc', + component: LedgerNanoSDecrypt, + initialParams: {}, + unlock: this.props.setWallet, + helpLink: + 'https://ledger.zendesk.com/hc/en-us/articles/115005200009-How-to-use-MyEtherWallet-with-Ledger' + }, + trezor: { + lid: 'x_Trezor', + icon: TrezorIcon, + description: 'ADD_HardwareDesc', + component: TrezorDecrypt, + initialParams: {}, + unlock: this.props.setWallet, + helpLink: 'https://doc.satoshilabs.com/trezor-apps/mew.html' + }, + 'digital-bitbox': { + lid: 'x_DigitalBitbox', + icon: DigitalBitboxIcon, + description: 'ADD_HardwareDesc', + component: DigitalBitboxDecrypt, + initialParams: {}, + unlock: this.props.setWallet, + helpLink: 'https://digitalbitbox.com/ethereum' + }, + 'keystore-file': { + lid: 'x_Keystore2', + example: 'UTC--2017-12-15T17-35-22.547Z--6be6e49e82425a5aa56396db03512f2cc10e95e8', + component: KeystoreDecrypt, + initialParams: { + file: '', + password: '' + }, + unlock: this.props.unlockKeystore, + helpLink: `${knowledgeBaseURL}/private-keys-passwords/difference-beween-private-key-and-keystore-file.html` + }, + 'mnemonic-phrase': { + lid: 'x_Mnemonic', + example: 'brain surround have swap horror cheese file distinct', + component: MnemonicDecrypt, + initialParams: {}, + unlock: this.props.unlockMnemonic, + helpLink: `${knowledgeBaseURL}/private-keys-passwords/difference-beween-private-key-and-keystore-file.html` + }, + 'private-key': { + lid: 'x_PrivKey2', + example: 'f1d0e0789c6d40f399ca90cc674b7858de4c719e0d5752a60d5d2f6baa45d4c9', + component: PrivateKeyDecrypt, + initialParams: { + key: '', + password: '' + }, + unlock: this.props.unlockPrivateKey, + helpLink: `${knowledgeBaseURL}/private-keys-passwords/difference-beween-private-key-and-keystore-file.html` + }, + 'view-only': { + lid: 'View Address', + example: '0x7cB57B5A97eAbe94205C07890BE4c1aD31E486A8', + component: ViewOnlyDecrypt, + initialParams: {}, + unlock: this.props.setWallet, + helpLink: '', + isReadOnly: true + } + }; + public state: State = { + selectedWalletKey: null, + value: null + }; + + public componentWillReceiveProps(nextProps) { + // Reset state when unlock is hidden / revealed + if (nextProps.hidden !== this.props.hidden) { + this.setState({ + value: null, + selectedWalletKey: null + }); + } + } + + public getSelectedWallet() { + const { selectedWalletKey } = this.state; + if (!selectedWalletKey) { + return null; + } + + return this.WALLETS[selectedWalletKey]; + } + + public getDecryptionComponent() { + const selectedWallet = this.getSelectedWallet(); + if (!selectedWallet) { + return null; + } + + return ( + + ); + } + + public isOnlineRequiredWalletAndOffline(selectedWalletKey) { + const onlineRequiredWallets = ['trezor', 'ledger-nano-s']; + return this.props.offline && onlineRequiredWallets.includes(selectedWalletKey); + } + + public buildWalletOptions() { + const viewOnly = this.WALLETS['view-only'] as InsecureWalletInfo; + + return ( +
+

{translate('decrypt_Access')}

+ +
+ {SECURE_WALLETS.map(type => { + const wallet = this.WALLETS[type] as SecureWalletInfo; + return ( + + ); + })} +
+
+ {INSECURE_WALLETS.map(type => { + const wallet = this.WALLETS[type] as InsecureWalletInfo; + return ( + + ); + })} + + +
+
+ ); + } + + public handleWalletChoice = (walletType: string) => { + const wallet = this.WALLETS[walletType]; + if (!wallet) { + return; + } + + let timeout = 0; + + if (wallet.attemptUnlock) { + timeout = 250; + wallet.unlock(); + } + + setTimeout(() => { + this.setState({ + selectedWalletKey: walletType, + value: wallet.initialParams + }); + }, timeout); + }; + + public clearWalletChoice = () => { + this.setState({ + selectedWalletKey: null, + value: null + }); + }; + + public render() { + const { wallet, hidden } = this.props; + const selectedWallet = this.getSelectedWallet(); + const decryptionComponent = this.getDecryptionComponent(); + const unlocked = !!wallet; + return ( +
+ + {!hidden && ( +
+
+ + {decryptionComponent && selectedWallet ? ( + +
+ +

+ {!selectedWallet.isReadOnly && 'Unlock your'}{' '} + {translate(selectedWallet.lid)} +

+
+ {decryptionComponent} +
+
+
+ ) : ( + + {this.buildWalletOptions()} + + )} +
+
+
+ )} +
+ ); + } + + public onChange = (value: UnlockParams) => { + this.setState({ value }); + }; + + public onUnlock = (payload: any) => { + const { value, selectedWalletKey } = this.state; + if (!selectedWalletKey) { + return; + } + + // some components (TrezorDecrypt) don't take an onChange prop, and thus + // this.state.value will remain unpopulated. in this case, we can expect + // the payload to contain the unlocked wallet info. + const unlockValue = value && !isEmpty(value) ? value : payload; + this.WALLETS[selectedWalletKey].unlock(unlockValue); + this.props.resetTransactionState(); + }; + + private isWalletDisabled = (walletKey: string) => { + if (!this.props.disabledWallets) { + return false; + } + return this.props.disabledWallets.indexOf(walletKey) !== -1; + }; +} + +function mapStateToProps(state: AppState) { + return { + offline: state.config.offline, + wallet: state.wallet.inst + }; +} + +export default connect(mapStateToProps, { + unlockKeystore, + unlockMnemonic, + unlockPrivateKey, + unlockWeb3, + setWallet, + resetWallet, + resetTransactionState: reset +})(WalletDecrypt); diff --git a/common/components/WalletDecrypt/DeterministicWalletsModal.scss b/common/components/WalletDecrypt/components/DeterministicWalletsModal.scss similarity index 100% rename from common/components/WalletDecrypt/DeterministicWalletsModal.scss rename to common/components/WalletDecrypt/components/DeterministicWalletsModal.scss diff --git a/common/components/WalletDecrypt/DeterministicWalletsModal.tsx b/common/components/WalletDecrypt/components/DeterministicWalletsModal.tsx similarity index 97% rename from common/components/WalletDecrypt/DeterministicWalletsModal.tsx rename to common/components/WalletDecrypt/components/DeterministicWalletsModal.tsx index 0c8b4b43..7e1c6ab0 100644 --- a/common/components/WalletDecrypt/DeterministicWalletsModal.tsx +++ b/common/components/WalletDecrypt/components/DeterministicWalletsModal.tsx @@ -52,7 +52,7 @@ interface State { page: number; } -class DeterministicWalletsModal extends React.Component { +class DeterministicWalletsModalClass extends React.Component { public state = { selectedAddress: '', selectedAddrIndex: 0, @@ -310,7 +310,7 @@ function mapStateToProps(state: AppState) { }; } -export default connect(mapStateToProps, { +export const DeterministicWalletsModal = connect(mapStateToProps, { getDeterministicWallets, setDesiredToken -})(DeterministicWalletsModal); +})(DeterministicWalletsModalClass); diff --git a/common/components/WalletDecrypt/components/DigitalBitbox.tsx b/common/components/WalletDecrypt/components/DigitalBitbox.tsx new file mode 100644 index 00000000..6b5dd911 --- /dev/null +++ b/common/components/WalletDecrypt/components/DigitalBitbox.tsx @@ -0,0 +1,7 @@ +import React from 'react'; + +export class DigitalBitboxDecrypt extends React.Component<{}, {}> { + public render() { + return Not yet implemented; + } +} diff --git a/common/components/WalletDecrypt/components/Keystore.tsx b/common/components/WalletDecrypt/components/Keystore.tsx new file mode 100644 index 00000000..98b95a2a --- /dev/null +++ b/common/components/WalletDecrypt/components/Keystore.tsx @@ -0,0 +1,106 @@ +import { isKeystorePassRequired } from 'libs/wallet'; +import React, { Component } from 'react'; +import translate, { translateRaw } from 'translations'; + +export interface KeystoreValue { + file: string; + password: string; + valid: boolean; +} + +function isPassRequired(file: string): boolean { + let passReq = false; + try { + passReq = isKeystorePassRequired(file); + } catch (e) { + // TODO: communicate invalid file to user + } + return passReq; +} + +export class KeystoreDecrypt extends Component { + public props: { + value: KeystoreValue; + onChange(value: KeystoreValue): void; + onUnlock(): void; + }; + + public render() { + const { file, password } = this.props.value; + const passReq = isPassRequired(file); + const unlockDisabled = !file || (passReq && !password); + + return ( +
+
+ + +
+

{translate('ADD_Label_3')}

+ 0 ? 'is-valid' : 'is-invalid'}`} + value={password} + onChange={this.onPasswordChange} + onKeyDown={this.onKeyDown} + placeholder={translateRaw('x_Password')} + type="password" + /> +
+
+ + +
+ ); + } + + private onKeyDown = (e: any) => { + if (e.keyCode === 13) { + this.unlock(e); + } + }; + + private unlock = (e: React.SyntheticEvent) => { + e.preventDefault(); + e.stopPropagation(); + this.props.onUnlock(); + }; + + private onPasswordChange = (e: any) => { + const valid = this.props.value.file.length && e.target.value.length; + this.props.onChange({ + ...this.props.value, + password: e.target.value, + valid + }); + }; + + private handleFileSelection = (e: any) => { + const fileReader = new FileReader(); + const target = e.target; + const inputFile = target.files[0]; + + fileReader.onload = () => { + const keystore = fileReader.result; + const passReq = isPassRequired(keystore); + + this.props.onChange({ + ...this.props.value, + file: keystore, + valid: keystore.length && !passReq + }); + }; + + fileReader.readAsText(inputFile, 'utf-8'); + }; +} diff --git a/common/components/WalletDecrypt/LedgerNano.scss b/common/components/WalletDecrypt/components/LedgerNano.scss similarity index 79% rename from common/components/WalletDecrypt/LedgerNano.scss rename to common/components/WalletDecrypt/components/LedgerNano.scss index b9b43d78..c18b2623 100644 --- a/common/components/WalletDecrypt/LedgerNano.scss +++ b/common/components/WalletDecrypt/components/LedgerNano.scss @@ -1,10 +1,5 @@ .LedgerDecrypt { text-align: center; - padding-top: 30px; - - &-decrypt { - width: 100%; - } &-help { margin-top: 10px; @@ -21,14 +16,15 @@ } &-buy { - margin-top: 10px; + margin: 10px 0; } &-message { display: flex; justify-content: center; align-items: center; - svg { + + .Spinner { margin-right: 16px; } } diff --git a/common/components/WalletDecrypt/LedgerNano.tsx b/common/components/WalletDecrypt/components/LedgerNano.tsx similarity index 95% rename from common/components/WalletDecrypt/LedgerNano.tsx rename to common/components/WalletDecrypt/components/LedgerNano.tsx index e44be37c..a372f38d 100644 --- a/common/components/WalletDecrypt/LedgerNano.tsx +++ b/common/components/WalletDecrypt/components/LedgerNano.tsx @@ -1,7 +1,7 @@ import './LedgerNano.scss'; import React, { Component } from 'react'; import translate, { translateRaw } from 'translations'; -import DeterministicWalletsModal from './DeterministicWalletsModal'; +import { DeterministicWalletsModal } from './DeterministicWalletsModal'; import { LedgerWallet } from 'libs/wallet'; import Ledger3 from 'vendor/ledger3'; import LedgerEth from 'vendor/ledger-eth'; @@ -23,7 +23,7 @@ interface State { showTip: boolean; } -export default class LedgerNanoSDecrypt extends Component { +export class LedgerNanoSDecrypt extends Component { public state: State = { publicKey: '', chainCode: '', @@ -44,7 +44,7 @@ export default class LedgerNanoSDecrypt extends Component { const showErr = error ? 'is-showing' : ''; return ( -
+
{showTip && (

Tip: Make sure you're logged into the ethereum app on your hardware @@ -52,7 +52,7 @@ export default class LedgerNanoSDecrypt extends Component {

)} + + {translate('Don’t have a Ledger? Order one now!')} + + +
{error || '-'}
+
Guides:
@@ -87,15 +98,7 @@ export default class LedgerNanoSDecrypt extends Component {
-
{error || '-'}
- - {translate('Don’t have a Ledger? Order one now!')} - + { onPathChange={this.handlePathChange} walletType={translateRaw('x_Ledger')} /> -
+ ); } diff --git a/common/components/WalletDecrypt/Mnemonic.tsx b/common/components/WalletDecrypt/components/Mnemonic.tsx similarity index 83% rename from common/components/WalletDecrypt/Mnemonic.tsx rename to common/components/WalletDecrypt/components/Mnemonic.tsx index 49ab4871..0a880807 100644 --- a/common/components/WalletDecrypt/Mnemonic.tsx +++ b/common/components/WalletDecrypt/components/Mnemonic.tsx @@ -2,7 +2,7 @@ import { mnemonicToSeed, validateMnemonic } from 'bip39'; import DPATHS from 'config/dpaths'; import React, { Component } from 'react'; import translate, { translateRaw } from 'translations'; -import DeterministicWalletsModal from './DeterministicWalletsModal'; +import { DeterministicWalletsModal } from './DeterministicWalletsModal'; import { formatMnemonic } from 'utils/formatters'; const DEFAULT_PATH = DPATHS.MNEMONIC[0].value; @@ -18,7 +18,7 @@ interface State { dPath: string; } -export default class MnemonicDecrypt extends Component { +export class MnemonicDecrypt extends Component { public state: State = { phrase: '', formattedPhrase: '', @@ -32,9 +32,8 @@ export default class MnemonicDecrypt extends Component { const isValidMnemonic = validateMnemonic(formattedPhrase); return ( -
+
-

{translate('ADD_Radio_5')}