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'>,