Browse Source

Swap council voting from TxModal (#2141)

* Swap council voting from TxModal

* Cleanup toggles

* Apply account filters

* useCouncilMembers

* Pass filter through to Propose

* Linting
Jaco Greeff 5 years ago
parent
commit
9905927862

+ 6 - 4
packages/app-council/src/Motions/Motion.tsx

@@ -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>

+ 16 - 27
packages/app-council/src/Motions/Propose.tsx

@@ -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
 );

+ 7 - 18
packages/app-council/src/Motions/Slashing.tsx

@@ -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 - 0
packages/app-council/src/Motions/Voting.tsx

@@ -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}
+      />
+    </>
+  );
+}

+ 14 - 20
packages/app-council/src/Motions/index.tsx

@@ -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 - 0
packages/app-council/src/useCouncilMembers.ts

@@ -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;
+}

+ 1 - 0
packages/app-democracy/src/Overview/Proposal.tsx

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

+ 4 - 1
packages/app-democracy/src/Overview/Referendum.tsx

@@ -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>
   );

+ 8 - 3
packages/app-democracy/src/Overview/Seconding.tsx

@@ -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')}

+ 10 - 4
packages/app-democracy/src/Overview/Voting.tsx

@@ -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}

+ 4 - 6
packages/app-treasury/src/Overview/Propose.tsx

@@ -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}

+ 0 - 1
packages/react-components/src/ActionItem.tsx

@@ -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}

+ 11 - 14
packages/react-components/src/InputAddress/index.tsx

@@ -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 => {

+ 23 - 28
packages/react-components/src/ProposedAction.tsx

@@ -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
         ? (

+ 3 - 0
packages/react-components/src/TxModal.tsx

@@ -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}

+ 3 - 1
packages/react-components/src/VoteAccount.tsx

@@ -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}

+ 0 - 125
packages/react-components/src/Voting.tsx

@@ -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' })
-);

+ 0 - 1
packages/react-components/src/index.tsx

@@ -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';