/ src / modules / dashboard / DashboardTopPanel.tsx
DashboardTopPanel.tsx
  1  import { ChainId } from '@aave/contract-helpers';
  2  import { normalize, UserIncentiveData, valueToBigNumber } from '@aave/math-utils';
  3  import { Trans } from '@lingui/macro';
  4  import { Box, Button, Typography, useMediaQuery, useTheme } from '@mui/material';
  5  import Link from 'next/link';
  6  import * as React from 'react';
  7  import { useState } from 'react';
  8  import { NetAPYTooltip } from 'src/components/infoTooltips/NetAPYTooltip';
  9  import { getMarketInfoById } from 'src/components/MarketSwitcher';
 10  import { FormattedNumber } from 'src/components/primitives/FormattedNumber';
 11  import { ROUTES } from 'src/components/primitives/Link';
 12  import { PageTitle } from 'src/components/TopInfoPanel/PageTitle';
 13  import { useModalContext } from 'src/hooks/useModal';
 14  import { useWeb3Context } from 'src/libs/hooks/useWeb3Context';
 15  import { useRootStore } from 'src/store/root';
 16  import { selectIsMigrationAvailable } from 'src/store/v3MigrationSelectors';
 17  import { DASHBOARD, GENERAL } from 'src/utils/mixPanelEvents';
 18  import { useShallow } from 'zustand/shallow';
 19  
 20  import { HealthFactorNumber } from '../../components/HealthFactorNumber';
 21  import { NoData } from '../../components/primitives/NoData';
 22  import { TopInfoPanel } from '../../components/TopInfoPanel/TopInfoPanel';
 23  import { TopInfoPanelItem } from '../../components/TopInfoPanel/TopInfoPanelItem';
 24  import { useAppDataContext } from '../../hooks/app-data-provider/useAppDataProvider';
 25  import { LiquidationRiskParametresInfoModal } from './LiquidationRiskParametresModal/LiquidationRiskParametresModal';
 26  
 27  export const DashboardTopPanel = () => {
 28    const { user, reserves, loading } = useAppDataContext();
 29    const { currentAccount } = useWeb3Context();
 30    const [open, setOpen] = useState(false);
 31    const { openClaimRewards } = useModalContext();
 32    const [
 33      trackEvent,
 34      currentNetworkConfig,
 35      currentMarketData,
 36      currentMarket,
 37      isMigrateToV3Available,
 38    ] = useRootStore(
 39      useShallow((store) => [
 40        store.trackEvent,
 41        store.currentNetworkConfig,
 42        store.currentMarketData,
 43        store.currentMarket,
 44        selectIsMigrationAvailable(store),
 45      ])
 46    );
 47    const { market } = getMarketInfoById(currentMarket);
 48    const showMigrateButton = user
 49      ? isMigrateToV3Available && currentAccount !== '' && Number(user.totalLiquidityUSD) > 0
 50      : false;
 51    const theme = useTheme();
 52    const downToSM = useMediaQuery(theme.breakpoints.down('sm'));
 53  
 54    const { claimableRewardsUsd } = user
 55      ? Object.keys(user.calculatedUserIncentives).reduce(
 56          (acc, rewardTokenAddress) => {
 57            const incentive: UserIncentiveData = user.calculatedUserIncentives[rewardTokenAddress];
 58            const rewardBalance = normalize(
 59              incentive.claimableRewards,
 60              incentive.rewardTokenDecimals
 61            );
 62  
 63            let tokenPrice = 0;
 64            // getting price from reserves for the native rewards for v2 markets
 65            if (!currentMarketData.v3 && Number(rewardBalance) > 0) {
 66              if (currentMarketData.chainId === ChainId.mainnet) {
 67                const aave = reserves.find((reserve) => reserve.symbol === 'AAVE');
 68                tokenPrice = aave ? Number(aave.priceInUSD) : 0;
 69              } else {
 70                reserves.forEach((reserve) => {
 71                  if (reserve.symbol === currentNetworkConfig.wrappedBaseAssetSymbol) {
 72                    tokenPrice = Number(reserve.priceInUSD);
 73                  }
 74                });
 75              }
 76            } else {
 77              tokenPrice = Number(incentive.rewardPriceFeed);
 78            }
 79  
 80            const rewardBalanceUsd = Number(rewardBalance) * tokenPrice;
 81  
 82            if (rewardBalanceUsd > 0) {
 83              if (acc.assets.indexOf(incentive.rewardTokenSymbol) === -1) {
 84                acc.assets.push(incentive.rewardTokenSymbol);
 85              }
 86  
 87              acc.claimableRewardsUsd += Number(rewardBalanceUsd);
 88            }
 89  
 90            return acc;
 91          },
 92          { claimableRewardsUsd: 0, assets: [] } as { claimableRewardsUsd: number; assets: string[] }
 93        )
 94      : { claimableRewardsUsd: 0 };
 95  
 96    const loanToValue =
 97      user?.totalCollateralMarketReferenceCurrency === '0'
 98        ? '0'
 99        : valueToBigNumber(user?.totalBorrowsMarketReferenceCurrency || '0')
100            .dividedBy(user?.totalCollateralMarketReferenceCurrency || '1')
101            .toFixed();
102  
103    const valueTypographyVariant = downToSM ? 'main16' : 'main21';
104    const noDataTypographyVariant = downToSM ? 'secondary16' : 'secondary21';
105  
106    return (
107      <>
108        {showMigrateButton && downToSM && (
109          <Box sx={{ width: '100%' }}>
110            <Link href={ROUTES.migrationTool}>
111              <Button
112                variant="gradient"
113                sx={{
114                  height: '40px',
115                  width: '100%',
116                }}
117              >
118                <Typography variant="buttonM">
119                  <Trans>Migrate to {market.marketTitle} v3 Market</Trans>
120                </Typography>
121              </Button>
122            </Link>
123          </Box>
124        )}
125        <TopInfoPanel
126          titleComponent={
127            <Box sx={{ display: 'flex', alignItems: 'center' }}>
128              <PageTitle
129                pageTitle={<Trans>Dashboard</Trans>}
130                withMarketSwitcher={true}
131                bridge={currentNetworkConfig.bridge}
132              />
133              {showMigrateButton && !downToSM && (
134                <Box sx={{ alignSelf: 'center', mb: 4, width: '100%' }}>
135                  <Link href={ROUTES.marketMigrationTool(currentMarket)}>
136                    <Button variant="gradient" sx={{ height: '20px' }}>
137                      <Typography variant="buttonS" data-cy={`migration-button`}>
138                        <Trans>Migrate to v3</Trans>
139                      </Typography>
140                    </Button>
141                  </Link>
142                </Box>
143              )}
144            </Box>
145          }
146        >
147          <TopInfoPanelItem title={<Trans>Net worth</Trans>} loading={loading} hideIcon>
148            {currentAccount ? (
149              <FormattedNumber
150                value={Number(user?.netWorthUSD || 0)}
151                symbol="USD"
152                variant={valueTypographyVariant}
153                visibleDecimals={2}
154                compact
155                symbolsColor="#A5A8B6"
156                symbolsVariant={noDataTypographyVariant}
157              />
158            ) : (
159              <NoData variant={noDataTypographyVariant} sx={{ opacity: '0.7' }} />
160            )}
161          </TopInfoPanelItem>
162  
163          <TopInfoPanelItem
164            title={
165              <div style={{ display: 'flex' }}>
166                <Trans>Net APY</Trans>
167                <NetAPYTooltip
168                  event={{
169                    eventName: GENERAL.TOOL_TIP,
170                    eventParams: { tooltip: 'NET APY: Dashboard Banner' },
171                  }}
172                />
173              </div>
174            }
175            loading={loading}
176            hideIcon
177          >
178            {currentAccount && user && Number(user.netWorthUSD) > 0 ? (
179              <FormattedNumber
180                value={user ? user.netAPY : 0}
181                variant={valueTypographyVariant}
182                visibleDecimals={2}
183                percent
184                symbolsColor="#A5A8B6"
185                symbolsVariant={noDataTypographyVariant}
186              />
187            ) : (
188              <NoData variant={noDataTypographyVariant} sx={{ opacity: '0.7' }} />
189            )}
190          </TopInfoPanelItem>
191  
192          {currentAccount && user?.healthFactor !== '-1' && (
193            <TopInfoPanelItem
194              title={
195                <Box sx={{ display: 'inline-flex', alignItems: 'center' }}>
196                  <Trans>Health factor</Trans>
197                </Box>
198              }
199              loading={loading}
200              hideIcon
201            >
202              <HealthFactorNumber
203                value={user?.healthFactor || '-1'}
204                variant={valueTypographyVariant}
205                onInfoClick={() => {
206                  trackEvent(DASHBOARD.VIEW_RISK_DETAILS);
207                  setOpen(true);
208                }}
209              />
210            </TopInfoPanelItem>
211          )}
212  
213          {currentAccount && claimableRewardsUsd > 0 && (
214            <TopInfoPanelItem title={<Trans>Available rewards</Trans>} loading={loading} hideIcon>
215              <Box
216                sx={{
217                  display: 'flex',
218                  alignItems: { xs: 'flex-start', xsm: 'center' },
219                  flexDirection: { xs: 'column', xsm: 'row' },
220                }}
221              >
222                <Box sx={{ display: 'inline-flex', alignItems: 'center' }} data-cy={'Claim_Box'}>
223                  <FormattedNumber
224                    value={claimableRewardsUsd}
225                    variant={valueTypographyVariant}
226                    visibleDecimals={2}
227                    compact
228                    symbol="USD"
229                    symbolsColor="#A5A8B6"
230                    symbolsVariant={noDataTypographyVariant}
231                    data-cy={'Claim_Value'}
232                  />
233                </Box>
234  
235                <Button
236                  variant="gradient"
237                  size="small"
238                  onClick={() => openClaimRewards()}
239                  sx={{ minWidth: 'unset', ml: { xs: 0, xsm: 2 } }}
240                  data-cy={'Dashboard_Claim_Button'}
241                >
242                  <Trans>Claim</Trans>
243                </Button>
244              </Box>
245            </TopInfoPanelItem>
246          )}
247        </TopInfoPanel>
248        <LiquidationRiskParametresInfoModal
249          open={open}
250          setOpen={setOpen}
251          healthFactor={user?.healthFactor || '-1'}
252          loanToValue={loanToValue}
253          currentLoanToValue={user?.currentLoanToValue || '0'}
254          currentLiquidationThreshold={user?.currentLiquidationThreshold || '0'}
255        />
256      </>
257    );
258  };