date-fns#differenceInSeconds TypeScript Examples

The following examples show how to use date-fns#differenceInSeconds. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: AuctionProgress.tsx    From glide-frontend with GNU General Public License v3.0 6 votes vote down vote up
AuctionProgress: React.FC<{ auction: Auction }> = ({ auction }) => {
  const [progress, setProgress] = useState<number>(0)
  const { slowRefresh } = useRefresh()

  // Note: opted to base it on date rather than block number to reduce the amount of calls and async handling
  useEffect(() => {
    if (auction.status === AuctionStatus.ToBeAnnounced || auction.status === AuctionStatus.Pending) {
      setProgress(0)
    } else {
      const now = new Date()
      const auctionDuration = differenceInSeconds(auction.endDate, auction.startDate)
      const secondsPassed = differenceInSeconds(now, auction.startDate)
      const percentagePassed = (secondsPassed * 100) / auctionDuration
      setProgress(percentagePassed < 100 ? percentagePassed : 100)
    }
  }, [slowRefresh, auction])

  return <Progress variant="flat" primaryStep={progress} />
}
Example #2
Source File: timeParser.ts    From tempomat with MIT License 6 votes vote down vote up
function intervalToSeconds(startTime: string, endTime: string, referenceDate: Date): number | null {
    var start = parseTime(startTime, referenceDate)
    var end = parseTime(endTime, referenceDate)
    if (!start || !end) return null
    const diff = differenceInSeconds(end, start)

    if (isAfter(end, start)) {
        return diff
    } else {
        const dayInSeconds = 86400
        return dayInSeconds + diff
    }
}
Example #3
Source File: AuctionProgress.tsx    From vvs-ui with GNU General Public License v3.0 6 votes vote down vote up
AuctionProgress: React.FC<{ auction: Auction }> = ({ auction }) => {
  const [progress, setProgress] = useState<number>(0)
  const { slowRefresh } = useRefresh()

  // Note: opted to base it on date rather than block number to reduce the amount of calls and async handling
  useEffect(() => {
    if (auction.status === AuctionStatus.ToBeAnnounced || auction.status === AuctionStatus.Pending) {
      setProgress(0)
    } else {
      const now = new Date()
      const auctionDuration = differenceInSeconds(auction.endDate, auction.startDate)
      const secondsPassed = differenceInSeconds(now, auction.startDate)
      const percentagePassed = (secondsPassed * 100) / auctionDuration
      setProgress(percentagePassed < 100 ? percentagePassed : 100)
    }
  }, [slowRefresh, auction])

  return <Progress variant="flat" primaryStep={progress} />
}
Example #4
Source File: TransactionDisplay.tsx    From anchor-web-app with Apache License 2.0 5 votes vote down vote up
function TransactionDisplayBase(props: TransactionDisplayProps) {
  const { className, tx } = props;

  const [isCopied, setCopied] = useClipboard(tx.txHash, {
    successDuration: 2000,
  });

  const [countdown, { start, stop }] = useCountdown({
    seconds: differenceInSeconds(new Date(), tx.display.timestamp),
    interval: 1000,
    isIncrement: true,
  });

  useEffect(() => {
    start();

    return () => {
      stop();
    };
    //eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return (
    <div className={className} key={tx.txHash}>
      <div className="details">
        <span className="action">{formatTxKind(tx.display.txKind)}</span>
        <div className="amount">{tx.display.amount ?? 'Unknown'}</div>
      </div>

      <div className="more-details">
        <span className="tx-hash" onClick={setCopied}>
          <span className="hash">{truncateEvm(tx.txHash)}</span>
          {isCopied && (
            <IconSpan className="copy">
              <Check /> Copied
            </IconSpan>
          )}
        </span>
        <div className="timestamp">
          {formatEllapsedSimple(countdown * 1000)}
        </div>
      </div>

      <TransactionProgress
        stepCount={txStepCount(tx)}
        currStep={tx.lastEventKind}
      />
    </div>
  );
}
Example #5
Source File: deployment.service.ts    From amplication with Apache License 2.0 5 votes vote down vote up
/**
   * Gets the updated status of running deployments from
   * DeployerService, and updates the step and deployment status. This function should
   * be called periodically from an external scheduler
   */
  async updateRunningDeploymentsStatus(): Promise<void> {
    const lastUpdateThreshold = subSeconds(
      new Date(),
      DEPLOY_STATUS_FETCH_INTERVAL_SEC
    );

    //find all deployments that are still running
    const deployments = await this.findMany({
      where: {
        statusUpdatedAt: {
          lt: lastUpdateThreshold
        },
        status: {
          equals: EnumDeploymentStatus.Waiting
        }
      },
      include: ACTION_INCLUDE
    });
    await Promise.all(
      deployments.map(async deployment => {
        const steps = await this.actionService.getSteps(deployment.actionId);
        const deployStep = steps.find(step => step.name === DEPLOY_STEP_NAME);
        const destroyStep = steps.find(step => step.name === DESTROY_STEP_NAME);

        const currentStep = destroyStep || deployStep; //when destroy step exist it is the current one
        try {
          const result = await this.deployerService.getStatus(
            deployment.statusQuery
          );
          //To avoid too many messages in the log, if the status is still "running" handle the results only if the bigger interval passed
          if (
            result.status !== EnumDeployStatus.Running ||
            differenceInSeconds(new Date(), deployment.statusUpdatedAt) >
              DEPLOY_STATUS_UPDATE_INTERVAL_SEC
          ) {
            this.logger.info(
              `Deployment ${deployment.id}: current status ${result.status}`
            );
            const updatedDeployment = await this.handleDeployResult(
              deployment,
              currentStep,
              result
            );
            return updatedDeployment.status;
          } else {
            return deployment.status;
          }
        } catch (error) {
          await this.actionService.logInfo(currentStep, error);
          await this.actionService.complete(
            currentStep,
            EnumActionStepStatus.Failed
          );
          const status = EnumDeploymentStatus.Failed;
          await this.updateStatus(deployment.id, status);
        }
      })
    );
  }
Example #6
Source File: AuctionTimer.tsx    From glide-frontend with GNU General Public License v3.0 5 votes vote down vote up
AuctionTimer: React.FC<{ auction: Auction }> = ({ auction }) => {
  const { t } = useTranslation()
  if (!auction) {
    return (
      <Flex justifyContent="center" alignItems="center" mb="48px">
        <Skeleton width="256px" height="40px" />
      </Flex>
    )
  }

  if (auction.status === AuctionStatus.ToBeAnnounced || auction.status === AuctionStatus.Closed) {
    return null
  }
  if (auction.status === AuctionStatus.Finished) {
    return (
      <Flex justifyContent="center" alignItems="center" mb="48px">
        <Text bold>{t('Closing')}...</Text>
        <PocketWatchIcon height="40px" width="40px" />
      </Flex>
    )
  }
  const { startDate, endDate } = auction
  const timerUntil = isAfter(startDate, new Date()) ? startDate : endDate
  const timerTitle = timerUntil === endDate ? t('Ending in') : t('Next auction')
  const secondsRemaining = differenceInSeconds(timerUntil, new Date())
  const { days, hours, minutes } = getTimePeriods(secondsRemaining)
  return (
    <Flex justifyContent="center" alignItems="center" mb="48px">
      <Text bold>{timerTitle}: </Text>
      <AuctionCountDown>
        {days !== 0 && (
          <>
            <Text verticalAlign="baseline" lineHeight="28px" fontSize="24px" bold color="secondary" mr="4px">
              {days}
            </Text>
            <Text verticalAlign="baseline" bold mr="4px">
              {t('d')}
            </Text>
          </>
        )}
        <Text verticalAlign="baseline" lineHeight="28px" fontSize="24px" bold color="secondary" mr="4px">
          {hours}
        </Text>
        <Text verticalAlign="baseline" bold mr="4px">
          {t('h')}
        </Text>
        <Text verticalAlign="baseline" lineHeight="28px" fontSize="24px" bold color="secondary" mr="4px">
          {minutes}
        </Text>
        <Text verticalAlign="baseline" bold>
          {t('m')}
        </Text>
      </AuctionCountDown>
      <PocketWatchIcon height="40px" width="40px" />
    </Flex>
  )
}
Example #7
Source File: CountdownBar.tsx    From mStable-apps with GNU Lesser General Public License v3.0 5 votes vote down vote up
CountdownBar: FC<Props> = ({ className, width = 150, percentage = 0, end, color, tip, textColor }) => {
  const [value, setValue] = useState((percentage / 100) * width)
  const endDate = new Date(end)
  const dateDifference = differenceInSeconds(endDate, new Date())
  const timeMultiplier = 60 // minute
  const interval = ((((100 - percentage) / 100) * width) / dateDifference) * timeMultiplier

  const renderer = ({ days: total, hours, minutes, completed }: CountdownRenderProps): ReactElement => {
    const years = Math.floor(total / YEAR)
    const months = Math.floor((total % YEAR) / MONTH)
    const weeks = Math.floor((total % MONTH) / WEEK)
    const days = total % 7
    return (
      <Time color={textColor}>
        {completed
          ? `Complete`
          : `${formatLabel(years, 'y')} 
             ${formatLabel(months, 'm')} 
             ${formatLabel(weeks, 'w')} 
             ${formatLabel(days, 'd')} 
             ${hours}h 
             ${minutes}m`}
      </Time>
    )
  }

  useInterval(() => {
    setValue(value - interval <= 0 ? 0 : value - interval)
  }, 1000 * timeMultiplier)

  return (
    <Container className={className}>
      <Progress style={{ width: `${width}px` }} color={color}>
        <div style={{ width: `${value}px` }} />
      </Progress>
      <Countdown date={end} renderer={renderer} />
      {tip && <StyledTooltip tip={tip} />}
    </Container>
  )
}
Example #8
Source File: AuctionTimer.tsx    From vvs-ui with GNU General Public License v3.0 5 votes vote down vote up
AuctionTimer: React.FC<{ auction: Auction }> = ({ auction }) => {
  const { t } = useTranslation()
  if (!auction) {
    return (
      <Flex justifyContent="center" alignItems="center" mb="48px">
        <Skeleton width="256px" height="40px" />
      </Flex>
    )
  }

  if (auction.status === AuctionStatus.ToBeAnnounced || auction.status === AuctionStatus.Closed) {
    return null
  }
  if (auction.status === AuctionStatus.Finished) {
    return (
      <Flex justifyContent="center" alignItems="center" mb="48px">
        <Text bold>{t('Closing')}...</Text>
        <PocketWatchIcon height="40px" width="40px" />
      </Flex>
    )
  }
  const { startDate, endDate } = auction
  const timerUntil = isAfter(startDate, new Date()) ? startDate : endDate
  const timerTitle = timerUntil === endDate ? t('Ending in') : t('Next auction')
  const secondsRemaining = differenceInSeconds(timerUntil, new Date())
  const { days, hours, minutes } = getTimePeriods(secondsRemaining)
  return (
    <Flex justifyContent="center" alignItems="center" mb="48px">
      <Text bold>{timerTitle}: </Text>
      <AuctionCountDown>
        {days !== 0 && (
          <>
            <Text verticalAlign="baseline" lineHeight="28px" fontSize="24px" bold color="secondary" mr="4px">
              {days}
            </Text>
            <Text verticalAlign="baseline" bold mr="4px">
              {t('d')}
            </Text>
          </>
        )}
        <Text verticalAlign="baseline" lineHeight="28px" fontSize="24px" bold color="secondary" mr="4px">
          {hours}
        </Text>
        <Text verticalAlign="baseline" bold mr="4px">
          {t('h')}
        </Text>
        <Text verticalAlign="baseline" lineHeight="28px" fontSize="24px" bold color="secondary" mr="4px">
          {minutes}
        </Text>
        <Text verticalAlign="baseline" bold>
          {t('m')}
        </Text>
      </AuctionCountDown>
      <PocketWatchIcon height="40px" width="40px" />
    </Flex>
  )
}
Example #9
Source File: NftWidget.hooks.ts    From atlas with GNU General Public License v3.0 4 votes vote down vote up
useNftWidget = (videoId?: string): UseNftWidgetReturn => {
  const { activeMemberId } = useUser()
  const { nft, nftStatus } = useNft(videoId ?? '', { pollInterval: POLL_INTERVAL })
  const {
    isOwner,
    englishTimerState,
    canWithdrawBid,
    needsSettling,
    auctionPlannedEndDate,
    userBid,
    startsAtDate,
    isUserTopBidder,
    userBidUnlockDate,
    saleType,
    startsAtBlock,
    canChangeBid,
    isUserWhitelisted,
    plannedEndAtBlock,
    hasTimersLoaded,
  } = useNftState(nft)

  const { bids: userBids } = useBids(
    {
      where: {
        isCanceled_eq: false,
        nft: { id_eq: nft?.id },
        bidder: { id_eq: activeMemberId },
      },
    },
    {
      fetchPolicy: 'cache-and-network',
      skip: !nft?.id || !activeMemberId,
      onError: (error) =>
        SentryLogger.error('Failed to fetch member bids', 'useNftState', error, {
          data: {
            nft: nft?.id,
            member: activeMemberId,
          },
        }),
    }
  )

  const unwithdrawnUserBids = userBids?.filter(
    (bid) =>
      bid.auction.auctionType.__typename === 'AuctionTypeOpen' &&
      (nftStatus?.status !== 'auction' || bid.auction.id !== nftStatus.auctionId) &&
      bid.auction.winningMemberId !== activeMemberId
  )
  const bidFromPreviousAuction = unwithdrawnUserBids?.[0]

  const owner = nft?.ownerMember

  const { url: ownerAvatarUri } = useMemberAvatar(owner)
  const { url: topBidderAvatarUri } = useMemberAvatar(nftStatus?.status === 'auction' ? nftStatus.topBidder : undefined)

  const { entries: nftHistory } = useNftHistoryEntries(videoId || null, { pollInterval: POLL_INTERVAL })

  switch (nftStatus?.status) {
    case 'auction': {
      return {
        ownerHandle: owner?.handle,
        ownerAvatarUri,
        isOwner,
        needsSettling,
        bidFromPreviousAuction,
        nftStatus: {
          ...nftStatus,
          startsAtDate,
          canWithdrawBid,
          canChangeBid,
          englishTimerState,
          auctionPlannedEndDate,
          topBidderAvatarUri,
          isUserTopBidder,
          userBidUnlockDate,
          startsAtBlock,
          plannedEndAtBlock,
          hasTimersLoaded,
          auctionBeginsInDays: startsAtDate ? differenceInCalendarDays(startsAtDate, new Date()) : 0,
          auctionBeginsInSeconds: startsAtDate ? differenceInSeconds(startsAtDate, new Date()) : 0,
          topBidderHandle: nftStatus.topBidder?.handle,
          userBidAmount: Number(userBid?.amount) || undefined,
          isUserWhitelisted,
        },
        nftHistory,
        saleType,
      }
    }
    case 'buy-now':
      return {
        ownerHandle: owner?.handle,
        ownerAvatarUri,
        isOwner,
        needsSettling,
        bidFromPreviousAuction,
        nftStatus: {
          ...nftStatus,
        },
        nftHistory,
        saleType,
      }
    case 'idle':
      return {
        ownerHandle: owner?.handle,
        ownerAvatarUri,
        isOwner,
        needsSettling,
        bidFromPreviousAuction,
        nftStatus: {
          ...nftStatus,
        },
        nftHistory,
        saleType,
      }
  }

  return null
}
Example #10
Source File: NftWidget.tsx    From atlas with GNU General Public License v3.0 4 votes vote down vote up
NftWidget: React.FC<NftWidgetProps> = ({
  ownerHandle,
  isOwner,
  nftStatus,
  nftHistory,
  needsSettling,
  ownerAvatarUri,
  onNftPutOnSale,
  onNftAcceptBid,
  onWithdrawBid,
  bidFromPreviousAuction,
  onNftCancelSale,
  onNftChangePrice,
  onNftPurchase,
  onNftSettlement,
  onNftBuyNow,
}) => {
  const timestamp = useMsTimestamp()
  const { ref, width = SMALL_VARIANT_MAXIMUM_SIZE + 1 } = useResizeObserver({
    box: 'border-box',
  })

  const size: Size = width > SMALL_VARIANT_MAXIMUM_SIZE ? 'medium' : 'small'
  const { convertToUSD, isLoadingPrice } = useTokenPrice()

  const content = useDeepMemo(() => {
    if (!nftStatus) {
      return
    }
    const contentTextVariant = size === 'small' ? 'h400' : 'h600'
    const buttonSize = size === 'small' ? 'medium' : 'large'
    const buttonColumnSpan = size === 'small' ? 1 : 2
    const timerColumnSpan = size === 'small' ? 1 : 2
    const BuyNow = ({ buyNowPrice }: { buyNowPrice?: number }) =>
      buyNowPrice ? (
        <NftInfoItem
          size={size}
          label="Buy now"
          content={
            <>
              <JoyTokenIcon size={size === 'small' ? 16 : 24} variant="silver" />
              <Text variant={contentTextVariant}>{formatNumberShort(buyNowPrice)}</Text>
            </>
          }
          secondaryText={convertToUSD(buyNowPrice)}
        />
      ) : null
    const InfoBanner = ({ title, description }: { title: string; description: string }) => (
      <GridItem colSpan={buttonColumnSpan}>
        <Banner id="" dismissable={false} icon={<SvgAlertsInformative24 />} {...{ title, description }} />
      </GridItem>
    )
    const WithdrawBidFromPreviousAuction = ({ secondary }: { secondary?: boolean }) =>
      bidFromPreviousAuction ? (
        <>
          <GridItem colSpan={buttonColumnSpan}>
            <Button variant={secondary ? 'secondary' : undefined} fullWidth size={buttonSize} onClick={onWithdrawBid}>
              Withdraw last bid
            </Button>
            <Text as="p" margin={{ top: 2 }} variant="t100" secondary align="center">
              You bid {formatNumberShort(Number(bidFromPreviousAuction?.amount))} tJOY on{' '}
              {formatDateTime(new Date(bidFromPreviousAuction.createdAt))}
            </Text>
          </GridItem>
        </>
      ) : null

    const BidPlacingInfoText = () => (
      <Text as="p" variant="t100" secondary align="center">
        Placing a bid will withdraw your last bid
      </Text>
    )

    switch (nftStatus.status) {
      case 'idle':
        return (
          <>
            {nftStatus.lastSalePrice ? (
              <NftInfoItem
                size={size}
                label="Last price"
                content={
                  <>
                    <JoyTokenIcon size={size === 'small' ? 16 : 24} variant="silver" />
                    <Text variant={contentTextVariant} secondary>
                      {formatNumberShort(nftStatus.lastSalePrice)}
                    </Text>
                  </>
                }
                secondaryText={nftStatus.lastSaleDate && formatDateTime(nftStatus.lastSaleDate)}
              />
            ) : (
              <NftInfoItem
                size={size}
                label="status"
                content={
                  <Text variant={contentTextVariant} secondary>
                    Not for sale
                  </Text>
                }
              />
            )}
            {bidFromPreviousAuction && (
              <>
                <InfoBanner
                  title="Withdraw your bid"
                  description="You placed a bid in a previous auction that you can now withdraw to claim back your money."
                />
                <WithdrawBidFromPreviousAuction />
              </>
            )}
            {isOwner && (
              <GridItem colSpan={buttonColumnSpan}>
                <Button fullWidth variant="secondary" size={buttonSize} onClick={onNftPutOnSale}>
                  Start sale of this NFT
                </Button>
              </GridItem>
            )}
          </>
        )
      case 'buy-now':
        return (
          <>
            <BuyNow buyNowPrice={nftStatus.buyNowPrice} />

            <GridItem colSpan={buttonColumnSpan}>
              <ButtonGrid data-size={size}>
                {isOwner ? (
                  <>
                    <Button fullWidth variant="secondary" size={buttonSize} onClick={onNftChangePrice}>
                      Change price
                    </Button>
                    <Button fullWidth variant="destructive" size={buttonSize} onClick={onNftCancelSale}>
                      Remove from sale
                    </Button>
                  </>
                ) : (
                  <GridItem colSpan={buttonColumnSpan}>
                    <Button fullWidth size={buttonSize} onClick={onNftPurchase}>
                      Buy now
                    </Button>
                  </GridItem>
                )}
                {bidFromPreviousAuction && (
                  <>
                    <InfoBanner
                      title="Withdraw your bid"
                      description="You placed a bid in a previous auction that you can now withdraw to claim back your money."
                    />
                    <WithdrawBidFromPreviousAuction secondary />
                  </>
                )}
              </ButtonGrid>
            </GridItem>
          </>
        )
      case 'auction': {
        const getInfoBannerProps = () => {
          const hasBids = !nftStatus.topBid?.isCanceled && nftStatus.topBidAmount
          if (nftStatus.type === 'open' && bidFromPreviousAuction) {
            return {
              title: 'Withdraw your bid to participate',
              description:
                'You placed a bid in a previous auction that you can now withdraw to be able to participate in this auction.',
            }
          }

          if (nftStatus.englishTimerState === 'expired' && isOwner && !hasBids) {
            return {
              title: 'Auction ended',
              description: 'This auction has ended and no one placed a bid. You can now remove this NFT from sale.',
            }
          }
          if (nftStatus.englishTimerState === 'expired' && !bidFromPreviousAuction && !hasBids && !isOwner) {
            return {
              title: 'Auction ended',
              description:
                "This auction has ended and no one placed a bid. We're waiting for the NFT owner to remove this NFT from sale.",
            }
          }
          if (
            nftStatus.englishTimerState === 'expired' &&
            !bidFromPreviousAuction &&
            hasBids &&
            !isOwner &&
            !nftStatus.isUserTopBidder
          ) {
            return {
              title: 'Auction ended',
              description:
                'We are waiting for this auction to be settled by the auction winner or the current NFT owner.',
            }
          }
          if (nftStatus.englishTimerState === 'expired' && bidFromPreviousAuction) {
            return {
              title: 'Withdraw your bid',
              description: 'You placed a bid in a previous auction that you can now withdraw.',
            }
          }

          if (nftStatus.englishTimerState === 'running' && bidFromPreviousAuction) {
            return {
              title: 'Withdraw your bid to participate',
              description:
                'You placed a bid in a previous auction that you can now withdraw to be able to participate in this auction.',
            }
          }

          if (nftStatus.englishTimerState === 'upcoming' && bidFromPreviousAuction) {
            return {
              title: 'Withdraw your bid to participate',
              description:
                'You placed a bid in a previous auction that you can now withdraw to be able to participate in this upcoming auction.',
            }
          }

          if (nftStatus.isUserWhitelisted === false) {
            return {
              title: "You're not on the whitelist",
              description: `This sale is available only to members whitelisted by ${ownerHandle}.`,
            }
          }

          return null
        }
        const infoBannerProps = getInfoBannerProps()

        const infoTextNode = !!nftStatus.userBidAmount && nftStatus.userBidUnlockDate && (
          <GridItem colSpan={buttonColumnSpan}>
            {nftStatus.type === 'english' ? (
              <BidPlacingInfoText />
            ) : (
              <Text as="p" variant="t100" secondary align="center">
                {nftStatus.canWithdrawBid
                  ? `Your last bid: ${nftStatus.userBidAmount} tJOY`
                  : `Your last bid (${nftStatus.userBidAmount} tJOY) becomes withdrawable on ${formatDateTime(
                      nftStatus.userBidUnlockDate
                    )}`}
              </Text>
            )}
          </GridItem>
        )

        return (
          <>
            {nftStatus.topBidAmount && !nftStatus.topBid?.isCanceled ? (
              <NftInfoItem
                size={size}
                label="Top bid"
                content={
                  <>
                    <TopBidderContainer>
                      <Avatar assetUrl={nftStatus.topBidderAvatarUri} size="bid" />
                      <TopBidderTokenContainer data-size={size}>
                        <JoyTokenIcon size={size === 'small' ? 16 : 24} variant="silver" />
                      </TopBidderTokenContainer>
                    </TopBidderContainer>
                    <Text variant={contentTextVariant}>{formatNumberShort(nftStatus.topBidAmount)}</Text>
                  </>
                }
                secondaryText={
                  !isLoadingPrice && nftStatus.topBidAmount ? (
                    <>
                      {convertToUSD(nftStatus.topBidAmount)} from{' '}
                      <OwnerHandle
                        to={absoluteRoutes.viewer.member(nftStatus.topBidderHandle)}
                        variant="secondary"
                        textOnly
                      >
                        <Text variant="t100">{nftStatus.isUserTopBidder ? 'you' : nftStatus.topBidderHandle}</Text>
                      </OwnerHandle>
                    </>
                  ) : null
                }
              />
            ) : (
              <NftInfoItem
                size={size}
                label="Starting Price"
                content={
                  <>
                    <JoyTokenIcon size={size === 'small' ? 16 : 24} variant="silver" />
                    <Text variant={contentTextVariant}>{formatNumberShort(nftStatus.startingPrice)}</Text>
                  </>
                }
                secondaryText={convertToUSD(nftStatus.startingPrice)}
              />
            )}
            <BuyNow buyNowPrice={nftStatus.buyNowPrice} />

            {nftStatus.englishTimerState === 'expired' && (
              <GridItem colSpan={timerColumnSpan}>
                <NftInfoItem
                  size={size}
                  label="Auction ended on"
                  loading={!nftStatus.auctionPlannedEndDate}
                  content={
                    nftStatus.auctionPlannedEndDate && (
                      <Text variant={contentTextVariant} secondary>
                        {formatDateTime(nftStatus.auctionPlannedEndDate)}
                      </Text>
                    )
                  }
                />
              </GridItem>
            )}
            {nftStatus.englishTimerState === 'running' && nftStatus?.auctionPlannedEndDate && (
              <GridItem colSpan={timerColumnSpan}>
                <NftTimerItem size={size} time={nftStatus.auctionPlannedEndDate} />
              </GridItem>
            )}
            {nftStatus.startsAtBlock && nftStatus.auctionBeginsInSeconds >= 0 && (
              <GridItem colSpan={timerColumnSpan}>
                <NftInfoItem
                  size={size}
                  label="Auction begins on"
                  loading={!nftStatus.startsAtDate}
                  content={
                    nftStatus.startsAtDate && (
                      <Text variant={contentTextVariant} secondary>
                        {nftStatus.auctionBeginsInDays > 1 && formatDateTime(nftStatus.startsAtDate)}
                        {nftStatus.auctionBeginsInDays === 1 && `Tomorrow at ${formatTime(nftStatus.startsAtDate)}`}
                        {nftStatus.auctionBeginsInDays < 1 &&
                          formatDurationShort(differenceInSeconds(nftStatus.startsAtDate, timestamp))}
                      </Text>
                    )
                  }
                />
              </GridItem>
            )}

            {nftStatus.hasTimersLoaded && infoBannerProps && <InfoBanner {...infoBannerProps} />}

            {nftStatus.hasTimersLoaded && needsSettling && (nftStatus.isUserTopBidder || isOwner) && (
              <GridItem colSpan={buttonColumnSpan}>
                <Button fullWidth size={buttonSize} onClick={onNftSettlement}>
                  Settle auction
                </Button>
              </GridItem>
            )}

            {nftStatus.hasTimersLoaded && bidFromPreviousAuction && <WithdrawBidFromPreviousAuction />}

            {nftStatus.hasTimersLoaded &&
              !needsSettling &&
              !bidFromPreviousAuction &&
              (isOwner
                ? (nftStatus.type === 'open' ||
                    // english auction with no bids
                    !nftStatus.topBidAmount ||
                    nftStatus.topBid?.isCanceled) && (
                    <GridItem colSpan={buttonColumnSpan}>
                      <ButtonGrid data-size={size}>
                        {nftStatus.type === 'open' && nftStatus.topBid && !nftStatus.topBid?.isCanceled && (
                          <Button fullWidth size={buttonSize} onClick={onNftAcceptBid}>
                            Review and accept bid
                          </Button>
                        )}
                        <Button
                          fullWidth
                          onClick={onNftCancelSale}
                          variant={
                            nftStatus.type === 'open' && !nftStatus.topBid?.isCanceled
                              ? 'destructive-secondary'
                              : 'destructive'
                          }
                          size={buttonSize}
                        >
                          Remove from sale
                        </Button>
                      </ButtonGrid>
                    </GridItem>
                  )
                : nftStatus.englishTimerState === 'running' &&
                  nftStatus.isUserWhitelisted !== false &&
                  (nftStatus.buyNowPrice ? (
                    <GridItem colSpan={buttonColumnSpan}>
                      <ButtonGrid data-size={size} data-two-columns={size === 'medium'}>
                        <Button fullWidth variant="secondary" size={buttonSize} onClick={onNftPurchase}>
                          {nftStatus.canChangeBid ? 'Change bid' : 'Place bid'}
                        </Button>
                        <Button fullWidth size={buttonSize} onClick={onNftBuyNow}>
                          Buy now
                        </Button>
                        {/* second row button */}
                        {nftStatus.canWithdrawBid && (
                          <GridItem colSpan={buttonColumnSpan}>
                            <Button fullWidth size={buttonSize} variant="destructive-secondary" onClick={onWithdrawBid}>
                              Withdraw bid
                            </Button>
                          </GridItem>
                        )}

                        {infoTextNode}
                      </ButtonGrid>
                    </GridItem>
                  ) : (
                    <GridItem colSpan={buttonColumnSpan}>
                      <ButtonGrid data-size={size}>
                        <GridItem colSpan={buttonColumnSpan}>
                          <Button fullWidth size={buttonSize} onClick={onNftPurchase}>
                            {nftStatus.canChangeBid ? 'Change bid' : 'Place bid'}
                          </Button>
                        </GridItem>
                        {nftStatus.canWithdrawBid && (
                          <GridItem colSpan={buttonColumnSpan}>
                            <Button fullWidth size={buttonSize} variant="destructive-secondary" onClick={onWithdrawBid}>
                              Withdraw bid
                            </Button>
                          </GridItem>
                        )}
                        {infoTextNode}
                      </ButtonGrid>
                    </GridItem>
                  )))}
          </>
        )
      }
    }
  }, [
    nftStatus,
    size,
    convertToUSD,
    bidFromPreviousAuction,
    onWithdrawBid,
    isOwner,
    onNftPutOnSale,
    onNftChangePrice,
    onNftCancelSale,
    onNftPurchase,
    isLoadingPrice,
    timestamp,
    needsSettling,
    onNftSettlement,
    onNftAcceptBid,
    onNftBuyNow,
    ownerHandle,
  ])

  if (!nftStatus) return null

  return (
    <Container ref={ref}>
      <NftOwnerContainer data-size={size}>
        <OwnerAvatar assetUrl={ownerAvatarUri} size="small" />
        <OwnerLabel variant="t100" secondary>
          This NFT is owned by
        </OwnerLabel>
        <OwnerHandle to={ownerHandle && absoluteRoutes.viewer.member(ownerHandle)} variant="secondary" textOnly>
          <Text variant="h300">{ownerHandle}</Text>
        </OwnerHandle>
      </NftOwnerContainer>
      <Content data-size={size}>{content}</Content>

      <NftHistory size={size} width={width} historyItems={nftHistory} />
    </Container>
  )
}
Example #11
Source File: useGetNftSlot.tsx    From atlas with GNU General Public License v3.0 4 votes vote down vote up
useGetNftSlot = ({
  englishTimerState,
  timerLoading,
  auctionPlannedEndDate,
  hasBuyNowPrice,
  needsSettling,
  startsAtDate,
  withNftLabel,
  status,
}: UseGetSlotsOpts): SlotsObject['bottomLeft'] => {
  const msTimestamp = useMsTimestamp({
    shouldStop: timerLoading || englishTimerState === 'expired' || !englishTimerState,
  })

  const generatePills: () => PillProps[] = useCallback(() => {
    const buyNowPill: PillProps = { icon: <SvgActionBuyNow />, variant: 'overlay', title: 'Buy now' }
    switch (status) {
      case 'idle':
        return [{ icon: <SvgActionNotForSale />, variant: 'overlay', title: 'Not for sale' }]
      case 'buy-now':
        return [buyNowPill]
      case 'auction': {
        const additionalBuyNowPill = hasBuyNowPrice ? [buyNowPill] : []
        if (needsSettling) {
          return [
            {
              icon: <SvgActionAuction />,
              label: 'To be settled',
              variant: 'overlay',
            },
            ...additionalBuyNowPill,
          ]
        }
        if (timerLoading) {
          return [
            {
              icon: <SvgActionAuction />,
              label: 'Loading',
              variant: 'overlay',
            },
            ...additionalBuyNowPill,
          ]
        }
        switch (englishTimerState) {
          case 'upcoming': {
            const diff = startsAtDate && differenceInSeconds(new Date(), startsAtDate) * -1
            const diffTime =
              diff && diff < 3600
                ? `Starts in ${formatDurationShort(diff)}`
                : startsAtDate && ` ${format(startsAtDate, 'd MMM')} at ${format(startsAtDate, 'HH:mm')}`
            return [
              {
                icon: <SvgActionClock />,
                label: diffTime,
                variant: 'overlay',
              },
              ...additionalBuyNowPill,
            ]
          }
          case 'running': {
            const diff = auctionPlannedEndDate && differenceInSeconds(auctionPlannedEndDate, new Date())
            const lessThanMinute = auctionPlannedEndDate && differenceInSeconds(auctionPlannedEndDate, msTimestamp) < 60
            const lessThanHour = auctionPlannedEndDate && differenceInHours(auctionPlannedEndDate, msTimestamp) < 1
            return [
              {
                icon: <SvgActionAuction />,
                label: diff ? (lessThanMinute ? '< 1 min' : formatDurationShort(diff, true)) : undefined,
                variant: lessThanHour ? 'danger' : 'overlay',
              },
              ...additionalBuyNowPill,
            ]
          }
          case 'expired':
            return [
              {
                icon: <SvgActionAuction />,
                label: 'Auction ended',
                variant: 'overlay',
              },
              ...additionalBuyNowPill,
            ]
          default:
            return []
        }
      }
      default:
        return []
    }
  }, [
    status,
    hasBuyNowPrice,
    needsSettling,
    timerLoading,
    englishTimerState,
    startsAtDate,
    auctionPlannedEndDate,
    msTimestamp,
  ])

  const nftPill: PillProps[] = withNftLabel ? [{ label: 'NFT', variant: 'overlay', title: 'NFT' }] : []

  return {
    element: <PillGroup items={[...nftPill, ...generatePills()]} />,
  }
}
Example #12
Source File: NftPurchaseBottomDrawer.tsx    From atlas with GNU General Public License v3.0 4 votes vote down vote up
NftPurchaseBottomDrawer: React.FC = () => {
  const { displaySnackbar } = useSnackbar()
  const [type, setType] = useState<'english_auction' | 'open_auction' | 'buy_now'>('english_auction')
  const [showBuyNowInfo, setBuyNowInfo] = useState(false)
  const { currentAction, closeNftAction, currentNftId, isBuyNowClicked } = useNftActions()
  const { nft, nftStatus, loading, refetch } = useNft(currentNftId || '')
  const { userBid, canChangeBid, userBidUnlockDate } = useNftState(nft)
  const { isLoadingAsset: thumbnailLoading, url: thumbnailUrl } = useAsset(nft?.video.thumbnailPhoto)
  const { url: creatorAvatarUrl } = useAsset(nft?.video.channel.avatarPhoto)
  const { url: ownerMemberAvatarUrl } = useMemberAvatar(nft?.ownerMember)
  const mdMatch = useMediaMatch('md')
  const { convertToUSD } = useTokenPrice()
  const accountBalance = useSubscribeAccountBalance()
  const timestamp = useMsTimestamp({ shouldStop: !currentAction })
  const { convertBlockToMsTimestamp, convertBlocksToDuration } = useBlockTimeEstimation()

  const {
    joystream,
    proxyCallback,
    currentBlock,
    chainState: { nftPlatformFeePercentage },
  } = useJoystream()
  const handleTransaction = useTransaction()
  const { activeMemberId } = useUser()

  const {
    watch,
    setValue,
    handleSubmit: createSubmitHandler,
    register,
    reset,
    formState: { errors },
  } = useForm<{ bid: number }>()

  const isAuction = nftStatus?.status === 'auction'
  const isBuyNow = nftStatus?.status === 'buy-now'
  const isEnglishAuction = nftStatus?.status === 'auction' && nftStatus.type === 'english'
  const isOpenAuction = nftStatus?.status === 'auction' && nftStatus?.type === 'open'

  useEffect(() => {
    if (isBuyNow) {
      setType('buy_now')
    }
    if (isEnglishAuction) {
      setType('english_auction')
    }
    if (isOpenAuction) {
      setType('open_auction')
    }
  }, [isBuyNow, isEnglishAuction, isOpenAuction])

  const auctionBuyNowPrice = (isAuction && nftStatus.buyNowPrice) || 0
  const bidLockingTime = isAuction && nftStatus.bidLockingTime && convertBlocksToDuration(nftStatus.bidLockingTime)
  const buyNowPrice = (isBuyNow && nftStatus.buyNowPrice) || 0
  const startingPrice = isAuction && nftStatus.startingPrice
  const topBidder = isAuction && nftStatus.topBidder ? nftStatus.topBidder : undefined
  const topBidAmount = (isAuction && !nftStatus.topBid?.isCanceled && nftStatus.topBidAmount) || 0
  const minimalBidStep = (isAuction && nftStatus.minimalBidStep) || 0
  const endAtBlock = isAuction && nftStatus.auctionPlannedEndBlock

  const minimumBidEnglishAuction = startingPrice > topBidAmount ? startingPrice : topBidAmount + minimalBidStep
  const minimumBid = type === 'open_auction' ? startingPrice : minimumBidEnglishAuction

  const endTime = endAtBlock && convertBlockToMsTimestamp(endAtBlock)

  const timeLeftSeconds = endTime ? Math.trunc((endTime - timestamp) / 1000) : 0

  const creatorRoyalty = nft?.creatorRoyalty || 0
  const ownerRoyalty = 100 - creatorRoyalty - nftPlatformFeePercentage

  // check if input value isn't bigger than fixed price
  useEffect(() => {
    if (type === 'buy_now' || !auctionBuyNowPrice) {
      return
    }
    const subscription = watch(({ bid }) => {
      if (bid >= auctionBuyNowPrice) {
        setBuyNowInfo(true)
      } else {
        setBuyNowInfo(false)
      }
      if (bid > auctionBuyNowPrice) {
        setValue('bid', auctionBuyNowPrice)
      }
    })

    return () => subscription.unsubscribe()
  }, [auctionBuyNowPrice, setValue, type, watch])

  const handleBuyNow = useCallback(async () => {
    if (!joystream || !currentNftId || !activeMemberId) return
    const completed = await handleTransaction({
      onError: () => refetch(),
      txFactory: async (updateStatus) => {
        if (!isAuction) {
          return (await joystream.extrinsics).buyNftNow(
            currentNftId,
            activeMemberId,
            buyNowPrice,
            proxyCallback(updateStatus)
          )
        } else {
          return (await joystream.extrinsics).makeNftBid(
            currentNftId,
            activeMemberId,
            Number(auctionBuyNowPrice),
            isEnglishAuction ? 'english' : 'open',
            proxyCallback(updateStatus)
          )
        }
      },
      onTxSync: async (_) => refetch(),
    })
    if (completed) {
      closeNftAction()
      displaySnackbar({
        title: 'You have successfully bought NFT.',
        iconType: 'success',
      })
    }
  }, [
    activeMemberId,
    auctionBuyNowPrice,
    buyNowPrice,
    closeNftAction,
    currentNftId,
    displaySnackbar,
    handleTransaction,
    isAuction,
    joystream,
    isEnglishAuction,
    proxyCallback,
    refetch,
  ])

  const handleBidOnAuction = useCallback(() => {
    const submit = createSubmitHandler(async (data) => {
      if (!joystream || !currentNftId || !activeMemberId) return
      const completed = await handleTransaction({
        txFactory: async (updateStatus) =>
          (
            await joystream.extrinsics
          ).makeNftBid(
            currentNftId,
            activeMemberId,
            Number(data.bid),
            isEnglishAuction ? 'english' : 'open',
            proxyCallback(updateStatus)
          ),
        onTxSync: (_) => refetch(),
      })
      if (completed) {
        if (Number(data.bid) === auctionBuyNowPrice) {
          displaySnackbar({
            title: 'You have bought this NFT successfully',
            iconType: 'success',
          })
        } else {
          displaySnackbar({
            title: 'Your bid has been placed',
            description: 'We will notify you about any changes.',
            iconType: 'success',
          })
        }
        closeNftAction()
      }
    })
    submit()
  }, [
    activeMemberId,
    auctionBuyNowPrice,
    closeNftAction,
    createSubmitHandler,
    currentNftId,
    displaySnackbar,
    handleTransaction,
    isEnglishAuction,
    joystream,
    proxyCallback,
    refetch,
  ])
  const isBuyNowAffordable = (buyNowPrice || auctionBuyNowPrice) + TRANSACTION_FEE < (accountBalance || 0)
  const bid = watch('bid')
  const timeLeftUnderMinute = !!timeLeftSeconds && timeLeftSeconds < 60
  const auctionEnded = type === 'english_auction' && timeLeftSeconds <= 0
  const insufficientFoundsError = errors.bid && errors.bid.type === 'bidTooHigh'

  const primaryButtonText =
    type === 'buy_now' || (auctionBuyNowPrice && auctionBuyNowPrice <= bid) || isBuyNowClicked
      ? 'Buy NFT'
      : canChangeBid
      ? 'Change bid'
      : 'Place bid'
  const blocksLeft = endAtBlock && endAtBlock - currentBlock

  const isOpen = currentAction === 'purchase'

  useEffect(() => {
    if (!isOpen) {
      setValue('bid', NaN)
    }
  }, [isOpen, setValue])

  const hasErrors = !!Object.keys(errors).length

  const { isLoadingAsset: userBidAvatarLoading, url: userBidAvatarUrl } = useMemberAvatar(userBid?.bidder)
  const { isLoadingAsset: topBidderAvatarLoading, url: topBidderAvatarUrl } = useMemberAvatar(topBidder)
  const timeToUnlockSeconds = userBidUnlockDate ? differenceInSeconds(userBidUnlockDate, new Date()) : 0

  return (
    <BottomDrawer
      isOpen={isOpen}
      onClose={() => {
        reset()
        closeNftAction()
      }}
      actionBar={{
        primaryButton: {
          text: primaryButtonText,
          disabled: isBuyNowClicked || type === 'buy_now' ? !isBuyNowAffordable : hasErrors,
          onClick: () => (isBuyNowClicked || type === 'buy_now' ? handleBuyNow() : handleBidOnAuction()),
        },
      }}
    >
      <Content>
        <NftPreview>
          <NftCard
            title={nft?.video.title}
            thumbnail={{
              loading: thumbnailLoading || loading || !nft,
              thumbnailUrl: thumbnailUrl,
            }}
            creator={{ name: nft?.video.channel.title, assetUrl: creatorAvatarUrl }}
            owner={{ name: nft?.ownerMember?.handle, assetUrl: ownerMemberAvatarUrl }}
            loading={loading}
            fullWidth={!mdMatch}
          />
        </NftPreview>
        <PlaceBidWrapper>
          <InnerContainer>
            <Header>
              <Text variant="h600">
                {type !== 'buy_now' && !isBuyNowClicked ? (canChangeBid ? 'Change bid' : 'Place bid') : 'Buy NFT'}
              </Text>
              {type === 'english_auction' && (
                <FlexWrapper>
                  <Text variant="h200" secondary>
                    Ending in:
                  </Text>
                  <Text
                    variant="h200"
                    margin={{ left: 2, right: 2 }}
                    color={
                      auctionEnded
                        ? cVar('colorTextMuted', true)
                        : timeLeftUnderMinute
                        ? cVar('colorTextError')
                        : undefined
                    }
                  >
                    {!auctionEnded
                      ? !timeLeftUnderMinute
                        ? formatDurationShort(timeLeftSeconds, true)
                        : 'Under 1 min'
                      : 'Auction ended'}
                    <Text variant="h200" as="span" secondary>
                      {' '}
                      / {pluralizeNoun(blocksLeft && blocksLeft > 0 ? blocksLeft : 0, 'block')}
                    </Text>
                  </Text>
                  <FlexWrapper>
                    {endAtBlock && (
                      <Information
                        text={`On blockchain, duration is expressed in number of blocks. This auction ends at block ${endAtBlock}.`}
                        placement="top"
                      />
                    )}
                  </FlexWrapper>
                </FlexWrapper>
              )}
            </Header>
            {type !== 'buy_now' && !isBuyNowClicked ? (
              <>
                <CurrentBidWrapper>
                  {topBidder && !!topBidAmount ? (
                    <ActiveBidWrapper>
                      <ActionBarCell>
                        <Text variant="h100" secondary margin={{ bottom: 2 }}>
                          Top bid
                        </Text>
                        <FlexWrapper>
                          <Avatar size="bid" assetUrl={topBidderAvatarUrl} loading={topBidderAvatarLoading} />
                          <TokenWrapper>
                            <StyledJoyTokenIcon variant="gray" size={24} />
                          </TokenWrapper>
                          <BidAmount variant="h400">{formatNumberShort(topBidAmount)}</BidAmount>
                        </FlexWrapper>
                        <Text variant="t100" secondary margin={{ top: 1 }}>
                          {topBidder.handle === userBid?.bidder.handle ? 'You' : topBidder.handle}
                        </Text>
                      </ActionBarCell>
                      {userBid && (
                        <ActionBarCell>
                          <Text variant="h100" secondary margin={{ bottom: 2 }}>
                            Your Bid
                          </Text>
                          <FlexWrapper>
                            <Avatar size="bid" assetUrl={userBidAvatarUrl} loading={userBidAvatarLoading} />
                            <TokenWrapper>
                              <StyledJoyTokenIcon variant="gray" size={24} />
                            </TokenWrapper>
                            <BidAmount variant="h400">{formatNumberShort(Number(userBid.amount))}</BidAmount>
                          </FlexWrapper>
                          <Text variant="t100" secondary margin={{ top: 1 }}>
                            You
                          </Text>
                        </ActionBarCell>
                      )}
                    </ActiveBidWrapper>
                  ) : (
                    <ActiveBidWrapper>
                      <ActionBarCell>
                        <Text variant="h100" secondary margin={{ bottom: 2 }}>
                          Top bid
                        </Text>
                        <Text variant="h400">No bids yet</Text>
                      </ActionBarCell>
                    </ActiveBidWrapper>
                  )}
                </CurrentBidWrapper>
                {!auctionEnded && (
                  <MinimumBidWrapper>
                    <MinimumBid>
                      <Text variant="h300" secondary>
                        Minimum bid
                      </Text>
                      <JoyTokenIcon variant="gray" size={24} /> <Text variant="h400">{minimumBid}</Text>
                    </MinimumBid>
                    {auctionBuyNowPrice > 0 && (
                      <Text variant="t100" secondary>
                        Buy now: {formatTokens(auctionBuyNowPrice, true)}
                      </Text>
                    )}
                  </MinimumBidWrapper>
                )}
                <TextField
                  {...register('bid', {
                    valueAsNumber: true,
                    validate: {
                      bidLocked: (value) => {
                        if (isOpenAuction && value < Number(userBid?.amount) && timeToUnlockSeconds > 0) {
                          return `You will be able to change your bid to a lower one after ${
                            userBidUnlockDate && formatDateTime(userBidUnlockDate)
                          }`
                        }
                        return true
                      },
                      bidTooLow: (value) =>
                        Number(value) >= minimumBid ? true : 'Your bid must be higher than the minimum bid',
                      bidTooHigh: (value) => {
                        return Number(value) + TRANSACTION_FEE > (accountBalance || 0)
                          ? 'You do not have enough funds to place this bid'
                          : true
                      },
                    },
                  })}
                  disabled={auctionEnded}
                  placeholder={auctionEnded ? 'Auction ended' : 'Enter your bid'}
                  nodeStart={<JoyTokenIcon variant="gray" size={24} />}
                  nodeEnd={!!bid && <Pill variant="default" label={`${convertToUSD(bid)}`} />}
                  type="number"
                  error={!!errors.bid}
                  helperText={errors.bid && errors.bid.message}
                  onBlur={(event) => {
                    const { target } = event
                    if (Number(target.value) % 1 !== 0) {
                      setValue('bid', Math.floor(Number(event.target.value)))
                    }
                  }}
                />
                {showBuyNowInfo && (
                  <BuyNowInfo variant="t100" margin={{ top: 2 }}>
                    Max bid cannot be more than buy now price. Bidding for amount higher than Buy now will automatically
                    end the auction and make you an owner of that NFT.
                  </BuyNowInfo>
                )}
              </>
            ) : (
              <MinimumBidWrapper>
                <MinimumBid>
                  <Text variant="h300" secondary>
                    Price:
                  </Text>
                  <JoyTokenIcon variant="silver" size={24} />{' '}
                  <Text variant="h400">{buyNowPrice || auctionBuyNowPrice}</Text>
                </MinimumBid>
              </MinimumBidWrapper>
            )}
            <Divider />
            <FlexWrapper>
              <Text variant="h400">Revenue split</Text>
              <Information
                placement="top"
                text="Revenue split shows the proceedings from this sale based on royalties set up by the creator"
              />
            </FlexWrapper>
            <PaymentSplitWrapper>
              <div>
                <Text variant="h300" secondary>
                  Owner
                </Text>
                <PaymentSplitValues>
                  <Avatar size="bid" assetUrl={ownerMemberAvatarUrl} />
                  <Text variant="h400" secondary margin={{ left: 2 }}>
                    {ownerRoyalty}%
                  </Text>
                </PaymentSplitValues>
              </div>
              <div>
                <Text variant="h300" secondary>
                  Creator
                </Text>
                <PaymentSplitValues>
                  <Avatar size="bid" assetUrl={creatorAvatarUrl} />
                  <Text variant="h400" secondary margin={{ left: 2 }}>
                    {creatorRoyalty}%
                  </Text>
                </PaymentSplitValues>
              </div>
              <div>
                <Text variant="h300" secondary>
                  Platform
                </Text>
                <PaymentSplitValues>
                  <SvgJoystreamLogoShort height={24} viewBox="0 0 26 32" />
                  <Text variant="h400" secondary margin={{ left: 2 }}>
                    {nftPlatformFeePercentage}%
                  </Text>
                </PaymentSplitValues>
              </div>
            </PaymentSplitWrapper>
            <Divider />
            <Text variant="h400" margin={{ bottom: 4 }}>
              Price breakdown
            </Text>
            <Row>
              <Text variant="t100" secondary color={insufficientFoundsError ? cVar('colorTextError') : undefined}>
                Your balance
              </Text>
              {accountBalance != null ? (
                <Text variant="t100" secondary color={insufficientFoundsError ? cVar('colorTextError') : undefined}>
                  {formatTokens(accountBalance, true)}
                </Text>
              ) : (
                <SkeletonLoader width={82} height={16} />
              )}
            </Row>
            <Row>
              <Text variant="t100" secondary>
                {type === 'buy_now' || isBuyNowClicked ? 'Price' : bid ? 'Your bid' : ''}
              </Text>
              {(bid > 0 || isBuyNowClicked || type === 'buy_now') && (
                <Text variant="t100" secondary>
                  {formatTokens(type !== 'buy_now' ? (isBuyNowClicked ? auctionBuyNowPrice : bid) : buyNowPrice, true)}
                </Text>
              )}
            </Row>
            {(bid || type === 'buy_now') && (
              <>
                <Row>
                  <Text variant="t100" secondary>
                    Transaction fee
                  </Text>
                  <Text variant="t100" secondary>
                    {formatTokens(TRANSACTION_FEE, true)}
                  </Text>
                </Row>
                <Row>
                  <Text variant="h500" secondary>
                    You will pay
                  </Text>
                  <Text variant="h500">
                    {formatTokens((type === 'buy_now' ? buyNowPrice : Number(bid) || 0) + TRANSACTION_FEE, true)}
                  </Text>
                </Row>
              </>
            )}
            {type === 'open_auction' && bidLockingTime && (
              <Messages>
                <SvgAlertsWarning24 />
                <Text variant="t200" secondary margin={{ left: 2 }}>
                  Your bid can be withdrawn if it’s not accepted by the owner within{' '}
                  {formatDuration(intervalToDuration({ start: 0, end: bidLockingTime }))} from placing it.
                </Text>
              </Messages>
            )}
            {type === 'english_auction' && !isBuyNowClicked && (
              <Messages>
                <SvgAlertsWarning24 />
                <Text variant="t200" secondary margin={{ left: 2 }}>
                  After placing your bid, you will not be able to withdraw it. If someone places higher bid, your bid
                  will be returned automatically.
                </Text>
              </Messages>
            )}
          </InnerContainer>
        </PlaceBidWrapper>
      </Content>
    </BottomDrawer>
  )
}
Example #13
Source File: ActionLog.tsx    From amplication with Apache License 2.0 4 votes vote down vote up
ActionLog = ({ action, title, versionNumber }: Props) => {
  const logData = useMemo(() => {
    if (!action?.steps) return [];

    return action?.steps.map((step) => {
      let duration = "";
      if (step.completedAt) {
        const seconds = differenceInSeconds(
          new Date(step.completedAt),
          new Date(step.createdAt)
        );
        duration = seconds.toString().concat(SECOND_STRING);
      }
      return {
        ...step,
        duration: duration,
        messages: step.logs
          ?.map((log) => {
            return chalk`{${LOG_LEVEL_TO_CHALK[log.level]} ${
              log.createdAt
            }  {gray (${log.level})} ${log.message} }`;
          })
          .join("\n"),
      };
    });
  }, [action]);

  const actionStatus = useMemo(() => {
    if (
      logData.find(
        (step) =>
          step.status === models.EnumActionStepStatus.Waiting ||
          step.status === models.EnumActionStepStatus.Running
      )
    )
      return models.EnumActionStepStatus.Running;

    if (
      logData.find((step) => step.status === models.EnumActionStepStatus.Failed)
    )
      return models.EnumActionStepStatus.Failed;

    return models.EnumActionStepStatus.Success;
  }, [logData]);

  const lastStepCompletedAt = useMemo(() => {
    if (actionStatus === models.EnumActionStepStatus.Running) return null;

    return last(logData)?.completedAt;
  }, [logData, actionStatus]);

  return (
    <div className={`${CLASS_NAME}`}>
      <div className={`${CLASS_NAME}__header`}>
        <Icon icon="option_set" />
        {!action ? (
          <h3>Action Log</h3>
        ) : (
          <>
            <h3>
              {title} <span>{versionNumber}</span>
            </h3>

            <div className={`${CLASS_NAME}__header__info__status`}>
              <CircleIcon
                size={EnumCircleIconSize.Small}
                {...STEP_STATUS_TO_STYLE[actionStatus]}
              />
              {actionStatus}
            </div>
            <div className="spacer" />
            <div className={`${CLASS_NAME}__header__info__time`}>
              Total duration{" "}
              <Timer
                startTime={action.createdAt}
                runTimer
                endTime={lastStepCompletedAt}
              />
            </div>
          </>
        )}
      </div>
      <div className={`${CLASS_NAME}__body`}>
        {logData.map((stepData) => (
          <div className={`${CLASS_NAME}__step`} key={stepData.id}>
            <div className={`${CLASS_NAME}__step__row`}>
              <span
                className={`${CLASS_NAME}__step__status ${CLASS_NAME}__step__status--${stepData.status.toLowerCase()}`}
              >
                {stepData.status === models.EnumActionStepStatus.Running ? (
                  <CircularProgress size={16} />
                ) : (
                  <Icon icon={STEP_STATUS_TO_ICON[stepData.status]} />
                )}
              </span>
              <span className={`${CLASS_NAME}__step__message`}>
                {stepData.message}
              </span>
              <span className={`${CLASS_NAME}__step__duration`}>
                {stepData.duration}
              </span>
            </div>
            {!isEmpty(stepData.messages) && (
              <div className={`${CLASS_NAME}__step__log`}>
                <LazyLog
                  rowHeight={LOG_ROW_HEIGHT}
                  lineClassName={`${CLASS_NAME}__line`}
                  extraLines={0}
                  enableSearch={false}
                  text={stepData.messages}
                  height={10} //we use a random value in order to disable the auto-sizing, and use "height:auto !important" in CSS
                />
              </div>
            )}
          </div>
        ))}

        {isEmpty(logData) && (
          <div className={`${CLASS_NAME}__empty-state`}>
            <img src={logsImage} alt="log is empty" />
            <div className={`${CLASS_NAME}__empty-state__title`}>
              Create or select an action to view the log
            </div>
          </div>
        )}
      </div>
    </div>
  );
}