From cd53cf1623367044eec15eba02af42a67fc1d845 Mon Sep 17 00:00:00 2001 From: Ewa Kowalska <30404388+ekowalsk@users.noreply.github.com> Date: Thu, 24 Dec 2020 09:52:09 +0100 Subject: [PATCH] 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 --- .../apps/public/locales/en/app-bounties.json | 1 + packages/page-bounties/src/Bounties.tsx | 1 + packages/page-bounties/src/Bounty.tsx | 13 +++++- packages/page-bounties/src/BountyClaim.tsx | 45 +++++++++++++++++++ packages/page-bounties/src/BountyCreate.tsx | 2 +- packages/page-bounties/src/helpers/index.tsx | 6 +++ .../src/helpers/isClaimable.spec.ts | 42 +++++++++++++++++ .../page-bounties/src/helpers/isClaimable.ts | 10 +++++ 8 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 packages/page-bounties/src/BountyClaim.tsx create mode 100644 packages/page-bounties/src/helpers/index.tsx create mode 100644 packages/page-bounties/src/helpers/isClaimable.spec.ts create mode 100644 packages/page-bounties/src/helpers/isClaimable.ts 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()); +}