Skip to content

Commit

Permalink
feat(management): added ability to import accounts from pevious versi…
Browse files Browse the repository at this point in the history
…on (#1186)

* feat(management): added ability to import accounts from pevious version

* chore(wallet): rename

* chore(app): fix import

* chore(app): cleanup

* chore(app): small fixes

* chore(management): pr comments + prop validation fix

* chore(app): change hover color
  • Loading branch information
Maurice Dalderup authored Oct 13, 2019
1 parent c862796 commit c3a6eb7
Show file tree
Hide file tree
Showing 18 changed files with 306 additions and 61 deletions.
1 change: 0 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
"linebreak-style": "off",
"react/jsx-fragments": "off",
"arrow-body-style": ["off"],
"arrow-parens": ["error", "always"],
"comma-dangle": ["error", "never"],
"no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
"no-else-return": ["off"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
cursor: pointer;
user-select: none;
color: $brand;

&:hover {
color: $brand-dark;
}
}
}

Expand Down
8 changes: 4 additions & 4 deletions src/renderer/account/components/Management/Management.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,6 @@ export default class Management extends React.PureComponent {
<div className={styles.heading}>
<div className={styles.title}>My Account</div>
<div className={styles.subHeader}>
<div className={styles.link} role="button" tabIndex={0} onClick={this.handleAddAccount}>
<AddIcon className={styles.icon} />
New Wallet
</div>
<div
className={styles.link}
role="button"
Expand All @@ -63,6 +59,10 @@ export default class Management extends React.PureComponent {
<ImportIcon className={styles.icon} />
Import Wallet
</div>
<div className={styles.link} role="button" tabIndex={0} onClick={this.handleAddAccount}>
<AddIcon className={styles.icon} />
New Wallet
</div>
</div>
</div>
{!account.isHardware && (
Expand Down
9 changes: 7 additions & 2 deletions src/renderer/account/components/Management/Management.scss
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,14 @@
user-select: none;
color: $brand;
display: flex;
margin-right: 15px;

&:first-child {
margin-right: 15px;
&:last-child {
margin-right: 0px;
}

&:hover {
color: $brand-dark;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
.iconTab {
display: flex;
flex-direction: row;
// align-items: flex-start;
justify-content: flex-start;

.icon {
Expand Down
10 changes: 10 additions & 0 deletions src/renderer/auth/actions/v5AccountsActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { createActions } from 'spunky';

import { getStorage } from 'shared/lib/storage';

export const ID = 'profiles_v1';

// Getters
export default createActions(ID, () => async () => {
return getStorage(ID);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import React from 'react';
import { string, func, shape, arrayOf } from 'prop-types';
import { map, isEmpty } from 'lodash';
import { wallet } from '@cityofzion/neon-js';

import LabeledInput from 'shared/components/Forms/LabeledInput';
import LabeledSelect from 'shared/components/Forms/LabeledSelect';
import { NEO } from 'shared/values/coins';
import accountShape from 'auth/shapes/accountShape';

const legacyAccountShape = shape({
walletName: string.isRequired,
encryptedKey: string.isRequired
});

export default class ExistingImport extends React.PureComponent {
static propTypes = {
className: string,
account: accountShape.isRequired,
passphrase: string.isRequired,
setPassphrase: func.isRequired,
addAccount: func.isRequired,
accounts: arrayOf(legacyAccountShape).isRequired,
setCurrentAccount: func.isRequired,
currentAccount: string.isRequired,
showErrorToast: func.isRequired
};

static defaultProps = {
className: null
};

render() {
const { className, accounts, passphrase, currentAccount } = this.props;

return (
<form className={className} onSubmit={this.submit} id="walletForm">
<LabeledSelect
id="profiel"
label="Account"
disabled={isEmpty(accounts)}
value={currentAccount}
items={this.getProfiles()}
onChange={this.handleChangeCurrentAccount}
/>

<LabeledInput
id="passphrase"
type="password"
label="Legacy Passphrase"
placeholder="Enter your legacy passphrase"
value={passphrase}
onChange={this.handleChangePassphrase}
/>
</form>
);
}

handleChangePassphrase = (event) => {
this.props.setPassphrase(event.target.value);
};

handleChangeCurrentAccount = (value) => {
this.props.setCurrentAccount(value);
};

submit = async () => {
const {
account,
passphrase,
accounts,
setPassphrase,
addAccount,
showErrorToast,
currentAccount
} = this.props;

const walletLabel = accounts.filter((acc) => acc.encryptedKey === currentAccount)[0].walletName;

try {
const wif = await wallet.decryptAsync(currentAccount, passphrase);
const { privateKey } = new wallet.Account(wif);

const options = {
walletLabel,
coinType: NEO,
isHardware: account.isHardware,
isImport: true,
privateKey
};

addAccount({ account, passphrase, options });
setPassphrase('');
} catch (e) {
showErrorToast(`Wrong passphrase for account ${walletLabel}`);
}
};

getProfiles = () => {
const { accounts } = this.props;

if (isEmpty(accounts)) {
return [{ label: 'No accounts Found', value: '' }];
}

return map(accounts, ({ walletName, encryptedKey }) => ({
label: walletName,
value: encryptedKey
}));
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { compose, withState } from 'recompose';
import { progressValues, withActions } from 'spunky';

import { withErrorToast } from 'shared/hocs/withToast';
import withProgressChange from 'shared/hocs/withProgressChange';
import { importWalletActions } from 'auth/actions/walletActions';

import ExistingImport from './ExistingImport';

const { FAILED, LOADED } = progressValues;

const mapAddAccountActionsToProps = (actions) => ({
addAccount: (data) => actions.call(data)
});

export default compose(
withActions(importWalletActions, mapAddAccountActionsToProps),

withState('currentAccount', 'setCurrentAccount', ({ accounts }) => {
return accounts[0] && accounts[0].encryptedKey;
}),
withState('passphrase', 'setPassphrase', ''),

withErrorToast(),
withProgressChange(importWalletActions, FAILED, (state, props) => {
props.showErrorToast(state.error);
}),
withProgressChange(importWalletActions, LOADED, (state, props) => {
props.onConfirm();
})
)(ExistingImport);
79 changes: 79 additions & 0 deletions src/renderer/shared/components/NewWallet/Import/Import/Import.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React from 'react';
import { string, func, bool, arrayOf } from 'prop-types';
import classNames from 'classnames';

import Button from 'shared/components/Forms/Button';
import PrimaryButton from 'shared/components/Forms/PrimaryButton';
import Pill from 'shared/components/Pill';
import accountShape from 'auth/shapes/accountShape';

import ExistingImport from '../ExistingImport';
import NewImport from '../NewImport';

import styles from './Import.scss';

export default class Import extends React.PureComponent {
static propTypes = {
className: string,
account: accountShape.isRequired,
accounts: arrayOf(accountShape),
onCancel: func.isRequired,
onConfirm: func.isRequired,
newImport: bool.isRequired,
setNewImport: func.isRequired
};

static defaultProps = {
className: null,
accounts: []
};

render() {
const { className, account, accounts, newImport } = this.props;
const { secretWord } = account;

return (
<div className={classNames(className, styles.mnemonic)}>
<Pill>{secretWord}</Pill>
{this.renderForm()}
{this.renderImportView({ accounts, newImport })}
{this.renderActions()}
</div>
);
}

renderForm = () => {
const { newImport, accounts, account, onConfirm } = this.props;

return newImport ? (
<NewImport account={account} onConfirm={onConfirm} />
) : (
<ExistingImport account={account} accounts={accounts} onConfirm={onConfirm} />
);
};

renderImportView = ({ accounts, newImport }) => (
<React.Fragment>
{accounts && (
<div className={styles.toggle} role="button" tabIndex={0} onClick={this.handleToggle}>
{newImport ? 'Import Legacy Wallet' : 'Import New Wallet'}
</div>
)}
</React.Fragment>
);

renderActions = () => (
<div className={styles.actions}>
<Button className={styles.action} onClick={this.props.onCancel}>
Cancel
</Button>
<PrimaryButton className={styles.action} type="submit" form="walletForm">
Import Wallet
</PrimaryButton>
</div>
);

handleToggle = () => {
this.props.setNewImport(!this.props.newImport);
};
}
27 changes: 27 additions & 0 deletions src/renderer/shared/components/NewWallet/Import/Import/Import.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
.mnemonic {
.actions {
flex: 0 0 auto;
margin: 1.5rem 0 0;

.action {
margin-right: 10px;

&:last-child {
margin-right: 0;
}
}
}

.toggle {
text-align: end;
margin-top: -15px;
font-size: 12px;
color: $brand;
cursor: pointer;
user-select: none;

&:hover {
color: $brand-dark;
}
}
}
18 changes: 18 additions & 0 deletions src/renderer/shared/components/NewWallet/Import/Import/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { compose, withState } from 'recompose';
import { withData } from 'spunky';

import withInitialCall from 'shared/hocs/withInitialCall';
import accountActions from 'auth/actions/v5AccountsActions';

import Import from './Import';

const mapAccountActionsDataToProps = (accounts) => ({
accounts: accounts && accounts.profiles
});

export default compose(
withInitialCall(accountActions),
withData(accountActions, mapAccountActionsDataToProps),

withState('newImport', 'setNewImport', true)
)(Import);
Loading

0 comments on commit c3a6eb7

Please sign in to comment.