Claim payout from bounty list (#4269)

* Add Claim functionality to bounty list

* Apply review suggestions

* Apply review suggestions

* Add tests for isClaimable

* Apply review suggestions
This commit is contained in:
Ewa Kowalska 2020-12-24 09:52:09 +01:00 committed by GitHub
parent e9fe9ff26c
commit cd53cf1623
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 118 additions and 2 deletions

View File

@ -4,6 +4,7 @@
"Bond is estimated based on bountyDepositBase and dataDepositPerByte constants.": "Bond is estimated based on bountyDepositBase and dataDepositPerByte constants.",
"Bounties": "Bounties",
"Calculated bond for bounty": "Calculated bond for bounty",
"Claim Bounty": "Claim Bounty",
"Claimable": "Claimable",
"Curators deposit": "Curators deposit",
"Curators fee": "Curators fee",

View File

@ -28,6 +28,7 @@ function Bounties (): React.ReactElement {
[t('beneficiary'), 'start'],
[t('payout due'), 'start'],
[],
[],
[]
]);

View File

@ -11,7 +11,8 @@ import { AddressSmall, Icon, LinkExternal } from '@polkadot/react-components';
import { BlockToTime, FormatBalance } from '@polkadot/react-query';
import { formatNumber } from '@polkadot/util';
import { getBountyStatus } from './helpers/getBountyStatus';
import BountyClaim from './BountyClaim';
import { getBountyStatus } from './helpers';
import { useTranslation } from './translate';
interface Props {
@ -56,6 +57,15 @@ function Bounty ({ bestNumber, bounty, className = '', description, index }: Pro
<td><DueBlocks dueBlocks={blocksUntilUpdate} /></td>
<td>{beneficiary ? <AddressSmall value={beneficiary} /> : EMPTY_CELL}</td>
<td><DueBlocks dueBlocks={blocksUntilPayout} /></td>
<td>
{beneficiary && blocksUntilPayout && (
<BountyClaim
beneficiaryId={beneficiary}
index={index}
payoutDue={blocksUntilPayout}
/>
)}
</td>
<td className='table-column-icon'>
<LinkExternal
data={index}
@ -101,6 +111,7 @@ function Bounty ({ bestNumber, bounty, className = '', description, index }: Pro
<td />
<td />
<td />
<td />
</tr>
</>
);

View File

@ -0,0 +1,45 @@
// Copyright 2017-2020 @polkadot/app-bounties authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { AccountId } from '@polkadot/types/interfaces';
import BN from 'bn.js';
import React, { useMemo } from 'react';
import { TxButton } from '@polkadot/react-components';
import { useAccounts, useApi } from '@polkadot/react-hooks';
import { isClaimable } from './helpers';
import { useTranslation } from './translate';
interface Props {
beneficiaryId: AccountId;
index: number;
payoutDue: BN;
}
function BountyClaim ({ beneficiaryId, index, payoutDue }: Props) {
const { t } = useTranslation();
const { api } = useApi();
const { allAccounts, hasAccounts } = useAccounts();
const isBountyClaimable = useMemo(() => isClaimable(allAccounts, beneficiaryId, payoutDue), [allAccounts, beneficiaryId, payoutDue]);
return hasAccounts && isBountyClaimable
? (
<TxButton
accountId={beneficiaryId}
icon='plus'
label={t<string>('Claim Bounty')}
params={[index]}
tx={
api.tx.bounties
? 'bounties.claimBounty'
: 'treasury.claimBounty'
}
/>
)
: null;
}
export default React.memo(BountyClaim);

View File

@ -10,7 +10,7 @@ import { Button, Input, InputAddress, InputBalance, Modal, TxButton } from '@pol
import { useApi, useToggle } from '@polkadot/react-hooks';
import { BN_ZERO } from '@polkadot/util';
import { calculateBountyBond } from './helpers/calculateBountyBond';
import { calculateBountyBond } from './helpers';
import { useTranslation } from './translate';
function BountyCreate () {

View File

@ -0,0 +1,6 @@
// Copyright 2017-2020 @polkadot/app-bounties authors & contributors
// SPDX-License-Identifier: Apache-2.0
export * from './calculateBountyBond';
export * from './getBountyStatus';
export * from './isClaimable';

View File

@ -0,0 +1,42 @@
// Copyright 2017-2020 @polkadot/app-bounties authors & contributors
// SPDX-License-Identifier: Apache-2.0
import BN from 'bn.js';
import { TypeRegistry } from '@polkadot/types/create';
import { isClaimable } from './isClaimable';
describe('Is claimable', () => {
const registry = new TypeRegistry();
const accountAddress = '5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM';
const beneficiaryId = registry.createType('AccountId', [registry, accountAddress]);
it('returns false, when payout due is greater than 0', () => {
const accounts = [accountAddress];
const payoutDue = new BN('1');
expect(isClaimable(accounts, beneficiaryId, payoutDue)).toBe(false);
});
it('returns false, when payout due is equal 0', () => {
const accounts = [accountAddress];
const payoutDue = new BN('0');
expect(isClaimable(accounts, beneficiaryId, payoutDue)).toBe(false);
});
it('returns true, when payout due is lesser than 0 and beneficiary is among accounts', () => {
const accounts = [accountAddress];
const payoutDue = new BN('-1');
expect(isClaimable(accounts, beneficiaryId, payoutDue)).toBe(true);
});
it('returns false, when beneficiary is not among accounts', () => {
const accounts = ['This_is_not_the_treasury_address_27Tt8tkntv6Q7JVPhFsTB'];
const payoutDue = new BN('-1');
expect(isClaimable(accounts, beneficiaryId, payoutDue)).toBe(false);
});
});

View File

@ -0,0 +1,10 @@
// Copyright 2017-2020 @polkadot/app-council authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { AccountId } from '@polkadot/types/interfaces';
import BN from 'bn.js';
export function isClaimable (accounts: string[], beneficiary: AccountId, payoutDue: BN): boolean {
return payoutDue.ltn(0) && accounts.includes(beneficiary.toString());
}