polkadot-js/apps

View on GitHub
packages/page-parachains/src/Auctions/Auction.tsx

Summary

Maintainability
A
2 hrs
Test Coverage
// Copyright 2017-2024 @polkadot/app-parachains authors & contributors
// SPDX-License-Identifier: Apache-2.0

import type { ParaId } from '@polkadot/types/interfaces';
import type { AuctionInfo, Campaign, Campaigns, WinnerData, Winning } from '../types.js';

import React, { useCallback, useMemo, useRef } from 'react';

import { Table } from '@polkadot/react-components';
import { useApi, useCall } from '@polkadot/react-hooks';

import { useTranslation } from '../translate.js';
import { useLeaseRangeMax } from '../useLeaseRanges.js';
import WinRange from './WinRange.js';

interface Props {
  auctionInfo?: AuctionInfo;
  campaigns: Campaigns;
  className?: string;
  winningData?: Winning[];
}

function Auction ({ auctionInfo, campaigns, className, winningData }: Props): React.ReactElement<Props> {
  const { t } = useTranslation();
  const { api } = useApi();
  const rangeMax = useLeaseRangeMax();
  const newRaise = useCall<ParaId[]>(api.query.crowdloan.newRaise);

  const headerRef = useRef<([React.ReactNode?, string?, number?] | false)[]>([
    [t('bids'), 'start', 3],
    [t('bidder'), 'address'],
    [t('crowdloan')],
    [t('leases')],
    [t('value')]
  ]);

  const loans = useMemo(
    (): Campaign[] | undefined => {
      if (newRaise && auctionInfo?.leasePeriod && campaigns.funds) {
        const leasePeriodStart = auctionInfo.leasePeriod;
        const leasePeriodEnd = leasePeriodStart.add(rangeMax);

        return campaigns.funds
          .filter(({ firstSlot, isWinner, lastSlot, paraId }) =>
            !isWinner &&
            newRaise.some((n) => n.eq(paraId)) &&
            firstSlot.gte(leasePeriodStart) &&
            lastSlot.lte(leasePeriodEnd)
          )
          .sort((a, b) => b.value.cmp(a.value));
      } else {
        return undefined;
      }
    },
    [auctionInfo, campaigns, newRaise, rangeMax]
  );

  const interleave = useCallback(
    (winners: WinnerData[], asIs: boolean): WinnerData[] => {
      if (asIs || !newRaise || !auctionInfo?.leasePeriod || !loans) {
        return winners;
      }

      return winners
        .concat(...loans.filter(({ firstSlot, lastSlot, paraId, value }) =>
          !winners.some((w) =>
            w.firstSlot.eq(firstSlot) &&
            w.lastSlot.eq(lastSlot)
          ) &&
          !loans.some((e) =>
            !paraId.eq(e.paraId) &&
            firstSlot.eq(e.firstSlot) &&
            lastSlot.eq(e.lastSlot) &&
            value.lt(e.value)
          )
        ))
        .map((w): WinnerData =>
          loans.find(({ firstSlot, lastSlot, value }) =>
            w.firstSlot.eq(firstSlot) &&
            w.lastSlot.eq(lastSlot) &&
            w.value.lt(value)
          ) || w
        )
        .sort((a, b) =>
          a.firstSlot.eq(b.firstSlot)
            ? a.lastSlot.cmp(b.lastSlot)
            : a.firstSlot.cmp(b.firstSlot)
        );
    },
    [auctionInfo, loans, newRaise]
  );

  return (
    <Table
      className={className}
      empty={
        newRaise && auctionInfo?.numAuctions && winningData && (
          auctionInfo.endBlock && !winningData.length
            ? t('No winners in this auction')
            : t('No ongoing auction')
        )
      }
      header={headerRef.current}
      noBodyTag
    >
      {auctionInfo?.leasePeriod && winningData && loans && (
        winningData.length
          ? winningData.map(({ blockNumber, winners }, round) => (
            <tbody key={round}>
              {interleave(winners, round !== 0 || winningData.length !== 1).map((value, index) => (
                <WinRange
                  auctionInfo={auctionInfo}
                  blockNumber={blockNumber}
                  isFirst={index === 0}
                  isLatest={round === 0}
                  key={`${blockNumber.toString()}:${value.key}`}
                  value={value}
                />
              ))}
            </tbody>
          ))
          : (loans.length !== 0) && (
            <tbody key='latest-crowd'>
              {interleave([], false).map((value, index) => (
                <WinRange
                  auctionInfo={auctionInfo}
                  isFirst={index === 0}
                  isLatest
                  key={`latest-crowd:${value.key}`}
                  value={value}
                />
              ))}
            </tbody>
          )
      )}
    </Table>
  );
}

export default React.memo(Auction);