Bounties - Award beneficiary action (#4501)
This commit is contained in:
parent
6839dfe7a0
commit
f2b1b3f03c
@ -8,18 +8,22 @@
|
||||
"Approval under voting": "Approval under voting",
|
||||
"Approve": "Approve",
|
||||
"Assign curator": "Assign curator",
|
||||
"Award Beneficiary": "Award Beneficiary",
|
||||
"Award bounty to a beneficiary account. The beneficiary will be able to claim the funds after a delay.": "Award bounty to a beneficiary account. The beneficiary will be able to claim the funds after a delay.",
|
||||
"Aye": "Aye",
|
||||
"Aye: {{count}}": "Aye: {{count}}",
|
||||
"Beneficiary": "Beneficiary",
|
||||
"Bond": "Bond",
|
||||
"Bounties": "Bounties",
|
||||
"Choose a curator whose background and expertise is such that they are capable of determining when the task is complete.": "Choose a curator whose background and expertise is such that they are capable of determining when the task is complete.",
|
||||
"Choose the Beneficiary for this bounty.": "Choose the Beneficiary for this bounty.",
|
||||
"Claim": "Claim",
|
||||
"Claimable": "Claimable",
|
||||
"Close": "Close",
|
||||
"Close Bounty": "Close Bounty",
|
||||
"Curator has been proposed by council. The bounty is waiting for curator to accept the role.": "Curator has been proposed by council. The bounty is waiting for curator to accept the role.",
|
||||
"Curator under voting": "Curator under voting",
|
||||
"Curator's account that will award a bounty to the Beneficiary.": "Curator's account that will award a bounty to the Beneficiary.",
|
||||
"Curator's deposit": "Curator's deposit",
|
||||
"Curator's deposit will be slashed and curator will be unassigned. Bounty will return to the Funded state.": "Curator's deposit will be slashed and curator will be unassigned. Bounty will return to the Funded state.",
|
||||
"Curator's fee": "Curator's fee",
|
||||
@ -49,6 +53,7 @@
|
||||
"Select the council account you wish to use to create a proposal for closing bounty.": "Select the council account you wish to use to create a proposal for closing bounty.",
|
||||
"Slash Curator": "Slash Curator",
|
||||
"Slash curator": "Slash curator",
|
||||
"The Curator account that will be used to send this transaction. Any applicable fees will be paid by this account.": "The Curator account that will be used to send this transaction. Any applicable fees will be paid by this account.",
|
||||
"The Curator account that will give up on it's role": "The Curator account that will give up on it's role",
|
||||
"The Curator that will be slashed.": "The Curator that will be slashed.",
|
||||
"The Curator that will be unassigned": "The Curator that will be unassigned",
|
||||
@ -65,6 +70,7 @@
|
||||
"This account will be used to create an extend bounty expire transaction.": "This account will be used to create an extend bounty expire transaction.",
|
||||
"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 Slash the Curator.": "This action will Slash the Curator.",
|
||||
"This action will award the Beneficiary and close the bounty after a delay.": "This action will award the Beneficiary and close the bounty after a delay.",
|
||||
"This action will create a Council motion to assign a Curator.": "This action will create a Council motion to assign a Curator.",
|
||||
"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 action will create a Council motion to slash the Curator.": "This action will create a Council motion to slash the Curator.",
|
||||
@ -83,6 +89,9 @@
|
||||
"Unassign curator under voting": "Unassign curator under voting",
|
||||
"You are giving up your curator role, the bounty will return to the Funded state. You will get your deposit back.": "You are giving up your curator role, the bounty will return to the Funded state. You will get your deposit back.",
|
||||
"active": "active",
|
||||
"award bounty": "award bounty",
|
||||
"award with account": "award with account",
|
||||
"beneficiary account": "beneficiary account",
|
||||
"bounty bond": "bounty bond",
|
||||
"bounty remark": "bounty remark",
|
||||
"bounty requested allocation": "bounty requested allocation",
|
||||
|
||||
@ -22,7 +22,7 @@ import { QueueProps, QueueTxExtrinsicAdd } from '@polkadot/react-components/Stat
|
||||
import { createAugmentedApi } from '@polkadot/test-support/api';
|
||||
import { balanceOf } from '@polkadot/test-support/creation/balance';
|
||||
import { BountyFactory } from '@polkadot/test-support/creation/bounties/bountyFactory';
|
||||
import { aliceSigner, MemoryStore } from '@polkadot/test-support/keyring';
|
||||
import { MemoryStore } from '@polkadot/test-support/keyring';
|
||||
import { TypeRegistry } from '@polkadot/types/create';
|
||||
import { keyring } from '@polkadot/ui-keyring';
|
||||
import { extractTime } from '@polkadot/util';
|
||||
@ -287,7 +287,6 @@ describe('Bounties', () => {
|
||||
|
||||
const proposingAccountInput = comboboxes[0].children[0];
|
||||
const proposingCuratorInput = comboboxes[1].children[0];
|
||||
const alice = aliceSigner().address;
|
||||
|
||||
fireEvent.change(proposingAccountInput, { target: { value: alice } });
|
||||
fireEvent.change(proposingCuratorInput, { target: { value: alice } });
|
||||
@ -376,6 +375,7 @@ describe('Bounties', () => {
|
||||
fireEvent.click(acceptButton);
|
||||
|
||||
expect(queueExtrinsic).toHaveBeenCalledWith(expect.objectContaining({ accountId: alice, extrinsic: 'mockUnassignExtrinsic' }));
|
||||
expect(mockBountyApi.unassignCurator).toHaveBeenCalledWith(aBountyIndex(0));
|
||||
});
|
||||
|
||||
it('for bounty in PendingPayout state', async () => {
|
||||
@ -402,6 +402,31 @@ describe('Bounties', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('award beneficiary action modal', () => {
|
||||
it('awards the beneficiary ', async () => {
|
||||
const bounty = aBounty({ status: bountyStatusWith({ curator: alice }) });
|
||||
const { findByText, getAllByRole } = renderOneBounty(bounty);
|
||||
|
||||
const awardBeneficiaryButton = await findByText('Award Beneficiary');
|
||||
|
||||
fireEvent.click(awardBeneficiaryButton);
|
||||
expect(await findByText('This action will award the Beneficiary and close the bounty after a delay.')).toBeTruthy();
|
||||
const comboboxes = getAllByRole('combobox');
|
||||
|
||||
const beneficiaryAccountInput = comboboxes[1].children[0];
|
||||
|
||||
fireEvent.change(beneficiaryAccountInput, { target: { value: bob } });
|
||||
fireEvent.keyDown(beneficiaryAccountInput, { code: 'Enter', key: 'Enter' });
|
||||
|
||||
const acceptButton = await findByText('Approve');
|
||||
|
||||
fireEvent.click(acceptButton);
|
||||
|
||||
expect(queueExtrinsic).toHaveBeenCalledWith(expect.objectContaining({ accountId: alice, extrinsic: 'mockAwardExtrinsic' }));
|
||||
expect(mockBountyApi.awardBounty).toHaveBeenCalledWith(aBountyIndex(0), bob);
|
||||
});
|
||||
});
|
||||
|
||||
describe('status is extended', () => {
|
||||
it('on proposed curator', async () => {
|
||||
const bounty = bountyWith({ status: 'Funded' });
|
||||
|
||||
92
packages/page-bounties/src/BountyActions/AwardBounty.tsx
Normal file
92
packages/page-bounties/src/BountyActions/AwardBounty.tsx
Normal file
@ -0,0 +1,92 @@
|
||||
// Copyright 2017-2021 @polkadot/app-treasury authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AccountId, BountyIndex } from '@polkadot/types/interfaces';
|
||||
|
||||
import React, { useMemo, useState } from 'react';
|
||||
|
||||
import { Button, InputAddress, Modal, TxButton } from '@polkadot/react-components';
|
||||
import { useAccounts, useToggle } from '@polkadot/react-hooks';
|
||||
|
||||
import { useBounties } from '../hooks';
|
||||
import { useTranslation } from '../translate';
|
||||
|
||||
interface Props {
|
||||
curatorId: AccountId
|
||||
index: BountyIndex;
|
||||
}
|
||||
|
||||
function AwardBounty ({ curatorId, index }: Props): React.ReactElement<Props> | null {
|
||||
const { t } = useTranslation();
|
||||
const { awardBounty } = useBounties();
|
||||
const { allAccounts } = useAccounts();
|
||||
const [isOpen, toggleOpen] = useToggle();
|
||||
const [beneficiaryId, setBeneficiaryId] = useState<string | null>(null);
|
||||
const isCurator = useMemo(() => allAccounts.includes(curatorId.toString()), [allAccounts, curatorId]);
|
||||
|
||||
return isCurator
|
||||
? (
|
||||
<>
|
||||
<Button
|
||||
icon='award'
|
||||
isDisabled={false}
|
||||
label={t<string>('Award Beneficiary')}
|
||||
onClick={toggleOpen}
|
||||
/>
|
||||
{isOpen && (
|
||||
<Modal
|
||||
header={t<string>('award bounty')}
|
||||
size='large'
|
||||
>
|
||||
<Modal.Content>
|
||||
<Modal.Column>
|
||||
<p>{t<string>('This action will award the Beneficiary and close the bounty after a delay.')}</p>
|
||||
</Modal.Column>
|
||||
<Modal.Columns>
|
||||
<Modal.Column>
|
||||
<InputAddress
|
||||
defaultValue={curatorId}
|
||||
help={t<string>("Curator's account that will award a bounty to the Beneficiary.")}
|
||||
isDisabled={true}
|
||||
label={t<string>('award with account')}
|
||||
type='account'
|
||||
withLabel
|
||||
/>
|
||||
</Modal.Column>
|
||||
<Modal.Column>
|
||||
<p>{t<string>('The Curator account that will be used to send this transaction. Any applicable fees will be paid by this account.')}</p>
|
||||
</Modal.Column>
|
||||
</Modal.Columns>
|
||||
<Modal.Columns>
|
||||
<Modal.Column>
|
||||
<InputAddress
|
||||
help={t<string>('Choose the Beneficiary for this bounty.')}
|
||||
label={t<string>('beneficiary account')}
|
||||
onChange={setBeneficiaryId}
|
||||
type='account'
|
||||
withLabel
|
||||
/>
|
||||
</Modal.Column>
|
||||
<Modal.Column>
|
||||
<p>{t<string>('Award bounty to a beneficiary account. The beneficiary will be able to claim the funds after a delay.')}</p>
|
||||
</Modal.Column>
|
||||
</Modal.Columns>
|
||||
</Modal.Content>
|
||||
<Modal.Actions onCancel={toggleOpen}>
|
||||
<TxButton
|
||||
accountId={curatorId}
|
||||
icon='check'
|
||||
label={t<string>('Approve')}
|
||||
onStart={toggleOpen}
|
||||
params={[index, beneficiaryId]}
|
||||
tx={awardBounty}
|
||||
/>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
: null;
|
||||
}
|
||||
|
||||
export default React.memo(AwardBounty);
|
||||
@ -7,6 +7,7 @@ import type { Balance, BlockNumber, BountyIndex, BountyStatus } from '@polkadot/
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
|
||||
import { getBountyStatus } from '../helpers';
|
||||
import AwardBounty from './AwardBounty';
|
||||
import BountyClaimAction from './BountyClaimAction';
|
||||
import BountyCuratorProposedActions from './BountyCuratorProposedActions';
|
||||
import BountyInitiateVoting from './BountyInitiateVoting';
|
||||
@ -84,6 +85,12 @@ export function BountyActions ({ bestNumber, description, index, proposals, stat
|
||||
status={status}
|
||||
/>
|
||||
)}
|
||||
{status.isActive && curator &&
|
||||
<AwardBounty
|
||||
curatorId={curator}
|
||||
index={index}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ import { BalanceOf, BlockNumber, BountyIndex } from '@polkadot/types/interfaces'
|
||||
export type BountyApi = {
|
||||
acceptCurator: ((...args: any[]) => SubmittableExtrinsic<'promise'>);
|
||||
approveBounty: ((...args: any[]) => SubmittableExtrinsic<'promise'>);
|
||||
awardBounty: ((...args: any[]) => SubmittableExtrinsic<'promise'>);
|
||||
bestNumber?: BlockNumber,
|
||||
bounties?: DeriveBounties,
|
||||
bountyDepositBase: BN,
|
||||
@ -45,10 +46,12 @@ export function useBounties (): BountyApi {
|
||||
const closeBounty = (api.tx.bounties || api.tx.treasury).closeBounty;
|
||||
const extendBountyExpiry = (api.tx.bounties || api.tx.treasury).extendBountyExpiry;
|
||||
const unassignCurator = (api.tx.bounties || api.tx.treasury).unassignCurator;
|
||||
const awardBounty = (api.tx.bounties || api.tx.treasury).awardBounty;
|
||||
|
||||
return {
|
||||
acceptCurator,
|
||||
approveBounty,
|
||||
awardBounty,
|
||||
bestNumber,
|
||||
bounties,
|
||||
bountyDepositBase,
|
||||
|
||||
@ -11,6 +11,7 @@ import { BlockNumber } from '@polkadot/types/interfaces';
|
||||
export const defaultBountyApi: BountyApi = {
|
||||
acceptCurator: jest.fn(),
|
||||
approveBounty: jest.fn(),
|
||||
awardBounty: jest.fn().mockReturnValue('mockAwardExtrinsic'),
|
||||
bestNumber: new BN(1) as BlockNumber,
|
||||
bounties: [] as DeriveBounties,
|
||||
bountyDepositBase: new BN(1),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user