Skip to content

Commit

Permalink
[Seedless Onboarding] Account center redesign (#2607)
Browse files Browse the repository at this point in the history
* feat: Implement account center redesign

* fix: Continue as button spacing

* fix: Failing e2e tests

* refactor: useConnectWallet hook, explicit onLogin handling

* fix: Failing test

* fix: Show social login in overview header

* fix: Move import data e2e test to global preference page
  • Loading branch information
usame-algan authored Oct 11, 2023
1 parent 6db3390 commit cc7398b
Show file tree
Hide file tree
Showing 18 changed files with 202 additions and 128 deletions.
2 changes: 1 addition & 1 deletion cypress/e2e/pages/create_wallet.pages.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as constants from '../../support/constants'

const newAccountBtnStr = 'Create new Account'
const newAccountBtnStr = 'Continue with E2E Wallet'

const nameInput = 'input[name="name"]'
const selectNetworkBtn = '[data-cy="create-safe-select-network"]'
Expand Down
6 changes: 5 additions & 1 deletion cypress/e2e/pages/import_export.pages.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export function clickOnImportBtn() {
}

export function clickOnImportBtnDataImportModal() {
cy.contains(dataImportModalStr).parent().contains('button', 'Import').click()
cy.contains('button', 'Import').click()
}

export function uploadFile(filePath) {
Expand All @@ -43,6 +43,10 @@ export function clickOnImportedSafe(safe) {
cy.contains(safe).click()
}

export function clickOnOpenSafeListSidebar() {
cy.contains('My Safe Accounts').click()
}

export function clickOnClosePushNotificationsBanner() {
cy.waitForSelector(() => {
return cy.get('h6').contains(enablePushNotificationsStr).siblings('.MuiButtonBase-root').click({ force: true })
Expand Down
4 changes: 2 additions & 2 deletions cypress/e2e/pages/load_safe.pages.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as constants from '../../support/constants'

const addExistingAccountBtnStr = 'Add existing Account'
const addExistingAccountBtnStr = 'Add existing one'
const contactStr = 'Name, address & network'
const invalidAddressFormatErrorMsg = 'Invalid address format'

Expand All @@ -16,7 +16,7 @@ const ownersConfirmationsStr = 'Owners and confirmations'
const transactionStr = 'Transactions'

export function openLoadSafeForm() {
cy.contains('button', addExistingAccountBtnStr).click()
cy.contains('a', addExistingAccountBtnStr).click()
cy.contains(contactStr)
}

Expand Down
6 changes: 3 additions & 3 deletions cypress/e2e/smoke/import_export_data.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@ import * as constants from '../../support/constants'
describe('Import Export Data', () => {
before(() => {
cy.clearLocalStorage()
cy.visit(constants.welcomeUrl)
cy.visit(constants.dataSettingsUrl)
main.acceptCookies()
file.verifyImportBtnIsVisible()
})

it('Uploads test file and access safe', () => {
const filePath = '../fixtures/data_import.json'
const safe = 'safe 1 goerli'

file.clickOnImportBtn()
file.uploadFile(filePath)
file.verifyImportModalData()
file.clickOnImportBtnDataImportModal()
cy.visit(constants.welcomeUrl)
file.clickOnOpenSafeListSidebar()
file.clickOnImportedSafe(safe)
file.clickOnClosePushNotificationsBanner()
})
Expand Down
1 change: 1 addition & 0 deletions cypress/support/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const requestPermissionsUrl = '/request-permissions'
export const getPermissionsUrl = '/get-permissions'
export const appSettingsUrl = '/settings/safe-apps'
export const setupUrl = '/settings/setup?safe='
export const dataSettingsUrl = '/settings/data'
export const invalidAppUrl = 'https://my-invalid-custom-app.com/manifest.json'
export const validAppUrlJson = 'https://my-valid-custom-app.com/manifest.json'
export const validAppUrl = 'https://my-valid-custom-app.com'
Expand Down
3 changes: 3 additions & 0 deletions public/images/common/lock.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
56 changes: 27 additions & 29 deletions src/components/common/ConnectWallet/AccountCenter.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import type { MouseEvent } from 'react'
import { useState } from 'react'
import { Box, Button, ButtonBase, Paper, Popover, Typography } from '@mui/material'
import { Box, Button, 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 Identicon from '@/components/common/Identicon'
import ChainSwitcher from '../ChainSwitcher'
import useAddressBook from '@/hooks/useAddressBook'
import { type ConnectedWallet } from '@/hooks/wallets/useOnboard'
import WalletInfo, { UNKNOWN_CHAIN_NAME } from '../WalletInfo'
import WalletInfo from '../WalletInfo'
import ChainIndicator from '@/components/common/ChainIndicator'
import { ONBOARD_MPC_MODULE_LABEL } from '@/services/mpc/module'
import SocialLoginInfo from '@/components/common/SocialLoginInfo'

const AccountCenter = ({ wallet }: { wallet: ConnectedWallet }) => {
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null)
Expand Down Expand Up @@ -53,7 +55,13 @@ const AccountCenter = ({ wallet }: { wallet: ConnectedWallet }) => {
<>
<ButtonBase onClick={handleClick} aria-describedby={id} disableRipple sx={{ alignSelf: 'stretch' }}>
<Box className={css.buttonContainer}>
<WalletInfo wallet={wallet} />
{wallet.label === ONBOARD_MPC_MODULE_LABEL ? (
<div className={css.socialLoginInfo}>
<SocialLoginInfo wallet={wallet} chainInfo={chainInfo} hideActions={true} />
</div>
) : (
<WalletInfo wallet={wallet} />
)}

<Box display="flex" alignItems="center" justifyContent="flex-end" marginLeft="auto">
{open ? <ExpandLessIcon color="border" /> : <ExpandMoreIcon color="border" />}
Expand All @@ -77,31 +85,21 @@ const AccountCenter = ({ wallet }: { wallet: ConnectedWallet }) => {
sx={{ marginTop: 1 }}
>
<Paper className={css.popoverContainer}>
<Identicon address={wallet.address} />

<Typography variant="h5" className={css.addressName}>
{addressBook[wallet.address] || wallet.ens}
</Typography>

<Box bgcolor="border.background" px={2} py={1} fontSize={14}>
<EthHashInfo
address={wallet.address}
showAvatar={false}
showName={false}
hasExplorer
showCopyButton
prefix={prefix}
/>
</Box>

<Box className={css.rowContainer}>
<Box className={css.row}>
<Typography variant="caption">Wallet</Typography>
<Typography variant="body2">{wallet.label}</Typography>
</Box>
<Box className={css.row}>
<Typography variant="caption">Connected network</Typography>
<Typography variant="body2">{chainInfo?.chainName || UNKNOWN_CHAIN_NAME}</Typography>
<Box className={css.accountContainer}>
<ChainIndicator />
<Box className={css.addressContainer}>
{wallet.label === ONBOARD_MPC_MODULE_LABEL ? (
<SocialLoginInfo wallet={wallet} chainInfo={chainInfo} />
) : (
<EthHashInfo
address={wallet.address}
name={addressBook[wallet.address] || wallet.ens}
hasExplorer
showCopyButton
prefix={prefix}
avatarSize={32}
/>
)}
</Box>
</Box>

Expand Down
50 changes: 26 additions & 24 deletions src/components/common/ConnectWallet/MPCLogin.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,48 @@
import { MPCWalletState } from '@/hooks/wallets/mpc/useMPCWallet'
import { Box, Button, SvgIcon, Typography } from '@mui/material'
import { useContext, useEffect, useState } from 'react'
import { useContext } from 'react'
import { MpcWalletContext } from './MPCWalletProvider'
import { PasswordRecovery } from './PasswordRecovery'
import GoogleLogo from '@/public/images/welcome/logo-google.svg'

import css from './styles.module.css'
import useWallet from '@/hooks/wallets/useWallet'
import { ONBOARD_MPC_MODULE_LABEL } from '@/services/mpc/module'

const MPCLogin = ({ onLogin }: { onLogin?: () => void }) => {
const { loginPending, triggerLogin, userInfo, walletState, recoverFactorWithPassword } = useContext(MpcWalletContext)
const { triggerLogin, userInfo, walletState, recoverFactorWithPassword } = useContext(MpcWalletContext)

const wallet = useWallet()

const [loginTriggered, setLoginTriggered] = useState(false)
const loginPending = walletState === MPCWalletState.AUTHENTICATING

const login = async () => {
setLoginTriggered(true)
await triggerLogin()
const success = await triggerLogin()

if (success) {
onLogin?.()
}
}

// If login was triggered through the Button we immediately continue if logged in
useEffect(() => {
if (loginTriggered && wallet && wallet.label === ONBOARD_MPC_MODULE_LABEL && onLogin) {
onLogin()
const recoverPassword = async (password: string, storeDeviceFactor: boolean) => {
const success = await recoverFactorWithPassword(password, storeDeviceFactor)

if (success) {
onLogin?.()
}
}, [loginTriggered, onLogin, wallet])
}

return (
<>
{wallet && userInfo ? (
<>
<Button
variant="outlined"
sx={{ padding: '1 2' }}
sx={{ px: 2, py: 1, borderWidth: '1px !important' }}
onClick={onLogin}
size="small"
disabled={loginPending}
fullWidth
>
<Box
width="100%"
justifyContent="space-between"
display="flex"
flexDirection="row"
alignItems="center"
gap={1}
>
<Box width="100%" display="flex" flexDirection="row" alignItems="center" gap={1}>
<img
src={userInfo.profileImage}
className={css.profileImg}
Expand All @@ -60,20 +55,27 @@ const MPCLogin = ({ onLogin }: { onLogin?: () => void }) => {
</Typography>
<Typography variant="body2">{userInfo.email}</Typography>
</div>
<SvgIcon component={GoogleLogo} inheritViewBox fontSize="medium" />
<SvgIcon component={GoogleLogo} inheritViewBox fontSize="medium" sx={{ marginLeft: 'auto' }} />
</Box>
</Button>
</>
) : (
<Button variant="outlined" onClick={login} size="small" disabled={loginPending} fullWidth>
<Button
variant="outlined"
onClick={login}
size="small"
disabled={loginPending}
fullWidth
sx={{ borderWidth: '1px !important' }}
>
<Box display="flex" flexDirection="row" alignItems="center" gap={1}>
<SvgIcon component={GoogleLogo} inheritViewBox fontSize="medium" /> Continue with Google
</Box>
</Button>
)}

{walletState === MPCWalletState.MANUAL_RECOVERY && (
<PasswordRecovery recoverFactorWithPassword={recoverFactorWithPassword} />
<PasswordRecovery recoverFactorWithPassword={recoverPassword} />
)}
</>
)
Expand Down
28 changes: 5 additions & 23 deletions src/components/common/ConnectWallet/MPCWalletProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,17 @@
import { useMPCWallet, MPCWalletState } from '@/hooks/wallets/mpc/useMPCWallet'
import { type UserInfo } from '@web3auth/mpc-core-kit'
import { useMPCWallet, MPCWalletState, type MPCWalletHook } from '@/hooks/wallets/mpc/useMPCWallet'
import { createContext, type ReactElement } from 'react'

type MPCWalletContext = {
loginPending: boolean
triggerLogin: () => Promise<void>
resetAccount: () => Promise<void>
upsertPasswordBackup: (password: string) => Promise<void>
recoverFactorWithPassword: (password: string, storeDeviceFactor: boolean) => Promise<void>
walletState: MPCWalletState
userInfo: UserInfo | undefined
}

export const MpcWalletContext = createContext<MPCWalletContext>({
loginPending: false,
export const MpcWalletContext = createContext<MPCWalletHook>({
walletState: MPCWalletState.NOT_INITIALIZED,
triggerLogin: () => Promise.resolve(),
triggerLogin: () => Promise.resolve(false),
resetAccount: () => Promise.resolve(),
upsertPasswordBackup: () => Promise.resolve(),
recoverFactorWithPassword: () => Promise.resolve(),
recoverFactorWithPassword: () => Promise.resolve(false),
userInfo: undefined,
})

export const MpcWalletProvider = ({ children }: { children: ReactElement }) => {
const mpcValue = useMPCWallet()

return (
<MpcWalletContext.Provider
value={{ ...mpcValue, loginPending: mpcValue.walletState === MPCWalletState.AUTHENTICATING }}
>
{children}
</MpcWalletContext.Provider>
)
return <MpcWalletContext.Provider value={mpcValue}>{children}</MpcWalletContext.Provider>
}
27 changes: 11 additions & 16 deletions src/components/common/ConnectWallet/WalletDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,22 @@
import { Button, Typography } from '@mui/material'
import { Divider, Typography } from '@mui/material'
import type { ReactElement } from 'react'

import KeyholeIcon from '@/components/common/icons/KeyholeIcon'
import useConnectWallet from '@/components/common/ConnectWallet/useConnectWallet'
import LockIcon from '@/public/images/common/lock.svg'
import MPCLogin from './MPCLogin'
import WalletLogin from '@/components/welcome/WelcomeLogin/WalletLogin'

const WalletDetails = ({ onConnect }: { onConnect?: () => void }): ReactElement => {
const connectWallet = useConnectWallet()

const handleConnect = () => {
onConnect?.()
connectWallet()
}

const WalletDetails = ({ onConnect }: { onConnect: () => void }): ReactElement => {
return (
<>
<Typography variant="h5">Connect a wallet</Typography>
<LockIcon />

<KeyholeIcon />
<WalletLogin onLogin={onConnect} />

<Button onClick={handleConnect} variant="contained" size="small" disableElevation fullWidth>
Connect
</Button>
<Divider sx={{ width: '100%' }}>
<Typography color="text.secondary" fontWeight={700} variant="overline">
or
</Typography>
</Divider>

<MPCLogin />
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ describe('MPCLogin', () => {
const mockOnLogin = jest.fn()
const walletAddress = hexZeroPad('0x1', 20)
const mockUseWallet = jest.spyOn(useWallet, 'default').mockReturnValue(null)
const mockTriggerLogin = jest.fn()
const mockTriggerLogin = jest.fn(() => true)
const mockUseMPCWallet = jest.spyOn(useMPCWallet, 'useMPCWallet').mockReturnValue({
userInfo: {
email: undefined,
Expand Down
Loading

0 comments on commit cc7398b

Please sign in to comment.