diff --git a/package.json b/package.json index 80aadd95e3c6..05bb6ea09a1b 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,10 @@ "packages/*" ], "resolutions": { - "@polkadot/api": "^0.99.0-beta.13", - "@polkadot/api-contract": "^0.99.0-beta.13", + "@polkadot/api": "^0.99.0-beta.15", + "@polkadot/api-contract": "^0.99.0-beta.15", "@polkadot/keyring": "^1.7.1", - "@polkadot/types": "^0.99.0-beta.13", + "@polkadot/types": "^0.99.0-beta.15", "@polkadot/util": "^1.7.1", "@polkadot/util-crypto": "^1.7.1", "babel-core": "^7.0.0-bridge.0", diff --git a/packages/app-contracts/package.json b/packages/app-contracts/package.json index d966e0e7149b..6f66f6bf5f18 100644 --- a/packages/app-contracts/package.json +++ b/packages/app-contracts/package.json @@ -11,7 +11,7 @@ "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.7.5", - "@polkadot/api-contract": "^0.99.0-beta.13", + "@polkadot/api-contract": "^0.99.0-beta.15", "@polkadot/react-components": "^0.38.0-beta.60" } } diff --git a/packages/app-staking/src/Actions/Account/index.tsx b/packages/app-staking/src/Actions/Account/index.tsx index 55cbf46ebf7f..9deb22334a14 100644 --- a/packages/app-staking/src/Actions/Account/index.tsx +++ b/packages/app-staking/src/Actions/Account/index.tsx @@ -1,57 +1,48 @@ -/* eslint-disable @typescript-eslint/camelcase */ // Copyright 2017-2019 @polkadot/app-staking 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 { DerivedBalances, DerivedStakingAccount, DerivedStakingOverview, DerivedHeartbeats } from '@polkadot/api-derive/types'; -import { ApiProps } from '@polkadot/react-api/types'; import { I18nProps } from '@polkadot/react-components/types'; import { AccountId, Exposure, StakingLedger, ValidatorPrefs } from '@polkadot/types/interfaces'; +import { Codec, ITuple } from '@polkadot/types/types'; -import React from 'react'; +import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; -import { AddressCard, AddressInfo, AddressMini, AddressRow, Button, Menu, Popup, TxButton } from '@polkadot/react-components'; -import { withCalls, withMulti } from '@polkadot/react-api'; +import { AddressInfo, AddressMini, AddressSmall, Button, Menu, Popup, TxButton } from '@polkadot/react-components'; +import { useApi, useCall, useToggle } from '@polkadot/react-hooks'; +import { u8aConcat, u8aToHex } from '@polkadot/util'; +import translate from '../../translate'; import BondExtra from './BondExtra'; import InjectKeys from './InjectKeys'; import Nominate from './Nominate'; import SetControllerAccount from './SetControllerAccount'; import SetRewardDestination from './SetRewardDestination'; import SetSessionKey from './SetSessionKey'; -import translate from '../../translate'; import Unbond from './Unbond'; import Validate from './Validate'; -import { u8aToHex, u8aConcat } from '@polkadot/util'; -interface Props extends ApiProps, I18nProps { +type ValidatorInfo = ITuple<[ValidatorPrefs, Codec]>; + +interface Props extends I18nProps { allStashes?: string[]; - balances_all?: DerivedBalances; - className?: string; isOwnStash: boolean; next: string[]; + onUpdateType: (stashId: string, type: 'validator' | 'nominator' | 'started' | 'other') => void; recentlyOnline?: DerivedHeartbeats; - staking_account?: DerivedStakingAccount; stakingOverview?: DerivedStakingOverview; stashId: string; } -interface State { +interface StakeState { controllerId: string | null; destination: number; hexSessionIdNext: string | null; hexSessionIdQueue: string | null; - isBondExtraOpen: boolean; - isInjectOpen: boolean; - isNominateOpen: boolean; - isSetControllerAccountOpen: boolean; - isSetRewardDestinationOpen: boolean; - isSetSessionAccountOpen: boolean; - isSettingPopupOpen: boolean; + isLoading: boolean; isStashNominating: boolean; isStashValidating: boolean; - isUnbondOpen: boolean; - isValidateOpen: boolean; nominees?: string[]; sessionIds: string[]; stakers?: Exposure; @@ -59,552 +50,312 @@ interface State { validatorPrefs?: ValidatorPrefs; } -const DEFAULT_BALANCES = { - available: true, - bonded: false, - total: false, - redeemable: false, - unlocking: false -}; - -const CONTROLLER_BALANCES = { - available: true, - bonded: false, - free: false, - redeemable: false, - unlocking: false -}; - function toIdString (id?: AccountId | null): string | null { return id ? id.toString() : null; } -class Account extends React.PureComponent { - public state: State = { - controllerId: null, - destination: 0, - hexSessionIdNext: null, - hexSessionIdQueue: null, - isBondExtraOpen: false, - isInjectOpen: false, - isNominateOpen: false, - isSetControllerAccountOpen: false, - isSettingPopupOpen: false, - isSetRewardDestinationOpen: false, - isSetSessionAccountOpen: false, - isStashNominating: false, - isStashValidating: false, - isUnbondOpen: false, - isValidateOpen: false, - sessionIds: [] +function getStakeState (allStashes: string[] | undefined, { controllerId, nextSessionIds, nominators, rewardDestination, sessionIds, stakers, stakingLedger, validatorPrefs }: DerivedStakingAccount, stashId: string, validateInfo: ValidatorInfo): StakeState { + const isStashNominating = !!(nominators?.length); + const isStashValidating = !validateInfo[1].isEmpty || !!allStashes?.includes(stashId); + const nextConcat = u8aConcat(...nextSessionIds.map((id): Uint8Array => id.toU8a())); + const currConcat = u8aConcat(...sessionIds.map((id): Uint8Array => id.toU8a())); + + return { + controllerId: toIdString(controllerId), + destination: rewardDestination?.toNumber() || 0, + hexSessionIdNext: u8aToHex(nextConcat, 48), + hexSessionIdQueue: u8aToHex(currConcat.length ? currConcat : nextConcat, 48), + isLoading: false, + isStashNominating, + isStashValidating, + // we assume that all ids are non-null + nominees: nominators?.map(toIdString) as string[], + sessionIds: ( + nextSessionIds.length + ? nextSessionIds + : sessionIds + ).map(toIdString) as string[], + stakers, + stakingLedger, + validatorPrefs }; +} - public static getDerivedStateFromProps ({ allStashes, staking_account, stashId }: Props): Pick | null { - if (!staking_account) { - return null; +function Account ({ allStashes, className, isOwnStash, next, onUpdateType, stakingOverview, stashId, t }: Props): React.ReactElement { + const { api, isSubstrateV2 } = useApi(); + const validateInfo = useCall(api.query.staking.validators, [stashId]); + const balancesAll = useCall(api.derive.balances.all as any, [stashId]); + const stakingAccount = useCall(api.derive.staking.account as any, [stashId]); + const [{ controllerId, destination, hexSessionIdQueue, hexSessionIdNext, isLoading, isStashNominating, isStashValidating, nominees, sessionIds, validatorPrefs }, setStakeState] = useState({ controllerId: null, destination: 0, hexSessionIdNext: null, hexSessionIdQueue: null, isLoading: true, isStashNominating: false, isStashValidating: false, sessionIds: [] }); + const [isBondExtraOpen, toggleBondExtra] = useToggle(); + const [isInjectOpen, toggleInject] = useToggle(); + const [isNominateOpen, toggleNominate] = useToggle(); + const [isRewardDestinationOpen, toggleRewardDestination] = useToggle(); + const [isSetControllerOpen, toggleSetController] = useToggle(); + const [isSetSessionOpen, toggleSetSession] = useToggle(); + const [isSettingsOpen, toggleSettings] = useToggle(); + const [isUnbondOpen, toggleUnbond] = useToggle(); + const [isValidateOpen, toggleValidate] = useToggle(); + + useEffect((): void => { + if (stakingAccount && validateInfo) { + const state = getStakeState(allStashes, stakingAccount, stashId, validateInfo); + + setStakeState(state); + + if (state.isStashValidating) { + onUpdateType(stashId, 'validator'); + } else if (state.isStashNominating) { + onUpdateType(stashId, 'nominator'); + } else { + onUpdateType(stashId, 'other'); + } } + }, [allStashes, stakingAccount, stashId, validateInfo]); - const { controllerId, nextSessionIds, nominators, rewardDestination, sessionIds, stakers, stakingLedger, validatorPrefs } = staking_account; - const isStashNominating = nominators && !!nominators.length; - const isStashValidating = !!allStashes && !!stashId && allStashes.includes(stashId); - const nextConcat = u8aConcat(...nextSessionIds.map((id): Uint8Array => id.toU8a())); - const currConcat = u8aConcat(...sessionIds.map((id): Uint8Array => id.toU8a())); - - return { - controllerId: toIdString(controllerId), - destination: rewardDestination && rewardDestination.toNumber(), - hexSessionIdNext: u8aToHex(nextConcat, 48), - hexSessionIdQueue: u8aToHex(currConcat.length ? currConcat : nextConcat, 48), - isStashNominating, - isStashValidating, - nominees: nominators && nominators.map(toIdString), - sessionIds: ( - nextSessionIds.length - ? nextSessionIds - : sessionIds - ).map(toIdString), - stakers, - stakingLedger, - validatorPrefs - }; - } - - public render (): React.ReactNode { - const { className, isSubstrateV2, stashId, t } = this.props; - const { controllerId, hexSessionIdNext, hexSessionIdQueue, isBondExtraOpen, isInjectOpen, isStashValidating, isUnbondOpen, nominees, sessionIds } = this.state; - - // Each component is rendered and gets a `is[Component]Open` passed in a `isOpen` props. - // These components will be loaded and return null at the first load (because is[Component]Open === false). - // This is deliberate in order to display the Component modals in a performant matter later on - // because their state will already be loaded. - return ( - + return ( + + + {isInjectOpen && ( - + )} - {this.renderSetValidatorPrefs()} - {this.renderNominate()} - {this.renderSetControllerAccount()} - {this.renderSetRewardDestination()} - {this.renderSetSessionAccount()} - {this.renderValidate()} -
-
- {controllerId && ( -
- -
- )} - {!isSubstrateV2 && !!sessionIds.length && ( -
- -
- )} -
-
-
- -
-
-
- {nominees && !!nominees.length && ( -
- {t('Nominating ({{count}})', { replace: { count: nominees.length } })} - {nominees.map((nomineeId, index): React.ReactNode => ( - - ))} -
+ {isNominateOpen && controllerId && ( + )} -
- ); - } - - private renderNominate (): React.ReactNode { - const { next, stakingOverview, stashId } = this.props; - const { controllerId, isNominateOpen, nominees } = this.state; - - if (!isNominateOpen || !stashId || !controllerId) { - return null; - } - - return ( - - ); - } - - private renderValidate (): React.ReactNode { - const { stashId } = this.props; - const { controllerId, isValidateOpen, validatorPrefs } = this.state; - - if (!controllerId) { - return null; - } - - return ( - - ); - } - - private renderButtons (): React.ReactNode { - const { isSubstrateV2, t } = this.props; - const { controllerId, hexSessionIdNext, isSettingPopupOpen, isStashNominating, isStashValidating, sessionIds } = this.state; - const buttons = []; - - // if we are validating/nominating show stop - if (isStashNominating || isStashValidating) { - buttons.push( - - ); - } else { - if (!sessionIds.length || (isSubstrateV2 && hexSessionIdNext === '0x')) { - buttons.push( -