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:
parent
0dbe4f927e
commit
9905927862
@ -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>
|
||||
|
||||
@ -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
|
||||
);
|
||||
|
||||
@ -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}
|
||||
|
||||
73
packages/app-council/src/Motions/Voting.tsx
Normal file
73
packages/app-council/src/Motions/Voting.tsx
Normal 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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -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}
|
||||
/>
|
||||
))}
|
||||
|
||||
33
packages/app-council/src/useCouncilMembers.ts
Normal file
33
packages/app-council/src/useCouncilMembers.ts
Normal 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;
|
||||
}
|
||||
@ -59,6 +59,7 @@ function Proposal ({ className, value: { balance, hash, index, proposal, propose
|
||||
<Seconding
|
||||
depositors={seconds || []}
|
||||
proposalId={index}
|
||||
proposal={proposal}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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')}
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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 => {
|
||||
|
||||
@ -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
|
||||
? (
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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' })
|
||||
);
|
||||
@ -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';
|
||||
|
||||
Loading…
Reference in New Issue
Block a user