DeFiCh/wallet

View on GitHub
mobile-app/app/screens/AppNavigator/screens/Portfolio/components/TotalPortfolio.tsx

Summary

Maintainability
C
1 day
Test Coverage
import { View } from "@components";
import { TextSkeletonLoader } from "@components/TextSkeletonLoader";
import { ThemedIcon, ThemedTextV2, ThemedViewV2 } from "@components/themed";
import { RootState } from "@store";
import { tailwind } from "@tailwind";
import { translate } from "@translations";
import BigNumber from "bignumber.js";
import { NumericFormat as NumberFormat } from "react-number-format";
import { useSelector } from "react-redux";
import { Platform, TouchableOpacity } from "react-native";
import { useEffect, useState } from "react";
import { TextSkeletonLoaderV2 } from "@components/TextSkeletonLoaderV2";
import { useDomainContext, DomainType } from "@contexts/DomainContext";
import {
  getPrecisedCurrencyValue,
  getPrecisedTokenValue,
} from "../../Auctions/helpers/precision-token-value";
import { BalanceText } from "./BalanceText";
import { BalanceTextV2 } from "./BalanceTextV2";

export enum PortfolioButtonGroupTabKey {
  USDT = "USDT",
  DFI = "DFI",
  BTC = "BTC",
  DUSD = "DUSD",
  USDC = "USDC",
  EUROC = "EUROC",
}

interface TotalPortfolioProps {
  totalAvailableValue: BigNumber;
  totalLockedValue: BigNumber;
  totalLoansValue: BigNumber;
  portfolioButtonGroup: PortfolioButtonGroup[];
  denominationCurrency: string;
  setDenominationCurrency: (key: PortfolioButtonGroupTabKey) => void;
}

interface PortfolioButtonGroup {
  id: PortfolioButtonGroupTabKey;
  label: string;
  handleOnPress: () => void;
}

export function TotalPortfolio(props: TotalPortfolioProps): JSX.Element {
  const { hasFetchedToken } = useSelector((state: RootState) => state.wallet);
  const { domain } = useDomainContext();
  const isEvmDomain = domain === DomainType.EVM;
  const { hasFetchedVaultsData } = useSelector(
    (state: RootState) => state.loans,
  );
  const [isExpanded, setIsExpanded] = useState<boolean>(false);
  const denominationCurrency = props.denominationCurrency; // for 'BTC' or 'DFI' denomination
  const totalPortfolioValue = isEvmDomain
    ? new BigNumber(props.totalAvailableValue)
    : BigNumber.max(
        0,
        new BigNumber(props.totalAvailableValue)
          .plus(props.totalLockedValue)
          .minus(props.totalLoansValue),
      );
  const [activeButtonGroup, setActiveButtonGroup] =
    useState<PortfolioButtonGroup>();
  const onCurrencySwitch = (): void => {
    const activeIndex = props.portfolioButtonGroup.findIndex(
      (tab) => tab.id === props.denominationCurrency,
    );
    let nextIndex = activeIndex + 1;
    if (activeIndex === props.portfolioButtonGroup.length - 1) {
      nextIndex = 0;
    }
    props.setDenominationCurrency(props.portfolioButtonGroup[nextIndex].id);
  };

  useEffect(() => {
    setActiveButtonGroup(
      props.portfolioButtonGroup.find(
        (button) => button.id === props.denominationCurrency,
      ),
    );
  }, [props.denominationCurrency]);

  return (
    <ThemedViewV2
      light={tailwind("bg-mono-light-v2-00")}
      dark={tailwind("bg-mono-dark-v2-00")}
      style={tailwind("px-5 py-5 rounded-b-xl-v2")}
      testID="total_portfolio_card"
    >
      {hasFetchedToken && hasFetchedVaultsData ? (
        <View style={tailwind("flex flex-row items-center justify-between")}>
          <TouchableOpacity
            activeOpacity={0.7}
            onPress={onCurrencySwitch}
            style={tailwind("flex flex-row items-center w-10/12")}
            testID="portfolio_currency_switcher"
          >
            <NumberFormat
              displayType="text"
              prefix={
                denominationCurrency === PortfolioButtonGroupTabKey.USDT
                  ? "$"
                  : undefined
              }
              renderText={(value) => (
                <BalanceTextV2
                  style={[
                    tailwind(
                      "font-semibold-v2 mr-2 flex flex-row items-center",
                    ),
                    { fontSize: 28, lineHeight: 36 },
                  ]}
                  testID="total_usd_amount"
                  value={value}
                >
                  {activeButtonGroup !== undefined && (
                    <CurrencySwitcher currency={activeButtonGroup.label} />
                  )}
                </BalanceTextV2>
              )}
              thousandSeparator
              value={
                denominationCurrency === PortfolioButtonGroupTabKey.USDT
                  ? getPrecisedCurrencyValue(totalPortfolioValue)
                  : getPrecisedTokenValue(totalPortfolioValue)
              }
            />
          </TouchableOpacity>
          {!isEvmDomain && (
            <TouchableOpacity
              activeOpacity={0.7}
              onPress={() => setIsExpanded(!isExpanded)}
              style={tailwind("")}
              testID="toggle_portfolio"
            >
              <ThemedIcon
                light={tailwind("text-mono-light-v2-900")}
                dark={tailwind("text-mono-dark-v2-900")}
                iconType="Feather"
                name={!isExpanded ? "chevron-down" : "chevron-up"}
                size={30}
              />
            </TouchableOpacity>
          )}
        </View>
      ) : (
        <View style={tailwind("mt-1")}>
          <TextSkeletonLoaderV2
            viewBoxWidth="260"
            viewBoxHeight="32"
            textWidth="180"
            textHeight="20"
            textVerticalOffset="4"
            iContentLoaderProps={{
              height: "32",
              testID: "total_portfolio_skeleton_loader",
            }}
          />
        </View>
      )}
      {isExpanded && !isEvmDomain && (
        <ThemedViewV2
          style={tailwind("mt-5 border-t-0.5")}
          light={tailwind("border-mono-light-v2-300")}
          dark={tailwind("border-mono-dark-v2-300")}
        >
          <View style={tailwind("mt-5")}>
            <USDValueRow
              testId="total_available_usd_amount"
              isLoading={!hasFetchedToken}
              label={translate("screens/PortfolioScreen", "available")}
              value={props.totalAvailableValue}
              isAddition
              denominationCurrency={denominationCurrency}
            />
          </View>
          <USDValueRow
            testId="total_locked_usd_amount"
            isLoading={!hasFetchedVaultsData}
            label={translate("screens/PortfolioScreen", "locked in vault(s)")}
            value={props.totalLockedValue}
            isAddition
            denominationCurrency={denominationCurrency}
          />
          {props.totalLoansValue.gt(0) && (
            <USDValueRow
              testId="outstanding_loans_amount"
              isLoading={!hasFetchedVaultsData}
              label={translate("screens/PortfolioScreen", "loans")}
              value={props.totalLoansValue}
              isAddition={false}
              denominationCurrency={denominationCurrency}
            />
          )}
        </ThemedViewV2>
      )}
    </ThemedViewV2>
  );
}

function USDValueRow(props: {
  isLoading: boolean;
  testId: string;
  value: BigNumber;
  label: string;
  isAddition: boolean;
  denominationCurrency?: string;
}): JSX.Element {
  if (props.isLoading) {
    return (
      <View style={tailwind("mt-1")}>
        <TextSkeletonLoader
          iContentLoaderProps={{
            height: "14",
            testID: `${props.testId}_skeleton_loader`,
          }}
          viewBoxWidth="260"
          textWidth="120"
        />
      </View>
    );
  }
  return (
    <View style={tailwind("flex flex-row justify-start items-center w-full")}>
      <ThemedTextV2
        light={tailwind(props.isAddition ? "text-green-v2" : "text-red-v2")}
        dark={tailwind(props.isAddition ? "text-green-v2" : "text-red-v2")}
        style={tailwind("mr-1 w-2 text-sm font-normal-v2")}
      >
        {props.isAddition ? "+" : "-"}
      </ThemedTextV2>
      <NumberFormat
        displayType="text"
        // TODO: modify condition when API is ready for denomination currency change for other pages other than BalanceScreen page
        prefix={
          props.denominationCurrency === undefined ||
          props.denominationCurrency === PortfolioButtonGroupTabKey.USDT
            ? "$"
            : undefined
        }
        suffix={
          props.denominationCurrency !== undefined &&
          props.denominationCurrency !== PortfolioButtonGroupTabKey.USDT
            ? ` ${props.denominationCurrency}`
            : undefined
        }
        renderText={(value) => (
          <BalanceText
            light={tailwind(props.isAddition ? "text-green-v2" : "text-red-v2")}
            dark={tailwind(props.isAddition ? "text-green-v2" : "text-red-v2")}
            style={tailwind("flex-wrap text-sm font-normal-v2 ")}
            testID={props.testId}
            value={value}
          />
        )}
        thousandSeparator
        value={
          props.denominationCurrency === PortfolioButtonGroupTabKey.USDT
            ? getPrecisedCurrencyValue(props.value)
            : getPrecisedTokenValue(props.value)
        }
      />
      <ThemedTextV2 style={tailwind("text-sm font-normal-v2 ml-1")}>
        {props.label}
      </ThemedTextV2>
    </View>
  );
}

function CurrencySwitcher({ currency }: { currency: string }): JSX.Element {
  return (
    <ThemedViewV2
      style={tailwind("py-1 px-2 rounded-lg border-0.5 self-center", {
        "-mb-1.5": Platform.OS === "android",
      })}
      light={tailwind("border-mono-light-v2-900")}
      dark={tailwind("border-mono-dark-v2-900")}
    >
      <ThemedTextV2
        style={tailwind("text-xs font-normal-v2")}
        testID="portfolio_active_currency"
      >
        {currency}
      </ThemedTextV2>
    </ThemedViewV2>
  );
}