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 };