Skip to content

Commit

Permalink
Refactor: Reuse Single account item component [SW-647] (#4658)
Browse files Browse the repository at this point in the history
* refactor: Reuse SingleAccountItem and delete SubAccountItem

* fix: Inconsistent signer layout

* fix: Only mount safe list when expanded

* refactor: Extract logic into hooks
  • Loading branch information
usame-algan authored Dec 13, 2024
1 parent 841f571 commit 0c1a737
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 260 deletions.
13 changes: 10 additions & 3 deletions src/components/common/SafeIcon/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ interface SafeIconProps extends IdenticonProps {
owners?: ThresholdProps['owners']
size?: number
chainId?: string
isSubItem?: boolean
isMultiChainItem?: boolean
}

const ChainIcon = ({ chainId }: { chainId: string }) => {
Expand All @@ -41,11 +41,18 @@ const ChainIcon = ({ chainId }: { chainId: string }) => {
)
}

const SafeIcon = ({ address, threshold, owners, size, chainId, isSubItem = false }: SafeIconProps): ReactElement => {
const SafeIcon = ({
address,
threshold,
owners,
size,
chainId,
isMultiChainItem = false,
}: SafeIconProps): ReactElement => {
return (
<div data-testid="safe-icon" className={css.container}>
{threshold && owners ? <Threshold threshold={threshold} owners={owners} /> : null}
{isSubItem && chainId ? <ChainIcon chainId={chainId} /> : <Identicon address={address} size={size} />}
{isMultiChainItem && chainId ? <ChainIcon chainId={chainId} /> : <Identicon address={address} size={size} />}
</div>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ const ChainIndicatorList = ({ chainIds }: { chainIds: string[] }) => {
{chainIds.map((chainId, index) => {
const chain = configs.find((chain) => chain.chainId === chainId)
return (
<Box key={chainId} display="inline-flex" flexWrap="wrap" position="relative" sx={{ top: 5 }}>
<Box key={chainId} display="inline-flex" flexWrap="wrap" position="relative" top={5}>
<ChainIndicator key={chainId} chainId={chainId} showUnknown={false} onlyLogo={true} />
<Typography position="relative" mx={0.5} sx={{ top: 2 }}>
<Typography position="relative" mx={0.5} top={2}>
{chain && chain.chainName}
{index === chainIds.length - 1 ? '.' : ','}
</Typography>
Expand Down
149 changes: 87 additions & 62 deletions src/features/myAccounts/components/AccountItems/MultiAccountItem.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { selectUndeployedSafes } from '@/features/counterfactual/store/undeployedSafesSlice'
import NetworkLogosList from '@/features/multichain/components/NetworkLogosList'
import SingleAccountItem from '@/features/myAccounts/components/AccountItems/SingleAccountItem'
import type { SafeOverview } from '@safe-global/safe-gateway-typescript-sdk'
import { useCallback, useMemo, useState } from 'react'
import {
Expand Down Expand Up @@ -28,7 +29,6 @@ import FiatValue from '@/components/common/FiatValue'
import { type MultiChainSafeItem } from '@/features/myAccounts/hooks/useAllSafesGrouped'
import { shortenAddress } from '@/utils/formatters'
import { type SafeItem } from '@/features/myAccounts/hooks/useAllSafes'
import SubAccountItem from './SubAccountItem'
import { getSafeSetups, getSharedSetup, hasMultiChainAddNetworkFeature } from '@/features/multichain/utils/utils'
import { AddNetworkButton } from '../AddNetworkButton'
import { isPredictedSafeProps } from '@/features/counterfactual/utils'
Expand Down Expand Up @@ -56,20 +56,9 @@ const MultichainIndicator = ({ safes }: { safes: SafeItem[] }) => {
<Tooltip
title={
<Box data-testid="multichain-tooltip">
<Typography
sx={{
fontSize: '14px',
}}
>
Multichain account on:
</Typography>
<Typography fontSize="14px">Multichain account on:</Typography>
{safes.map((safeItem) => (
<Box
key={safeItem.chainId}
sx={{
p: '4px 0px',
}}
>
<Box key={safeItem.chainId} sx={{ p: '4px 0px' }}>
<ChainIndicator chainId={safeItem.chainId} />
</Box>
))}
Expand All @@ -84,68 +73,73 @@ const MultichainIndicator = ({ safes }: { safes: SafeItem[] }) => {
)
}

const MultiAccountItem = ({ onLinkClick, multiSafeAccountItem }: MultiAccountItemProps) => {
function useMultiAccountItemData(multiSafeAccountItem: MultiChainSafeItem) {
const { address, safes, isPinned } = multiSafeAccountItem
const undeployedSafes = useAppSelector(selectUndeployedSafes)
const safeAddress = useSafeAddress()

const router = useRouter()
const isCurrentSafe = sameAddress(safeAddress, address)
const isWelcomePage = router.pathname === AppRoutes.welcome.accounts
const [expanded, setExpanded] = useState(isCurrentSafe)
const chains = useAppSelector(selectChains)
const { orderBy } = useAppSelector(selectOrderByPreference)

const sortComparator = getComparator(orderBy)
const sortedSafes = useMemo(() => safes.sort(sortComparator), [safes, sortComparator])

const allAddedSafes = useAppSelector((state) => selectAllAddedSafes(state))
const dispatch = useAppDispatch()
const safeAddress = useSafeAddress()
const isCurrentSafe = sameAddress(safeAddress, address)

const deployedChainIds = useMemo(() => safes.map((safe) => safe.chainId), [safes])
const { orderBy } = useAppSelector(selectOrderByPreference)
const sortComparator = useMemo(() => getComparator(orderBy), [orderBy])
const sortedSafes = useMemo(() => [...safes].sort(sortComparator), [safes, sortComparator])

const isReadOnly = useMemo(
() => multiSafeAccountItem.safes.every((safe) => safe.isReadOnly),
[multiSafeAccountItem.safes],
const undeployedSafes = useAppSelector(selectUndeployedSafes)
const deployedSafes = useMemo(
() => sortedSafes.filter((safe) => !undeployedSafes[safe.chainId]?.[safe.address]),
[sortedSafes, undeployedSafes],
)

const trackingLabel = isWelcomePage ? OVERVIEW_LABELS.login_page : OVERVIEW_LABELS.sidebar

const toggleExpand = () => {
!expanded && trackEvent({ ...OVERVIEW_EVENTS.EXPAND_MULTI_SAFE, label: trackingLabel })
setExpanded((prev) => !prev)
}

const currency = useAppSelector(selectCurrency)
const { address: walletAddress } = useWallet() ?? {}
const deployedSafes = useMemo(
() => safes.filter((safe) => undeployedSafes[safe.chainId]?.[safe.address] === undefined),
[safes, undeployedSafes],
)
const { address: walletAddress = '' } = useWallet() || {}

const { data: safeOverviews } = useGetMultipleSafeOverviewsQuery({ currency, walletAddress, safes: deployedSafes })

const safeSetups = useMemo(
() => getSafeSetups(safes, safeOverviews ?? [], undeployedSafes),
[safeOverviews, safes, undeployedSafes],
() => getSafeSetups(sortedSafes, safeOverviews ?? [], undeployedSafes),
[safeOverviews, sortedSafes, undeployedSafes],
)
const sharedSetup = getSharedSetup(safeSetups)
const sharedSetup = useMemo(() => getSharedSetup(safeSetups), [safeSetups])

const totalFiatValue = useMemo(
() => safeOverviews?.reduce((prev, current) => prev + Number(current.fiatTotal), 0),
() => safeOverviews?.reduce((sum, overview) => sum + Number(overview.fiatTotal), 0),
[safeOverviews],
)

const hasReplayableSafe = useMemo(
() =>
safes.some((safeItem) => {
const undeployedSafe = undeployedSafes[safeItem.chainId]?.[safeItem.address]
const chain = chains.data.find((chain) => chain.chainId === safeItem.chainId)
const addNetworkFeatureEnabled = hasMultiChainAddNetworkFeature(chain)
const chains = useAppSelector(selectChains)
const hasReplayableSafe = useMemo(() => {
return sortedSafes.some((safeItem) => {
const undeployedSafe = undeployedSafes[safeItem.chainId]?.[safeItem.address]
const chain = chains.data.find((chain) => chain.chainId === safeItem.chainId)
const addNetworkFeatureEnabled = hasMultiChainAddNetworkFeature(chain)
// Replayable if deployed or new counterfactual safe and the chain supports add network
return (!undeployedSafe || !isPredictedSafeProps(undeployedSafe.props)) && addNetworkFeatureEnabled
})
}, [chains.data, sortedSafes, undeployedSafes])

const isReadOnly = useMemo(() => sortedSafes.every((safe) => safe.isReadOnly), [sortedSafes])

// We can only replay deployed Safes and new counterfactual Safes.
return (!undeployedSafe || !isPredictedSafeProps(undeployedSafe.props)) && addNetworkFeatureEnabled
}),
[chains.data, safes, undeployedSafes],
)
const deployedChainIds = useMemo(() => sortedSafes.map((safe) => safe.chainId), [sortedSafes])

return {
address,
sortedSafes,
safeOverviews,
sharedSetup,
totalFiatValue,
hasReplayableSafe,
isPinned,
isCurrentSafe,
isReadOnly,
isWelcomePage,
deployedChainIds,
}
}

function usePinActions(address: string, safes: SafeItem[], safeOverviews: SafeOverview[] | undefined) {
const dispatch = useAppDispatch()
const allAddedSafes = useAppSelector(selectAllAddedSafes)

const findOverview = useCallback(
(item: SafeItem) => {
Expand Down Expand Up @@ -189,6 +183,37 @@ const MultiAccountItem = ({ onLinkClick, multiSafeAccountItem }: MultiAccountIte
trackEvent({ ...OVERVIEW_EVENTS.PIN_SAFE, label: PIN_SAFE_LABELS.unpin })
}, [safes, dispatch])

return { addToPinnedList, removeFromPinnedList }
}

const MultiAccountItem = ({ onLinkClick, multiSafeAccountItem }: MultiAccountItemProps) => {
const {
address,
sortedSafes,
safeOverviews,
sharedSetup,
totalFiatValue,
hasReplayableSafe,
isPinned,
isCurrentSafe,
isReadOnly,
isWelcomePage,
deployedChainIds,
} = useMultiAccountItemData(multiSafeAccountItem)
const { addToPinnedList, removeFromPinnedList } = usePinActions(address, sortedSafes, safeOverviews)

const [expanded, setExpanded] = useState(isCurrentSafe)
const trackingLabel = isWelcomePage ? OVERVIEW_LABELS.login_page : OVERVIEW_LABELS.sidebar

const toggleExpand = () => {
setExpanded((prev) => {
if (!prev) {
trackEvent({ ...OVERVIEW_EVENTS.EXPAND_MULTI_SAFE, label: trackingLabel })
}
return !prev
})
}

return (
<ListItemButton
data-testid="safe-list-item"
Expand Down Expand Up @@ -226,7 +251,7 @@ const MultiAccountItem = ({ onLinkClick, multiSafeAccountItem }: MultiAccountIte
{shortenAddress(address)}
</Typography>
</Typography>
<MultichainIndicator safes={safes} />
<MultichainIndicator safes={sortedSafes} />
<Typography
data-testid="group-balance"
variant="body2"
Expand Down Expand Up @@ -270,11 +295,11 @@ const MultiAccountItem = ({ onLinkClick, multiSafeAccountItem }: MultiAccountIte
<AccordionDetails sx={{ padding: '0px 12px' }}>
<Box data-testid="subacounts-container">
{sortedSafes.map((safeItem) => (
<SubAccountItem
<SingleAccountItem
onLinkClick={onLinkClick}
safeItem={safeItem}
key={`${safeItem.chainId}:${safeItem.address}`}
safeOverview={findOverview(safeItem)}
isMultiChainItem
/>
))}
</Box>
Expand All @@ -293,7 +318,7 @@ const MultiAccountItem = ({ onLinkClick, multiSafeAccountItem }: MultiAccountIte
<AddNetworkButton
currentName={multiSafeAccountItem.name ?? ''}
safeAddress={address}
deployedChains={safes.map((safe) => safe.chainId)}
deployedChains={sortedSafes.map((safe) => safe.chainId)}
/>
</Box>
</>
Expand Down
Loading

0 comments on commit 0c1a737

Please sign in to comment.