diff --git a/.env.example b/.env.example index 6642c2c52c..4ee43536d7 100644 --- a/.env.example +++ b/.env.example @@ -40,4 +40,9 @@ NEXT_PUBLIC_FIREBASE_OPTIONS_STAGING= NEXT_PUBLIC_FIREBASE_VAPID_KEY_STAGING= # Redefine -NEXT_PUBLIC_REDEFINE_API= \ No newline at end of file +NEXT_PUBLIC_REDEFINE_API= + +# Social Login +NEXT_PUBLIC_SOCIAL_WALLET_OPTIONS_STAGING= +NEXT_PUBLIC_SOCIAL_WALLET_OPTIONS_PRODUCTION= + diff --git a/.github/workflows/build/action.yml b/.github/workflows/build/action.yml index fd57aada4f..8a8c32e0c1 100644 --- a/.github/workflows/build/action.yml +++ b/.github/workflows/build/action.yml @@ -43,6 +43,8 @@ runs: NEXT_PUBLIC_SAFE_RELAY_SERVICE_URL_STAGING: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_SAFE_GELATO_RELAY_SERVICE_URL_STAGING }} NEXT_PUBLIC_IS_OFFICIAL_HOST: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_IS_OFFICIAL_HOST }} NEXT_PUBLIC_REDEFINE_API: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_REDEFINE_API }} + NEXT_PUBLIC_SOCIAL_WALLET_OPTIONS_STAGING: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_SOCIAL_WALLET_OPTIONS_STAGING }} + NEXT_PUBLIC_SOCIAL_WALLET_OPTIONS_PRODUCTION: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_SOCIAL_WALLET_OPTIONS_PRODUCTION }} NEXT_PUBLIC_FIREBASE_OPTIONS_PRODUCTION: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_FIREBASE_OPTIONS_PRODUCTION }} NEXT_PUBLIC_FIREBASE_OPTIONS_STAGING: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_FIREBASE_OPTIONS_STAGING }} NEXT_PUBLIC_FIREBASE_VAPID_KEY_PRODUCTION: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_FIREBASE_VAPID_KEY_PRODUCTION }} diff --git a/cypress/e2e/pages/create_wallet.pages.js b/cypress/e2e/pages/create_wallet.pages.js index 706f7e4da1..ba4f9f8599 100644 --- a/cypress/e2e/pages/create_wallet.pages.js +++ b/cypress/e2e/pages/create_wallet.pages.js @@ -1,5 +1,7 @@ import * as constants from '../../support/constants' +const welcomeLoginScreen = '[data-testid="welcome-login"]' +const expandMoreIcon = 'svg[data-testid="ExpandMoreIcon"]' const nameInput = 'input[name="name"]' const selectNetworkBtn = '[data-cy="create-safe-select-network"]' const ownerInput = 'input[name^="owners"][name$="name"]' @@ -7,16 +9,17 @@ const ownerAddress = 'input[name^="owners"][name$="address"]' const thresholdInput = 'input[name="threshold"]' export const removeOwnerBtn = 'button[aria-label="Remove owner"]' const connectingContainer = 'div[class*="connecting-container"]' -const createNewSafeBtn = 'span[data-track="create-safe: Open stepper"]' +const createNewSafeBtn = 'span[data-track="create-safe: Continue to creation"]' +const connectWalletBtn = 'Connect wallet' const changeNetworkWarningStr = 'Change your wallet network' const safeAccountSetupStr = 'Safe Account setup' -const policy1_1 = '1/1 policy' +const policy1_2 = '1/1 policy' export const walletName = 'test1-sepolia-safe' -export const defaltSepoliaPlaceholder = 'sepolia-safe' +export const defaltSepoliaPlaceholder = 'Sepolia Safe' export function verifyPolicy1_1() { - cy.contains(policy1_1).should('exist') + cy.contains(policy1_2).should('exist') // TOD: Need data-cy for containers } @@ -49,13 +52,23 @@ export function clickOnCreateNewSafeBtn() { cy.get(createNewSafeBtn).click().wait(1000) } +export function clickOnConnectWalletBtn() { + cy.get(welcomeLoginScreen).within(() => { + cy.get('button').contains(connectWalletBtn).should('be.visible').should('be.enabled').click().wait(1000) + }) +} + export function typeWalletName(name) { cy.get(nameInput).type(name).should('have.value', name) } +export function clearWalletName() { + cy.get(nameInput).clear() +} + export function selectNetwork(network, regex = false) { cy.wait(1000) - cy.get(selectNetworkBtn).should('be.visible').click() + cy.get(expandMoreIcon).eq(1).parents('div').eq(1).click() cy.wait(1000) cy.get('li').contains(network).click() cy.get('body').click() @@ -91,7 +104,7 @@ export function typeOwnerAddress(address, index, clearOnly = false) { } export function clickOnAddNewOwnerBtn() { - cy.contains('button', 'Add new owner').click() + cy.contains('button', 'Add new owner').click().wait(700) } export function addNewOwner(name, address, index) { diff --git a/cypress/e2e/pages/import_export.pages.js b/cypress/e2e/pages/import_export.pages.js index e5b49348ba..d2bf9b050b 100644 --- a/cypress/e2e/pages/import_export.pages.js +++ b/cypress/e2e/pages/import_export.pages.js @@ -25,7 +25,7 @@ export function clickOnImportBtn() { } export function clickOnImportBtnDataImportModal() { - cy.contains(dataImportModalStr).parent().contains('button', 'Import').click() + cy.contains('button', 'Import').click() } export function uploadFile(filePath) { @@ -44,6 +44,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 }) diff --git a/cypress/e2e/pages/load_safe.pages.js b/cypress/e2e/pages/load_safe.pages.js index bc0fa83daa..488b343bc6 100644 --- a/cypress/e2e/pages/load_safe.pages.js +++ b/cypress/e2e/pages/load_safe.pages.js @@ -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' @@ -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) } diff --git a/cypress/e2e/pages/owners.pages.js b/cypress/e2e/pages/owners.pages.js index 51d0c658f6..6e2e30aef3 100644 --- a/cypress/e2e/pages/owners.pages.js +++ b/cypress/e2e/pages/owners.pages.js @@ -19,6 +19,7 @@ const thresholdDropdown = 'div[aria-haspopup="listbox"]' const thresholdOption = 'li[role="option"]' const existingOwnerAddressInput = (index) => `input[name="owners.${index}.address"]` const existingOwnerNameInput = (index) => `input[name="owners.${index}.name"]` +const singleOwnerNameInput = 'input[name="name"]' const disconnectBtnStr = 'Disconnect' const notConnectedStatus = 'Connect' @@ -57,9 +58,9 @@ export function verifyExistingOwnerName(index, name) { cy.get(existingOwnerNameInput(index)).should('have.value', name) } -export function typeExistingOwnerName(index, name) { - cy.get(existingOwnerNameInput(index)).clear().type(name) - main.verifyInputValue(existingOwnerNameInput(index), name) +export function typeExistingOwnerName(name) { + cy.get(singleOwnerNameInput).clear().type(name) + main.verifyInputValue(singleOwnerNameInput, name) } export function verifyOwnerDeletionWindowDisplayed() { diff --git a/cypress/e2e/smoke/create_safe_simple.cy.js b/cypress/e2e/smoke/create_safe_simple.cy.js index 9dc90f4723..7f5a8690c3 100644 --- a/cypress/e2e/smoke/create_safe_simple.cy.js +++ b/cypress/e2e/smoke/create_safe_simple.cy.js @@ -1,29 +1,27 @@ import * as constants from '../../support/constants' import * as main from '../../e2e/pages/main.page' import * as createwallet from '../pages/create_wallet.pages' - import * as owner from '../pages/owners.pages' describe('Safe creation tests', () => { beforeEach(() => { - cy.visit(constants.createNewSafeSepoliaUrl) + cy.visit(constants.welcomeUrl + '?chain=sep') cy.clearLocalStorage() main.acceptCookies() }) it('Verify a Wallet can be connected [C56101]', () => { - owner.waitForConnectionStatus() - cy.visit(constants.welcomeUrl) + createwallet.clickOnCreateNewSafeBtn() owner.clickOnWalletExpandMoreIcon() owner.clickOnDisconnectBtn() - createwallet.clickOnCreateNewSafeBtn() - owner.clickOnConnectBtn() + createwallet.clickOnConnectWalletBtn() createwallet.connectWallet() }) it('Verify Next button is disabled until switching to network is done [C56102]', () => { owner.waitForConnectionStatus() createwallet.selectNetwork(constants.networks.ethereum) + createwallet.clickOnCreateNewSafeBtn() createwallet.checkNetworkChangeWarningMsg() createwallet.verifyNextBtnIsDisabled() createwallet.selectNetwork(constants.networks.sepolia) @@ -32,43 +30,49 @@ describe('Safe creation tests', () => { it('Verify that a new Wallet has default name related to the selected network [C56099]', () => { owner.waitForConnectionStatus() + createwallet.clickOnCreateNewSafeBtn() createwallet.verifyDefaultWalletName(createwallet.defaltSepoliaPlaceholder) }) it('Verify error message is displayed if wallet name input exceeds 50 characters [C56098]', () => { owner.waitForConnectionStatus() + createwallet.clickOnCreateNewSafeBtn() createwallet.typeWalletName(main.generateRandomString(51)) owner.verifyErrorMsgInvalidAddress(constants.addressBookErrrMsg.exceedChars) + createwallet.clearWalletName() }) it('Verify there is no error message is displayed if wallet name input contains less than 50 characters [C56100]', () => { owner.waitForConnectionStatus() + createwallet.clickOnCreateNewSafeBtn() createwallet.typeWalletName(main.generateRandomString(50)) owner.verifyValidWalletName(constants.addressBookErrrMsg.exceedChars) }) it('Verify current connected account is shown as default owner [C56091]', () => { owner.waitForConnectionStatus() + createwallet.clickOnCreateNewSafeBtn() owner.clickOnNextBtn() owner.verifyExistingOwnerAddress(0, constants.DEFAULT_OWNER_ADDRESS) }) it('Verify error message is displayed if owner name input exceeds 50 characters [C56092]', () => { owner.waitForConnectionStatus() - owner.clickOnNextBtn() - owner.typeExistingOwnerName(0, main.generateRandomString(51)) + createwallet.clickOnCreateNewSafeBtn() + owner.typeExistingOwnerName(main.generateRandomString(51)) owner.verifyErrorMsgInvalidAddress(constants.addressBookErrrMsg.exceedChars) }) it('Verify there is no error message is displayed if owner name input contains less than 50 characters [C56093]', () => { owner.waitForConnectionStatus() - owner.clickOnNextBtn() - owner.typeExistingOwnerName(0, main.generateRandomString(50)) + createwallet.clickOnCreateNewSafeBtn() + owner.typeExistingOwnerName(main.generateRandomString(50)) owner.verifyValidWalletName(constants.addressBookErrrMsg.exceedChars) }) it('Verify Add and Remove Owner Row works as expected [C56094]', () => { owner.waitForConnectionStatus() + createwallet.clickOnCreateNewSafeBtn() owner.clickOnNextBtn() createwallet.clickOnAddNewOwnerBtn() owner.verifyNumberOfOwners(2) @@ -82,26 +86,34 @@ describe('Safe creation tests', () => { it('Verify Threshold Setup [C56096]', () => { owner.waitForConnectionStatus() + createwallet.clickOnCreateNewSafeBtn() owner.clickOnNextBtn() createwallet.clickOnAddNewOwnerBtn() - owner.verifyNumberOfOwners(2) createwallet.clickOnAddNewOwnerBtn() owner.verifyNumberOfOwners(3) - owner.verifyThresholdLimit(1, 3) + createwallet.clickOnAddNewOwnerBtn() + owner.verifyNumberOfOwners(4) + owner.verifyThresholdLimit(1, 4) createwallet.updateThreshold(3) createwallet.removeOwner(1) + owner.verifyThresholdLimit(1, 3) + createwallet.removeOwner(1) owner.verifyThresholdLimit(1, 2) + createwallet.updateThreshold(1) }) it('Verify data persistence [C56103]', () => { const ownerName = 'David' owner.waitForConnectionStatus() - createwallet.typeWalletName(createwallet.walletName) + createwallet.clickOnCreateNewSafeBtn() owner.clickOnNextBtn() createwallet.clickOnAddNewOwnerBtn() createwallet.typeOwnerName(ownerName, 1) createwallet.typeOwnerAddress(constants.SEPOLIA_OWNER_2, 1) - owner.verifyThresholdLimit(1, 2) + owner.clickOnBackBtn() + createwallet.clearWalletName() + createwallet.typeWalletName(createwallet.walletName) + owner.clickOnNextBtn() owner.clickOnNextBtn() createwallet.verifySafeNameInSummaryStep(createwallet.walletName) createwallet.verifyOwnerNameInSummaryStep(ownerName) @@ -125,12 +137,14 @@ describe('Safe creation tests', () => { it('Verify tip is displayed on right side for threshold 1/1 [C56097]', () => { owner.waitForConnectionStatus() + createwallet.clickOnCreateNewSafeBtn() owner.clickOnNextBtn() createwallet.verifyPolicy1_1() }) it('Verify address input validation rules [C56095]', () => { owner.waitForConnectionStatus() + createwallet.clickOnCreateNewSafeBtn() owner.clickOnNextBtn() createwallet.clickOnAddNewOwnerBtn() createwallet.typeOwnerAddress(main.generateRandomString(10), 1) diff --git a/cypress/e2e/smoke/import_export_data.cy.js b/cypress/e2e/smoke/import_export_data.cy.js index 140b5d9dc1..2bcc125943 100644 --- a/cypress/e2e/smoke/import_export_data.cy.js +++ b/cypress/e2e/smoke/import_export_data.cy.js @@ -6,19 +6,19 @@ import * as constants from '../../support/constants' describe('Import Export Data tests', () => { before(() => { cy.clearLocalStorage() - cy.visit(constants.welcomeUrl) + cy.visit(constants.dataSettingsUrl) main.acceptCookies() - file.verifyImportBtnIsVisible() }) it('Verify Safe can be accessed after test file upload [C56111]', () => { const filePath = '../fixtures/data_import.json' const safe = constants.SEPOLIA_CSV_ENTRY.name - file.clickOnImportBtn() file.uploadFile(filePath) file.verifyImportModalData() file.clickOnImportBtnDataImportModal() + cy.visit(constants.welcomeUrl) + file.clickOnOpenSafeListSidebar() file.clickOnImportedSafe(safe) file.clickOnClosePushNotificationsBanner() }) diff --git a/cypress/support/constants.js b/cypress/support/constants.js index 6daeacfc92..e9dce33964 100644 --- a/cypress/support/constants.js +++ b/cypress/support/constants.js @@ -54,6 +54,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' diff --git a/jest.config.cjs b/jest.config.cjs index 2ba19a6f88..1b06adda63 100644 --- a/jest.config.cjs +++ b/jest.config.cjs @@ -8,13 +8,13 @@ const createJestConfig = nextJest({ // Add any custom config to be passed to Jest const customJestConfig = { setupFilesAfterEnv: ['/jest.setup.js'], + moduleNameMapper: { // Handle module aliases (this will be automatically configured for you soon) '^@/(.*)$': '/src/$1', '^.+\\.(svg)$': '/mocks/svg.js', isows: '/node_modules/isows/_cjs/index.js', }, - transformIgnorePatterns: ['node_modules/(?!isows/)'], testEnvironment: 'jest-environment-jsdom', testEnvironmentOptions: { url: 'http://localhost/balances?safe=rin:0xb3b83bf204C458B461de9B0CD2739DB152b4fa5A' }, globals: { @@ -25,5 +25,5 @@ const customJestConfig = { // createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async module.exports = async () => ({ ...(await createJestConfig(customJestConfig)()), - transformIgnorePatterns: ['node_modules/(?!(uint8arrays|multiformats)/)'], + transformIgnorePatterns: ['node_modules/(?!(uint8arrays|multiformats|@web3-onboard/common)/)'], }) diff --git a/next.config.mjs b/next.config.mjs index 1470c8d524..07d1f2a35d 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -32,7 +32,7 @@ const nextConfig = { dirs: ['src'], }, experimental: { - optimizePackageImports: ['@mui/material', '@mui/icons-material', 'lodash', 'date-fns'] + optimizePackageImports: ['@mui/material', '@mui/icons-material', 'lodash', 'date-fns'], }, webpack(config) { config.module.rules.push({ diff --git a/package.json b/package.json index 9882a430e1..26ac24be3c 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "homepage": "https://github.com/safe-global/safe-wallet-web", "license": "GPL-3.0", "type": "module", - "version": "1.21.0", + "version": "1.22.0", "scripts": { "dev": "next dev", "start": "next dev", @@ -56,6 +56,7 @@ "@safe-global/safe-react-components": "^2.0.6", "@sentry/react": "^7.74.0", "@sentry/tracing": "^7.74.0", + "@tkey-mpc/common-types": "^8.2.2", "@truffle/hdwallet-provider": "^2.1.4", "@walletconnect/utils": "^2.10.2", "@walletconnect/web3wallet": "^1.9.2", @@ -66,7 +67,9 @@ "@web3-onboard/ledger": "2.3.2", "@web3-onboard/trezor": "^2.4.2", "@web3-onboard/walletconnect": "^2.4.7", + "@web3auth/mpc-core-kit": "^1.1.3", "blo": "^1.1.1", + "bn.js": "^5.2.1", "classnames": "^2.3.1", "date-fns": "^2.29.2", "ethers": "5.7.2", diff --git a/public/images/common/bar-chart.svg b/public/images/common/bar-chart.svg new file mode 100644 index 0000000000..f6beb44899 --- /dev/null +++ b/public/images/common/bar-chart.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/images/common/check-filled.svg b/public/images/common/check-filled.svg new file mode 100644 index 0000000000..284624fc19 --- /dev/null +++ b/public/images/common/check-filled.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/images/common/lock-small.svg b/public/images/common/lock-small.svg new file mode 100644 index 0000000000..2620c043c8 --- /dev/null +++ b/public/images/common/lock-small.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/images/common/lock-warning.svg b/public/images/common/lock-warning.svg new file mode 100644 index 0000000000..6972e1bb15 --- /dev/null +++ b/public/images/common/lock-warning.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/images/common/lock.svg b/public/images/common/lock.svg new file mode 100644 index 0000000000..9535a642e0 --- /dev/null +++ b/public/images/common/lock.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/images/common/shield-off.svg b/public/images/common/shield-off.svg new file mode 100644 index 0000000000..f36482d5ca --- /dev/null +++ b/public/images/common/shield-off.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/public/images/common/shield.svg b/public/images/common/shield.svg new file mode 100644 index 0000000000..f7d12a900f --- /dev/null +++ b/public/images/common/shield.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/images/logo-text.svg b/public/images/logo-text.svg new file mode 100644 index 0000000000..b71ae734a4 --- /dev/null +++ b/public/images/logo-text.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/images/welcome/logo-google.svg b/public/images/welcome/logo-google.svg new file mode 100644 index 0000000000..65781d4881 --- /dev/null +++ b/public/images/welcome/logo-google.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/components/common/ConnectWallet/AccountCenter.tsx b/src/components/common/ConnectWallet/AccountCenter.tsx index 161e9e1bb2..c28d607e09 100644 --- a/src/components/common/ConnectWallet/AccountCenter.tsx +++ b/src/components/common/ConnectWallet/AccountCenter.tsx @@ -1,48 +1,21 @@ import type { MouseEvent } from 'react' import { useState } from 'react' -import { Box, Button, ButtonBase, Paper, Popover, Typography } 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 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 WalletOverview from '../WalletOverview' +import WalletInfo from '@/components/common/WalletInfo' -const AccountCenter = ({ wallet }: { wallet: ConnectedWallet }) => { +export const AccountCenter = ({ wallet }: { wallet: ConnectedWallet }) => { const [anchorEl, setAnchorEl] = useState(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) => { + const openWalletInfo = (event: MouseEvent) => { setAnchorEl(event.currentTarget) } - const handleClose = () => { + const closeWalletInfo = () => { setAnchorEl(null) } @@ -51,9 +24,15 @@ const AccountCenter = ({ wallet }: { wallet: ConnectedWallet }) => { return ( <> - + - + {open ? : } @@ -65,7 +44,7 @@ const AccountCenter = ({ wallet }: { wallet: ConnectedWallet }) => { id={id} open={open} anchorEl={anchorEl} - onClose={handleClose} + onClose={closeWalletInfo} anchorOrigin={{ vertical: 'bottom', horizontal: 'center', @@ -81,43 +60,7 @@ const AccountCenter = ({ wallet }: { wallet: ConnectedWallet }) => { }} > - - - - {addressBook[wallet.address] || wallet.ens} - - - - - - - - - Wallet - {wallet.label} - - - Connected network - {chainInfo?.chainName || UNKNOWN_CHAIN_NAME} - - - - - - - - + diff --git a/src/components/common/ConnectWallet/ConnectionCenter.tsx b/src/components/common/ConnectWallet/ConnectionCenter.tsx new file mode 100644 index 0000000000..1e04b377d2 --- /dev/null +++ b/src/components/common/ConnectWallet/ConnectionCenter.tsx @@ -0,0 +1,79 @@ +import ConnectWalletButton from '@/components/common/ConnectWallet/ConnectWalletButton' +import { useHasFeature } from '@/hooks/useChains' +import { FEATURES } from '@/utils/chains' +import madProps from '@/utils/mad-props' +import { Popover, ButtonBase, Typography, Paper, Box } from '@mui/material' +import ExpandMoreIcon from '@mui/icons-material/ExpandMore' +import ExpandLessIcon from '@mui/icons-material/ExpandLess' +import classnames from 'classnames' +import { useState, type MouseEvent, type ReactElement } from 'react' + +import KeyholeIcon from '@/components/common/icons/KeyholeIcon' +import WalletDetails from '@/components/common/ConnectWallet/WalletDetails' + +import css from '@/components/common/ConnectWallet/styles.module.css' + +export const ConnectionCenter = ({ isSocialLoginEnabled }: { isSocialLoginEnabled: boolean }): ReactElement => { + const [anchorEl, setAnchorEl] = useState(null) + const open = !!anchorEl + + const handleClick = (event: MouseEvent) => { + setAnchorEl(event.currentTarget) + } + + const handleClose = () => { + setAnchorEl(null) + } + + const ExpandIcon = open ? ExpandLessIcon : ExpandMoreIcon + + if (!isSocialLoginEnabled) { + return ( + + + + ) + } + + return ( + <> + + + + + Not connected + palette.error.main }}> + Connect wallet + + + + + + + + + + + + + ) +} + +const useIsSocialLoginEnabled = () => useHasFeature(FEATURES.SOCIAL_LOGIN) + +export default madProps(ConnectionCenter, { + isSocialLoginEnabled: useIsSocialLoginEnabled, +}) diff --git a/src/components/common/ConnectWallet/WalletDetails.tsx b/src/components/common/ConnectWallet/WalletDetails.tsx new file mode 100644 index 0000000000..5f0442f50a --- /dev/null +++ b/src/components/common/ConnectWallet/WalletDetails.tsx @@ -0,0 +1,30 @@ +import { Box, Divider, SvgIcon, Typography } from '@mui/material' +import type { ReactElement } from 'react' + +import LockIcon from '@/public/images/common/lock.svg' +import SocialSigner from '@/components/common/SocialSigner' +import WalletLogin from '@/components/welcome/WelcomeLogin/WalletLogin' + +const WalletDetails = ({ onConnect }: { onConnect: () => void }): ReactElement => { + return ( + <> + + + + + + + + + + + or + + + + + + ) +} + +export default WalletDetails diff --git a/src/components/common/ConnectWallet/__tests__/AccountCenter.test.tsx b/src/components/common/ConnectWallet/__tests__/AccountCenter.test.tsx new file mode 100644 index 0000000000..382b7acb1d --- /dev/null +++ b/src/components/common/ConnectWallet/__tests__/AccountCenter.test.tsx @@ -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() + + 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() + }) + }) +}) diff --git a/src/components/common/ConnectWallet/__tests__/ConnectionCenter.test.tsx b/src/components/common/ConnectWallet/__tests__/ConnectionCenter.test.tsx new file mode 100644 index 0000000000..5ca16f4512 --- /dev/null +++ b/src/components/common/ConnectWallet/__tests__/ConnectionCenter.test.tsx @@ -0,0 +1,18 @@ +import { ConnectionCenter } from '@/components/common/ConnectWallet/ConnectionCenter' +import { render } from '@/tests/test-utils' + +describe('ConnectionCenter', () => { + it('displays a Connect wallet button if the social login feature is enabled', () => { + const { getByText, queryByText } = render() + + expect(getByText('Connect wallet')).toBeInTheDocument() + expect(queryByText('Connect')).not.toBeInTheDocument() + }) + + it('displays the ConnectWalletButton if the social login feature is disabled', () => { + const { getByText, queryByText } = render() + + expect(queryByText('Connect wallet')).not.toBeInTheDocument() + expect(getByText('Connect')).toBeInTheDocument() + }) +}) diff --git a/src/components/common/ConnectWallet/index.tsx b/src/components/common/ConnectWallet/index.tsx index aaf6812b1a..45a9f33a9f 100644 --- a/src/components/common/ConnectWallet/index.tsx +++ b/src/components/common/ConnectWallet/index.tsx @@ -1,13 +1,12 @@ import type { ReactElement } from 'react' import useWallet from '@/hooks/wallets/useWallet' import AccountCenter from '@/components/common/ConnectWallet/AccountCenter' -import ConnectWalletButton from './ConnectWalletButton' -import css from './styles.module.css' +import ConnectionCenter from './ConnectionCenter' const ConnectWallet = (): ReactElement => { const wallet = useWallet() - return
{wallet ? : }
+ return wallet ? : } export default ConnectWallet diff --git a/src/components/common/ConnectWallet/styles.module.css b/src/components/common/ConnectWallet/styles.module.css index 24057834f2..b41bbc8399 100644 --- a/src/components/common/ConnectWallet/styles.module.css +++ b/src/components/common/ConnectWallet/styles.module.css @@ -1,5 +1,6 @@ -.container { - padding: 0 var(--space-2); +.connectedContainer { + display: flex; + align-items: center; } .buttonContainer { @@ -7,18 +8,23 @@ align-items: center; text-align: left; gap: var(--space-1); + padding: 0 var(--space-2); } .popoverContainer { padding: var(--space-2); - width: 250px; + width: 300px; display: flex; flex-direction: column; align-items: center; - gap: var(--space-2); + gap: var(--space-1); border: 1px solid var(--color-border-light); } +.largeGap { + gap: var(--space-2); +} + .addressName { text-align: center; overflow: hidden; @@ -27,6 +33,18 @@ width: 100%; } +.profileImg { + border-radius: var(--space-2); + width: 32px; + height: 32px; +} + +.profileData { + display: flex; + flex-direction: column; + align-items: flex-start; +} + .rowContainer { align-self: stretch; margin-left: calc(var(--space-2) * -1); @@ -45,3 +63,29 @@ .row:last-of-type { border-bottom: 1px solid var(--color-border-light); } + +.pairingDetails { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--space-2); +} + +.loginButton { + min-height: 42px; +} + +.loginError { + width: 100%; + margin: 0; +} + +@media (max-width: 599.95px) { + .socialLoginInfo > div > div { + display: none; + } + + .notConnected { + display: none; + } +} diff --git a/src/components/common/ConnectWallet/useConnectWallet.ts b/src/components/common/ConnectWallet/useConnectWallet.ts index 9dc10e7930..b3f832229e 100644 --- a/src/components/common/ConnectWallet/useConnectWallet.ts +++ b/src/components/common/ConnectWallet/useConnectWallet.ts @@ -1,19 +1,17 @@ -import { useMemo } from 'react' +import { useCallback } from 'react' import useOnboard, { connectWallet } from '@/hooks/wallets/useOnboard' import { OVERVIEW_EVENTS, trackEvent } from '@/services/analytics' -const useConnectWallet = (): (() => void) => { +const useConnectWallet = () => { const onboard = useOnboard() - return useMemo(() => { + return useCallback(() => { if (!onboard) { - return () => {} + return Promise.resolve(undefined) } - return () => { - trackEvent(OVERVIEW_EVENTS.OPEN_ONBOARD) - connectWallet(onboard) - } + trackEvent(OVERVIEW_EVENTS.OPEN_ONBOARD) + return connectWallet(onboard) }, [onboard]) } diff --git a/src/components/common/ErrorBoundary/index.tsx b/src/components/common/ErrorBoundary/index.tsx index e277db308a..f25c1bbcfa 100644 --- a/src/components/common/ErrorBoundary/index.tsx +++ b/src/components/common/ErrorBoundary/index.tsx @@ -33,7 +33,7 @@ const ErrorBoundary: FallbackRender = ({ error, componentStack }) => { {componentStack} )} - + Go home diff --git a/src/components/common/EthHashInfo/SrcEthHashInfo/index.tsx b/src/components/common/EthHashInfo/SrcEthHashInfo/index.tsx index 1fbbd76d89..34041dbdfb 100644 --- a/src/components/common/EthHashInfo/SrcEthHashInfo/index.tsx +++ b/src/components/common/EthHashInfo/SrcEthHashInfo/index.tsx @@ -71,7 +71,7 @@ const SrcEthHashInfo = ({ {name && ( - + {name} )} diff --git a/src/components/common/Footer/index.tsx b/src/components/common/Footer/index.tsx index afd19922e0..545b1f43aa 100644 --- a/src/components/common/Footer/index.tsx +++ b/src/components/common/Footer/index.tsx @@ -11,7 +11,7 @@ import MUILink from '@mui/material/Link' import { IS_DEV, IS_OFFICIAL_HOST } from '@/config/constants' const footerPages = [ - AppRoutes.welcome, + AppRoutes.welcome.index, AppRoutes.settings.index, AppRoutes.imprint, AppRoutes.privacy, diff --git a/src/components/common/Header/index.tsx b/src/components/common/Header/index.tsx index c18ebcd039..6919ca1526 100644 --- a/src/components/common/Header/index.tsx +++ b/src/components/common/Header/index.tsx @@ -34,7 +34,7 @@ const Header = ({ onMenuToggle, onBatchToggle }: HeaderProps): ReactElement => { const enableWc = !!chain && hasFeature(chain, FEATURES.NATIVE_WALLETCONNECT) // Logo link: if on Dashboard, link to Welcome, otherwise to the root (which redirects to either Dashboard or Welcome) - const logoHref = router.pathname === AppRoutes.home ? AppRoutes.welcome : AppRoutes.index + const logoHref = router.pathname === AppRoutes.home ? AppRoutes.welcome.index : AppRoutes.index const handleMenuToggle = () => { if (onMenuToggle) { diff --git a/src/components/common/NetworkSelector/index.tsx b/src/components/common/NetworkSelector/index.tsx index efe261b122..5e3ac1e676 100644 --- a/src/components/common/NetworkSelector/index.tsx +++ b/src/components/common/NetworkSelector/index.tsx @@ -1,20 +1,34 @@ import Link from 'next/link' import type { SelectChangeEvent } from '@mui/material' -import { MenuItem, Select, Skeleton } from '@mui/material' +import { MenuItem, Select, Skeleton, Tooltip } from '@mui/material' import ExpandMoreIcon from '@mui/icons-material/ExpandMore' import useChains from '@/hooks/useChains' import { useRouter } from 'next/router' import ChainIndicator from '../ChainIndicator' import css from './styles.module.css' import { useChainId } from '@/hooks/useChainId' -import type { ReactElement } from 'react' +import { type ReactElement, forwardRef } from 'react' import { useCallback } from 'react' import { AppRoutes } from '@/config/routes' import { trackEvent, OVERVIEW_EVENTS } from '@/services/analytics' +import useWallet from '@/hooks/wallets/useWallet' +import { isSocialWalletEnabled } from '@/hooks/wallets/wallets' +import { isSocialLoginWallet } from '@/services/mpc/SocialLoginModule' -const keepPathRoutes = [AppRoutes.welcome, AppRoutes.newSafe.create, AppRoutes.newSafe.load] +const keepPathRoutes = [AppRoutes.welcome.index, AppRoutes.newSafe.create, AppRoutes.newSafe.load] + +const MenuWithTooltip = forwardRef(function MenuWithTooltip(props: any, ref) { + return ( + +
    + {props.children} +
+
+ ) +}) const NetworkSelector = (props: { onChainSelect?: () => void }): ReactElement => { + const wallet = useWallet() const { configs } = useChains() const chainId = useChainId() const router = useRouter() @@ -53,6 +67,8 @@ const NetworkSelector = (props: { onChainSelect?: () => void }): ReactElement => } } + const isSocialLogin = isSocialLoginWallet(wallet?.label) + return configs.length ? (