diff --git a/packages/app-council/src/Motions/Motion.tsx b/packages/app-council/src/Motions/Motion.tsx new file mode 100644 index 0000000000..f948f51aa6 --- /dev/null +++ b/packages/app-council/src/Motions/Motion.tsx @@ -0,0 +1,171 @@ +// Copyright 2017-2019 @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 { I18nProps } from '@polkadot/ui-app/types'; +import { Option, Proposal, Votes } from '@polkadot/types'; + +import BN from 'bn.js'; +import React from 'react'; +import styled from 'styled-components'; +import { ActionItem, InputAddress, Labelled, Voting } from '@polkadot/ui-app'; +import { withCalls, withMulti } from '@polkadot/ui-api'; + +import translate from '../translate'; + +type Props = I18nProps & { + chain_bestNumber?: BN, + hash: string, + proposal: Proposal | null, + votes: Votes | null +}; + +type State = { + votedTotal: number, + votedNay: number, + votedAye: number +}; + +class Motion extends React.PureComponent { + state: State = { + votedTotal: 0, + votedAye: 0, + votedNay: 0 + }; + + static getDerivedStateFromProps ({ votes }: Props): State | null { + if (!votes) { + return null; + } + + const { ayes, nays } = votes; + + const newState: State = { + votedNay: nays.length, + votedAye: ayes.length, + votedTotal: ayes.length + nays.length + }; + + return newState; + } + + render () { + const { className, hash, proposal, votes } = this.props; + + if (!proposal || !votes) { + return null; + } + + const { index } = votes; + + return ( + + } + expandNested + idNumber={index} + proposal={proposal} + > + {this.renderInfo()} + + ); + } + + private renderInfo () { + const { votes, t } = this.props; + + if (!votes) { + return null; + } + + const { ayes, nays, threshold } = votes; + + return ( +
+

+ {t( + 'ayes ({{ayes}}/{{threshold}} to approve)', + { + replace: { + ayes: ayes.length, + threshold: threshold.toString() + } + } + )} +

+ {ayes.map((address, index) => ( + + + + ))} +

+ {t( + 'nays ({{nays}})', + { + replace: { + nays: nays.length + } + } + )} +

+ {nays.map((address, index) => ( + + + + ))} +
+ ); + } +} + +export default withMulti( + styled(Motion as React.ComponentClass)` + .democracy--Motion-results { + margin-bottom: 1em; + + &.chart { + text-align: center; + } + } + `, + translate, + withCalls( + [ + 'query.councilMotions.proposalOf', + { + paramName: 'hash', + propName: 'proposal', + transform: (value: Option) => value.unwrapOr(null) + } + ], + [ + 'query.councilMotions.voting', + { + paramName: 'hash', + propName: 'votes', + transform: (value: Option) => value.unwrapOr(null) + } + ] + ) +); diff --git a/packages/app-council/src/Motions/Propose.tsx b/packages/app-council/src/Motions/Propose.tsx new file mode 100644 index 0000000000..6ccc18c8b0 --- /dev/null +++ b/packages/app-council/src/Motions/Propose.tsx @@ -0,0 +1,144 @@ +// Copyright 2017-2019 @polkadot/ui-staking authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import { ApiProps } from '@polkadot/ui-api/types'; +import { Method, Proposal } from '@polkadot/types'; + +import BN from 'bn.js'; +import React from 'react'; + +import { Button, Extrinsic, InputNumber } from '@polkadot/ui-app'; +import TxModal, { TxModalState, TxModalProps } from '@polkadot/ui-app/TxModal'; +import { withApi, withCalls, withMulti } from '@polkadot/ui-api'; + +import translate from '../translate'; + +type Props = TxModalProps & ApiProps & { + memberCount: number +}; + +type State = TxModalState & { + method: Method | null, + threshold: BN | null +}; + +class Propose extends TxModal { + constructor (props: Props) { + super(props); + + this.defaultState = { + ...this.defaultState, + method: null, + threshold: props.memberCount ? new BN((props.memberCount / 2) + 1) : null + }; + this.state = this.defaultState; + } + + static getDerivedStateFromProps ({ memberCount }: Props, { threshold }: State) { + if (!threshold && memberCount > 0) { + const simpleMajority = new BN((memberCount / 2) + 1); + return { threshold: simpleMajority }; + } + return null; + } + + headerText = () => this.props.t('Propose a council motion'); + + txMethod = () => 'councilMotions.propose'; + txParams = () => { + const { method, threshold } = this.state; + + return [ + threshold, + ...(method ? [new Proposal(method)] : []) + ]; + } + + isDisabled = () => { + const { memberCount = 0 } = this.props; + const { accountId, method, threshold } = this.state; + + const hasThreshold = !!threshold && threshold.gtn(0) && threshold.ltn(memberCount + 1); + const hasMethod = !!method; + + return !accountId || !hasMethod || !hasThreshold; + } + + renderTrigger = () => { + const { t } = this.props; + + return ( + +