Skip to content

Commit

Permalink
[Multichain] feat: redesign of multiaccounts and context menu for mul…
Browse files Browse the repository at this point in the history
…tiaccounts (#4125)

- Redesigns how sub-items are displayed under multi-accounts.
- Adds context menu for multi accounts offering renaming and adding a new network
- Some UX fixes around the context menu
  • Loading branch information
schmanu authored Sep 17, 2024
1 parent 4f5e9bf commit 6555c95
Show file tree
Hide file tree
Showing 9 changed files with 253 additions and 91 deletions.
12 changes: 8 additions & 4 deletions src/components/address-book/EntryDialog/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import ModalDialog from '@/components/common/ModalDialog'
import NameInput from '@/components/common/NameInput'
import useChainId from '@/hooks/useChainId'
import { useAppDispatch } from '@/store'
import { upsertAddressBookEntry } from '@/store/addressBookSlice'
import { upsertAddressBookEntry, upsertMultichainAddressBookEntry } from '@/store/addressBookSlice'
import madProps from '@/utils/mad-props'

export type AddressEntry = {
Expand All @@ -22,13 +22,13 @@ function EntryDialog({
address: '',
},
disableAddressInput = false,
chainId,
chainIds,
currentChainId,
}: {
handleClose: () => void
defaultValues?: AddressEntry
disableAddressInput?: boolean
chainId?: string
chainIds?: string[]
currentChainId: string
}): ReactElement {
const dispatch = useAppDispatch()
Expand All @@ -41,7 +41,11 @@ function EntryDialog({
const { handleSubmit, formState } = methods

const submitCallback = handleSubmit((data: AddressEntry) => {
dispatch(upsertAddressBookEntry({ ...data, chainId: chainId || currentChainId }))
if (chainIds) {
dispatch(upsertMultichainAddressBookEntry({ ...data, chainIds }))
} else {
dispatch(upsertAddressBookEntry({ ...data, chainId: currentChainId }))
}
handleClose()
})

Expand Down
106 changes: 106 additions & 0 deletions src/components/sidebar/SafeListContextMenu/MultiAccountContextMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import type { MouseEvent } from 'react'
import { useState, type ReactElement } from 'react'
import ListItemIcon from '@mui/material/ListItemIcon'
import IconButton from '@mui/material/IconButton'
import MoreVertIcon from '@mui/icons-material/MoreVert'
import MenuItem from '@mui/material/MenuItem'
import ListItemText from '@mui/material/ListItemText'

import EntryDialog from '@/components/address-book/EntryDialog'
import EditIcon from '@/public/images/common/edit.svg'
import PlusIcon from '@/public/images/common/plus.svg'
import ContextMenu from '@/components/common/ContextMenu'
import { trackEvent, OVERVIEW_EVENTS, OVERVIEW_LABELS } from '@/services/analytics'
import { SvgIcon } from '@mui/material'
import { AppRoutes } from '@/config/routes'
import router from 'next/router'
import { CreateSafeOnNewChain } from '@/features/multichain/components/CreateSafeOnNewChain'

enum ModalType {
RENAME = 'rename',
ADD_CHAIN = 'add_chain',
}

const defaultOpen = { [ModalType.RENAME]: false, [ModalType.ADD_CHAIN]: false }

const MultiAccountContextMenu = ({
name,
address,
chainIds,
}: {
name: string
address: string
chainIds: string[]
}): ReactElement => {
const [anchorEl, setAnchorEl] = useState<HTMLElement | undefined>()
const [open, setOpen] = useState<typeof defaultOpen>(defaultOpen)

const handleOpenContextMenu = (e: MouseEvent<HTMLButtonElement, globalThis.MouseEvent>) => {
e.stopPropagation()
setAnchorEl(e.currentTarget)
}

const handleCloseContextMenu = (event: MouseEvent) => {
event.stopPropagation()
setAnchorEl(undefined)
}

const handleOpenModal =
(type: ModalType, event: typeof OVERVIEW_EVENTS.SIDEBAR_RENAME | typeof OVERVIEW_EVENTS.ADD_NEW_NETWORK) =>
(e: MouseEvent) => {
const trackingLabel =
router.pathname === AppRoutes.welcome.accounts ? OVERVIEW_LABELS.login_page : OVERVIEW_LABELS.sidebar
handleCloseContextMenu(e)
setOpen((prev) => ({ ...prev, [type]: true }))

trackEvent({ ...event, label: trackingLabel })
}

const handleCloseModal = () => {
setOpen(defaultOpen)
}

return (
<>
<IconButton data-testid="safe-options-btn" edge="end" size="small" onClick={handleOpenContextMenu}>
<MoreVertIcon sx={({ palette }) => ({ color: palette.border.main })} />
</IconButton>
<ContextMenu anchorEl={anchorEl} open={!!anchorEl} onClose={handleCloseContextMenu}>
<MenuItem onClick={handleOpenModal(ModalType.RENAME, OVERVIEW_EVENTS.SIDEBAR_RENAME)}>
<ListItemIcon>
<SvgIcon component={EditIcon} inheritViewBox fontSize="small" color="success" />
</ListItemIcon>
<ListItemText data-testid="rename-btn">Rename</ListItemText>
</MenuItem>

<MenuItem onClick={handleOpenModal(ModalType.ADD_CHAIN, OVERVIEW_EVENTS.ADD_NEW_NETWORK)}>
<ListItemIcon>
<SvgIcon component={PlusIcon} inheritViewBox fontSize="small" color="primary" />
</ListItemIcon>
<ListItemText data-testid="add-chain-btn">Add another network</ListItemText>
</MenuItem>
</ContextMenu>

{open[ModalType.RENAME] && (
<EntryDialog
handleClose={handleCloseModal}
defaultValues={{ name, address }}
chainIds={chainIds}
disableAddressInput
/>
)}

{open[ModalType.ADD_CHAIN] && (
<CreateSafeOnNewChain
onClose={handleCloseModal}
currentName={name}
deployedChainIds={chainIds}
open
safeAddress={address}
/>
)}
</>
)
}

export default MultiAccountContextMenu
2 changes: 1 addition & 1 deletion src/components/sidebar/SafeListContextMenu/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ const SafeListContextMenu = ({
<EntryDialog
handleClose={handleCloseModal}
defaultValues={{ name, address }}
chainId={chainId}
chainIds={[chainId]}
disableAddressInput
/>
)}
Expand Down
47 changes: 35 additions & 12 deletions src/components/welcome/MyAccounts/MultiAccountItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
AccordionDetails,
AccordionSummary,
Divider,
Tooltip,
} from '@mui/material'
import SafeIcon from '@/components/common/SafeIcon'
import { OVERVIEW_EVENTS, OVERVIEW_LABELS, trackEvent } from '@/services/analytics'
Expand All @@ -25,18 +26,41 @@ import FiatValue from '@/components/common/FiatValue'
import { type MultiChainSafeItem } from './useAllSafesGrouped'
import MultiChainIcon from '@/public/images/sidebar/multichain-account.svg'
import { shortenAddress } from '@/utils/formatters'
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
import { type SafeItem } from './useAllSafes'
import SubAccountItem from './SubAccountItem'
import { getSharedSetup } from './utils/multiChainSafe'
import { AddNetworkButton } from './AddNetworkButton'
import ChainIndicator from '@/components/common/ChainIndicator'
import MultiAccountContextMenu from '@/components/sidebar/SafeListContextMenu/MultiAccountContextMenu'

type MultiAccountItemProps = {
multiSafeAccountItem: MultiChainSafeItem
safeOverviews?: SafeOverview[]
onLinkClick?: () => void
}

const MultichainIndicator = ({ safes }: { safes: SafeItem[] }) => {
return (
<Tooltip
title={
<Box>
<Typography fontSize="14px">Multichain account on:</Typography>
{safes.map((safeItem) => (
<Box p="4px 0px" key={safeItem.chainId}>
<ChainIndicator chainId={safeItem.chainId} />
</Box>
))}
</Box>
}
arrow
>
<Box height="26px">
<MultiChainIcon />
</Box>
</Tooltip>
)
}

const MultiAccountItem = ({ onLinkClick, multiSafeAccountItem, safeOverviews }: MultiAccountItemProps) => {
const { address, safes } = multiSafeAccountItem
const undeployedSafes = useAppSelector(selectUndeployedSafes)
Expand All @@ -46,6 +70,8 @@ const MultiAccountItem = ({ onLinkClick, multiSafeAccountItem, safeOverviews }:
const isWelcomePage = router.pathname === AppRoutes.welcome.accounts
const [expanded, setExpanded] = useState(isCurrentSafe)

const deployedChains = useMemo(() => safes.map((safe) => safe.chainId), [safes])

const isWatchlist = useMemo(
() => multiSafeAccountItem.safes.every((safe) => safe.isWatchlist),
[multiSafeAccountItem.safes],
Expand Down Expand Up @@ -83,24 +109,22 @@ const MultiAccountItem = ({ onLinkClick, multiSafeAccountItem, safeOverviews }:
<ListItemButton
data-testid="safe-list-item"
selected={isCurrentSafe}
className={classnames(css.listItem, { [css.currentListItem]: isCurrentSafe })}
className={classnames(css.multiListItem, css.listItem, { [css.currentListItem]: isCurrentSafe })}
sx={{ p: 0 }}
>
<Accordion expanded={expanded} sx={{ border: 'none' }}>
<AccordionSummary
onClick={toggleExpand}
expandIcon={<ExpandMoreIcon />}
sx={{
pl: 0,
'& .MuiAccordionSummary-content': { m: 0 },
'& .MuiAccordionSummary-content': { m: 0, alignItems: 'center' },
'&.Mui-expanded': { backgroundColor: 'transparent !important' },
}}
>
<Box className={css.safeLink} width="100%">
<Box pr={2.5}>
<SafeIcon address={address} owners={sharedSetup?.owners.length} threshold={sharedSetup?.threshold} />
</Box>

<Typography variant="body2" component="div" className={css.safeAddress}>
{name && (
<Typography variant="subtitle2" component="p" fontWeight="bold" className={css.safeName}>
Expand All @@ -111,20 +135,19 @@ const MultiAccountItem = ({ onLinkClick, multiSafeAccountItem, safeOverviews }:
{shortenAddress(address)}
</Typography>
</Typography>

<Typography variant="body2" fontWeight="bold" textAlign="right" pr={4}>
{totalFiatValue !== undefined ? (
<FiatValue value={totalFiatValue} />
) : (
<Skeleton variant="text" sx={{ ml: 'auto' }} />
)}
</Typography>

<MultiChainIcon />
<MultichainIndicator safes={safes} />
</Box>
<MultiAccountContextMenu name={name ?? ''} address={address} chainIds={deployedChains} />
</AccordionSummary>
<AccordionDetails sx={{ padding: 0 }}>
<Box sx={{ padding: '0px 0px 0px 36px' }}>
<AccordionDetails sx={{ padding: '0px 12px' }}>
<Box>
{safes.map((safeItem) => (
<SubAccountItem
onLinkClick={onLinkClick}
Expand All @@ -136,8 +159,8 @@ const MultiAccountItem = ({ onLinkClick, multiSafeAccountItem, safeOverviews }:
</Box>
{!isWatchlist && (
<>
<Divider />
<Box display="flex" alignItems="center" justifyContent="center">
<Divider sx={{ ml: '-12px', mr: '-12px' }} />
<Box display="flex" alignItems="center" justifyContent="center" sx={{ ml: '-12px', mr: '-12px' }}>
<AddNetworkButton
currentName={name}
safeAddress={address}
Expand Down
1 change: 0 additions & 1 deletion src/components/welcome/MyAccounts/SubAccountItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,6 @@ const SubAccountItem = ({ onLinkClick, safeItem, safeOverview }: SubAccountItem)
safeAddress={address}
chainShortName={chain?.shortName || ''}
/>
<div className={css.borderLeft} />
</ListItemButton>
)
}
Expand Down
43 changes: 9 additions & 34 deletions src/components/welcome/MyAccounts/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@
background-color: var(--color-background-light) !important;
}

.currentListItem.multiListItem {
border: 1px solid var(--color-border-light);
background-color: none;
}

.listItem :global .MuiAccordion-root,
.listItem :global .MuiAccordion-root:hover > .MuiAccordionSummary-root {
background-color: transparent;
Expand All @@ -54,48 +59,18 @@
}

.listItem.subItem {
border: none;
margin-bottom: 0px;
border-radius: 0px;
}

.subItem:before {
content: '';
display: block;
width: 8px;
height: 1px;
background: var(--color-border-light);
left: 0;
top: 50%;
position: absolute;
}

.subItem.currentListItem {
border: none;
}

.subItem.currentListItem:before {
background: var(--color-secondary-light);
height: 1px;
margin-bottom: 8px;
}

.subItem .borderLeft {
top: 0;
bottom: 0;
position: absolute;
border-left: 1px solid var(--color-border-light);
border-radius: 6px;
border: 1px solid var(--color-border-light);
}
.subItem.currentListItem .borderLeft {
border-left: 1px solid var(--color-secondary-light);
}

.subItem:last-child {
border-left: none;
}

.subItem:last-child .borderLeft {
top: 0%;
bottom: 50%;
border-left: 4px solid var(--color-secondary-light);
}

.listItem > :first-child {
Expand Down
Loading

0 comments on commit 6555c95

Please sign in to comment.