Skip to content

Commit

Permalink
Send button on account (#1193)
Browse files Browse the repository at this point in the history
* starting point

* show all accounts

* edit and save a name but remains ugly

* actually don't use deriveStateFromProps

* fix AdressRow

* indentation

* upperCase

* bring back the index

* trim input

* change edition l&f

* lint

* cater for small screens

* address comments

* lint and alphabetical order

* allow viewing and editing tags

* better UX when tag editing and styling for no tags

* nonce refactoring and show balances

* crypto type

* styling balances

* use css grid

* fix address row and creator

* with buttons

* button styling

* locked tooltip

* edit button always visible

* init

* fix conflicts

* transfer functionnality

* styling the modal

* clean up

* clean up bis

* Transfer dialog enhancements (#1196)

* Button/TxButton submit on enter (#1170)

* Button/TxButton submit on enter

* promise linting

* testing ref approach

* Add TxComponent to handle submit on enter

* remove accountNonce

* remove input ref

* remove TxButton constructor

* remove comment

* empty constructor

* linting

* bug with null ref

* linting

* [CI Skip]  0.33.0-beta.74

* Redirect correctly when user has no account (#1191)

* right link and hide tabs plus buttons primary

* fix comments

* create highlight

* [CI Skip]  0.33.0-beta.75

* Mark ExtrinsicFailed as error (#1190)

* Hide edit on injected accounts, Button w/ tooltip (#1192)

* Hide edit on injected accounts, Button w/ tooltip

* Don't display "add tags" when not isEditable

* Indentation

* [CI Skip]  0.33.0-beta.76

* Make enable() call to extensions (#1194)

* Make enable() call to extensions

* It works

* Add overlay for injected (prompt)

* Use signer from injectedPromise

* [CI Skip]  0.33.0-beta.77

* withInjected -> PureComponent (fix new refs) (#1195)

* withInjected -> PureComponent (fix new refs)

* Consume data from context

* Expand injected

* Close modal on tx start

* align fees

* remove blank lines
  • Loading branch information
Tbaut authored May 24, 2019
1 parent 3e929c1 commit fc59f53
Show file tree
Hide file tree
Showing 7 changed files with 320 additions and 33 deletions.
79 changes: 54 additions & 25 deletions packages/app-accounts/src/Account.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import { I18nProps } from '@polkadot/ui-app/types';

import React from 'react';
import styled from 'styled-components';
import { AddressSummary, Available, Balance, Bonded, Button, CryptoType, Nonce, Unlocking } from '@polkadot/ui-app';
import { AddressSummary, Available, Balance, Bonded, Button, CryptoType, Icon, Nonce, Unlocking } from '@polkadot/ui-app';
import keyring from '@polkadot/ui-keyring';

import Backup from './modals/Backup';
import ChangePass from './modals/ChangePass';
import Forgetting from './modals/Forgetting';
import Transfer from './modals/Transfer';

import translate from './translate';

type Props = I18nProps & {
Expand All @@ -23,7 +25,8 @@ type State = {
isBackupOpen: boolean,
isEditable: boolean,
isForgetOpen: boolean,
isPasswordOpen: boolean
isPasswordOpen: boolean,
isTransferOpen: boolean
};

const Wrapper = styled.article`
Expand Down Expand Up @@ -111,7 +114,8 @@ class Account extends React.PureComponent<Props> {
isBackupOpen: false,
isEditable: !(keyring.getAccount(props.address).getMeta().isInjected),
isForgetOpen: false,
isPasswordOpen: false
isPasswordOpen: false,
isTransferOpen: false
};
}

Expand Down Expand Up @@ -187,7 +191,7 @@ class Account extends React.PureComponent<Props> {

private renderModals () {
const { address } = this.props;
const { isBackupOpen, isForgetOpen, isPasswordOpen } = this.state;
const { isBackupOpen, isForgetOpen, isPasswordOpen, isTransferOpen } = this.state;

if (!address) {
return null;
Expand Down Expand Up @@ -226,6 +230,16 @@ class Account extends React.PureComponent<Props> {
);
}

if (isTransferOpen) {
modals.push(
<Transfer
address={address}
key='modal-transfer'
onClose={this.toggleTransfer}
/>
);
}

return modals;
}

Expand Down Expand Up @@ -253,6 +267,14 @@ class Account extends React.PureComponent<Props> {
});
}

private toggleTransfer = (): void => {
const { isTransferOpen } = this.state;

this.setState({
isTransferOpen: !isTransferOpen
});
}

private onForget = (): void => {
const { address, t } = this.props;

Expand Down Expand Up @@ -314,32 +336,39 @@ class Account extends React.PureComponent<Props> {
const { t } = this.props;
const { isEditable } = this.state;

if (!isEditable) {
return null;
}

return (
<div className='accounts--Account-buttons'>
{isEditable && (
<>
<Button
isNegative
onClick={this.toggleForget}
icon='trash'
size='small'
tooltip={t('Forget this account')}
/>
<Button
icon='cloud download'
isPrimary
onClick={this.toggleBackup}
size='small'
tooltip={t('Create a backup file for this account')}
/>
<Button
icon='key'
isPrimary
onClick={this.togglePass}
size='small'
tooltip={t("Change this account's password")}
/>
</>
)}
<Button
isNegative
onClick={this.toggleForget}
icon='trash'
size='small'
tooltip={t('Forget this account')}
/>
<Button
icon='cloud download'
isPrimary
onClick={this.toggleBackup}
size='small'
tooltip={t('Create a backup file for this account')}
/>
<Button
icon='key'
isPrimary
onClick={this.togglePass}
label={<><Icon name='paper plane' /> {t('send')}</>}
onClick={this.toggleTransfer}
size='small'
tooltip={t("Change this account's password")}
tooltip={t('Send funds from this account')}
/>
</div>
);
Expand Down
247 changes: 247 additions & 0 deletions packages/app-accounts/src/modals/Transfer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
// Copyright 2017-2019 @polkadot/app-accounts 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 { ApiProps } from '@polkadot/ui-api/types';
import { DerivedFees, DerivedBalances } from '@polkadot/api-derive/types';
import { I18nProps } from '@polkadot/ui-app/types';
import { SubmittableExtrinsic } from '@polkadot/api/promise/types';

import BN from 'bn.js';
import React from 'react';
import styled from 'styled-components';
import { Button, InputAddress, InputBalance, Modal, TxButton } from '@polkadot/ui-app';
import Checks, { calcSignatureLength } from '@polkadot/ui-signer/Checks';
import { withApi, withCalls, withMulti } from '@polkadot/ui-api';
import { ZERO_FEES } from '@polkadot/ui-signer/Checks/constants';

import translate from '../translate';

type Props = ApiProps & I18nProps & {
address: string,
balances_fees?: DerivedFees,
balances_votingBalance?: DerivedBalances,
onClose: () => void,
system_accountNonce?: BN
};

type State = {
amount: BN,
extrinsic: SubmittableExtrinsic | null,
hasAvailable: boolean,
maxBalance?: BN,
recipientId: string | null
};

const ZERO = new BN(0);

const Wrapper = styled.div`
article.padded {
box-shadow: none;
margin: .75rem 15rem;
padding: 0;
}
label.with-help {
flex-basis: 10rem;
}
.ui--Labelled-content {
flex: initial;
width: 40em;
}
`;

class Transfer extends React.PureComponent<Props> {
state: State = {
amount: ZERO,
extrinsic: null,
hasAvailable: true,
maxBalance: ZERO,
recipientId: null
};

componentDidUpdate (prevProps: Props, prevState: State) {
const { balances_fees } = this.props;
const { extrinsic, recipientId } = this.state;

const hasLengthChanged = ((extrinsic && extrinsic.encodedLength) || 0) !== ((prevState.extrinsic && prevState.extrinsic.encodedLength) || 0);

if ((recipientId && prevState.recipientId !== recipientId) ||
(balances_fees !== prevProps.balances_fees) ||
hasLengthChanged
) {
this.setMaxBalance();
}
}

render () {
return (
<Modal
className='app--accounts-Modal'
dimmer='inverted'
open
>
{this.renderContent()}
{this.renderButtons()}
</Modal>
);
}

private nextState (newState: Partial<State>): void {
this.setState((prevState: State): State => {
const { api } = this.props;
const { amount = prevState.amount, recipientId = prevState.recipientId, hasAvailable = prevState.hasAvailable, maxBalance = prevState.maxBalance } = newState;
const extrinsic = recipientId
? api.tx.balances.transfer(recipientId, amount)
: null;

return {
amount,
extrinsic,
hasAvailable,
maxBalance,
recipientId
};
});
}

private renderButtons () {
const { address, onClose, t } = this.props;
const { extrinsic, hasAvailable } = this.state;

return (
<Modal.Actions>
<Button.Group>
<Button
isNegative
label={t('Cancel')}
onClick={onClose}
/>
<Button.Or />
<TxButton
accountId={address}
extrinsic={extrinsic}
isDisabled={!hasAvailable}
isPrimary
label={t('Make Transfer')}
onStart={onClose}
withSpinner={false}
/>
</Button.Group>
</Modal.Actions>
);
}

private renderContent () {
const { address, t } = this.props;
const { extrinsic, hasAvailable, maxBalance } = this.state;

return (
<>
<Modal.Header>
{t('Send funds')}
</Modal.Header>
<Modal.Content className='app--account-Backup-content'>
<Wrapper className='account--Transfer-data'>
<InputAddress
defaultValue={address}
help={t('The account you will send funds from.')}
isDisabled
label={t('from')}
type='account'
/>
<InputAddress
defaultValue={'5xxxxxxxxxxxxxxx'}
help={t('Select a contact or paste the address you want to send funds to.')}
label={t('to')}
onChange={this.onChangeTo}
type='all'
/>
<InputBalance
help={t('Type the amount you want to transfer. Note that you can select the unit on the right e.g sending 1 mili is equivalent to sending 0.001.')}
isError={!hasAvailable}
label={t('amount')}
maxValue={maxBalance}
onChange={this.onChangeAmount}
withMax
/>
<Checks
accountId={address}
extrinsic={extrinsic}
isSendable
onChange={this.onChangeFees}
/>
</Wrapper>
</Modal.Content>
</>
);
}

private onChangeAmount = (amount: BN = new BN(0)) => {
this.nextState({ amount });
}

private onChangeTo = (recipientId: string) => {
this.nextState({ recipientId });
}

private onChangeFees = (hasAvailable: boolean) => {
this.setState({ hasAvailable });
}

private setMaxBalance = () => {
const { address, api, balances_fees = ZERO_FEES } = this.props;
const { recipientId } = this.state;

if (!address || !recipientId) {
return;
}

void api.query.system.accountNonce(address, (accountNonce) => {

const { transferFee, transactionBaseFee, transactionByteFee, creationFee } = balances_fees;

void api.derive.balances.all(address, ({ availableBalance: senderBalance }) => {
void api.derive.balances.all(recipientId, ({ availableBalance: recipientBalance }) => {

let prevMax = new BN(0);
let maxBalance = new BN(1);
let extrinsic;

while (!prevMax.eq(maxBalance)) {
prevMax = maxBalance;

extrinsic = address && recipientId
? api.tx.balances.transfer(recipientId, prevMax)
: null;

const txLength = calcSignatureLength(extrinsic, accountNonce);

const fees = transactionBaseFee
.add(transactionByteFee.muln(txLength))
.add(transferFee)
.add(senderBalance.isZero() ? creationFee : ZERO)
.add(recipientBalance.isZero() ? creationFee : ZERO);

maxBalance = new BN(senderBalance).sub(fees);
}

this.nextState({
extrinsic,
maxBalance
});
});
});
});
}
}

export default withMulti(
Transfer,
translate,
withApi,
withCalls<Props>(
'derive.balances.fees'
)
);
Loading

0 comments on commit fc59f53

Please sign in to comment.