Enhance dialog layouts (round 1) (#4637)
* Added SelectionList component for selections * Use SelectionList in DappPermisions * AddDapps uses SelectionList * Fix AccountCard to consistent height * Display type icons in creation dialog * Complimentary colours * Convert Signer defaults to SelectionList * Fix Geth import - actually pass addresses through * Work from addresses returned via RPC * Display actual addresses imported (not selected) * Update tests to cover bug fixed * Prettyfy Geth import * Description on selection actions * SelectionList as entry point * Update failing tests * Subtle selection border * Styling updates for account details * Add ModalBox summary * AddAddress updated * Convert VaultAccounts to SelectionList * Add tests for SectionList component * Add tests for ModalBox component * Re-apply stretch fix * Apply scroll fixes from lates commit in #4621 * Clear name on switch (test in #4652, not pulling in) * Remove refs (cleanup)
This commit is contained in:
parent
570e6f32b0
commit
609e24ef04
@ -18,8 +18,8 @@ import { observer } from 'mobx-react';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Button, Form, Input, InputAddress, Portal } from '~/ui';
|
||||
import { AddIcon, CancelIcon } from '~/ui/Icons';
|
||||
import { Button, Form, Input, InputAddress, ModalBox, Portal } from '~/ui';
|
||||
import { AddIcon, AddressesIcon, CancelIcon } from '~/ui/Icons';
|
||||
|
||||
import Store from './store';
|
||||
|
||||
@ -76,7 +76,6 @@ export default class AddAddress extends Component {
|
||||
/>
|
||||
}
|
||||
onClick={ this.onClose }
|
||||
ref='closeButton'
|
||||
/>,
|
||||
<Button
|
||||
disabled={ hasError }
|
||||
@ -89,7 +88,6 @@ export default class AddAddress extends Component {
|
||||
/>
|
||||
}
|
||||
onClick={ this.onAdd }
|
||||
ref='addButton'
|
||||
/>
|
||||
];
|
||||
}
|
||||
@ -98,6 +96,15 @@ export default class AddAddress extends Component {
|
||||
const { address, addressError, description, name, nameError } = this.store;
|
||||
|
||||
return (
|
||||
<ModalBox
|
||||
icon={ <AddressesIcon /> }
|
||||
summary={
|
||||
<FormattedMessage
|
||||
id='addAddress.header'
|
||||
defaultMessage='To add a new entry to your addressbook, you need the network address of the account and can supply an optional description. Once added it will reflect in your address book.'
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Form>
|
||||
<InputAddress
|
||||
allowCopy={ false }
|
||||
@ -117,7 +124,6 @@ export default class AddAddress extends Component {
|
||||
/>
|
||||
}
|
||||
onChange={ this.onEditAddress }
|
||||
ref='inputAddress'
|
||||
value={ address }
|
||||
/>
|
||||
<Input
|
||||
@ -135,7 +141,6 @@ export default class AddAddress extends Component {
|
||||
/>
|
||||
}
|
||||
onChange={ this.onEditName }
|
||||
ref='inputName'
|
||||
value={ name }
|
||||
/>
|
||||
<Input
|
||||
@ -152,10 +157,10 @@ export default class AddAddress extends Component {
|
||||
/>
|
||||
}
|
||||
onChange={ this.onEditDescription }
|
||||
ref='inputDescription'
|
||||
value={ description }
|
||||
/>
|
||||
</Form>
|
||||
</ModalBox>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@ import { observer } from 'mobx-react';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Form, Input, InputAddress, QrCode } from '~/ui';
|
||||
import { IdentityIcon, Input, QrCode, Title } from '~/ui';
|
||||
|
||||
import styles from '../createAccount.css';
|
||||
|
||||
@ -29,36 +29,28 @@ export default class AccountDetails extends Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { address, name } = this.props.store;
|
||||
const { address, description, name } = this.props.store;
|
||||
|
||||
return (
|
||||
<div className={ styles.details }>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='createAccount.accountDetails.intro'
|
||||
defaultMessage='Your account has been created with the following details:'
|
||||
<div className={ styles.info }>
|
||||
<div className={ styles.account }>
|
||||
<div className={ styles.name }>
|
||||
<IdentityIcon
|
||||
address={ address }
|
||||
className={ styles.icon }
|
||||
center
|
||||
/>
|
||||
</p>
|
||||
<Form className={ styles.infoForm }>
|
||||
<Title
|
||||
byline={ description }
|
||||
className={ styles.title }
|
||||
title={ name }
|
||||
/>
|
||||
</div>
|
||||
<div className={ styles.description }>
|
||||
<Input
|
||||
allowCopy
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='createAccount.accountDetails.name.hint'
|
||||
defaultMessage='a descriptive name for the account'
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='createAccount.accountDetails.name.label'
|
||||
defaultMessage='account name'
|
||||
/>
|
||||
}
|
||||
readOnly
|
||||
value={ name }
|
||||
/>
|
||||
<InputAddress
|
||||
disabled
|
||||
hideUnderline
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='createAccount.accountDetails.address.hint'
|
||||
@ -72,14 +64,17 @@ export default class AccountDetails extends Component {
|
||||
/>
|
||||
}
|
||||
value={ address }
|
||||
allowCopy={ address }
|
||||
/>
|
||||
{ this.renderPhrase() }
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
<QrCode
|
||||
className={ styles.qr }
|
||||
value={ address }
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,23 +0,0 @@
|
||||
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
/* This file is part of Parity.
|
||||
/*
|
||||
/* Parity is free software: you can redistribute it and/or modify
|
||||
/* it under the terms of the GNU General Public License as published by
|
||||
/* the Free Software Foundation, either version 3 of the License, or
|
||||
/* (at your option) any later version.
|
||||
/*
|
||||
/* Parity is distributed in the hope that it will be useful,
|
||||
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
/* GNU General Public License for more details.
|
||||
/*
|
||||
/* You should have received a copy of the GNU General Public License
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.address {
|
||||
color: #999;
|
||||
line-height: 1.618em;
|
||||
padding-left: 2em;
|
||||
padding-top: 1em;
|
||||
}
|
@ -18,7 +18,10 @@ import { observer } from 'mobx-react';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import styles from './accountDetailsGeth.css';
|
||||
import { SectionList } from '~/ui';
|
||||
import GethCard from '../GethCard';
|
||||
|
||||
import styles from '../createAccount.css';
|
||||
|
||||
@observer
|
||||
export default class AccountDetailsGeth extends Component {
|
||||
@ -27,33 +30,37 @@ export default class AccountDetailsGeth extends Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { gethImported } = this.props.store;
|
||||
const { gethAccountsAvailable, gethImported } = this.props.store;
|
||||
|
||||
const accounts = gethAccountsAvailable.filter((account) => gethImported.includes(account.address));
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<div className={ styles.summary }>
|
||||
<FormattedMessage
|
||||
id='createAccount.accountDetailsGeth.imported'
|
||||
defaultMessage='You have imported {number} addresses from the Geth keystore:'
|
||||
defaultMessage='You have completed the import of {number} addresses from the Geth keystore. These will now be available in your accounts list as a normal account, along with their associated balances on the network.'
|
||||
values={ {
|
||||
number: gethImported.length
|
||||
} }
|
||||
/>
|
||||
</div>
|
||||
<div className={ styles.address }>
|
||||
{ this.formatAddresses(gethImported) }
|
||||
</div>
|
||||
<SectionList
|
||||
items={ accounts }
|
||||
noStretch
|
||||
renderItem={ this.renderAccount }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
formatAddresses (addresses) {
|
||||
return addresses.map((address, index) => {
|
||||
const comma = !index
|
||||
? ''
|
||||
: ((index === addresses.length - 1) ? ' & ' : ', ');
|
||||
|
||||
return `${comma}${address}`;
|
||||
}).join('');
|
||||
renderAccount = (account, index) => {
|
||||
return (
|
||||
<GethCard
|
||||
address={ account.address }
|
||||
balance={ account.balance }
|
||||
name={ `Geth Import ${index + 1}` }
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -39,22 +39,4 @@ describe('modals/CreateAccount/AccountDetailsGeth', () => {
|
||||
it('renders with defaults', () => {
|
||||
expect(render()).to.be.ok;
|
||||
});
|
||||
|
||||
describe('utility', () => {
|
||||
describe('formatAddresses', () => {
|
||||
let instance;
|
||||
|
||||
beforeEach(() => {
|
||||
instance = component.instance();
|
||||
});
|
||||
|
||||
it('renders a single item', () => {
|
||||
expect(instance.formatAddresses(['one'])).to.equal('one');
|
||||
});
|
||||
|
||||
it('renders multiple items', () => {
|
||||
expect(instance.formatAddresses(['one', 'two', 'three'])).to.equal('one, two & three');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -18,8 +18,9 @@ import { observer } from 'mobx-react';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { RadioButtons } from '~/ui';
|
||||
import { Container, SelectionList, Title } from '~/ui';
|
||||
|
||||
import TypeIcon from '../TypeIcon';
|
||||
import styles from '../createAccount.css';
|
||||
|
||||
const TYPES = [
|
||||
@ -27,13 +28,13 @@ const TYPES = [
|
||||
description: (
|
||||
<FormattedMessage
|
||||
id='createAccount.creationType.fromNew.description'
|
||||
defaultMessage='Create an account by selecting your identity icon and specifying the password'
|
||||
defaultMessage='Selecting your identity icon and specifying the password'
|
||||
/>
|
||||
),
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id='createAccount.creationType.fromNew.label'
|
||||
defaultMessage='Create new account manually'
|
||||
defaultMessage='New Account'
|
||||
/>
|
||||
),
|
||||
key: 'fromNew'
|
||||
@ -42,13 +43,13 @@ const TYPES = [
|
||||
description: (
|
||||
<FormattedMessage
|
||||
id='createAccount.creationType.fromPhrase.description'
|
||||
defaultMessage='Recover an account by entering a previously stored recovery phrase and new password'
|
||||
defaultMessage='Recover using a previously stored recovery phrase and new password'
|
||||
/>
|
||||
),
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id='createAccount.creationType.fromPhrase.label'
|
||||
defaultMessage='Recover account from recovery phrase'
|
||||
defaultMessage='Recovery phrase'
|
||||
/>
|
||||
),
|
||||
key: 'fromPhrase'
|
||||
@ -57,13 +58,13 @@ const TYPES = [
|
||||
description: (
|
||||
<FormattedMessage
|
||||
id='createAccount.creationType.fromGeth.description'
|
||||
defaultMessage='Import an accounts from the Geth keystore with the original password'
|
||||
defaultMessage='Import accounts from the Geth keystore with the original password'
|
||||
/>
|
||||
),
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id='createAccount.creationType.fromGeth.label'
|
||||
defaultMessage='Import accounts from Geth keystore'
|
||||
defaultMessage='Geth keystore'
|
||||
/>
|
||||
),
|
||||
key: 'fromGeth'
|
||||
@ -72,13 +73,13 @@ const TYPES = [
|
||||
description: (
|
||||
<FormattedMessage
|
||||
id='createAccount.creationType.fromJSON.description'
|
||||
defaultMessage='Create an account by importing an industry-standard JSON keyfile'
|
||||
defaultMessage='Import an industry-standard JSON keyfile with the original password'
|
||||
/>
|
||||
),
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id='createAccount.creationType.fromJSON.label'
|
||||
defaultMessage='Import account from a backup JSON file'
|
||||
defaultMessage='JSON file'
|
||||
/>
|
||||
),
|
||||
key: 'fromJSON'
|
||||
@ -87,13 +88,13 @@ const TYPES = [
|
||||
description: (
|
||||
<FormattedMessage
|
||||
id='createAccount.creationType.fromPresale.description'
|
||||
defaultMessage='Create an account by importing an Ethereum presale wallet file'
|
||||
defaultMessage='Import an Ethereum presale wallet file with the original password'
|
||||
/>
|
||||
),
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id='createAccount.creationType.fromPresale.label'
|
||||
defaultMessage='Import account from an Ethereum pre-sale wallet'
|
||||
defaultMessage='Presale wallet'
|
||||
/>
|
||||
),
|
||||
key: 'fromPresale'
|
||||
@ -102,13 +103,13 @@ const TYPES = [
|
||||
description: (
|
||||
<FormattedMessage
|
||||
id='createAccount.creationType.fromRaw.description'
|
||||
defaultMessage='Create an account by entering a previously backed-up raw private key'
|
||||
defaultMessage='Enter a previously created raw private key with a new password'
|
||||
/>
|
||||
),
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id='createAccount.creationType.fromRaw.label'
|
||||
defaultMessage='Import raw private key'
|
||||
defaultMessage='Private key'
|
||||
/>
|
||||
),
|
||||
key: 'fromRaw'
|
||||
@ -125,20 +126,58 @@ export default class CreationType extends Component {
|
||||
const { createType } = this.props.store;
|
||||
|
||||
return (
|
||||
<div className={ styles.spaced }>
|
||||
<RadioButtons
|
||||
name='creationType'
|
||||
onChange={ this.onChange }
|
||||
value={ createType }
|
||||
values={ TYPES }
|
||||
<div>
|
||||
<div className={ styles.summary }>
|
||||
<FormattedMessage
|
||||
id='createAccount.creationType.info'
|
||||
defaultMessage='Please select the type of account you want to create. Either create an account via name & password, or import it from a variety of existing sources. From here the wizard will guid you through the process of completing your account creation.'
|
||||
/>
|
||||
</div>
|
||||
{ this.renderList(createType) }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
onChange = (item, type) => {
|
||||
renderList () {
|
||||
return (
|
||||
<SelectionList
|
||||
isChecked={ this.isSelected }
|
||||
items={ TYPES }
|
||||
noStretch
|
||||
onSelectClick={ this.onChange }
|
||||
renderItem={ this.renderItem }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderItem = (item) => {
|
||||
return (
|
||||
<Container>
|
||||
<div className={ styles.selectItem }>
|
||||
<TypeIcon
|
||||
className={ styles.icon }
|
||||
store={ this.props.store }
|
||||
type={ item.key }
|
||||
/>
|
||||
<Title
|
||||
byline={ item.description }
|
||||
className={ styles.info }
|
||||
title={ item.label }
|
||||
/>
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
isSelected = (item) => {
|
||||
const { createType } = this.props.store;
|
||||
|
||||
return item.key === createType;
|
||||
}
|
||||
|
||||
onChange = (item) => {
|
||||
const { store } = this.props;
|
||||
|
||||
store.setCreateType(type);
|
||||
store.setCreateType(item.key);
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import { createStore } from '../createAccount.test.js';
|
||||
import CreationType from './';
|
||||
|
||||
let component;
|
||||
let instance;
|
||||
let store;
|
||||
|
||||
function render () {
|
||||
@ -31,6 +32,7 @@ function render () {
|
||||
store={ store }
|
||||
/>
|
||||
);
|
||||
instance = component.instance();
|
||||
|
||||
return component;
|
||||
}
|
||||
@ -44,28 +46,10 @@ describe('modals/CreateAccount/CreationType', () => {
|
||||
expect(component).to.be.ok;
|
||||
});
|
||||
|
||||
describe('selector', () => {
|
||||
const SELECT_TYPE = 'fromRaw';
|
||||
let selector;
|
||||
|
||||
beforeEach(() => {
|
||||
store.setCreateType(SELECT_TYPE);
|
||||
selector = component.find('RadioButtons');
|
||||
});
|
||||
|
||||
it('renders the selector', () => {
|
||||
expect(selector.get(0)).to.be.ok;
|
||||
});
|
||||
|
||||
it('passes the store type to defaultSelected', () => {
|
||||
expect(selector.props().value).to.equal(SELECT_TYPE);
|
||||
});
|
||||
});
|
||||
|
||||
describe('events', () => {
|
||||
describe('onChange', () => {
|
||||
beforeEach(() => {
|
||||
component.instance().onChange(null, 'testing');
|
||||
instance.onChange({ key: 'testing' });
|
||||
});
|
||||
|
||||
it('changes the store createType', () => {
|
||||
|
51
js/src/modals/CreateAccount/GethCard/gethCard.js
Normal file
51
js/src/modals/CreateAccount/GethCard/gethCard.js
Normal file
@ -0,0 +1,51 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
|
||||
import imagesEthereum from '~/../assets/images/contracts/ethereum-black-64x64.png';
|
||||
import { AccountCard } from '~/ui';
|
||||
|
||||
export default class GethCard extends Component {
|
||||
static propTypes = {
|
||||
address: PropTypes.string.isRequired,
|
||||
balance: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
render () {
|
||||
const { address, balance, name } = this.props;
|
||||
|
||||
return (
|
||||
<AccountCard
|
||||
account={ {
|
||||
address,
|
||||
name
|
||||
} }
|
||||
balance={ {
|
||||
tokens: [ {
|
||||
value: balance,
|
||||
token: {
|
||||
image: imagesEthereum,
|
||||
native: true,
|
||||
tag: 'ETH'
|
||||
}
|
||||
} ]
|
||||
} }
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
17
js/src/modals/CreateAccount/GethCard/index.js
Normal file
17
js/src/modals/CreateAccount/GethCard/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
export default from './gethCard';
|
@ -17,11 +17,11 @@
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { Checkbox } from 'material-ui';
|
||||
|
||||
import { IdentityIcon } from '~/ui';
|
||||
import { SelectionList } from '~/ui';
|
||||
|
||||
import styles from './newGeth.css';
|
||||
import GethCard from '../GethCard';
|
||||
import styles from '../createAccount.css';
|
||||
|
||||
@observer
|
||||
export default class NewGeth extends Component {
|
||||
@ -36,9 +36,23 @@ export default class NewGeth extends Component {
|
||||
render () {
|
||||
const { gethAccountsAvailable, gethAddresses } = this.props.store;
|
||||
|
||||
if (!gethAccountsAvailable.length) {
|
||||
return (
|
||||
<div className={ styles.list }>
|
||||
return gethAccountsAvailable.length
|
||||
? (
|
||||
<div>
|
||||
<div className={ styles.summary }>
|
||||
<FormattedMessage
|
||||
id='createAccount.newGeth.available'
|
||||
defaultMessage='There are currently {count} importable keys available from the Geth keystore which are not already available on your Parity instance. Select the accounts you wish to import and move to the next step to complete the import.'
|
||||
values={ {
|
||||
count: gethAccountsAvailable.length
|
||||
} }
|
||||
/>
|
||||
</div>
|
||||
{ this.renderList(gethAccountsAvailable, gethAddresses) }
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<div className={ styles.summary }>
|
||||
<FormattedMessage
|
||||
id='createAccount.newGeth.noKeys'
|
||||
defaultMessage='There are currently no importable keys available from the Geth keystore, which are not already available on your Parity instance'
|
||||
@ -47,49 +61,37 @@ export default class NewGeth extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
const checkboxes = gethAccountsAvailable.map((account) => {
|
||||
const onSelect = (event) => this.onSelectAddress(event, account.address);
|
||||
|
||||
const label = (
|
||||
<div className={ styles.selection }>
|
||||
<div className={ styles.icon }>
|
||||
<IdentityIcon
|
||||
address={ account.address }
|
||||
center
|
||||
inline
|
||||
/>
|
||||
</div>
|
||||
<div className={ styles.detail }>
|
||||
<div className={ styles.address }>
|
||||
{ account.address }
|
||||
</div>
|
||||
<div className={ styles.balance }>
|
||||
{ account.balance } ETH
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
renderList (gethAccountsAvailable) {
|
||||
return (
|
||||
<Checkbox
|
||||
checked={ gethAddresses.includes(account.address) }
|
||||
key={ account.address }
|
||||
label={ label }
|
||||
onCheck={ onSelect }
|
||||
<SelectionList
|
||||
isChecked={ this.isSelected }
|
||||
items={ gethAccountsAvailable }
|
||||
noStretch
|
||||
onSelectClick={ this.onSelect }
|
||||
renderItem={ this.renderAccount }
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={ styles.list }>
|
||||
{ checkboxes }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
onSelectAddress = (event, address) => {
|
||||
renderAccount = (account, index) => {
|
||||
return (
|
||||
<GethCard
|
||||
address={ account.address }
|
||||
balance={ account.balance }
|
||||
name={ `Geth Account ${index + 1}` }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
isSelected = (account) => {
|
||||
const { gethAddresses } = this.props.store;
|
||||
|
||||
return gethAddresses.includes(account.address);
|
||||
}
|
||||
|
||||
onSelect = (account) => {
|
||||
const { store } = this.props;
|
||||
|
||||
store.selectGethAccount(address);
|
||||
store.selectGethAccount(account.address);
|
||||
}
|
||||
}
|
||||
|
@ -48,10 +48,10 @@ describe('modals/CreateAccount/NewGeth', () => {
|
||||
});
|
||||
|
||||
describe('events', () => {
|
||||
describe('onSelectAddress', () => {
|
||||
describe('onSelect', () => {
|
||||
beforeEach(() => {
|
||||
sinon.spy(store, 'selectGethAccount');
|
||||
instance.onSelectAddress(null, 'testAddress');
|
||||
instance.onSelect({ address: 'testAddress' });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
17
js/src/modals/CreateAccount/TypeIcon/index.js
Normal file
17
js/src/modals/CreateAccount/TypeIcon/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
export default from './typeIcon';
|
59
js/src/modals/CreateAccount/TypeIcon/typeIcon.js
Normal file
59
js/src/modals/CreateAccount/TypeIcon/typeIcon.js
Normal file
@ -0,0 +1,59 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
|
||||
import { AccountsIcon, DoneIcon, FileIcon, FileUploadIcon, KeyboardIcon, KeyIcon, MembershipIcon } from '~/ui/Icons';
|
||||
|
||||
import { STAGE_INFO } from '../store';
|
||||
|
||||
export default class TypeIcon extends Component {
|
||||
static propTypes = {
|
||||
className: PropTypes.string,
|
||||
store: PropTypes.object.isRequired,
|
||||
type: PropTypes.string
|
||||
}
|
||||
|
||||
render () {
|
||||
const { className, store, type } = this.props;
|
||||
const { createType, stage } = store;
|
||||
|
||||
if (stage === STAGE_INFO) {
|
||||
return <DoneIcon className={ className } />;
|
||||
}
|
||||
|
||||
switch (type || createType) {
|
||||
case 'fromGeth':
|
||||
return <FileUploadIcon className={ className } />;
|
||||
|
||||
case 'fromPhrase':
|
||||
return <KeyboardIcon className={ className } />;
|
||||
|
||||
case 'fromRaw':
|
||||
return <KeyIcon className={ className } />;
|
||||
|
||||
case 'fromJSON':
|
||||
return <FileIcon className={ className } />;
|
||||
|
||||
case 'fromPresale':
|
||||
return <MembershipIcon className={ className } />;
|
||||
|
||||
case 'fromNew':
|
||||
default:
|
||||
return <AccountsIcon className={ className } />;
|
||||
}
|
||||
}
|
||||
}
|
@ -16,15 +16,42 @@
|
||||
*/
|
||||
|
||||
.details {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
|
||||
.infoForm {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
padding: 1.5em;
|
||||
.info {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
.account {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
|
||||
.name {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
|
||||
.icon {
|
||||
flex: 0 0 56px;
|
||||
margin: 0 1em 0 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.qr {
|
||||
margin: 1.5em auto 0 auto;
|
||||
flex: 0 0 136px;
|
||||
margin: 1.5em 0 0 1.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,3 +115,24 @@
|
||||
.checkbox {
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
.selectItem {
|
||||
display: flex;
|
||||
|
||||
.icon {
|
||||
flex: 0 0 56px;
|
||||
height: 56px !important;
|
||||
margin-right: 0.75em;
|
||||
width: 56px !important;
|
||||
}
|
||||
|
||||
.info {
|
||||
flex: 1 1;
|
||||
}
|
||||
}
|
||||
|
||||
.summary {
|
||||
line-height: 1.618em;
|
||||
padding: 0 4em 1.5em 4em;
|
||||
text-align: center;
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ import { bindActionCreators } from 'redux';
|
||||
|
||||
import { createIdentityImg } from '~/api/util/identity';
|
||||
import { newError } from '~/redux/actions';
|
||||
import { Button, Portal } from '~/ui';
|
||||
import { Button, ModalBox, Portal } from '~/ui';
|
||||
import { CancelIcon, CheckIcon, DoneIcon, NextIcon, PrevIcon, PrintIcon } from '~/ui/Icons';
|
||||
import ParityLogo from '~/../assets/images/parity-logo-black-no-text.svg';
|
||||
|
||||
@ -35,6 +35,7 @@ import NewImport from './NewImport';
|
||||
import RawKey from './RawKey';
|
||||
import RecoveryPhrase from './RecoveryPhrase';
|
||||
import Store, { STAGE_CREATE, STAGE_INFO, STAGE_SELECT_TYPE } from './store';
|
||||
import TypeIcon from './TypeIcon';
|
||||
import print from './print';
|
||||
import recoveryPage from './recoveryPage.ejs';
|
||||
|
||||
@ -97,7 +98,9 @@ class CreateAccount extends Component {
|
||||
: STAGE_IMPORT
|
||||
}
|
||||
>
|
||||
<ModalBox icon={ <TypeIcon store={ this.store } /> }>
|
||||
{ this.renderPage() }
|
||||
</ModalBox>
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
@ -246,11 +249,11 @@ class CreateAccount extends Component {
|
||||
: null,
|
||||
<Button
|
||||
icon={ <DoneIcon /> }
|
||||
key='close'
|
||||
key='done'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='createAccount.button.close'
|
||||
defaultMessage='Close'
|
||||
id='createAccount.button.done'
|
||||
defaultMessage='Done'
|
||||
/>
|
||||
}
|
||||
onClick={ this.onClose }
|
||||
|
@ -91,6 +91,7 @@ export default class Store {
|
||||
this.password = '';
|
||||
this.passwordRepeat = '';
|
||||
this.phrase = '';
|
||||
this.name = '';
|
||||
this.nameError = null;
|
||||
this.rawKeyError = null;
|
||||
this.walletFileError = null;
|
||||
|
@ -24,7 +24,7 @@ import { setAddressImage } from './imagesActions';
|
||||
import * as ABIS from '~/contracts/abi';
|
||||
import { notifyTransaction } from '~/util/notifications';
|
||||
import { LOG_KEYS, getLogger } from '~/config';
|
||||
import imagesEthereum from '../../../assets/images/contracts/ethereum-black-64x64.png';
|
||||
import imagesEthereum from '~/../assets/images/contracts/ethereum-black-64x64.png';
|
||||
|
||||
const log = getLogger(LOG_KEYS.Balances);
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
*/
|
||||
.form {
|
||||
margin-top: -1em;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.autofill {
|
||||
|
@ -15,13 +15,16 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
export AccountsIcon from 'material-ui/svg-icons/action/account-balance-wallet';
|
||||
export AddressesIcon from 'material-ui/svg-icons/communication/contacts';
|
||||
export AddIcon from 'material-ui/svg-icons/content/add';
|
||||
export AppsIcon from 'material-ui/svg-icons/navigation/apps';
|
||||
export AttachFileIcon from 'material-ui/svg-icons/editor/attach-file';
|
||||
export CancelIcon from 'material-ui/svg-icons/content/clear';
|
||||
export CheckIcon from 'material-ui/svg-icons/navigation/check';
|
||||
export CloseIcon from 'material-ui/svg-icons/navigation/close';
|
||||
export CompareIcon from 'material-ui/svg-icons/action/compare-arrows';
|
||||
export ComputerIcon from 'material-ui/svg-icons/hardware/desktop-mac';
|
||||
export ContactsIcon from 'material-ui/svg-icons/image/grid-on';
|
||||
export ContractIcon from 'material-ui/svg-icons/action/code';
|
||||
export CopyIcon from 'material-ui/svg-icons/content/content-copy';
|
||||
export DashboardIcon from 'material-ui/svg-icons/action/dashboard';
|
||||
@ -30,10 +33,13 @@ export DoneIcon from 'material-ui/svg-icons/action/done-all';
|
||||
export EditIcon from 'material-ui/svg-icons/content/create';
|
||||
export ErrorIcon from 'material-ui/svg-icons/alert/error';
|
||||
export FileUploadIcon from 'material-ui/svg-icons/file/file-upload';
|
||||
export FileIcon from 'material-ui/svg-icons/editor/insert-drive-file';
|
||||
export FingerprintIcon from 'material-ui/svg-icons/action/fingerprint';
|
||||
export KeyIcon from 'material-ui/svg-icons/communication/vpn-key';
|
||||
export KeyboardIcon from 'material-ui/svg-icons/hardware/keyboard';
|
||||
export LinkIcon from 'material-ui/svg-icons/content/link';
|
||||
export LockedIcon from 'material-ui/svg-icons/action/lock';
|
||||
export MembershipIcon from 'material-ui/svg-icons/action/card-membership';
|
||||
export MoveIcon from 'material-ui/svg-icons/action/open-with';
|
||||
export NextIcon from 'material-ui/svg-icons/navigation/arrow-forward';
|
||||
export PrevIcon from 'material-ui/svg-icons/navigation/arrow-back';
|
||||
@ -41,10 +47,12 @@ export PrintIcon from 'material-ui/svg-icons/action/print';
|
||||
export RefreshIcon from 'material-ui/svg-icons/action/autorenew';
|
||||
export SaveIcon from 'material-ui/svg-icons/content/save';
|
||||
export SendIcon from 'material-ui/svg-icons/content/send';
|
||||
export SettingsIcon from 'material-ui/svg-icons/action/settings';
|
||||
export SnoozeIcon from 'material-ui/svg-icons/av/snooze';
|
||||
export StarCircleIcon from 'material-ui/svg-icons/action/stars';
|
||||
export StarIcon from 'material-ui/svg-icons/toggle/star';
|
||||
export StarOutlineIcon from 'material-ui/svg-icons/toggle/star-border';
|
||||
export StatusIcon from 'material-ui/svg-icons/action/track-changes';
|
||||
export UnlockedIcon from 'material-ui/svg-icons/action/lock-open';
|
||||
export UpdateIcon from 'material-ui/svg-icons/action/system-update-alt';
|
||||
export UpdateWaitIcon from 'material-ui/svg-icons/action/update';
|
||||
|
17
js/src/ui/ModalBox/index.js
Normal file
17
js/src/ui/ModalBox/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
export default from './modalBox';
|
@ -14,29 +14,49 @@
|
||||
/* You should have received a copy of the GNU General Public License
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
.list {
|
||||
input+div>div {
|
||||
top: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.selection {
|
||||
display: inline-block;
|
||||
margin-bottom: 0.5em;
|
||||
.body {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
|
||||
.body {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.summary {
|
||||
line-height: 1.618em;
|
||||
padding: 0 4em 1.5em 4em;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: inline-block;
|
||||
align-items: center;
|
||||
background: rgb(167, 151, 0);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
flex: 0 0 5em;
|
||||
flex-direction: column;
|
||||
height: 5em;
|
||||
justify-content: center;
|
||||
margin-right: 1.5em;
|
||||
padding: 0.75em;
|
||||
width: 5em;
|
||||
|
||||
img {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.detail {
|
||||
display: inline-block;
|
||||
|
||||
.address {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.balance {
|
||||
font-family: 'Roboto Mono', monospace;
|
||||
svg {
|
||||
fill: white !important;
|
||||
font-size: 3.5em !important;
|
||||
height: 3.5em !important;
|
||||
margin: 0 !important;
|
||||
width: 3.5em !important;
|
||||
}
|
||||
}
|
||||
}
|
61
js/src/ui/ModalBox/modalBox.js
Normal file
61
js/src/ui/ModalBox/modalBox.js
Normal file
@ -0,0 +1,61 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
|
||||
import { nodeOrStringProptype } from '~/util/proptypes';
|
||||
|
||||
import styles from './modalBox.css';
|
||||
|
||||
export default class ModalBox extends Component {
|
||||
static propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
icon: PropTypes.node.isRequired,
|
||||
summary: nodeOrStringProptype()
|
||||
}
|
||||
|
||||
render () {
|
||||
const { children, icon } = this.props;
|
||||
|
||||
return (
|
||||
<div className={ styles.body }>
|
||||
<div className={ styles.icon }>
|
||||
{ icon }
|
||||
</div>
|
||||
<div className={ styles.content }>
|
||||
{ this.renderSummary() }
|
||||
<div className={ styles.body }>
|
||||
{ children }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderSummary () {
|
||||
const { summary } = this.props;
|
||||
|
||||
if (!summary) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ styles.summary }>
|
||||
{ summary }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
56
js/src/ui/ModalBox/modalBox.spec.js
Normal file
56
js/src/ui/ModalBox/modalBox.spec.js
Normal file
@ -0,0 +1,56 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import ModalBox from './';
|
||||
|
||||
let component;
|
||||
|
||||
function render () {
|
||||
component = shallow(
|
||||
<ModalBox
|
||||
children={ <div id='testChild'>testChild</div> }
|
||||
icon={ <div id='testIcon'>testIcon</div> }
|
||||
summary={ <div id='testSummary'>testSummary</div> }
|
||||
/>
|
||||
);
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('ui/ModalBox', () => {
|
||||
beforeEach(() => {
|
||||
render();
|
||||
});
|
||||
|
||||
it('renders defaults', () => {
|
||||
expect(component).to.be.ok;
|
||||
});
|
||||
|
||||
it('adds the children as supplied', () => {
|
||||
expect(component.find('#testChild').text()).to.equal('testChild');
|
||||
});
|
||||
|
||||
it('adds the icon as supplied', () => {
|
||||
expect(component.find('#testIcon').text()).to.equal('testIcon');
|
||||
});
|
||||
|
||||
it('adds the summary as supplied', () => {
|
||||
expect(component.find('#testSummary').text()).to.equal('testSummary');
|
||||
});
|
||||
});
|
@ -40,6 +40,7 @@ export LanguageSelector from './LanguageSelector';
|
||||
export Loading from './Loading';
|
||||
export MethodDecoding from './MethodDecoding';
|
||||
export { Busy as BusyStep, Completed as CompletedStep } from './Modal';
|
||||
export ModalBox from './ModalBox';
|
||||
export muiTheme from './Theme';
|
||||
export Page from './Page';
|
||||
export ParityBackground from './ParityBackground';
|
||||
|
@ -15,15 +15,9 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from 'react';
|
||||
import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet';
|
||||
import ActionFingerprint from 'material-ui/svg-icons/action/fingerprint';
|
||||
import ActionTrackChanges from 'material-ui/svg-icons/action/track-changes';
|
||||
import ActionSettings from 'material-ui/svg-icons/action/settings';
|
||||
import CommunicationContacts from 'material-ui/svg-icons/communication/contacts';
|
||||
import ImageGridOn from 'material-ui/svg-icons/image/grid-on';
|
||||
import NavigationApps from 'material-ui/svg-icons/navigation/apps';
|
||||
|
||||
import imagesEthcoreBlock from '~/../assets/images/parity-logo-white-no-text.svg';
|
||||
import { AccountsIcon, AddressesIcon, AppsIcon, ContactsIcon, FingerprintIcon, SettingsIcon, StatusIcon } from '~/ui/Icons';
|
||||
|
||||
import styles from './views.css';
|
||||
|
||||
@ -44,35 +38,35 @@ const defaultViews = {
|
||||
accounts: {
|
||||
active: true,
|
||||
fixed: true,
|
||||
icon: <ActionAccountBalanceWallet />,
|
||||
icon: <AccountsIcon />,
|
||||
route: '/accounts',
|
||||
value: 'account'
|
||||
},
|
||||
|
||||
addresses: {
|
||||
active: true,
|
||||
icon: <CommunicationContacts />,
|
||||
icon: <AddressesIcon />,
|
||||
route: '/addresses',
|
||||
value: 'address'
|
||||
},
|
||||
|
||||
apps: {
|
||||
active: true,
|
||||
icon: <NavigationApps />,
|
||||
icon: <AppsIcon />,
|
||||
route: '/apps',
|
||||
value: 'app'
|
||||
},
|
||||
|
||||
contracts: {
|
||||
active: false,
|
||||
icon: <ImageGridOn />,
|
||||
icon: <ContactsIcon />,
|
||||
route: '/contracts',
|
||||
value: 'contract'
|
||||
},
|
||||
|
||||
status: {
|
||||
active: false,
|
||||
icon: <ActionTrackChanges />,
|
||||
icon: <StatusIcon />,
|
||||
route: '/status',
|
||||
value: 'status'
|
||||
},
|
||||
@ -80,7 +74,7 @@ const defaultViews = {
|
||||
signer: {
|
||||
active: true,
|
||||
fixed: true,
|
||||
icon: <ActionFingerprint />,
|
||||
icon: <FingerprintIcon />,
|
||||
route: '/signer',
|
||||
value: 'signer'
|
||||
},
|
||||
@ -88,7 +82,7 @@ const defaultViews = {
|
||||
settings: {
|
||||
active: true,
|
||||
fixed: true,
|
||||
icon: <ActionSettings />,
|
||||
icon: <SettingsIcon />,
|
||||
route: '/settings',
|
||||
value: 'settings'
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user