antoncoding/monarch

View on GitHub
app/positions/components/SuppliedMarketsDetail.tsx

Summary

Maintainability
A
0 mins
Test Coverage
import React from 'react';
import { Tooltip } from '@nextui-org/tooltip';
import { ExternalLinkIcon } from '@radix-ui/react-icons';
import { IoWarningOutline } from 'react-icons/io5';
import OracleVendorBadge from '@/components/OracleVendorBadge';
import { TokenIcon } from '@/components/TokenIcon';
import { formatReadable, formatBalance } from '@/utils/balance';
import { getMarketURL } from '@/utils/external';
import { MarketPosition, GroupedPosition, WarningWithDetail, WarningCategory } from '@/utils/types';
import { getCollateralColor } from '../utils/colors';

type SuppliedMarketsDetailProps = {
  groupedPosition: GroupedPosition;
  setShowModal: (show: boolean) => void;
  setSelectedPosition: (position: MarketPosition) => void;
};

function WarningTooltip({ warnings }: { warnings: WarningWithDetail[] }) {
  return (
    <div className="p-2 font-zen">
      {Object.values(WarningCategory).map((category) => {
        const categoryWarnings = warnings.filter((w) => w.category === category);
        if (categoryWarnings.length === 0) return null;
        return (
          <div key={category} className="mb-2">
            <h4 className="font-bold capitalize">{category}</h4>
            <ul className="list-none pl-0">
              {categoryWarnings.map((warning, index) => (
                <li key={index}>- {warning.description}</li>
              ))}
            </ul>
          </div>
        );
      })}
    </div>
  );
}

export function SuppliedMarketsDetail({
  groupedPosition,
  setShowModal,
  setSelectedPosition,
}: SuppliedMarketsDetailProps) {
  const sortedMarkets = [...groupedPosition.markets].sort(
    (a, b) =>
      Number(formatBalance(b.supplyAssets, b.market.loanAsset.decimals)) -
      Number(formatBalance(a.supplyAssets, a.market.loanAsset.decimals)),
  );

  const totalSupply = groupedPosition.totalSupply;

  const getWarningColor = (warnings: WarningWithDetail[]) => {
    if (warnings.some((w) => w.level === 'alert')) return 'text-red-500';
    if (warnings.some((w) => w.level === 'warning')) return 'text-yellow-500';
    return '';
  };

  return (
    <div className="bg-secondary bg-opacity-20 p-4">
      <div className="mb-4 flex items-center justify-center">
        <div className="my-4 w-1/2">
          <h3 className="mb-2 text-base font-semibold">Collateral Exposure</h3>
          <div className="flex h-3 w-full overflow-hidden rounded-full bg-gray-200">
            {groupedPosition.processedCollaterals.map((collateral, colIndex) => (
              <div
                key={`${collateral.address}-${colIndex}`}
                className="h-full opacity-70"
                style={{
                  width: `${collateral.percentage}%`,
                  backgroundColor:
                    collateral.symbol === 'Others'
                      ? '#A0AEC0'
                      : getCollateralColor(collateral.address),
                }}
                title={`${collateral.symbol}: ${collateral.percentage.toFixed(2)}%`}
              />
            ))}
          </div>
          <div className="mt-1 flex flex-wrap justify-center text-xs">
            {groupedPosition.processedCollaterals.map((collateral, colIndex) => (
              <span key={`${collateral.address}-${colIndex}`} className="mb-1 mr-2 opacity-70">
                <span
                  style={{
                    color:
                      collateral.symbol === 'Others'
                        ? '#A0AEC0'
                        : getCollateralColor(collateral.address),
                  }}
                >
                  ■
                </span>{' '}
                {collateral.symbol}: {formatReadable(collateral.percentage)}%
              </span>
            ))}
          </div>
        </div>
      </div>
      <table className="no-hover-effect w-full font-zen">
        <thead className="table-header">
          <tr>
            <th>Market</th>
            <th>Collateral</th>
            <th>Oracle</th>
            <th>LLTV</th>
            <th>APY</th>
            <th>Supplied</th>
            <th>% of Portfolio</th>
            <th>Actions</th>
          </tr>
        </thead>
        <tbody className="table-body text-xs">
          {sortedMarkets.map((position) => {
            const suppliedAmount = Number(
              formatBalance(position.supplyAssets, position.market.loanAsset.decimals),
            );
            const percentageOfPortfolio =
              totalSupply > 0 ? (suppliedAmount / totalSupply) * 100 : 0;
            const warningColor = getWarningColor(position.warningsWithDetail);

            return (
              <tr key={position.market.uniqueKey} className="gap-1">
                <td data-label="Market" className="text-center">
                  <div className="flex items-center justify-center">
                    <div className="mr-1 w-4">
                      {position.warningsWithDetail.length > 0 ? (
                        <Tooltip
                          content={<WarningTooltip warnings={position.warningsWithDetail} />}
                          placement="top"
                        >
                          <div>
                            <IoWarningOutline className={`h-4 w-4 ${warningColor}`} />
                          </div>
                        </Tooltip>
                      ) : (
                        <div className="h-4 w-4" />
                      )}
                    </div>
                    {/* <Tooltip content="View on Explorer" placement="top"> */}
                    <a
                      className="group flex items-center justify-center no-underline hover:underline"
                      href={getMarketURL(
                        position.market.uniqueKey,
                        position.market.morphoBlue.chain.id,
                      )}
                      target="_blank"
                      rel="noopener noreferrer"
                    >
                      {position.market.uniqueKey.slice(2, 8)}
                      <ExternalLinkIcon className="ml-1 opacity-0 group-hover:opacity-100" />
                    </a>
                    {/* </Tooltip> */}
                  </div>
                </td>
                <td data-label="Collateral" className="text-center">
                  {position.market.collateralAsset ? (
                    <div className="flex items-center justify-center gap-1">
                      <TokenIcon
                        address={position.market.collateralAsset.address}
                        chainId={position.market.morphoBlue.chain.id}
                        width={18}
                        height={18}
                      />
                      {position.market.collateralAsset.symbol}
                    </div>
                  ) : (
                    'N/A'
                  )}
                </td>
                <td data-label="Oracle" className="text-center">
                  <div className="flex justify-center">
                    <OracleVendorBadge oracleData={position.market.oracle.data} useTooltip />
                  </div>
                </td>
                <td data-label="LLTV" className="text-center">
                  {formatBalance(position.market.lltv, 16)}%
                </td>
                <td data-label="APY" className="text-center">
                  {formatReadable(position.market.dailyApys.netSupplyApy * 100)}%
                </td>
                <td data-label="Supplied" className="text-center">
                  {formatReadable(suppliedAmount)} {position.market.loanAsset.symbol}
                </td>
                <td data-label="% of Portfolio" className="text-center">
                  <div className="flex items-center">
                    <div className="mr-2 h-2 w-full rounded-full bg-gray-200">
                      <div
                        className="h-full rounded-full bg-blue-500"
                        style={{ width: `${percentageOfPortfolio}%` }}
                      />
                    </div>
                    <span className="whitespace-nowrap">
                      {formatReadable(percentageOfPortfolio)}%
                    </span>
                  </div>
                </td>
                <td data-label="Actions" className="text-right">
                  <button
                    type="button"
                    className="bg-hovered rounded-sm p-1 text-xs duration-300 ease-in-out hover:bg-orange-500"
                    onClick={() => {
                      setShowModal(true);
                      setSelectedPosition(position);
                    }}
                  >
                    Withdraw
                  </button>
                </td>
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>
  );
}