diff --git a/packages/apps/public/locales/en/app-bounties.json b/packages/apps/public/locales/en/app-bounties.json index 6327266528..f88147cd93 100644 --- a/packages/apps/public/locales/en/app-bounties.json +++ b/packages/apps/public/locales/en/app-bounties.json @@ -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" } \ No newline at end of file diff --git a/packages/apps/public/locales/en/app-treasury.json b/packages/apps/public/locales/en/app-treasury.json index ab4f3b22d5..de6d5ca3ff 100644 --- a/packages/apps/public/locales/en/app-treasury.json +++ b/packages/apps/public/locales/en/app-treasury.json @@ -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", diff --git a/packages/page-bounties/src/Bounties.tsx b/packages/page-bounties/src/Bounties.tsx index 74d2ce2f5f..706e99bd79 100644 --- a/packages/page-bounties/src/Bounties.tsx +++ b/packages/page-bounties/src/Bounties.tsx @@ -41,13 +41,14 @@ function Bounties (): React.ReactElement { empty={deriveBounties && t('No open bounties')} header={headerRef.current} > - {deriveBounties && bestNumber && deriveBounties.map(({ bounty, description, index }) => ( + {deriveBounties && bestNumber && deriveBounties.map(({ bounty, description, index, proposals }) => ( ))} diff --git a/packages/page-bounties/src/Bounty.tsx b/packages/page-bounties/src/Bounty.tsx index 771321bd51..78848bf104 100644 --- a/packages/page-bounties/src/Bounty.tsx +++ b/packages/page-bounties/src/Bounty.tsx @@ -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 { +function Bounty ({ bestNumber, bounty, className = '', description, index, proposals }: Props): React.ReactElement { const { t } = useTranslation(); const [isExpanded, setIsExpanded] = useState(false); @@ -61,6 +63,7 @@ function Bounty ({ bestNumber, bounty, className = '', description, index }: Pro diff --git a/packages/page-bounties/src/BountyActions.tsx b/packages/page-bounties/src/BountyActions.tsx index be4e871e98..64728b7d95 100644 --- a/packages/page-bounties/src/BountyActions.tsx +++ b/packages/page-bounties/src/BountyActions.tsx @@ -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 && + } {status.isCuratorProposed && curator && | null { + const { t } = useTranslation(); + const { api } = useApi(); + const { isMember, members } = useMembers(); + const [isOpen, toggleOpen] = useToggle(); + const [accountId, setAccountId] = useState(null); + const [threshold, setThreshold] = useState(); + + 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 + ? ( + <> +