Swap council voting from TxModal (#2141)

* Swap council voting from TxModal

* Cleanup toggles

* Apply account filters

* useCouncilMembers

* Pass filter through to Propose

* Linting
This commit is contained in:
Jaco Greeff 2020-01-23 12:22:10 +01:00 committed by GitHub
parent 0dbe4f927e
commit 9905927862
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 216 additions and 253 deletions

View File

@ -5,19 +5,21 @@
import { DerivedCollectiveProposal } from '@polkadot/api-derive/types';
import React from 'react';
import { AddressMini, Voting } from '@polkadot/react-components';
import { AddressMini } from '@polkadot/react-components';
import ProposalCell from '@polkadot/app-democracy/Overview/ProposalCell';
import { formatNumber } from '@polkadot/util';
import { useTranslation } from '../translate';
import Voting from './Voting';
interface Props {
className?: string;
isMember: boolean;
members: string[];
motion: DerivedCollectiveProposal;
}
export default function Motion ({ className, isMember, motion: { hash, proposal, votes } }: Props): React.ReactElement<Props> | null {
export default function Motion ({ className, isMember, members, motion: { hash, proposal, votes } }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
if (!votes) {
@ -61,9 +63,9 @@ export default function Motion ({ className, isMember, motion: { hash, proposal,
<td className='number top together'>
<Voting
hash={hash}
isCouncil
isDisabled={!isMember}
idNumber={index}
isDisabled={!isMember}
members={members}
proposal={proposal}
/>
</td>

View File

@ -7,8 +7,7 @@ import { Call, Proposal } from '@polkadot/types/interfaces';
import BN from 'bn.js';
import React from 'react';
import { registry } from '@polkadot/react-api';
import { withCalls, withMulti } from '@polkadot/react-api/hoc';
import { registry, withApi, withMulti } from '@polkadot/react-api';
import { Button, Extrinsic, InputNumber } from '@polkadot/react-components';
import TxModal, { TxModalState, TxModalProps } from '@polkadot/react-components/TxModal';
import { createType } from '@polkadot/types';
@ -17,7 +16,7 @@ import translate from '../translate';
interface Props extends TxModalProps, ApiProps {
isMember: boolean;
memberCount: number;
members: string[];
}
interface State extends TxModalState {
@ -32,16 +31,14 @@ class Propose extends TxModal<Props, State> {
this.defaultState = {
...this.defaultState,
method: null,
threshold: props.memberCount ? new BN((props.memberCount / 2) + 1) : null
threshold: props.members.length ? new BN(Math.ceil(props.members.length * 0.5)) : null
};
this.state = this.defaultState;
}
public static getDerivedStateFromProps ({ memberCount }: Props, { threshold }: State): Pick<State, never> | null {
if (!threshold && memberCount > 0) {
const simpleMajority = new BN((memberCount / 2) + 1);
return { threshold: simpleMajority };
public static getDerivedStateFromProps ({ members }: Props, { threshold }: State): Pick<State, never> | null {
if (!threshold && members.length) {
return { threshold: new BN(Math.ceil(members.length * 0.5)) };
}
return null;
@ -61,10 +58,10 @@ class Propose extends TxModal<Props, State> {
}
protected isDisabled = (): boolean => {
const { memberCount = 0 } = this.props;
const { members } = this.props;
const { accountId, method, threshold } = this.state;
const hasThreshold = !!threshold && threshold.gtn(0) && threshold.ltn(memberCount + 1);
const hasThreshold = !!threshold && threshold.gtn(0) && threshold.ltn(members.length + 1);
const hasMethod = !!method;
return !accountId || !hasMethod || !hasThreshold;
@ -85,7 +82,7 @@ class Propose extends TxModal<Props, State> {
}
protected renderContent = (): React.ReactNode => {
const { apiDefaultTxSudo, memberCount = 0, t } = this.props;
const { apiDefaultTxSudo, members, t } = this.props;
const { threshold } = this.state;
return (
@ -94,13 +91,13 @@ class Propose extends TxModal<Props, State> {
className='medium'
label={t('threshold')}
help={t('The minimum number of council votes required to approve this motion')}
isError={!threshold || threshold.eqn(0) || threshold.gtn(memberCount)}
isError={!threshold || threshold.eqn(0) || threshold.gtn(members.length)}
onChange={this.onChangeThreshold}
onEnter={this.sendTx}
placeholder={
t(
'Positive number between 1 and {{memberCount}}',
{ replace: { memberCount } }
{ replace: { memberCount: members.length } }
)
}
value={threshold || new BN(0)}
@ -116,10 +113,10 @@ class Propose extends TxModal<Props, State> {
}
private onChangeThreshold = (threshold: BN | null = null): void => {
const { memberCount = 0 } = this.props;
const { members } = this.props;
if (memberCount > 0 && !this.defaultState.threshold) {
this.defaultState.threshold = new BN((memberCount / 2) + 1);
if (members.length && !this.defaultState.threshold) {
this.defaultState.threshold = new BN(Math.ceil(members.length * 0.5));
}
this.setState({ threshold });
@ -135,14 +132,6 @@ class Propose extends TxModal<Props, State> {
}
export default withMulti(
Propose,
translate,
withCalls(
['query.electionsPhragmen.members', {
fallbacks: ['query.elections.members'],
propName: 'memberCount',
transform: (value: any[]): number =>
value.length
}]
)
translate(Propose),
withApi
);

View File

@ -2,12 +2,11 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { AccountId } from '@polkadot/types/interfaces';
import { CallFunction } from '@polkadot/types/types';
import React, { useEffect, useState } from 'react';
import { Button, Dropdown, Input, InputAddress, Modal, TxButton } from '@polkadot/react-components';
import { useApi, useCall, useToggle } from '@polkadot/react-hooks';
import { useApi, useToggle } from '@polkadot/react-hooks';
import { useTranslation } from '../translate';
import useAvailableSlashes from './useAvailableSlashes';
@ -15,6 +14,7 @@ import useAvailableSlashes from './useAvailableSlashes';
interface Props {
className?: string;
isMember: boolean;
members: string[];
}
interface Option {
@ -22,28 +22,16 @@ interface Option {
value: number;
}
export default function Slashing ({ className, isMember }: Props): React.ReactElement<Props> {
export default function Slashing ({ className, isMember, members }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const members = useCall<AccountId[]>(api.query.electionsPhragmen?.members || api.query.elections.members, []);
const slashes = useAvailableSlashes();
const [isVisible, toggleVisible] = useToggle();
const [isSelectedMember, setIsMember] = useState(false);
const [accountId, setAcountId] = useState<string | null>(null);
const [proposal, setProposal] = useState<CallFunction | null>(null);
const [eras, setEras] = useState<Option[]>([]);
const [selectedEra, setSelectedEra] = useState(0);
const threshold = Math.ceil((members?.length || 0) * 0.5);
useEffect((): void => {
if (accountId && members) {
setIsMember(
members
.map(([accountId]): string => accountId.toString())
.includes(accountId)
);
}
}, [accountId, members]);
const threshold = Math.ceil((members.length || 0) * 0.5);
useEffect((): void => {
if (slashes?.length) {
@ -77,7 +65,7 @@ export default function Slashing ({ className, isMember }: Props): React.ReactEl
<>
<Button
icon='cancel'
isDisabled={!isMember || !members?.length || !slashes.length}
isDisabled={!isMember || !members.length || !slashes.length}
isPrimary
label={t('Cancel slashes')}
onClick={toggleVisible}
@ -89,6 +77,7 @@ export default function Slashing ({ className, isMember }: Props): React.ReactEl
>
<Modal.Content>
<InputAddress
filter={members}
help={t('Select the account you wish to make the proposal with.')}
label={t('propose from account')}
onChange={setAcountId}
@ -118,7 +107,7 @@ export default function Slashing ({ className, isMember }: Props): React.ReactEl
<TxButton
accountId={accountId}
icon='repeat'
isDisabled={!threshold || !isSelectedMember || !proposal}
isDisabled={!threshold || !members.includes(accountId || '') || !proposal}
isPrimary
label={t('Revert')}
onStart={toggleVisible}

View File

@ -0,0 +1,73 @@
// Copyright 2017-2020 @polkadot/app-council 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 { Hash, Proposal, ProposalIndex } from '@polkadot/types/interfaces';
import React, { useState } from 'react';
import { Button, Modal, ProposedAction, VoteAccount, VoteActions, VoteToggle } from '@polkadot/react-components';
import { useAccounts, useToggle } from '@polkadot/react-hooks';
import { isBoolean } from '@polkadot/util';
import { useTranslation } from '../translate';
interface Props {
hash: Hash;
idNumber: ProposalIndex;
isDisabled: boolean;
members: string[];
proposal: Proposal;
}
export default function Voting ({ hash, idNumber, isDisabled, members, proposal }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const { hasAccounts } = useAccounts();
const [isVotingOpen, toggleVoting] = useToggle();
const [accountId, setAccountId] = useState<string | null>(null);
const [voteValue, setVoteValue] = useState(true);
if (!hasAccounts) {
return null;
}
const _onChangeVote = (vote?: boolean): void => setVoteValue(isBoolean(vote) ? vote : true);
return (
<>
{isVotingOpen && (
<Modal
header={t('Vote on proposal')}
size='small'
>
<Modal.Content>
<ProposedAction
idNumber={idNumber}
proposal={proposal}
/>
<VoteAccount
filter={members}
onChange={setAccountId}
/>
<VoteToggle
onChange={_onChangeVote}
value={voteValue}
/>
</Modal.Content>
<VoteActions
accountId={accountId}
onClick={toggleVoting}
params={[hash, idNumber, voteValue]}
tx='council.vote'
/>
</Modal>
)}
<Button
icon='check'
isDisabled={isDisabled}
isPrimary
label={t('Vote')}
onClick={toggleVoting}
/>
</>
);
}

View File

@ -3,16 +3,15 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { DerivedCollectiveProposals, DerivedCollectiveProposal } from '@polkadot/api-derive/types';
import { AccountId, Balance } from '@polkadot/types/interfaces';
import React, { useEffect, useState } from 'react';
import React from 'react';
import { Button, Table } from '@polkadot/react-components';
import { useApi, useAccounts, useCall } from '@polkadot/react-hooks';
import { useTranslation } from '../translate';
import useCouncilMembers from '../useCouncilMembers';
import Motion from './Motion';
import Propose from './Propose';
import Slashing from './Slashing';
import { useTranslation } from '../translate';
interface Props {
className?: string;
@ -21,27 +20,21 @@ interface Props {
export default function Proposals ({ className, motions }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const { allAccounts } = useAccounts();
const members = useCall<[AccountId, Balance][]>(api.query.electionsPhragmen?.members || api.query.elections.members, []);
const [isMember, setIsMember] = useState(false);
useEffect((): void => {
if (allAccounts && members) {
setIsMember(
members
.map(([accountId]): string => accountId.toString())
.some((accountId): boolean => allAccounts.includes(accountId))
);
}
}, [allAccounts, members]);
const { isMember, members } = useCouncilMembers();
return (
<div className={className}>
<Button.Group>
<Propose isMember={isMember} />
<Propose
filter={members}
isMember={isMember}
members={members}
/>
<Button.Or />
<Slashing isMember={isMember} />
<Slashing
isMember={isMember}
members={members}
/>
</Button.Group>
{motions?.length
? (
@ -51,6 +44,7 @@ export default function Proposals ({ className, motions }: Props): React.ReactEl
<Motion
isMember={isMember}
key={motion.hash.toHex()}
members={members}
motion={motion}
/>
))}

View File

@ -0,0 +1,33 @@
// Copyright 2017-2020 @polkadot/app-democracy 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 { AccountId, Balance } from '@polkadot/types/interfaces';
import { useState, useEffect } from 'react';
import { useAccounts, useApi, useCall } from '@polkadot/react-hooks';
interface Result {
isMember: boolean;
members: string[];
}
export default function useCouncilMembers (): Result {
const { api } = useApi();
const { allAccounts } = useAccounts();
const queryMembers = useCall<[AccountId, Balance][]>(api.query.electionsPhragmen?.members || api.query.elections.members, []);
const [members, setMembers] = useState<Result>({ isMember: false, members: [] });
useEffect((): void => {
if (allAccounts && queryMembers) {
const members = queryMembers.map(([accountId]): string => accountId.toString());
setMembers({
members,
isMember: members.some((accountId): boolean => allAccounts.includes(accountId))
});
}
}, [allAccounts, queryMembers]);
return members;
}

View File

@ -59,6 +59,7 @@ function Proposal ({ className, value: { balance, hash, index, proposal, propose
<Seconding
depositors={seconds || []}
proposalId={index}
proposal={proposal}
/>
</td>
</tr>

View File

@ -109,7 +109,10 @@ function Referendum ({ className, idNumber, value }: Props): React.ReactElement<
<FormatBalance value={votedNay} />
</td>
<td className='number together top'>
<Voting referendumId={value.index} />
<Voting
proposal={value.proposal}
referendumId={value.index}
/>
</td>
</tr>
);

View File

@ -2,11 +2,11 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { AccountId } from '@polkadot/types/interfaces';
import { AccountId, Proposal } from '@polkadot/types/interfaces';
import BN from 'bn.js';
import React, { useState } from 'react';
import { Button, InputAddress, Modal, TxButton } from '@polkadot/react-components';
import { Button, InputAddress, Modal, ProposedAction, TxButton } from '@polkadot/react-components';
import { useAccounts } from '@polkadot/react-hooks';
import { useTranslation } from '../translate';
@ -14,10 +14,11 @@ import { useTranslation } from '../translate';
interface Props {
className?: string;
depositors: AccountId[];
proposal?: Proposal;
proposalId: BN | number;
}
export default function Seconding ({ depositors, proposalId }: Props): React.ReactElement<Props> | null {
export default function Seconding ({ depositors, proposal, proposalId }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const { hasAccounts } = useAccounts();
const [accountId, setAccountId] = useState<string | null>(null);
@ -38,6 +39,10 @@ export default function Seconding ({ depositors, proposalId }: Props): React.Rea
size='small'
>
<Modal.Content>
<ProposedAction
idNumber={proposalId}
proposal={proposal}
/>
<InputAddress
help={t('Select the account you wish to second with. This will lock your funds until the proposal is either approved or rejected')}
label={t('second with account')}

View File

@ -2,19 +2,21 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import BN from 'bn.js';
import { PropIndex, Proposal } from '@polkadot/types/interfaces';
import React, { useState } from 'react';
import { Button, Modal, VoteAccount, VoteActions, VoteToggle } from '@polkadot/react-components';
import { Button, Modal, ProposedAction, VoteAccount, VoteActions, VoteToggle } from '@polkadot/react-components';
import { useAccounts } from '@polkadot/react-hooks';
import { isBoolean } from '@polkadot/util';
import { useTranslation } from '../translate';
interface Props {
referendumId: BN | number;
proposal?: Proposal;
referendumId: PropIndex;
}
export default function Voting ({ referendumId }: Props): React.ReactElement<Props> | null {
export default function Voting ({ proposal, referendumId }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const { hasAccounts } = useAccounts();
const [accountId, setAccountId] = useState<string | null>(null);
@ -36,6 +38,10 @@ export default function Voting ({ referendumId }: Props): React.ReactElement<Pro
size='small'
>
<Modal.Content>
<ProposedAction
idNumber={referendumId}
proposal={proposal}
/>
<VoteAccount onChange={setAccountId} />
<VoteToggle
onChange={_onChangeVote}

View File

@ -5,7 +5,7 @@
import BN from 'bn.js';
import React, { useState } from 'react';
import { Button, InputAddress, InputBalance, Modal, TxButton } from '@polkadot/react-components';
import { useAccounts } from '@polkadot/react-hooks';
import useCouncilMembers from '@polkadot/app-council/useCouncilMembers';
import { useTranslation } from '../translate';
@ -15,16 +15,12 @@ interface Props {
export default function Propose ({ className }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const { hasAccounts } = useAccounts();
const { isMember, members } = useCouncilMembers();
const [accountId, setAccountId] = useState<string | null>(null);
const [beneficiary, setBeneficiary] = useState<string | null>(null);
const [isProposeOpen, setIsProposeOpen] = useState(false);
const [value, setValue] = useState<BN | undefined>();
if (!hasAccounts) {
return null;
}
const _togglePropose = (): void => setIsProposeOpen(!isProposeOpen);
const hasValue = value?.gtn(0);
@ -39,6 +35,7 @@ export default function Propose ({ className }: Props): React.ReactElement<Props
>
<Modal.Content>
<InputAddress
filter={members}
help={t('Select the account you wish to submit the proposal from.')}
label={t('submit with account')}
onChange={setAccountId}
@ -76,6 +73,7 @@ export default function Propose ({ className }: Props): React.ReactElement<Props
)}
<Button
icon='check'
isDisabled={!isMember}
isPrimary
label={t('Submit proposal')}
onClick={_togglePropose}

View File

@ -34,7 +34,6 @@ function ActionItem ({ className, children, accessory, idNumber, proposal, expan
<div className='ui--Row-details'>
<ProposedAction
idNumber={idNumber}
isCollapsible
proposal={proposal}
withLinks={expandNested}
expandNested={expandNested}

View File

@ -23,6 +23,7 @@ import createItem from './createItem';
interface Props extends BareProps {
defaultValue?: Uint8Array | string | null;
filter?: string[];
help?: React.ReactNode;
hideAddress?: boolean;
isDisabled?: boolean;
@ -131,7 +132,7 @@ class InputAddress extends React.PureComponent<Props, State> {
}
public render (): React.ReactNode {
const { className, defaultValue, help, hideAddress = false, isDisabled = false, isError, isMultiple, label, labelExtra, options, optionsAll, placeholder, type = DEFAULT_TYPE, style, withEllipsis, withLabel } = this.props;
const { className, defaultValue, filter = [], help, hideAddress = false, isDisabled = false, isError, isMultiple, label, labelExtra, options, optionsAll, placeholder, type = DEFAULT_TYPE, style, withEllipsis, withLabel } = this.props;
const { value } = this.state;
const hasOptions = (options && options.length !== 0) || (optionsAll && Object.keys(optionsAll[type]).length !== 0);
@ -156,7 +157,7 @@ class InputAddress extends React.PureComponent<Props, State> {
isDisabled && actualValue
? [createOption(actualValue)]
: optionsAll
? optionsAll[type]
? optionsAll[type].filter(({ value }): boolean => !filter.length || filter.includes(value || ''))
: []
);
const _defaultValue = (isMultiple || !isUndefined(value))
@ -207,13 +208,7 @@ class InputAddress extends React.PureComponent<Props, State> {
}
private getLastOptionValue (): KeyringSectionOption | undefined {
const { optionsAll, type = DEFAULT_TYPE } = this.props;
if (!optionsAll) {
return;
}
const available = optionsAll[type].filter(({ value }): boolean => !!value);
const available = this.getFiltered();
return available.length
? available[available.length - 1]
@ -221,13 +216,15 @@ class InputAddress extends React.PureComponent<Props, State> {
}
private hasValue (test?: Uint8Array | string): boolean {
const { optionsAll, type = DEFAULT_TYPE } = this.props;
return this.getFiltered().some(({ value }): boolean => test === value);
}
if (!optionsAll) {
return false;
}
private getFiltered (): Option[] {
const { filter, optionsAll, type = DEFAULT_TYPE } = this.props;
return !!optionsAll[type].find(({ value }): boolean => test === value);
return !optionsAll
? []
: optionsAll[type].filter(({ value }): boolean => !!value && (!filter || filter.includes(value)));
}
private onChange = (address: string): void => {

View File

@ -8,7 +8,7 @@ import BN from 'bn.js';
import React from 'react';
import styled from 'styled-components';
import { registry } from '@polkadot/react-api';
import { formatNumber } from '@polkadot/util';
import { formatNumber, isString } from '@polkadot/util';
import Call from './Call';
import Inset, { InsetProps } from './Inset';
@ -27,9 +27,9 @@ interface Props {
}
export const styles = `
.ui--ProposedAction-extrinsic {
margin-bottom: 1rem;
margin-bottom: 1rem;
.ui--ProposedAction-extrinsic {
.ui--Params-Content {
padding-left: 0;
}
@ -40,43 +40,38 @@ export const styles = `
}
`;
function ProposedAction (props: Props): React.ReactElement<Props> {
const { className, asInset, insetProps, isCollapsible, proposal, withLinks, expandNested } = props;
const idNumber = typeof props.idNumber === 'string'
? props.idNumber
: formatNumber(props.idNumber);
function ProposedAction ({ className, asInset, idNumber, insetProps, isCollapsible = true, proposal, withLinks, expandNested }: Props): React.ReactElement<Props> {
const stringId = isString(idNumber)
? idNumber
: formatNumber(idNumber);
if (!proposal) {
return (
<h3>#{idNumber}</h3>
<h3>#{stringId}</h3>
);
}
const { meta, method, section } = registry.findMetaCall(proposal.callIndex);
const header = `#${idNumber}: ${section}.${method}`;
const header = `#${stringId}: ${section}.${method}`;
const documentation = meta?.documentation
? (
<summary>{meta.documentation.join(' ')}</summary>
)
: null;
const params = (isTreasuryProposalVote(proposal) && expandNested) ? (
<TreasuryProposal
className='ui--ProposedAction-extrinsic'
asInset={withLinks}
insetProps={{
withTopMargin: true,
withBottomMargin: true,
...(withLinks ? { href: '/treasury' } : {})
}}
proposalId={proposal.args[0].toString()}
/>
) : (
<Call
className='ui--ProposedAction-extrinsic'
value={proposal}
/>
);
const params = (isTreasuryProposalVote(proposal) && expandNested)
? (
<TreasuryProposal
asInset={withLinks}
insetProps={{
withTopMargin: true,
withBottomMargin: true,
...(withLinks ? { href: '/treasury' } : {})
}}
proposalId={proposal.args[0].toString()}
/>
)
: <Call value={proposal} />;
if (asInset) {
return (
@ -94,7 +89,7 @@ function ProposedAction (props: Props): React.ReactElement<Props> {
}
return (
<div className={className}>
<div className={`ui--ProposedAction ${className}`}>
<h3>{header}</h3>
{isCollapsible
? (

View File

@ -8,6 +8,7 @@ import React from 'react';
import { InputAddress, Modal, TxButton, TxComponent } from '@polkadot/react-components';
export interface TxModalProps extends I18nProps {
filter?: string[];
onSubmit?: () => void;
onClose?: () => void;
onSuccess?: () => void;
@ -120,11 +121,13 @@ export default class TxModal<P extends TxModalProps, S extends TxModalState> ext
}
protected renderInputAccount (): React.ReactNode {
const { filter } = this.props;
const { accountId, isBusy } = this.state;
return (
<InputAddress
defaultValue={accountId}
filter={filter}
help={this.accountHelp()}
isDisabled={isBusy}
isInput={false}

View File

@ -9,15 +9,17 @@ import { useTranslation } from './translate';
interface Props {
className?: string;
filter?: string[];
onChange: (value: string | null) => void;
}
export default function VoteAccount ({ className, onChange }: Props): React.ReactElement<Props> {
export default function VoteAccount ({ className, filter, onChange }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
return (
<InputAddress
className={className}
filter={filter}
help={t('Select the account you wish to vote with. You can approve "aye" or deny "nay" the proposal.')}
label={t('vote with account')}
onChange={onChange}

View File

@ -1,125 +0,0 @@
// Copyright 2017-2020 @polkadot/app-democracy 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 { Proposal } from '@polkadot/types/interfaces';
import { I18nProps } from '@polkadot/react-components/types';
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
import BN from 'bn.js';
import React from 'react';
import keyring from '@polkadot/ui-keyring';
import { withMulti, withObservable } from '@polkadot/react-api/hoc';
import translate from './translate';
import Button from './Button';
import ProposedAction from './ProposedAction';
import TxModal, { TxModalProps, TxModalState } from './TxModal';
import VoteToggle from './VoteToggle';
import { isTreasuryProposalVote } from './util';
interface Props extends I18nProps, TxModalProps {
allAccounts?: SubjectInfo;
hash?: string;
idNumber: BN | number;
isCouncil: boolean;
isDisabled?: boolean;
proposal?: Proposal | null;
preContent?: React.ReactNode;
}
interface State extends TxModalState {
voteValue: boolean;
}
class Voting extends TxModal<Props, State> {
public state: State;
protected headerText = (): string => {
const { isCouncil, t } = this.props;
return isCouncil ? t('Vote on council proposal') : t('Vote on proposal');
}
protected accountLabel = (): string => this.props.t('Vote with account');
protected accountHelp = (): string => this.props.t('Select the account you wish to vote with. You can approve "aye" or deny "nay" the proposal.');
protected txMethod = (): string => {
const { isCouncil } = this.props;
return isCouncil ? 'council.vote' : 'democracy.vote';
}
protected txParams = (): any[] => {
const { hash, idNumber, isCouncil } = this.props;
const { voteValue } = this.state;
return isCouncil
? [hash, idNumber, voteValue]
: [idNumber, voteValue];
}
constructor (props: Props) {
super(props);
this.state = {
...this.defaultState,
voteValue: true
};
}
protected renderPreContent = (): React.ReactNode => {
const { idNumber, proposal } = this.props;
return (
<>
<ProposedAction
expandNested={isTreasuryProposalVote(proposal)}
idNumber={idNumber}
isCollapsible
proposal={proposal}
/>
<br />
<br />
</>
);
}
protected renderContent = (): React.ReactNode => {
const { voteValue } = this.state;
return (
<VoteToggle
onChange={this.onChangeVote}
value={voteValue}
/>
);
}
protected renderTrigger = (): React.ReactNode => {
const { isDisabled, t } = this.props;
return (
<div className='ui--Row-buttons'>
<Button
isDisabled={isDisabled}
isPrimary
label={t('Vote')}
icon='check'
onClick={this.showModal}
/>
</div>
);
}
private onChangeVote = (voteValue: boolean): void => {
this.setState({ voteValue });
}
}
export default withMulti(
Voting,
translate,
withObservable(keyring.accounts.subject, { propName: 'allAccounts' })
);

View File

@ -87,6 +87,5 @@ export { default as TxModalNew } from './TxModalNew';
export { default as VoteAccount } from './VoteAccount';
export { default as VoteActions } from './VoteActions';
export { default as VoteToggle } from './VoteToggle';
export { default as Voting } from './Voting';
export { default as media } from './media';