From af972440891283ea97390f0aea48602cdb1ad666 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ux=C3=ADo?= Date: Wed, 8 Nov 2023 18:13:16 +0100 Subject: [PATCH 01/19] Chore: Optimize docker image (#2771) - Reduce size by using multistage builds - Run `yarn build` on the build process and not when the docker image starts (it can take a few minutes to complete) --- .dockerignore | 6 +++++- Dockerfile | 35 ++++++++++++++++++++++++----------- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/.dockerignore b/.dockerignore index 355611305d..d659b8076c 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,10 +2,14 @@ Dockerfile .dockerignore node_modules npm-debug.log -README.md .next .git coverage .DS_Store .idea dist + +build/ +coverage/ +cypress/ +out/ diff --git a/Dockerfile b/Dockerfile index 82391964d5..9e8cba5245 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,21 +1,34 @@ -FROM node:18-alpine +FROM node:18-alpine AS base +ENV NEXT_TELEMETRY_DISABLED 1 + +FROM base AS builder + RUN apk add --no-cache libc6-compat git python3 py3-pip make g++ libusb-dev eudev-dev linux-headers WORKDIR /app + +# Install dependencies +COPY package.json yarn.lock* ./ +RUN yarn --frozen-lockfile COPY . . +RUN yarn run after-install -# install deps -RUN yarn install --frozen-lockfile -RUN yarn after-install +RUN yarn build + +# Production image +FROM base AS runner +WORKDIR /app ENV NODE_ENV production +ENV REVERSE_PROXY_UI_PORT 8080 -# Next.js collects completely anonymous telemetry data about general usage. -# Learn more here: https://nextjs.org/telemetry -# Uncomment the following line in case you want to disable telemetry during the build. -ENV NEXT_TELEMETRY_DISABLED 1 +RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 nextjs +COPY --from=builder /app/out ./out + +# Set the correct permission for prerender cache +RUN mkdir .next && chown nextjs:nodejs .next -EXPOSE 3000 +USER nextjs -ENV PORT 3000 +EXPOSE ${REVERSE_PROXY_UI_PORT} -CMD ["yarn", "static-serve"] +CMD npx -y serve out -p ${REVERSE_PROXY_UI_PORT} From e72f2223da693e63168c076a5358ecd7746f5c76 Mon Sep 17 00:00:00 2001 From: Usame Algan <5880855+usame-algan@users.noreply.github.com> Date: Thu, 9 Nov 2023 09:02:10 +0100 Subject: [PATCH 02/19] fix: Remove WC onboarding tooltip (#2746) * fix: Remove WC onboarding tooltip * fix: Onboard Safe logo size * fix: Update e2e tests acceptCookies call * fix: Failing e2e test --- cypress/e2e/safe-apps/apps_list.cy.js | 2 +- .../e2e/safe-apps/browser_permissions.cy.js | 2 +- .../e2e/safe-apps/drain_account.spec.cy.js | 2 +- cypress/e2e/safe-apps/safe_permissions.cy.js | 4 ++-- cypress/e2e/safe-apps/tx-builder.spec.cy.js | 4 ++-- cypress/e2e/smoke/add_owner.cy.js | 2 +- cypress/e2e/smoke/address_book.cy.js | 2 +- cypress/e2e/smoke/assets.cy.js | 2 +- cypress/e2e/smoke/balances.cy.js | 2 +- cypress/e2e/smoke/balances_pagination.cy.js | 2 +- cypress/e2e/smoke/batch_tx.cy.js | 2 +- cypress/e2e/smoke/create_tx.cy.js | 2 +- cypress/e2e/smoke/dashboard.cy.js | 2 +- cypress/e2e/smoke/import_export_data.cy.js | 2 +- cypress/e2e/smoke/nfts.cy.js | 2 +- cypress/e2e/smoke/tx_history.cy.js | 2 +- public/images/logo-round.svg | 2 +- .../walletconnect/WcHeaderWidget/index.tsx | 24 +++++++------------ 18 files changed, 27 insertions(+), 35 deletions(-) diff --git a/cypress/e2e/safe-apps/apps_list.cy.js b/cypress/e2e/safe-apps/apps_list.cy.js index 4081b813cf..9265117bcd 100644 --- a/cypress/e2e/safe-apps/apps_list.cy.js +++ b/cypress/e2e/safe-apps/apps_list.cy.js @@ -9,7 +9,7 @@ describe('Safe Apps tests', () => { beforeEach(() => { cy.clearLocalStorage() cy.visit(constants.SEPOLIA_TEST_SAFE_4 + constants.appsUrl, { failOnStatusCode: false }) - main.acceptCookies(1) + main.acceptCookies() }) it('Verify app list can be filtered by app name [C56130]', () => { diff --git a/cypress/e2e/safe-apps/browser_permissions.cy.js b/cypress/e2e/safe-apps/browser_permissions.cy.js index da34de03ab..916604d9a6 100644 --- a/cypress/e2e/safe-apps/browser_permissions.cy.js +++ b/cypress/e2e/safe-apps/browser_permissions.cy.js @@ -15,7 +15,7 @@ describe('Browser permissions tests', () => { }) }) cy.visitSafeApp(`${constants.testAppUrl}/app`) - main.acceptCookies(1) + main.acceptCookies() }) it('Verify a permissions slide to the user is displayed [C56137]', () => { diff --git a/cypress/e2e/safe-apps/drain_account.spec.cy.js b/cypress/e2e/safe-apps/drain_account.spec.cy.js index 87d878f307..1536de692f 100644 --- a/cypress/e2e/safe-apps/drain_account.spec.cy.js +++ b/cypress/e2e/safe-apps/drain_account.spec.cy.js @@ -15,7 +15,7 @@ describe('Drain Account Safe App tests', { defaultCommandTimeout: 12000 }, () => cy.clearLocalStorage() cy.visit(visitUrl) - main.acceptCookies(1) + main.acceptCookies() safeapps.clickOnContinueBtn() }) diff --git a/cypress/e2e/safe-apps/safe_permissions.cy.js b/cypress/e2e/safe-apps/safe_permissions.cy.js index 771beee30d..3b2b6fa6f8 100644 --- a/cypress/e2e/safe-apps/safe_permissions.cy.js +++ b/cypress/e2e/safe-apps/safe_permissions.cy.js @@ -17,7 +17,7 @@ describe('Safe permissions system tests', () => { it('Verify that requesting permissions with wallet_requestPermissions shows the permissions prompt and return the permissions on accept [C56150]', () => { cy.visitSafeApp(constants.testAppUrl + constants.requestPermissionsUrl) - main.acceptCookies(1) + main.acceptCookies() safeapps.clickOnContinueBtn() safeapps.verifyWarningDefaultAppMsgIsDisplayed() safeapps.clickOnContinueBtn() @@ -56,7 +56,7 @@ describe('Safe permissions system tests', () => { }) cy.visitSafeApp(constants.testAppUrl + constants.getPermissionsUrl) - main.acceptCookies(1) + main.acceptCookies() safeapps.clickOnContinueBtn() safeapps.verifyWarningDefaultAppMsgIsDisplayed() safeapps.clickOnContinueBtn() diff --git a/cypress/e2e/safe-apps/tx-builder.spec.cy.js b/cypress/e2e/safe-apps/tx-builder.spec.cy.js index fd850c112f..e2399d03e4 100644 --- a/cypress/e2e/safe-apps/tx-builder.spec.cy.js +++ b/cypress/e2e/safe-apps/tx-builder.spec.cy.js @@ -10,7 +10,7 @@ describe('Tx-builder Safe App tests', { defaultCommandTimeout: 20000 }, () => { beforeEach(() => { cy.clearLocalStorage() cy.visit(visitUrl) - main.acceptCookies(1) + main.acceptCookies() safeapps.clickOnContinueBtn() }) @@ -146,7 +146,7 @@ describe('Tx-builder Safe App tests', { defaultCommandTimeout: 20000 }, () => { getBody().findByText(safeapps.createBatchStr).click() getBody().findByText(safeapps.sendBatchStr).click() }) - cy.get('p').contains('1').should('be.visible') + cy.get('p').contains('1').should('exist') cy.get('p').contains('2').should('be.visible') }) diff --git a/cypress/e2e/smoke/add_owner.cy.js b/cypress/e2e/smoke/add_owner.cy.js index a36635750c..e19dbca96b 100644 --- a/cypress/e2e/smoke/add_owner.cy.js +++ b/cypress/e2e/smoke/add_owner.cy.js @@ -7,7 +7,7 @@ describe('Add Owners tests', () => { beforeEach(() => { cy.visit(constants.setupUrl + constants.SEPOLIA_TEST_SAFE_1) cy.clearLocalStorage() - main.acceptCookies(1) + main.acceptCookies() cy.contains(owner.safeAccountNonceStr, { timeout: 10000 }) }) diff --git a/cypress/e2e/smoke/address_book.cy.js b/cypress/e2e/smoke/address_book.cy.js index 3a5af18932..d14ca5e2b4 100644 --- a/cypress/e2e/smoke/address_book.cy.js +++ b/cypress/e2e/smoke/address_book.cy.js @@ -12,7 +12,7 @@ describe('Address book tests', () => { beforeEach(() => { cy.clearLocalStorage() cy.visit(constants.addressBookUrl + constants.SEPOLIA_TEST_SAFE_1) - main.acceptCookies(1) + main.acceptCookies() }) it('Verify entry can be added [C56061]', () => { diff --git a/cypress/e2e/smoke/assets.cy.js b/cypress/e2e/smoke/assets.cy.js index aae5c455ad..f056ce3bae 100644 --- a/cypress/e2e/smoke/assets.cy.js +++ b/cypress/e2e/smoke/assets.cy.js @@ -13,7 +13,7 @@ describe('Assets tests', () => { beforeEach(() => { cy.visit(constants.BALANCE_URL + constants.SEPOLIA_TEST_SAFE_5) cy.clearLocalStorage() - main.acceptCookies(2) + main.acceptCookies() }) it('Verify that the token tab is selected by default and the table is visible [C56039]', () => { diff --git a/cypress/e2e/smoke/balances.cy.js b/cypress/e2e/smoke/balances.cy.js index df8e1c3681..7d5a0bb7b9 100644 --- a/cypress/e2e/smoke/balances.cy.js +++ b/cypress/e2e/smoke/balances.cy.js @@ -14,7 +14,7 @@ describe('Balance tests', () => { beforeEach(() => { cy.clearLocalStorage() cy.visit(constants.BALANCE_URL + constants.SEPOLIA_TEST_SAFE_5) - main.acceptCookies(2) + main.acceptCookies() cy.contains('Assets') cy.get(balances.balanceSingleRow).should('have.length.lessThan', ASSETS_LENGTH) balances.selectTokenList(balances.tokenListOptions.allTokens) diff --git a/cypress/e2e/smoke/balances_pagination.cy.js b/cypress/e2e/smoke/balances_pagination.cy.js index b1033b9242..27703f9c2b 100644 --- a/cypress/e2e/smoke/balances_pagination.cy.js +++ b/cypress/e2e/smoke/balances_pagination.cy.js @@ -9,7 +9,7 @@ describe('Balance tests', () => { cy.clearLocalStorage() // Open the Safe used for testing cy.visit(constants.BALANCE_URL + constants.SEPOLIA_TEST_SAFE_6) - main.acceptCookies(2) + main.acceptCookies() balances.selectTokenList(balances.tokenListOptions.allTokens) }) diff --git a/cypress/e2e/smoke/batch_tx.cy.js b/cypress/e2e/smoke/batch_tx.cy.js index 507b2b3ecd..347d723e77 100644 --- a/cypress/e2e/smoke/batch_tx.cy.js +++ b/cypress/e2e/smoke/batch_tx.cy.js @@ -10,7 +10,7 @@ describe('Batch transaction tests', () => { beforeEach(() => { cy.clearLocalStorage() cy.visit(constants.BALANCE_URL + constants.SEPOLIA_TEST_SAFE_5) - main.acceptCookies(2) + main.acceptCookies() }) it('Verify empty batch list can be opened [C56082]', () => { diff --git a/cypress/e2e/smoke/create_tx.cy.js b/cypress/e2e/smoke/create_tx.cy.js index b1a5e1ac1a..a96db26acc 100644 --- a/cypress/e2e/smoke/create_tx.cy.js +++ b/cypress/e2e/smoke/create_tx.cy.js @@ -9,7 +9,7 @@ describe('Create transactions tests', () => { before(() => { cy.clearLocalStorage() cy.visit(constants.BALANCE_URL + constants.SEPOLIA_TEST_SAFE_5) - main.acceptCookies(2) + main.acceptCookies() }) it('Verify a new send token transaction can be created [C56104]', () => { diff --git a/cypress/e2e/smoke/dashboard.cy.js b/cypress/e2e/smoke/dashboard.cy.js index ad0ca6d0ee..c69c918df1 100644 --- a/cypress/e2e/smoke/dashboard.cy.js +++ b/cypress/e2e/smoke/dashboard.cy.js @@ -6,7 +6,7 @@ describe('Dashboard tests', () => { beforeEach(() => { cy.clearLocalStorage() cy.visit(constants.BALANCE_URL + constants.SEPOLIA_TEST_SAFE_5) - main.acceptCookies(2) + main.acceptCookies() main.clickOnSideMenuItem(constants.mainSideMenuOptions.home) dashboard.verifyConnectTransactStrIsVisible() }) diff --git a/cypress/e2e/smoke/import_export_data.cy.js b/cypress/e2e/smoke/import_export_data.cy.js index de7e8f3881..140b5d9dc1 100644 --- a/cypress/e2e/smoke/import_export_data.cy.js +++ b/cypress/e2e/smoke/import_export_data.cy.js @@ -24,7 +24,7 @@ describe('Import Export Data tests', () => { }) it('Verify address book imported data [C56112]', () => { - main.acceptCookies(1) + main.acceptCookies() file.clickOnAddressBookBtn() file.verifyImportedAddressBookData() }) diff --git a/cypress/e2e/smoke/nfts.cy.js b/cypress/e2e/smoke/nfts.cy.js index e86f1d671a..b6015e592a 100644 --- a/cypress/e2e/smoke/nfts.cy.js +++ b/cypress/e2e/smoke/nfts.cy.js @@ -10,7 +10,7 @@ describe('NFTs tests', () => { beforeEach(() => { cy.clearLocalStorage() cy.visit(constants.BALANCE_URL + constants.SEPOLIA_TEST_SAFE_5) - main.acceptCookies(2) + main.acceptCookies() nfts.clickOnNftsTab() }) diff --git a/cypress/e2e/smoke/tx_history.cy.js b/cypress/e2e/smoke/tx_history.cy.js index 42fdbeeb44..4ad1e5b929 100644 --- a/cypress/e2e/smoke/tx_history.cy.js +++ b/cypress/e2e/smoke/tx_history.cy.js @@ -17,7 +17,7 @@ describe('Transaction history tests', () => { // So that tests that rely on this feature don't randomly fail cy.window().then((win) => win.localStorage.setItem('SAFE_v2__AB_human-readable', true)) - main.acceptCookies(1) + main.acceptCookies() }) it('Verify October 29th transactions are displayed [C56128]', () => { diff --git a/public/images/logo-round.svg b/public/images/logo-round.svg index 7782f914a7..dd8af7d0ca 100644 --- a/public/images/logo-round.svg +++ b/public/images/logo-round.svg @@ -1,4 +1,4 @@ - + diff --git a/src/components/walletconnect/WcHeaderWidget/index.tsx b/src/components/walletconnect/WcHeaderWidget/index.tsx index fa95cab249..51382b8ad6 100644 --- a/src/components/walletconnect/WcHeaderWidget/index.tsx +++ b/src/components/walletconnect/WcHeaderWidget/index.tsx @@ -2,8 +2,6 @@ import { type ReactNode, useRef } from 'react' import type { SessionTypes } from '@walletconnect/types' import Popup from '@/components/common/Popup' import WcIcon from './WcIcon' -import { OnboardingTooltip } from '@/components/common/OnboardingTooltip' -import useSafeInfo from '@/hooks/useSafeInfo' type WcHeaderWidgetProps = { children: ReactNode @@ -14,25 +12,19 @@ type WcHeaderWidgetProps = { onClose: () => void } -const TOOLTIP_TEXT = 'Connect Safe{Wallet} to any dApp with WalletConnect' -const TOOLTIP_ID = 'native_wc_onboarding' - const WcHeaderWidget = ({ sessions, ...props }: WcHeaderWidgetProps) => { const iconRef = useRef(null) - const { safeLoaded } = useSafeInfo() return ( <> - -
- -
- +
+ +
{props.children} From b2ba82dee6ebbe8cf6114caf6b188d68376007ea Mon Sep 17 00:00:00 2001 From: katspaugh <381895+katspaugh@users.noreply.github.com> Date: Thu, 9 Nov 2023 11:44:51 +0100 Subject: [PATCH 03/19] Fix: redirect /wc URL (#2747) * Fix: redirect /wc URL * Use raw URL query * Fix tests * Rm raw query parsing * Fix: unblock Base and Arbitrum bridges in WC --- .../walletconnect/WcInput/index.tsx | 2 + src/config/routes.ts | 3 +- src/pages/wc.tsx | 40 +++++++++++++++++++ src/services/walletconnect/constants.ts | 4 +- .../useWalletConnectSearchParamUri.ts | 9 +---- 5 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 src/pages/wc.tsx diff --git a/src/components/walletconnect/WcInput/index.tsx b/src/components/walletconnect/WcInput/index.tsx index fcfa32a919..125695708d 100644 --- a/src/components/walletconnect/WcInput/index.tsx +++ b/src/components/walletconnect/WcInput/index.tsx @@ -79,7 +79,9 @@ const WcInput = ({ uri }: { uri: string }) => { error={!!error} label={error ? error.message : 'Pairing code'} placeholder="wc:" + spellCheck={false} InputProps={{ + autoComplete: 'off', endAdornment: isClipboardSupported() ? undefined : ( diff --git a/src/config/routes.ts b/src/config/routes.ts index 6090dbb7ad..27402a440f 100644 --- a/src/config/routes.ts +++ b/src/config/routes.ts @@ -1,7 +1,7 @@ export const AppRoutes = { '404': '/404', - _offline: '/_offline', welcome: '/welcome', + wc: '/wc', terms: '/terms', privacy: '/privacy', licenses: '/licenses', @@ -11,6 +11,7 @@ export const AppRoutes = { cookie: '/cookie', addressBook: '/address-book', addOwner: '/addOwner', + _offline: '/_offline', apps: { open: '/apps/open', index: '/apps', diff --git a/src/pages/wc.tsx b/src/pages/wc.tsx new file mode 100644 index 0000000000..02c41159db --- /dev/null +++ b/src/pages/wc.tsx @@ -0,0 +1,40 @@ +import { useEffect } from 'react' +import type { NextPage } from 'next' +import { useRouter } from 'next/router' +import useLastSafe from '@/hooks/useLastSafe' +import { AppRoutes } from '@/config/routes' +import { WC_URI_SEARCH_PARAM } from '@/services/walletconnect/useWalletConnectSearchParamUri' + +const WcPage: NextPage = () => { + const router = useRouter() + const lastSafe = useLastSafe() + + useEffect(() => { + if (!router.isReady || router.pathname !== AppRoutes.wc) { + return + } + + const { uri } = router.query + + router.replace( + lastSafe + ? { + pathname: AppRoutes.home, + query: { + safe: lastSafe, + [WC_URI_SEARCH_PARAM]: uri, + }, + } + : { + pathname: AppRoutes.welcome, + query: { + [WC_URI_SEARCH_PARAM]: uri, + }, + }, + ) + }, [router, lastSafe]) + + return <> +} + +export default WcPage diff --git a/src/services/walletconnect/constants.ts b/src/services/walletconnect/constants.ts index 435b964832..2206a0069c 100644 --- a/src/services/walletconnect/constants.ts +++ b/src/services/walletconnect/constants.ts @@ -36,8 +36,6 @@ export const EIP155 = 'eip155' as const // Bridges enforcing same address on destination chains export const BlockedBridges = [ 'app.chainport.io', - 'bridge.arbitrum.io', - 'bridge.base.org', 'cbridge.celer.network', 'www.orbiter.finance', 'zksync-era.l2scan.co', @@ -66,6 +64,8 @@ export const BlockedBridges = [ export const WarnedBridges = [ 'across.to', // doesn't send their URL in the session proposal 'app.allbridge.io', + 'bridge.arbitrum.io', + 'bridge.base.org', 'core.allbridge.io', 'bungee.exchange', 'www.carrier.so', diff --git a/src/services/walletconnect/useWalletConnectSearchParamUri.ts b/src/services/walletconnect/useWalletConnectSearchParamUri.ts index d21a1bc14a..41ed350120 100644 --- a/src/services/walletconnect/useWalletConnectSearchParamUri.ts +++ b/src/services/walletconnect/useWalletConnectSearchParamUri.ts @@ -1,16 +1,11 @@ import { useRouter } from 'next/router' import { useCallback } from 'react' -import { isPairingUri } from './utils' - -const WC_URI_SEARCH_PARAM = 'wc' +export const WC_URI_SEARCH_PARAM = 'wc' export function useWalletConnectSearchParamUri(): [string | null, (wcUri: string | null) => void] { const router = useRouter() - - const wcUriQuery = router.query[WC_URI_SEARCH_PARAM] - const value = wcUriQuery ? (Array.isArray(wcUriQuery) ? wcUriQuery[0] : wcUriQuery) : null - const wcUri = value && isPairingUri(value) ? value : null + const wcUri = (router.query[WC_URI_SEARCH_PARAM] || '').toString() || null const setWcUri = useCallback( (wcUri: string | null) => { From 798e998b4c08cfd00c6a1af9fffd8290e9f99f5c Mon Sep 17 00:00:00 2001 From: Manuel Gellfart Date: Thu, 9 Nov 2023 16:06:54 +0100 Subject: [PATCH 04/19] Epic Seedless-onboarding (#2534) - Adds option to create a social signer via google - Adds Settings page for setting up MFA via password recovery for Social signer - Redesigns Welcome Page --------- Co-authored-by: Usame Algan Co-authored-by: Usame Algan <5880855+usame-algan@users.noreply.github.com> Co-authored-by: Michael <30682308+mike10ca@users.noreply.github.com> Co-authored-by: katspaugh <381895+katspaugh@users.noreply.github.com> --- .env.example | 7 +- .github/workflows/build/action.yml | 2 + cypress/e2e/pages/create_wallet.pages.js | 25 +- cypress/e2e/pages/import_export.pages.js | 6 +- cypress/e2e/pages/load_safe.pages.js | 4 +- cypress/e2e/pages/owners.pages.js | 7 +- cypress/e2e/smoke/create_safe_simple.cy.js | 42 +- cypress/e2e/smoke/import_export_data.cy.js | 6 +- cypress/support/constants.js | 1 + jest.config.cjs | 4 +- next.config.mjs | 2 +- package.json | 5 +- public/images/common/bar-chart.svg | 5 + public/images/common/check-filled.svg | 3 + public/images/common/lock-small.svg | 4 + public/images/common/lock-warning.svg | 6 + public/images/common/lock.svg | 3 + public/images/common/shield-off.svg | 12 + public/images/common/shield.svg | 3 + public/images/logo-text.svg | 9 + public/images/welcome/logo-google.svg | 6 + .../common/ConnectWallet/AccountCenter.tsx | 89 +- .../common/ConnectWallet/ConnectionCenter.tsx | 79 ++ .../common/ConnectWallet/WalletDetails.tsx | 30 + .../__tests__/AccountCenter.test.tsx | 35 + .../__tests__/ConnectionCenter.test.tsx | 18 + src/components/common/ConnectWallet/index.tsx | 5 +- .../common/ConnectWallet/styles.module.css | 52 +- .../common/ConnectWallet/useConnectWallet.ts | 14 +- src/components/common/ErrorBoundary/index.tsx | 2 +- .../EthHashInfo/SrcEthHashInfo/index.tsx | 2 +- src/components/common/Footer/index.tsx | 2 +- src/components/common/Header/index.tsx | 2 +- .../common/NetworkSelector/index.tsx | 28 +- .../common/NetworkSelector/styles.module.css | 4 + .../common/PageHeader/styles.module.css | 2 +- src/components/common/PageLayout/index.tsx | 1 - .../common/SafeLoadingError/index.tsx | 2 +- .../common/SocialLoginInfo/index.tsx | 55 ++ .../common/SocialLoginInfo/styles.module.css | 35 + .../common/SocialSigner/PasswordRecovery.tsx | 114 +++ .../__tests__/PasswordRecovery.test.tsx | 48 + .../__tests__/SocialSignerLogin.test.tsx | 201 +++++ src/components/common/SocialSigner/index.tsx | 185 ++++ .../common/SocialSigner/styles.module.css | 24 + .../common/WalletInfo/index.test.tsx | 182 ++++ src/components/common/WalletInfo/index.tsx | 137 ++- .../common/WalletInfo/styles.module.css | 44 +- .../common/WalletOverview/index.tsx | 56 ++ .../common/WalletOverview/styles.module.css | 38 + .../common/icons/KeyholeIcon/index.tsx | 31 +- .../new-safe/create/OverviewWidget/index.tsx | 4 +- .../__tests__/useSyncSafeCreationStep.test.ts | 23 +- src/components/new-safe/create/index.tsx | 23 +- .../create/steps/ConnectWalletStep/index.tsx | 40 - .../create/steps/ReviewStep/index.test.tsx | 42 + .../create/steps/ReviewStep/index.tsx | 142 ++- .../create/steps/SetNameStep/index.tsx | 12 +- .../create/steps/StatusStep/index.tsx | 2 +- .../create/useSyncSafeCreationStep.ts | 14 +- src/components/new-safe/load/index.tsx | 2 +- .../PushNotificationsBanner/index.tsx | 11 +- .../ExportMPCAccountModal.tsx | 147 ++++ .../SocialSignerExport/index.tsx | 47 + .../SocialSignerExport/styles.module.css | 10 + .../SocialSignerMFA/PasswordInput.tsx | 44 + .../SocialSignerMFA/index.test.tsx | 45 + .../SecurityLogin/SocialSignerMFA/index.tsx | 298 +++++++ .../SocialSignerMFA/styles.module.css | 76 ++ .../settings/SecurityLogin/index.tsx | 51 ++ .../settings/SettingsHeader/index.test.tsx | 78 ++ .../settings/SettingsHeader/index.tsx | 28 +- src/components/sidebar/SafeList/index.tsx | 10 +- src/components/sidebar/Sidebar/index.tsx | 2 +- .../sidebar/SidebarNavigation/config.tsx | 8 + src/components/tx-flow/index.tsx | 1 + src/components/welcome/NewSafe.tsx | 193 +---- src/components/welcome/NewSafeSocial.tsx | 86 ++ .../{ => SafeListDrawer}/DataWidget.tsx | 0 .../welcome/SafeListDrawer/index.tsx | 55 ++ .../welcome/SafeListDrawer/styles.module.css | 33 + .../welcome/WelcomeLogin/WalletLogin.tsx | 63 ++ .../__tests__/WalletLogin.test.tsx | 96 ++ src/components/welcome/WelcomeLogin/index.tsx | 60 ++ .../welcome/WelcomeLogin/styles.module.css | 19 + src/components/welcome/styles.module.css | 147 ++-- src/config/routes.ts | 6 +- src/hooks/__tests__/useTxTracking.test.ts | 39 +- src/hooks/useMnemonicName/dict.ts | 11 +- src/hooks/useMnemonicName/index.ts | 12 +- .../useMnemonicName/useMnemonicName.test.ts | 16 +- .../wallets/__tests__/useOnboard.test.ts | 254 ++++++ src/hooks/wallets/consts.ts | 3 + .../wallets/mpc/__tests__/useMPC.test.ts | 226 +++++ src/hooks/wallets/mpc/useMPC.ts | 92 ++ src/hooks/wallets/mpc/useSocialWallet.ts | 63 ++ src/hooks/wallets/useOnboard.test.ts | 69 -- src/hooks/wallets/useOnboard.ts | 16 +- src/hooks/wallets/wallets.ts | 8 + src/pages/_app.tsx | 5 + src/pages/index.tsx | 4 +- src/pages/settings/security-login.tsx | 23 + src/pages/wc.tsx | 2 +- src/pages/{welcome.tsx => welcome/index.tsx} | 0 src/pages/welcome/social-login.tsx | 17 + src/service-workers/index.ts | 3 + src/service-workers/mpc-core-kit-sw.ts | 325 +++++++ .../analytics/events/createLoadSafe.ts | 11 +- src/services/analytics/events/mpcWallet.ts | 56 ++ src/services/exceptions/ErrorCodes.ts | 3 + src/services/mpc/PasswordRecoveryModal.tsx | 42 + src/services/mpc/SocialLoginModule.ts | 136 +++ src/services/mpc/SocialWalletService.ts | 163 ++++ .../mpc/__mocks__/SocialWalletService.ts | 74 ++ .../mpc/__tests__/SocialWalletService.test.ts | 271 ++++++ src/services/mpc/__tests__/module.test.ts | 125 +++ src/services/mpc/config.ts | 40 + src/services/mpc/icon.ts | 12 + src/services/mpc/interfaces.ts | 52 ++ .../mpc/recovery/DeviceShareRecovery.ts | 59 ++ .../mpc/recovery/SecurityQuestionRecovery.ts | 48 + src/store/__tests__/addressBookSlice.test.ts | 12 + src/store/addressBookSlice.ts | 3 + src/styles/globals.css | 4 + src/tests/builders/eip1193Provider.ts | 10 + src/tests/builders/wallet.ts | 16 + src/utils/addresses.ts | 6 +- src/utils/chains.ts | 1 + yarn.lock | 820 +++++++++++++++++- 129 files changed, 6109 insertions(+), 640 deletions(-) create mode 100644 public/images/common/bar-chart.svg create mode 100644 public/images/common/check-filled.svg create mode 100644 public/images/common/lock-small.svg create mode 100644 public/images/common/lock-warning.svg create mode 100644 public/images/common/lock.svg create mode 100644 public/images/common/shield-off.svg create mode 100644 public/images/common/shield.svg create mode 100644 public/images/logo-text.svg create mode 100644 public/images/welcome/logo-google.svg create mode 100644 src/components/common/ConnectWallet/ConnectionCenter.tsx create mode 100644 src/components/common/ConnectWallet/WalletDetails.tsx create mode 100644 src/components/common/ConnectWallet/__tests__/AccountCenter.test.tsx create mode 100644 src/components/common/ConnectWallet/__tests__/ConnectionCenter.test.tsx create mode 100644 src/components/common/SocialLoginInfo/index.tsx create mode 100644 src/components/common/SocialLoginInfo/styles.module.css create mode 100644 src/components/common/SocialSigner/PasswordRecovery.tsx create mode 100644 src/components/common/SocialSigner/__tests__/PasswordRecovery.test.tsx create mode 100644 src/components/common/SocialSigner/__tests__/SocialSignerLogin.test.tsx create mode 100644 src/components/common/SocialSigner/index.tsx create mode 100644 src/components/common/SocialSigner/styles.module.css create mode 100644 src/components/common/WalletInfo/index.test.tsx create mode 100644 src/components/common/WalletOverview/index.tsx create mode 100644 src/components/common/WalletOverview/styles.module.css delete mode 100644 src/components/new-safe/create/steps/ConnectWalletStep/index.tsx create mode 100644 src/components/new-safe/create/steps/ReviewStep/index.test.tsx create mode 100644 src/components/settings/SecurityLogin/SocialSignerExport/ExportMPCAccountModal.tsx create mode 100644 src/components/settings/SecurityLogin/SocialSignerExport/index.tsx create mode 100644 src/components/settings/SecurityLogin/SocialSignerExport/styles.module.css create mode 100644 src/components/settings/SecurityLogin/SocialSignerMFA/PasswordInput.tsx create mode 100644 src/components/settings/SecurityLogin/SocialSignerMFA/index.test.tsx create mode 100644 src/components/settings/SecurityLogin/SocialSignerMFA/index.tsx create mode 100644 src/components/settings/SecurityLogin/SocialSignerMFA/styles.module.css create mode 100644 src/components/settings/SecurityLogin/index.tsx create mode 100644 src/components/settings/SettingsHeader/index.test.tsx create mode 100644 src/components/welcome/NewSafeSocial.tsx rename src/components/welcome/{ => SafeListDrawer}/DataWidget.tsx (100%) create mode 100644 src/components/welcome/SafeListDrawer/index.tsx create mode 100644 src/components/welcome/SafeListDrawer/styles.module.css create mode 100644 src/components/welcome/WelcomeLogin/WalletLogin.tsx create mode 100644 src/components/welcome/WelcomeLogin/__tests__/WalletLogin.test.tsx create mode 100644 src/components/welcome/WelcomeLogin/index.tsx create mode 100644 src/components/welcome/WelcomeLogin/styles.module.css create mode 100644 src/hooks/wallets/__tests__/useOnboard.test.ts create mode 100644 src/hooks/wallets/mpc/__tests__/useMPC.test.ts create mode 100644 src/hooks/wallets/mpc/useMPC.ts create mode 100644 src/hooks/wallets/mpc/useSocialWallet.ts delete mode 100644 src/hooks/wallets/useOnboard.test.ts create mode 100644 src/pages/settings/security-login.tsx rename src/pages/{welcome.tsx => welcome/index.tsx} (100%) create mode 100644 src/pages/welcome/social-login.tsx create mode 100644 src/service-workers/mpc-core-kit-sw.ts create mode 100644 src/services/analytics/events/mpcWallet.ts create mode 100644 src/services/mpc/PasswordRecoveryModal.tsx create mode 100644 src/services/mpc/SocialLoginModule.ts create mode 100644 src/services/mpc/SocialWalletService.ts create mode 100644 src/services/mpc/__mocks__/SocialWalletService.ts create mode 100644 src/services/mpc/__tests__/SocialWalletService.test.ts create mode 100644 src/services/mpc/__tests__/module.test.ts create mode 100644 src/services/mpc/config.ts create mode 100644 src/services/mpc/icon.ts create mode 100644 src/services/mpc/interfaces.ts create mode 100644 src/services/mpc/recovery/DeviceShareRecovery.ts create mode 100644 src/services/mpc/recovery/SecurityQuestionRecovery.ts create mode 100644 src/tests/builders/eip1193Provider.ts create mode 100644 src/tests/builders/wallet.ts 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 ? (