Submit candidacy (#1402)

* Change council to collective/elections

* fix proposal type

* package

* Submit candidacy

* update index

* Minor eslint cleanups

* rename back to council

* blank line
This commit is contained in:
kwingram25 2019-07-15 21:42:17 +01:00 committed by Jaco Greeff
parent 84ceae3ec5
commit a7791a8e69
26 changed files with 225 additions and 132 deletions

View File

@ -1 +0,0 @@
# @polkadot/app-collective

View File

@ -1,21 +0,0 @@
// 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 React from 'react';
import Members from './Members';
import Summary from './Summary';
interface Props {}
export default class Overview extends React.PureComponent<Props> {
public render (): React.ReactNode {
return (
<>
<Summary />
<Members />
</>
);
}
}

View File

@ -0,0 +1 @@
# @polkadot/app-council

View File

@ -1,7 +1,7 @@
{ {
"name": "@polkadot/app-collective", "name": "@polkadot/app-council",
"version": "0.34.0-beta.65", "version": "0.34.0-beta.65",
"description": "Collective", "description": "Council",
"main": "index.js", "main": "index.js",
"scripts": {}, "scripts": {},
"author": "Jaco Greeff <jacogr@gmail.com>", "author": "Jaco Greeff <jacogr@gmail.com>",

View File

@ -7,7 +7,6 @@ import { Option, Proposal as ProposalType, Votes } from '@polkadot/types';
import BN from 'bn.js'; import BN from 'bn.js';
import React from 'react'; import React from 'react';
import styled from 'styled-components';
import { ActionItem, InputAddress, Labelled, Voting } from '@polkadot/ui-app'; import { ActionItem, InputAddress, Labelled, Voting } from '@polkadot/ui-app';
import { withCalls, withMulti } from '@polkadot/ui-api'; import { withCalls, withMulti } from '@polkadot/ui-api';
@ -26,7 +25,7 @@ interface State {
votedAye: number; votedAye: number;
} }
class Proposal extends React.PureComponent<Props, State> { class Motion extends React.PureComponent<Props, State> {
public state: State = { public state: State = {
votedTotal: 0, votedTotal: 0,
votedAye: 0, votedAye: 0,
@ -64,7 +63,7 @@ class Proposal extends React.PureComponent<Props, State> {
accessory={ accessory={
<Voting <Voting
hash={hash} hash={hash}
isCollective isCouncil
idNumber={index} idNumber={index}
proposal={proposal} proposal={proposal}
/> />
@ -140,15 +139,7 @@ class Proposal extends React.PureComponent<Props, State> {
} }
export default withMulti( export default withMulti(
styled(Proposal as React.ComponentClass<Props>)` Motion,
.democracy--Proposal-results {
margin-bottom: 1em;
&.chart {
text-align: center;
}
}
`,
translate, translate,
withCalls<Props>( withCalls<Props>(
[ [

View File

@ -15,16 +15,16 @@ import { withApi, withCalls, withMulti } from '@polkadot/ui-api';
import translate from '../translate'; import translate from '../translate';
type Props = TxModalProps & ApiProps & { type Props = TxModalProps & ApiProps & {
memberCount: number; memberCount: number
}; };
interface State extends TxModalState { type State = TxModalState & {
method: Method | null; method: Method | null,
threshold: BN | null; threshold: BN | null
} };
class Propose extends TxModal<Props, State> { class Propose extends TxModal<Props, State> {
public constructor (props: Props) { constructor (props: Props) {
super(props); super(props);
this.defaultState = { this.defaultState = {
@ -43,7 +43,7 @@ class Propose extends TxModal<Props, State> {
return null; return null;
} }
headerText = () => this.props.t('Make a collective proposal'); headerText = () => this.props.t('Propose a council motion');
txMethod = () => 'collective.propose'; txMethod = () => 'collective.propose';
txParams = () => { txParams = () => {
@ -72,7 +72,7 @@ class Propose extends TxModal<Props, State> {
<Button.Group> <Button.Group>
<Button <Button
isPrimary isPrimary
label={t('Make a collective proposal')} label={t('Propose a council motion')}
labelIcon='add' labelIcon='add'
onClick={this.showModal} onClick={this.showModal}
/> />
@ -89,7 +89,7 @@ class Propose extends TxModal<Props, State> {
<InputNumber <InputNumber
className='medium' className='medium'
label={t('threshold')} label={t('threshold')}
help={t('The minimum number of collective votes required to approve this proposal')} 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(memberCount)}
onChange={this.onChangeThreshold} onChange={this.onChangeThreshold}
onEnter={this.sendTx} onEnter={this.sendTx}
@ -137,7 +137,7 @@ export default withMulti(
'query.elections.members', 'query.elections.members',
{ {
propName: 'memberCount', propName: 'memberCount',
transform: (value: any[]) => value.length transform: (value: Array<any>) => value.length
} }
] ]
) )

View File

@ -10,7 +10,7 @@ import React from 'react';
import { withCalls } from '@polkadot/ui-api'; import { withCalls } from '@polkadot/ui-api';
import { CardGrid } from '@polkadot/ui-app'; import { CardGrid } from '@polkadot/ui-app';
import Proposal from './Proposal'; import Motion from './Motion';
import Propose from './Propose'; import Propose from './Propose';
import translate from '../translate'; import translate from '../translate';
@ -24,8 +24,8 @@ class Proposals extends React.PureComponent<Props> {
return ( return (
<CardGrid <CardGrid
emptyText={t('No proposals')} emptyText={t('No council motions')}
headerText={t('Proposals')} headerText={t('Motions')}
buttons={ buttons={
<Propose /> <Propose />
} }
@ -39,7 +39,7 @@ class Proposals extends React.PureComponent<Props> {
const { collective_proposals = [] } = this.props; const { collective_proposals = [] } = this.props;
return collective_proposals.map((hash: Hash): React.ReactNode => ( return collective_proposals.map((hash: Hash): React.ReactNode => (
<Proposal <Motion
hash={hash.toHex()} hash={hash.toHex()}
key={hash.toHex()} key={hash.toHex()}
/> />

View File

@ -11,19 +11,19 @@ import { formatNumber } from '@polkadot/util';
import translate from '../translate'; import translate from '../translate';
interface Props extends I18nProps { type Props = I18nProps & {
address: string; address: string,
block: BN; block: BN
} };
class Member extends React.PureComponent<Props> { class Member extends React.PureComponent<Props> {
public render (): React.ReactNode { render () {
const { address, block, t } = this.props; const { address, block, t } = this.props;
return ( return (
<AddressCard <AddressCard
buttons={<div><label>{t('active until')}</label>#{formatNumber(block)}</div>} buttons={<div><label>{t('active until')}</label>#{formatNumber(block)}</div>}
defaultName='collective member' defaultName='council member'
value={address} value={address}
/> />
); );

View File

@ -4,24 +4,23 @@
// of the Apache-2.0 license. See the LICENSE file for details. // of the Apache-2.0 license. See the LICENSE file for details.
import { I18nProps } from '@polkadot/ui-app/types'; import { I18nProps } from '@polkadot/ui-app/types';
import { ElectionsInfo } from './types';
import React from 'react'; import React from 'react';
import { AccountId, BlockNumber } from '@polkadot/types';
import { Columar, Column } from '@polkadot/ui-app'; import { Columar, Column } from '@polkadot/ui-app';
import { withCalls } from '@polkadot/ui-api';
import translate from '../translate'; import translate from '../translate';
import Candidate from './Candidate'; import Candidate from './Candidate';
import Member from './Member'; import Member from './Member';
interface Props extends I18nProps { interface Props extends I18nProps {
elections_members?: [string, BlockNumber][]; electionsInfo: ElectionsInfo;
elections_candidates?: string[];
} }
class Members extends React.PureComponent<Props> { class Members extends React.PureComponent<Props> {
public render (): React.ReactNode { public render (): React.ReactNode {
const { elections_members = [], elections_candidates = [], t } = this.props; const { electionsInfo, t } = this.props;
const { members, candidates } = electionsInfo;
return ( return (
<Columar> <Columar>
@ -29,7 +28,7 @@ class Members extends React.PureComponent<Props> {
emptyText={t('No members found')} emptyText={t('No members found')}
headerText={t('members')} headerText={t('members')}
> >
{elections_members.map(([address, block]): React.ReactNode => ( {members.map(([address, block]): React.ReactNode => (
<Member <Member
address={address} address={address}
block={block} block={block}
@ -41,7 +40,7 @@ class Members extends React.PureComponent<Props> {
emptyText={t('No members found')} emptyText={t('No members found')}
headerText={t('candidates')} headerText={t('candidates')}
> >
{elections_candidates.map((address): React.ReactNode => ( {candidates.map((address): React.ReactNode => (
<Candidate <Candidate
address={address} address={address}
key={address} key={address}
@ -53,17 +52,4 @@ class Members extends React.PureComponent<Props> {
} }
} }
export default translate( export default translate(Members);
withCalls<Props>(
['query.elections.members', {
transform: (active: [AccountId, BlockNumber][]): [string, BlockNumber][] =>
active.map(([accountId, blockNumber]): [string, BlockNumber] =>
[accountId.toString(), blockNumber]
)
}],
['query.elections.candidates', {
transform: (candidates: AccountId[]): string[] =>
candidates.map((accountId): string => accountId.toString())
}]
)(Members)
);

View File

@ -0,0 +1,56 @@
// 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 { ElectionsInfo } from './types';
import React from 'react';
import { Button } from '@polkadot/ui-app';
import TxModal, { TxModalState, TxModalProps } from '@polkadot/ui-app/TxModal';
import translate from '../translate';
type Props = TxModalProps & {
electionsInfo: ElectionsInfo
};
type State = TxModalState;
class SubmitCandidacy extends TxModal<Props, State> {
headerText = () => this.props.t('Submit your council candidacy');
accountLabel = () => this.props.t('Candidate account');
accountHelp = () => this.props.t('This account will be nominated to fill the council slot you specify.');
txMethod = () => 'elections.submitCandidacy';
txParams = () => {
const { electionsInfo: { candidateCount } } = this.props;
return [
candidateCount
];
}
isDisabled = () => {
const { accountId } = this.state;
return !accountId;
}
renderTrigger = () => {
const { t } = this.props;
return (
<Button.Group>
<Button
isPrimary
label={t('Submit candidacy')}
labelIcon='add'
onClick={this.showModal}
/>
</Button.Group>
);
}
}
export default translate(SubmitCandidacy);

View File

@ -4,60 +4,48 @@
// of the Apache-2.0 license. See the LICENSE file for details. // of the Apache-2.0 license. See the LICENSE file for details.
import { I18nProps } from '@polkadot/ui-app/types'; import { I18nProps } from '@polkadot/ui-app/types';
import { ElectionsInfo } from './types';
import BN from 'bn.js';
import React from 'react'; import React from 'react';
import { AccountId, BlockNumber } from '@polkadot/types';
import { SummaryBox, CardSummary } from '@polkadot/ui-app'; import { SummaryBox, CardSummary } from '@polkadot/ui-app';
import { withCalls } from '@polkadot/ui-api';
import { formatNumber } from '@polkadot/util'; import { formatNumber } from '@polkadot/util';
import translate from '../translate'; import translate from '../translate';
interface Props extends I18nProps { interface Props extends I18nProps {
elections_members?: [AccountId, BlockNumber][]; electionsInfo: ElectionsInfo;
elections_candidateCount?: BN;
elections_desiredSeats?: BN;
elections_termDuration?: BN;
elections_voteCount?: BN;
} }
class Summary extends React.PureComponent<Props> { class Summary extends React.PureComponent<Props> {
public render (): React.ReactNode { public render (): React.ReactNode {
const { elections_members = [], elections_candidateCount = new BN(0), elections_desiredSeats = new BN(1), elections_termDuration = new BN(0), elections_voteCount = new BN(0), t } = this.props; const { electionsInfo, t } = this.props;
const { members, candidateCount, desiredSeats, termDuration, voteCount } = electionsInfo;
return ( return (
<SummaryBox> <SummaryBox>
<section> <section>
<CardSummary label={t('seats')}> <CardSummary label={t('seats')}>
{formatNumber(elections_members.length)}/{formatNumber(elections_desiredSeats)} {formatNumber(members.length)}/{formatNumber(desiredSeats)}
</CardSummary> </CardSummary>
<CardSummary label={t('candidates')}> <CardSummary label={t('candidates')}>
{formatNumber(elections_candidateCount)} {formatNumber(candidateCount)}
</CardSummary> </CardSummary>
</section> </section>
<section> <section>
<CardSummary label={t('total votes')}> <CardSummary label={t('total votes')}>
{formatNumber(elections_voteCount)} {formatNumber(voteCount)}
</CardSummary> </CardSummary>
</section> </section>
<section> <section>
<CardSummary label={t('term duration')}> <CardSummary label={t('term duration')}>
{formatNumber(elections_termDuration)} {formatNumber(termDuration)}
</CardSummary> </CardSummary>
</section> </section>
</SummaryBox> </SummaryBox>
); );
} }
} }
export default translate( export default translate(Summary);
withCalls<Props>(
'query.elections.members',
'query.elections.candidateCount',
'query.elections.desiredSeats',
'query.elections.termDuration',
'query.elections.voteCount'
)(Summary)
);

View File

@ -0,0 +1,76 @@
// 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 { AccountId, BlockNumber } from '@polkadot/types';
import { ElectionsInfo } from './types';
import BN from 'bn.js';
import React from 'react';
import { withCalls } from '@polkadot/ui-api';
import Members from './Members';
import SubmitCandidacy from './SubmitCandidacy';
import Summary from './Summary';
interface Props {
elections_members?: [string, BlockNumber][];
elections_candidates?: string[];
elections_candidateCount?: BN;
elections_desiredSeats?: BN;
elections_termDuration?: BN;
elections_voteCount?: BN;
}
interface State {
electionsInfo: ElectionsInfo;
}
class Overview extends React.PureComponent<Props, State> {
public static getDerivedStateFromProps ({
elections_members: members = [],
elections_candidates: candidates = [],
elections_candidateCount: candidateCount = new BN(0),
elections_desiredSeats: desiredSeats = new BN(0),
elections_termDuration: termDuration = new BN(0),
elections_voteCount: voteCount = new BN(0)
}: Props): State {
const electionsInfo: ElectionsInfo = {
members, candidates, candidateCount, desiredSeats, termDuration, voteCount
};
return { electionsInfo };
}
public render (): React.ReactNode {
const { electionsInfo } = this.state;
return (
<>
<Summary electionsInfo={electionsInfo} />
<SubmitCandidacy electionsInfo={electionsInfo} />
<Members electionsInfo={electionsInfo} />
</>
);
}
}
export default withCalls<Props>(
['query.elections.members', {
transform: (active: [AccountId, BlockNumber][]): [string, BlockNumber][] =>
active.map(([accountId, blockNumber]): [string, BlockNumber] =>
[accountId.toString(), blockNumber]
)
}],
['query.elections.candidates', {
transform: (candidates: AccountId[]): string[] =>
candidates.map((accountId): string =>
accountId.toString()
)
}],
'query.elections.candidateCount',
'query.elections.desiredSeats',
'query.elections.termDuration',
'query.elections.voteCount'
)(Overview);

View File

@ -0,0 +1,16 @@
// 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 { BlockNumber } from '@polkadot/types';
import BN from 'bn.js';
export interface ElectionsInfo {
members: [string, BlockNumber][];
candidates: string[];
candidateCount: BN;
desiredSeats: BN;
termDuration: BN;
voteCount: BN;
}

View File

@ -10,17 +10,17 @@ import { Route, Switch } from 'react-router';
import { Tabs } from '@polkadot/ui-app'; import { Tabs } from '@polkadot/ui-app';
import Overview from './Overview'; import Overview from './Overview';
import Proposals from './Proposals'; import Motions from './Motions';
import translate from './translate'; import translate from './translate';
type Props = AppProps & BareProps & I18nProps; type Props = AppProps & BareProps & I18nProps;
interface State { type State = {
tabs: TabItem[]; tabs: Array<TabItem>
} };
class App extends React.PureComponent<Props, State> { class App extends React.PureComponent<Props, State> {
public constructor (props: Props) { constructor (props: Props) {
super(props); super(props);
const { t } = props; const { t } = props;
@ -30,17 +30,17 @@ class App extends React.PureComponent<Props, State> {
{ {
isRoot: true, isRoot: true,
name: 'overview', name: 'overview',
text: t('Collective overview') text: t('Council overview')
}, },
{ {
name: 'proposals', name: 'motions',
text: t('Proposals') text: t('Motions')
} }
] ]
}; };
} }
public render (): React.ReactNode { render () {
const { basePath } = this.props; const { basePath } = this.props;
const { tabs } = this.state; const { tabs } = this.state;
@ -53,7 +53,7 @@ class App extends React.PureComponent<Props, State> {
/> />
</header> </header>
<Switch> <Switch>
<Route path={`${basePath}/proposals`} component={Proposals} /> <Route path={`${basePath}/motions`} component={Motions} />
<Route component={Overview} /> <Route component={Overview} />
</Switch> </Switch>
</main> </main>

View File

@ -1,7 +1,7 @@
// Copyright 2017-2019 @polkadot/app-collective authors & contributors // Copyright 2017-2019 @polkadot/app-council authors & contributors
// This software may be modified and distributed under the terms // This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details. // of the Apache-2.0 license. See the LICENSE file for details.
import { withTranslation } from 'react-i18next'; import { withTranslation } from 'react-i18next';
export default withTranslation(['collective', 'ui']); export default withTranslation(['council', 'ui']);

View File

@ -79,8 +79,8 @@ class Approve extends TxModal<Props, State> {
return ( return (
<Dropdown <Dropdown
help={t('Propose a majority collective proposal to either approve or reject this spend proposal')} help={t('Propose a majority council motion to either approve or reject this spend proposal')}
label={t('proposed collective action')} label={t('proposed council action')}
options={this.approveOptions()} options={this.approveOptions()}
onChange={this.onChangeApproving} onChange={this.onChangeApproving}
value={isApproving} value={isApproving}

View File

@ -84,7 +84,7 @@ class ProposalsBase extends React.PureComponent<Props> {
onRespond = () => { onRespond = () => {
const { history } = this.props; const { history } = this.props;
history.push('/collective/proposals'); history.push('/council/motions');
} }
private onPopulateProposal = () => { private onPopulateProposal = () => {

View File

@ -4,20 +4,20 @@
import { Routes } from './types'; import { Routes } from './types';
import Collective from '@polkadot/app-collective'; import Council from '@polkadot/app-council';
export default ([ export default ([
{ {
Component: Collective, Component: Council,
display: { display: {
needsApi: [ needsApi: [
'query.elections.candidates' 'query.elections.candidates'
] ]
}, },
i18n: { i18n: {
defaultValue: 'Collective' defaultValue: 'Council'
}, },
icon: 'building', icon: 'building',
name: 'collective' name: 'council'
} }
] as Routes); ] as Routes);

View File

@ -10,7 +10,7 @@ import template from './123code';
import accounts from './accounts'; import accounts from './accounts';
import addressbook from './addressbook'; import addressbook from './addressbook';
import contracts from './contracts'; import contracts from './contracts';
import collective from './collective'; import council from './council';
import dashboard from './dashboard'; import dashboard from './dashboard';
import democracy from './democracy'; import democracy from './democracy';
import explorer from './explorer'; import explorer from './explorer';
@ -35,7 +35,7 @@ const routes: Routes = appSettings.uiMode === 'light'
null, null,
staking, staking,
democracy, democracy,
collective, council,
// TODO Not sure about the inclusion of treasury & parachains here // TODO Not sure about the inclusion of treasury & parachains here
null, null,
settings, settings,
@ -50,7 +50,7 @@ const routes: Routes = appSettings.uiMode === 'light'
null, null,
staking, staking,
democracy, democracy,
collective, council,
treasury, treasury,
parachains, parachains,
null, null,

View File

@ -52,6 +52,7 @@ const Wrapper = styled.div`
.labelExtra { .labelExtra {
position: absolute; position: absolute;
text-align: left; text-align: left;
text-transform: lowercase;
top: 0.5rem; top: 0.5rem;
z-index: 1; z-index: 1;
} }

View File

@ -22,7 +22,7 @@ type Props = I18nProps & TxModalProps & {
allAccounts?: SubjectInfo; allAccounts?: SubjectInfo;
hash?: string; hash?: string;
idNumber: BN | number; idNumber: BN | number;
isCollective: boolean; isCouncil: boolean;
proposal?: Proposal | null; proposal?: Proposal | null;
preContent?: React.ReactNode; preContent?: React.ReactNode;
}; };
@ -36,25 +36,25 @@ class Voting extends TxModal<Props, State> {
public state: State; public state: State;
headerText = () => { headerText = () => {
const { isCollective, t } = this.props; const { isCouncil, t } = this.props;
return t(isCollective ? 'Vote on collective proposal' : 'Vote on proposal'); return t(isCouncil ? 'Vote on council proposal' : 'Vote on proposal');
} }
accountLabel = () => this.props.t('Vote with account'); accountLabel = () => this.props.t('Vote with account');
accountHelp = () => this.props.t('Select the account you wish to vote with. You can approve "aye" or deny "nay" the proposal.'); accountHelp = () => this.props.t('Select the account you wish to vote with. You can approve "aye" or deny "nay" the proposal.');
txMethod = () => { txMethod = () => {
const { isCollective } = this.props; const { isCouncil } = this.props;
return isCollective ? 'collective.vote' : 'democracy.vote'; return isCouncil ? 'collective.vote' : 'democracy.vote';
} }
txParams = () => { txParams = () => {
const { hash, idNumber, isCollective } = this.props; const { hash, idNumber, isCouncil } = this.props;
const { voteValue } = this.state; const { voteValue } = this.state;
return isCollective return isCouncil
? [hash!, idNumber, voteValue] ? [hash!, idNumber, voteValue]
: [idNumber, voteValue]; : [idNumber, voteValue];
} }

View File

@ -6,7 +6,7 @@
border-left: 0.25rem solid #f2f2f2; border-left: 0.25rem solid #f2f2f2;
label { label {
text-transform: none; text-transform: none !important;
font-family: monospace; font-family: monospace;
} }

View File

@ -11,8 +11,8 @@
"@polkadot/app-address-book/*": [ "packages/app-address-book/src/*" ], "@polkadot/app-address-book/*": [ "packages/app-address-book/src/*" ],
"@polkadot/app-contracts": [ "packages/app-contracts/src" ], "@polkadot/app-contracts": [ "packages/app-contracts/src" ],
"@polkadot/app-contracts/*": [ "packages/app-contracts/src/*" ], "@polkadot/app-contracts/*": [ "packages/app-contracts/src/*" ],
"@polkadot/app-collective": [ "packages/app-collective/src" ], "@polkadot/app-council": [ "packages/app-council/src" ],
"@polkadot/app-collective/*": [ "packages/app-collective/src/*" ], "@polkadot/app-council/*": [ "packages/app-council/src/*" ],
"@polkadot/app-dashboard": [ "packages/app-dashboard/src" ], "@polkadot/app-dashboard": [ "packages/app-dashboard/src" ],
"@polkadot/app-dashboard/*": [ "packages/app-dashboard/src/*" ], "@polkadot/app-dashboard/*": [ "packages/app-dashboard/src/*" ],
"@polkadot/app-democracy": [ "packages/app-democracy/src" ], "@polkadot/app-democracy": [ "packages/app-democracy/src" ],