Skip to content

Commit

Permalink
[Seedless Onboarding] Adjust Account center design (#2673)
Browse files Browse the repository at this point in the history
* fix: Add MFA button to account center

* fix: Add Delete account button on dev

* fix: Extract WalletInfo logic from AccountCenter, use madProps and write tests

* fix: Adjust disconnect wallet test

* fix: Simplify mock
  • Loading branch information
usame-algan authored Oct 25, 2023
1 parent 5d05596 commit 3352841
Show file tree
Hide file tree
Showing 10 changed files with 466 additions and 155 deletions.
4 changes: 4 additions & 0 deletions public/images/common/lock-small.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
83 changes: 16 additions & 67 deletions src/components/common/ConnectWallet/AccountCenter.tsx
Original file line number Diff line number Diff line change
@@ -1,63 +1,38 @@
import type { MouseEvent } from 'react'
import { useState } from 'react'
import { Box, Button, ButtonBase, Paper, Popover } from '@mui/material'
import { Box, ButtonBase, Paper, Popover } from '@mui/material'
import css from '@/components/common/ConnectWallet/styles.module.css'
import EthHashInfo from '@/components/common/EthHashInfo'
import ExpandLessIcon from '@mui/icons-material/ExpandLess'
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
import useOnboard, { switchWallet } from '@/hooks/wallets/useOnboard'
import { useAppSelector } from '@/store'
import { selectChainById } from '@/store/chainsSlice'
import ChainSwitcher from '../ChainSwitcher'
import useAddressBook from '@/hooks/useAddressBook'
import { type ConnectedWallet } from '@/hooks/wallets/useOnboard'
import WalletInfo from '../WalletInfo'
import ChainIndicator from '@/components/common/ChainIndicator'
import { isSocialLoginWallet } from '@/services/mpc/module'
import SocialLoginInfo from '@/components/common/SocialLoginInfo'
import WalletOverview from '../WalletOverview'
import WalletInfo from '@/components/common/WalletInfo'

const AccountCenter = ({ wallet }: { wallet: ConnectedWallet }) => {
export const AccountCenter = ({ wallet }: { wallet: ConnectedWallet }) => {
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null)
const onboard = useOnboard()
const chainInfo = useAppSelector((state) => selectChainById(state, wallet.chainId))
const addressBook = useAddressBook()
const prefix = chainInfo?.shortName

const handleSwitchWallet = () => {
if (onboard) {
handleClose()
switchWallet(onboard)
}
}

const handleDisconnect = () => {
if (!wallet) return

onboard?.disconnectWallet({
label: wallet.label,
})

handleClose()
}

const handleClick = (event: MouseEvent<HTMLButtonElement>) => {
const openWalletInfo = (event: MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget)
}

const handleClose = () => {
const closeWalletInfo = () => {
setAnchorEl(null)
}

const open = Boolean(anchorEl)
const id = open ? 'simple-popover' : undefined

const isSocialLogin = isSocialLoginWallet(wallet.label)

return (
<>
<ButtonBase onClick={handleClick} aria-describedby={id} disableRipple sx={{ alignSelf: 'stretch' }}>
<ButtonBase
onClick={openWalletInfo}
aria-describedby={id}
disableRipple
sx={{ alignSelf: 'stretch' }}
data-testid="open-account-center"
>
<Box className={css.buttonContainer}>
<WalletInfo wallet={wallet} />
<WalletOverview wallet={wallet} />

<Box display="flex" alignItems="center" justifyContent="flex-end" marginLeft="auto">
{open ? <ExpandLessIcon color="border" /> : <ExpandMoreIcon color="border" />}
Expand All @@ -69,7 +44,7 @@ const AccountCenter = ({ wallet }: { wallet: ConnectedWallet }) => {
id={id}
open={open}
anchorEl={anchorEl}
onClose={handleClose}
onClose={closeWalletInfo}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center',
Expand All @@ -81,33 +56,7 @@ const AccountCenter = ({ wallet }: { wallet: ConnectedWallet }) => {
sx={{ marginTop: 1 }}
>
<Paper className={css.popoverContainer}>
<Box className={css.accountContainer}>
<ChainIndicator />
<Box className={css.addressContainer}>
{isSocialLogin ? (
<SocialLoginInfo wallet={wallet} chainInfo={chainInfo} />
) : (
<EthHashInfo
address={wallet.address}
name={addressBook[wallet.address] || wallet.ens}
hasExplorer
showCopyButton
prefix={prefix}
avatarSize={32}
/>
)}
</Box>
</Box>

<ChainSwitcher fullWidth />

<Button variant="contained" size="small" onClick={handleSwitchWallet} fullWidth>
Switch wallet
</Button>

<Button onClick={handleDisconnect} variant="danger" size="small" fullWidth disableElevation>
Disconnect
</Button>
<WalletInfo wallet={wallet} handleClose={closeWalletInfo} />
</Paper>
</Popover>
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { render } from '@/tests/test-utils'
import { AccountCenter } from '@/components/common/ConnectWallet/AccountCenter'
import { type EIP1193Provider } from '@web3-onboard/core'
import { act, waitFor } from '@testing-library/react'

const mockWallet = {
address: '0x1234567890123456789012345678901234567890',
chainId: '5',
label: '',
provider: null as unknown as EIP1193Provider,
}

describe('AccountCenter', () => {
it('should open and close the account center on click', async () => {
const { getByText, getByTestId } = render(<AccountCenter wallet={mockWallet} />)

const openButton = getByTestId('open-account-center')

act(() => {
openButton.click()
})

const disconnectButton = getByText('Disconnect')

expect(disconnectButton).toBeInTheDocument()

act(() => {
disconnectButton.click()
})

await waitFor(() => {
expect(disconnectButton).not.toBeInTheDocument()
})
})
})
17 changes: 0 additions & 17 deletions src/components/common/ConnectWallet/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -70,23 +70,6 @@
min-height: 42px;
}

.accountContainer {
width: 100%;
margin-bottom: var(--space-1);
}

.accountContainer > span {
border-radius: 8px 8px 0 0;
}

.addressContainer {
border-radius: 0 0 8px 8px;
padding: 12px;
border: 1px solid var(--color-border-light);
border-top: 0;
font-size: 14px;
}

@media (max-width: 599.95px) {
.socialLoginInfo > div > div {
display: none;
Expand Down
184 changes: 184 additions & 0 deletions src/components/common/WalletInfo/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import { render } from '@/tests/test-utils'
import { WalletInfo } from '@/components/common/WalletInfo/index'
import { type EIP1193Provider, type OnboardAPI } from '@web3-onboard/core'
import { type NextRouter } from 'next/router'
import * as mpcModule from '@/services/mpc/module'
import * as constants from '@/config/constants'
import * as mfaHelper from '@/components/settings/SecurityLogin/SocialSignerMFA/helper'
import { type Web3AuthMPCCoreKit } from '@web3auth/mpc-core-kit'
import { act } from '@testing-library/react'

const mockWallet = {
address: '0x1234567890123456789012345678901234567890',
chainId: '5',
label: '',
provider: null as unknown as EIP1193Provider,
}

const mockRouter = {
query: {},
pathname: '',
} as NextRouter

const mockOnboard = {
connectWallet: jest.fn(),
disconnectWallet: jest.fn(),
setChain: jest.fn(),
} as unknown as OnboardAPI

describe('WalletInfo', () => {
beforeEach(() => {
jest.resetAllMocks()
})

it('should display the wallet address', () => {
const { getByText } = render(
<WalletInfo
wallet={mockWallet}
resetAccount={jest.fn()}
mpcCoreKit={undefined}
router={mockRouter}
onboard={mockOnboard}
addressBook={{}}
handleClose={jest.fn()}
/>,
)

expect(getByText('0x1234...7890')).toBeInTheDocument()
})

it('should display a switch wallet button', () => {
const { getByText } = render(
<WalletInfo
wallet={mockWallet}
resetAccount={jest.fn()}
mpcCoreKit={undefined}
router={mockRouter}
onboard={mockOnboard}
addressBook={{}}
handleClose={jest.fn()}
/>,
)

expect(getByText('Switch wallet')).toBeInTheDocument()
})

it('should disconnect the wallet when the button is clicked', () => {
const { getByText } = render(
<WalletInfo
wallet={mockWallet}
resetAccount={jest.fn()}
mpcCoreKit={undefined}
router={mockRouter}
onboard={mockOnboard}
addressBook={{}}
handleClose={jest.fn()}
/>,
)

const disconnectButton = getByText('Disconnect')

expect(disconnectButton).toBeInTheDocument()

act(() => {
disconnectButton.click()
})

expect(mockOnboard.disconnectWallet).toHaveBeenCalled()
})

it('should display a Delete Account button on dev for social login', () => {
jest.spyOn(mpcModule, 'isSocialLoginWallet').mockReturnValue(true)
jest.spyOn(constants, 'IS_PRODUCTION', 'get').mockImplementation(() => false)

const { getByText } = render(
<WalletInfo
wallet={mockWallet}
resetAccount={jest.fn()}
mpcCoreKit={undefined}
router={mockRouter}
onboard={mockOnboard}
addressBook={{}}
handleClose={jest.fn()}
/>,
)

expect(getByText('Delete Account')).toBeInTheDocument()
})

it('should not display a Delete Account on prod', () => {
jest.spyOn(mpcModule, 'isSocialLoginWallet').mockReturnValue(true)
jest.spyOn(constants, 'IS_PRODUCTION', 'get').mockImplementation(() => true)

const { queryByText } = render(
<WalletInfo
wallet={mockWallet}
resetAccount={jest.fn()}
mpcCoreKit={undefined}
router={mockRouter}
onboard={mockOnboard}
addressBook={{}}
handleClose={jest.fn()}
/>,
)

expect(queryByText('Delete Account')).not.toBeInTheDocument()
})

it('should not display a Delete Account if not social login', () => {
jest.spyOn(mpcModule, 'isSocialLoginWallet').mockReturnValue(false)
jest.spyOn(constants, 'IS_PRODUCTION', 'get').mockImplementation(() => false)

const { queryByText } = render(
<WalletInfo
wallet={mockWallet}
resetAccount={jest.fn()}
mpcCoreKit={undefined}
router={mockRouter}
onboard={mockOnboard}
addressBook={{}}
handleClose={jest.fn()}
/>,
)

expect(queryByText('Delete Account')).not.toBeInTheDocument()
})

it('should display an enable mfa button if mfa is not enabled', () => {
jest.spyOn(mpcModule, 'isSocialLoginWallet').mockReturnValue(true)
jest.spyOn(mfaHelper, 'isMFAEnabled').mockReturnValue(false)

const { getByText } = render(
<WalletInfo
wallet={mockWallet}
resetAccount={jest.fn()}
mpcCoreKit={{} as Web3AuthMPCCoreKit}
router={mockRouter}
onboard={mockOnboard}
addressBook={{}}
handleClose={jest.fn()}
/>,
)

expect(getByText('Add multifactor authentication')).toBeInTheDocument()
})

it('should not display an enable mfa button if mfa is already enabled', () => {
jest.spyOn(mpcModule, 'isSocialLoginWallet').mockReturnValue(true)
jest.spyOn(mfaHelper, 'isMFAEnabled').mockReturnValue(true)

const { queryByText } = render(
<WalletInfo
wallet={mockWallet}
resetAccount={jest.fn()}
mpcCoreKit={{} as Web3AuthMPCCoreKit}
router={mockRouter}
onboard={mockOnboard}
addressBook={{}}
handleClose={jest.fn()}
/>,
)

expect(queryByText('Add multifactor authentication')).not.toBeInTheDocument()
})
})
Loading

0 comments on commit 3352841

Please sign in to comment.