parent
b07100b8b0
commit
55af72d88f
@ -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);
|
||||
|
||||
@ -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 (
|
||||
<div className={className}>
|
||||
<Summary activeBounties={bounties?.length} />
|
||||
<Summary info={info} />
|
||||
<Button.Group>
|
||||
<BountyCreate />
|
||||
</Button.Group>
|
||||
<Table
|
||||
className='bounties-table-wrapper'
|
||||
empty={bounties && t<string>('No open bounties')}
|
||||
empty={sorted && t<string>('No open bounties')}
|
||||
header={headerRef.current}
|
||||
withCollapsibleRows
|
||||
>
|
||||
{bounties && bestNumber &&
|
||||
bounties
|
||||
.sort((a, b) => b.index.cmp(a.index))
|
||||
.map(({ bounty, description, index, proposals }) => (
|
||||
<Bounty
|
||||
bestNumber={bestNumber}
|
||||
bounty={bounty}
|
||||
description={description}
|
||||
index={index}
|
||||
key={index.toNumber()}
|
||||
proposals={proposals}
|
||||
/>
|
||||
))
|
||||
}
|
||||
{sorted && info.bestNumber && sorted.map(({ bounty, description, index, proposals }) => (
|
||||
<Bounty
|
||||
bestNumber={info.bestNumber}
|
||||
bounty={bounty}
|
||||
description={description}
|
||||
index={index}
|
||||
key={index.toNumber()}
|
||||
proposals={proposals}
|
||||
/>
|
||||
))}
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -33,8 +33,6 @@ interface Props {
|
||||
proposals?: DeriveCollectiveProposal[];
|
||||
}
|
||||
|
||||
const EMPTY_CELL = '-';
|
||||
|
||||
function Bounty ({ bestNumber, bounty, className = '', description, index, proposals }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const [isExpanded, toggleExpanded] = useToggle(false);
|
||||
@ -155,18 +153,18 @@ function Bounty ({ bestNumber, bounty, className = '', description, index, propo
|
||||
<div className='label'>{t('Bond')}</div>
|
||||
<div className='inline-balance'><FormatBalance value={bond} /></div>
|
||||
</div>
|
||||
{curator && (
|
||||
<div className='label-column-right'>
|
||||
<div className='label'>{t("Curator's fee")}</div>
|
||||
<div className='inline-balance'>{<FormatBalance value={fee} />}</div>
|
||||
</div>
|
||||
)}
|
||||
<div className='label-column-right'>
|
||||
<div className='label'>{t("Curator's fee")}</div>
|
||||
<div className='inline-balance'>{curator ? <FormatBalance value={fee} /> : EMPTY_CELL}</div>
|
||||
</div>
|
||||
<div className='label-column-right'>
|
||||
{!curatorDeposit.isZero && (
|
||||
{curator && !curatorDeposit.isZero() && (
|
||||
<>
|
||||
<div className='label'>{t("Curator's deposit")}</div>
|
||||
<div className='inline-balance'>
|
||||
{curator
|
||||
? <FormatBalance value={curatorDeposit} />
|
||||
: EMPTY_CELL}
|
||||
<FormatBalance value={curatorDeposit} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
@ -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<Props> {
|
||||
function Summary ({ className = '', info: { bestNumber, bounties, bountyCount, childCount } }: Props): React.ReactElement<Props> {
|
||||
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 (
|
||||
<SummaryBox className={`ui--BountySummary ${className}`}>
|
||||
<section>
|
||||
<CardSummary label={t<string>('active')}>
|
||||
{activeBounties}
|
||||
</CardSummary>
|
||||
{activeBounties !== undefined && (
|
||||
{bounties && (
|
||||
<CardSummary label={t<string>('active')}>
|
||||
{formatNumber(bounties.length)}
|
||||
</CardSummary>
|
||||
)}
|
||||
{bountyCount && bounties && (
|
||||
<CardSummary label={t<string>('past')}>
|
||||
{bountyIndex?.subn(activeBounties).toString()}
|
||||
{formatNumber(bountyCount.subn(bounties.length))}
|
||||
</CardSummary>
|
||||
)}
|
||||
{childCount && (
|
||||
<CardSummary label={t<string>('children')}>
|
||||
{formatNumber(childCount)}
|
||||
</CardSummary>
|
||||
)}
|
||||
</section>
|
||||
@ -47,7 +54,7 @@ function Summary ({ activeBounties, className = '' }: Props): React.ReactElement
|
||||
</CardSummary>
|
||||
</section>
|
||||
<section>
|
||||
{bestNumber && spendPeriod.gtn(0) && (
|
||||
{bestNumber && !spendPeriod.isZero() && (
|
||||
<CardSummary
|
||||
label={t<string>('funding period')}
|
||||
progress={{
|
||||
|
||||
@ -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<DeriveBounties>(api.derive.bounties.bounties);
|
||||
const bountyIndex = useCall<BountyIndex>((api.query.bounties || api.query.treasury).bountyCount);
|
||||
const bountyCount = useCall<BN>((api.query.bounties || api.query.treasury).bountyCount);
|
||||
const childCount = useCall<BN>(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);
|
||||
|
||||
@ -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'>,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user