diff --git a/packages/apps/public/locales/en/app-bounties.json b/packages/apps/public/locales/en/app-bounties.json
index 4a00a23431..469a740024 100644
--- a/packages/apps/public/locales/en/app-bounties.json
+++ b/packages/apps/public/locales/en/app-bounties.json
@@ -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",
diff --git a/packages/page-bounties/src/Bounties.tsx b/packages/page-bounties/src/Bounties.tsx
index 99d7feb95f..91b361cbbe 100644
--- a/packages/page-bounties/src/Bounties.tsx
+++ b/packages/page-bounties/src/Bounties.tsx
@@ -28,6 +28,7 @@ function Bounties (): React.ReactElement {
[t('beneficiary'), 'start'],
[t('payout due'), 'start'],
[],
+ [],
[]
]);
diff --git a/packages/page-bounties/src/Bounty.tsx b/packages/page-bounties/src/Bounty.tsx
index 7c9709837d..79d11fde43 100644
--- a/packages/page-bounties/src/Bounty.tsx
+++ b/packages/page-bounties/src/Bounty.tsx
@@ -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
|
{beneficiary ? : EMPTY_CELL} |
|
+
+ {beneficiary && blocksUntilPayout && (
+
+ )}
+ |
| |
|
+ |
>
);
diff --git a/packages/page-bounties/src/BountyClaim.tsx b/packages/page-bounties/src/BountyClaim.tsx
new file mode 100644
index 0000000000..514856d82b
--- /dev/null
+++ b/packages/page-bounties/src/BountyClaim.tsx
@@ -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
+ ? (
+ ('Claim Bounty')}
+ params={[index]}
+ tx={
+ api.tx.bounties
+ ? 'bounties.claimBounty'
+ : 'treasury.claimBounty'
+ }
+ />
+ )
+ : null;
+}
+
+export default React.memo(BountyClaim);
diff --git a/packages/page-bounties/src/BountyCreate.tsx b/packages/page-bounties/src/BountyCreate.tsx
index ad93d2add5..48cc8300ff 100644
--- a/packages/page-bounties/src/BountyCreate.tsx
+++ b/packages/page-bounties/src/BountyCreate.tsx
@@ -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 () {
diff --git a/packages/page-bounties/src/helpers/index.tsx b/packages/page-bounties/src/helpers/index.tsx
new file mode 100644
index 0000000000..01ce75df59
--- /dev/null
+++ b/packages/page-bounties/src/helpers/index.tsx
@@ -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';
diff --git a/packages/page-bounties/src/helpers/isClaimable.spec.ts b/packages/page-bounties/src/helpers/isClaimable.spec.ts
new file mode 100644
index 0000000000..7f2727ea34
--- /dev/null
+++ b/packages/page-bounties/src/helpers/isClaimable.spec.ts
@@ -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);
+ });
+});
diff --git a/packages/page-bounties/src/helpers/isClaimable.ts b/packages/page-bounties/src/helpers/isClaimable.ts
new file mode 100644
index 0000000000..972674d4a5
--- /dev/null
+++ b/packages/page-bounties/src/helpers/isClaimable.ts
@@ -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());
+}