-
Notifications
You must be signed in to change notification settings - Fork 73
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(auth): add the ability to import existing seed (#1118)
* feat(auth): add the ability to import existing seed * chore(auth): fix register shape
- Loading branch information
1 parent
3f4acdb
commit 6b698c8
Showing
14 changed files
with
367 additions
and
69 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
111 changes: 111 additions & 0 deletions
111
src/renderer/register/components/AccountView/ImportView/ImportView.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
import * as bip39 from 'bip39'; | ||
import { debounce } from 'lodash'; | ||
import React from 'react'; | ||
import { bool, func, arrayOf, string } from 'prop-types'; | ||
|
||
import AuthPanel from 'auth/components/AuthPanel'; | ||
import NavigationButtons from 'shared/components/NavigationButtons'; | ||
import { DEFAULT_LANGUAGE } from 'shared/values/languages'; | ||
import registerShape from 'register/shapes/registerShape'; | ||
|
||
import MnemonicWordInput from './MnemonicWordInput'; | ||
import styles from './ImportView.scss'; | ||
|
||
export default class ImportView extends React.PureComponent { | ||
static propTypes = { | ||
account: registerShape.isRequired, | ||
loading: bool, | ||
onCancel: func.isRequired, | ||
previousStep: func.isRequired, | ||
storeFormData: func.isRequired, | ||
showErrorToast: func.isRequired, | ||
mnemonic: arrayOf(string).isRequired, | ||
setMnemonic: func.isRequired | ||
}; | ||
|
||
static defaultProps = { | ||
loading: false | ||
}; | ||
|
||
state = { | ||
validMnemonic: false | ||
}; | ||
|
||
mnemonicRefs = Array(24) | ||
.fill(undefined) | ||
.map(() => React.createRef()); | ||
|
||
showErrorToast = debounce((e) => { | ||
this.props.showErrorToast(e); | ||
}, 1000); | ||
|
||
render() { | ||
const { onCancel, loading, mnemonic, previousStep } = this.props; | ||
|
||
return ( | ||
<AuthPanel | ||
sidePanel | ||
step="2" | ||
onCancel={onCancel} | ||
className={styles.importView} | ||
sidePanelText="Copy and paste your 24-word seed in the right order into the following form." | ||
> | ||
<form onSubmit={this.handleStoreMnemonic}> | ||
<div className={styles.mnemonic}> | ||
{mnemonic.map((word, count) => ( | ||
<MnemonicWordInput | ||
key={`mnemonic-input-word-${count + 1}`} | ||
count={count + 1} | ||
word={word} | ||
inputRef={this.mnemonicRefs[count]} | ||
onChange={this.setMnemonic(count)} | ||
/> | ||
))} | ||
</div> | ||
<NavigationButtons | ||
onBack={previousStep} | ||
nextBtnText="Next: verify" | ||
disabled={loading || !this.state.validMnemonic} | ||
isSubmit | ||
/> | ||
</form> | ||
</AuthPanel> | ||
); | ||
} | ||
|
||
handleStoreMnemonic = (e) => { | ||
e.preventDefault(); | ||
|
||
const { mnemonic: mnemonicArray, account, storeFormData } = this.props; | ||
const mnemonic = mnemonicArray.join(' '); | ||
|
||
storeFormData({ | ||
...account, | ||
mnemonic | ||
}); | ||
}; | ||
|
||
setMnemonic = (count) => (event) => { | ||
event.preventDefault(); | ||
const { mnemonic, setMnemonic } = this.props; | ||
|
||
let newMnemonic = event.target.value.trim().split(' '); | ||
if (newMnemonic.length !== 24) { | ||
newMnemonic = [...mnemonic]; | ||
newMnemonic[count] = event.target.value; | ||
} | ||
|
||
const validMnemonic = bip39.validateMnemonic( | ||
newMnemonic.join(' '), | ||
bip39.wordlists[DEFAULT_LANGUAGE] | ||
); | ||
|
||
this.setState({ validMnemonic }, () => { | ||
setMnemonic(newMnemonic); | ||
|
||
const hasEmptyWords = newMnemonic.filter((x) => x === ''); | ||
if (!this.state.validMnemonic && hasEmptyWords.length === 0) | ||
this.showErrorToast('Seed is invalid'); | ||
}); | ||
}; | ||
} |
20 changes: 20 additions & 0 deletions
20
src/renderer/register/components/AccountView/ImportView/ImportView.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
.importView { | ||
.mnemonic { | ||
display: flex; | ||
flex-direction: row; | ||
flex-wrap: wrap; | ||
justify-content: space-between; | ||
|
||
width: 520px; | ||
padding: 0 40px; | ||
} | ||
|
||
.button { | ||
margin: 10px 40px 0 40px; | ||
@extend %tertiaryButtonColor; | ||
|
||
svg { | ||
vertical-align: middle; | ||
} | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
...derer/register/components/AccountView/ImportView/MnemonicWordInput/MnemonicWordInput.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
.wordInput { | ||
margin: 0 !important; | ||
|
||
// TODO: The way Input's styles are ordered doesn't allow for simple overrides. Style overhaul. | ||
box-shadow: none !important; | ||
background: transparent !important; | ||
|
||
input { | ||
padding: 3px 12px; | ||
} | ||
} | ||
|
||
.focus { | ||
box-shadow: inset 0 0 0 2px $brand !important; | ||
|
||
.input { | ||
outline: none; | ||
} | ||
} |
51 changes: 51 additions & 0 deletions
51
src/renderer/register/components/AccountView/ImportView/MnemonicWordInput/index.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import classNames from 'classnames'; | ||
import { func, string, number } from 'prop-types'; | ||
import React from 'react'; | ||
|
||
import Input from 'shared/components/Forms/Input'; | ||
import refShape from 'shared/shapes/refShape'; | ||
|
||
import mnemonicWordStyles from '../../MnemonicView/MnemonicWord/MnemonicWord.scss'; | ||
import styles from './MnemonicWordInput.scss'; | ||
|
||
const handleInputFocus = (setFocus, value) => (event) => { | ||
event.preventDefault(); | ||
setFocus(value); | ||
}; | ||
|
||
const MnemonicWordInput = ({ count, word, inputRef, onChange }) => { | ||
const [focus, setFocus] = React.useState(false); | ||
const useFocus = handleInputFocus(setFocus, true); | ||
const useBlur = handleInputFocus(setFocus, false); | ||
|
||
return ( | ||
<div | ||
className={classNames(mnemonicWordStyles.mnemonicWord, { | ||
[styles.focus]: focus | ||
})} | ||
> | ||
<div className={mnemonicWordStyles.count}>{count}</div> | ||
<Input | ||
id={`mnemonic-word-${count}`} | ||
className={classNames(mnemonicWordStyles.word, styles.wordInput)} | ||
type="text" | ||
value={word} | ||
ref={inputRef} | ||
onFocus={useFocus} | ||
onBlur={useBlur} | ||
onChange={onChange} | ||
/> | ||
</div> | ||
); | ||
}; | ||
|
||
MnemonicWordInput.propTypes = { | ||
count: number.isRequired, | ||
word: string.isRequired, | ||
inputRef: refShape.isRequired, | ||
onChange: func.isRequired | ||
}; | ||
|
||
MnemonicWordInput.defaultProps = {}; | ||
|
||
export default MnemonicWordInput; |
33 changes: 33 additions & 0 deletions
33
src/renderer/register/components/AccountView/ImportView/index.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { compose, withState } from 'recompose'; | ||
import { withActions, progressValues } from 'spunky'; | ||
|
||
import withLoadingProp from 'shared/hocs/withLoadingProp'; | ||
import { withErrorToast } from 'shared/hocs/withToast'; | ||
import withProgressChange from 'shared/hocs/withProgressChange'; | ||
import pureStrategy from 'shared/hocs/strategies/pureStrategy'; | ||
import registerFormActions from 'register/actions/registerFormActions'; | ||
|
||
import ImportView from './ImportView'; | ||
|
||
const { FAILED, LOADED } = progressValues; | ||
|
||
const emptyMnemonicWordArray = Array(24).fill(''); | ||
|
||
const mapRegisterActionsToProps = (actions) => ({ | ||
storeFormData: (data) => actions.call(data) | ||
}); | ||
|
||
export default compose( | ||
withActions(registerFormActions, mapRegisterActionsToProps), | ||
withLoadingProp(registerFormActions, { strategy: pureStrategy }), | ||
|
||
withState('mnemonic', 'setMnemonic', ({ mnemonic }) => mnemonic || emptyMnemonicWordArray), | ||
|
||
withErrorToast(), | ||
withProgressChange(registerFormActions, FAILED, (state, props) => { | ||
props.showErrorToast(state.error); | ||
}), | ||
withProgressChange(registerFormActions, LOADED, (state, props) => { | ||
props.nextStep(); | ||
}) | ||
)(ImportView); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.