Account/Address create modals (#1221)

* Account/Address create modals

* Spacing

* Account Create modal (& use by vanity)

* Account Create & Import wired in
This commit is contained in:
Jaco Greeff 2019-05-27 22:09:36 +02:00 committed by GitHub
parent 23c62c6a1e
commit f76f1ab594
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 464 additions and 195 deletions

View File

@ -2,20 +2,30 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { ComponentProps } from './types';
import { I18nProps } from '@polkadot/ui-app/types';
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
import { ComponentProps } from './types';
import React from 'react';
import styled from 'styled-components';
import accountObservable from '@polkadot/ui-keyring/observable/accounts';
import { withMulti, withObservable } from '@polkadot/ui-api';
import { Button } from '@polkadot/ui-app';
import CreateModal from './modals/Create';
import ImportModal from './modals/Import';
import Account from './Account';
import translate from './translate';
type Props = ComponentProps & {
type Props = ComponentProps & I18nProps & {
accounts?: SubjectInfo[]
};
type State = {
isCreateOpen: boolean,
isImportOpen: boolean
};
const Wrapper = styled.div`
.accounts {
display: flex;
@ -29,37 +39,71 @@ const Wrapper = styled.div`
}
`;
class Overview extends React.PureComponent<Props> {
constructor (props: Props) {
super(props);
}
class Overview extends React.PureComponent<Props, State> {
state: State = {
isCreateOpen: false,
isImportOpen: false
};
render () {
const { accounts } = this.props;
if (!accounts || Object.keys(accounts).length === 0) {
return null;
}
const { accounts, onStatusChange, t } = this.props;
const { isCreateOpen, isImportOpen } = this.state;
return (
<Wrapper>
<Button.Group>
<Button
isPrimary
label={t('Add account')}
onClick={this.toggleCreate}
/>
<Button.Or />
<Button
isPrimary
label={t('Restore JSON')}
onClick={this.toggleImport}
/>
</Button.Group>
{isCreateOpen && (
<CreateModal
onClose={this.toggleCreate}
onStatusChange={onStatusChange}
/>
)}
{isImportOpen && (
<ImportModal
onClose={this.toggleImport}
onStatusChange={onStatusChange}
/>
)}
<div className='accounts'>
{Object.keys(accounts).map((address) => {
return (
<Account
address={address}
key={address}
/>
);
})}
{accounts && Object.keys(accounts).map((address) => (
<Account
address={address}
key={address}
/>
))}
<div className='spacer' />
</div>
</Wrapper>
);
}
private toggleCreate = (): void => {
this.setState(({ isCreateOpen }) => ({
isCreateOpen: !isCreateOpen
}));
}
private toggleImport = (): void => {
this.setState(({ isImportOpen }) => ({
isImportOpen: !isImportOpen
}));
}
}
export default withMulti(
Overview,
translate,
withObservable(accountObservable.subject, { propName: 'accounts' })
);

View File

@ -12,6 +12,7 @@ import styled from 'styled-components';
import { Button, Dropdown, Input, TxComponent } from '@polkadot/ui-app';
import uiSettings from '@polkadot/ui-settings';
import CreateModal from '../modals/Create';
import generator from '../vanitygen';
import matchRegex from '../vanitygen/regex';
import generatorSort from '../vanitygen/sort';
@ -21,6 +22,7 @@ import translate from './translate';
type Props = ComponentProps & I18nProps;
type State = {
createSeed: string | null,
elapsed: number,
isMatchValid: boolean,
isRunning: boolean,
@ -54,6 +56,7 @@ const Wrapper = styled.div`
class VanityApp extends TxComponent<Props, State> {
results: Array<Generator$Result> = [];
state: State = {
createSeed: null,
elapsed: 0,
isMatchValid: true,
isRunning: false,
@ -73,12 +76,23 @@ class VanityApp extends TxComponent<Props, State> {
}
render () {
const { onStatusChange } = this.props;
const { createSeed, type } = this.state;
return (
<Wrapper>
{this.renderOptions()}
{this.renderButtons()}
{this.renderStats()}
{this.renderMatches()}
{createSeed && (
<CreateModal
onClose={this.closeCreate}
onStatusChange={onStatusChange}
seed={createSeed}
type={type}
/>
)}
</Wrapper>
);
}
@ -249,11 +263,8 @@ class VanityApp extends TxComponent<Props, State> {
}, 0);
}
private onCreateToggle = (seed: string) => {
const { basePath } = this.props;
const { type } = this.state;
window.location.hash = `${basePath}/create/${type}/${seed}`;
private onCreateToggle = (createSeed: string) => {
this.setState({ createSeed });
}
private onChangeCase = (withCase: boolean): void => {
@ -299,6 +310,10 @@ class VanityApp extends TxComponent<Props, State> {
this.executeGeneration
);
}
private closeCreate = () => {
this.setState({ createSeed: null });
}
}
export default translate(VanityApp);

View File

@ -16,9 +16,7 @@ import { withMulti, withObservable } from '@polkadot/ui-api';
import './index.css';
import basicMd from './md/basic.md';
import Creator from './Creator';
import Overview from './Overview';
import Restore from './Restore';
import translate from './translate';
import Vanity from './Vanity';
@ -49,15 +47,6 @@ class AccountsApp extends React.PureComponent<Props, State> {
name: 'overview',
text: t('Overview')
},
{
hasParams: true,
name: 'create',
text: t('Create account')
},
{
name: 'restore',
text: t('Restore account')
},
{
name: 'vanity',
text: t('Vanity address')
@ -77,7 +66,7 @@ class AccountsApp extends React.PureComponent<Props, State> {
// Hide vanity as well - since the route order and matching changes, the
// /create/:seed route become problematic, so don't allow that option
return {
hidden: ['overview','vanity']
hidden: ['vanity']
};
}
@ -98,7 +87,6 @@ class AccountsApp extends React.PureComponent<Props, State> {
render () {
const { basePath } = this.props;
const { hidden, tabs } = this.state;
const renderCreator = this.renderComponent(Creator);
return (
<main className='accounts--App'>
@ -111,17 +99,8 @@ class AccountsApp extends React.PureComponent<Props, State> {
/>
</header>
<Switch>
<Route path={`${basePath}/create/:type/:seed`} render={renderCreator} />
<Route path={`${basePath}/create`} render={renderCreator} />
<Route path={`${basePath}/restore`} render={this.renderComponent(Restore)} />
<Route path={`${basePath}/vanity`} render={this.renderComponent(Vanity)} />
<Route
render={
hidden.includes('overview')
? renderCreator
: this.renderComponent(Overview)
}
/>
<Route render={this.renderComponent(Overview)} />
</Switch>
</main>
);

View File

@ -6,28 +6,24 @@ import { I18nProps } from '@polkadot/ui-app/types';
import { ApiProps } from '@polkadot/ui-api/types';
import { ActionStatus } from '@polkadot/ui-app/Status/types';
import { KeypairType } from '@polkadot/util-crypto/types';
import { ComponentProps } from './types';
import { ModalProps } from '../types';
import FileSaver from 'file-saver';
import React from 'react';
import { DEV_PHRASE } from '@polkadot/keyring/defaults';
import { withApi, withMulti } from '@polkadot/ui-api';
import { AddressRow, Button, Dropdown, Input, InputTags, Labelled, Modal, Password, TxComponent } from '@polkadot/ui-app';
import { AddressRow, Button, Dropdown, Input, InputTags, Labelled, Modal, Password } from '@polkadot/ui-app';
import { InputAddress } from '@polkadot/ui-app/InputAddress';
import keyring from '@polkadot/ui-keyring';
import uiSettings from '@polkadot/ui-settings';
import { isHex, u8aToHex } from '@polkadot/util';
import { keyExtractPath, mnemonicGenerate, mnemonicValidate, randomAsU8a } from '@polkadot/util-crypto';
import translate from './translate';
import translate from '../translate';
type Props = ComponentProps & ApiProps & I18nProps & {
match: {
params: {
seed?: string,
type?: KeypairType
}
}
type Props = ModalProps & ApiProps & I18nProps & {
seed?: string,
type?: KeypairType
};
type SeedType = 'bip' | 'raw' | 'dev';
@ -90,13 +86,13 @@ function addressFromSeed (phrase: string, derivePath: string, pairType: KeypairT
.address();
}
class Creator extends TxComponent<Props, State> {
class Create extends React.PureComponent<Props, State> {
state: State = { seedType: 'bip' } as State;
constructor (props: Props) {
super(props);
const { isDevelopment, match: { params: { seed, type } }, t } = this.props;
const { isDevelopment, seed, t, type } = this.props;
const seedOptions: Array<SeedOption> = [
{ value: 'bip', text: t('Mnemonic') },
{ value: 'raw', text: t('Raw seed') }
@ -113,24 +109,18 @@ class Creator extends TxComponent<Props, State> {
}
render () {
const { address, name, isSeedValid } = this.state;
const { t } = this.props;
return (
<div className='accounts--Creator'>
<Modal
dimmer='inverted'
open
>
<Modal.Header>{t('Add an account via seed')}</Modal.Header>
{this.renderModal()}
<div className='ui--grid'>
{this.renderInput()}
<AddressRow
className='shrink'
defaultName={name}
value={
isSeedValid
? address
: ''
}
/>
</div>
</div>
{this.renderInput()}
{this.renderButtons()}
</Modal>
);
}
@ -139,26 +129,27 @@ class Creator extends TxComponent<Props, State> {
const { isValid } = this.state;
return (
<Button.Group>
<Button
label={t('Reset')}
onClick={this.onDiscard}
/>
<Button.Or />
<Button
isDisabled={!isValid}
isPrimary
label={t('Save')}
onClick={this.onShowWarning}
ref={this.button}
/>
</Button.Group>
<Modal.Actions>
<Button.Group>
<Button
label={t('Cancel')}
onClick={this.onDiscard}
/>
<Button.Or />
<Button
isDisabled={!isValid}
isPrimary
label={t('Save')}
onClick={this.onShowWarning}
/>
</Button.Group>
</Modal.Actions>
);
}
private renderInput () {
const { t } = this.props;
const { deriveError, derivePath, isNameValid, isPassValid, isSeedValid, name, pairType, password, seed, seedOptions, seedType, tags } = this.state;
const { address, deriveError, derivePath, isNameValid, isPassValid, isSeedValid, name, pairType, password, seed, seedOptions, seedType, tags } = this.state;
const seedLabel = (() => {
switch (seedType) {
case 'bip':
@ -171,8 +162,11 @@ class Creator extends TxComponent<Props, State> {
})();
return (
<div className='grow'>
<div className='ui--row'>
<Modal.Content>
<AddressRow
defaultName={name}
value={isSeedValid ? address : ''}
>
<Input
autoFocus
className='full'
@ -180,11 +174,9 @@ class Creator extends TxComponent<Props, State> {
isError={!isNameValid}
label={t('name')}
onChange={this.onChangeName}
onEnter={this.submit}
onEnter={this.onCommit}
value={name}
/>
</div>
<div className='ui--row'>
<Input
className='full'
help={t('The private key for your account is derived from this seed. This seed must be kept secret as anyone in its possession has access to the funds of this account. If you validate, use the seed of the session account as the "--key" parameter of your node.')}
@ -192,7 +184,7 @@ class Creator extends TxComponent<Props, State> {
isError={!isSeedValid}
label={seedLabel}
onChange={this.onChangeSeed}
onEnter={this.submit}
onEnter={this.onCommit}
value={seed}
>
<Dropdown
@ -202,61 +194,56 @@ class Creator extends TxComponent<Props, State> {
options={seedOptions}
/>
</Input>
</div>
<div className='ui--row'>
<Password
className='full'
help={t('This password is used to encrypt your private key. It must be strong and unique! You will need it to sign transactions with this account. You can recover this account using this password together with the backup file (generated in the next step).')}
isError={!isPassValid}
label={t('password')}
onChange={this.onChangePass}
onEnter={this.submit}
onEnter={this.onCommit}
value={password}
/>
</div>
<div className='ui--row'>
<InputTags
help={t('Additional user-specified tags that can be used to identify the account. Tags can be used for categorization and filtering.')}
label={t('user-defined tags')}
onChange={this.onChangeTags}
value={tags}
/>
</div>
<details
className='accounts--Creator-advanced'
open
>
<summary>{t('Advanced creation options')}</summary>
<div className='ui--Params'>
<div className='ui--row'>
<Dropdown
defaultValue={pairType}
help={t('Determines what cryptography will be used to create this account. Note that to validate on Polkadot, the session account must use "ed25519".')}
label={t('keypair crypto type')}
onChange={this.onChangePairType}
options={uiSettings.availableCryptos}
/>
<details
className='accounts--Creator-advanced'
open
>
<summary>{t('Advanced creation options')}</summary>
<div className='ui--Params'>
<div className='ui--row'>
<Dropdown
defaultValue={pairType}
help={t('Determines what cryptography will be used to create this account. Note that to validate on Polkadot, the session account must use "ed25519".')}
label={t('keypair crypto type')}
onChange={this.onChangePairType}
options={uiSettings.availableCryptos}
/>
</div>
<div className='ui--row'>
<Input
className='full'
help={t('You can set a custom derivation path for this account using the following syntax "/<soft-key>//<hard-key>". The "/<soft-key>" and "//<hard-key>" may be repeated and mixed`.')}
isError={!!deriveError}
label={t('secret derivation path')}
onChange={this.onChangeDerive}
onEnter={this.onCommit}
value={derivePath}
/>
</div>
{
deriveError
? <Labelled label=''><article className='error'>{deriveError}</article></Labelled>
: null
}
</div>
<div className='ui--row'>
<Input
className='full'
help={t('You can set a custom derivation path for this account using the following syntax "/<soft-key>//<hard-key>". The "/<soft-key>" and "//<hard-key>" may be repeated and mixed`.')}
isError={!!deriveError}
label={t('secret derivation path')}
onChange={this.onChangeDerive}
onEnter={this.submit}
value={derivePath}
/>
</div>
{
deriveError
? <Labelled label=''><article className='error'>{deriveError}</article></Labelled>
: null
}
</div>
</details>
{this.renderButtons()}
</div>
</details>
</AddressRow>
</Modal.Content>
);
}
@ -266,7 +253,6 @@ class Creator extends TxComponent<Props, State> {
return (
<Modal
className='app--accounts-Modal'
dimmer='inverted'
open={showWarning}
>
@ -417,12 +403,13 @@ class Creator extends TxComponent<Props, State> {
}
private onCommit = (): void => {
const { basePath, onStatusChange, t } = this.props;
const { derivePath, name, pairType, password, seed, tags } = this.state;
const { onClose, onStatusChange, t } = this.props;
const { derivePath, isValid, name, pairType, password, seed, tags } = this.state;
const status = { action: 'create' } as ActionStatus;
const status = {
action: 'create'
} as ActionStatus;
if (!isValid) {
return;
}
try {
const { json, pair } = keyring.addUri(`${seed}${derivePath}`, password, { name, tags }, pairType);
@ -443,14 +430,13 @@ class Creator extends TxComponent<Props, State> {
this.onHideWarning();
onStatusChange(status);
window.location.hash = basePath;
onClose();
}
private onDiscard = (): void => {
this.setState(({ pairType }) =>
this.emptyState(null, '', pairType)
);
const { onClose } = this.props;
onClose();
}
private selectSeedType = (seedType: SeedType): void => {
@ -466,7 +452,7 @@ class Creator extends TxComponent<Props, State> {
}
export default withMulti(
Creator,
Create,
translate,
withApi
);

View File

@ -5,17 +5,17 @@
import { KeyringPair$Json } from '@polkadot/keyring/types';
import { I18nProps } from '@polkadot/ui-app/types';
import { ActionStatus } from '@polkadot/ui-app/Status/types';
import { ComponentProps } from './types';
import { ModalProps } from '../types';
import React from 'react';
import { AddressRow, Button, InputFile, Password, TxComponent } from '@polkadot/ui-app';
import { AddressRow, Button, InputFile, Modal, Password, TxComponent } from '@polkadot/ui-app';
import { InputAddress } from '@polkadot/ui-app/InputAddress';
import { isHex, isObject, u8aToString } from '@polkadot/util';
import keyring from '@polkadot/ui-keyring';
import translate from './translate';
import translate from '../translate';
type Props = ComponentProps & I18nProps;
type Props = ModalProps & I18nProps;
type State = {
address: string | null,
@ -25,7 +25,7 @@ type State = {
password: string
};
class Restore extends TxComponent<Props, State> {
class Import extends TxComponent<Props, State> {
state: State = {
address: null,
isFileValid: false,
@ -35,33 +35,47 @@ class Restore extends TxComponent<Props, State> {
};
render () {
const { address, isFileValid } = this.state;
const { onClose, t } = this.props;
const { isFileValid, isPassValid } = this.state;
return (
<div className='accounts--Restore'>
<div className='ui--grid'>
{this.renderInput()}
<AddressRow
className='shrink'
value={
isFileValid && address
? address
: null
}
/>
</div>
</div>
<Modal
dimmer='inverted'
open
>
<Modal.Header>{t('Add an account via seed')}</Modal.Header>
{this.renderInput()}
<Modal.Actions>
<Button.Group>
<Button
label={t('Cancel')}
onClick={onClose}
/>
<Button.Or />
<Button
isDisabled={!isFileValid || !isPassValid}
isPrimary
onClick={this.onSave}
label={t('Restore')}
ref={this.button}
/>
</Button.Group>
</Modal.Actions>
</Modal>
);
}
private renderInput () {
const { t } = this.props;
const { isFileValid, isPassValid, password } = this.state;
const { address, isFileValid, isPassValid, json, password } = this.state;
const acceptedFormats = ['application/json', 'text/plain'].join(', ');
return (
<div className='grow'>
<div className='ui--row'>
<Modal.Content>
<AddressRow
defaultName={isFileValid && json ? json.meta.name : null}
value={isFileValid && address ? address : null}
>
<InputFile
accept={acceptedFormats}
className='full'
@ -71,8 +85,6 @@ class Restore extends TxComponent<Props, State> {
onChange={this.onChangeFile}
withLabel
/>
</div>
<div className='ui--row'>
<Password
autoFocus
className='full'
@ -83,17 +95,8 @@ class Restore extends TxComponent<Props, State> {
onEnter={this.submit}
value={password}
/>
</div>
<Button.Group>
<Button
isDisabled={!isFileValid || !isPassValid}
isPrimary
onClick={this.onSave}
label={t('Restore')}
ref={this.button}
/>
</Button.Group>
</div>
</AddressRow>
</Modal.Content>
);
}
@ -131,16 +134,14 @@ class Restore extends TxComponent<Props, State> {
}
private onSave = (): void => {
const { basePath, onStatusChange, t } = this.props;
const { onClose, onStatusChange, t } = this.props;
const { json, password } = this.state;
if (!json) {
return;
}
const status = {
action: 'restore'
} as ActionStatus;
const status = { action: 'restore' } as ActionStatus;
try {
const pair = keyring.restoreAccount(json, password);
@ -161,9 +162,9 @@ class Restore extends TxComponent<Props, State> {
onStatusChange(status);
if (status.status !== 'error') {
window.location.hash = basePath;
onClose();
}
}
}
export default translate(Restore);
export default translate(Import);

View File

@ -3,6 +3,7 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { AppProps } from '@polkadot/ui-app/types';
import { ActionStatus } from '@polkadot/ui-app/Status/types';
import { WithTranslation } from 'react-i18next';
@ -22,3 +23,8 @@ export type BareProps = {
export type I18nProps = BareProps & WithTranslation;
export type ComponentProps = AppProps & LocationProps;
export type ModalProps = {
onClose: () => void,
onStatusChange: (status: ActionStatus) => void
};

View File

@ -16,6 +16,7 @@ import Tabs, { TabItem } from '@polkadot/ui-app/Tabs';
import { withMulti, withObservable } from '@polkadot/ui-api';
import basicMd from './md/basic.md';
import CreateModal from './modals/Create';
import Creator from './Creator';
import Editor from './Editor';
import translate from './translate';
@ -26,7 +27,8 @@ type Props = AppProps & I18nProps & {
type State = {
hidden: Array<string>,
items: Array<TabItem>
items: Array<TabItem>,
isCreateOpen: boolean
};
class AddressBookApp extends React.PureComponent<Props, State> {
@ -42,6 +44,7 @@ class AddressBookApp extends React.PureComponent<Props, State> {
this.state = {
...baseState,
isCreateOpen: false,
items: [
{
name: 'edit',
@ -82,8 +85,8 @@ class AddressBookApp extends React.PureComponent<Props, State> {
}
render () {
const { basePath } = this.props;
const { hidden, items } = this.state;
const { basePath, onStatusChange } = this.props;
const { hidden, isCreateOpen, items } = this.state;
const renderCreator = this.renderComponent(Creator);
return (
@ -106,6 +109,12 @@ class AddressBookApp extends React.PureComponent<Props, State> {
}
/>
</Switch>
{isCreateOpen && (
<CreateModal
onClose={this.toggleCreate}
onStatusChange={onStatusChange}
/>
)}
</main>
);
}
@ -123,6 +132,12 @@ class AddressBookApp extends React.PureComponent<Props, State> {
);
};
}
private toggleCreate = (): void => {
this.setState(({ isCreateOpen }) => ({
isCreateOpen: !isCreateOpen
}));
}
}
export default withMulti(

View File

@ -0,0 +1,221 @@
// Copyright 2017-2019 @polkadot/app-address-book authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { I18nProps } from '@polkadot/ui-app/types';
import { ActionStatus } from '@polkadot/ui-app/Status/types';
import { ModalProps } from '../types';
import React from 'react';
import { AddressRow, Button, Input, InputTags, Modal } from '@polkadot/ui-app';
import { InputAddress } from '@polkadot/ui-app/InputAddress';
import keyring from '@polkadot/ui-keyring';
import translate from '../translate';
type Props = ModalProps & I18nProps;
type State = {
address: string,
isAddressExisting: boolean,
isAddressValid: boolean,
isNameValid: boolean,
isValid: boolean,
name: string,
tags: Array<string>
};
class Create extends React.PureComponent<Props, State> {
state: State;
constructor (props: Props) {
super(props);
this.state = this.emptyState();
}
render () {
const { t } = this.props;
return (
<Modal
dimmer='inverted'
open
>
<Modal.Header>{t('Add an address')}</Modal.Header>
{this.renderContent()}
{this.renderButtons()}
</Modal>
);
}
private renderButtons () {
const { t } = this.props;
const { isValid } = this.state;
return (
<Modal.Actions>
<Button.Group>
<Button
isNegative
onClick={this.onDiscard}
label={t('Cancel')}
/>
<Button.Or />
<Button
isDisabled={!isValid}
isPrimary
onClick={this.onCommit}
label={t('Save')}
/>
</Button.Group>
</Modal.Actions>
);
}
private renderContent () {
const { t } = this.props;
const { address, isAddressValid, isNameValid, name, tags } = this.state;
return (
<Modal.Content>
<AddressRow
defaultName={name}
value={address}
>
<Input
autoFocus
className='full'
help={t('Paste here the address of the contact you want to add to your address book.')}
isError={!isAddressValid}
label={t('address')}
onChange={this.onChangeAddress}
onEnter={this.onCommit}
value={address}
/>
<Input
className='full'
help={t('Type the name of your contact. This name will be used across all the apps. It can be edited later on.')}
isError={!isNameValid}
label={t('name')}
onChange={this.onChangeName}
onEnter={this.onCommit}
value={name}
/>
<InputTags
help={t('Additional user-specified tags that can be used to identify the address. Tags can be used for categorization and filtering.')}
label={t('user-defined tags')}
onChange={this.onChangeTags}
value={tags}
/>
</AddressRow>
</Modal.Content>
);
}
private emptyState (): State {
return {
address: '',
isAddressExisting: false,
isAddressValid: false,
isNameValid: true,
isValid: false,
name: 'new address',
tags: []
};
}
private nextState (newState: State, allowEdit: boolean = false): void {
this.setState(
(prevState: State): State => {
let { address = prevState.address, name = prevState.name, tags = prevState.tags } = newState;
let isAddressValid = true;
let isAddressExisting = false;
let newAddress = address;
try {
newAddress = keyring.encodeAddress(
keyring.decodeAddress(address)
);
isAddressValid = keyring.isAvailable(newAddress);
if (!isAddressValid) {
const old = keyring.getAddress(newAddress);
if (old.isValid) {
if (!allowEdit) {
name = old.getMeta().name || name;
}
isAddressExisting = true;
isAddressValid = true;
}
}
} catch (error) {
isAddressValid = false;
}
const isNameValid = !!name;
return {
address: newAddress,
isAddressExisting,
isAddressValid,
isNameValid,
isValid: isAddressValid && isNameValid,
name,
tags
};
}
);
}
private onChangeAddress = (address: string): void => {
this.nextState({ address } as State);
}
private onChangeName = (name: string): void => {
this.nextState({ name } as State, true);
}
private onChangeTags = (tags: Array<string>): void => {
this.setState({ tags });
}
private onCommit = (): void => {
const { onClose, onStatusChange, t } = this.props;
const { address, isAddressExisting, isValid, name, tags } = this.state;
const status = { action: 'create' } as ActionStatus;
if (!isValid) {
return;
}
try {
keyring.saveAddress(address, { name, tags });
status.account = address;
status.status = address ? 'success' : 'error';
status.message = isAddressExisting
? t('address edited')
: t('address created');
InputAddress.setLastValue('address', address);
} catch (error) {
status.status = 'error';
status.message = error.message;
}
onStatusChange(status);
onClose();
}
private onDiscard = (): void => {
const { onClose } = this.props;
onClose();
}
}
export default translate(Create);

View File

@ -3,5 +3,11 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { AppProps } from '@polkadot/ui-app/types';
import { ActionStatus } from '@polkadot/ui-app/Status/types';
export type ComponentProps = AppProps;
export type ModalProps = {
onClose: () => void,
onStatusChange: (status: ActionStatus) => void
};

View File

@ -110,10 +110,6 @@
text-align: right;
}
> .content {
text-align: center;
}
> .header:not(.ui) {
background: #f5f5f5;
font-size: 1.25rem !important;