diff --git a/packages/page-bounties/src/Bounties.spec.ts b/packages/page-bounties/src/Bounties.spec.ts index 40dab79c24..4ad4d9efa7 100644 --- a/packages/page-bounties/src/Bounties.spec.ts +++ b/packages/page-bounties/src/Bounties.spec.ts @@ -463,8 +463,7 @@ describe('Bounties', () => { { curator: alice, status: 'Active', - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - updateDue: mockBountyHooks.bountyApi.bestNumber!.toNumber() + updateDue: mockBountyHooks.bountyApi.bestNumber?.toNumber() }) }); bountiesPage.renderOne(bounty); diff --git a/packages/page-bounties/src/Bounties.tsx b/packages/page-bounties/src/Bounties.tsx index ab72eb7e32..d107e330d5 100644 --- a/packages/page-bounties/src/Bounties.tsx +++ b/packages/page-bounties/src/Bounties.tsx @@ -1,7 +1,7 @@ // Copyright 2017-2022 @polkadot/app-bounties authors & contributors // SPDX-License-Identifier: Apache-2.0 -import React, { useRef } from 'react'; +import React, { useMemo, useRef } from 'react'; import styled from 'styled-components'; import Summary from '@polkadot/app-bounties/Summary'; @@ -18,7 +18,12 @@ interface Props { function Bounties ({ className }: Props): React.ReactElement { const { t } = useTranslation(); - const { bestNumber, bounties } = useBounties(); + const info = useBounties(); + + const sorted = useMemo( + () => info && info.bounties && [...info.bounties].sort((a, b) => b.index.cmp(a.index)), + [info] + ); const headerRef = useRef([ [t('bounties'), 'start', 3], @@ -29,30 +34,26 @@ function Bounties ({ className }: Props): React.ReactElement { return (
- + ('No open bounties')} + empty={sorted && t('No open bounties')} header={headerRef.current} withCollapsibleRows > - {bounties && bestNumber && - bounties - .sort((a, b) => b.index.cmp(a.index)) - .map(({ bounty, description, index, proposals }) => ( - - )) - } + {sorted && info.bestNumber && sorted.map(({ bounty, description, index, proposals }) => ( + + ))}
); diff --git a/packages/page-bounties/src/Bounty.tsx b/packages/page-bounties/src/Bounty.tsx index 64223eb390..879eea0dc6 100644 --- a/packages/page-bounties/src/Bounty.tsx +++ b/packages/page-bounties/src/Bounty.tsx @@ -33,8 +33,6 @@ interface Props { proposals?: DeriveCollectiveProposal[]; } -const EMPTY_CELL = '-'; - function Bounty ({ bestNumber, bounty, className = '', description, index, proposals }: Props): React.ReactElement { const { t } = useTranslation(); const [isExpanded, toggleExpanded] = useToggle(false); @@ -155,18 +153,18 @@ function Bounty ({ bestNumber, bounty, className = '', description, index, propo
{t('Bond')}
+ {curator && ( +
+
{t("Curator's fee")}
+
{}
+
+ )}
-
{t("Curator's fee")}
-
{curator ? : EMPTY_CELL}
-
-
- {!curatorDeposit.isZero && ( + {curator && !curatorDeposit.isZero() && ( <>
{t("Curator's deposit")}
- {curator - ? - : EMPTY_CELL} +
)} diff --git a/packages/page-bounties/src/Summary.tsx b/packages/page-bounties/src/Summary.tsx index 42a64a0847..d6fb171c86 100644 --- a/packages/page-bounties/src/Summary.tsx +++ b/packages/page-bounties/src/Summary.tsx @@ -1,24 +1,24 @@ // Copyright 2017-2022 @polkadot/app-bounties authors & contributors // SPDX-License-Identifier: Apache-2.0 +import type { BountyApi } from './hooks/useBounties'; + import React, { useMemo } from 'react'; import { CardSummary, SummaryBox } from '@polkadot/react-components'; import { useTreasury } from '@polkadot/react-hooks'; import { FormatBalance } from '@polkadot/react-query'; -import { BN } from '@polkadot/util'; +import { BN, formatNumber } from '@polkadot/util'; -import { useBounties } from './hooks'; import { useTranslation } from './translate'; interface Props { - activeBounties?: number; className?: string; + info: BountyApi; } -function Summary ({ activeBounties, className = '' }: Props): React.ReactElement { +function Summary ({ className = '', info: { bestNumber, bounties, bountyCount, childCount } }: Props): React.ReactElement { const { t } = useTranslation(); - const { bestNumber, bounties, bountyIndex } = useBounties(); const { spendPeriod } = useTreasury(); const totalValue = useMemo( @@ -29,12 +29,19 @@ function Summary ({ activeBounties, className = '' }: Props): React.ReactElement return (
- ('active')}> - {activeBounties} - - {activeBounties !== undefined && ( + {bounties && ( + ('active')}> + {formatNumber(bounties.length)} + + )} + {bountyCount && bounties && ( ('past')}> - {bountyIndex?.subn(activeBounties).toString()} + {formatNumber(bountyCount.subn(bounties.length))} + + )} + {childCount && ( + ('children')}> + {formatNumber(childCount)} )}
@@ -47,7 +54,7 @@ function Summary ({ activeBounties, className = '' }: Props): React.ReactElement
- {bestNumber && spendPeriod.gtn(0) && ( + {bestNumber && !spendPeriod.isZero() && ( ('funding period')} progress={{ diff --git a/packages/page-bounties/src/hooks/useBounties.tsx b/packages/page-bounties/src/hooks/useBounties.tsx index 5c1fe21bf6..6d026f57b9 100644 --- a/packages/page-bounties/src/hooks/useBounties.tsx +++ b/packages/page-bounties/src/hooks/useBounties.tsx @@ -1,77 +1,98 @@ // Copyright 2017-2022 @polkadot/app-bounties authors & contributors // SPDX-License-Identifier: Apache-2.0 +import type { ApiPromise } from '@polkadot/api'; import type { SubmittableExtrinsicFunction } from '@polkadot/api/types'; import type { BN } from '@polkadot/util'; +import { useMemo } from 'react'; + import { DeriveBounties } from '@polkadot/api-derive/types'; import { createNamedHook, useApi, useBestNumber, useCall } from '@polkadot/react-hooks'; -import { BalanceOf, BlockNumber, BountyIndex } from '@polkadot/types/interfaces'; +import { BalanceOf } from '@polkadot/types/interfaces'; import { BN_ZERO } from '@polkadot/util'; -export type BountyApi = { +interface BountyApiTxs { acceptCurator: SubmittableExtrinsicFunction<'promise'>; approveBounty: SubmittableExtrinsicFunction<'promise'>; awardBounty: SubmittableExtrinsicFunction<'promise'>; - bestNumber?: BlockNumber, - bounties?: DeriveBounties, - bountyCuratorDeposit: BN, - bountyDepositBase: BN, - bountyIndex?: BN, - bountyUpdatePeriod?: BN, - bountyValueMinimum: BN, claimBounty: SubmittableExtrinsicFunction<'promise'>; closeBounty: SubmittableExtrinsicFunction<'promise'>; - dataDepositPerByte: BN, extendBountyExpiry: SubmittableExtrinsicFunction<'promise'>; - maximumReasonLength: number, proposeBounty: SubmittableExtrinsicFunction<'promise'>; proposeCurator: SubmittableExtrinsicFunction<'promise'>; unassignCurator: SubmittableExtrinsicFunction<'promise'>; -}; +} + +interface BountyApiConstants { + bountyCuratorDeposit: BN; + bountyDepositBase: BN; + bountyUpdatePeriod?: BN; + bountyValueMinimum: BN; + dataDepositPerByte: BN; + maximumReasonLength: number; +} + +interface BountyApiStatics extends BountyApiConstants, BountyApiTxs { + // nothing additional +} + +export interface BountyApi extends BountyApiStatics { + bestNumber?: BN; + bounties?: DeriveBounties; + bountyCount?: BN; + childCount?: BN; +} + +function getStatics (api: ApiPromise): BountyApiStatics { + const constsBase = api.consts.bounties || api.consts.treasury; + const txBase = api.tx.bounties || api.tx.treasury; + + return { + // constants + bountyCuratorDeposit: (constsBase.bountyCuratorDeposit as BalanceOf || { toBn: () => BN_ZERO }).toBn(), + bountyDepositBase: (constsBase.bountyDepositBase as BalanceOf).toBn(), + bountyUpdatePeriod: constsBase.bountyUpdatePeriod, + bountyValueMinimum: (constsBase.bountyValueMinimum as BalanceOf).toBn(), + dataDepositPerByte: (constsBase.dataDepositPerByte as BalanceOf).toBn(), + maximumReasonLength: constsBase.maximumReasonLength.toNumber(), + + // extrinsics + // eslint-disable-next-line sort-keys + acceptCurator: txBase.acceptCurator, + approveBounty: txBase.approveBounty, + awardBounty: txBase.awardBounty, + claimBounty: txBase.claimBounty, + closeBounty: txBase.closeBounty, + extendBountyExpiry: txBase.extendBountyExpiry, + proposeBounty: txBase.proposeBounty, + proposeCurator: txBase.proposeCurator, + unassignCurator: txBase.unassignCurator + }; +} function useBountiesImpl (): BountyApi { const { api } = useApi(); const bounties = useCall(api.derive.bounties.bounties); - const bountyIndex = useCall((api.query.bounties || api.query.treasury).bountyCount); + const bountyCount = useCall((api.query.bounties || api.query.treasury).bountyCount); + const childCount = useCall(api.query.childBounties?.childBountyCount); const bestNumber = useBestNumber(); - const constsBase = api.consts.bounties || api.consts.treasury; - const bountyCuratorDeposit = (constsBase.bountyCuratorDeposit as BalanceOf || { toBn: () => BN_ZERO }).toBn(); - const bountyDepositBase = (constsBase.bountyDepositBase as BalanceOf).toBn(); - const bountyValueMinimum = (constsBase.bountyValueMinimum as BalanceOf).toBn(); - const maximumReasonLength = constsBase.maximumReasonLength.toNumber(); - const dataDepositPerByte = (constsBase.dataDepositPerByte as BalanceOf).toBn(); - const bountyUpdatePeriod = constsBase.bountyUpdatePeriod; - const proposeBounty = (api.tx.bounties || api.tx.treasury).proposeBounty; - const proposeCurator = (api.tx.bounties || api.tx.treasury).proposeCurator; - const claimBounty = (api.tx.bounties || api.tx.treasury).claimBounty; - const acceptCurator = (api.tx.bounties || api.tx.treasury).acceptCurator; - const approveBounty = (api.tx.bounties || api.tx.treasury).approveBounty; - 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, - bountyCuratorDeposit, - bountyDepositBase, - bountyIndex, - bountyUpdatePeriod, - bountyValueMinimum, - claimBounty, - closeBounty, - dataDepositPerByte, - extendBountyExpiry, - maximumReasonLength, - proposeBounty, - proposeCurator, - unassignCurator - }; + const statics = useMemo( + () => getStatics(api), + [api] + ); + + return useMemo( + (): BountyApi => ({ + ...statics, + bestNumber, + bounties, + bountyCount, + childCount + }), + [bestNumber, bounties, bountyCount, childCount, statics] + ); } export const useBounties = createNamedHook('useBounties', useBountiesImpl); diff --git a/packages/page-bounties/test/hooks/defaults.ts b/packages/page-bounties/test/hooks/defaults.ts index 646941a187..8ffb314a1b 100644 --- a/packages/page-bounties/test/hooks/defaults.ts +++ b/packages/page-bounties/test/hooks/defaults.ts @@ -5,8 +5,7 @@ import type { SubmittableExtrinsicFunction } from '@polkadot/api/types'; import { DeriveBounties } from '@polkadot/api-derive/types'; import { balanceOf } from '@polkadot/test-support/creation/balance'; -import { BlockNumber } from '@polkadot/types/interfaces'; -import { BN } from '@polkadot/util'; +import { BN, BN_ONE } from '@polkadot/util'; import { BountyApi } from '../../src/hooks'; @@ -16,15 +15,15 @@ export const defaultBountyApi: BountyApi = { acceptCurator: jest.fn() as unknown as SubmittableExtrinsicFunction<'promise'>, approveBounty: jest.fn() as unknown as SubmittableExtrinsicFunction<'promise'>, awardBounty: jest.fn().mockReturnValue('mockAwardExtrinsic') as unknown as SubmittableExtrinsicFunction<'promise'>, - bestNumber: new BN(1) as BlockNumber, + bestNumber: BN_ONE, bounties: [] as DeriveBounties, bountyCuratorDeposit: balanceOf(500000), - bountyDepositBase: new BN(1), + bountyDepositBase: BN_ONE, bountyUpdatePeriod: defaultBountyUpdatePeriod, - bountyValueMinimum: new BN(1), + bountyValueMinimum: BN_ONE, claimBounty: jest.fn() as unknown as SubmittableExtrinsicFunction<'promise'>, closeBounty: jest.fn().mockReturnValue({ length: 4 }) as unknown as SubmittableExtrinsicFunction<'promise'>, - dataDepositPerByte: new BN(1), + dataDepositPerByte: BN_ONE, extendBountyExpiry: jest.fn().mockReturnValue('mockExtendExtrinsic') as unknown as SubmittableExtrinsicFunction<'promise'>, maximumReasonLength: 100, proposeBounty: jest.fn() as unknown as SubmittableExtrinsicFunction<'promise'>,