Skip to content
This repository has been archived by the owner on Apr 15, 2019. It is now read-only.

Commit

Permalink
Implement multiple account management
Browse files Browse the repository at this point in the history
  • Loading branch information
slaweet committed Nov 8, 2017
1 parent 4093148 commit c438b92
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 50 deletions.
9 changes: 6 additions & 3 deletions i18n/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"About": "About",
"Account saved": "Account saved",
"Account was successfully forgotten.": "Account was successfully forgotten.",
"Add active account": "Add active account",
"Add vote to": "Add vote to",
"Address": "Address",
"Address copied to clipboard": "Address copied to clipboard",
Expand All @@ -19,6 +20,7 @@
"Blockchain Application Registration": "Blockchain Application Registration",
"Cancel": "Cancel",
"Click to send all funds": "Click to send all funds",
"Close": "Close",
"Confirm": "Confirm",
"Connection re-established": "Connection re-established",
"Copy": "Copy",
Expand All @@ -42,7 +44,7 @@
"Fee": "Fee",
"Fee: {{amount}} LSK": "Fee: {{amount}} LSK",
"Fee: {{fee}} LSK": "Fee: {{fee}} LSK",
"Forget this account": "Forget this account",
"Forget": "Forget",
"Forging": "Forging",
"From / To": "From / To",
"Help": "Help",
Expand Down Expand Up @@ -73,6 +75,7 @@
"Move your mouse to generate random bytes": "Move your mouse to generate random bytes",
"Multisignature Creation": "Multisignature Creation",
"Name": "Name",
"Network": "Network",
"New Account": "New Account",
"Next": "Next",
"No delegates found": "No delegates found",
Expand Down Expand Up @@ -112,16 +115,15 @@
"Register as delegate": "Register as delegate",
"Register second passphrase": "Register second passphrase",
"Reload": "Reload",
"Remember this account": "Remember this account",
"Remove vote from": "Remove vote from",
"Repeat the transaction": "Repeat the transaction",
"Report Issue...": "Report Issue...",
"Required": "Required",
"Result": "Result",
"Result copied to clipboard": "Result copied to clipboard",
"Reward": "Reward",
"Save account": "Save account",
"Save your passphrase in a safe place": "Save your passphrase in a safe place",
"Saved accounts": "Saved accounts",
"Search": "Search",
"Search by username": "Search by username",
"Second Passphrase": "Second Passphrase",
Expand All @@ -143,6 +145,7 @@
"Signing a message with this tool indicates ownership of a privateKey (secret) and provides a level of proof that you are the owner of the key. Its important to bear in mind that this is not a 100% proof as computer systems can be compromised, but is still an effective tool for proving ownership of a particular publicKey/address pair.": "Signing a message with this tool indicates ownership of a privateKey (secret) and provides a level of proof that you are the owner of the key. Its important to bear in mind that this is not a 100% proof as computer systems can be compromised, but is still an effective tool for proving ownership of a particular publicKey/address pair.",
"Submit": "Submit",
"Success": "Success",
"Switch": "Switch",
"Testnet": "Testnet",
"The URL was invalid": "The URL was invalid",
"There are no transactions, yet.": "There are no transactions, yet.",
Expand Down
11 changes: 6 additions & 5 deletions src/actions/savedAccounts.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import actionTypes from '../constants/actions';
import { getSavedAccount, setSavedAccount, removeSavedAccount } from '../utils/saveAccount';
import { getSavedAccounts, setSavedAccount, removeSavedAccount, setLastActiveAccount } from '../utils/saveAccount';

/**
* An action to dispatch accountSaved
*
*/
export const accountSaved = (data) => {
setSavedAccount(data);
setLastActiveAccount(data);
return {
data,
type: actionTypes.accountSaved,
Expand All @@ -16,10 +17,10 @@ export const accountSaved = (data) => {
/**
* An action to dispatch accountRemoved
*/
export const accountRemoved = (publicKey) => {
removeSavedAccount(publicKey);
export const accountRemoved = (account) => {
removeSavedAccount(account);
return {
data: publicKey,
data: account,
type: actionTypes.accountRemoved,
};
};
Expand All @@ -43,7 +44,7 @@ export const accountSwitched = (account) => {
*
*/
export const accountsRetrieved = () => {
const accounts = getSavedAccount();
const accounts = getSavedAccounts();
const data = accounts !== undefined ? accounts : [];
return {
data,
Expand Down
2 changes: 1 addition & 1 deletion src/actions/savedAccounts.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ describe('actions: savedAccount', () => {

describe('accountsRetrieved', () => {
it('should create an action to retrieved the saved accounts list', () => {
sinon.stub(saveAccountUtils, 'getSavedAccount').returns([data]);
sinon.stub(saveAccountUtils, 'getSavedAccounts').returns([data]);
const expectedAction = {
data: [data],
type: actionTypes.accountsRetrieved,
Expand Down
8 changes: 5 additions & 3 deletions src/components/saveAccount/index.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { connect } from 'react-redux';
import { translate } from 'react-i18next';
import { accountSaved } from '../../actions/savedAccounts';
import { accountSaved, accountRemoved, accountSwitched } from '../../actions/savedAccounts';
import SaveAccount from './saveAccount';

const mapStateToProps = state => ({
address: state.peers.data.options.address,
publicKey: state.account.publicKey,
network: state.peers.data.options.name,
networkOptions: state.peers.options,
savedAccounts: state.savedAccounts,
});

const mapDispatchToProps = dispatch => ({
accountSaved: data => dispatch(accountSaved(data)),
accountRemoved: data => dispatch(accountRemoved(data)),
accountSwitched: data => dispatch(accountSwitched(data)),
});

export default connect(
Expand Down
14 changes: 7 additions & 7 deletions src/components/saveAccount/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,16 @@ describe('SaveAccountHOC', () => {
publicKey: 'sample_key',
username: 'lisk-nano',
};
const peers = { data: {
options: {
address: 'http://localhost:4000',
network: 'Custom node',
},
} };
const options = {
address: 'http://localhost:4000',
network: 'Custom node',
};
const peers = { data: { options }, options };
const store = configureMockStore([])({
peers,
account,
activePeerSet: () => {},
savedAccounts: [],
});

beforeEach(() => {
Expand All @@ -45,7 +45,7 @@ describe('SaveAccountHOC', () => {

it('should bind accountSaved action to SaveAccount props.accountSaved', () => {
const actionsSpy = sinon.spy(savedAccounts, 'accountSaved');
wrapper.find('SaveAccount .save-account-button button').simulate('click');
wrapper.find('SaveAccount button.add-active-account-button').simulate('click');
expect(actionsSpy).to.be.calledWith();
actionsSpy.restore();
});
Expand Down
73 changes: 58 additions & 15 deletions src/components/saveAccount/saveAccount.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,84 @@
import { Table, TableHead, TableRow, TableCell } from 'react-toolbox/lib/table';
import React from 'react';
import InfoParagraph from '../infoParagraph';
import { IconButton } from 'react-toolbox/lib/button';
import ActionBar from '../actionBar';
import InfoParagraph from '../infoParagraph';
import networks from '../../constants/networks';
import getNetwork from '../../utils/getNetwork';
import { extractAddress } from '../../utils/api/account';

const SaveAccount = ({
network,
address,
networkOptions,
publicKey,
closeDialog,
accountSaved,
accountRemoved,
accountSwitched,
savedAccounts,
t,
}) => {
const save = () => {
// eslint-disable-next-line arrow-body-style
const index = Object.keys(networks).map((item, i) => {
return (networks[item].name === network) ? i : null;
}).find(item => item !== null);
accountSaved({
network: index,
address,
network: networkOptions.code,
address: networkOptions.address,
publicKey,
});
closeDialog();
};

const isActive = account => (
account.publicKey === publicKey &&
account.network === networkOptions.code);

return (
<div className='save-account'>
<InfoParagraph>
{t('This will save public key of your account on this device, so next time it will launch without the need to log in. However, you will be prompted to enter the passphrase once you want to do any transaction.')}
</InfoParagraph>
{ savedAccounts.length === 0 ?
<InfoParagraph>
{t('This will save public key of your account on this device, so next time it will launch without the need to log in. However, you will be prompted to enter the passphrase once you want to do any transaction.')}
</InfoParagraph> :
<div style={{ margin: '-24px -24px 24px -24px' }} >
<Table selectable={false}>
<TableHead>
<TableCell style={{ width: 20 }} >{t('Switch')}</TableCell>
<TableCell>{t('Address')}</TableCell>
<TableCell>{t('Network')}</TableCell>
<TableCell style={{ 'text-align': 'right' }}>{t('Forget')}</TableCell>
</TableHead>
{savedAccounts.map(account => (
<TableRow key={account.publicKey + account.network}
style={{ 'font-weight': (isActive(account) ? 'bold' : 'normal') }}>
<TableCell style={{ width: 20 }} >
<IconButton icon='exit_to_app'
disabled={isActive(account)}
className='switch-button'
onClick={accountSwitched.bind(this, account)} />
</TableCell>
<TableCell>
{extractAddress(account.publicKey)}
</TableCell>
<TableCell>
{account.network === networks.customNode.code ?
account.address :
t((getNetwork(account.network) || {}).name)}
</TableCell>
<TableCell style={{ 'text-align': 'right' }}>
<IconButton icon='clear' className='forget-button'
onClick={accountRemoved.bind(this, account)} />
</TableCell>
</TableRow>
))}
</Table>
</div>
}
<ActionBar
secondaryButton={{
onClick: closeDialog,
label: t('Close'),
className: 'close-button',
}}
primaryButton={{
label: t('Save account'),
className: 'save-account-button',
label: t('Add active account'),
className: 'add-active-account-button',
disabled: savedAccounts.filter(isActive).length !== 0,
onClick: save.bind(this),
}} />
</div>
Expand Down
10 changes: 7 additions & 3 deletions src/components/saveAccount/saveAccount.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ describe('SaveAccount', () => {
},
closeDialog: () => {},
accountSaved: () => {},
accountRemoved: () => {},
accountSwitched: () => {},
networkOptions: {},
publicKey: [],
savedAccounts: [],
t: key => key,
};

Expand Down Expand Up @@ -49,10 +54,9 @@ describe('SaveAccount', () => {
expect(wrapper.find('ActionBar')).to.have.lengthOf(1);
});

it('should call props.closeDialog and props.accountSaved on "save button" click', () => {
wrapper.find('button.save-account-button').simulate('click');
it('should call props.accountSaved on "save button" click', () => {
wrapper.find('button.add-active-account-button').simulate('click');
const componentProps = wrapper.find(SaveAccount).props();
expect(componentProps.closeDialog).to.have.been.calledWith();
expect(componentProps.accountSaved).to.have.been.calledWith();
});
});
Expand Down
43 changes: 30 additions & 13 deletions src/utils/saveAccount.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,41 @@
export const getSavedAccount = () => {
export const getSavedAccounts = () => {
const savedAccounts = localStorage.getItem('accounts');
let account;
let accounts = [];
if (savedAccounts) {
account = JSON.parse(savedAccounts);
accounts = JSON.parse(savedAccounts);
}

return account;
return accounts;
};

export const getLastActiveAccount = () => (
getSavedAccounts()[localStorage.getItem('lastActiveAccountIndex') || 0]
);

export const setLastActiveAccount = ({ publicKey, network, address }) => {
const lastActiveAccountIndex = getSavedAccounts().findIndex(account => (
account.publicKey === publicKey &&
account.network === network &&
account.address === address
));
if (lastActiveAccountIndex > -1) {
localStorage.setItem('lastActiveAccountIndex', lastActiveAccountIndex);
}
};

export const setSavedAccount = ({ publicKey, network, address }) => {
localStorage.setItem('accounts', JSON.stringify([{
publicKey,
network,
address,
}]));
localStorage.setItem('accounts', JSON.stringify([
...getSavedAccounts(),
{
publicKey,
network,
address,
},
]));
};

export const removeSavedAccount = (publicKey) => {
let accounts = localStorage.getItem('accounts');
accounts = JSON.parse(accounts);
accounts = accounts.filter(account => account.publicKey !== publicKey);
export const removeSavedAccount = ({ network, publicKey }) => {
const accounts = getSavedAccounts().filter(account =>
!(account.publicKey === publicKey && account.network === network));
localStorage.setItem('accounts', JSON.stringify(accounts));
};

0 comments on commit c438b92

Please sign in to comment.