Rework auction list display (#4837)

* Rework auction list display

* Adjust
This commit is contained in:
Jaco Greeff 2021-03-13 10:58:48 +01:00 committed by GitHub
parent 44b38580bd
commit 93538d55f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 65 additions and 78 deletions

View File

@ -6,7 +6,6 @@ import type { AuctionIndex, BlockNumber, LeasePeriodOf } from '@polkadot/types/i
import React, { useRef } from 'react';
import { Table } from '@polkadot/react-components';
import { BN_ZERO } from '@polkadot/util';
import { useTranslation } from '../translate';
import useWinningData from './useWinningData';
@ -14,14 +13,13 @@ import WinBlock from './WinBlock';
interface Props {
auctionInfo: [LeasePeriodOf, BlockNumber] | null;
bestNumber?: BlockNumber;
className?: string;
numAuctions: AuctionIndex | null;
}
function Auction ({ auctionInfo, bestNumber, className, numAuctions }: Props): React.ReactElement<Props> {
function Auction ({ auctionInfo, className, numAuctions }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const winningData = useWinningData();
const winningData = useWinningData(auctionInfo);
const headerRef = useRef([
[t('winners'), 'start', 4],
@ -29,26 +27,21 @@ function Auction ({ auctionInfo, bestNumber, className, numAuctions }: Props): R
[t('value'), 'number']
]);
const offset = auctionInfo && bestNumber && bestNumber.gt(auctionInfo[1])
? bestNumber.sub(auctionInfo[1])
: BN_ZERO;
return (
<Table
className={className}
empty={winningData && numAuctions && auctionInfo
? t<string>('No winners in this auction')
: t<string>('No ongoing auction')
}
empty={numAuctions && winningData && (
auctionInfo && !winningData.length
? t<string>('No winners in this auction')
: t<string>('No ongoing auction')
)}
header={headerRef.current}
>
{offset && bestNumber && winningData?.map((value, count) => (
{winningData?.map((value, count) => (
<WinBlock
bestNumber={bestNumber}
isEven={!!(count % 2)}
isLatest={count === 0}
key={value.blockNumber.toString()}
offset={offset}
value={value}
/>
))}

View File

@ -22,10 +22,10 @@ function Bid ({ className, id }: Props): React.ReactElement<Props> {
const { api } = useApi();
const { hasAccounts } = useAccounts();
const [accountId, setAccountId] = useState<string | null>(null);
const [amount, setAmount] = useState<BN | undefined>();
const [firstSlot, setFirstSlot] = useState<BN | undefined>();
const [lastSlot, setLastSlot] = useState<BN | undefined>();
const [paraId, setParaId] = useState<BN | undefined>();
const [amount, setAmount] = useState<BN | undefined>(BN_ZERO);
const [firstSlot, setFirstSlot] = useState<BN | undefined>(BN_ZERO);
const [lastSlot, setLastSlot] = useState<BN | undefined>(BN_ZERO);
const [paraId, setParaId] = useState<BN | undefined>(BN_ZERO);
const [isOpen, toggleOpen] = useToggle();
const isLastError = !lastSlot || !firstSlot || lastSlot.lt(firstSlot) || lastSlot.gt(firstSlot.addn(3));
@ -62,6 +62,7 @@ function Bid ({ className, id }: Props): React.ReactElement<Props> {
<Modal.Column>
<InputNumber
autoFocus
defaultValue={paraId?.toString()}
isZeroable={false}
label={t<string>('parachain id')}
onChange={setParaId}
@ -86,10 +87,12 @@ function Bid ({ className, id }: Props): React.ReactElement<Props> {
<Modal.Columns>
<Modal.Column>
<InputNumber
defaultValue={firstSlot?.toString()}
label={t<string>('first slot')}
onChange={setFirstSlot}
/>
<InputNumber
defaultValue={lastSlot?.toString()}
isError={isLastError}
label={t<string>('last slot')}
onChange={setLastSlot}

View File

@ -6,19 +6,21 @@ import type { AuctionIndex, BlockNumber, LeasePeriodOf } from '@polkadot/types/i
import React from 'react';
import { CardSummary, SummaryBox } from '@polkadot/react-components';
import { useApi, useCall } from '@polkadot/react-hooks';
import { formatNumber } from '@polkadot/util';
import { useTranslation } from '../translate';
interface Props {
auctionInfo: [LeasePeriodOf, BlockNumber] | null;
bestNumber?: BlockNumber;
className?: string;
numAuctions?: AuctionIndex | null;
}
function Summary ({ auctionInfo, bestNumber, className, numAuctions }: Props): React.ReactElement<Props> {
function Summary ({ auctionInfo, className, numAuctions }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const bestNumber = useCall<BlockNumber>(api.derive.chain.bestNumber);
const [period, ending] = auctionInfo || [];
return (

View File

@ -1,8 +1,6 @@
// Copyright 2017-2021 @polkadot/app-crowdloan authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type BN from 'bn.js';
import type { BlockNumber } from '@polkadot/types/interfaces';
import type { Winning } from './types';
import React from 'react';
@ -10,26 +8,22 @@ import React from 'react';
import WinRange from './WinRange';
interface Props {
bestNumber: BlockNumber;
className?: string;
isEven: boolean;
isLatest: boolean;
offset: BN;
value: Winning;
}
function WinBlock ({ bestNumber, className = '', isEven, isLatest, offset, value: { blockNumber, winners } }: Props): React.ReactElement<Props> {
function WinBlock ({ className = '', isEven, isLatest, value: { blockNumber, winners } }: Props): React.ReactElement<Props> {
return (
<>
{winners.map((value, index) => (
<WinRange
bestNumber={bestNumber}
blockNumber={blockNumber}
className={`${className} ${isEven ? 'isEven' : 'isOdd'} ${index === (winners.length - 1) ? '' : 'noBorder'}`}
isFirst={index === 0}
isLatest={isLatest}
key={index}
offset={offset}
value={value}
/>
))}

View File

@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
import type BN from 'bn.js';
import type { BlockNumber } from '@polkadot/types/interfaces';
import type { WinnerData } from './types';
import React from 'react';
@ -14,16 +13,14 @@ import { formatNumber } from '@polkadot/util';
import { useTranslation } from '../translate';
interface Props {
bestNumber: BlockNumber;
blockNumber: BlockNumber;
blockNumber: BN;
className?: string;
isFirst: boolean;
isLatest: boolean;
offset: BN;
value: WinnerData;
}
function WinRanges ({ bestNumber, blockNumber, className = '', isFirst, isLatest, offset, value: { accountId, paraId, range, value } }: Props): React.ReactElement<Props> {
function WinRanges ({ blockNumber, className = '', isFirst, isLatest, value: { accountId, paraId, range, value } }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
return (
@ -33,7 +30,7 @@ function WinRanges ({ bestNumber, blockNumber, className = '', isFirst, isLatest
<h1>
{isLatest
? t<string>('latest')
: <>#{formatNumber(bestNumber.sub(offset).iadd(blockNumber))}</>
: <>#{formatNumber(blockNumber)}</>
}
</h1>
)}

View File

@ -8,7 +8,7 @@ import type { ITuple } from '@polkadot/types/types';
import React from 'react';
import { Button } from '@polkadot/react-components';
import { useApi, useCall, useCallMulti } from '@polkadot/react-hooks';
import { useApi, useCallMulti } from '@polkadot/react-hooks';
import Auction from './Auction';
import Bid from './Bid';
@ -36,7 +36,6 @@ const optionsMulti = {
function Auctions ({ className }: Props): React.ReactElement<Props> {
const { api } = useApi();
const bestNumber = useCall<BlockNumber>(api.derive.chain.bestNumber);
const { auctionInfo, numAuctions } = useCallMulti<QueryState>([
api.query.auctions.auctionCounter,
api.query.auctions.auctionInfo
@ -46,7 +45,6 @@ function Auctions ({ className }: Props): React.ReactElement<Props> {
<div className={className}>
<Summary
auctionInfo={auctionInfo}
bestNumber={bestNumber}
numAuctions={numAuctions}
/>
<Button.Group>
@ -54,7 +52,6 @@ function Auctions ({ className }: Props): React.ReactElement<Props> {
</Button.Group>
<Auction
auctionInfo={auctionInfo}
bestNumber={bestNumber}
numAuctions={numAuctions}
/>
</div>

View File

@ -1,7 +1,8 @@
// Copyright 2017-2021 @polkadot/app-crowdloan authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { AccountId, BalanceOf, BlockNumber, ParaId } from '@polkadot/types/interfaces';
import type BN from 'bn.js';
import type { AccountId, BalanceOf, ParaId } from '@polkadot/types/interfaces';
export interface WinnerData {
accountId: AccountId;
@ -11,6 +12,6 @@ export interface WinnerData {
}
export interface Winning {
blockNumber: BlockNumber;
blockNumber: BN;
winners: WinnerData[];
}

View File

@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import type { Option, StorageKey } from '@polkadot/types';
import type { BlockNumber, WinningData } from '@polkadot/types/interfaces';
import type { BlockNumber, LeasePeriodOf, WinningData } from '@polkadot/types/interfaces';
import type { WinnerData, Winning } from './types';
import { useEffect, useState } from 'react';
@ -16,18 +16,10 @@ const RANGES = [
'3-3'
];
function flattenData (winning: Winning[]): Winning[] {
return winning
.sort((a, b) => b.blockNumber.cmp(a.blockNumber))
.filter(({ winners }, index, winning) =>
index === 0 ||
JSON.stringify({ winners }) !== JSON.stringify({ winners: winning[index - 1].winners })
);
}
function extractData (values: [StorageKey<[BlockNumber]>, Option<WinningData>][]): Winning[] {
return flattenData(
values.reduce<Winning[]>((winning, [{ args: [blockNumber] }, optData]): Winning[] => {
function extractData (endBlock: BlockNumber | null, values: [StorageKey<[BlockNumber]>, Option<WinningData>][]): Winning[] {
return values
.sort(([{ args: [a] }], [{ args: [b] }]) => a.cmp(b))
.reduce((all: Winning[], [{ args: [blockOffset] }, optData]): Winning[] => {
if (optData.isSome) {
const winners = optData.unwrap().reduce<WinnerData[]>((winners, optEntry, index): WinnerData[] => {
if (optEntry.isSome) {
@ -39,17 +31,23 @@ function extractData (values: [StorageKey<[BlockNumber]>, Option<WinningData>][]
return winners;
}, []);
if (winners.length) {
winning.push({ blockNumber, winners });
}
winners.length && (
all.length === 0 ||
JSON.stringify({ winners }) !== JSON.stringify({ winners: all[all.length - 1].winners })
) && all.push({
blockNumber: endBlock
? blockOffset.add(endBlock)
: blockOffset,
winners
});
}
return winning;
return all;
}, [])
);
.reverse();
}
export default function useWinningData (): Winning[] | null {
export default function useWinningData (auctionInfo: [LeasePeriodOf, BlockNumber] | null): Winning[] | null {
const { api } = useApi();
const [winning, setWinning] = useState<Winning[] | null>(null);
const trigger = useEventTrigger([api.events.auctions.BidAccepted]);
@ -58,9 +56,11 @@ export default function useWinningData (): Winning[] | null {
trigger &&
api.query.auctions.winning
.entries<Option<WinningData>, [BlockNumber]>()
.then((values) => setWinning(extractData(values)))
.then((values) => setWinning(
extractData(auctionInfo && auctionInfo[1], values))
)
.catch(console.error);
}, [api, trigger]);
}, [api, auctionInfo, trigger]);
return winning;
}

View File

@ -5,11 +5,11 @@ import type BN from 'bn.js';
import type { UInt } from '@polkadot/types';
import type { Balance, ParaId } from '@polkadot/types/interfaces';
import React, { useMemo, useState } from 'react';
import React, { useState } from 'react';
import { Button, InputAddress, InputBalance, MarkWarning, Modal, TxButton } from '@polkadot/react-components';
import { useAccounts, useApi, useToggle } from '@polkadot/react-hooks';
import { BN_ZERO, formatBalance } from '@polkadot/util';
import { formatBalance } from '@polkadot/util';
import { useTranslation } from '../translate';
@ -28,21 +28,19 @@ function FundContribute ({ cap, className, paraId, raised }: Props): React.React
const [accountId, setAccountId] = useState<string | null>(null);
const [amount, setAmount] = useState<BN | undefined>();
const maxAmount = useMemo(
() => cap.sub(raised),
[cap, raised]
);
// TODO verifier signature
const remaining = cap.sub(raised);
const allowContribute = remaining.gte(api.consts.crowdloan.minContribution as UInt);
const isAmountBelow = !amount || amount.lt(api.consts.crowdloan.minContribution as UInt);
const isAmountError = !amount || !amount.gt(BN_ZERO) || isAmountBelow;
const isAmountOver = !!(amount && amount.gt(remaining));
const isAmountError = isAmountBelow || isAmountOver;
return (
<>
<Button
icon='plus'
isDisabled={!hasAccounts}
isDisabled={!hasAccounts || !allowContribute}
label={t<string>('Contribute')}
onClick={toggleOpen}
/>
@ -73,11 +71,13 @@ function FundContribute ({ cap, className, paraId, raised }: Props): React.React
isError={isAmountError}
isZeroable={false}
label={t<string>('contribution')}
maxValue={maxAmount}
onChange={setAmount}
/>
{isAmountBelow && (
<MarkWarning content={t<string>('The amount is less than the minimum allowed contribution of {{min}}', { replace: { min: formatBalance(api.consts.crowdloan.minContribution as UInt) } })} />
<MarkWarning content={t<string>('The amount is less than the minimum allowed contribution of {{value}}', { replace: { value: formatBalance(api.consts.crowdloan.minContribution as UInt) } })} />
)}
{isAmountOver && (
<MarkWarning content={t<string>('The amount is more than the remaining contribution needed {{value}}', { replace: { value: formatBalance(remaining) } })} />
)}
</Modal.Column>
<Modal.Column>

View File

@ -45,24 +45,24 @@ function Progress ({ className = '', isDisabled, size = 'normal', total, value }
return null;
}
const drawAngle = angle % 360;
return (
<div className={`ui--Progress${isDisabled ? ' isDisabled' : ''} ${size}Size ${className}`}>
<div className='background highlight--bg' />
<Clip
angle={
angle <= 180
? angle.toFixed(1)
drawAngle <= 180
? drawAngle.toFixed(1)
: '180'
}
type='first'
/>
<Clip
angle={
angle <= 180
drawAngle <= 180
? '0'
: angle >= 360
? '180'
: (angle - 180).toFixed(1)
: (drawAngle - 180).toFixed(1)
}
type='second'
/>