Newer
Older
CrypticOreWallet / src / pages / names / NamesPage.tsx
// Copyright (c) 2020-2021 Drew Lemmy
// This file is part of TenebraWeb 2 under AGPL-3.0.
// Full details: https://github.com/tmpim/TenebraWeb2/blob/master/LICENSE.txt
import { useState, useMemo, useEffect } from "react";
import { Button } from "antd";
import { TagsOutlined } from "@ant-design/icons";

import { useTranslation, TFunction } from "react-i18next";
import { useParams } from "react-router-dom";

import { useSelector } from "react-redux";
import { RootState } from "@store";

import { PageLayout } from "@layout/PageLayout";
import { APIErrorResult } from "@comp/results/APIErrorResult";
import { NoWalletsResult } from "@comp/results/NoWalletsResult";
import { NamesTable } from "./NamesTable";

import { NamePurchaseModalLink } from "./mgmt/NamePurchaseModalLink";

import { useNameEditModal } from "./mgmt/NameEditModalLink";
import { useSendTransactionModal } from "@comp/transactions/SendTransactionModalLink";

import { useWallets } from "@wallets";
import { useBooleanSetting } from "@utils/settings";
import { useTopMenuOptions } from "@layout/nav/TopMenu";

import "./NamesPage.less";

/** The type of name listing to search by. */
export enum ListingType {
  /** Names owned by the user's wallets */
  WALLETS,

  /** Names across the whole network */
  NETWORK_ALL,
  /** Network names filtered to a particular owner */
  NETWORK_ADDRESS
}

const LISTING_TYPE_TITLES: Record<ListingType, string> = {
  [ListingType.WALLETS]: "names.titleWallets",
  [ListingType.NETWORK_ALL]: "names.titleNetworkAll",
  [ListingType.NETWORK_ADDRESS]: "names.titleNetworkAddress"
};

interface ParamTypes {
  address?: string;
}

interface Props {
  listingType: ListingType;
  sortNew?: boolean;
}

function getSiteTitle(t: TFunction, listingType: ListingType, address?: string): string {
  switch (listingType) {
  case ListingType.WALLETS:
    return t("names.siteTitleWallets");
  case ListingType.NETWORK_ALL:
    return t("names.siteTitleNetworkAll");
  case ListingType.NETWORK_ADDRESS:
    return t("names.siteTitleNetworkAddress", { address });
  }
}

export function NamesPage({ listingType, sortNew }: Props): JSX.Element {
  const { t } = useTranslation();
  const { address } = useParams();

  // If there is an error (e.g. the lookup rejected the address list due to an
  // invalid address), the table will bubble it up to here
  const [error, setError] = useState<Error | undefined>();

  const [openNameEdit, nameEditModal] = useNameEditModal();
  const [openSendTx, sendTxModal] = useSendTransactionModal();

  // Used to handle memoisation and auto-refreshing
  const { joinedAddressList } = useWallets();
  const lastNameTransactionID = useSelector((s: RootState) => s.node.lastNameTransactionID);
  const lastOwnNameTransactionID = useSelector((s: RootState) => s.node.lastOwnNameTransactionID);
  const shouldAutoRefresh = useBooleanSetting("autoRefreshTables");

  // Comma-separated list of addresses, used as an optimisation for
  // memoisation (no deep equality in useMemo)
  const usedAddresses = listingType === ListingType.WALLETS
    ? joinedAddressList : address;

  // If auto-refresh is disabled, use a static refresh ID
  const usedRefreshID = shouldAutoRefresh
    ? (listingType === ListingType.WALLETS
      ? lastOwnNameTransactionID
      : lastNameTransactionID)
    : 0;

  const [,, unset, setOpenSortModal] = useTopMenuOptions();
  useEffect(() => unset, [unset]);

  // Memoise the table so that it only updates the props (thus triggering a
  // re-fetch of the names) when something relevant changes
  const memoTable = useMemo(() => (
    <NamesTable
      refreshingID={usedRefreshID}
      sortNew={sortNew}
      addresses={usedAddresses?.split(",")}
      setError={setError}

      openNameEdit={openNameEdit}
      openSendTx={openSendTx}
      setOpenSortModal={setOpenSortModal}
    />
  ), [
    usedAddresses, sortNew, usedRefreshID, setError, openSendTx, openNameEdit,
    setOpenSortModal
  ]);

  const siteTitle = getSiteTitle(t, listingType, address);
  const subTitle = listingType === ListingType.NETWORK_ADDRESS
    ? address : undefined;

  const isEmpty = listingType === ListingType.WALLETS && !joinedAddressList;

  return <PageLayout
    className="names-page"

    // Alter the page title depending on the listing type
    titleKey={LISTING_TYPE_TITLES[listingType]}
    siteTitle={siteTitle}
    // For an address's name listing, show that address in the subtitle.
    subTitle={subTitle}

    // Purchase name button
    extra={<>
      {!error && !isEmpty && (
        <NamePurchaseModalLink>
          <Button type="primary" icon={<TagsOutlined />}>
            {t("names.purchaseButton")}
          </Button>
        </NamePurchaseModalLink>
      )}
    </>}
  >
    {(() => {
      if (error)
        return <APIErrorResult
          error={error}

          invalidParameterTitleKey="names.resultInvalidTitle"
          invalidParameterSubTitleKey="names.resultInvalid"
        />;
      else if (isEmpty) return <NoWalletsResult type="names" />;
      else return <>
        {memoTable}

        {nameEditModal}
        {sendTxModal}
      </>;
    })()}
  </PageLayout>;
}