MarketAssetsListContainer.tsx
1 import { API_ETH_MOCK_ADDRESS } from '@aave/contract-helpers'; 2 import { Trans } from '@lingui/macro'; 3 import { Box, Switch, Typography, useMediaQuery, useTheme } from '@mui/material'; 4 import { useState } from 'react'; 5 import { ListWrapper } from 'src/components/lists/ListWrapper'; 6 import { NoSearchResults } from 'src/components/NoSearchResults'; 7 import { Link } from 'src/components/primitives/Link'; 8 import { Warning } from 'src/components/primitives/Warning'; 9 import { TitleWithSearchBar } from 'src/components/TitleWithSearchBar'; 10 import { useAppDataContext } from 'src/hooks/app-data-provider/useAppDataProvider'; 11 import MarketAssetsList from 'src/modules/markets/MarketAssetsList'; 12 import { useRootStore } from 'src/store/root'; 13 import { fetchIconSymbolAndName } from 'src/ui-config/reservePatches'; 14 import { getGhoReserve, GHO_MINTING_MARKETS, GHO_SYMBOL } from 'src/utils/ghoUtilities'; 15 import { useShallow } from 'zustand/shallow'; 16 17 import { GENERAL } from '../../utils/mixPanelEvents'; 18 import { GhoBanner } from './Gho/GhoBanner'; 19 20 function shouldDisplayGhoBanner(marketTitle: string, searchTerm: string): boolean { 21 // GHO banner is only displayed on markets where new GHO is mintable (i.e. Ethereum) 22 // If GHO is listed as a reserve, then it will be displayed in the normal market asset list 23 if (!GHO_MINTING_MARKETS.includes(marketTitle)) { 24 return false; 25 } 26 27 if (!searchTerm) { 28 return true; 29 } 30 31 const normalizedSearchTerm = searchTerm.toLowerCase().trim(); 32 return ( 33 normalizedSearchTerm.length <= 3 && GHO_SYMBOL.toLowerCase().includes(normalizedSearchTerm) 34 ); 35 } 36 37 export const MarketAssetsListContainer = () => { 38 const { reserves, loading } = useAppDataContext(); 39 const [trackEvent, currentMarket, currentMarketData, currentNetworkConfig] = useRootStore( 40 useShallow((store) => [ 41 store.trackEvent, 42 store.currentMarket, 43 store.currentMarketData, 44 store.currentNetworkConfig, 45 ]) 46 ); 47 const [searchTerm, setSearchTerm] = useState(''); 48 const { breakpoints } = useTheme(); 49 const sm = useMediaQuery(breakpoints.down('sm')); 50 51 const ghoReserve = getGhoReserve(reserves); 52 const displayGhoBanner = shouldDisplayGhoBanner(currentMarket, searchTerm); 53 54 const filteredData = reserves 55 // Filter out any non-active reserves 56 .filter((res) => res.isActive) 57 // Filter out GHO if the banner is being displayed 58 .filter((res) => (displayGhoBanner ? res !== ghoReserve : true)) 59 // filter out any that don't meet search term criteria 60 .filter((res) => { 61 if (!searchTerm) return true; 62 const term = searchTerm.toLowerCase().trim(); 63 return ( 64 res.symbol.toLowerCase().includes(term) || 65 res.name.toLowerCase().includes(term) || 66 res.underlyingAsset.toLowerCase().includes(term) 67 ); 68 }) 69 // Transform the object for list to consume it 70 .map((reserve) => ({ 71 ...reserve, 72 ...(reserve.isWrappedBaseAsset 73 ? fetchIconSymbolAndName({ 74 symbol: currentNetworkConfig.baseAssetSymbol, 75 underlyingAsset: API_ETH_MOCK_ADDRESS.toLowerCase(), 76 }) 77 : {}), 78 })); 79 // const marketFrozen = !reserves.some((reserve) => !reserve.isFrozen); 80 // const showFrozenMarketWarning = 81 // marketFrozen && ['Fantom', 'Ethereum AMM'].includes(currentMarketData.marketTitle); 82 const unfrozenReserves = filteredData.filter((r) => !r.isFrozen && !r.isPaused); 83 const [showFrozenMarketsToggle, setShowFrozenMarketsToggle] = useState(false); 84 85 const handleChange = () => { 86 setShowFrozenMarketsToggle((prevState) => !prevState); 87 }; 88 89 const frozenOrPausedReserves = filteredData.filter((r) => r.isFrozen || r.isPaused); 90 91 return ( 92 <ListWrapper 93 titleComponent={ 94 <TitleWithSearchBar 95 onSearchTermChange={setSearchTerm} 96 title={ 97 <> 98 {currentMarketData.marketTitle} <Trans>assets</Trans> 99 </> 100 } 101 searchPlaceholder={sm ? 'Search asset' : 'Search asset name, symbol, or address'} 102 /> 103 } 104 > 105 {displayGhoBanner && ( 106 <Box mb={4}> 107 <GhoBanner reserve={ghoReserve} /> 108 </Box> 109 )} 110 111 {/* Unfrozen assets list */} 112 <MarketAssetsList reserves={unfrozenReserves} loading={loading} /> 113 114 {/* Frozen or paused assets list */} 115 {frozenOrPausedReserves.length > 0 && ( 116 <Box sx={{ mt: 10, px: { xs: 4, xsm: 6 } }}> 117 <Typography variant="h4" mb={4}> 118 <Trans>Show Frozen or paused assets</Trans> 119 120 <Switch 121 checked={showFrozenMarketsToggle} 122 onChange={handleChange} 123 inputProps={{ 'aria-label': 'controlled' }} 124 /> 125 </Typography> 126 {showFrozenMarketsToggle && ( 127 <Warning severity="info"> 128 <Trans> 129 These assets are temporarily frozen or paused by Aave community decisions, meaning 130 that further supply / borrow, or rate swap of these assets are unavailable. 131 Withdrawals and debt repayments are allowed. Follow the{' '} 132 <Link 133 onClick={() => { 134 trackEvent(GENERAL.EXTERNAL_LINK, { 135 link: 'Frozen Market Markets Page', 136 frozenMarket: currentNetworkConfig.name, 137 }); 138 }} 139 href="https://governance.aave.com" 140 underline="always" 141 > 142 Aave governance forum 143 </Link>{' '} 144 for further updates. 145 </Trans> 146 </Warning> 147 )} 148 </Box> 149 )} 150 {showFrozenMarketsToggle && ( 151 <MarketAssetsList reserves={frozenOrPausedReserves} loading={loading} /> 152 )} 153 154 {/* Show no search results message if nothing hits in either list */} 155 {!loading && filteredData.length === 0 && !displayGhoBanner && ( 156 <NoSearchResults 157 searchTerm={searchTerm} 158 subtitle={ 159 <Trans> 160 We couldn't find any assets related to your search. Try again with a different 161 asset name, symbol, or address. 162 </Trans> 163 } 164 /> 165 )} 166 </ListWrapper> 167 ); 168 };