import React, { useEffect, useState, useMemo } from 'react' import Fuse from 'fuse.js' import { useAccount, useAccountEffect } from 'wagmi' import { Address } from 'viem' import { useAppDispatch } from '@/store/hooks' import { setFromChainId } from '@/slices/bridge/reducer' import { PortfolioTabManager } from './components/PortfolioTabManager' import { NetworkTokenBalances, TokenAndBalance, } from '@/utils/actions/fetchPortfolioBalances' import { PortfolioContent } from './components/PortfolioContent' import { useFetchPortfolioBalances, usePortfolioState, } from '@/slices/portfolio/hooks' import { PortfolioTabs, resetPortfolioState, FetchState, } from '@/slices/portfolio/actions' import { resetTransactionsState } from '@/slices/transactions/actions' import { PortfolioState } from '@/slices/portfolio/reducer' import { useBridgeState } from '@/slices/bridge/hooks' import { BridgeState, resetBridgeInputs } from '@/slices/bridge/reducer' import { ViewSearchAddressBanner } from './components/ViewSearchAddressBanner' import { Activity } from '../Activity/Activity' import { useSearchInputState } from './hooks/useSearchInputStatus' export const Portfolio = () => { const dispatch = useAppDispatch() const { fromChainId }: BridgeState = useBridgeState() const { activeTab, searchInput, searchStatus, searchedBalances, }: PortfolioState = usePortfolioState() const { chain, address } = useAccount() useAccountEffect({ onDisconnect() { dispatch(resetPortfolioState()) dispatch(resetTransactionsState()) dispatch(resetBridgeInputs()) }, }) const { balances: portfolioData, status: fetchState } = useFetchPortfolioBalances() const filteredPortfolioData: NetworkTokenBalances = filterPortfolioBalancesWithBalances(portfolioData) const { isSearchInputActive, isSearchInputAddress, isMasqueradeActive, masqueradeAddress, } = useSearchInputState() const filteredSearchedPortfolioDataForBalances = useMemo(() => { if (isMasqueradeActive) { return { balances: filterPortfolioBalancesWithBalances( searchedBalances[masqueradeAddress] ), address: masqueradeAddress, } } return { balances: {}, address: '', } }, [searchedBalances, isMasqueradeActive, searchInput]) const flattenedPortfolioData = flattenData(filteredPortfolioData) const filteredBySearchInput = filterBySearchInput( flattenedPortfolioData, searchInput ) useEffect(() => { if (!chain) { dispatch(setFromChainId(null)) } if (address && chain?.id) { dispatch(setFromChainId(chain.id)) } }, [chain]) const [mounted, setMounted] = useState(false) useEffect(() => setMounted(true), []) return (
{mounted && ( <> {searchStatus === FetchState.LOADING && (
Loading new address...
)} {isMasqueradeActive ? ( <> ) : (
)} )}
) } function flattenData(portfolioData: NetworkTokenBalances): TokenAndBalance[] { const flattened: TokenAndBalance[] = [] Object.entries(portfolioData).forEach(([chainId, tokens]) => { tokens.forEach((token: TokenAndBalance) => { flattened.push({ ...token }) }) }) return flattened } function filterBySearchInput( portfolioData: TokenAndBalance[], searchInput: string ) { const searchFiltered: NetworkTokenBalances = {} const fuseOptions = { includeScore: true, threshold: 0.33, distance: 20, keys: ['queriedChain.name', 'token.name', 'token.symbol'], } const fuse = new Fuse(portfolioData, fuseOptions) if (searchInput.length > 0) { fuse .search(searchInput) .map((i: Fuse.FuseResult) => i.item) .forEach((item) => { const chainId = item.queriedChain.id if (!searchFiltered[chainId]) { searchFiltered[chainId] = [] } searchFiltered[chainId].push(item) }) } return searchFiltered } export function filterPortfolioBalancesWithBalances( balances: NetworkTokenBalances ): NetworkTokenBalances { return Object.entries(balances).reduce( (filteredBalances: NetworkTokenBalances, [key, tokenWithBalances]) => { const filtered = tokenWithBalances.filter( (token: TokenAndBalance) => token.balance > 0n ) if (filtered.length > 0) { filteredBalances[key] = filtered } return filteredBalances }, {} ) }