GhoBorrowAssetsListItem.tsx
1 import { Trans } from '@lingui/macro'; 2 import { Box, Button, Stack, Tooltip, Typography, useMediaQuery, useTheme } from '@mui/material'; 3 import { CapType } from 'src/components/caps/helper'; 4 import { GhoIncentivesCard } from 'src/components/incentives/GhoIncentivesCard'; 5 import { AvailableTooltip } from 'src/components/infoTooltips/AvailableTooltip'; 6 import { FixedAPYTooltip } from 'src/components/infoTooltips/FixedAPYTooltip'; 7 import { ListColumn } from 'src/components/lists/ListColumn'; 8 import { ListItem } from 'src/components/lists/ListItem'; 9 import { Row } from 'src/components/primitives/Row'; 10 import { TokenIcon } from 'src/components/primitives/TokenIcon'; 11 import { useAppDataContext } from 'src/hooks/app-data-provider/useAppDataProvider'; 12 import { useModalContext } from 'src/hooks/useModal'; 13 import { useRootStore } from 'src/store/root'; 14 import { CustomMarket } from 'src/ui-config/marketsConfig'; 15 import { DASHBOARD_LIST_COLUMN_WIDTHS } from 'src/utils/dashboardSortUtils'; 16 import { getMaxGhoMintAmount } from 'src/utils/getMaxAmountAvailableToBorrow'; 17 import { weightedAverageAPY } from 'src/utils/ghoUtilities'; 18 19 import { Link, ROUTES } from '../../../../components/primitives/Link'; 20 import { ListButtonsColumn } from '../ListButtonsColumn'; 21 import { ListMobileItemWrapper } from '../ListMobileItemWrapper'; 22 import { ListValueColumn } from '../ListValueColumn'; 23 import { ListValueRow } from '../ListValueRow'; 24 import { GhoBorrowAssetsItem } from './types'; 25 26 export const GhoBorrowAssetsListItem = ({ 27 symbol, 28 iconSymbol, 29 name, 30 underlyingAsset, 31 isFreezed, 32 reserve, 33 }: GhoBorrowAssetsItem) => { 34 const { openBorrow } = useModalContext(); 35 const { user } = useAppDataContext(); 36 const currentMarket = useRootStore((store) => store.currentMarket); 37 const { ghoReserveData, ghoUserData, ghoUserLoadingData, ghoLoadingData } = useAppDataContext(); 38 const theme = useTheme(); 39 const downToXSM = useMediaQuery(theme.breakpoints.down('xsm')); 40 41 const maxAmountUserCanMint = user ? Number(getMaxGhoMintAmount(user, reserve)) : 0; 42 const availableBorrows = Math.min( 43 maxAmountUserCanMint, 44 ghoReserveData.aaveFacilitatorRemainingCapacity 45 ); 46 const borrowButtonDisable = isFreezed || availableBorrows <= 0; 47 const debtBalanceAfterMaxBorrow = availableBorrows + ghoUserData.userGhoBorrowBalance; 48 49 // Determine borrow APY range 50 const userCurrentBorrowApy = weightedAverageAPY( 51 ghoReserveData.ghoVariableBorrowAPY, 52 ghoUserData.userGhoBorrowBalance, 53 ghoUserData.userGhoAvailableToBorrowAtDiscount, 54 ghoReserveData.ghoBorrowAPYWithMaxDiscount 55 ); 56 const userBorrowApyAfterNewBorrow = weightedAverageAPY( 57 ghoReserveData.ghoVariableBorrowAPY, 58 debtBalanceAfterMaxBorrow, 59 ghoUserData.userGhoAvailableToBorrowAtDiscount, 60 ghoReserveData.ghoBorrowAPYWithMaxDiscount 61 ); 62 const ghoApyRange: [number, number] | undefined = !ghoUserLoadingData 63 ? [ 64 ghoUserData.userGhoAvailableToBorrowAtDiscount === 0 65 ? ghoReserveData.ghoBorrowAPYWithMaxDiscount 66 : userCurrentBorrowApy, 67 userBorrowApyAfterNewBorrow, 68 ] 69 : undefined; 70 71 const props: GhoBorrowAssetsListItemProps = { 72 symbol, 73 iconSymbol, 74 name, 75 underlyingAsset, 76 currentMarket, 77 availableBorrows, 78 borrowButtonDisable, 79 userDiscountTokenBalance: ghoUserData.userDiscountTokenBalance, 80 ghoApyRange, 81 ghoUserDataFetched: !ghoUserLoadingData, 82 userBorrowApyAfterNewBorrow, 83 ghoLoadingData, 84 onBorrowClick: () => openBorrow(underlyingAsset, currentMarket, name, 'dashboard'), 85 }; 86 if (downToXSM) { 87 return <GhoBorrowAssetsListItemMobile {...props} />; 88 } else { 89 return <GhoBorrowAssetsListItemDesktop {...props} />; 90 } 91 }; 92 93 interface GhoBorrowAssetsListItemProps { 94 symbol: string; 95 iconSymbol: string; 96 name: string; 97 underlyingAsset: string; 98 currentMarket: CustomMarket; 99 availableBorrows: number; 100 borrowButtonDisable: boolean; 101 userDiscountTokenBalance: number; 102 ghoApyRange: [number, number] | undefined; 103 ghoUserDataFetched: boolean; 104 userBorrowApyAfterNewBorrow: number; 105 ghoLoadingData: boolean; 106 onBorrowClick: () => void; 107 } 108 109 const GhoBorrowAssetsListItemDesktop = ({ 110 symbol, 111 iconSymbol, 112 name, 113 underlyingAsset, 114 currentMarket, 115 availableBorrows, 116 borrowButtonDisable, 117 userDiscountTokenBalance, 118 ghoApyRange, 119 ghoUserDataFetched, 120 userBorrowApyAfterNewBorrow, 121 onBorrowClick, 122 }: GhoBorrowAssetsListItemProps) => { 123 return ( 124 <ListItem 125 sx={{ borderTop: '1px solid', borderBottom: '1px solid', borderColor: 'divider', mb: 2 }} 126 data-cy={`dashboardBorrowListItem_${symbol.toUpperCase()}`} 127 > 128 <ListColumn maxWidth={DASHBOARD_LIST_COLUMN_WIDTHS.CELL} isRow> 129 <Link 130 href={ROUTES.reserveOverview(underlyingAsset, currentMarket)} 131 noWrap 132 sx={{ display: 'inline-flex', alignItems: 'center' }} 133 > 134 <TokenIcon symbol={iconSymbol} fontSize="large" /> 135 <Tooltip title={`${name} (${symbol})`} arrow placement="top"> 136 <Typography variant="subheader1" sx={{ ml: 3 }} noWrap data-cy={`assetName`}> 137 {symbol} 138 </Typography> 139 </Tooltip> 140 </Link> 141 </ListColumn> 142 <ListColumn> 143 <Box display="flex" flexDirection="column"> 144 <AvailableTooltip 145 capType={CapType.borrowCap} 146 text={<Trans>Available</Trans>} 147 variant="subheader2" 148 color="text.secondary" 149 ml={-1} 150 /> 151 <ListValueColumn 152 listColumnProps={{ 153 p: 0, 154 }} 155 symbol={symbol} 156 value={availableBorrows} 157 subValue={availableBorrows} 158 disabled={availableBorrows === 0} 159 withTooltip 160 /> 161 </Box> 162 </ListColumn> 163 <ListColumn> 164 <FixedAPYTooltip 165 text={<Trans>APY, borrow rate</Trans>} 166 variant="subheader2" 167 color="text.secondary" 168 /> 169 <GhoIncentivesCard 170 withTokenIcon={false} 171 useApyRange 172 rangeValues={ghoApyRange} 173 value={ghoUserDataFetched ? userBorrowApyAfterNewBorrow : -1} 174 data-cy={`apyType`} 175 stkAaveBalance={userDiscountTokenBalance} 176 ghoRoute={ROUTES.reserveOverview(underlyingAsset, currentMarket) + '/#discount'} 177 forceShowTooltip 178 userQualifiesForDiscount 179 /> 180 </ListColumn> 181 <ListButtonsColumn> 182 <Button disabled={borrowButtonDisable} variant="contained" onClick={onBorrowClick}> 183 <Trans>Borrow</Trans> 184 </Button> 185 <Button 186 variant="outlined" 187 component={Link} 188 href={ROUTES.reserveOverview(underlyingAsset, currentMarket)} 189 > 190 <Trans>Details</Trans> 191 </Button> 192 </ListButtonsColumn> 193 </ListItem> 194 ); 195 }; 196 197 const GhoBorrowAssetsListItemMobile = ({ 198 symbol, 199 iconSymbol, 200 name, 201 underlyingAsset, 202 currentMarket, 203 availableBorrows, 204 borrowButtonDisable, 205 userDiscountTokenBalance, 206 ghoApyRange, 207 ghoLoadingData, 208 userBorrowApyAfterNewBorrow, 209 onBorrowClick, 210 }: GhoBorrowAssetsListItemProps) => { 211 return ( 212 <ListMobileItemWrapper 213 symbol={symbol} 214 iconSymbol={iconSymbol} 215 name={name} 216 underlyingAsset={underlyingAsset} 217 currentMarket={currentMarket} 218 > 219 <ListValueRow 220 title={<Trans>Available to borrow</Trans>} 221 value={availableBorrows} 222 subValue={availableBorrows} 223 disabled={availableBorrows === 0} 224 /> 225 226 <Row 227 caption={ 228 <FixedAPYTooltip 229 text={<Trans>APY, borrow rate</Trans>} 230 key="APY_dash_mob_variable_ type" 231 variant="description" 232 /> 233 } 234 align="flex-start" 235 captionVariant="description" 236 mb={2} 237 > 238 <Stack alignItems="end"> 239 <GhoIncentivesCard 240 withTokenIcon={false} 241 useApyRange 242 rangeValues={ghoApyRange} 243 value={ghoLoadingData ? -1 : userBorrowApyAfterNewBorrow} 244 data-cy="apyType" 245 stkAaveBalance={userDiscountTokenBalance} 246 ghoRoute={ROUTES.reserveOverview(underlyingAsset, currentMarket) + '/#discount'} 247 forceShowTooltip 248 userQualifiesForDiscount 249 /> 250 </Stack> 251 </Row> 252 253 <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mt: 5 }}> 254 <Button 255 disabled={borrowButtonDisable} 256 variant="contained" 257 onClick={onBorrowClick} 258 sx={{ mr: 1.5 }} 259 fullWidth 260 > 261 <Trans>Borrow</Trans> 262 </Button> 263 <Button 264 variant="outlined" 265 component={Link} 266 href={ROUTES.reserveOverview(underlyingAsset, currentMarket)} 267 fullWidth 268 > 269 <Trans>Details</Trans> 270 </Button> 271 </Box> 272 </ListMobileItemWrapper> 273 ); 274 };