diff --git a/apps/browser-extension-wallet/src/features/dapp/components/DappError.tsx b/apps/browser-extension-wallet/src/features/dapp/components/DappError.tsx new file mode 100644 index 0000000000..438445600d --- /dev/null +++ b/apps/browser-extension-wallet/src/features/dapp/components/DappError.tsx @@ -0,0 +1,61 @@ +import React, { ReactNode, useCallback, useEffect } from 'react'; +import { Image } from 'antd'; +import { useTranslation } from 'react-i18next'; +import Empty from '../../../assets/icons/empty.svg'; +import styles from './Layout.module.scss'; +import { Button } from '@lace/common'; + +type DappErrorProps = { + title: string; + description: ReactNode; + closeButtonLabel?: string; + onCloseClick?: () => void; + onMount?: () => void; + containerTestId: string; + imageTestId: string; + titleTestId: string; + descriptionTestId: string; + closeButtonTestId: string; +}; +export const DappError = ({ + title, + description, + closeButtonLabel, + onCloseClick, + onMount, + containerTestId, + imageTestId, + titleTestId, + descriptionTestId, + closeButtonTestId +}: DappErrorProps): React.ReactElement => { + const { t } = useTranslation(); + const handleClose = useCallback(() => { + if (onCloseClick) onCloseClick(); + window.close(); + }, [onCloseClick]); + + useEffect(() => { + if (onMount) onMount(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( +
+
+ +
+ {title} +
+
+ {description} +
+
+
+ +
+
+ ); +}; diff --git a/apps/browser-extension-wallet/src/features/dapp/components/DappTransactionFail.tsx b/apps/browser-extension-wallet/src/features/dapp/components/DappTransactionFail.tsx index e008f2810f..465bbf40b7 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/DappTransactionFail.tsx +++ b/apps/browser-extension-wallet/src/features/dapp/components/DappTransactionFail.tsx @@ -24,8 +24,8 @@ export const DappTransactionFail = (): React.ReactElement => { }, [analytics]); return ( -
-
+
+
{t('dapp.sign.failure.title')}
{t('dapp.sign.failure.description')}
diff --git a/apps/browser-extension-wallet/src/features/dapp/components/DappTransactionSuccess.tsx b/apps/browser-extension-wallet/src/features/dapp/components/DappTransactionSuccess.tsx index c69b4bf667..42650cdf24 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/DappTransactionSuccess.tsx +++ b/apps/browser-extension-wallet/src/features/dapp/components/DappTransactionSuccess.tsx @@ -25,8 +25,8 @@ export const DappTransactionSuccess = (): React.ReactElement => { }, [analytics]); return ( -
-
+
+
{t('browserView.transaction.success.youCanSafelyCloseThisPanel')} diff --git a/apps/browser-extension-wallet/src/features/dapp/components/Layout.module.scss b/apps/browser-extension-wallet/src/features/dapp/components/Layout.module.scss index edad913a6a..58262eb769 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/Layout.module.scss +++ b/apps/browser-extension-wallet/src/features/dapp/components/Layout.module.scss @@ -26,7 +26,7 @@ padding-top: size_unit(4); } -.noWalletContainer { +.dappErrorContainer { align-items: center; display: flex; flex-direction: column; @@ -34,7 +34,7 @@ justify-content: space-between; width: 100%; - .noWalletContent { + .dappErrorContent { padding: 0 size_unit(3); display: flex; flex: 1; @@ -45,7 +45,7 @@ .heading { color: var(--text-color-secondary); font-size: var(--bodyLarge); - font-weight: 800; + font-weight: 600; letter-spacing: 0.02em; line-height: size_unit(3); margin-top: size_unit(2); @@ -55,6 +55,7 @@ .description { color: var(--text-color-secondary); font-size: var(--bodySmall); + font-weight: 500; letter-spacing: 0.02em; line-height: size_unit(3); margin-top: size_unit(2); diff --git a/apps/browser-extension-wallet/src/features/dapp/components/NoWallet.tsx b/apps/browser-extension-wallet/src/features/dapp/components/NoWallet.tsx deleted file mode 100644 index 86050bb7ea..0000000000 --- a/apps/browser-extension-wallet/src/features/dapp/components/NoWallet.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react'; -import { Image } from 'antd'; -import { useTranslation } from 'react-i18next'; -import Empty from '../../../assets/icons/empty.svg'; -import styles from './Layout.module.scss'; -import { Button } from '@lace/common'; -import { tabs } from 'webextension-polyfill'; - -const openCreatePage = () => { - tabs.create({ url: 'app.html#/setup' }); - window.close(); -}; - -export const NoWallet = (): React.ReactElement => { - const { t } = useTranslation(); - - return ( -
-
- -
- {t('dapp.noWallet.heading')} -
-
- {t('dapp.noWallet.description')} -
-
-
- -
-
- ); -}; diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmDRepRetirementContainer.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmDRepRetirementContainer.tsx index 42650c2b8e..2727c88121 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmDRepRetirementContainer.tsx +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmDRepRetirementContainer.tsx @@ -1,34 +1,76 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { ConfirmDRepRetirement } from '@lace/core'; -import { SignTxData } from './types'; -import { certificateInspectorFactory, drepIDasBech32FromHash, getOwnRetirementMessageKey } from './utils'; +import { certificateInspectorFactory, drepIDasBech32FromHash } from './utils'; import { Wallet } from '@lace/cardano'; import { useWalletStore } from '@src/stores'; -import { useIsOwnPubDRepKey } from './hooks'; - -const { CertificateType } = Wallet.Cardano; +import { SignTxData } from './types'; +import { useGetOwnPubDRepKeyHash } from './hooks'; +import { Skeleton } from 'antd'; +import { exposeApi, RemoteApiPropertyType } from '@cardano-sdk/web-extension'; +import { UserPromptService } from '@lib/scripts/background/services'; +import { of } from 'rxjs'; +import { ApiError, APIErrorCode } from '@cardano-sdk/dapp-connector'; +import { DAPP_CHANNELS } from '@utils/constants'; +import { runtime } from 'webextension-polyfill'; +import { DappError } from '../DappError'; interface Props { signTxData: SignTxData; errorMessage?: string; + onError: () => void; } -export const ConfirmDRepRetirementContainer = ({ signTxData, errorMessage }: Props): React.ReactElement => { +export const disallowSignTx = (): void => { + exposeApi>( + { + api$: of({ + async allowSignTx(): Promise { + return Promise.reject(new ApiError(APIErrorCode.InvalidRequest, 'DRep ID mismatch')); + } + }), + baseChannel: DAPP_CHANNELS.userPrompt, + properties: { allowSignTx: RemoteApiPropertyType.MethodReturningPromise } + }, + { logger: console, runtime } + ); +}; + +export const ConfirmDRepRetirementContainer = ({ signTxData, onError, errorMessage }: Props): React.ReactElement => { const { t } = useTranslation(); const { - walletUI: { cardanoCoin }, - inMemoryWallet + walletUI: { cardanoCoin } } = useWalletStore(); const certificate = certificateInspectorFactory( - CertificateType.UnregisterDelegateRepresentative + Wallet.Cardano.CertificateType.UnregisterDelegateRepresentative )(signTxData.tx); const depositPaidWithCardanoSymbol = `${Wallet.util.lovelacesToAdaString(certificate.deposit.toString())} ${ cardanoCoin.symbol }`; + const { loading: loadingOwnPubDRepKeyHash, ownPubDRepKeyHash } = useGetOwnPubDRepKeyHash(); + const isNotOwnDRepKey = certificate.dRepCredential.hash !== ownPubDRepKeyHash; + + if (loadingOwnPubDRepKeyHash) { + return ; + } - const isOwnRetirement = useIsOwnPubDRepKey(inMemoryWallet.getPubDRepKey, certificate.dRepCredential.hash); - const ownRetirementMessageKey = getOwnRetirementMessageKey(isOwnRetirement); + if (isNotOwnDRepKey) { + return ( + { + disallowSignTx(); + onError(); + }} + containerTestId="drep-id-mismatch-container" + imageTestId="drep-id-mismatch-image" + titleTestId="drep-id-mismatch-heading" + descriptionTestId="drep-id-mismatch-description" + closeButtonTestId="drep-id-mismatch-close-button" + /> + ); + } return ( ); }; diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmTransaction.module.scss b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmTransaction.module.scss index 9d25c19f3c..3376b96ac5 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmTransaction.module.scss +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmTransaction.module.scss @@ -1,30 +1,26 @@ @import '../../../../../../../packages/common/src/ui/styles/theme.scss'; @import '../../../../../src/styles/rules/flex.scss'; -.actions { - display: flex; - gap: size_unit(1); - justify-content: space-evenly; - flex-direction: column; -} - .spaceBetween { justify-content: space-between; padding-top: size_unit(2); } +.layoutError { + padding: 0; +} + .actions { - background-color: var(--bg-color-body); @extend %flex-column; - justify-content: center; + background-color: var(--bg-color-body); gap: size_unit(1); padding: size_unit(2) size_unit(3) size_unit(2) size_unit(3); border-top: 2px solid var(--light-mode-light-grey-plus, var(--dark-mode-mid-grey)); margin: size_unit(4) size_unit(-3) size_unit(-2) size_unit(-3); position: sticky; bottom: 0; + z-index: 10; .actionBtn { width: 100%; } - z-index: 10; } diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmTransaction.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmTransaction.tsx index f90eea8d72..e17e48769d 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmTransaction.tsx +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmTransaction.tsx @@ -1,5 +1,6 @@ /* eslint-disable no-console */ -import React, { useMemo } from 'react'; +import React, { useMemo, useState } from 'react'; +import cn from 'classnames'; import { Button, PostHogAction } from '@lace/common'; import { useTranslation } from 'react-i18next'; import { Layout } from '../Layout'; @@ -38,7 +39,8 @@ export const ConfirmTransaction = (): React.ReactElement => { ); const { getKeyAgentType } = useWalletStore(); const analytics = useAnalyticsContext(); - const { signTxData, errorMessage } = useSignTxData(dappDataApi.getSignTxData); + const { signTxData, errorMessage: getSignTxDataError } = useSignTxData(dappDataApi.getSignTxData); + const [confirmTransactionError, setConfirmTransactionError] = useState(false); const keyAgentType = getKeyAgentType(); const isUsingHardwareWallet = keyAgentType !== Wallet.KeyManagement.KeyAgentType.InMemory; const disallowSignTx = useDisallowSignTx(); @@ -62,29 +64,40 @@ export const ConfirmTransaction = (): React.ReactElement => { useOnBeforeUnload(disallowSignTx); return ( - - -
- - -
+ + setConfirmTransactionError(true)} + errorMessage={getSignTxDataError} + /> + {!confirmTransactionError && ( +
+ + +
+ )}
); }; diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmTransactionContent.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmTransactionContent.tsx index eb931efab5..4d35efc983 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmTransactionContent.tsx +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmTransactionContent.tsx @@ -13,9 +13,10 @@ interface Props { txType?: Wallet.Cip30TxType; signTxData?: SignTxData; errorMessage?: string; + onError?: () => void; } -export const ConfirmTransactionContent = ({ txType, signTxData, errorMessage }: Props): React.ReactElement => { +export const ConfirmTransactionContent = ({ txType, signTxData, onError, errorMessage }: Props): React.ReactElement => { if (!signTxData) { return ; } @@ -23,7 +24,7 @@ export const ConfirmTransactionContent = ({ txType, signTxData, errorMessage }: return ; } if (txType === Wallet.Cip30TxType.DRepRetirement) { - return ; + return ; } if (txType === Wallet.Cip30TxType.DRepUpdate) { return ; diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmDRepRetirementContainer.test.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmDRepRetirementContainer.test.tsx index bb0fe2b214..570bbdb16e 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmDRepRetirementContainer.test.tsx +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmDRepRetirementContainer.test.tsx @@ -2,11 +2,21 @@ /* eslint-disable unicorn/no-null */ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable import/imports-first */ + +import { UserPromptService } from '@lib/scripts/background/services'; +import { ExposeApiProps } from '@cardano-sdk/web-extension'; + const mockUseWalletStore = jest.fn(); const t = jest.fn().mockImplementation((res) => res); const mockUseTranslation = jest.fn(() => ({ t })); const mockConfirmDRepRetirement = jest.fn(); -const mockPubDRepKeyToHash = jest.fn(); +const mockDappError = jest.fn(); +const mockUseGetOwnPubDRepKeyHash = jest.fn(); +const mockExposeApi = jest.fn((props: ExposeApiProps>) => { + let returnValue; + props.api$.forEach((v) => (returnValue = v.allowSignTx())); + return returnValue; +}); import * as React from 'react'; import { cleanup, render } from '@testing-library/react'; import { ConfirmDRepRetirementContainer } from '../ConfirmDRepRetirementContainer'; @@ -29,7 +39,6 @@ import { postHogClientMocks } from '@src/utils/mocks/test-helpers'; import { buildMockTx } from '@src/utils/mocks/tx'; import { Wallet } from '@lace/cardano'; import BigNumber from 'bignumber.js'; - const LOVELACE_VALUE = 1_000_000; const DEFAULT_DECIMALS = 2; @@ -55,6 +64,24 @@ const cardanoCoinMock = { symbol: 'cardanoCoinMockSymbol' }; +jest.mock('@cardano-sdk/web-extension', () => { + const original = jest.requireActual('@cardano-sdk/web-extension'); + return { + __esModule: true, + ...original, + exposeApi: mockExposeApi + }; +}); + +jest.mock('../hooks.ts', () => { + const original = jest.requireActual('../hooks.ts'); + return { + __esModule: true, + ...original, + useGetOwnPubDRepKeyHash: mockUseGetOwnPubDRepKeyHash + }; +}); + jest.mock('@src/stores', () => ({ ...jest.requireActual('@src/stores'), useWalletStore: mockUseWalletStore @@ -69,21 +96,21 @@ jest.mock('@lace/core', () => { }; }); -jest.mock('react-i18next', () => { - const original = jest.requireActual('react-i18next'); +jest.mock('../../DappError', () => { + const original = jest.requireActual('../../DappError'); return { __esModule: true, ...original, - useTranslation: mockUseTranslation + DappError: mockDappError }; }); -jest.mock('../utils.ts', () => { - const original = jest.requireActual('../utils.ts'); +jest.mock('react-i18next', () => { + const original = jest.requireActual('react-i18next'); return { __esModule: true, ...original, - pubDRepKeyToHash: mockPubDRepKeyToHash + useTranslation: mockUseTranslation }; }); @@ -114,6 +141,11 @@ const getWrapper = describe('Testing ConfirmDRepRetirementContainer component', () => { beforeEach(() => { mockUseWalletStore.mockReset(); + mockExposeApi.mockRestore(); + mockUseGetOwnPubDRepKeyHash.mockImplementationOnce(() => ({ + loading: false, + ownPubDRepKeyHash: hash + })); mockUseWalletStore.mockImplementation(() => ({ inMemoryWallet, walletUI: { cardanoCoin: cardanoCoinMock }, @@ -121,8 +153,8 @@ describe('Testing ConfirmDRepRetirementContainer component', () => { })); mockConfirmDRepRetirement.mockReset(); mockConfirmDRepRetirement.mockReturnValue(); - mockPubDRepKeyToHash.mockReset(); - mockPubDRepKeyToHash.mockImplementation(async () => await '123'); + mockDappError.mockReset(); + mockDappError.mockReturnValue(); mockUseTranslation.mockReset(); mockUseTranslation.mockImplementation(() => ({ t })); }); @@ -150,12 +182,14 @@ describe('Testing ConfirmDRepRetirementContainer component', () => { certificates: [certificate] }); const errorMessage = 'errorMessage'; + // eslint-disable-next-line unicorn/consistent-function-scoping + const onErrorMock = jest.fn(); test('should render ConfirmDRepRetirementContainer component with proper props', async () => { let queryByTestId: any; await act(async () => { ({ queryByTestId } = render( - , + , { wrapper: getWrapper() } @@ -186,13 +220,14 @@ describe('Testing ConfirmDRepRetirementContainer component', () => { }); test('should render ConfirmDRepRetirementContainer component with proper error for own retirement', async () => { - mockPubDRepKeyToHash.mockReset(); - mockPubDRepKeyToHash.mockImplementation(async (_hash) => await _hash); let queryByTestId: any; await act(async () => { - ({ queryByTestId } = render(, { - wrapper: getWrapper() - })); + ({ queryByTestId } = render( + , + { + wrapper: getWrapper() + } + )); }); expect(queryByTestId('ConfirmDRepRetirementContainer')).toBeInTheDocument(); @@ -202,19 +237,21 @@ describe('Testing ConfirmDRepRetirementContainer component', () => { }); test('should render ConfirmDRepRetirementContainer component with proper error for not own retirement', async () => { - mockPubDRepKeyToHash.mockReset(); - mockPubDRepKeyToHash.mockImplementation(async () => await ''); + mockUseGetOwnPubDRepKeyHash.mockReset(); + mockUseGetOwnPubDRepKeyHash.mockImplementation(() => ({ + loading: false, + ownPubDRepKeyHash: Crypto.Hash28ByteBase16(Buffer.from('WRONG_dRepCredentialHashdRep').toString('hex')) + })); let queryByTestId: any; await act(async () => { - ({ queryByTestId } = render(, { - wrapper: getWrapper() - })); + ({ queryByTestId } = render( + , + { + wrapper: getWrapper() + } + )); }); - expect(queryByTestId('ConfirmDRepRetirementContainer')).toBeInTheDocument(); - - expect( - mockConfirmDRepRetirement.mock.calls[mockConfirmDRepRetirement.mock.calls.length - 1][0].errorMessage - ).toEqual(t('core.DRepRetirement.isNotOwnRetirement')); + expect(queryByTestId('DappError')).toBeInTheDocument(); }); }); diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmTransaction.test.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmTransaction.test.tsx index 351276597b..6be8a2a2c9 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmTransaction.test.tsx +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmTransaction.test.tsx @@ -57,6 +57,11 @@ jest.mock('@src/stores', () => ({ useWalletStore: mockUseWalletStore })); +jest.mock('@stores', () => ({ + ...jest.requireActual('@stores'), + useWalletStore: mockUseWalletStore +})); + jest.mock('@cardano-sdk/web-extension', () => { const original = jest.requireActual('@cardano-sdk/web-extension'); return { @@ -165,8 +170,6 @@ describe('Testing ConfirmTransaction component', () => { mockUseViewsFlowContext.mockReturnValue({ utils: {} }); mockConfirmTransactionContent.mockReset(); mockConfirmTransactionContent.mockImplementation(() => ); - mockConfirmTransactionContent.mockReset(); - mockConfirmTransactionContent.mockImplementation(() => ); }); afterEach(() => { @@ -217,7 +220,9 @@ describe('Testing ConfirmTransaction component', () => { expect(mockConfirmTransactionContent).toHaveBeenLastCalledWith( { txType, - signTxData + signTxData, + errorMessage: undefined, + onError: expect.any(Function) }, {} ); @@ -300,7 +305,10 @@ describe('Testing ConfirmTransaction component', () => { }); expect(queryByTestId('ConfirmTransactionContent')).toBeInTheDocument(); - expect(mockConfirmTransactionContent).toHaveBeenLastCalledWith({ errorMessage: error }, {}); + expect(mockConfirmTransactionContent).toHaveBeenLastCalledWith( + { errorMessage: error, onError: expect.any(Function), signTxData: undefined, txType: undefined }, + {} + ); expect(queryByTestId(testIds.dappTransactionConfirm).closest('button')).toHaveAttribute('disabled'); }); }); diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/hooks.test.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/hooks.test.tsx index 609255a03f..cddbbb6c29 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/hooks.test.tsx +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/hooks.test.tsx @@ -12,10 +12,11 @@ const mockGetTransactionAssetsId = jest.fn(); const mockGetAssetsInformation = jest.fn(); const mockCalculateAssetBalance = jest.fn(); const mockLovelacesToAdaString = jest.fn(); +const mockUseWalletStore = jest.fn(); import { act, cleanup } from '@testing-library/react'; import { useCreateAssetList, - useIsOwnPubDRepKey, + useGetOwnPubDRepKeyHash, useOnBeforeUnload, useSignTxData, useSignWithHardwareWallet, @@ -30,6 +31,11 @@ import { AddressListType } from '@src/views/browser-view/features/activity'; import { WalletInfo } from '@src/types'; import * as Core from '@cardano-sdk/core'; +jest.mock('@stores', () => ({ + ...jest.requireActual('@stores'), + useWalletStore: mockUseWalletStore +})); + jest.mock('@cardano-sdk/core', () => ({ ...jest.requireActual('@cardano-sdk/core'), createTxInspector: jest.fn() @@ -398,27 +404,25 @@ describe('Testing hooks', () => { removeEventListeners(); }); - test('useIsOwnPubDRepKey', async () => { + test('useGetOwnPubDRepKeyHash', async () => { const ed25519PublicKeyHexMock = 'ed25519PublicKeyHexMock'; mockPubDRepKeyToHash.mockReset(); mockPubDRepKeyToHash.mockImplementation(async (val: Wallet.Crypto.Ed25519PublicKeyHex) => await val); - - const hook = renderHook(() => - useIsOwnPubDRepKey( - async () => (await ed25519PublicKeyHexMock) as Wallet.Crypto.Ed25519PublicKeyHex, - ed25519PublicKeyHexMock as Wallet.Crypto.Hash28ByteBase16 - ) - ); - await hook.waitFor(() => { - expect(hook.result.current).toBe(true); + mockUseWalletStore.mockReset(); + mockUseWalletStore.mockReturnValue({ + inMemoryWallet: { + getPubDRepKey: jest.fn(async () => await ed25519PublicKeyHexMock) + } }); - mockPubDRepKeyToHash.mockReset(); - mockPubDRepKeyToHash.mockImplementation(async () => await 1); - hook.rerender(); + let hook: any; + await act(async () => { + hook = renderHook(() => useGetOwnPubDRepKeyHash()); + expect(hook.result.current.loading).toEqual(true); + }); await hook.waitFor(() => { - expect(hook.result.current).toBe(false); + expect(hook.result.current.ownPubDRepKeyHash).toEqual(ed25519PublicKeyHexMock); }); }); }); diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/utils.test.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/utils.test.tsx index e420b4bdc2..d998977a9f 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/utils.test.tsx +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/utils.test.tsx @@ -16,7 +16,6 @@ import { certificateInspectorFactory, votingProceduresInspector, getTxType, - getOwnRetirementMessageKey, drepIDasBech32FromHash, pubDRepKeyToHash } from '../utils'; @@ -148,10 +147,4 @@ describe('Testing utils', () => { const pubDRepKeyHex = '_pubDRepKeyHex'; expect(await pubDRepKeyToHash(pubDRepKeyHex as Wallet.Crypto.Ed25519PublicKeyHex)).toEqual(pubDRepKeyHex); }); - - test('testing getOwnRetirementMessageKey', () => { - expect(getOwnRetirementMessageKey(undefined)).toEqual(''); - expect(getOwnRetirementMessageKey(true)).toEqual('core.DRepRetirement.isOwnRetirement'); - expect(getOwnRetirementMessageKey(false)).toEqual('core.DRepRetirement.isNotOwnRetirement'); - }); }); diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/hooks.ts b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/hooks.ts index 4b3fc82e4c..602b689441 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/hooks.ts +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/hooks.ts @@ -17,6 +17,7 @@ import { getTransactionAssetsId } from '@src/stores/slices'; import { AddressListType } from '@src/views/browser-view/features/activity'; import { allowSignTx, pubDRepKeyToHash, disallowSignTx, getTxType } from './utils'; import { GetSignTxData, SignTxData } from './types'; +import { useWalletStore } from '@stores'; export const useCreateAssetList = ({ assets, @@ -163,6 +164,7 @@ export const useSignTxData = (getSignTxData: GetSignTxData): { signTxData?: Sign }) .catch((error) => { setErrorMessage(error); + // TODO: consider mocking or removing this log console.error(error); }); }, [getSignTxData, setSignTxData, setErrorMessage]); @@ -262,22 +264,27 @@ export const useOnBeforeUnload = (callBack: () => void): void => { }, [callBack]); }; -export const useIsOwnPubDRepKey = ( - getOwnPubDRepKey: () => Promise, - drepHash: Wallet.Crypto.Hash28ByteBase16 -): boolean => { - const [isOwnDRepKey, setIsOwnDRepKey] = useState(); +type UseGetOwnPubDRepKeyHash = { + loading: boolean; + ownPubDRepKeyHash: Wallet.Crypto.Hash28ByteBase16; +}; + +export const useGetOwnPubDRepKeyHash = (): UseGetOwnPubDRepKeyHash => { + const [ownPubDRepKeyHash, setOwnPubDRepKeyHash] = useState(); + const { inMemoryWallet } = useWalletStore(); useEffect(() => { + if (!inMemoryWallet) return; const get = async () => { - const ownPubDRepKey = await getOwnPubDRepKey(); + const ownPubDRepKey = await inMemoryWallet.getPubDRepKey(); const ownDRepKeyHash = await pubDRepKeyToHash(ownPubDRepKey); - setIsOwnDRepKey(drepHash === ownDRepKeyHash); + setOwnPubDRepKeyHash(ownDRepKeyHash); }; get(); - }, [getOwnPubDRepKey, drepHash]); + }, [inMemoryWallet]); - return isOwnDRepKey; + // TODO consider using Zustand or at least some common abstraction e.g. https://github.com/streamich/react-use/blob/master/src/useAsync.ts + return { loading: ownPubDRepKeyHash === undefined, ownPubDRepKeyHash }; }; diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/utils.ts b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/utils.ts index e17be60297..681e9a86cc 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/utils.ts +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/utils.ts @@ -120,10 +120,3 @@ export const pubDRepKeyToHash = async ( const drepKeyHex = (await pubDRepKey.hash()).hex(); return Wallet.Crypto.Hash28ByteBase16.fromEd25519KeyHashHex(drepKeyHex); }; - -export const getOwnRetirementMessageKey = (isOwnRetirement: boolean | undefined): string => { - if (isOwnRetirement === undefined) { - return ''; - } - return isOwnRetirement ? 'core.DRepRetirement.isOwnRetirement' : 'core.DRepRetirement.isNotOwnRetirement'; -}; diff --git a/apps/browser-extension-wallet/src/features/dapp/components/index.ts b/apps/browser-extension-wallet/src/features/dapp/components/index.ts index 4d3125556d..c96197a717 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/index.ts +++ b/apps/browser-extension-wallet/src/features/dapp/components/index.ts @@ -2,7 +2,6 @@ export * from './Connect'; export * from './SignTxFlowContainer'; export * from './DappTransactionSuccess'; export * from './DappTransactionFail'; -export * from './NoWallet'; export * from './ConfirmData'; export * from './BetaPill'; export * from './SignDataFlowContainer'; diff --git a/apps/browser-extension-wallet/src/lib/translations/en.json b/apps/browser-extension-wallet/src/lib/translations/en.json index 8bfd3ccbe9..4f53c319ed 100644 --- a/apps/browser-extension-wallet/src/lib/translations/en.json +++ b/apps/browser-extension-wallet/src/lib/translations/en.json @@ -328,7 +328,7 @@ "copyHandle": "Copy handle" }, "dapp": { - "nowallet.btn": "Create or restore a wallet", + "dappErrorPage.closeButton": "Cancel", "connect.header": "Authorize DApp", "connect.btn.accept": "Authorize", "connect.btn.cancel": "Cancel", @@ -367,6 +367,7 @@ "delete.confirm": "Disconnect DApp", "noWallet.heading": "You don't have a wallet right now", "noWallet.description": "You'll need to create or restore a wallet to connect to a dApp or make a transaction.", + "noWallet.closeButton": "Create or restore a wallet", "educationBanner.title": "DApp Guide", "betaModal": { "header": "DApp connector is now in Beta", @@ -1168,8 +1169,11 @@ "metadata": "Metadata", "drepId": "DRep ID", "depositReturned": "Deposit returned", - "isOwnRetirement": "This is your DRep retirement.", - "isNotOwnRetirement": "The presented DRepID does not match your wallet's DRepID." + "drepIdMismatchScreen": { + "title": "DRep ID mismatch", + "description": "The presented DRepID does not match your wallet's DRepID", + "cancel": "Cancel" + } }, "DRepUpdate": { "title": "Confirm DRep Update", diff --git a/apps/browser-extension-wallet/src/routes/DappConnectorView.tsx b/apps/browser-extension-wallet/src/routes/DappConnectorView.tsx index e223e57d02..5553484572 100644 --- a/apps/browser-extension-wallet/src/routes/DappConnectorView.tsx +++ b/apps/browser-extension-wallet/src/routes/DappConnectorView.tsx @@ -11,7 +11,6 @@ import { Connect as DappConnect, SignTxFlowContainer, SignDataFlowContainer, - NoWallet, DappTransactionSuccess, DappTransactionFail, DappCollateralContainer @@ -23,6 +22,9 @@ import { lockWalletSelector } from '@src/features/unlock-wallet/selectors'; import { useAppSettingsContext } from '@providers'; import dayjs from 'dayjs'; import duration from 'dayjs/plugin/duration'; +import { DappError } from '@src/features/dapp/components/DappError'; +import { tabs } from 'webextension-polyfill'; +import { useTranslation } from 'react-i18next'; dayjs.extend(duration); @@ -34,6 +36,7 @@ const isLastValidationExpired = (lastVerification: string, frequency: string): b // TODO: unify providers and logic to load wallet and such for popup, dapp and browser view in one place [LW-5341] export const DappConnectorView = (): React.ReactElement => { + const { t } = useTranslation(); const [{ lastMnemonicVerification, mnemonicVerificationFrequency }] = useAppSettingsContext(); const { inMemoryWallet, keyAgentData, currentChain, walletInfo, setKeyAgentData, initialHdDiscoveryCompleted } = useWalletStore(); @@ -76,7 +79,19 @@ export const DappConnectorView = (): React.ReactElement => { if (hasNoAvailableWallet) { return ( - + { + tabs.create({ url: 'app.html#/setup' }); + }} + containerTestId="no-wallet-container" + imageTestId="no-wallet-image" + titleTestId="no-wallet-heading" + descriptionTestId="no-wallet-description" + closeButtonTestId="create-or-restore-wallet-btn" + /> ); } @@ -97,7 +112,6 @@ export const DappConnectorView = (): React.ReactElement => { return ( -