Initiate voting from bounty list - accept / reject bounty (#4320)
* Add initiate voting functionality * Use bounty proposals from bounty derive * Apply review suggestions * Apply review suggestions * Use useRef for approveBounty and closeBounty extrinsics
This commit is contained in:
parent
adf3f3b160
commit
967d10b6cb
@ -3,6 +3,7 @@
|
||||
"Account does not have enough funds.": "Account does not have enough funds.",
|
||||
"Add Bounty": "Add Bounty",
|
||||
"Allocation value is smaller than the minimum bounty value.": "Allocation value is smaller than the minimum bounty value.",
|
||||
"Approve": "Approve",
|
||||
"Bond": "Bond",
|
||||
"Bond is estimated based on bountyDepositBase and dataDepositPerByte constants.": "Bond is estimated based on bountyDepositBase and dataDepositPerByte constants.",
|
||||
"Bounties": "Bounties",
|
||||
@ -13,14 +14,19 @@
|
||||
"Curators fee": "Curators fee",
|
||||
"Description of the Bounty (to be stored on-chain)": "Description of the Bounty (to be stored on-chain)",
|
||||
"How much should be paid out for completed Bounty. Upon funding, the amount will be reserved in treasury.": "How much should be paid out for completed Bounty. Upon funding, the amount will be reserved in treasury.",
|
||||
"Initiate Voting": "Initiate Voting",
|
||||
"No open bounties": "No open bounties",
|
||||
"Proposer": "Proposer",
|
||||
"Proposer bond depends on bounty title length.": "Proposer bond depends on bounty title length.",
|
||||
"Reject": "Reject",
|
||||
"Select the account you wish to propose the bounty from.": "Select the account you wish to propose the bounty from.",
|
||||
"Select the council account you wish to use to create a motion for the Bounty.": "Select the council account you wish to use to create a motion for the Bounty.",
|
||||
"The council member that is will create a motion, submission equates to an \"aye\" vote for chosen option.": "The council member that is will create a motion, submission equates to an \"aye\" vote for chosen option.",
|
||||
"The council member that will create a motion, submission equates to an \"aye\" vote for chosen option.": "The council member that will create a motion, submission equates to an \"aye\" vote for chosen option.",
|
||||
"The description of this bounty": "The description of this bounty",
|
||||
"The total payment amount of this bounty, curators fee included.": "The total payment amount of this bounty, curators fee included.",
|
||||
"This account will propose the bounty. Bond amount will be reserved on its balance.": "This account will propose the bounty. Bond amount will be reserved on its balance.",
|
||||
"This action will create a Council motion to either approve or reject the Bounty.": "This action will create a Council motion to either approve or reject the Bounty.",
|
||||
"This amount will be reserved from origin account and returned on approval or slashed upon rejection.": "This amount will be reserved from origin account and returned on approval or slashed upon rejection.",
|
||||
"Title too long": "Title too long",
|
||||
"Use this account to propose bounty from. This can be a normal or council account.": "Use this account to propose bounty from. This can be a normal or council account.",
|
||||
@ -36,5 +42,6 @@
|
||||
"title": "title",
|
||||
"update due": "update due",
|
||||
"value": "value",
|
||||
"vote with account": "vote with account",
|
||||
"{{blocks}} blocks": "{{blocks}} blocks"
|
||||
}
|
||||
@ -12,6 +12,7 @@
|
||||
"No open tips": "No open tips",
|
||||
"No pending proposals": "No pending proposals",
|
||||
"Of the beneficiary amount, at least {{bondPercentage}} would need to be put up as collateral. The maximum of this and the minimum bond will be used to secure the proposal, refundable if it passes.": "Of the beneficiary amount, at least {{bondPercentage}} would need to be put up as collateral. The maximum of this and the minimum bond will be used to secure the proposal, refundable if it passes.",
|
||||
"Proposal can either be to approve or reject this spend. Once approved, the change is applied by either removing the proposal or scheduling payout.": "Proposal can either be to approve or reject this spend. Once approved, the change is applied by either removing the proposal or scheduling payout.",
|
||||
"Proposal can either be to approve or reject this spend. One approved, the change is applied by either removing the proposal or scheduling payout.": "Proposal can either be to approve or reject this spend. One approved, the change is applied by either removing the proposal or scheduling payout.",
|
||||
"Proposals": "Proposals",
|
||||
"Propose tip": "Propose tip",
|
||||
|
||||
@ -41,13 +41,14 @@ function Bounties (): React.ReactElement {
|
||||
empty={deriveBounties && t<string>('No open bounties')}
|
||||
header={headerRef.current}
|
||||
>
|
||||
{deriveBounties && bestNumber && deriveBounties.map(({ bounty, description, index }) => (
|
||||
{deriveBounties && bestNumber && deriveBounties.map(({ bounty, description, index, proposals }) => (
|
||||
<Bounty
|
||||
bestNumber={bestNumber}
|
||||
bounty={bounty}
|
||||
description={description}
|
||||
index={index}
|
||||
key={index.toNumber()}
|
||||
proposals={proposals}
|
||||
/>
|
||||
))}
|
||||
</Table>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// Copyright 2017-2021 @polkadot/app-bounties authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { DeriveCollectiveProposal } from '@polkadot/api-derive/types';
|
||||
import type { BlockNumber, Bounty as BountyType } from '@polkadot/types/interfaces';
|
||||
|
||||
import BN from 'bn.js';
|
||||
@ -21,6 +22,7 @@ interface Props {
|
||||
className?: string;
|
||||
description: string;
|
||||
index: number;
|
||||
proposals?: DeriveCollectiveProposal[];
|
||||
}
|
||||
|
||||
interface DueProps {
|
||||
@ -29,7 +31,7 @@ interface DueProps {
|
||||
|
||||
const EMPTY_CELL = '-';
|
||||
|
||||
function Bounty ({ bestNumber, bounty, className = '', description, index }: Props): React.ReactElement<Props> {
|
||||
function Bounty ({ bestNumber, bounty, className = '', description, index, proposals }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
|
||||
@ -61,6 +63,7 @@ function Bounty ({ bestNumber, bounty, className = '', description, index }: Pro
|
||||
<BountyActions
|
||||
bestNumber={bestNumber}
|
||||
index={index}
|
||||
proposals={proposals}
|
||||
status={status}
|
||||
/>
|
||||
</td>
|
||||
|
||||
@ -1,21 +1,24 @@
|
||||
// Copyright 2017-2021 @polkadot/app-bounties authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { DeriveCollectiveProposal } from '@polkadot/api-derive/types';
|
||||
import type { BlockNumber, BountyStatus } from '@polkadot/types/interfaces';
|
||||
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
|
||||
import BountyClaimAction from './BountyClaimAction';
|
||||
import BountyCuratorProposedActions from './BountyCuratorProposedActions';
|
||||
import BountyInitiateVoting from './BountyInitiateVoting';
|
||||
import { getBountyStatus } from './helpers';
|
||||
|
||||
interface Props {
|
||||
bestNumber: BlockNumber;
|
||||
index: number;
|
||||
proposals?: DeriveCollectiveProposal[];
|
||||
status: BountyStatus;
|
||||
}
|
||||
|
||||
export function BountyActions ({ bestNumber, index, status }: Props): JSX.Element {
|
||||
export function BountyActions ({ bestNumber, index, proposals, status }: Props): JSX.Element {
|
||||
const updateStatus = useCallback(() => getBountyStatus(status), [status]);
|
||||
|
||||
const { beneficiary, curator, unlockAt } = updateStatus();
|
||||
@ -24,6 +27,11 @@ export function BountyActions ({ bestNumber, index, status }: Props): JSX.Elemen
|
||||
|
||||
return (
|
||||
<>
|
||||
{status.isProposed &&
|
||||
<BountyInitiateVoting
|
||||
index={index}
|
||||
proposals={proposals}
|
||||
/>}
|
||||
{status.isCuratorProposed && curator &&
|
||||
<BountyCuratorProposedActions
|
||||
curatorId={curator}
|
||||
|
||||
106
packages/page-bounties/src/BountyInitiateVoting.tsx
Normal file
106
packages/page-bounties/src/BountyInitiateVoting.tsx
Normal file
@ -0,0 +1,106 @@
|
||||
// Copyright 2017-2020 @polkadot/app-treasury authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { DeriveCollectiveProposal } from '@polkadot/api-derive/types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { getTreasuryProposalThreshold } from '@polkadot/apps-config';
|
||||
import { Button, InputAddress, Modal, TxButton } from '@polkadot/react-components';
|
||||
import { useApi, useMembers, useToggle } from '@polkadot/react-hooks';
|
||||
|
||||
import { useTranslation } from './translate';
|
||||
|
||||
interface Props {
|
||||
index: number;
|
||||
proposals?: DeriveCollectiveProposal[];
|
||||
}
|
||||
|
||||
const BOUNTY_METHODS = ['approveBounty', 'closeBounty'];
|
||||
|
||||
function BountyInitiateVoting ({ index, proposals }: Props): React.ReactElement<Props> | null {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const { isMember, members } = useMembers();
|
||||
const [isOpen, toggleOpen] = useToggle();
|
||||
const [accountId, setAccountId] = useState<string | null>(null);
|
||||
const [threshold, setThreshold] = useState<BN>();
|
||||
|
||||
useEffect((): void => {
|
||||
members && setThreshold(
|
||||
new BN(Math.ceil(members.length * getTreasuryProposalThreshold(api)))
|
||||
);
|
||||
}, [api, members]);
|
||||
|
||||
const approveBountyProposal = useRef((api.tx.bounties || api.tx.treasury).approveBounty(index));
|
||||
const closeBountyProposal = useRef((api.tx.bounties || api.tx.treasury).closeBounty(index));
|
||||
|
||||
const isVotingInitiated = useMemo(() => proposals?.filter(({ proposal }) => BOUNTY_METHODS.includes(proposal.method)).length !== 0, [proposals]);
|
||||
|
||||
return isMember && !isVotingInitiated
|
||||
? (
|
||||
<>
|
||||
<Button
|
||||
icon='step-forward'
|
||||
isDisabled={false}
|
||||
label={t<string>('Initiate Voting')}
|
||||
onClick={toggleOpen}
|
||||
/>
|
||||
{isOpen && (
|
||||
<Modal
|
||||
header={t<string>('Initiate Voting')}
|
||||
size='large'
|
||||
>
|
||||
<Modal.Content>
|
||||
<Modal.Columns>
|
||||
<Modal.Column>
|
||||
<p>{t<string>('This action will create a Council motion to either approve or reject the Bounty.')}</p>
|
||||
</Modal.Column>
|
||||
<Modal.Column>
|
||||
</Modal.Column>
|
||||
</Modal.Columns>
|
||||
<Modal.Columns>
|
||||
<Modal.Column>
|
||||
<InputAddress
|
||||
filter={members}
|
||||
help={t<string>('Select the council account you wish to use to create a motion for the Bounty.')}
|
||||
label={t<string>('vote with account')}
|
||||
onChange={setAccountId}
|
||||
type='account'
|
||||
withLabel
|
||||
/>
|
||||
</Modal.Column>
|
||||
<Modal.Column>
|
||||
<p>{t<string>('The council member that will create a motion, submission equates to an "aye" vote for chosen option.')}</p>
|
||||
</Modal.Column>
|
||||
</Modal.Columns>
|
||||
</Modal.Content>
|
||||
<Modal.Actions onCancel={toggleOpen}>
|
||||
<TxButton
|
||||
accountId={accountId}
|
||||
icon='check'
|
||||
isDisabled={false}
|
||||
label={t<string>('Approve')}
|
||||
onStart={toggleOpen}
|
||||
params={[threshold, approveBountyProposal.current, approveBountyProposal.current.length]}
|
||||
tx={api.tx.council.propose}
|
||||
/>
|
||||
<TxButton
|
||||
accountId={accountId}
|
||||
icon='ban'
|
||||
isDisabled={false}
|
||||
label={t<string>('Reject')}
|
||||
onStart={toggleOpen}
|
||||
params={[threshold, closeBountyProposal.current, closeBountyProposal.current.length]}
|
||||
tx={api.tx.council.propose}
|
||||
/>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
: null;
|
||||
}
|
||||
|
||||
export default React.memo(BountyInitiateVoting);
|
||||
@ -80,7 +80,7 @@ function Council ({ id, isDisabled, members }: Props): React.ReactElement<Props>
|
||||
/>
|
||||
</Modal.Column>
|
||||
<Modal.Column>
|
||||
<p>{t<string>('Proposal can either be to approve or reject this spend. One approved, the change is applied by either removing the proposal or scheduling payout.')}</p>
|
||||
<p>{t<string>('Proposal can either be to approve or reject this spend. Once approved, the change is applied by either removing the proposal or scheduling payout.')}</p>
|
||||
</Modal.Column>
|
||||
</Modal.Columns>
|
||||
</Modal.Content>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user