Common AddressSummary component, update staking layout (#194)
* Move AddressSummary component into ui-app * Add children rendering to summary * User AddressSummary in staking * Mini summary component * Show nominating in modal * Check nomination against intentions (not validators) * Update input description
This commit is contained in:
parent
9f9c937a41
commit
c80956b7f2
@ -1,110 +0,0 @@
|
||||
// Copyright 2017-2018 @polkadot/app-accounts authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { I18nProps } from '@polkadot/ui-app/types';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import CopyButton from '@polkadot/ui-app/CopyButton';
|
||||
import classes from '@polkadot/ui-app/util/classes';
|
||||
import IdentityIcon from '@polkadot/ui-react/IdentityIcon';
|
||||
import Balance from '@polkadot/ui-react-rx/Balance';
|
||||
import Nonce from '@polkadot/ui-react-rx/Nonce';
|
||||
import addressDecode from '@polkadot/util-keyring/address/decode';
|
||||
import addressEncode from '@polkadot/util-keyring/address/encode';
|
||||
|
||||
import translate from './translate';
|
||||
|
||||
type Props = I18nProps & {
|
||||
value: string;
|
||||
};
|
||||
|
||||
type State = {
|
||||
address: string,
|
||||
isValid: boolean,
|
||||
publicKey: Uint8Array | null,
|
||||
shortValue: string
|
||||
};
|
||||
|
||||
const DEFAULT_ADDR = '5'.padEnd(16, 'x');
|
||||
const DEFAULT_SHORT = `${DEFAULT_ADDR.slice(0, 7)}…${DEFAULT_ADDR.slice(-7)}`;
|
||||
|
||||
class Address extends React.PureComponent<Props, State> {
|
||||
state: State = {} as State;
|
||||
|
||||
static getDerivedStateFromProps ({ value }: Props, { address, publicKey, shortValue }: State): State {
|
||||
try {
|
||||
publicKey = addressDecode(value);
|
||||
address = addressEncode(publicKey);
|
||||
shortValue = `${address.slice(0, 7)}…${address.slice(-7)}`;
|
||||
} catch (error) {
|
||||
publicKey = null;
|
||||
}
|
||||
|
||||
const isValid = !!publicKey && publicKey.length === 32;
|
||||
|
||||
return {
|
||||
address: isValid ? address : DEFAULT_ADDR,
|
||||
isValid,
|
||||
publicKey,
|
||||
shortValue: isValid ? shortValue : DEFAULT_SHORT
|
||||
};
|
||||
}
|
||||
|
||||
render () {
|
||||
const { className, style } = this.props;
|
||||
const { address, isValid, shortValue } = this.state;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes('accounts--Address', !isValid && 'invalid', className)}
|
||||
style={style}
|
||||
>
|
||||
<IdentityIcon
|
||||
className='accounts--Address-icon'
|
||||
size={96}
|
||||
value={address}
|
||||
/>
|
||||
<div className='accounts--Address-data'>
|
||||
<div className='accounts--Address-address'>
|
||||
{shortValue}
|
||||
</div>
|
||||
<CopyButton value={address} />
|
||||
</div>
|
||||
{this.renderBalance()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderBalance () {
|
||||
const { t } = this.props;
|
||||
const { isValid, publicKey } = this.state;
|
||||
|
||||
if (!isValid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
<Balance
|
||||
className='accounts--Address-balance'
|
||||
key='balance'
|
||||
label={t('address.balance', {
|
||||
defaultValue: 'balance '
|
||||
})}
|
||||
params={publicKey}
|
||||
/>,
|
||||
<Nonce
|
||||
className='accounts--Address-nonce'
|
||||
key='nonce'
|
||||
params={publicKey}
|
||||
>
|
||||
{t('address.transactions', {
|
||||
defaultValue: ' transactions'
|
||||
})}
|
||||
</Nonce>
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(Address);
|
||||
@ -19,7 +19,7 @@ import keypairFromSeed from '@polkadot/util-crypto/nacl/keypair/fromSeed';
|
||||
import randomBytes from '@polkadot/util-crypto/random/asU8a';
|
||||
import addressEncode from '@polkadot/util-keyring/address/encode';
|
||||
|
||||
import Address from './Address';
|
||||
import AddressSummary from '@polkadot/ui-app/AddressSummary';
|
||||
import translate from './translate';
|
||||
|
||||
type Props = I18nProps & {
|
||||
@ -70,7 +70,7 @@ class Creator extends React.PureComponent<Props, State> {
|
||||
style={style}
|
||||
>
|
||||
<div className='ui--grid'>
|
||||
<Address
|
||||
<AddressSummary
|
||||
className='shrink'
|
||||
value={
|
||||
isSeedValid
|
||||
|
||||
@ -13,7 +13,7 @@ import InputAddress from '@polkadot/ui-app/InputAddress';
|
||||
import classes from '@polkadot/ui-app/util/classes';
|
||||
import keyring from '@polkadot/ui-keyring/index';
|
||||
|
||||
import Address from './Address';
|
||||
import AddressSummary from '@polkadot/ui-app/AddressSummary';
|
||||
import translate from './translate';
|
||||
|
||||
type Props = I18nProps & {
|
||||
@ -107,7 +107,7 @@ class Editor extends React.PureComponent<Props, State> {
|
||||
|
||||
return (
|
||||
<div className='ui--grid'>
|
||||
<Address
|
||||
<AddressSummary
|
||||
className='shrink'
|
||||
value={address}
|
||||
/>
|
||||
|
||||
@ -10,47 +10,3 @@
|
||||
margin-bottom: 2em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.accounts--Address {
|
||||
padding: 0 2em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.accounts--Address .rx--updated {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.accounts--Address.invalid {
|
||||
filter: grayscale(100);
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
.accounts--Address-icon {
|
||||
display: block
|
||||
}
|
||||
|
||||
.accounts--Address-balance,
|
||||
.accounts--Address-nonce {
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
.accounts--Address-balance.rx--updated,
|
||||
.accounts--Address-nonce.rx--updated {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.accounts--Address-data {
|
||||
padding: 0.5em 0;
|
||||
}
|
||||
|
||||
.accounts--Address-data * {
|
||||
margin: 0 0.25rem !important;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.accounts--Address-address {
|
||||
display: inline-block;
|
||||
font-family: monospace;
|
||||
font-size: 1.5em;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@ -16,7 +16,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.0.0-beta.51",
|
||||
"@polkadot/app-accounts": "^0.19.17",
|
||||
"@polkadot/ui-app": "^0.19.17"
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ import keyring from '@polkadot/ui-keyring/index';
|
||||
import addressDecode from '@polkadot/util-keyring/address/decode';
|
||||
import addressEncode from '@polkadot/util-keyring/address/encode';
|
||||
|
||||
import Address from '@polkadot/app-accounts/Address';
|
||||
import AddressSummary from '@polkadot/ui-app/AddressSummary';
|
||||
import translate from './translate';
|
||||
|
||||
type Props = I18nProps & {
|
||||
@ -47,7 +47,7 @@ class Creator extends React.PureComponent<Props, State> {
|
||||
style={style}
|
||||
>
|
||||
<div className='ui--grid'>
|
||||
<Address
|
||||
<AddressSummary
|
||||
className='shrink'
|
||||
value={address}
|
||||
/>
|
||||
|
||||
@ -13,7 +13,7 @@ import InputAddress from '@polkadot/ui-app/InputAddress';
|
||||
import classes from '@polkadot/ui-app/util/classes';
|
||||
import keyring from '@polkadot/ui-keyring/index';
|
||||
|
||||
import Address from '@polkadot/app-accounts/Address';
|
||||
import AddressSummary from '@polkadot/ui-app/AddressSummary';
|
||||
import translate from './translate';
|
||||
|
||||
type Props = I18nProps & {
|
||||
@ -107,7 +107,7 @@ class Editor extends React.PureComponent<Props, State> {
|
||||
|
||||
return (
|
||||
<div className='ui--grid'>
|
||||
<Address
|
||||
<AddressSummary
|
||||
className='shrink'
|
||||
value={address}
|
||||
/>
|
||||
|
||||
@ -12,39 +12,34 @@ import BN from 'bn.js';
|
||||
import React from 'react';
|
||||
import extrinsics from '@polkadot/extrinsics';
|
||||
import storage from '@polkadot/storage';
|
||||
import AddressMini from '@polkadot/ui-app/AddressMini';
|
||||
import AddressSummary from '@polkadot/ui-app/AddressSummary';
|
||||
import Button from '@polkadot/ui-app/Button';
|
||||
import Icon from '@polkadot/ui-app/Icon';
|
||||
import Input from '@polkadot/ui-app/Input';
|
||||
import classes from '@polkadot/ui-app/util/classes';
|
||||
import IdentityIcon from '@polkadot/ui-react/IdentityIcon';
|
||||
import RxBalance from '@polkadot/ui-react-rx/Balance';
|
||||
import RxNonce from '@polkadot/ui-react-rx/Nonce';
|
||||
import withMulti from '@polkadot/ui-react-rx/with/multi';
|
||||
import withStorage from '@polkadot/ui-react-rx/with/storage';
|
||||
import decodeAddress from '@polkadot/util-keyring/address/decode';
|
||||
import encodeAddress from '@polkadot/util-keyring/address/encode';
|
||||
|
||||
import Nominating from './Nominating';
|
||||
import UnnominateButton from './UnnominateButton';
|
||||
import translate from './translate';
|
||||
|
||||
type Props = I18nProps & {
|
||||
accountIndex?: BN,
|
||||
address: string,
|
||||
name: string,
|
||||
nonce?: BN,
|
||||
nominating?: string,
|
||||
nominatorsFor?: Array<string>,
|
||||
intentionPosition: number,
|
||||
isIntending: boolean,
|
||||
intentions: Array<string>,
|
||||
isValidator: boolean,
|
||||
queueExtrinsic: QueueTx$ExtrinsicAdd,
|
||||
validators: Array<string>
|
||||
};
|
||||
|
||||
type State = {
|
||||
isInNominate: boolean,
|
||||
isNomineeValid: boolean,
|
||||
nominee: string,
|
||||
nonce: BN
|
||||
isNominateOpen: boolean
|
||||
};
|
||||
|
||||
class Account extends React.PureComponent<Props, State> {
|
||||
@ -52,79 +47,44 @@ class Account extends React.PureComponent<Props, State> {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isInNominate: false,
|
||||
isNomineeValid: false,
|
||||
nonce: new BN(0),
|
||||
nominee: ''
|
||||
isNominateOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
render () {
|
||||
const { className, style } = this.props;
|
||||
const { address, className, intentions, isValidator, name, nominating, style } = this.props;
|
||||
const { isNominateOpen } = this.state;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes('staking--Account', className)}
|
||||
style={style}
|
||||
>
|
||||
{this.renderAccount()}
|
||||
{this.renderButtons()}
|
||||
{this.renderNominate()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderAccount () {
|
||||
const { address, isValidator, name, t } = this.props;
|
||||
// TODO: Still now sure what the best layout is, so just keeping the shortened around
|
||||
// const addrShort = `${address.slice(0, 7)}…${address.slice(-7)}`;
|
||||
|
||||
return (
|
||||
<div className='staking--Account-details'>
|
||||
<div>
|
||||
<Nominating
|
||||
isOpen={isNominateOpen}
|
||||
onClose={this.toggleNominate}
|
||||
onNominate={this.nominate}
|
||||
intentions={intentions}
|
||||
/>
|
||||
<AddressSummary
|
||||
name={name}
|
||||
value={address}
|
||||
>
|
||||
<Icon
|
||||
className={classes('staking--Account-validating', isValidator ? 'isValidator' : '')}
|
||||
name='certificate'
|
||||
size='large'
|
||||
/>
|
||||
<IdentityIcon
|
||||
className='staking--Account-icon'
|
||||
size={32}
|
||||
value={address}
|
||||
/>
|
||||
<div className='staking--Account-info'>
|
||||
<div className='staking--Account-name'>{name}</div>
|
||||
<div className='staking--Account-address'>{address}</div>
|
||||
</div>
|
||||
</div>
|
||||
<RxBalance
|
||||
className='staking--Account-balance'
|
||||
label={t('account.balance', {
|
||||
defaultValue: 'balance '
|
||||
})}
|
||||
params={address}
|
||||
/>
|
||||
<RxNonce
|
||||
className='staking--Account-nonce'
|
||||
onChange={this.onChangeNonce}
|
||||
params={address}
|
||||
>
|
||||
{t('account.transactions', {
|
||||
defaultValue: ' transactions'
|
||||
})}
|
||||
</RxNonce>
|
||||
{this.renderButtons()}
|
||||
<AddressMini value={nominating} />
|
||||
</AddressSummary>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderButtons () {
|
||||
const { isInNominate } = this.state;
|
||||
|
||||
if (isInNominate) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { address, isIntending, nominating, t } = this.props;
|
||||
const { address, intentions, nominating, t } = this.props;
|
||||
const isIntending = intentions.includes(address);
|
||||
const isNominating = !!nominating;
|
||||
const canStake = !isIntending && !isNominating;
|
||||
|
||||
@ -181,60 +141,13 @@ class Account extends React.PureComponent<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
private renderNominate () {
|
||||
const { isInNominate, isNomineeValid, nominee } = this.state;
|
||||
|
||||
if (!isInNominate) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { t } = this.props;
|
||||
|
||||
return (
|
||||
<div className='staking--Account-nominate'>
|
||||
<div className='ui--row'>
|
||||
<Input
|
||||
className='medium'
|
||||
isError={!isNomineeValid}
|
||||
label={t('nominator.address', {
|
||||
defaultValue: 'nominate the following validator'
|
||||
})}
|
||||
onChange={this.onChangeNominee}
|
||||
value={nominee}
|
||||
/>
|
||||
</div>
|
||||
<div className='ui--row'>
|
||||
<div className='medium'>
|
||||
<Button.Group>
|
||||
<Button
|
||||
onClick={this.toggleNominate}
|
||||
text={t('nominators.discard', {
|
||||
defaultValue: 'Cancel'
|
||||
})}
|
||||
/>
|
||||
<Button
|
||||
isDisabled={!isNomineeValid}
|
||||
isPrimary
|
||||
onClick={this.nominate}
|
||||
text={t('nominator.nominate', {
|
||||
defaultValue: 'nominate'
|
||||
})}
|
||||
/>
|
||||
</Button.Group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private send (extrinsic: SectionItem<Extrinsics>, values: Array<RawParam$Value>) {
|
||||
const { address, queueExtrinsic } = this.props;
|
||||
const { nonce } = this.state;
|
||||
const { accountIndex = new BN(0), address, queueExtrinsic } = this.props;
|
||||
const publicKey = decodeAddress(address);
|
||||
|
||||
queueExtrinsic({
|
||||
extrinsic,
|
||||
nonce,
|
||||
nonce: accountIndex,
|
||||
publicKey,
|
||||
values
|
||||
});
|
||||
@ -242,24 +155,13 @@ class Account extends React.PureComponent<Props, State> {
|
||||
|
||||
private toggleNominate = () => {
|
||||
this.setState(
|
||||
({ isInNominate }: State) => ({
|
||||
isInNominate: !isInNominate
|
||||
({ isNominateOpen }: State) => ({
|
||||
isNominateOpen: !isNominateOpen
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private onChangeNominee = (nominee: string) => {
|
||||
const { validators } = this.props;
|
||||
|
||||
this.setState({
|
||||
isNomineeValid: validators.includes(nominee),
|
||||
nominee
|
||||
});
|
||||
}
|
||||
|
||||
private nominate = () => {
|
||||
const { nominee } = this.state;
|
||||
|
||||
private nominate = (nominee: string) => {
|
||||
this.send(extrinsics.staking.public.nominate, [nominee]);
|
||||
|
||||
this.toggleNominate();
|
||||
@ -274,13 +176,9 @@ class Account extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
private unstake = () => {
|
||||
const { intentionPosition } = this.props;
|
||||
const { address, intentions } = this.props;
|
||||
|
||||
this.send(extrinsics.staking.public.unstake, [intentionPosition]);
|
||||
}
|
||||
|
||||
private onChangeNonce = (nonce: BN) => {
|
||||
this.setState({ nonce });
|
||||
this.send(extrinsics.staking.public.unstake, [intentions.indexOf(address)]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -303,5 +201,12 @@ export default withMulti(
|
||||
paramProp: 'address',
|
||||
transform: encodeAddress
|
||||
}
|
||||
),
|
||||
withStorage(
|
||||
storage.system.public.accountIndexOf,
|
||||
{
|
||||
propName: 'accountIndex',
|
||||
paramProp: 'address'
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
133
packages/app-staking/src/Nominating.tsx
Normal file
133
packages/app-staking/src/Nominating.tsx
Normal file
@ -0,0 +1,133 @@
|
||||
// Copyright 2017-2018 @polkadot/ui-signer authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { I18nProps } from '@polkadot/ui-app/types';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import Button from '@polkadot/ui-app/Button';
|
||||
import Input from '@polkadot/ui-app/Input';
|
||||
import Modal from '@polkadot/ui-app/Modal';
|
||||
import classes from '@polkadot/ui-app/util/classes';
|
||||
|
||||
import translate from './translate';
|
||||
|
||||
type Props = I18nProps & {
|
||||
isOpen: boolean,
|
||||
onClose: () => void,
|
||||
onNominate: (nominee: string) => void,
|
||||
intentions: Array<string>
|
||||
};
|
||||
|
||||
type State = {
|
||||
isNomineeValid: boolean,
|
||||
nominee: string
|
||||
};
|
||||
|
||||
class Nominating extends React.PureComponent<Props> {
|
||||
state: State;
|
||||
|
||||
constructor (props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isNomineeValid: false,
|
||||
nominee: ''
|
||||
};
|
||||
}
|
||||
|
||||
render () {
|
||||
const { className, isOpen, style } = this.props;
|
||||
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className={classes('ui--signer-Signer', className)}
|
||||
dimmer='inverted'
|
||||
open
|
||||
style={style}
|
||||
>
|
||||
{this.renderContent()}
|
||||
{this.renderButtons()}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
renderButtons () {
|
||||
const { onClose, t } = this.props;
|
||||
const { isNomineeValid } = this.state;
|
||||
|
||||
return (
|
||||
<Modal.Actions>
|
||||
<Button.Group>
|
||||
<Button
|
||||
isNegative
|
||||
onClick={onClose}
|
||||
text={t('nominators.cancel', {
|
||||
defaultValue: 'Cancel'
|
||||
})}
|
||||
/>
|
||||
<Button.Or />
|
||||
<Button
|
||||
isDisabled={!isNomineeValid}
|
||||
isPrimary
|
||||
onClick={this.nominate}
|
||||
text={t('nominator.nominate', {
|
||||
defaultValue: 'nominate'
|
||||
})}
|
||||
/>
|
||||
</Button.Group>
|
||||
</Modal.Actions>
|
||||
);
|
||||
}
|
||||
|
||||
renderContent () {
|
||||
const { t } = this.props;
|
||||
const { isNomineeValid, nominee } = this.state;
|
||||
|
||||
return [
|
||||
<Modal.Header key='header'>
|
||||
{t('nominator.header', {
|
||||
defaultValue: 'Nominate Validator'
|
||||
})}
|
||||
</Modal.Header>,
|
||||
<Modal.Content className='ui--signer-Signer-Content' key='content'>
|
||||
<Input
|
||||
className='medium'
|
||||
isError={!isNomineeValid}
|
||||
label={t('nominator.address', {
|
||||
defaultValue: 'nominate the following address (validator or intention)'
|
||||
})}
|
||||
onChange={this.onChangeNominee}
|
||||
value={nominee}
|
||||
/>
|
||||
</Modal.Content>
|
||||
];
|
||||
}
|
||||
|
||||
private onChangeNominee = (nominee: string) => {
|
||||
const { intentions } = this.props;
|
||||
|
||||
this.setState({
|
||||
isNomineeValid: intentions.includes(nominee),
|
||||
nominee
|
||||
});
|
||||
}
|
||||
|
||||
private nominate = () => {
|
||||
const { onClose, onNominate } = this.props;
|
||||
const { nominee } = this.state;
|
||||
|
||||
if (!nominee) {
|
||||
onClose();
|
||||
} else {
|
||||
onNominate(nominee);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(Nominating);
|
||||
@ -36,13 +36,11 @@ class StakeList extends React.PureComponent<Props> {
|
||||
return (
|
||||
<Account
|
||||
address={address}
|
||||
intentionPosition={intentions.indexOf(address)}
|
||||
isIntending={intentions.includes(address)}
|
||||
intentions={intentions}
|
||||
isValidator={validators.includes(address)}
|
||||
key={address}
|
||||
name={name}
|
||||
queueExtrinsic={queueExtrinsic}
|
||||
validators={validators}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@ -12,12 +12,15 @@
|
||||
}
|
||||
|
||||
.staking--Account {
|
||||
margin-bottom: 1rem;
|
||||
background: #f9f9f9;
|
||||
border-radius: 0.25rem;
|
||||
display: inline-block;
|
||||
margin: 1rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.staking--Account .ui--Button-Group {
|
||||
display: inline-block;
|
||||
margin-top: 1.25rem;
|
||||
margin-left: 1em;
|
||||
vertical-align: top;
|
||||
}
|
||||
@ -37,6 +40,18 @@
|
||||
padding: 0 0 3em 3em;
|
||||
}
|
||||
|
||||
.staking--Account-nominating {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.staking--Account-nominating * {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.staking--Account-nominating .ui--IdentityIcon {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.staking--Account-info {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
43
packages/ui-app/src/AddressMini.tsx
Normal file
43
packages/ui-app/src/AddressMini.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
// Copyright 2017-2018 @polkadot/app-staking authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { BareProps } from '@polkadot/ui-app/types';
|
||||
|
||||
import React from 'react';
|
||||
import IdentityIcon from '@polkadot/ui-react/IdentityIcon';
|
||||
|
||||
import classes from './util/classes';
|
||||
import translate from './translate';
|
||||
|
||||
type Props = BareProps & {
|
||||
isShort?: boolean,
|
||||
value?: string
|
||||
};
|
||||
|
||||
class AddressMini extends React.PureComponent<Props> {
|
||||
render () {
|
||||
const { className, isShort = true, style, value } = this.props;
|
||||
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const shortValue = `${value.slice(0, 7)}…${value.slice(-7)}`;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes('staking--Account-nominating', className)}
|
||||
style={style}
|
||||
>
|
||||
<IdentityIcon
|
||||
size={24}
|
||||
value={value}
|
||||
/>
|
||||
{isShort ? shortValue : value}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(AddressMini);
|
||||
131
packages/ui-app/src/AddressSummary.tsx
Normal file
131
packages/ui-app/src/AddressSummary.tsx
Normal file
@ -0,0 +1,131 @@
|
||||
// Copyright 2017-2018 @polkadot/ui-app authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { I18nProps } from '@polkadot/ui-app/types';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import IdentityIcon from '@polkadot/ui-react/IdentityIcon';
|
||||
import Balance from '@polkadot/ui-react-rx/Balance';
|
||||
import Nonce from '@polkadot/ui-react-rx/Nonce';
|
||||
import addressDecode from '@polkadot/util-keyring/address/decode';
|
||||
import addressEncode from '@polkadot/util-keyring/address/encode';
|
||||
|
||||
import classes from './util/classes';
|
||||
import CopyButton from './CopyButton';
|
||||
import translate from './translate';
|
||||
|
||||
type Props = I18nProps & {
|
||||
children?: React.ReactNode,
|
||||
name?: string,
|
||||
value: string,
|
||||
withBalance?: boolean,
|
||||
withNonce?: boolean
|
||||
};
|
||||
|
||||
type State = {
|
||||
address: string,
|
||||
isValid: boolean,
|
||||
publicKey: Uint8Array | null,
|
||||
shortValue: string
|
||||
};
|
||||
|
||||
const DEFAULT_ADDR = '5'.padEnd(16, 'x');
|
||||
const DEFAULT_SHORT = `${DEFAULT_ADDR.slice(0, 7)}…${DEFAULT_ADDR.slice(-7)}`;
|
||||
|
||||
class AddressSummary extends React.PureComponent<Props, State> {
|
||||
state: State = {} as State;
|
||||
|
||||
static getDerivedStateFromProps ({ value }: Props, { address, publicKey, shortValue }: State): State {
|
||||
try {
|
||||
publicKey = addressDecode(value);
|
||||
address = addressEncode(publicKey);
|
||||
shortValue = `${address.slice(0, 7)}…${address.slice(-7)}`;
|
||||
} catch (error) {
|
||||
publicKey = null;
|
||||
}
|
||||
|
||||
const isValid = !!publicKey && publicKey.length === 32;
|
||||
|
||||
return {
|
||||
address: isValid ? address : DEFAULT_ADDR,
|
||||
isValid,
|
||||
publicKey,
|
||||
shortValue: isValid ? shortValue : DEFAULT_SHORT
|
||||
};
|
||||
}
|
||||
|
||||
render () {
|
||||
const { children, className, name, style } = this.props;
|
||||
const { address, isValid, shortValue } = this.state;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes('ui--AddressSummary', !isValid && 'invalid', className)}
|
||||
style={style}
|
||||
>
|
||||
<div className='ui--AddressSummary-base'>
|
||||
<IdentityIcon
|
||||
className='ui--AddressSummary-icon'
|
||||
size={96}
|
||||
value={address}
|
||||
/>
|
||||
<div className='ui--AddressSummary-data'>
|
||||
<div className='ui--AddressSummary-name'>
|
||||
{name}
|
||||
</div>
|
||||
<div className='ui--AddressSummary-address'>
|
||||
{shortValue}
|
||||
</div>
|
||||
<CopyButton value={address} />
|
||||
</div>
|
||||
{this.renderBalance()}
|
||||
</div>
|
||||
<div className='ui--AddressSummary-children'>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderBalance () {
|
||||
const { isValid, publicKey } = this.state;
|
||||
|
||||
if (!isValid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { t, withBalance = true, withNonce = true } = this.props;
|
||||
|
||||
return [
|
||||
withBalance
|
||||
? (
|
||||
<Balance
|
||||
className='ui--AddressSummary-balance'
|
||||
key='balance'
|
||||
label={t('addressSummary.balance', {
|
||||
defaultValue: 'balance '
|
||||
})}
|
||||
params={publicKey}
|
||||
/>
|
||||
)
|
||||
: null,
|
||||
withNonce
|
||||
? (
|
||||
<Nonce
|
||||
className='ui--AddressSummary-nonce'
|
||||
key='nonce'
|
||||
params={publicKey}
|
||||
>
|
||||
{t('addressSummary.transactions', {
|
||||
defaultValue: ' transactions'
|
||||
})}
|
||||
</Nonce>
|
||||
)
|
||||
: null
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(AddressSummary);
|
||||
@ -30,3 +30,54 @@
|
||||
.ui--Button-Group .button {
|
||||
border-radius: .28571429rem !important;
|
||||
}
|
||||
|
||||
.ui--AddressSummary {
|
||||
padding: 0 2em;
|
||||
text-align: center;
|
||||
white-space: nowrap
|
||||
}
|
||||
|
||||
.ui--AddressSummary .ui--AddressSummary-base,
|
||||
.ui--AddressSummary .ui--AddressSummary-children {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.ui--AddressSummary .rx--updated {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.ui--AddressSummary.invalid {
|
||||
filter: grayscale(100);
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
.ui--AddressSummary-icon {
|
||||
display: block
|
||||
}
|
||||
|
||||
.ui--AddressSummary-balance,
|
||||
.ui--AddressSummary-nonce {
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
.ui--AddressSummary-balance.rx--updated,
|
||||
.ui--AddressSummary-nonce.rx--updated {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.ui--AddressSummary-data {
|
||||
padding: 0.5em 0;
|
||||
}
|
||||
|
||||
.ui--AddressSummary-data * {
|
||||
margin: 0 0.25rem !important;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.ui--AddressSummary-address {
|
||||
display: inline-block;
|
||||
font-family: monospace;
|
||||
font-size: 1.5em;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user