-
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
- Loading branch information
1 parent
a70c856
commit 62db7bb
Showing
15 changed files
with
398 additions
and
68 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
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 MnemonicWord from './MnemonicWord'; | ||
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); | ||
}, 500); | ||
|
||
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) => ( | ||
<MnemonicWord | ||
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) => async (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; | ||
} | ||
} | ||
} |
54 changes: 54 additions & 0 deletions
54
src/renderer/register/components/AccountView/ImportView/MnemonicWord/MnemonicWord.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,54 @@ | ||
.mnemonicWord { | ||
@extend %innerPanelColor; | ||
@extend %textColor; | ||
border-radius: 4px; | ||
margin: 10px 6px; | ||
width: 22%; | ||
|
||
display: flex; | ||
align-items: center; /* Vertical center alignment */ | ||
|
||
.count { | ||
background: $gray-1; | ||
color: $gray-4; | ||
|
||
@include theme(DARK) { | ||
background: opacify($gray-7, 0.5); | ||
color: $gray-4; | ||
} | ||
|
||
width: 18px; | ||
height: 18px; | ||
|
||
display: flex; | ||
justify-content: center; | ||
align-items: center; | ||
|
||
border-radius: 2px; | ||
font-size: 10px; | ||
text-align: center; | ||
margin: 2px; | ||
} | ||
|
||
.word { | ||
margin: 0; | ||
font-weight: 700; | ||
font-size: 14px; | ||
|
||
// 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; | ||
} | ||
} | ||
} |
50 changes: 50 additions & 0 deletions
50
src/renderer/register/components/AccountView/ImportView/MnemonicWord/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,50 @@ | ||
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 styles from './MnemonicWord.scss'; | ||
|
||
const handleInputFocus = (setFocus, value) => (event) => { | ||
event.preventDefault(); | ||
setFocus(value); | ||
}; | ||
|
||
const MnemonicWord = ({ count, word, inputRef, onChange }) => { | ||
const [focus, setFocus] = React.useState(false); | ||
const useFocus = handleInputFocus(setFocus, true); | ||
const useBlur = handleInputFocus(setFocus, false); | ||
|
||
return ( | ||
<div | ||
className={classNames(styles.mnemonicWord, { | ||
[styles.focus]: focus | ||
})} | ||
> | ||
<div className={styles.count}>{count}</div> | ||
<Input | ||
id={`mnemonic-word-${count}`} | ||
className={styles.word} | ||
type="text" | ||
value={word} | ||
ref={inputRef} | ||
onFocus={useFocus} | ||
onBlur={useBlur} | ||
onChange={onChange} | ||
/> | ||
</div> | ||
); | ||
}; | ||
|
||
MnemonicWord.propTypes = { | ||
count: number.isRequired, | ||
word: string.isRequired, | ||
inputRef: refShape.isRequired, | ||
onChange: func.isRequired | ||
}; | ||
|
||
MnemonicWord.defaultProps = {}; | ||
|
||
export default MnemonicWord; |
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.