From c9af7bc77daefd612ff3899418d2235748431500 Mon Sep 17 00:00:00 2001 From: Janusz Janus Date: Mon, 27 Nov 2023 09:47:31 +0100 Subject: [PATCH 01/16] feat(staking): maintenance 24 Nov 2023 (#754) * feat(staking): maintenance 24 Nov 2023 --- .../src/features/EmptyStatesExtended.feature | 1 - .../src/features/EmptyStatesPopup.feature | 1 - .../src/features/FullExperiencePopup.feature | 1 - .../MultiDelegationPageExtended.feature | 12 ---- .../features/MultiDelegationPagePopup.feature | 1 - ...tidelegationDelegatedFundsExtended.feature | 2 - ...MultidelegationDelegatedFundsPopup.feature | 11 ++-- .../features/NavigationMainExtended.feature | 15 +++-- .../src/features/NavigationMainPopup.feature | 1 - ...elegationSwitchingPoolsExtendedE2E.feature | 1 - .../e2e-tests/src/hooks/beforeTagHooks.ts | 63 +++++++++++-------- 11 files changed, 47 insertions(+), 62 deletions(-) diff --git a/packages/e2e-tests/src/features/EmptyStatesExtended.feature b/packages/e2e-tests/src/features/EmptyStatesExtended.feature index b1437d70ee..273e140f50 100644 --- a/packages/e2e-tests/src/features/EmptyStatesExtended.feature +++ b/packages/e2e-tests/src/features/EmptyStatesExtended.feature @@ -29,7 +29,6 @@ Feature: Empty states @LW-8447 Scenario: Extended View - Staking empty state - When I disable showing Multidelegation beta banner And I navigate to Staking extended page Then I see empty state banner for Staking page in extended mode When I click "Copy" button on empty state banner diff --git a/packages/e2e-tests/src/features/EmptyStatesPopup.feature b/packages/e2e-tests/src/features/EmptyStatesPopup.feature index c58982208f..daa1864c58 100644 --- a/packages/e2e-tests/src/features/EmptyStatesPopup.feature +++ b/packages/e2e-tests/src/features/EmptyStatesPopup.feature @@ -28,7 +28,6 @@ Feature: Empty states @LW-8470 Scenario: Popup View - Staking empty state - When I disable showing Multidelegation beta banner And I navigate to Staking popup page Then I see empty state banner for Staking page in popup mode When I click "Copy" button on empty state banner diff --git a/packages/e2e-tests/src/features/FullExperiencePopup.feature b/packages/e2e-tests/src/features/FullExperiencePopup.feature index 9cbc58ea86..2209cd4938 100644 --- a/packages/e2e-tests/src/features/FullExperiencePopup.feature +++ b/packages/e2e-tests/src/features/FullExperiencePopup.feature @@ -12,7 +12,6 @@ Feature: Full experience - popup view @LW-3446 Scenario Outline: Popup View - opened - "Expand" button click - Given I disable showing Multidelegation beta banner And I am on popup page When I click on "Expand" button Then the page is displayed on a new tab in extended view diff --git a/packages/e2e-tests/src/features/MultiDelegationPageExtended.feature b/packages/e2e-tests/src/features/MultiDelegationPageExtended.feature index ca562f6571..1b571b3a7c 100644 --- a/packages/e2e-tests/src/features/MultiDelegationPageExtended.feature +++ b/packages/e2e-tests/src/features/MultiDelegationPageExtended.feature @@ -7,7 +7,6 @@ Feature: Staking Page - Extended View @LW-8931 @Testnet Scenario: Extended View - Start Staking component Given I save token: "Cardano" balance - And I disable showing Multidelegation beta banner When I navigate to Staking extended page Then I see Start Staking page in extended mode @@ -23,14 +22,12 @@ Feature: Staking Page - Extended View @LW-8449 @Testnet @Mainnet Scenario: Extended View - Staking search control is displayed with appropriate content - Given I disable showing Multidelegation beta banner When I navigate to Staking extended page And I click Browse pools tab Then I see the stake pool search control with appropriate content @LW-8448 @Testnet Scenario Outline: Extended View - Stake pool search for "" returns the expected number of results with appropriate content - Given I disable showing Multidelegation beta banner When I navigate to Staking extended page And I click Browse pools tab And I input "" into stake pool search bar @@ -49,7 +46,6 @@ Feature: Staking Page - Extended View @LW-8448 @Mainnet Scenario Outline: Extended View - Stake pool search for "" returns the expected number of results with appropriate content - Given I disable showing Multidelegation beta banner When I navigate to Staking extended page And I click Browse pools tab And I input "" into stake pool search bar @@ -69,12 +65,10 @@ Feature: Staking Page - Extended View @LW-8466 @Testnet @Mainnet Scenario: Extended View - "About staking" widget Given I am on Staking extended page - And I close Multi-delegation beta modal Then I see "About staking" widget with all relevant items @LW-8465 @Testnet @Mainnet Scenario Outline: Extended View - "About staking" widget item click - - Given I disable showing Multidelegation beta banner And I am on Staking extended page When I click on a widget item with subtitle: "" Then I see a "" article with title "" @@ -87,13 +81,11 @@ Feature: Staking Page - Extended View @LW-8469 @Testnet @Mainnet Scenario: Extended View - Network info component is present with expected content - Given I disable showing Multidelegation beta banner When I navigate to Staking extended page Then I see the Network Info component with the expected content @LW-8499 @Testnet @Mainnet Scenario Outline: Extended View - Staking - Show tooltip for column names in browse pools section - Given I disable showing Multidelegation beta banner When I navigate to Staking extended page And I click Browse pools tab When I hover over "" column name in stake pool list @@ -105,7 +97,6 @@ Feature: Staking Page - Extended View @LW-8637 @Testnet @Mainnet Scenario: Extended View - Staking password screen details - Given I disable showing Multidelegation beta banner When I navigate to Staking extended page And I click Overview tab And I click Browse pools tab @@ -118,7 +109,6 @@ Feature: Staking Page - Extended View @LW-8445 @Testnet Scenario: Extended View - Selecting stakepool from list opens drawer with appropriate details - Given I disable showing Multidelegation beta banner And I am on Staking extended page And I click Browse pools tab And I input "ADA Capital" into stake pool search bar @@ -127,7 +117,6 @@ Feature: Staking Page - Extended View @LW-8438 @Testnet Scenario: Extended View - Staking - Stakepool details drawer - Close drawer - Given I disable showing Multidelegation beta banner And I am on Staking extended page And I click Browse pools tab And I input "ADA Capital" into stake pool search bar @@ -137,7 +126,6 @@ Feature: Staking Page - Extended View @LW-8463 @Testnet @Mainnet Scenario: Extended View - Stake pool list item - Given I disable showing Multidelegation beta banner And I am on Staking extended page And I click Browse pools tab And I wait for stake pool list to be populated diff --git a/packages/e2e-tests/src/features/MultiDelegationPagePopup.feature b/packages/e2e-tests/src/features/MultiDelegationPagePopup.feature index da12730b12..f0810fafb1 100644 --- a/packages/e2e-tests/src/features/MultiDelegationPagePopup.feature +++ b/packages/e2e-tests/src/features/MultiDelegationPagePopup.feature @@ -7,7 +7,6 @@ Feature: Staking Page - Popup View @LW-8933 @Testnet Scenario: Popup View - Start Staking component Given I save token: "Cardano" balance - And I disable showing Multidelegation beta banner When I navigate to Staking popup page Then I see Start Staking page in popup mode diff --git a/packages/e2e-tests/src/features/MultidelegationDelegatedFundsExtended.feature b/packages/e2e-tests/src/features/MultidelegationDelegatedFundsExtended.feature index 422a2d405b..000d5aba03 100644 --- a/packages/e2e-tests/src/features/MultidelegationDelegatedFundsExtended.feature +++ b/packages/e2e-tests/src/features/MultidelegationDelegatedFundsExtended.feature @@ -6,7 +6,6 @@ Feature: Staking Page - Extended View @LW-8436 @LW-8439 @LW-8440 @LW-8598 Scenario Outline: Extended View - Staking - Close drawer - Given I disable showing Multidelegation beta banner When I navigate to Staking extended page And I click Browse pools tab And I pick "1" pools for delegation from browse pools view: "ADA Capital" @@ -28,7 +27,6 @@ Feature: Staking Page - Extended View @LW-8450 Scenario Outline: Extended View - Staking - Hover over currently staking element: - Given I disable showing Multidelegation beta banner And I navigate to Staking extended page When I hover over in currently staking component Then I see tooltip for element in currently staking component diff --git a/packages/e2e-tests/src/features/MultidelegationDelegatedFundsPopup.feature b/packages/e2e-tests/src/features/MultidelegationDelegatedFundsPopup.feature index a397fdee1b..a138ee1ae7 100644 --- a/packages/e2e-tests/src/features/MultidelegationDelegatedFundsPopup.feature +++ b/packages/e2e-tests/src/features/MultidelegationDelegatedFundsPopup.feature @@ -7,15 +7,14 @@ Feature: Staking Page - Popup View @LW-8330 Scenario Outline: Popup View - Delegation card displays correct data Given I open wallet: "" in: popup mode - And I disable showing Multidelegation beta banner And I disable showing Multidelegation persistence banner When I navigate to Staking popup page Then I see Delegation title displayed for multidelegation And I see Delegation card displaying correct data Examples: - | walletName | - | MultidelegationDelegatedSingle | - | MultidelegationDelegatedMulti | + | walletName | + | MultidelegationDelegatedSingle | + | MultidelegationDelegatedMulti | @LW-8338 Scenario Outline: Popup View - Delegated pools cards are present @@ -31,9 +30,7 @@ Feature: Staking Page - Popup View @LW-8480 Scenario Outline: Popup View - Staking - Hover over currently staking element: - Given Lace is ready for test - And I disable showing Multidelegation beta banner - And I navigate to Staking popup page + Given I navigate to Staking popup page When I hover over in currently staking component Then I see tooltip for element in currently staking component Examples: diff --git a/packages/e2e-tests/src/features/NavigationMainExtended.feature b/packages/e2e-tests/src/features/NavigationMainExtended.feature index a12cae4da7..d9d7cd27ed 100644 --- a/packages/e2e-tests/src/features/NavigationMainExtended.feature +++ b/packages/e2e-tests/src/features/NavigationMainExtended.feature @@ -3,7 +3,6 @@ Feature: Main Navigation - Extended view Background: Given Lace is ready for test - And I disable showing Multidelegation beta banner @LW-2692 @Smoke Scenario: Extended view - Main navigation is displayed with all items @@ -38,13 +37,13 @@ Feature: Main Navigation - Extended view When I click on the logo icon Then I see Tokens counter with total number of tokens displayed Examples: - | section | validateIfSectionIsDisplayed | - | Tokens | I see Tokens counter with total number of tokens displayed | - | NFTs | I see NFTs counter with total number of NFTs displayed | - | Transactions | Transactions section is displayed | - | Staking | I see Delegation title displayed for multidelegation | - | Settings | I see settings page | - | Address Book | I see address book title | + | section | validateIfSectionIsDisplayed | + | Tokens | I see Tokens counter with total number of tokens displayed | + | NFTs | I see NFTs counter with total number of NFTs displayed | + | Transactions | Transactions section is displayed | + | Staking | I see Delegation title displayed for multidelegation | + | Settings | I see settings page | + | Address Book | I see address book title | @LW-6662 Scenario Outline: Extended view - Main Navigation - Right side panel not displayed in
section diff --git a/packages/e2e-tests/src/features/NavigationMainPopup.feature b/packages/e2e-tests/src/features/NavigationMainPopup.feature index 9aba459aaa..86acaab892 100644 --- a/packages/e2e-tests/src/features/NavigationMainPopup.feature +++ b/packages/e2e-tests/src/features/NavigationMainPopup.feature @@ -32,7 +32,6 @@ Feature: Main Navigation - Popup View @LW-2610 Scenario Outline: Extended view - Click Lace logo -
And Wallet is synced - And I disable showing Multidelegation beta banner And I navigate to
popup page And When I click on the logo icon diff --git a/packages/e2e-tests/src/features/e2e/MultidelegationSwitchingPoolsExtendedE2E.feature b/packages/e2e-tests/src/features/e2e/MultidelegationSwitchingPoolsExtendedE2E.feature index e30451b57e..f441fc755b 100644 --- a/packages/e2e-tests/src/features/e2e/MultidelegationSwitchingPoolsExtendedE2E.feature +++ b/packages/e2e-tests/src/features/e2e/MultidelegationSwitchingPoolsExtendedE2E.feature @@ -3,7 +3,6 @@ Feature: Staking Page - Switching pools - Extended Browser View - E2E Background: Given Wallet is synced - And I disable showing Multidelegation beta banner And I navigate to Staking extended page @LW-7819 @Testnet @Pending diff --git a/packages/e2e-tests/src/hooks/beforeTagHooks.ts b/packages/e2e-tests/src/hooks/beforeTagHooks.ts index b58e8583e9..760d72fdd3 100644 --- a/packages/e2e-tests/src/hooks/beforeTagHooks.ts +++ b/packages/e2e-tests/src/hooks/beforeTagHooks.ts @@ -30,22 +30,31 @@ Before( { tags: '@AddressBook-extended or @Transactions-Extended or @Tokens-extended or @Staking-Extended or @LockWallet-extended or @Top-Navigation-Extended or @NFTs-Extended or @NFT-Folders-Extended or @SendTx-Bundles-Extended or @SendTx-Simple-Extended or @MainNavigation-Extended or @Send-Transaction-Metadata-Extended or @Settings-Extended or @DAppConnector or @DAppConnector-Extended' }, - async () => await extendedViewWalletInitialization() + async () => { + await extendedViewWalletInitialization(); + await localStorageInitializer.disableShowingMultidelegationBetaBanner(); + } ); Before( { tags: '@Tokens-popup or @Transactions-Popup or @Staking-Popup or @LockWallet-popup or @Top-Navigation-Popup or @AddressBook-popup or @Common-Popup or @SendTx-Simple-Popup or @MainNavigation-Popup or @Settings-Popup or @NFTs-Popup or @NFT-Folders-Popup or @Send-Transaction-Metadata-Popup or @ForgotPassword or @DAppConnector-Popup' }, - async () => await popupViewWalletInitialization() + async () => { + await popupViewWalletInitialization(); + await localStorageInitializer.disableShowingMultidelegationBetaBanner(); + } ); -Before( - { tags: '@EmptyStates-Extended' }, - async () => await extendedViewWalletInitialization(TestWalletName.TAWalletNoFunds) -); +Before({ tags: '@EmptyStates-Extended' }, async () => { + await extendedViewWalletInitialization(TestWalletName.TAWalletNoFunds); + await localStorageInitializer.disableShowingMultidelegationBetaBanner(); +}); -Before({ tags: '@EmptyStates-Popup' }, async () => await popupViewWalletInitialization(TestWalletName.TAWalletNoFunds)); +Before({ tags: '@EmptyStates-Popup' }, async () => { + await popupViewWalletInitialization(TestWalletName.TAWalletNoFunds); + await localStorageInitializer.disableShowingMultidelegationBetaBanner(); +}); Before( { tags: '@SendTx-MultipleSelection-Popup' }, @@ -104,15 +113,15 @@ Before( async () => await extendedViewWalletInitialization(TestWalletName.TAWalletDelegatedFunds) ); -Before( - { tags: '@Staking-NonDelegatedFunds-Extended' }, - async () => await extendedViewWalletInitialization(TestWalletName.TAWalletNonDelegatedFunds) -); +Before({ tags: '@Staking-NonDelegatedFunds-Extended' }, async () => { + await extendedViewWalletInitialization(TestWalletName.TAWalletNonDelegatedFunds); + await localStorageInitializer.disableShowingMultidelegationBetaBanner(); +}); -Before( - { tags: '@Staking-NonDelegatedFunds-Popup' }, - async () => await popupViewWalletInitialization(TestWalletName.TAWalletNonDelegatedFunds) -); +Before({ tags: '@Staking-NonDelegatedFunds-Popup' }, async () => { + await popupViewWalletInitialization(TestWalletName.TAWalletNonDelegatedFunds); + await localStorageInitializer.disableShowingMultidelegationBetaBanner(); +}); Before( { tags: '@Staking-SwitchingPools-Extended-E2E' }, @@ -126,10 +135,10 @@ Before( Before({ tags: '@AdaHandle-popup' }, async () => await popupViewWalletInitialization(TestWalletName.WalletAdaHandle)); -Before( - { tags: '@Multidelegation-SwitchingPools-Extended-E2E' }, - async () => await extendedViewWalletInitialization(TestWalletName.WalletMultidelegationSwitchPoolsE2E) -); +Before({ tags: '@Multidelegation-SwitchingPools-Extended-E2E' }, async () => { + await extendedViewWalletInitialization(TestWalletName.WalletMultidelegationSwitchPoolsE2E); + await localStorageInitializer.disableShowingMultidelegationBetaBanner(); +}); Before( { tags: '@HdWallet-extended' }, @@ -141,12 +150,12 @@ Before( async () => await extendedViewWalletInitialization(TestWalletName.WalletSendNftHdWalletE2E) ); -Before( - { tags: '@Multidelegation-DelegatedFunds-Popup' }, - async () => await popupViewWalletInitialization(TestWalletName.MultidelegationDelegatedSingle) -); +Before({ tags: '@Multidelegation-DelegatedFunds-Popup' }, async () => { + await popupViewWalletInitialization(TestWalletName.MultidelegationDelegatedSingle); + await localStorageInitializer.disableShowingMultidelegationBetaBanner(); +}); -Before( - { tags: '@Multidelegation-DelegatedFunds-Extended' }, - async () => await extendedViewWalletInitialization(TestWalletName.MultidelegationDelegatedSingle) -); +Before({ tags: '@Multidelegation-DelegatedFunds-Extended' }, async () => { + await extendedViewWalletInitialization(TestWalletName.MultidelegationDelegatedSingle); + await localStorageInitializer.disableShowingMultidelegationBetaBanner(); +}); From d4415a2500a35d999869122da15de7dba9a9c406 Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 27 Nov 2023 10:24:59 -0300 Subject: [PATCH 02/16] [LW-9151] Edit account name component (#746) * feat(ui): input component * feat(extension): edit account drawer * feat(extension): add unit test * feat: rename input component * feat(ui): use pseudo parameters * feat(ui): input width * feat(ui): input disabled style * feat(extension): add ui package * feat: update lock file * feat(ui): remove redundant styles --- apps/browser-extension-wallet/package.json | 1 + .../EditAccount/EditAccountDrawer.test.tsx | 57 +++++++++++ .../EditAccount/EditAccountDrawer.tsx | 60 ++++++++++++ .../account/components/EditAccount/hooks.ts | 11 +++ .../src/lib/translations/en.json | 9 ++ packages/ui/src/design-system/index.ts | 1 + .../ui/src/design-system/text-box/index.ts | 2 + .../text-box/text-box.component.tsx | 72 ++++++++++++++ .../design-system/text-box/text-box.css.ts | 57 +++++++++++ .../text-box/text-box.stories.tsx | 96 +++++++++++++++++++ yarn.lock | 1 + 11 files changed, 367 insertions(+) create mode 100644 apps/browser-extension-wallet/src/features/account/components/EditAccount/EditAccountDrawer.test.tsx create mode 100644 apps/browser-extension-wallet/src/features/account/components/EditAccount/EditAccountDrawer.tsx create mode 100644 apps/browser-extension-wallet/src/features/account/components/EditAccount/hooks.ts create mode 100644 packages/ui/src/design-system/text-box/index.ts create mode 100644 packages/ui/src/design-system/text-box/text-box.component.tsx create mode 100644 packages/ui/src/design-system/text-box/text-box.css.ts create mode 100644 packages/ui/src/design-system/text-box/text-box.stories.tsx diff --git a/apps/browser-extension-wallet/package.json b/apps/browser-extension-wallet/package.json index 7b93f63baf..22f390146c 100644 --- a/apps/browser-extension-wallet/package.json +++ b/apps/browser-extension-wallet/package.json @@ -54,6 +54,7 @@ "@lace/common": "0.1.0", "@lace/core": "0.1.0", "@lace/staking": "0.1.0", + "@lace/ui": "^0.1.0", "@react-rxjs/core": "^0.9.8", "@react-rxjs/utils": "^0.9.5", "@vespaiach/axios-fetch-adapter": "^0.3.0", diff --git a/apps/browser-extension-wallet/src/features/account/components/EditAccount/EditAccountDrawer.test.tsx b/apps/browser-extension-wallet/src/features/account/components/EditAccount/EditAccountDrawer.test.tsx new file mode 100644 index 0000000000..7f06c9d6d2 --- /dev/null +++ b/apps/browser-extension-wallet/src/features/account/components/EditAccount/EditAccountDrawer.test.tsx @@ -0,0 +1,57 @@ +import React from 'react'; +import { render, fireEvent, screen } from '@testing-library/react'; +import { EditAccountDrawer } from './EditAccountDrawer'; +import '@testing-library/jest-dom'; + +jest.mock('react-i18next', () => ({ + useTranslation: () => ({ t: jest.fn() }) +})); + +describe('EditAccountDrawer', () => { + const onSaveMock = jest.fn(); + const hideMock = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('displays default account name', () => { + render(); + + expect(screen.getByTestId('edit-account')).toBeInTheDocument(); + expect(screen.getByTestId('drawer-navigation-title')).toHaveTextContent('Account #1'); + expect(screen.getByTestId('edit-account-name-input')).toHaveValue(''); + expect(screen.getByTestId('edit-account-save-btn')).toBeDisabled(); + }); + + it('displays correct account name', () => { + render(); + + expect(screen.getByTestId('edit-account')).toBeInTheDocument(); + expect(screen.getByTestId('drawer-navigation-title')).toHaveTextContent('Test Account'); + expect(screen.getByTestId('edit-account-name-input')).toHaveValue('Test Account'); + expect(screen.getByTestId('edit-account-save-btn')).toBeDisabled(); + }); + + it('updates input value on change and enables save button', () => { + render(); + + const input = screen.getByTestId('edit-account-name-input'); + + fireEvent.change(input, { target: { value: 'New Account Name' } }); + fireEvent.click(screen.getByTestId('edit-account-save-btn')); + + expect(input).toHaveValue('New Account Name'); + expect(screen.getByTestId('drawer-navigation-title')).toHaveTextContent('Test Account'); + expect(screen.getByTestId('edit-account-save-btn')).toBeEnabled(); + expect(onSaveMock).toHaveBeenCalledWith('New Account Name'); + }); + + it('calls hide function when Cancel button is clicked', () => { + render(); + + fireEvent.click(screen.getByTestId('edit-account-cancel-btn')); + + expect(hideMock).toHaveBeenCalled(); + }); +}); diff --git a/apps/browser-extension-wallet/src/features/account/components/EditAccount/EditAccountDrawer.tsx b/apps/browser-extension-wallet/src/features/account/components/EditAccount/EditAccountDrawer.tsx new file mode 100644 index 0000000000..1e1f27b568 --- /dev/null +++ b/apps/browser-extension-wallet/src/features/account/components/EditAccount/EditAccountDrawer.tsx @@ -0,0 +1,60 @@ +import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Box, Flex, Button, Text, TextBox } from '@lace/ui'; +import { Drawer, DrawerNavigation } from '@lace/common'; + +export type Props = { + onSave: (name: string) => void; + visible: boolean; + hide: () => void; + name: string; + index: string; +}; + +export const EditAccountDrawer = ({ name, index, visible, onSave, hide }: Props): React.ReactElement => { + const { t } = useTranslation(); + const [currentName, setCurrentName] = useState(name); + + return ( + } + footer={ + + + onSave(currentName)} + data-testid="edit-account-save-btn" + label={t('account.edit.footer.save')} + /> + + + + } + > +
+ + {t('account.edit.title')} + + + {t('account.edit.subtitle')} + + setCurrentName(e.target.value)} + /> +
+
+ ); +}; diff --git a/apps/browser-extension-wallet/src/features/account/components/EditAccount/hooks.ts b/apps/browser-extension-wallet/src/features/account/components/EditAccount/hooks.ts new file mode 100644 index 0000000000..f17ee3d5f8 --- /dev/null +++ b/apps/browser-extension-wallet/src/features/account/components/EditAccount/hooks.ts @@ -0,0 +1,11 @@ +import { useState } from 'react'; + +export const useEditAccountDrawer = (): { isOpen: boolean; open: () => void; hide: () => void } => { + const [visible, setVisible] = useState(false); + + return { + isOpen: visible, + open: () => setVisible(true), + hide: () => setVisible(false) + }; +}; diff --git a/apps/browser-extension-wallet/src/lib/translations/en.json b/apps/browser-extension-wallet/src/lib/translations/en.json index a383db68f8..6b60b78510 100644 --- a/apps/browser-extension-wallet/src/lib/translations/en.json +++ b/apps/browser-extension-wallet/src/lib/translations/en.json @@ -1313,5 +1313,14 @@ "errorText": "Wallet failed to sync", "successText": "Wallet synced successfully" } + }, + "account": { + "edit": { + "title": "Edit account name", + "subtitle": "Choose a name to identify your account", + "input.label": "Account name", + "footer.save": "Save", + "footer.cancel": "Cancel" + } } } diff --git a/packages/ui/src/design-system/index.ts b/packages/ui/src/design-system/index.ts index f865c3f124..e4c37ec074 100644 --- a/packages/ui/src/design-system/index.ts +++ b/packages/ui/src/design-system/index.ts @@ -27,3 +27,4 @@ export { PasswordBox } from './password-box'; export { Metadata } from './metadata'; export { TextLink } from './text-link'; export * as ProfileDropdown from './profile-dropdown'; +export { TextBox } from './text-box'; diff --git a/packages/ui/src/design-system/text-box/index.ts b/packages/ui/src/design-system/text-box/index.ts new file mode 100644 index 0000000000..65e82881ee --- /dev/null +++ b/packages/ui/src/design-system/text-box/index.ts @@ -0,0 +1,2 @@ +export { TextBox } from './text-box.component'; +export type { TextBoxProps } from './text-box.component'; diff --git a/packages/ui/src/design-system/text-box/text-box.component.tsx b/packages/ui/src/design-system/text-box/text-box.component.tsx new file mode 100644 index 0000000000..4103d53e4a --- /dev/null +++ b/packages/ui/src/design-system/text-box/text-box.component.tsx @@ -0,0 +1,72 @@ +import React from 'react'; + +import * as Form from '@radix-ui/react-form'; +import cn from 'classnames'; + +import { Flex } from '../flex'; +import * as Typography from '../typography'; + +import * as cx from './text-box.css'; + +export interface TextBoxProps extends Form.FormControlProps { + required?: boolean; + disabled?: boolean; + id?: string; + label: string; + name?: string; + value: string; + errorMessage?: string; + onChange?: (event: Readonly>) => void; + containerClassName?: string; + containerStyle?: React.CSSProperties; + 'data-testid'?: string; +} + +export const TextBox = ({ + required = false, + disabled = false, + id, + label, + name, + value, + errorMessage = '', + containerClassName = '', + onChange, + containerStyle, + ...rest +}: Readonly): JSX.Element => { + return ( + + + + + + + {label} + + + {errorMessage && ( + + {errorMessage} + + )} + + ); +}; diff --git a/packages/ui/src/design-system/text-box/text-box.css.ts b/packages/ui/src/design-system/text-box/text-box.css.ts new file mode 100644 index 0000000000..9743bda7c3 --- /dev/null +++ b/packages/ui/src/design-system/text-box/text-box.css.ts @@ -0,0 +1,57 @@ +import { globalStyle, vars, style } from '../../design-tokens'; + +export const container = style({ + background: vars.colors.$input_container_bgColor, + paddingTop: vars.spacing.$12, + maxHeight: vars.spacing.$52, + borderRadius: vars.radius.$medium, + width: 'auto', + fontWeight: vars.fontWeights.$semibold, + fontFamily: vars.fontFamily.$nova, +}); + +export const input = style({ + width: 'calc(100% - 90px)', + fontSize: vars.fontSizes.$18, + padding: `${vars.spacing.$10} ${vars.spacing.$20}`, + border: 'none', + outline: 'none', + background: 'transparent', + color: vars.colors.$input_value_color, +}); + +export const label = style({ + position: 'relative', + display: 'block', + left: vars.spacing.$20, + top: '-40px', + transitionDuration: '0.2s', + pointerEvents: 'none', + color: vars.colors.$input_label_color, + fontSize: vars.fontSizes.$18, +}); + +export const errorMessage = style({ + color: vars.colors.$input_error_message_color, + marginLeft: vars.spacing.$20, +}); + +globalStyle( + `${input}:focus + ${label}, ${input}:not(:placeholder-shown) + ${label}`, + { + top: '-52px', + fontSize: vars.fontSizes.$12, + }, +); + +globalStyle(`${container}:has(${input}:disabled)`, { + opacity: vars.opacities.$0_5, +}); + +globalStyle(`${container}:has(${input}:hover:not(:disabled))`, { + outline: `2px solid ${vars.colors.$input_container_hover_outline_color}`, +}); + +globalStyle(`${container}:has(${input}:focus)`, { + outline: `3px solid ${vars.colors.$input_container_focused_outline_color}`, +}); diff --git a/packages/ui/src/design-system/text-box/text-box.stories.tsx b/packages/ui/src/design-system/text-box/text-box.stories.tsx new file mode 100644 index 0000000000..eb5236347c --- /dev/null +++ b/packages/ui/src/design-system/text-box/text-box.stories.tsx @@ -0,0 +1,96 @@ +import React from 'react'; + +import type { Meta } from '@storybook/react'; + +import { LocalThemeProvider, ThemeColorScheme } from '../../design-tokens'; +import { page, Section, Variants } from '../decorators'; +import { Divider } from '../divider'; +import { Flex } from '../flex'; +import { Cell, Grid } from '../grid'; + +import { TextBox } from './text-box.component'; + +export default { + title: 'Input Fields/Text Box', + component: TextBox, + decorators: [ + page({ + title: 'Text Box', + subtitle: 'A text input', + }), + ], +} as Meta; + +const MainComponents = (): JSX.Element => ( + + + + + + + + + + + + + + + + + + + + +); + +export const Overview = (): JSX.Element => { + const [value, setValue] = React.useState(''); + + return ( + + +
+ + { + setValue(event.target.value); + }} + /> + +
+ + + +
+ + + + + + + + +
+
+
+ ); +}; + +Overview.parameters = { + pseudo: { + hover: '#hover', + focus: '#focus', + }, +}; diff --git a/yarn.lock b/yarn.lock index fb109c846a..fda8b47d21 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7853,6 +7853,7 @@ __metadata: "@lace/common": 0.1.0 "@lace/core": 0.1.0 "@lace/staking": 0.1.0 + "@lace/ui": ^0.1.0 "@react-rxjs/core": ^0.9.8 "@react-rxjs/utils": ^0.9.5 "@types/dotenv-webpack": 7.0.3 From 8ab805f18da100c3e8e71cf430e6572630ad4139 Mon Sep 17 00:00:00 2001 From: Leonel Gobbi <57540576+lgobbi-atix@users.noreply.github.com> Date: Tue, 28 Nov 2023 10:38:12 -0300 Subject: [PATCH 03/16] feat(extension): [LW-5806] update Minting transaction display from dapps (#608) * feat: identify minting txs from dapps and display proper UI to confirm refs: LW-5806 * feat(extension): update dapp transaction styling * refactor(extension,core): fix sonarcloud quality checks * ci(core): add @lace/ui to dependencies * test(extension): add mocks in ConfirmTransaction.test.tsx * test(extension): remove leftover from local testing * refactor(core): add missing newline at end of file --------- Co-authored-by: przemyslaw.wlodek --- .../dapp/components/ConfirmTransaction.tsx | 134 ++++++++++++----- .../__tests__/ConfirmTransaction.test.tsx | 13 +- .../src/lib/translations/en.json | 12 ++ packages/cardano/src/wallet/types.ts | 4 +- packages/core/package.json | 1 + .../TransactionDetails.module.scss | 25 +--- .../ActivityDetail/TransactionDetails.tsx | 39 ++--- .../ActivityDetail/TransactionFee.module.scss | 109 ++++++++++++++ .../ActivityDetail/TransactionFee.tsx | 36 +++++ .../src/ui/components/ActivityDetail/index.ts | 1 + .../DappTransaction.module.scss | 96 +----------- .../DappTransaction/DappTransaction.tsx | 139 +++++++++--------- .../DappTxAsset/DappTxAsset.module.scss | 41 ++++++ .../DappTxAsset/DappTxAsset.tsx | 28 ++++ .../DappTxHeader/DappTxHeader.module.scss | 27 ++++ .../DappTxHeader/DappTxHeader.tsx | 20 +++ .../DappTxOutput/DappTxOutput.module.scss | 67 +++++++++ .../DappTxOutput/DappTxOutput.tsx | 42 ++++++ packages/core/src/ui/lib/translations/en.json | 12 ++ yarn.lock | 1 + 20 files changed, 598 insertions(+), 249 deletions(-) create mode 100644 packages/core/src/ui/components/ActivityDetail/TransactionFee.module.scss create mode 100644 packages/core/src/ui/components/ActivityDetail/TransactionFee.tsx create mode 100644 packages/core/src/ui/components/DappTransaction/DappTxAsset/DappTxAsset.module.scss create mode 100644 packages/core/src/ui/components/DappTransaction/DappTxAsset/DappTxAsset.tsx create mode 100644 packages/core/src/ui/components/DappTransaction/DappTxHeader/DappTxHeader.module.scss create mode 100644 packages/core/src/ui/components/DappTransaction/DappTxHeader/DappTxHeader.tsx create mode 100644 packages/core/src/ui/components/DappTransaction/DappTxOutput/DappTxOutput.module.scss create mode 100644 packages/core/src/ui/components/DappTransaction/DappTxOutput/DappTxOutput.tsx diff --git a/apps/browser-extension-wallet/src/features/dapp/components/ConfirmTransaction.tsx b/apps/browser-extension-wallet/src/features/dapp/components/ConfirmTransaction.tsx index 37186dde44..e711aa50ed 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/ConfirmTransaction.tsx +++ b/apps/browser-extension-wallet/src/features/dapp/components/ConfirmTransaction.tsx @@ -14,16 +14,21 @@ import { consumeRemoteApi, exposeApi, RemoteApiPropertyType } from '@cardano-sdk import { DappDataService } from '@lib/scripts/types'; import { DAPP_CHANNELS } from '@src/utils/constants'; import { runtime } from 'webextension-polyfill'; -import { useRedirection } from '@hooks'; -import { assetsBurnedInspector, assetsMintedInspector, createTxInspector } from '@cardano-sdk/core'; +import { useFetchCoinPrice, useRedirection } from '@hooks'; +import { + assetsBurnedInspector, + assetsMintedInspector, + createTxInspector, + AssetsMintedInspection, + MintedAsset +} from '@cardano-sdk/core'; import { Skeleton } from 'antd'; import { dAppRoutePaths } from '@routes'; import { UserPromptService } from '@lib/scripts/background/services'; import { of } from 'rxjs'; -import { CardanoTxOut } from '@src/types'; import { getAssetsInformation, TokenInfo } from '@src/utils/get-assets-information'; import * as HardwareLedger from '../../../../../../node_modules/@cardano-sdk/hardware-ledger/dist/cjs'; -import { useAnalyticsContext } from '@providers'; +import { useCurrencyStore, useAnalyticsContext } from '@providers'; import { TX_CREATION_TYPE_KEY, TxCreationType } from '@providers/AnalyticsProvider/analyticsTracker'; import { txSubmitted$ } from '@providers/AnalyticsProvider/onChain'; @@ -39,6 +44,45 @@ const dappDataApi = consumeRemoteApi>( { logger: console, runtime } ); +const convertMetadataArrayToObj = (arr: unknown[]): Record => { + const result: Record = {}; + for (const item of arr) { + if (typeof item === 'object' && !Array.isArray(item) && item !== null) { + Object.assign(result, item); + } + } + return result; +}; + +// eslint-disable-next-line complexity, sonarjs/cognitive-complexity +const getAssetNameFromMintMetadata = (asset: MintedAsset, metadata: Wallet.Cardano.TxMetadata): string | undefined => { + if (!asset || !metadata) return; + const decodedAssetName = Buffer.from(asset.assetName, 'hex').toString(); + + // Tries to find the asset name in the tx metadata under label 721 or 20 + for (const [key, value] of metadata.entries()) { + // eslint-disable-next-line no-magic-numbers + if (key !== BigInt(721) && key !== BigInt(20)) return; + const cip25Metadata = Wallet.cardanoMetadatumToObj(value); + if (!Array.isArray(cip25Metadata)) return; + + // cip25Metadata should be an array containing all policies for the minted assets in the tx + const policyLevelMetadata = convertMetadataArrayToObj(cip25Metadata)[asset.policyId]; + if (!Array.isArray(policyLevelMetadata)) return; + + // policyLevelMetadata should be an array of objects with the minted assets names as key + // e.g. "policyId" = [{ "AssetName1": { ...metadataAsset1 } }, { "AssetName2": { ...metadataAsset2 } }]; + const assetProperties = convertMetadataArrayToObj(policyLevelMetadata)?.[decodedAssetName]; + if (!Array.isArray(assetProperties)) return; + + // assetProperties[decodedAssetName] should be an array of objects with the properties as keys + // e.g. [{ "name": "Asset Name" }, { "description": "An asset" }, ...] + const assetMetadataName = convertMetadataArrayToObj(assetProperties)?.name; + // eslint-disable-next-line consistent-return + return typeof assetMetadataName === 'string' ? assetMetadataName : undefined; + } +}; + // eslint-disable-next-line sonarjs/cognitive-complexity export const ConfirmTransaction = withAddressBookContext((): React.ReactElement => { const { @@ -49,9 +93,12 @@ export const ConfirmTransaction = withAddressBookContext((): React.ReactElement walletInfo, inMemoryWallet, getKeyAgentType, - blockchainProvider: { assetProvider } + blockchainProvider: { assetProvider }, + walletUI: { cardanoCoin } } = useWalletStore(); + const { fiatCurrency } = useCurrencyStore(); const { list: addressList } = useAddressBookContext(); + const { priceResult } = useFetchCoinPrice(); const analytics = useAnalyticsContext(); const [tx, setTx] = useState(); @@ -67,20 +114,23 @@ export const ConfirmTransaction = withAddressBookContext((): React.ReactElement const [assetsInfo, setAssetsInfo] = useState(); const [dappInfo, setDappInfo] = useState(); - const getTransactionAssetsId = (outputs: CardanoTxOut[]) => { - const assetIds: Wallet.Cardano.AssetId[] = []; - const assetMaps = outputs.map((output) => output.value.assets); + // All assets' ids in the transaction body. Used to fetch their info from cardano services + const assetIds = useMemo(() => { + const uniqueAssetIds = new Set(); + // Merge all assets (TokenMaps) from the tx outputs and mint + const assetMaps = tx?.body?.outputs?.map((output) => output.value.assets) ?? []; + if (tx?.body?.mint?.size > 0) assetMaps.push(tx.body.mint); + + // Extract all unique asset ids from the array of TokenMaps for (const asset of assetMaps) { if (asset) { for (const id of asset.keys()) { - !assetIds.includes(id) && assetIds.push(id); + !uniqueAssetIds.has(id) && uniqueAssetIds.add(id); } } } - return assetIds; - }; - - const assetIds = useMemo(() => tx?.body?.outputs && getTransactionAssetsId(tx.body.outputs), [tx?.body?.outputs]); + return [...uniqueAssetIds.values()]; + }, [tx]); useEffect(() => { if (assetIds?.length > 0) { @@ -154,16 +204,38 @@ export const ConfirmTransaction = withAddressBookContext((): React.ReactElement }); }, []); + const createMintedList = useCallback( + (mintedAssets: AssetsMintedInspection) => { + if (!assetsInfo) return []; + return mintedAssets.map((asset) => { + const assetId = Wallet.Cardano.AssetId.fromParts(asset.policyId, asset.assetName); + const assetInfo = assets.get(assetId) || assetsInfo?.get(assetId); + // If it's a new asset or the name is being updated we should be getting it from the tx metadata + const metadataName = getAssetNameFromMintMetadata(asset, tx?.auxiliaryData?.blob); + return { + name: assetInfo?.name.toString() || asset.fingerprint || assetId, + ticker: + metadataName ?? + assetInfo?.nftMetadata?.name ?? + assetInfo?.tokenMetadata?.ticker ?? + assetInfo?.tokenMetadata?.name ?? + asset.fingerprint.toString(), + amount: Wallet.util.calculateAssetBalance(asset.quantity, assetInfo) + }; + }); + }, + [assets, assetsInfo, tx] + ); + const createAssetList = useCallback( (txAssets: Wallet.Cardano.TokenMap) => { if (!assetsInfo) return []; const assetList: Wallet.Cip30SignTxAssetItem[] = []; - // eslint-disable-next-line unicorn/no-array-for-each txAssets.forEach(async (value, key) => { const walletAsset = assets.get(key) || assetsInfo?.get(key); assetList.push({ - name: walletAsset.name.toString() || key.toString(), - ticker: walletAsset.tokenMetadata?.ticker || walletAsset.nftMetadata?.name, + name: walletAsset?.name.toString() || key.toString(), + ticker: walletAsset?.tokenMetadata?.ticker || walletAsset?.nftMetadata?.name, amount: Wallet.util.calculateAssetBalance(value, walletAsset) }); }); @@ -185,17 +257,9 @@ export const ConfirmTransaction = withAddressBookContext((): React.ReactElement }); const { minted, burned } = inspector(tx as Wallet.Cardano.HydratedTx); - const isMintTransaction = minted.length > 0; - const isBurnTransaction = burned.length > 0; + const isMintTransaction = minted.length > 0 || burned.length > 0; - let txType: 'Send' | 'Mint' | 'Burn'; - if (isMintTransaction) { - txType = 'Mint'; - } else if (isBurnTransaction) { - txType = 'Burn'; - } else { - txType = 'Send'; - } + const txType = isMintTransaction ? 'Mint' : 'Send'; const externalOutputs = tx.body.outputs.filter((output) => { if (txType === 'Send') { @@ -223,17 +287,11 @@ export const ConfirmTransaction = withAddressBookContext((): React.ReactElement return { fee: Wallet.util.lovelacesToAdaString(tx.body.fee.toString()), outputs: txSummaryOutputs, - type: txType + type: txType, + mintedAssets: createMintedList(minted), + burnedAssets: createMintedList(burned) }; - }, [tx, walletInfo.addresses, createAssetList, addressToNameMap]); - - const translations = { - transaction: t('core.dappTransaction.transaction'), - amount: t('core.dappTransaction.amount'), - recipient: t('core.dappTransaction.recipient'), - fee: t('core.dappTransaction.fee'), - adaFollowingNumericValue: t('general.adaFollowingNumericValue') - }; + }, [tx, walletInfo.addresses, createAssetList, createMintedList, addressToNameMap]); const onConfirm = () => { analytics.sendEventToPostHog(PostHogAction.SendTransactionSummaryConfirmClick, { @@ -256,7 +314,9 @@ export const ConfirmTransaction = withAddressBookContext((): React.ReactElement transaction={txSummary} dappInfo={dappInfo} errorMessage={errorMessage} - translations={translations} + fiatCurrencyCode={fiatCurrency?.code} + fiatCurrencyPrice={priceResult?.cardano?.price} + coinSymbol={cardanoCoin.symbol} /> ) : ( diff --git a/apps/browser-extension-wallet/src/features/dapp/components/__tests__/ConfirmTransaction.test.tsx b/apps/browser-extension-wallet/src/features/dapp/components/__tests__/ConfirmTransaction.test.tsx index c82c6724d6..7acabf64d1 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/__tests__/ConfirmTransaction.test.tsx +++ b/apps/browser-extension-wallet/src/features/dapp/components/__tests__/ConfirmTransaction.test.tsx @@ -1,5 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable import/imports-first */ +import * as CurrencyProvider from '@providers/currency'; + const mockGetKeyAgentType = jest.fn(); const mockUseWalletStore = jest.fn(); const error = 'error in getSignTxData'; @@ -7,6 +9,7 @@ const mockConsumeRemoteApi = jest.fn().mockReturnValue({ getSignTxData: async () => await Promise.reject(error) }); const mockCreateTxInspector = jest.fn().mockReturnValue(() => ({ minted: [] as any, burned: [] as any })); +const mockUseCurrencyStore = jest.fn().mockReturnValue({ fiatCurrency: { code: 'usd', symbol: '$' } }); import * as React from 'react'; import { cleanup, render, waitFor } from '@testing-library/react'; import { ConfirmTransaction } from '../ConfirmTransaction'; @@ -31,6 +34,8 @@ import { postHogClientMocks } from '@src/utils/mocks/test-helpers'; const assetInfo$ = new BehaviorSubject(new Map()); const available$ = new BehaviorSubject([]); +const tokenPrices$ = new BehaviorSubject({}); +const adaPrices$ = new BehaviorSubject({}); const assetProvider = { getAsset: () => ({}), @@ -50,6 +55,11 @@ jest.mock('@src/stores', () => ({ useWalletStore: mockUseWalletStore })); +jest.mock('@providers/currency', (): typeof CurrencyProvider => ({ + ...jest.requireActual('@providers/currency'), + useCurrencyStore: mockUseCurrencyStore +})); + jest.mock('@cardano-sdk/web-extension', () => { const original = jest.requireActual('@cardano-sdk/web-extension'); return { @@ -74,7 +84,8 @@ const testIds = { const backgroundService = { getBackgroundStorage: jest.fn(), - setBackgroundStorage: jest.fn() + setBackgroundStorage: jest.fn(), + coinPrices: { tokenPrices$, adaPrices$ } } as unknown as BackgroundServiceAPIProviderProps['value']; const getWrapper = diff --git a/apps/browser-extension-wallet/src/lib/translations/en.json b/apps/browser-extension-wallet/src/lib/translations/en.json index 6b60b78510..4df8181120 100644 --- a/apps/browser-extension-wallet/src/lib/translations/en.json +++ b/apps/browser-extension-wallet/src/lib/translations/en.json @@ -69,6 +69,18 @@ "noMatchPassword": "Oops! The passwords don't match.", "secondLevelPasswordStrengthFeedback": "Getting there! Add some symbols and numbers to make it stronger.", "firstLevelPasswordStrengthFeedback": "Weak password. Add some numbers and characters to make it stronger." + }, + "dappTransaction": { + "asset": "Asset", + "burn": "Burn", + "fee": "Transaction Fee", + "insufficientFunds": "You do not have enough funds to complete the transaction", + "mint": "Mint", + "quantity": "Quantity", + "recipient": "Recipient", + "send": "Send", + "sending": "Sending", + "transaction": "Transaction" } }, "tab.main.title": "Tab extension", diff --git a/packages/cardano/src/wallet/types.ts b/packages/cardano/src/wallet/types.ts index 75707709dd..ab4164389f 100644 --- a/packages/cardano/src/wallet/types.ts +++ b/packages/cardano/src/wallet/types.ts @@ -35,7 +35,9 @@ export type Cip30SignTxSummary = { recipient: string; assets?: Cip30SignTxAssetItem[]; }[]; - type: 'Send' | 'Mint' | 'Burn'; + type: 'Send' | 'Mint'; + mintedAssets?: Cip30SignTxAssetItem[]; + burnedAssets?: Cip30SignTxAssetItem[]; }; export type Cip30SignTxAssetItem = { diff --git a/packages/core/package.json b/packages/core/package.json index a74b1549fc..6001c10540 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -39,6 +39,7 @@ "dependencies": { "@ant-design/icons": "^4.7.0", "@lace/common": "0.1.0", + "@lace/ui": "^0.1.0", "antd": "^4.24.10", "axios": "0.21.4", "axios-cache-adapter": "2.7.3", diff --git a/packages/core/src/ui/components/ActivityDetail/TransactionDetails.module.scss b/packages/core/src/ui/components/ActivityDetail/TransactionDetails.module.scss index a11c7e8b65..73fdb278f2 100644 --- a/packages/core/src/ui/components/ActivityDetail/TransactionDetails.module.scss +++ b/packages/core/src/ui/components/ActivityDetail/TransactionDetails.module.scss @@ -149,27 +149,6 @@ $border-bottom: 1px solid var(--light-mode-light-grey-plus, var(--dark-mode-mid- } } - .txFeeContainer { - display: flex; - align-items: center; - justify-content: center; - gap: size_unit(1); - - .txfee { - color: var(--text-color-primary); - font-size: var(--body, 16px); - font-weight: 600; - line-height: size_unit(3); - } - - .infoIcon { - width: size_unit(2); - height: size_unit(2); - margin-bottom: -#{size_unit(0.5)}; - color: var(--text-color-secondary); - } - } - .metadataLabel { display: flex; flex: 0 0 50%; @@ -266,7 +245,7 @@ $border-bottom: 1px solid var(--light-mode-light-grey-plus, var(--dark-mode-mid- margin-bottom: size_unit(2); .poolsTitle { - @include text-body-semi-bold + @include text-body-semi-bold; } .poolsList { @@ -280,7 +259,7 @@ $border-bottom: 1px solid var(--light-mode-light-grey-plus, var(--dark-mode-mid- .poolHeading { display: flex; justify-content: flex-end; - gap: size_unit(1) + gap: size_unit(1); } .poolRewardAmount { diff --git a/packages/core/src/ui/components/ActivityDetail/TransactionDetails.tsx b/packages/core/src/ui/components/ActivityDetail/TransactionDetails.tsx index e54e20cc15..bc40229b6c 100644 --- a/packages/core/src/ui/components/ActivityDetail/TransactionDetails.tsx +++ b/packages/core/src/ui/components/ActivityDetail/TransactionDetails.tsx @@ -1,16 +1,15 @@ /* eslint-disable no-magic-numbers */ import React from 'react'; import cn from 'classnames'; -import { InfoCircleOutlined } from '@ant-design/icons'; -import { Tooltip } from 'antd'; -import styles from './TransactionDetails.module.scss'; -import { TransactionDetailAsset, TxOutputInput, TransactionMetadataProps, TxSummary } from './TransactionDetailAsset'; -import type { ActivityStatus } from '../Activity'; +import { TransactionDetailAsset, TransactionMetadataProps, TxOutputInput, TxSummary } from './TransactionDetailAsset'; import { Ellipsis, toast } from '@lace/common'; -import { ReactComponent as Info } from '../../assets/icons/info-icon.component.svg'; -import { TransactionInputOutput } from './TransactionInputOutput'; +import { Box } from '@lace/ui'; import { useTranslate } from '@src/ui/hooks'; import CopyToClipboard from 'react-copy-to-clipboard'; +import type { ActivityStatus } from '../Activity'; +import styles from './TransactionDetails.module.scss'; +import { TransactionInputOutput } from './TransactionInputOutput'; +import { TransactionFee } from './TransactionFee'; import { ActivityDetailHeader } from './ActivityDetailHeader'; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -265,31 +264,11 @@ export const TransactionDetails = ({  {includedTime} - {fee && fee !== '-' && ( -
-
-
{t('package.core.activityDetails.transactionFee')}
- - {Info ? ( - - ) : ( - - )} - -
- -
-
- {`${fee} ${coinSymbol}`} - - {amountTransformer(fee)} - -
-
-
+ + + )} - {deposit && renderDepositValueSection({ value: deposit, label: t('package.core.activityDetails.deposit') })} {depositReclaim && renderDepositValueSection({ diff --git a/packages/core/src/ui/components/ActivityDetail/TransactionFee.module.scss b/packages/core/src/ui/components/ActivityDetail/TransactionFee.module.scss new file mode 100644 index 0000000000..7ba4758637 --- /dev/null +++ b/packages/core/src/ui/components/ActivityDetail/TransactionFee.module.scss @@ -0,0 +1,109 @@ +@import '../../styles/theme.scss'; +@import '../../../../../common/src/ui/styles/abstracts/typography'; + +.txFeeContainer { + display: flex; + align-items: center; + justify-content: center; + gap: size_unit(1); +} + +.txfee { + color: var(--text-color-primary); + font-size: var(--body, 16px); + font-weight: 600; + line-height: size_unit(3); +} + +.details { + color: var(--text-color-primary, #ffffff); + display: flex; + justify-content: space-between; + align-items: flex-start; + width: 100%; + + .title { + display: flex; + flex: 0 0 50%; + align-self: baseline; + color: var(--text-color-primary, #ffffff); + @include text-body-semi-bold; + } + + .detail { + align-items: end; + display: flex; + flex-direction: column; + gap: size_unit(2); + color: var(--text-color-primary, #ffffff); + text-align: right; + word-break: break-all; + @include text-body-medium; + + @media (max-width: $breakpoint-popup) { + flex-direction: column; + } + + &.hash { + @include text-address; + font-weight: 500; + text-align: right; + cursor: pointer; + } + &.txLink { + color: var(--text-color-blue, #3489f7); + line-height: 17px; + @media (max-width: $breakpoint-popup) { + flex: 60%; + } + } + &.poolId { + color: var(--text-color-secondary); + font-size: var(--bodySmall); + font-weight: 500; + line-height: 17px; + } + } + + .timestamp { + flex: 0 0 35%; + } + + .amount { + display: flex; + flex-direction: column; + width: 100%; + align-items: flex-end; + + .ada { + color: var(--text-color-primary, #ffffff); + } + + .fiat { + color: var(--text-color-secondary, #878e9e); + } + + .addrName { + margin-bottom: size_unit(1); + } + } + + .addressDetail { + font-size: var(--bodySmall, 14px); + font-weight: 400; + line-height: size_unit(2); + text-align: right; + margin-bottom: size_unit(5); + @media (max-width: $breakpoint-popup) { + margin-bottom: size_unit(6); + } + } + + .metadataLabel { + display: flex; + flex: 0 0 50%; + align-self: baseline; + @include text-bodyLarge-bold; + color: var(--text-color-primary); + } +} diff --git a/packages/core/src/ui/components/ActivityDetail/TransactionFee.tsx b/packages/core/src/ui/components/ActivityDetail/TransactionFee.tsx new file mode 100644 index 0000000000..bab28f0a2c --- /dev/null +++ b/packages/core/src/ui/components/ActivityDetail/TransactionFee.tsx @@ -0,0 +1,36 @@ +/* eslint-disable no-magic-numbers */ +import React from 'react'; +import { InfoCircleOutlined } from '@ant-design/icons'; +import { Tooltip } from 'antd'; +import styles from './TransactionFee.module.scss'; +import { ReactComponent as Info } from '../../assets/icons/info-icon.component.svg'; +import { useTranslate } from '@src/ui/hooks'; + +export interface TransactionFeeProps { + fee: string; + amountTransformer: (amount: string) => string; + coinSymbol: string; +} +export const TransactionFee = ({ fee, amountTransformer, coinSymbol }: TransactionFeeProps): React.ReactElement => { + const { t } = useTranslate(); + + return ( +
+
+
{t('package.core.activityDetails.transactionFee')}
+ + {Info ? : } + +
+ +
+
+ {`${fee} ${coinSymbol}`} + + {amountTransformer(fee)} + +
+
+
+ ); +}; diff --git a/packages/core/src/ui/components/ActivityDetail/index.ts b/packages/core/src/ui/components/ActivityDetail/index.ts index 26ccb7a4b5..7ba95eea83 100644 --- a/packages/core/src/ui/components/ActivityDetail/index.ts +++ b/packages/core/src/ui/components/ActivityDetail/index.ts @@ -3,3 +3,4 @@ export * from './RewardsDetails'; export * from './ActivityTypeIcon'; export * from './TransactionDetailAsset'; export * from './TransactionInputOutput'; +export * from './TransactionFee'; diff --git a/packages/core/src/ui/components/DappTransaction/DappTransaction.module.scss b/packages/core/src/ui/components/DappTransaction/DappTransaction.module.scss index ac80eec8e0..ea1eb99b34 100644 --- a/packages/core/src/ui/components/DappTransaction/DappTransaction.module.scss +++ b/packages/core/src/ui/components/DappTransaction/DappTransaction.module.scss @@ -1,5 +1,6 @@ @import '../../styles/theme.scss'; @import '../../../../../common/src/ui/styles/abstracts/_typography.scss'; + .dappInfo { margin: size_unit(1) 0px; } @@ -12,92 +13,14 @@ margin: size_unit(4) 0 size_unit(2) 0px; padding: size_unit(3) 0; border-top: 2px solid var(--light-mode-light-grey-plus, var(--dark-mode-mid-grey)); + gap: size_unit(3); } .error { margin: size_unit(2) 0px; } -.header { - font-size: var(--bodyLarge); - letter-spacing: -0.015em; - margin-bottom: size_unit(1); - display: flex; - justify-content: space-between; - align-items: center; - - .title { - font-weight: 600; - line-height: size_unit(3); - /* or 133% */ - /* Secondary - Black */ - color: var(--text-color-primary); - } - .type { - font-weight: 500; - line-height: size_unit(4); - /* or 178% */ - text-align: right; - /* Primary - Purple */ - color: var(--primary-default, #7f5af0); - } -} -.body { - display: flex; - flex-direction: column; - gap: size_unit(2); -} -.detail { - display: flex; - justify-content: space-between; - align-items: baseline; - - > * { - display: flex; - flex: 0 1 50%; - min-width: 0; - } - - .title { - font-weight: 500; - font-size: var(--body); - line-height: size_unit(3); - /* or 150% */ - /* Secondary - Black */ - color: var(--text-color-primary); - text-align: right; - } - .value { - display: flex; - align-items: flex-end; - flex-direction: column; - - font-size: var(--bodySmall); - font-weight: 500; - line-height: size_unit(2); - /* Secondary - Black */ - color: var(--text-color-primary); - - .bold { - font-weight: 600; - line-height: size_unit(3); - font-size: var(--body); - word-break: break-all; - } - - .rightAligned { - text-align: right; - > div { - justify-content: flex-end; - } - div, - p { - text-align: right; - } - } - } -} .warningAlert { flex-direction: row; background: var(--lace-cream); @@ -118,15 +41,12 @@ margin: 0; } } -:global(.__react_component_tooltip) { - @include tooltip-default; -} -.sub { - @include text-bodySmall-medium; - /* or 171% */ - letter-spacing: -0.015em; - /* Data - Dark Grey */ +.feeContainer { + display: flex; + flex-direction: row; + align-items: flex-start; + justify-content: space-between; + @include text-body-semi-bold; color: var(--text-color-primary); - text-align: right; } diff --git a/packages/core/src/ui/components/DappTransaction/DappTransaction.tsx b/packages/core/src/ui/components/DappTransaction/DappTransaction.tsx index d7c4da265c..9f5717baae 100644 --- a/packages/core/src/ui/components/DappTransaction/DappTransaction.tsx +++ b/packages/core/src/ui/components/DappTransaction/DappTransaction.tsx @@ -1,21 +1,21 @@ +/* eslint-disable sonarjs/no-duplicate-string */ import React from 'react'; -import { Ellipsis, ErrorPane } from '@lace/common'; +import { ErrorPane } from '@lace/common'; +import { Wallet } from '@lace/cardano'; import { DappInfo, DappInfoProps } from '../DappInfo'; +import { DappTxHeader } from './DappTxHeader/DappTxHeader'; +import { DappTxAsset, DappTxAssetProps } from './DappTxAsset/DappTxAsset'; +import { DappTxOutput, DappTxOutputProps } from './DappTxOutput/DappTxOutput'; import styles from './DappTransaction.module.scss'; -import { TranslationsFor } from '@ui/utils/types'; +import { useTranslate } from '@src/ui/hooks'; +import { TransactionFee } from '@ui/components/ActivityDetail'; type TransactionDetails = { fee: string; - outputs: { - coins: string; - recipient: string; - assets?: { - name: string; - amount: string; - ticker?: string; - }[]; - }[]; - type: 'Send' | 'Mint' | 'Burn'; + outputs: DappTxOutputProps[]; + type: 'Send' | 'Mint'; + mintedAssets?: DappTxAssetProps[]; + burnedAssets?: DappTxAssetProps[]; }; export interface DappTransactionProps { @@ -25,67 +25,68 @@ export interface DappTransactionProps { dappInfo: Omit; /** Optional error message */ errorMessage?: string; - translations: TranslationsFor<'transaction' | 'amount' | 'recipient' | 'fee' | 'adaFollowingNumericValue'>; + fiatCurrencyCode?: string; + fiatCurrencyPrice?: number; + coinSymbol?: string; } export const DappTransaction = ({ - transaction: { type, outputs, fee }, + transaction: { type, outputs, fee, mintedAssets, burnedAssets }, dappInfo, errorMessage, - translations -}: DappTransactionProps): React.ReactElement => ( -
- - {errorMessage && } -
-
-
- {translations.transaction} -
-
- {type} -
+ fiatCurrencyCode, + fiatCurrencyPrice, + coinSymbol +}: DappTransactionProps): React.ReactElement => { + const { t } = useTranslate(); + return ( +
+ + {errorMessage && } +
+ {type === 'Mint' && mintedAssets?.length > 0 && ( + <> + + {mintedAssets.map((asset) => ( + + ))} + + )} + {type === 'Mint' && burnedAssets?.length > 0 && ( + <> + 0 ? undefined : t('package.core.dappTransaction.transaction')} + subtitle={t('package.core.dappTransaction.burn')} + /> + {burnedAssets.map((asset) => ( + + ))} + + )} + {type === 'Send' && ( + <> + + {outputs.map((output) => ( + + ))} + + )} + {fee && fee !== '-' && ( + + `${Wallet.util.convertAdaToFiat({ ada, fiat: fiatCurrencyPrice })} ${fiatCurrencyCode}` + } + coinSymbol={coinSymbol} + /> + )}
- {outputs.map((output) => ( -
-
-
- {translations.amount} -
-
-
- {output.coins.toString()} ADA -
- {outputs.length === 1 && ( -
- {translations.fee}: {fee.toString()} ADA -
- )} - {output.assets && - output.assets.map((asset) => ( -
- {asset.amount} {asset.ticker || asset.name} -
- ))} -
-
-
-
- {translations.recipient} -
-
- -
-
-
- ))} - {outputs.length > 1 && ( -
-
- {translations.fee}: {fee.toString()} ADA -
-
- )}
-
-); + ); +}; diff --git a/packages/core/src/ui/components/DappTransaction/DappTxAsset/DappTxAsset.module.scss b/packages/core/src/ui/components/DappTransaction/DappTxAsset/DappTxAsset.module.scss new file mode 100644 index 0000000000..0d5bf78af1 --- /dev/null +++ b/packages/core/src/ui/components/DappTransaction/DappTxAsset/DappTxAsset.module.scss @@ -0,0 +1,41 @@ +@import '../../../styles/theme.scss'; +@import '../../../../../../common/src/ui/styles/abstracts/_typography.scss'; + +.body { + display: flex; + flex-direction: column; + gap: size_unit(3); + background-color: var(--light-mode-light-grey, var(--dark-mode-grey, #f9f9f9)); + border-radius: size_unit(2); + padding: size_unit(2); + + .detail { + @include text-body-semi-bold; + display: flex; + justify-content: space-between; + align-items: center; + + .title { + color: var(--text-color-primary); + line-height: 1; + } + + .value { + display: flex; + max-width: 50%; + align-items: flex-end; + flex-direction: column; + gap: size_unit(2); + + font-size: var(--bodySmall); + font-weight: 500; + line-height: 1; + /* Secondary - Black */ + color: var(--text-color-primary); + } + + .ellipsis > p { + margin: 0; + } + } +} diff --git a/packages/core/src/ui/components/DappTransaction/DappTxAsset/DappTxAsset.tsx b/packages/core/src/ui/components/DappTransaction/DappTxAsset/DappTxAsset.tsx new file mode 100644 index 0000000000..f421b19c95 --- /dev/null +++ b/packages/core/src/ui/components/DappTransaction/DappTxAsset/DappTxAsset.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import styles from './DappTxAsset.module.scss'; +import { Ellipsis } from '@lace/common'; +import { useTranslate } from '@src/ui/hooks'; + +export interface DappTxAssetProps { + name: string; + amount: string; + ticker?: string; +} + +export const DappTxAsset = ({ amount, name, ticker }: DappTxAssetProps): React.ReactElement => { + const { t } = useTranslate(); + return ( +
+
+
{t('package.core.dappTransaction.asset')}
+
+ +
+
+
+
{t('package.core.dappTransaction.quantity')}
+
{amount}
+
+
+ ); +}; diff --git a/packages/core/src/ui/components/DappTransaction/DappTxHeader/DappTxHeader.module.scss b/packages/core/src/ui/components/DappTransaction/DappTxHeader/DappTxHeader.module.scss new file mode 100644 index 0000000000..94eefef49f --- /dev/null +++ b/packages/core/src/ui/components/DappTransaction/DappTxHeader/DappTxHeader.module.scss @@ -0,0 +1,27 @@ +@import '../../../styles/theme.scss'; +@import '../../../../../../common/src/ui/styles/abstracts/_typography.scss'; + +.header { + font-size: var(--bodyLarge); + letter-spacing: -0.015em; + margin-bottom: size_unit(1); + display: flex; + justify-content: space-between; + align-items: center; + + .title { + font-weight: 600; + line-height: size_unit(3); + /* or 133% */ + /* Secondary - Black */ + color: var(--text-color-primary); + } + .type { + font-weight: 500; + line-height: size_unit(4); + /* or 178% */ + text-align: right; + /* Primary - Purple */ + color: var(--primary-default, #7f5af0); + } +} diff --git a/packages/core/src/ui/components/DappTransaction/DappTxHeader/DappTxHeader.tsx b/packages/core/src/ui/components/DappTransaction/DappTxHeader/DappTxHeader.tsx new file mode 100644 index 0000000000..8117fcd3e0 --- /dev/null +++ b/packages/core/src/ui/components/DappTransaction/DappTxHeader/DappTxHeader.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import styles from './DappTxHeader.module.scss'; + +export interface DappTxHeaderProps { + title?: string; + subtitle?: string; +} + +export const DappTxHeader = (props: DappTxHeaderProps): React.ReactElement => ( +
+
+ {props?.title ?? ''} +
+ {props?.subtitle && ( +
+ {props.subtitle} +
+ )} +
+); diff --git a/packages/core/src/ui/components/DappTransaction/DappTxOutput/DappTxOutput.module.scss b/packages/core/src/ui/components/DappTransaction/DappTxOutput/DappTxOutput.module.scss new file mode 100644 index 0000000000..04c3b12f0a --- /dev/null +++ b/packages/core/src/ui/components/DappTransaction/DappTxOutput/DappTxOutput.module.scss @@ -0,0 +1,67 @@ +@import '../../../styles/theme.scss'; +@import '../../../../../../common/src/ui/styles/abstracts/_typography.scss'; + +.body { + display: flex; + flex-direction: column; + gap: size_unit(3); + background-color: var(--light-mode-light-grey, var(--dark-mode-grey, #f9f9f9)); + border-radius: size_unit(2); + padding: size_unit(2); +} + +.detail { + display: flex; + justify-content: space-between; + align-items: baseline; + + > * { + display: flex; + flex: 0 1 50%; + min-width: 0; + } + + .title { + font-weight: 500; + font-size: var(--body); + line-height: size_unit(3); + /* or 150% */ + /* Secondary - Black */ + color: var(--text-color-primary); + text-align: right; + } + .value { + display: flex; + align-items: flex-end; + flex-direction: column; + gap: size_unit(2); + + font-size: var(--bodySmall); + font-weight: 500; + line-height: size_unit(2); + /* Secondary - Black */ + color: var(--text-color-primary); + + .bold { + font-weight: 600; + line-height: size_unit(3); + font-size: var(--body); + word-break: break-all; + } + + .rightAligned { + text-align: right; + > div { + justify-content: flex-end; + } + div, + p { + text-align: right; + } + } + } +} + +:global(.__react_component_tooltip) { + @include tooltip-default; +} diff --git a/packages/core/src/ui/components/DappTransaction/DappTxOutput/DappTxOutput.tsx b/packages/core/src/ui/components/DappTransaction/DappTxOutput/DappTxOutput.tsx new file mode 100644 index 0000000000..d608929168 --- /dev/null +++ b/packages/core/src/ui/components/DappTransaction/DappTxOutput/DappTxOutput.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { Ellipsis } from '@lace/common'; +import { DappTxAssetProps } from '../DappTxAsset/DappTxAsset'; +import styles from './DappTxOutput.module.scss'; +import { useTranslate } from '@src/ui/hooks'; + +export interface DappTxOutputProps { + coins: string; + recipient: string; + assets?: DappTxAssetProps[]; +} + +export const DappTxOutput = ({ recipient, coins, assets }: DappTxOutputProps): React.ReactElement => { + const { t } = useTranslate(); + return ( +
+
+
+ {t('package.core.dappTransaction.sending')} +
+
+
+ {coins.toString()} ADA +
+ {assets?.map((asset) => ( +
+ {asset.amount} {asset.ticker || asset.name} +
+ ))} +
+
+
+
+ {t('package.core.dappTransaction.recipient')} +
+
+ +
+
+
+ ); +}; diff --git a/packages/core/src/ui/lib/translations/en.json b/packages/core/src/ui/lib/translations/en.json index c73e862cbd..7336752ffd 100644 --- a/packages/core/src/ui/lib/translations/en.json +++ b/packages/core/src/ui/lib/translations/en.json @@ -100,6 +100,18 @@ "noMatchPassword": "Oops! The passwords don't match.", "secondLevelPasswordStrengthFeedback": "Getting there! Add some symbols and numbers to make it stronger.", "firstLevelPasswordStrengthFeedback": "Weak password. Add some numbers and characters to make it stronger." + }, + "dappTransaction": { + "asset": "Asset", + "burn": "Burn", + "fee": "Transaction Fee", + "insufficientFunds": "You do not have enough funds to complete the transaction", + "mint": "Mint", + "quantity": "Quantity", + "recipient": "Recipient", + "send": "Send", + "sending": "Sending", + "transaction": "Transaction" } } } diff --git a/yarn.lock b/yarn.lock index fda8b47d21..7153b124da 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7975,6 +7975,7 @@ __metadata: dependencies: "@ant-design/icons": ^4.7.0 "@lace/common": 0.1.0 + "@lace/ui": ^0.1.0 "@types/debounce-promise": ^3.1.6 antd: ^4.24.10 axios: 0.21.4 From 22cbd6fa16aa6e5e5ebe43d87ef40f170ef58f97 Mon Sep 17 00:00:00 2001 From: bslabiak <112852128+bslabiak@users.noreply.github.com> Date: Tue, 28 Nov 2023 17:06:00 +0100 Subject: [PATCH 04/16] test(extension): maintenance 27 nov (#757) * test(extension): maintenance nov 27 * test(extension): clear failed request test logs * test(extension): add real time console test logging, remove unnecessary logs * test(extension): update wdio version * test(extension): add node option env variable for e2e github runs * test(extension): move node options to e2e test sections * test(extension): increase max-old-space-size * test(extension): downgrade wdio to stable version, remove node env variable --- packages/e2e-tests/package.json | 18 +- .../analytics/AnalyticsSendExtended.feature | 3 +- .../e2e-tests/src/utils/networkManager.ts | 9 +- packages/e2e-tests/src/utils/utils.ts | 1 - packages/e2e-tests/wdio.conf.base.ts | 7 +- yarn.lock | 357 +++++++++++------- 6 files changed, 253 insertions(+), 142 deletions(-) diff --git a/packages/e2e-tests/package.json b/packages/e2e-tests/package.json index 4af33bf873..55746c84ac 100755 --- a/packages/e2e-tests/package.json +++ b/packages/e2e-tests/package.json @@ -34,14 +34,14 @@ "@types/flat": "5.0.2", "@typescript-eslint/eslint-plugin": "6.0.0", "@typescript-eslint/parser": "6.0.0", - "@wdio/allure-reporter": "8.24.0", - "@wdio/cli": "8.24.0", - "@wdio/config": "8.24.0", - "@wdio/cucumber-framework": "8.24.0", - "@wdio/devtools-service": "8.24.0", - "@wdio/local-runner": "8.24.0", - "@wdio/spec-reporter": "8.24.0", - "@wdio/types": "8.24.0", + "@wdio/allure-reporter": "8.20.0", + "@wdio/cli": "8.20.0", + "@wdio/config": "8.20.0", + "@wdio/cucumber-framework": "8.20.0", + "@wdio/devtools-service": "8.20.0", + "@wdio/local-runner": "8.20.0", + "@wdio/spec-reporter": "8.20.0", + "@wdio/types": "8.20.0", "allure-commandline": "2.24.1", "clipboardy": "2.3.0", "eslint": "8.38.0", @@ -53,6 +53,6 @@ "ts-node": "10.9.1", "typescript": "4.9.5", "wdio-intercept-service": "4.4.0", - "webdriverio": "8.24.0" + "webdriverio": "8.20.0" } } diff --git a/packages/e2e-tests/src/features/analytics/AnalyticsSendExtended.feature b/packages/e2e-tests/src/features/analytics/AnalyticsSendExtended.feature index f159bf2ea8..4a7edbc963 100644 --- a/packages/e2e-tests/src/features/analytics/AnalyticsSendExtended.feature +++ b/packages/e2e-tests/src/features/analytics/AnalyticsSendExtended.feature @@ -15,6 +15,7 @@ Feature: Analytics - Posthog - Sending - Extended View | ADA | Cardano | tADA | 1.1234 | When I click "Add bundle" button on "Send" page And I enter "$test_handle_1" in the bundle 2 recipient's address + And Green tick icon is displayed next to ADA handle And I enter a value of: 1 to the "tADA" asset in bundle 2 And I click "Review transaction" button on "Send" page Then I validate latest analytics single event "send | transaction data | review transaction | click" @@ -29,7 +30,7 @@ Feature: Analytics - Posthog - Sending - Extended View And I click "View transaction" button on submitted transaction page And Local storage unconfirmedTransaction contains tx with type: "internal" And I validate latest analytics single event "send | all done | view transaction | click" - When the Sent transaction is displayed with value: "1.12 tADA" and tokens count 1 + When the Sent transaction is displayed with value: "2.12 tADA" and tokens count 1 Then I validate latest analytics single event "send | transaction confirmed" And I validate that the "send | transaction confirmed" event includes property "tx_creation_type" with value "internal" in posthog And I validate that 7 analytics event(s) have been sent diff --git a/packages/e2e-tests/src/utils/networkManager.ts b/packages/e2e-tests/src/utils/networkManager.ts index 05437448fc..d882a5d319 100644 --- a/packages/e2e-tests/src/utils/networkManager.ts +++ b/packages/e2e-tests/src/utils/networkManager.ts @@ -106,7 +106,10 @@ export class NetworkManager { const approximateTimestamp = new Date().toString(); const combinedFailedRequestInfo = `URL:\n${request.response.url}\n\nRESPONSE CODE:\n${request.response.status}\n\nAPPROXIMATE TIME:\n${approximateTimestamp}\n\nRESPONSE BODY:\n${responseBody}\n\nREQUEST PAYLOAD:\n${requestPayload}`; allure.addAttachment('Failed request', combinedFailedRequestInfo, 'text/plain'); - console.error('Failed request', combinedFailedRequestInfo); + console.error( + 'Failed request', + `URL: ${request.response.url} | RESPONSE CODE: ${request.response.status}` + ); } }); }); @@ -124,8 +127,8 @@ export class NetworkManager { let postData = ''; try { postData = JSON.stringify(await client.send('Network.getRequestPostData', { requestId })); - } catch (error) { - Logger.warn(`${error}`); + } catch { + /* empty */ } return postData; }; diff --git a/packages/e2e-tests/src/utils/utils.ts b/packages/e2e-tests/src/utils/utils.ts index 78dcd470b0..52f00d4979 100644 --- a/packages/e2e-tests/src/utils/utils.ts +++ b/packages/e2e-tests/src/utils/utils.ts @@ -42,7 +42,6 @@ class ExtensionUtils { break; } } - Logger.log(`Using network: ${network} with id: ${id}`); return { name: network, id }; } diff --git a/packages/e2e-tests/wdio.conf.base.ts b/packages/e2e-tests/wdio.conf.base.ts index 65cd4f434c..572e840109 100755 --- a/packages/e2e-tests/wdio.conf.base.ts +++ b/packages/e2e-tests/wdio.conf.base.ts @@ -22,7 +22,12 @@ export const config: WebdriverIO.Config = { connectionRetryCount: 3, framework: 'cucumber', reporters: [ - 'spec', + [ + 'spec', + { + realtimeReporting: true + } + ], [ 'allure', { diff --git a/yarn.lock b/yarn.lock index 7153b124da..edcdec2482 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6094,18 +6094,6 @@ __metadata: languageName: node linkType: hard -"@cucumber/messages@npm:23.0.0": - version: 23.0.0 - resolution: "@cucumber/messages@npm:23.0.0" - dependencies: - "@types/uuid": 9.0.6 - class-transformer: 0.5.1 - reflect-metadata: 0.1.13 - uuid: 9.0.1 - checksum: e2a71c5853d711275fd8bf860702f330c56516e59368542f4f38a3ce8929c82a147b8500206756f27321cf26d02b62faa775e91e8de79b8df468065fc00afa25 - languageName: node - linkType: hard - "@cucumber/messages@npm:^19.1.4": version: 19.1.4 resolution: "@cucumber/messages@npm:19.1.4" @@ -8009,14 +7997,14 @@ __metadata: "@types/flat": 5.0.2 "@typescript-eslint/eslint-plugin": 6.0.0 "@typescript-eslint/parser": 6.0.0 - "@wdio/allure-reporter": 8.24.0 - "@wdio/cli": 8.24.0 - "@wdio/config": 8.24.0 - "@wdio/cucumber-framework": 8.24.0 - "@wdio/devtools-service": 8.24.0 - "@wdio/local-runner": 8.24.0 - "@wdio/spec-reporter": 8.24.0 - "@wdio/types": 8.24.0 + "@wdio/allure-reporter": 8.20.0 + "@wdio/cli": 8.20.0 + "@wdio/config": 8.20.0 + "@wdio/cucumber-framework": 8.20.0 + "@wdio/devtools-service": 8.20.0 + "@wdio/local-runner": 8.20.0 + "@wdio/spec-reporter": 8.20.0 + "@wdio/types": 8.20.0 allure-commandline: 2.24.1 chai: 4.3.10 chai-sorted: 0.2.0 @@ -8031,7 +8019,7 @@ __metadata: ts-node: 10.9.1 typescript: 4.9.5 wdio-intercept-service: 4.4.0 - webdriverio: 8.24.0 + webdriverio: 8.20.0 languageName: unknown linkType: soft @@ -8309,7 +8297,7 @@ __metadata: languageName: node linkType: hard -"@ljharb/through@npm:^2.3.11": +"@ljharb/through@npm:^2.3.9": version: 2.3.11 resolution: "@ljharb/through@npm:2.3.11" dependencies: @@ -15303,13 +15291,6 @@ __metadata: languageName: node linkType: hard -"@types/uuid@npm:9.0.6": - version: 9.0.6 - resolution: "@types/uuid@npm:9.0.6" - checksum: 739dcb2e620ff097fa916edeab455eb75640c4883a850784fdb15b32f67b719e05275c6d6419bb6da11350d26bd14ed05ba5e992ff228411cdd98cbc772d71ef - languageName: node - linkType: hard - "@types/validator@npm:^13.7.10": version: 13.7.12 resolution: "@types/validator@npm:13.7.12" @@ -16148,51 +16129,66 @@ __metadata: languageName: node linkType: hard -"@wdio/allure-reporter@npm:8.24.0": - version: 8.24.0 - resolution: "@wdio/allure-reporter@npm:8.24.0" +"@wdio/allure-reporter@npm:8.20.0": + version: 8.20.0 + resolution: "@wdio/allure-reporter@npm:8.20.0" dependencies: "@types/node": ^20.1.0 - "@wdio/reporter": 8.24.0 - "@wdio/types": 8.24.0 + "@wdio/reporter": 8.20.0 + "@wdio/types": 8.20.0 allure-js-commons: ^2.5.0 csv-stringify: ^6.0.4 strip-ansi: ^7.1.0 - checksum: 121d5ba0b33b8b4cbc0047f54c314c1e18962abbccc2d3407939141b0066c87ae6d74ef22efe554eebd148191a9dc7489b574a310b82b679d995ad369f986c12 + checksum: ea1187dc516369f72fa91e372a89877ed382aec5c464d1d1f93361086cc40231ff3a036f16df8a706b368bc42bebe56b4f1d3c4a0d9c971dbb7edfc2f8390001 languageName: node linkType: hard -"@wdio/cli@npm:8.24.0": - version: 8.24.0 - resolution: "@wdio/cli@npm:8.24.0" +"@wdio/cli@npm:8.20.0": + version: 8.20.0 + resolution: "@wdio/cli@npm:8.20.0" dependencies: "@types/node": ^20.1.1 - "@wdio/config": 8.24.0 - "@wdio/globals": 8.24.0 + "@wdio/config": 8.20.0 + "@wdio/globals": 8.20.0 "@wdio/logger": 8.16.17 - "@wdio/protocols": 8.23.0 - "@wdio/types": 8.24.0 - "@wdio/utils": 8.24.0 + "@wdio/protocols": 8.18.0 + "@wdio/types": 8.20.0 + "@wdio/utils": 8.20.0 async-exit-hook: ^2.0.1 chalk: ^5.2.0 chokidar: ^3.5.3 cli-spinners: ^2.9.0 - detect-package-manager: ^3.0.1 dotenv: ^16.3.1 ejs: ^3.1.9 execa: ^8.0.1 import-meta-resolve: ^3.0.0 - inquirer: 9.2.12 + inquirer: 9.2.11 lodash.flattendeep: ^4.4.0 lodash.pickby: ^4.6.0 lodash.union: ^4.6.0 - read-pkg-up: ^10.0.0 + read-pkg-up: 10.1.0 recursive-readdir: ^2.2.3 - webdriverio: 8.24.0 + webdriverio: 8.20.0 yargs: ^17.7.2 bin: wdio: bin/wdio.js - checksum: a1c3337d67db7cb2176e5eb5928603c89972ceee262683ceaf4ca0ae28ac5ed134da83c68673b6448edbb8f9272d0e8c1f20c8cc06347ca9cc83abc9dee5dfa2 + checksum: 2e39de390ffd1a40efbbc17342743b577b590dbca325c376b286a67819e28aeebce90be34d8d26a667f6b0309ec939858057adb9d637325c1d2e0c0ca03b27ec + languageName: node + linkType: hard + +"@wdio/config@npm:8.20.0": + version: 8.20.0 + resolution: "@wdio/config@npm:8.20.0" + dependencies: + "@wdio/logger": 8.16.17 + "@wdio/types": 8.20.0 + "@wdio/utils": 8.20.0 + decamelize: ^6.0.0 + deepmerge-ts: ^5.0.0 + glob: ^10.2.2 + import-meta-resolve: ^3.0.0 + read-pkg-up: ^10.0.0 + checksum: 25e56a396fd2970aed1c00eae86c2db83e1ee4215a15202a4477f1838c1a913e7033c1db4aee1644fcbf6c6fe744795c52c34f6db9d156422230276b9c44ea74 languageName: node linkType: hard @@ -16211,35 +16207,35 @@ __metadata: languageName: node linkType: hard -"@wdio/cucumber-framework@npm:8.24.0": - version: 8.24.0 - resolution: "@wdio/cucumber-framework@npm:8.24.0" +"@wdio/cucumber-framework@npm:8.20.0": + version: 8.20.0 + resolution: "@wdio/cucumber-framework@npm:8.20.0" dependencies: "@cucumber/cucumber": 9.5.1 "@cucumber/gherkin": 27.0.0 - "@cucumber/messages": 23.0.0 + "@cucumber/messages": 22.0.0 "@types/node": ^20.1.0 "@wdio/logger": 8.16.17 - "@wdio/types": 8.24.0 - "@wdio/utils": 8.24.0 + "@wdio/types": 8.20.0 + "@wdio/utils": 8.20.0 glob: ^10.2.2 got: ^13.0.0 is-glob: ^4.0.0 - checksum: 2ebcf2b19dec06444ac44a5f6e1f7ddfa36f2b245924fb043b6e020d7f7deb0ff2d4c5a61fb274700e459179ac34174606246b01fd2463910ed50b6bf77a545b + checksum: d9fd34ee3945d5f61df6519283912c76511d516ecec9f1dc1cb2f094c6869b54a58b42ae01d9d48f3948bea2e7c8a2485e53d2ff139617859786b59a460ea009 languageName: node linkType: hard -"@wdio/devtools-service@npm:8.24.0": - version: 8.24.0 - resolution: "@wdio/devtools-service@npm:8.24.0" +"@wdio/devtools-service@npm:8.20.0": + version: 8.20.0 + resolution: "@wdio/devtools-service@npm:8.20.0" dependencies: "@babel/core": ^7.18.0 "@tracerbench/trace-event": ^8.0.0 "@types/node": ^20.1.0 "@wdio/logger": 8.16.17 - "@wdio/types": 8.24.0 + "@wdio/types": 8.20.0 babel-plugin-istanbul: ^6.1.1 - devtools-protocol: ^0.0.1213968 + devtools-protocol: ^0.0.1209236 istanbul-lib-coverage: ^3.2.0 istanbul-lib-report: ^3.0.0 istanbul-reports: ^3.1.4 @@ -16247,12 +16243,27 @@ __metadata: puppeteer-core: 20.3.0 speedline: ^1.4.3 stable: ^0.1.8 - webdriverio: 8.24.0 - checksum: e16e781227171ec163566f51a9c35ba374919e959464f537a9ffa34769a5e6a3d1b527c589cbf19acdef7d7662e014a94cdf631bbfbdd85ff07454b54736cdae + webdriverio: 8.20.0 + checksum: 7badaa54dcca22695190bf859acfc6828e6357b4d6d0ba7f00d308469c4c1a562ced2ad664e324ea87f0ab6767cd528c69d1dba745772fc8530f700f43b1c3ac languageName: node linkType: hard -"@wdio/globals@npm:8.24.0, @wdio/globals@npm:^8.23.1": +"@wdio/globals@npm:8.20.0": + version: 8.20.0 + resolution: "@wdio/globals@npm:8.20.0" + dependencies: + expect-webdriverio: ^4.2.5 + webdriverio: 8.20.0 + dependenciesMeta: + expect-webdriverio: + optional: true + webdriverio: + optional: true + checksum: f32c6853001533540f06c1a1bc5d56898d7ae045baca814754ca7b70eb9c344e10a247b95ebd00c004a4a2f95b8a26cf1d7104e28b12d60ff3416ebce659f60e + languageName: node + linkType: hard + +"@wdio/globals@npm:^8.23.1": version: 8.24.0 resolution: "@wdio/globals@npm:8.24.0" dependencies: @@ -16267,19 +16278,19 @@ __metadata: languageName: node linkType: hard -"@wdio/local-runner@npm:8.24.0": - version: 8.24.0 - resolution: "@wdio/local-runner@npm:8.24.0" +"@wdio/local-runner@npm:8.20.0": + version: 8.20.0 + resolution: "@wdio/local-runner@npm:8.20.0" dependencies: "@types/node": ^20.1.0 "@wdio/logger": 8.16.17 - "@wdio/repl": 8.23.1 - "@wdio/runner": 8.24.0 - "@wdio/types": 8.24.0 + "@wdio/repl": 8.10.1 + "@wdio/runner": 8.20.0 + "@wdio/types": 8.20.0 async-exit-hook: ^2.0.1 split2: ^4.1.0 stream-buffers: ^3.0.2 - checksum: 47a8bc4a19d161e2f1daa930182a24a26da1f2835d5b48b33329c23369d1029f9e5ffc2bc8875bd9339603cd84c558e2a5df3b643c51d2f42a2b65595d25f457 + checksum: 505249930713aebd90be67fcd80609abb88c38db9ccb635d5d667142ff4c2a7ca7e9277c0933f03f9e1fa448543c2681bb44ef1a4f571f85b07b004e0afcde68 languageName: node linkType: hard @@ -16307,6 +16318,13 @@ __metadata: languageName: node linkType: hard +"@wdio/protocols@npm:8.18.0": + version: 8.18.0 + resolution: "@wdio/protocols@npm:8.18.0" + checksum: 290ba962644131039102c6ba73ab2326d1bd3d94669bf41f5242b4e44bbf306e992221f054b33cce895a40eff01046c8bc81ad289f07bdbeab3bf8d84dc640bb + languageName: node + linkType: hard + "@wdio/protocols@npm:8.23.0": version: 8.23.0 resolution: "@wdio/protocols@npm:8.23.0" @@ -16314,6 +16332,15 @@ __metadata: languageName: node linkType: hard +"@wdio/repl@npm:8.10.1": + version: 8.10.1 + resolution: "@wdio/repl@npm:8.10.1" + dependencies: + "@types/node": ^20.1.0 + checksum: 7c770769e3db82f743f2dc9f604da8200f6eb7dfe4a708ed0b30e9c9b5c9c627342455991917c884d76448e4cc31054b85f9f843ba09c166faa32de9934571b3 + languageName: node + linkType: hard + "@wdio/repl@npm:8.23.1": version: 8.23.1 resolution: "@wdio/repl@npm:8.23.1" @@ -16323,48 +16350,57 @@ __metadata: languageName: node linkType: hard -"@wdio/reporter@npm:8.24.0": - version: 8.24.0 - resolution: "@wdio/reporter@npm:8.24.0" +"@wdio/reporter@npm:8.20.0": + version: 8.20.0 + resolution: "@wdio/reporter@npm:8.20.0" dependencies: "@types/node": ^20.1.0 "@wdio/logger": 8.16.17 - "@wdio/types": 8.24.0 + "@wdio/types": 8.20.0 diff: ^5.0.0 object-inspect: ^1.12.0 - checksum: 78643a430f3b6a32dc1a94f9de83bd81f221aee12e9c3d72e7dcb6a1a86ff8dcaa89c7363ae2b48c7d097122af784d7ca3f8b1d464ac231c1f70a12bc93fd8f6 + checksum: 67ce0056357db17bbffc7363b79fad66c55be244c9ac23a2c46d1368295ed7d3f50f6160f7d604ffdb07ef5600f996a8e8cd36d1f72fa2538c5b566424d8e912 languageName: node linkType: hard -"@wdio/runner@npm:8.24.0": - version: 8.24.0 - resolution: "@wdio/runner@npm:8.24.0" +"@wdio/runner@npm:8.20.0": + version: 8.20.0 + resolution: "@wdio/runner@npm:8.20.0" dependencies: "@types/node": ^20.1.0 - "@wdio/config": 8.24.0 - "@wdio/globals": 8.24.0 + "@wdio/config": 8.20.0 + "@wdio/globals": 8.20.0 "@wdio/logger": 8.16.17 - "@wdio/types": 8.24.0 - "@wdio/utils": 8.24.0 + "@wdio/types": 8.20.0 + "@wdio/utils": 8.20.0 deepmerge-ts: ^5.0.0 - expect-webdriverio: ^4.6.1 + expect-webdriverio: ^4.2.5 gaze: ^1.1.2 - webdriver: 8.24.0 - webdriverio: 8.24.0 - checksum: 3d2cbaa81f33aa441d4e4ebd7ab149485a0d9599e7e9596335ab44e9c992fe18535a6d03c1cab4cce68229719d74d99b9a208a1b7fe2bbe0b325f36da0005b7c + webdriver: 8.20.0 + webdriverio: 8.20.0 + checksum: 375f87b5a5cda3456aef346b0e849f7b6ac78834370715f84b817d64310e004683200c50d9a8dbd06a86a276b60214e553afd6ea06970c2123ec66b7c615a461 languageName: node linkType: hard -"@wdio/spec-reporter@npm:8.24.0": - version: 8.24.0 - resolution: "@wdio/spec-reporter@npm:8.24.0" +"@wdio/spec-reporter@npm:8.20.0": + version: 8.20.0 + resolution: "@wdio/spec-reporter@npm:8.20.0" dependencies: - "@wdio/reporter": 8.24.0 - "@wdio/types": 8.24.0 + "@wdio/reporter": 8.20.0 + "@wdio/types": 8.20.0 chalk: ^5.1.2 easy-table: ^1.2.0 pretty-ms: ^7.0.0 - checksum: 437d51b002abcd472a19462c5886e3e4dd6d02d37502205808b82e702ee7ebecd2a0a54a1957013db0c885f25d0b32e11f2010efeb5b8aeb554ffb8bfc253285 + checksum: 5114da5703d841c19dca6c77924744260fd1ee605addb2ccdc683669910f7a17cf19ca5cb3bb056c4b7b3d545f2a771a224547ebca643c9ebbb51e0e626c5b35 + languageName: node + linkType: hard + +"@wdio/types@npm:8.20.0": + version: 8.20.0 + resolution: "@wdio/types@npm:8.20.0" + dependencies: + "@types/node": ^20.1.0 + checksum: c3239b165b7f8dd3fa90f9b6a16375ad2bb5660b36eea74ab7bf50f974ce9b3d3b2e9c72a64bad7662dba04ec88bd88a9ed70c2ae0367aaf9513061e1d560f10 languageName: node linkType: hard @@ -16377,6 +16413,28 @@ __metadata: languageName: node linkType: hard +"@wdio/utils@npm:8.20.0": + version: 8.20.0 + resolution: "@wdio/utils@npm:8.20.0" + dependencies: + "@puppeteer/browsers": ^1.6.0 + "@wdio/logger": 8.16.17 + "@wdio/types": 8.20.0 + decamelize: ^6.0.0 + deepmerge-ts: ^5.1.0 + edgedriver: ^5.3.5 + geckodriver: ^4.2.0 + get-port: ^7.0.0 + got: ^13.0.0 + import-meta-resolve: ^3.0.0 + locate-app: ^2.1.0 + safaridriver: ^0.1.0 + split2: ^4.2.0 + wait-port: ^1.0.4 + checksum: cbaa3b5e177e246ebde6e610724ab0076ed854ec081b5d64a1c06b9fcf7d7ee543c2db1f23af5299a2d0d307e8af387529ce2ef697ee96ba2d8247f910c42295 + languageName: node + linkType: hard + "@wdio/utils@npm:8.24.0": version: 8.24.0 resolution: "@wdio/utils@npm:8.24.0" @@ -22867,15 +22925,6 @@ __metadata: languageName: node linkType: hard -"detect-package-manager@npm:^3.0.1": - version: 3.0.1 - resolution: "detect-package-manager@npm:3.0.1" - dependencies: - execa: ^5.1.1 - checksum: 3a203ba269183baea9969b89f18dd93a9bb451a51280b39ea2ee392f44bada0f79b676425fcd1e7fca6b8c8f03a048a5cc1d3b39bb9c0ebd0300337ae00a0b14 - languageName: node - linkType: hard - "detect-port@npm:^1.3.0": version: 1.3.0 resolution: "detect-port@npm:1.3.0" @@ -22903,6 +22952,13 @@ __metadata: languageName: node linkType: hard +"devtools-protocol@npm:^0.0.1209236": + version: 0.0.1209236 + resolution: "devtools-protocol@npm:0.0.1209236" + checksum: f50157153417e1909ac4ad708df3b67570a64766c9a187c57b104ac8472b90b91f6d2b75715c65ffff68715f8756e52614d2e4305cda208dcdda0b76b6e0e82c + languageName: node + linkType: hard + "devtools-protocol@npm:^0.0.1213968": version: 0.0.1213968 resolution: "devtools-protocol@npm:0.0.1213968" @@ -25427,7 +25483,7 @@ __metadata: languageName: node linkType: hard -"expect-webdriverio@npm:^4.6.1": +"expect-webdriverio@npm:^4.2.5, expect-webdriverio@npm:^4.6.1": version: 4.6.1 resolution: "expect-webdriverio@npm:4.6.1" dependencies: @@ -28836,11 +28892,11 @@ __metadata: languageName: node linkType: hard -"inquirer@npm:9.2.12": - version: 9.2.12 - resolution: "inquirer@npm:9.2.12" +"inquirer@npm:9.2.11": + version: 9.2.11 + resolution: "inquirer@npm:9.2.11" dependencies: - "@ljharb/through": ^2.3.11 + "@ljharb/through": ^2.3.9 ansi-escapes: ^4.3.2 chalk: ^5.3.0 cli-cursor: ^3.1.0 @@ -28855,7 +28911,7 @@ __metadata: string-width: ^4.2.3 strip-ansi: ^6.0.1 wrap-ansi: ^6.2.0 - checksum: 8c372832367f5adb4bb08a0c3ee3b8b16e83202c125d1a681ece2c0ef2f00a5d7d6589a501fd58a0249b4ad49a8013584ac58ae12a20d29b1c24a0ec450927a5 + checksum: af59b422eb6005dac90f6c5e8295013d0611ac5471ff4fbf4ad3e228136e0f41db73af2d5a68e36770f9e31ac203ae1589d35c3e970acbc6110bb5df905928f9 languageName: node linkType: hard @@ -40005,17 +40061,7 @@ __metadata: languageName: node linkType: hard -"read-pkg-up@npm:^1.0.1": - version: 1.0.1 - resolution: "read-pkg-up@npm:1.0.1" - dependencies: - find-up: ^1.0.0 - read-pkg: ^1.0.0 - checksum: d18399a0f46e2da32beb2f041edd0cda49d2f2cc30195a05c759ef3ed9b5e6e19ba1ad1bae2362bdec8c6a9f2c3d18f4d5e8c369e808b03d498d5781cb9122c7 - languageName: node - linkType: hard - -"read-pkg-up@npm:^10.0.0": +"read-pkg-up@npm:10.1.0, read-pkg-up@npm:^10.0.0": version: 10.1.0 resolution: "read-pkg-up@npm:10.1.0" dependencies: @@ -40026,6 +40072,16 @@ __metadata: languageName: node linkType: hard +"read-pkg-up@npm:^1.0.1": + version: 1.0.1 + resolution: "read-pkg-up@npm:1.0.1" + dependencies: + find-up: ^1.0.0 + read-pkg: ^1.0.0 + checksum: d18399a0f46e2da32beb2f041edd0cda49d2f2cc30195a05c759ef3ed9b5e6e19ba1ad1bae2362bdec8c6a9f2c3d18f4d5e8c369e808b03d498d5781cb9122c7 + languageName: node + linkType: hard + "read-pkg-up@npm:^3.0.0": version: 3.0.0 resolution: "read-pkg-up@npm:3.0.0" @@ -46086,15 +46142,6 @@ __metadata: languageName: node linkType: hard -"uuid@npm:9.0.1": - version: 9.0.1 - resolution: "uuid@npm:9.0.1" - bin: - uuid: dist/bin/uuid - checksum: 39931f6da74e307f51c0fb463dc2462807531dc80760a9bff1e35af4316131b4fc3203d16da60ae33f07fdca5b56f3f1dd662da0c99fea9aaeab2004780cc5f4 - languageName: node - linkType: hard - "uuid@npm:^3.3.2": version: 3.4.0 resolution: "uuid@npm:3.4.0" @@ -46960,6 +47007,25 @@ __metadata: languageName: node linkType: hard +"webdriver@npm:8.20.0": + version: 8.20.0 + resolution: "webdriver@npm:8.20.0" + dependencies: + "@types/node": ^20.1.0 + "@types/ws": ^8.5.3 + "@wdio/config": 8.20.0 + "@wdio/logger": 8.16.17 + "@wdio/protocols": 8.18.0 + "@wdio/types": 8.20.0 + "@wdio/utils": 8.20.0 + deepmerge-ts: ^5.1.0 + got: ^ 12.6.1 + ky: ^0.33.0 + ws: ^8.8.0 + checksum: bc14cc9aabc406fbb02cf2451adbba901f134535c36b69173451552c10297629f52f3d9e8120d425b10490a95b5ac1bace88d5ed1af75db39cc2664ff45b9292 + languageName: node + linkType: hard + "webdriver@npm:8.24.0": version: 8.24.0 resolution: "webdriver@npm:8.24.0" @@ -46979,6 +47045,43 @@ __metadata: languageName: node linkType: hard +"webdriverio@npm:8.20.0": + version: 8.20.0 + resolution: "webdriverio@npm:8.20.0" + dependencies: + "@types/node": ^20.1.0 + "@wdio/config": 8.20.0 + "@wdio/logger": 8.16.17 + "@wdio/protocols": 8.18.0 + "@wdio/repl": 8.10.1 + "@wdio/types": 8.20.0 + "@wdio/utils": 8.20.0 + archiver: ^6.0.0 + aria-query: ^5.0.0 + css-shorthand-properties: ^1.1.1 + css-value: ^0.0.1 + devtools-protocol: ^0.0.1209236 + grapheme-splitter: ^1.0.2 + import-meta-resolve: ^3.0.0 + is-plain-obj: ^4.1.0 + lodash.clonedeep: ^4.5.0 + lodash.zip: ^4.2.0 + minimatch: ^9.0.0 + puppeteer-core: ^20.9.0 + query-selector-shadow-dom: ^1.0.0 + resq: ^1.9.1 + rgb2hex: 0.2.5 + serialize-error: ^11.0.1 + webdriver: 8.20.0 + peerDependencies: + devtools: ^8.14.0 + peerDependenciesMeta: + devtools: + optional: true + checksum: 9e36a9abaa704f6e6a7af9e5048a3ab9d44bd19a86998d7a1c427cdfc48eccd4ce54f7be761c2298cac84affc6c62e103ed0b4a8a80d48459fdb82216246c367 + languageName: node + linkType: hard + "webdriverio@npm:8.24.0, webdriverio@npm:^8.23.1": version: 8.24.0 resolution: "webdriverio@npm:8.24.0" From 5b71918b5a68351abd2f5de776681295195086bd Mon Sep 17 00:00:00 2001 From: Tomek Marciniak <16132011+mrcnk@users.noreply.github.com> Date: Tue, 28 Nov 2023 19:25:08 +0100 Subject: [PATCH 05/16] feat(staking): [LW-6437, LW-8877] past epochs rewards chart (#718) --------- Co-authored-by: refi93 Co-authored-by: przemyslaw.wlodek Co-authored-by: januszjanus --- packages/staking/package.json | 3 + .../src/features/activity/Activity.tsx | 12 ++- .../activity/EpochsSwitch.module.scss | 4 + .../src/features/activity/EpochsSwitch.tsx | 33 ++++++++ .../PastEpochsRewards/PastEpochsRewards.tsx | 23 ++++++ .../PastEpochsRewards/PoolIndicator.tsx | 7 ++ .../PastEpochsRewards/RewardsChart.tsx | 41 ++++++++++ .../PastEpochsRewards/RewardsChartTooltip.tsx | 45 ++++++++++ .../hooks/useRewardsByEpoch.ts | 82 +++++++++++++++++++ .../hooks/useRewardsChartPoolsColorMapper.tsx | 47 +++++++++++ .../activity/PastEpochsRewards/index.ts | 1 + .../src/features/activity/RewardsHistory.tsx | 2 +- .../src/features/i18n/translations/en.ts | 6 ++ packages/staking/src/features/i18n/types.ts | 8 ++ .../stateMachine/types.ts | 3 + yarn.lock | 47 ++++++++++- 16 files changed, 358 insertions(+), 6 deletions(-) create mode 100644 packages/staking/src/features/activity/EpochsSwitch.module.scss create mode 100644 packages/staking/src/features/activity/EpochsSwitch.tsx create mode 100644 packages/staking/src/features/activity/PastEpochsRewards/PastEpochsRewards.tsx create mode 100644 packages/staking/src/features/activity/PastEpochsRewards/PoolIndicator.tsx create mode 100644 packages/staking/src/features/activity/PastEpochsRewards/RewardsChart.tsx create mode 100644 packages/staking/src/features/activity/PastEpochsRewards/RewardsChartTooltip.tsx create mode 100644 packages/staking/src/features/activity/PastEpochsRewards/hooks/useRewardsByEpoch.ts create mode 100644 packages/staking/src/features/activity/PastEpochsRewards/hooks/useRewardsChartPoolsColorMapper.tsx create mode 100644 packages/staking/src/features/activity/PastEpochsRewards/index.ts diff --git a/packages/staking/package.json b/packages/staking/package.json index 4d44c00627..bc2f8734e9 100644 --- a/packages/staking/package.json +++ b/packages/staking/package.json @@ -59,11 +59,14 @@ "i18next": "^22.5.1", "immer": "^10.0.2", "lodash": "4.17.21", + "rambda": "^8.5.0", "react-copy-to-clipboard": "^5.1.0", "react-i18next": "^12.3.1", + "recharts": "^2.9.2", "zustand": "^4.4.1" }, "devDependencies": { + "@cardano-sdk/core": "0.21.0", "@cardano-sdk/input-selection": "0.12.4", "@cardano-sdk/tx-construction": "0.14.2", "@cardano-sdk/util": "0.14.2", diff --git a/packages/staking/src/features/activity/Activity.tsx b/packages/staking/src/features/activity/Activity.tsx index f6fb246cc4..80801527d8 100644 --- a/packages/staking/src/features/activity/Activity.tsx +++ b/packages/staking/src/features/activity/Activity.tsx @@ -1,6 +1,7 @@ import { StateStatus, useOutsideHandles } from 'features/outside-handles-provider'; import { getGroupedRewardsActivities } from './helpers/getGroupedRewardsHistory'; import { NoStakingActivity } from './NoStakingActivity'; +import { PastEpochsRewards } from './PastEpochsRewards'; import { RewardsHistory } from './RewardsHistory'; export const Activity = () => { @@ -13,10 +14,13 @@ export const Activity = () => { {walletActivitiesStatus === StateStatus.LOADED && groupedRewardsActivities.length === 0 ? ( ) : ( - + <> + + + )} ); diff --git a/packages/staking/src/features/activity/EpochsSwitch.module.scss b/packages/staking/src/features/activity/EpochsSwitch.module.scss new file mode 100644 index 0000000000..de49cf4684 --- /dev/null +++ b/packages/staking/src/features/activity/EpochsSwitch.module.scss @@ -0,0 +1,4 @@ +.buttonsBackground { + background-color: var(--light-mode-light-grey, var(--dark-mode-dark-grey, #2f2f2f)); + border-radius: 1rem; +} diff --git a/packages/staking/src/features/activity/EpochsSwitch.tsx b/packages/staking/src/features/activity/EpochsSwitch.tsx new file mode 100644 index 0000000000..d72cb2b819 --- /dev/null +++ b/packages/staking/src/features/activity/EpochsSwitch.tsx @@ -0,0 +1,33 @@ +import { ControlButton, Flex, Text } from '@lace/ui'; +import { useTranslation } from 'react-i18next'; +import styles from './EpochsSwitch.module.scss'; + +// eslint-disable-next-line no-magic-numbers +const EPOCHS_OPTIONS = [5, 15]; + +type EpochsSwitchProps = { + epochsCount: number; + setEpochsCount: (epochsCount: number) => void; +}; + +export const EpochsSwitch = ({ epochsCount, setEpochsCount }: EpochsSwitchProps) => { + const { t } = useTranslation(); + return ( + + {t('activity.rewardsChart.epochs')}: + + {EPOCHS_OPTIONS.map((option, i) => { + const activeOption = epochsCount === option; + const Component = activeOption ? ControlButton.Filled : ControlButton.Outlined; + return ( + setEpochsCount(option)} + /> + ); + })} + + + ); +}; diff --git a/packages/staking/src/features/activity/PastEpochsRewards/PastEpochsRewards.tsx b/packages/staking/src/features/activity/PastEpochsRewards/PastEpochsRewards.tsx new file mode 100644 index 0000000000..2fa6547dc0 --- /dev/null +++ b/packages/staking/src/features/activity/PastEpochsRewards/PastEpochsRewards.tsx @@ -0,0 +1,23 @@ +import { Flex, Text } from '@lace/ui'; +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { EpochsSwitch } from '../EpochsSwitch'; +import { useRewardsByEpoch } from './hooks/useRewardsByEpoch'; +import { RewardsChart } from './RewardsChart'; + +const DEFAULT_LAST_EPOCHS = 5; + +export const PastEpochsRewards = () => { + const [epochsCount, setEpochsCount] = useState(DEFAULT_LAST_EPOCHS); + const { t } = useTranslation(); + const { rewardsByEpoch } = useRewardsByEpoch({ epochsCount }); + return ( + <> + + {t('activity.rewardsChart.title')} + + + {rewardsByEpoch && } + + ); +}; diff --git a/packages/staking/src/features/activity/PastEpochsRewards/PoolIndicator.tsx b/packages/staking/src/features/activity/PastEpochsRewards/PoolIndicator.tsx new file mode 100644 index 0000000000..00dee66204 --- /dev/null +++ b/packages/staking/src/features/activity/PastEpochsRewards/PoolIndicator.tsx @@ -0,0 +1,7 @@ +import { PIE_CHART_DEFAULT_COLOR_SET } from '@lace/ui'; + +export const PoolIndicator = ({ color = PIE_CHART_DEFAULT_COLOR_SET[0] }: { color?: string }) => ( + + + +); diff --git a/packages/staking/src/features/activity/PastEpochsRewards/RewardsChart.tsx b/packages/staking/src/features/activity/PastEpochsRewards/RewardsChart.tsx new file mode 100644 index 0000000000..341791fd89 --- /dev/null +++ b/packages/staking/src/features/activity/PastEpochsRewards/RewardsChart.tsx @@ -0,0 +1,41 @@ +import { Card } from '@lace/ui'; +import { Bar, BarChart, Cell, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts'; +import type { RewardsByEpoch } from './hooks/useRewardsByEpoch'; +import { useRewardsChartPoolsColorMapper } from './hooks/useRewardsChartPoolsColorMapper'; +import { RewardsChartTooltip } from './RewardsChartTooltip'; + +export const RewardsChart = ({ chartData }: { chartData: RewardsByEpoch }) => { + const poolColorMapper = useRewardsChartPoolsColorMapper(chartData); + // eslint-disable-next-line unicorn/no-array-reduce + const maxPoolsPerEpochCount = chartData.reduce((acc, epochRewards) => Math.max(acc, epochRewards.rewards.length), 0); + + return ( + + + + + `${value} ADA`} /> + } /> + {Array.from({ length: maxPoolsPerEpochCount }).map((_, i) => ( + + {chartData.map((entry, j) => { + const fill = poolColorMapper(entry.rewards[i]?.poolId); + return ; + })} + + ))} + + + + ); +}; diff --git a/packages/staking/src/features/activity/PastEpochsRewards/RewardsChartTooltip.tsx b/packages/staking/src/features/activity/PastEpochsRewards/RewardsChartTooltip.tsx new file mode 100644 index 0000000000..edb87a2c9f --- /dev/null +++ b/packages/staking/src/features/activity/PastEpochsRewards/RewardsChartTooltip.tsx @@ -0,0 +1,45 @@ +import { Card, Flex, Text } from '@lace/ui'; +import { useTranslation } from 'react-i18next'; +import { TooltipProps } from 'recharts'; +import { NameType, ValueType } from 'recharts/types/component/DefaultTooltipContent'; +import { useRewardsChartPoolsColorMapper } from './hooks/useRewardsChartPoolsColorMapper'; +import { PoolIndicator } from './PoolIndicator'; + +export const RewardsChartTooltip = ({ + active, + payload, + label, + poolColorMapper, +}: TooltipProps & { poolColorMapper: ReturnType }) => { + const { t } = useTranslation(); + + if (active && payload && payload.length > 0) { + return ( + + + + {t('activity.rewardsChart.epoch')} {label} + + + {payload.map((p, i) => { + const poolId = p.payload?.rewards?.[i]?.poolId; + return ( + + + + {p.payload?.rewards?.[i]?.metadata.name} + + {t('activity.rewardsChart.rewards')}: {payload[i]?.value} ADA + + + + ); + })} + + + + ); + } + + return null; +}; diff --git a/packages/staking/src/features/activity/PastEpochsRewards/hooks/useRewardsByEpoch.ts b/packages/staking/src/features/activity/PastEpochsRewards/hooks/useRewardsByEpoch.ts new file mode 100644 index 0000000000..d44ce1c1fe --- /dev/null +++ b/packages/staking/src/features/activity/PastEpochsRewards/hooks/useRewardsByEpoch.ts @@ -0,0 +1,82 @@ +import { Cardano, Reward } from '@cardano-sdk/core'; +import { RewardsHistory } from '@cardano-sdk/wallet'; +import { Wallet } from '@lace/cardano'; +import { useObservable } from '@lace/common'; +import { useOutsideHandles } from 'features/outside-handles-provider'; +import { groupBy, sortBy, takeLast, uniqBy } from 'rambda'; +import { useEffect, useState } from 'react'; + +type RewardWithPoolMetadata = Omit & { + metadata: Cardano.StakePoolMetadata | undefined; + spendableEpoch: Cardano.EpochNo; + rewards: string; +}; + +export type RewardsByEpoch = { spendableEpoch: Cardano.EpochNo; rewards: RewardWithPoolMetadata[] }[]; + +export type UseRewardsByEpochProps = { + epochsCount: number; +}; + +const getPoolInfos = async (poolIds: Wallet.Cardano.PoolId[], stakePoolProvider: Wallet.StakePoolProvider) => { + const filters: Wallet.QueryStakePoolsArgs = { + filters: { + identifier: { + _condition: 'or', + values: poolIds.map((poolId) => ({ id: poolId })), + }, + }, + pagination: { + limit: 100, + startAt: 0, + }, + }; + const { pageResults: pools } = await stakePoolProvider.queryStakePools(filters); + + return pools; +}; + +type GetRewardsByEpochProps = { + rewardsHistory: RewardsHistory; + stakePoolProvider: Wallet.StakePoolProvider; + epochsCount: number; +}; + +const buildRewardsByEpoch = async ({ rewardsHistory, stakePoolProvider, epochsCount }: GetRewardsByEpochProps) => { + const REWARD_SPENDABLE_DELAY_EPOCHS = 2; + const uniqPoolIds = uniqBy((rewards) => rewards.poolId, rewardsHistory.all) + .map((reward) => reward.poolId) + .filter(Boolean) as Wallet.Cardano.PoolId[]; + const stakePoolsData = await getPoolInfos(uniqPoolIds, stakePoolProvider); + const rewardsHistoryWithMetadata = rewardsHistory.all.map((reward) => ({ + ...reward, + metadata: stakePoolsData.find((poolInfo) => poolInfo.id === reward.poolId)?.metadata, + rewards: Wallet.util.lovelacesToAdaString(reward.rewards.toString()), + spendableEpoch: (reward.epoch + REWARD_SPENDABLE_DELAY_EPOCHS) as Cardano.EpochNo, + })); + const groupedRewards = groupBy(({ epoch }) => epoch.toString(), rewardsHistoryWithMetadata); + const groupedRewardsArray = Object.entries(groupedRewards).map(([epoch, rewards]) => ({ + rewards, + spendableEpoch: (Number.parseInt(epoch) + REWARD_SPENDABLE_DELAY_EPOCHS) as Cardano.EpochNo, + })); + const sortedByEpoch = sortBy((entry) => entry.spendableEpoch, groupedRewardsArray); + return takeLast(epochsCount, sortedByEpoch); +}; + +export const useRewardsByEpoch = ({ epochsCount }: UseRewardsByEpochProps) => { + const [rewardsByEpoch, setRewardsByEpoch] = useState(); + const { walletStoreInMemoryWallet: inMemoryWallet, walletStoreBlockchainProvider } = useOutsideHandles(); + const rewardsHistory = useObservable(inMemoryWallet.delegation.rewardsHistory$); + useEffect(() => { + if (!rewardsHistory) return; + (async () => { + const result = await buildRewardsByEpoch({ + epochsCount, + rewardsHistory, + stakePoolProvider: walletStoreBlockchainProvider.stakePoolProvider, + }); + setRewardsByEpoch(result); + })(); + }, [epochsCount, rewardsHistory, walletStoreBlockchainProvider]); + return { rewardsByEpoch }; +}; diff --git a/packages/staking/src/features/activity/PastEpochsRewards/hooks/useRewardsChartPoolsColorMapper.tsx b/packages/staking/src/features/activity/PastEpochsRewards/hooks/useRewardsChartPoolsColorMapper.tsx new file mode 100644 index 0000000000..29ffe3d476 --- /dev/null +++ b/packages/staking/src/features/activity/PastEpochsRewards/hooks/useRewardsChartPoolsColorMapper.tsx @@ -0,0 +1,47 @@ +/* eslint-disable unicorn/no-array-reduce */ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { PIE_CHART_DEFAULT_COLOR_SET } from '@lace/ui'; +import { useDelegationPortfolioStore } from 'features/store'; +import difference from 'lodash/difference'; +import { useMemo } from 'react'; +import type { RewardsByEpoch } from './useRewardsByEpoch'; +import type { Cardano } from '@cardano-sdk/core'; + +const GRAYSCALE_PALETTE = [ + '#343434', + '#4a4a4a', + '#616161', + '#787878', + '#8e8e8e', + '#a5a5a5', + '#bbbbbb', + '#d2d2d2', + '#e8e8e8', + '#fafafa', +]; + +export const useRewardsChartPoolsColorMapper = (rewardsByEpoch: RewardsByEpoch) => { + const { currentPortfolio } = useDelegationPortfolioStore(); + + const coloring = useMemo(() => { + const poolsInPortfolio = currentPortfolio.map(({ stakePool }) => stakePool.id); + const historicalPools = rewardsByEpoch + .flatMap((rewards) => rewards.rewards.map((reward) => reward.poolId)) + .filter(Boolean) as Cardano.PoolId[]; + const poolsNotInPortfolio = difference(historicalPools, poolsInPortfolio); + + const poolsInPortfolioColoring = poolsInPortfolio.reduce((acc, poolId, index) => { + acc[poolId] = PIE_CHART_DEFAULT_COLOR_SET[index % PIE_CHART_DEFAULT_COLOR_SET.length]!; + return acc; + }, {} as Record); + + const poolsNotInPortfolioColoring = poolsNotInPortfolio.reduce((acc, poolId, index) => { + acc[poolId] = GRAYSCALE_PALETTE[index % GRAYSCALE_PALETTE.length]!; + return acc; + }, {} as Record); + + return { ...poolsInPortfolioColoring, ...poolsNotInPortfolioColoring }; + }, [currentPortfolio, rewardsByEpoch]); + + return (poolId?: Cardano.PoolId) => (poolId ? coloring[poolId] : GRAYSCALE_PALETTE[0]); +}; diff --git a/packages/staking/src/features/activity/PastEpochsRewards/index.ts b/packages/staking/src/features/activity/PastEpochsRewards/index.ts new file mode 100644 index 0000000000..3cdf22ed5a --- /dev/null +++ b/packages/staking/src/features/activity/PastEpochsRewards/index.ts @@ -0,0 +1 @@ +export { PastEpochsRewards } from './PastEpochsRewards'; diff --git a/packages/staking/src/features/activity/RewardsHistory.tsx b/packages/staking/src/features/activity/RewardsHistory.tsx index 4f2a2cc45f..d17c010722 100644 --- a/packages/staking/src/features/activity/RewardsHistory.tsx +++ b/packages/staking/src/features/activity/RewardsHistory.tsx @@ -15,7 +15,7 @@ export const RewardsHistory = ({ groupedRewardsActivities, walletActivitiesStatu return ( <> - + {t('activity.rewardsHistory.title')} diff --git a/packages/staking/src/features/i18n/translations/en.ts b/packages/staking/src/features/i18n/translations/en.ts index 7a61df7d70..51328d6be1 100644 --- a/packages/staking/src/features/i18n/translations/en.ts +++ b/packages/staking/src/features/i18n/translations/en.ts @@ -1,6 +1,12 @@ import { Translations } from '../types'; export const en: Translations = { + 'activity.rewardsChart.all': 'All', + 'activity.rewardsChart.epoch': 'Epoch', + 'activity.rewardsChart.epochs': 'Epochs', + 'activity.rewardsChart.last': 'Last', + 'activity.rewardsChart.rewards': 'Rewards', + 'activity.rewardsChart.title': 'Rewards', 'activity.rewardsHistory.noStakingActivityYet': 'No staking activity yet.', 'activity.rewardsHistory.title': 'History', 'browsePools.stakePoolTableBrowser.addPool': 'Add pool', diff --git a/packages/staking/src/features/i18n/types.ts b/packages/staking/src/features/i18n/types.ts index bec77a1cb5..c191e5af8b 100644 --- a/packages/staking/src/features/i18n/types.ts +++ b/packages/staking/src/features/i18n/types.ts @@ -20,6 +20,14 @@ type KeysStructure = { title: ''; noStakingActivityYet: ''; }; + rewardsChart: { + title: ''; + epochs: ''; + epoch: ''; + last: ''; + all: ''; + rewards: ''; + }; }; browsePools: { stakePoolTableBrowser: { diff --git a/packages/staking/src/features/store/delegationPortfolioStore/stateMachine/types.ts b/packages/staking/src/features/store/delegationPortfolioStore/stateMachine/types.ts index 41b5639f85..84a6e3ef8a 100644 --- a/packages/staking/src/features/store/delegationPortfolioStore/stateMachine/types.ts +++ b/packages/staking/src/features/store/delegationPortfolioStore/stateMachine/types.ts @@ -89,6 +89,9 @@ export type StateOverview = MakeState<{ export type StateActivity = MakeState<{ activeDelegationFlow: DelegationFlow.Activity; activeDrawerStep: undefined; + draftPortfolio: undefined; + pendingSelectedPortfolio: undefined; + viewedStakePool: undefined; }>; export type StateCurrentPoolDetails = MakeState<{ diff --git a/yarn.lock b/yarn.lock index edcdec2482..c3954a3db9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8028,6 +8028,7 @@ __metadata: resolution: "@lace/staking@workspace:packages/staking" dependencies: "@ant-design/icons": ^4.7.0 + "@cardano-sdk/core": 0.21.0 "@cardano-sdk/input-selection": 0.12.4 "@cardano-sdk/tx-construction": 0.14.2 "@cardano-sdk/util": 0.14.2 @@ -8055,11 +8056,13 @@ __metadata: immer: ^10.0.2 lodash: 4.17.21 normalize.css: ^8.0.1 + rambda: ^8.5.0 react: 17.0.2 react-copy-to-clipboard: ^5.1.0 react-dom: 17.0.2 react-i18next: ^12.3.1 react-infinite-scroll-component: ^6.1.0 + recharts: ^2.9.2 tsup: ^6.7.0 typescript: ^4.9.5 vite-plugin-checker: ^0.6.0 @@ -38459,6 +38462,13 @@ __metadata: languageName: node linkType: hard +"rambda@npm:^8.5.0": + version: 8.5.0 + resolution: "rambda@npm:8.5.0" + checksum: 4f188195f9859e5b0955f5eddfa454855d3d390c8dfdf22f19b6e493ad978ba1123b09201ef0ebddc2ab608ef22b68c541582a1810cbf3f789870dd10bba5598 + languageName: node + linkType: hard + "ramda@npm:^0.21.0": version: 0.21.0 resolution: "ramda@npm:0.21.0" @@ -39949,6 +39959,20 @@ __metadata: languageName: node linkType: hard +"react-smooth@npm:^2.0.4": + version: 2.0.5 + resolution: "react-smooth@npm:2.0.5" + dependencies: + fast-equals: ^5.0.0 + react-transition-group: 2.9.0 + peerDependencies: + prop-types: ^15.6.0 + react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + checksum: 914c17f741e8b533ff6e3d5e3285aea0625cdd0f98e04202d01351f9516dbdc0a0e297dc22cc2377d6916fb819da8d4ed999c0314a4c186592ca51870012e6f7 + languageName: node + linkType: hard + "react-style-singleton@npm:^2.2.1": version: 2.2.1 resolution: "react-style-singleton@npm:2.2.1" @@ -40299,6 +40323,27 @@ __metadata: languageName: node linkType: hard +"recharts@npm:^2.9.2": + version: 2.9.2 + resolution: "recharts@npm:2.9.2" + dependencies: + classnames: ^2.2.5 + eventemitter3: ^4.0.1 + lodash: ^4.17.19 + react-is: ^16.10.2 + react-resize-detector: ^8.0.4 + react-smooth: ^2.0.4 + recharts-scale: ^0.4.4 + tiny-invariant: ^1.3.1 + victory-vendor: ^36.6.8 + peerDependencies: + prop-types: ^15.6.0 + react: ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 + checksum: 77a87e3d91229ac5400240409568e3345ded50fc117e70e43e61a135b44bbc3164048704393aa988201ea2989278e1c4e96ce350f6a2f87044d1f0a48f290e84 + languageName: node + linkType: hard + "rechoir@npm:^0.6.2": version: 0.6.2 resolution: "rechoir@npm:0.6.2" @@ -44389,7 +44434,7 @@ __metadata: languageName: node linkType: hard -"tiny-invariant@npm:^1.1.0": +"tiny-invariant@npm:^1.1.0, tiny-invariant@npm:^1.3.1": version: 1.3.1 resolution: "tiny-invariant@npm:1.3.1" checksum: 872dbd1ff20a21303a2fd20ce3a15602cfa7fcf9b228bd694a52e2938224313b5385a1078cb667ed7375d1612194feaca81c4ecbe93121ca1baebe344de4f84c From 4020bc97b0ac996ff896f15f69a6751645e57f0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Korba=C5=A1?= Date: Tue, 28 Nov 2023 19:30:27 +0100 Subject: [PATCH 06/16] feat(extension): track manual re-sync and hd wallet discovery (#712) --- .../settings/components/SettingsWalletBase.tsx | 16 ++++++++++++++-- .../components/HardwareWalletFlow.tsx | 9 +++++++++ .../components/WalletSetupWizard.tsx | 10 ++++++++++ packages/common/src/analytics/types.ts | 3 +++ 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/apps/browser-extension-wallet/src/views/browser-view/features/settings/components/SettingsWalletBase.tsx b/apps/browser-extension-wallet/src/views/browser-view/features/settings/components/SettingsWalletBase.tsx index c634abb085..1dfe5636a3 100644 --- a/apps/browser-extension-wallet/src/views/browser-view/features/settings/components/SettingsWalletBase.tsx +++ b/apps/browser-extension-wallet/src/views/browser-view/features/settings/components/SettingsWalletBase.tsx @@ -15,6 +15,7 @@ import { useAnalyticsContext, useBackgroundServiceAPIContext } from '@providers' import { useSearchParams, useObservable, Button } from '@lace/common'; import { walletRoutePaths } from '@routes/wallet-paths'; import { PostHogAction } from '@providers/AnalyticsProvider/analyticsTracker'; +import uniq from 'lodash/uniq'; const { Title } = Typography; @@ -58,7 +59,7 @@ export const SettingsWalletBase = ({ const { t } = useTranslation(); const { addressesDiscoverer } = useAddressesDiscoverer(); - const { environmentName, inMemoryWallet } = useWalletStore(); + const { environmentName, inMemoryWallet, walletInfo } = useWalletStore(); const { AVAILABLE_CHAINS } = config(); const unspendable = useObservable(inMemoryWallet.balance.utxo.unspendable$); @@ -116,7 +117,18 @@ export const SettingsWalletBase = ({ ) : ( +
+ ) : ( + + )} ); }; diff --git a/apps/browser-extension-wallet/src/components/ExpandButton/ExpandButton.module.scss b/apps/browser-extension-wallet/src/components/ExpandButton/ExpandButton.module.scss index dfa9d4f4f9..e16f9a4a5f 100644 --- a/apps/browser-extension-wallet/src/components/ExpandButton/ExpandButton.module.scss +++ b/apps/browser-extension-wallet/src/components/ExpandButton/ExpandButton.module.scss @@ -36,6 +36,21 @@ } } +.multiWallet { + max-width: size_unit(6); + width: size_unit(6); + height: size_unit(6); + border-radius: size_unit(2); + flex-shrink: 0; + + &:hover { + max-width: size_unit(6); + width: size_unit(6); + gap: 0px; + padding: 0; + } +} + .text { color: var(--text-color-secondary); white-space: nowrap; diff --git a/apps/browser-extension-wallet/src/components/ExpandButton/ExpandButton.tsx b/apps/browser-extension-wallet/src/components/ExpandButton/ExpandButton.tsx index 1e7da942e1..5abafd532a 100644 --- a/apps/browser-extension-wallet/src/components/ExpandButton/ExpandButton.tsx +++ b/apps/browser-extension-wallet/src/components/ExpandButton/ExpandButton.tsx @@ -1,11 +1,31 @@ -import React from 'react'; +/* eslint-disable react/no-multi-comp */ +import React, { ReactNode } from 'react'; import ExpandIcon from '../../assets/icons/expand.component.svg'; +import classnames from 'classnames'; +import { Tooltip } from 'antd'; import styles from './ExpandButton.module.scss'; +const RenderTooltipIfMultiWallet = ({ children, label }: { children: ReactNode; label: string }) => { + if (process.env.USE_MULTI_WALLET === 'true') { + return {children}; + } + + return <>{children}; +}; + export const ExpandButton = ({ label, onClick }: { label: string; onClick: () => void }): React.ReactElement => ( - - - {label} - + + + + {process.env.USE_MULTI_WALLET !== 'true' && {label}} + + ); diff --git a/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/DropdownMenuOverlay.module.scss b/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/DropdownMenuOverlay.module.scss index 098024b802..e43bee1f1f 100644 --- a/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/DropdownMenuOverlay.module.scss +++ b/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/DropdownMenuOverlay.module.scss @@ -17,7 +17,6 @@ .userInfoWrapper { display: flex; flex-direction: column; - padding: 10px size_unit(2) size_unit(2.75); gap: size_unit(2); .userInfo { @@ -50,6 +49,14 @@ } } + .singleWalletWrapper { + padding: 10px size_unit(2) size_unit(2.75); + } + + .multiWalletWrapper { + padding-bottom: size_unit(2.75); + } + .walletStatusInfo { cursor: default; display: flex; diff --git a/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/DropdownMenuOverlay.tsx b/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/DropdownMenuOverlay.tsx index da8edbfe45..b48c5e9270 100644 --- a/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/DropdownMenuOverlay.tsx +++ b/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/DropdownMenuOverlay.tsx @@ -8,7 +8,8 @@ import { ThemeSwitcher, LockWallet, UserInfo, - NetworkChoise + NetworkChoise, + AddNewWalletLink } from './components'; import styles from './DropdownMenuOverlay.module.scss'; import { NetworkInfo } from './components/NetworkInfo'; @@ -42,6 +43,7 @@ export const DropdownMenuOverlay: VFC = ({ <> {topSection} + {process.env.USE_MULTI_WALLET === 'true' && } diff --git a/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/components/AddNewWalletLink.tsx b/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/components/AddNewWalletLink.tsx new file mode 100644 index 0000000000..281fa80ea5 --- /dev/null +++ b/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/components/AddNewWalletLink.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { walletRoutePaths } from '@routes'; +import { Menu } from 'antd'; +import { useTranslation } from 'react-i18next'; +import { Link } from 'react-router-dom'; +import styles from '../DropdownMenuOverlay.module.scss'; + +const handleOnClicked = (): void => void 0; + +export const AddNewWalletLink = (): React.ReactElement => { + const { t } = useTranslation(); + + return ( + + + {t('browserView.sideMenu.links.addNewWallet')} + + + ); +}; diff --git a/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/components/UserInfo.tsx b/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/components/UserInfo.tsx index 801c51ae89..e36d810bd7 100644 --- a/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/components/UserInfo.tsx +++ b/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/components/UserInfo.tsx @@ -11,6 +11,8 @@ import { UserAvatar } from './UserAvatar'; import { useGetHandles } from '@hooks'; import { useAnalyticsContext } from '@providers'; import { PostHogAction } from '@providers/AnalyticsProvider/analyticsTracker'; +import { ProfileDropdown } from '@lace/ui'; +import { getAssetImageUrl } from '@src/utils/get-asset-image-url'; const ADRESS_FIRST_PART_LENGTH = 10; const ADRESS_LAST_PART_LENGTH = 5; @@ -36,6 +38,7 @@ export const UserInfo = ({ avatarVisible = true }: UserInfoProps): React.ReactEl const walletName = addEllipsis(walletInfo.name.toString(), WALLET_NAME_MAX_LENGTH, 0); const [handle] = useGetHandles(); const handleName = handle?.nftMetadata?.name; + const handleImage = handle?.profilePic; const handleOnAddressCopy = () => { toast.notify({ duration: TOAST_DEFAULT_DURATION, text: t('general.clipboard.copiedToClipboard') }); @@ -44,29 +47,51 @@ export const UserInfo = ({ avatarVisible = true }: UserInfoProps): React.ReactEl return ( -
+
- - {handleName ? t('settings.copyHandle') : t('settings.copyAddress')} - - } - > -
- {avatarVisible && } -
-

- {walletName} -

-

- {handleName || shortenedWalletAddress} -

+ {process.env.USE_MULTI_WALLET === 'true' ? ( + + ) : ( + + {handleName ? t('settings.copyHandle') : t('settings.copyAddress')} + + } + > +
+ {avatarVisible && } +
+

+ {walletName} +

+

+ {handleName || shortenedWalletAddress} +

+
-
- + + )}
diff --git a/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/components/index.ts b/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/components/index.ts index 040f0a990c..5382f3cf6f 100644 --- a/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/components/index.ts +++ b/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/components/index.ts @@ -8,3 +8,4 @@ export * from './UserInfo'; export * from './UserAvatar'; export * from './NetworkChoise'; export * from './NetworkInfo'; +export * from './AddNewWalletLink'; diff --git a/apps/browser-extension-wallet/src/components/MainMenu/MainHeader.module.scss b/apps/browser-extension-wallet/src/components/MainMenu/MainHeader.module.scss index cb96dbc1c8..853218748c 100644 --- a/apps/browser-extension-wallet/src/components/MainMenu/MainHeader.module.scss +++ b/apps/browser-extension-wallet/src/components/MainMenu/MainHeader.module.scss @@ -36,3 +36,12 @@ } } } + +.multiWallet { + flex-wrap: wrap; +} + +.multiWalletNetworkPillBox { + width: 100%; + margin-bottom: 12px; +} diff --git a/apps/browser-extension-wallet/src/components/MainMenu/MainHeader.tsx b/apps/browser-extension-wallet/src/components/MainMenu/MainHeader.tsx index 734aad4c85..ac2c66bebf 100644 --- a/apps/browser-extension-wallet/src/components/MainMenu/MainHeader.tsx +++ b/apps/browser-extension-wallet/src/components/MainMenu/MainHeader.tsx @@ -4,6 +4,7 @@ import styles from './MainHeader.module.scss'; import LaceLogoMark from '../../assets/branding/lace-logo-mark.component.svg'; import { useTranslation } from 'react-i18next'; import { walletRoutePaths } from '@routes'; +import classNames from 'classnames'; import { DropdownMenu } from '@components/DropdownMenu'; import { ExpandButton } from '@components/ExpandButton'; @@ -30,7 +31,16 @@ export const MainHeader = (): React.ReactElement => { return (
-
+
+ {process.env.USE_MULTI_WALLET === 'true' && ( +
+ +
+ )} { onClick={() => analytics.sendEventToPostHog(PostHogAction.WalletLaceClick)} > - + {process.env.USE_MULTI_WALLET !== 'true' && }
{ +export const NetworkPill = ({ isExpandable, isPopup = false }: NetworkPillProp): ReactElement => { const { environmentName } = useWalletStore(); const { t } = useTranslation(); const { isOnline, isBackendFailing } = useNetwork(); @@ -19,10 +20,19 @@ export const NetworkPill = ({ isExpandable }: NetworkPillProp): ReactElement => if (isOnline && !isBackendFailing && environmentName !== 'Mainnet') { return (
- {environmentName} + + {environmentName} +
); } @@ -30,7 +40,10 @@ export const NetworkPill = ({ isExpandable }: NetworkPillProp): ReactElement => return (
@@ -45,7 +58,10 @@ export const NetworkPill = ({ isExpandable }: NetworkPillProp): ReactElement => return (
diff --git a/apps/browser-extension-wallet/src/lib/translations/en.json b/apps/browser-extension-wallet/src/lib/translations/en.json index 4df8181120..79ecd3e268 100644 --- a/apps/browser-extension-wallet/src/lib/translations/en.json +++ b/apps/browser-extension-wallet/src/lib/translations/en.json @@ -417,12 +417,14 @@ "assetDetails.tokenInformation": "Token Information", "assetDetails.fingerprint": "Fingerprint", "assetDetails.policyId": "Policy ID", + "sideMenu.links.general": "General", "sideMenu.links.tokens": "Tokens", "sideMenu.links.nfts": "NFTs", "sideMenu.links.activity": "Activity", "sideMenu.links.staking": "Staking", "sideMenu.links.dappStore": "Dapp Store", "sideMenu.links.voting": "Voting", + "sideMenu.links.addNewWallet": "Add new wallet", "sideMenu.links.addressBook": "Address Book", "sideMenu.dapps.header": "Open Dapps", "sideMenu.mode.light": "Light mode", diff --git a/apps/browser-extension-wallet/src/providers/UIThemeProvider/context.tsx b/apps/browser-extension-wallet/src/providers/UIThemeProvider/context.tsx new file mode 100644 index 0000000000..b5a0cdff17 --- /dev/null +++ b/apps/browser-extension-wallet/src/providers/UIThemeProvider/context.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { ThemeColorScheme, ThemeProvider } from '@lace/ui'; + +import { useTheme } from '@providers/ThemeProvider'; + +interface Props { + children: React.ReactNode; +} + +export const UIThemeProvider = ({ children }: Props): React.ReactElement => { + const { theme } = useTheme(); + return ( + + {children} + + ); +}; diff --git a/apps/browser-extension-wallet/src/providers/UIThemeProvider/index.ts b/apps/browser-extension-wallet/src/providers/UIThemeProvider/index.ts new file mode 100644 index 0000000000..c38e8e8215 --- /dev/null +++ b/apps/browser-extension-wallet/src/providers/UIThemeProvider/index.ts @@ -0,0 +1 @@ +export * from './context'; diff --git a/apps/browser-extension-wallet/src/providers/index.ts b/apps/browser-extension-wallet/src/providers/index.ts index df31b7f4f0..95c9462829 100644 --- a/apps/browser-extension-wallet/src/providers/index.ts +++ b/apps/browser-extension-wallet/src/providers/index.ts @@ -8,3 +8,4 @@ export * from './ThemeProvider'; export * from './ViewFlowProvider'; export * from './AnalyticsProvider'; export * from './BackgroundServiceAPI'; +export * from './UIThemeProvider'; diff --git a/apps/browser-extension-wallet/src/routes/wallet-paths.ts b/apps/browser-extension-wallet/src/routes/wallet-paths.ts index 965651c41e..f27073a607 100644 --- a/apps/browser-extension-wallet/src/routes/wallet-paths.ts +++ b/apps/browser-extension-wallet/src/routes/wallet-paths.ts @@ -20,6 +20,12 @@ export const walletRoutePaths = { create: '/setup/create', hardware: '/setup/hardware', restore: '/setup/restore' + }, + newWallet: { + home: '/new-wallet', + create: '/new-wallet/create', + hardware: '/new-wallet/hardware', + restore: '/new-wallet/restore' } }; diff --git a/apps/browser-extension-wallet/src/views/browser-view/components/Layout/SectionLayout.modules.scss b/apps/browser-extension-wallet/src/views/browser-view/components/Layout/SectionLayout.modules.scss index c23a28c9bc..71313dd823 100644 --- a/apps/browser-extension-wallet/src/views/browser-view/components/Layout/SectionLayout.modules.scss +++ b/apps/browser-extension-wallet/src/views/browser-view/components/Layout/SectionLayout.modules.scss @@ -95,6 +95,22 @@ $aside-width-large: 420px; } } +.navigationBoxFlexible { + // TODO: update width from navigationBox to auto once feature is rollout + width: auto; +} + +.topNavigationBox { + position: absolute; + right: 0; + // Large value to fit both send and receive buttons, plus dropdown trigger + width: 500px; +} + +.sidePanelContentBox { + margin-top: 120px; +} + .topBarAlignment { display: flex; width: 100%; diff --git a/apps/browser-extension-wallet/src/views/browser-view/components/Layout/SidePanel.tsx b/apps/browser-extension-wallet/src/views/browser-view/components/Layout/SidePanel.tsx index 5e1c3c7f25..6884f437db 100644 --- a/apps/browser-extension-wallet/src/views/browser-view/components/Layout/SidePanel.tsx +++ b/apps/browser-extension-wallet/src/views/browser-view/components/Layout/SidePanel.tsx @@ -60,7 +60,8 @@ export const SidePanel = ({ sidePanelContent, isSidePanelFixed = true }: Section const topNavigation = (
@@ -88,8 +89,17 @@ export const SidePanel = ({ sidePanelContent, isSidePanelFixed = true }: Section [styles.topBarAlignment]: isScreenTooSmallForSidePanel })} > - {topNavigation} - {!isScreenTooSmallForSidePanel && sidePanelContent} + {process.env.USE_MULTI_WALLET === 'true' ? ( + <> +
{topNavigation}
+ {!isScreenTooSmallForSidePanel &&
{sidePanelContent}
} + + ) : ( + <> + {topNavigation} + {!isScreenTooSmallForSidePanel && sidePanelContent} + + )}
)} diff --git a/apps/browser-extension-wallet/src/views/browser-view/components/LeftSidePanel/LeftSidePanel.module.scss b/apps/browser-extension-wallet/src/views/browser-view/components/LeftSidePanel/LeftSidePanel.module.scss index 07b061362d..73dd660165 100644 --- a/apps/browser-extension-wallet/src/views/browser-view/components/LeftSidePanel/LeftSidePanel.module.scss +++ b/apps/browser-extension-wallet/src/views/browser-view/components/LeftSidePanel/LeftSidePanel.module.scss @@ -50,6 +50,7 @@ $navigation-col-width: 215px; padding-top: size_unit(3); display: flex; align-items: center; + position: relative; .logo { cursor: pointer; @@ -62,3 +63,8 @@ $navigation-col-width: 215px; cursor: pointer; pointer-events: auto; } + +.networkPillBox { + position: absolute; + left: 120px; +} diff --git a/apps/browser-extension-wallet/src/views/browser-view/components/LeftSidePanel/LeftSidePanel.tsx b/apps/browser-extension-wallet/src/views/browser-view/components/LeftSidePanel/LeftSidePanel.tsx index 84527543ee..d321168afd 100644 --- a/apps/browser-extension-wallet/src/views/browser-view/components/LeftSidePanel/LeftSidePanel.tsx +++ b/apps/browser-extension-wallet/src/views/browser-view/components/LeftSidePanel/LeftSidePanel.tsx @@ -59,7 +59,11 @@ export const LeftSidePanel = ({ theme }: VerticalNavigationBarProps): React.Reac
{logo} - {!isNarrowWindow && } + {!isNarrowWindow && ( +
+ +
+ )}
diff --git a/apps/browser-extension-wallet/src/views/browser-view/components/SendReceiveBox/SendReceiveBox.module.scss b/apps/browser-extension-wallet/src/views/browser-view/components/SendReceiveBox/SendReceiveBox.module.scss index 21c496c405..80e5972997 100644 --- a/apps/browser-extension-wallet/src/views/browser-view/components/SendReceiveBox/SendReceiveBox.module.scss +++ b/apps/browser-extension-wallet/src/views/browser-view/components/SendReceiveBox/SendReceiveBox.module.scss @@ -1,4 +1,5 @@ .btn { padding: 0 !important; - --btn-medium-min-width: 0; + --btn-medium-min-width: 116px; + max-width: 116px; } diff --git a/apps/browser-extension-wallet/src/views/browser-view/index.tsx b/apps/browser-extension-wallet/src/views/browser-view/index.tsx index eb6eeab97b..fc02e16ac7 100644 --- a/apps/browser-extension-wallet/src/views/browser-view/index.tsx +++ b/apps/browser-extension-wallet/src/views/browser-view/index.tsx @@ -8,7 +8,8 @@ import { AxiosClientProvider, AppSettingsProvider, ThemeProvider, - AnalyticsProvider + AnalyticsProvider, + UIThemeProvider } from '@providers'; import { CardanoWalletManagerProvider } from '@providers/CardanoWalletManager'; import { StoreProvider } from '@stores'; @@ -43,15 +44,17 @@ const App = (): React.ReactElement => ( - - - - - - - - - + + + + + + + + + + + diff --git a/packages/ui/src/design-system/profile-dropdown/profile-dropdown-trigger.component.tsx b/packages/ui/src/design-system/profile-dropdown/profile-dropdown-trigger.component.tsx index 306b9ee4fc..a9090ba09a 100644 --- a/packages/ui/src/design-system/profile-dropdown/profile-dropdown-trigger.component.tsx +++ b/packages/ui/src/design-system/profile-dropdown/profile-dropdown-trigger.component.tsx @@ -58,6 +58,7 @@ export const Trigger = ({ title={{ text: title, type: 'button' }} subtitle={subtitle} type={type} + testId={makeTestId(id)} /> diff --git a/packages/ui/src/design-system/profile-dropdown/profile-dropdown-wallet-card.component.tsx b/packages/ui/src/design-system/profile-dropdown/profile-dropdown-wallet-card.component.tsx index 6ae05a0cc8..4b7bdd9ef7 100644 --- a/packages/ui/src/design-system/profile-dropdown/profile-dropdown-wallet-card.component.tsx +++ b/packages/ui/src/design-system/profile-dropdown/profile-dropdown-wallet-card.component.tsx @@ -25,32 +25,44 @@ export interface Props { delayMs?: number; }; type: WalletType; + testId?: string; } +const makeTestId = (namespace = '', path = ''): string => { + return namespace === '' ? namespace : `${namespace}${path}`; +}; + export const WalletCard = ({ title, subtitle, profile, type, + testId = '', }: Readonly): JSX.Element => { const Title = title.type === 'button' ? Text.Label : Text.Address; return ( {profile === undefined ? ( - + ) : ( )} - {title.text} + + {title.text} + - + {subtitle} diff --git a/packages/ui/src/design-system/profile-dropdown/profile-dropdown-wallet-icon.component.tsx b/packages/ui/src/design-system/profile-dropdown/profile-dropdown-wallet-icon.component.tsx index 0ee1e1e14f..1e4a9cfffa 100644 --- a/packages/ui/src/design-system/profile-dropdown/profile-dropdown-wallet-icon.component.tsx +++ b/packages/ui/src/design-system/profile-dropdown/profile-dropdown-wallet-icon.component.tsx @@ -12,6 +12,7 @@ import type { WalletType } from './profile-dropdown.data'; export interface Props { type: WalletType; + testId?: string; } const icons: Record>> = { @@ -20,13 +21,15 @@ const icons: Record>> = { shared: SharedWallet, }; -export const WalletIcon = ({ type }: Readonly): JSX.Element => { +export const WalletIcon = ({ type, testId }: Readonly): JSX.Element => { const Icon = icons[type]; return ( diff --git a/packages/ui/src/design-system/profile-dropdown/profile-dropdown-wallet-option.component.tsx b/packages/ui/src/design-system/profile-dropdown/profile-dropdown-wallet-option.component.tsx index 3161624481..73d63e1494 100644 --- a/packages/ui/src/design-system/profile-dropdown/profile-dropdown-wallet-option.component.tsx +++ b/packages/ui/src/design-system/profile-dropdown/profile-dropdown-wallet-option.component.tsx @@ -26,6 +26,12 @@ export type Props = Omit, 'type'> & { type: WalletType; }; +const makeTestId = (namespace = '', path = ''): string => { + return namespace === '' + ? namespace + : `profile-dropdown-wallet-option-${namespace}${path}`; +}; + export const WalletOption = ({ id, disabled, @@ -42,6 +48,7 @@ export const WalletOption = ({ id={id} disabled={disabled} className={classNames(cx.button, cx.container, className)} + data-testid={makeTestId(id)} > Date: Wed, 29 Nov 2023 19:02:06 +0100 Subject: [PATCH 12/16] refactor(extension,core,cardano): post-merge fixes --- .../dapp/components/ConfirmTransaction.tsx | 347 ------------------ .../__tests__/ConfirmTransaction.test.tsx | 144 -------- .../ConfirmTransaction.tsx | 23 +- .../ConfirmTransactionContent.tsx | 14 +- .../DappTransactionContainer.tsx | 29 +- .../ConfirmTransactionContent.test.tsx | 27 +- .../__tests__/hooks.test.tsx | 12 +- .../__tests__/utils.test.tsx | 29 +- .../components/confirm-transaction/hooks.ts | 127 ++++++- .../components/confirm-transaction/utils.ts | 42 +-- .../stores/slices/activity-detail-slice.ts | 17 +- packages/cardano/src/wallet/types.ts | 25 +- .../DappTransaction/DappTransaction.tsx | 16 +- .../DappTxAsset/DappTxAsset.tsx | 9 +- .../DappTransaction/DappTxAsset/index.ts | 1 + .../DappTxOutput/DappTxOutput.tsx | 4 +- 16 files changed, 259 insertions(+), 607 deletions(-) delete mode 100644 apps/browser-extension-wallet/src/features/dapp/components/ConfirmTransaction.tsx delete mode 100644 apps/browser-extension-wallet/src/features/dapp/components/__tests__/ConfirmTransaction.test.tsx create mode 100644 packages/core/src/ui/components/DappTransaction/DappTxAsset/index.ts diff --git a/apps/browser-extension-wallet/src/features/dapp/components/ConfirmTransaction.tsx b/apps/browser-extension-wallet/src/features/dapp/components/ConfirmTransaction.tsx deleted file mode 100644 index e711aa50ed..0000000000 --- a/apps/browser-extension-wallet/src/features/dapp/components/ConfirmTransaction.tsx +++ /dev/null @@ -1,347 +0,0 @@ -import React, { useState, useEffect, useMemo, useCallback } from 'react'; -import { Button, PostHogAction, useObservable } from '@lace/common'; -import { useTranslation } from 'react-i18next'; -import { DappTransaction } from '@lace/core'; -import { Layout } from './Layout'; -import { useViewsFlowContext } from '@providers/ViewFlowProvider'; -import { sectionTitle, DAPP_VIEWS } from '../config'; -import styles from './ConfirmTransaction.module.scss'; -import { Wallet } from '@lace/cardano'; -import { useAddressBookContext, withAddressBookContext } from '@src/features/address-book/context'; -import { useWalletStore } from '@stores'; -import { AddressListType } from '@views/browser/features/activity'; -import { consumeRemoteApi, exposeApi, RemoteApiPropertyType } from '@cardano-sdk/web-extension'; -import { DappDataService } from '@lib/scripts/types'; -import { DAPP_CHANNELS } from '@src/utils/constants'; -import { runtime } from 'webextension-polyfill'; -import { useFetchCoinPrice, useRedirection } from '@hooks'; -import { - assetsBurnedInspector, - assetsMintedInspector, - createTxInspector, - AssetsMintedInspection, - MintedAsset -} from '@cardano-sdk/core'; -import { Skeleton } from 'antd'; -import { dAppRoutePaths } from '@routes'; -import { UserPromptService } from '@lib/scripts/background/services'; -import { of } from 'rxjs'; -import { getAssetsInformation, TokenInfo } from '@src/utils/get-assets-information'; -import * as HardwareLedger from '../../../../../../node_modules/@cardano-sdk/hardware-ledger/dist/cjs'; -import { useCurrencyStore, useAnalyticsContext } from '@providers'; -import { TX_CREATION_TYPE_KEY, TxCreationType } from '@providers/AnalyticsProvider/analyticsTracker'; -import { txSubmitted$ } from '@providers/AnalyticsProvider/onChain'; - -const DAPP_TOAST_DURATION = 50; - -const dappDataApi = consumeRemoteApi>( - { - baseChannel: DAPP_CHANNELS.dappData, - properties: { - getSignTxData: RemoteApiPropertyType.MethodReturningPromise - } - }, - { logger: console, runtime } -); - -const convertMetadataArrayToObj = (arr: unknown[]): Record => { - const result: Record = {}; - for (const item of arr) { - if (typeof item === 'object' && !Array.isArray(item) && item !== null) { - Object.assign(result, item); - } - } - return result; -}; - -// eslint-disable-next-line complexity, sonarjs/cognitive-complexity -const getAssetNameFromMintMetadata = (asset: MintedAsset, metadata: Wallet.Cardano.TxMetadata): string | undefined => { - if (!asset || !metadata) return; - const decodedAssetName = Buffer.from(asset.assetName, 'hex').toString(); - - // Tries to find the asset name in the tx metadata under label 721 or 20 - for (const [key, value] of metadata.entries()) { - // eslint-disable-next-line no-magic-numbers - if (key !== BigInt(721) && key !== BigInt(20)) return; - const cip25Metadata = Wallet.cardanoMetadatumToObj(value); - if (!Array.isArray(cip25Metadata)) return; - - // cip25Metadata should be an array containing all policies for the minted assets in the tx - const policyLevelMetadata = convertMetadataArrayToObj(cip25Metadata)[asset.policyId]; - if (!Array.isArray(policyLevelMetadata)) return; - - // policyLevelMetadata should be an array of objects with the minted assets names as key - // e.g. "policyId" = [{ "AssetName1": { ...metadataAsset1 } }, { "AssetName2": { ...metadataAsset2 } }]; - const assetProperties = convertMetadataArrayToObj(policyLevelMetadata)?.[decodedAssetName]; - if (!Array.isArray(assetProperties)) return; - - // assetProperties[decodedAssetName] should be an array of objects with the properties as keys - // e.g. [{ "name": "Asset Name" }, { "description": "An asset" }, ...] - const assetMetadataName = convertMetadataArrayToObj(assetProperties)?.name; - // eslint-disable-next-line consistent-return - return typeof assetMetadataName === 'string' ? assetMetadataName : undefined; - } -}; - -// eslint-disable-next-line sonarjs/cognitive-complexity -export const ConfirmTransaction = withAddressBookContext((): React.ReactElement => { - const { - utils: { setNextView } - } = useViewsFlowContext(); - const { t } = useTranslation(); - const { - walletInfo, - inMemoryWallet, - getKeyAgentType, - blockchainProvider: { assetProvider }, - walletUI: { cardanoCoin } - } = useWalletStore(); - const { fiatCurrency } = useCurrencyStore(); - const { list: addressList } = useAddressBookContext(); - const { priceResult } = useFetchCoinPrice(); - const analytics = useAnalyticsContext(); - - const [tx, setTx] = useState(); - const assets = useObservable(inMemoryWallet.assetInfo$); - const [errorMessage, setErrorMessage] = useState(); - const redirectToSignFailure = useRedirection(dAppRoutePaths.dappTxSignFailure); - const [isConfirmingTx, setIsConfirmingTx] = useState(); - const keyAgentType = getKeyAgentType(); - const isUsingHardwareWallet = useMemo( - () => keyAgentType !== Wallet.KeyManagement.KeyAgentType.InMemory, - [keyAgentType] - ); - const [assetsInfo, setAssetsInfo] = useState(); - const [dappInfo, setDappInfo] = useState(); - - // All assets' ids in the transaction body. Used to fetch their info from cardano services - const assetIds = useMemo(() => { - const uniqueAssetIds = new Set(); - // Merge all assets (TokenMaps) from the tx outputs and mint - const assetMaps = tx?.body?.outputs?.map((output) => output.value.assets) ?? []; - if (tx?.body?.mint?.size > 0) assetMaps.push(tx.body.mint); - - // Extract all unique asset ids from the array of TokenMaps - for (const asset of assetMaps) { - if (asset) { - for (const id of asset.keys()) { - !uniqueAssetIds.has(id) && uniqueAssetIds.add(id); - } - } - } - return [...uniqueAssetIds.values()]; - }, [tx]); - - useEffect(() => { - if (assetIds?.length > 0) { - getAssetsInformation(assetIds, assets, { - assetProvider, - extraData: { nftMetadata: true, tokenMetadata: true } - }) - .then((result) => setAssetsInfo(result)) - .catch((error) => { - console.error(error); - }); - } - }, [assetIds, assetProvider, assets]); - - const cancelTransaction = useCallback((close = false) => { - exposeApi>( - { - api$: of({ - async allowSignTx(): Promise { - return Promise.reject(); - } - }), - baseChannel: DAPP_CHANNELS.userPrompt, - properties: { allowSignTx: RemoteApiPropertyType.MethodReturningPromise } - }, - { logger: console, runtime } - ); - close && setTimeout(() => window.close(), DAPP_TOAST_DURATION); - }, []); - - window.addEventListener('beforeunload', cancelTransaction); - - const signWithHardwareWallet = async () => { - setIsConfirmingTx(true); - try { - HardwareLedger.LedgerKeyAgent.establishDeviceConnection(Wallet.KeyManagement.CommunicationType.Web) - .then(() => { - exposeApi>( - { - api$: of({ - async allowSignTx(): Promise { - return Promise.resolve(true); - } - }), - baseChannel: DAPP_CHANNELS.userPrompt, - properties: { allowSignTx: RemoteApiPropertyType.MethodReturningPromise } - }, - { logger: console, runtime } - ); - }) - .catch((error) => { - throw error; - }); - } catch (error) { - console.error('error', error); - cancelTransaction(false); - redirectToSignFailure(); - } - }; - - useEffect(() => { - dappDataApi - .getSignTxData() - .then(({ dappInfo: backgroundDappInfo, tx: backgroundTx }) => { - setDappInfo(backgroundDappInfo); - setTx(backgroundTx); - }) - .catch((error) => { - setErrorMessage(error); - console.error(error); - }); - }, []); - - const createMintedList = useCallback( - (mintedAssets: AssetsMintedInspection) => { - if (!assetsInfo) return []; - return mintedAssets.map((asset) => { - const assetId = Wallet.Cardano.AssetId.fromParts(asset.policyId, asset.assetName); - const assetInfo = assets.get(assetId) || assetsInfo?.get(assetId); - // If it's a new asset or the name is being updated we should be getting it from the tx metadata - const metadataName = getAssetNameFromMintMetadata(asset, tx?.auxiliaryData?.blob); - return { - name: assetInfo?.name.toString() || asset.fingerprint || assetId, - ticker: - metadataName ?? - assetInfo?.nftMetadata?.name ?? - assetInfo?.tokenMetadata?.ticker ?? - assetInfo?.tokenMetadata?.name ?? - asset.fingerprint.toString(), - amount: Wallet.util.calculateAssetBalance(asset.quantity, assetInfo) - }; - }); - }, - [assets, assetsInfo, tx] - ); - - const createAssetList = useCallback( - (txAssets: Wallet.Cardano.TokenMap) => { - if (!assetsInfo) return []; - const assetList: Wallet.Cip30SignTxAssetItem[] = []; - txAssets.forEach(async (value, key) => { - const walletAsset = assets.get(key) || assetsInfo?.get(key); - assetList.push({ - name: walletAsset?.name.toString() || key.toString(), - ticker: walletAsset?.tokenMetadata?.ticker || walletAsset?.nftMetadata?.name, - amount: Wallet.util.calculateAssetBalance(value, walletAsset) - }); - }); - return assetList; - }, - [assets, assetsInfo] - ); - - const addressToNameMap = useMemo( - () => new Map(addressList?.map((item: AddressListType) => [item.address, item.name])), - [addressList] - ); - - const txSummary: Wallet.Cip30SignTxSummary | undefined = useMemo(() => { - if (!tx) return; - const inspector = createTxInspector({ - minted: assetsMintedInspector, - burned: assetsBurnedInspector - }); - - const { minted, burned } = inspector(tx as Wallet.Cardano.HydratedTx); - const isMintTransaction = minted.length > 0 || burned.length > 0; - - const txType = isMintTransaction ? 'Mint' : 'Send'; - - const externalOutputs = tx.body.outputs.filter((output) => { - if (txType === 'Send') { - return walletInfo.addresses.every((addr) => output.address !== addr.address); - } - return true; - }); - - // eslint-disable-next-line unicorn/no-array-reduce - const txSummaryOutputs: Wallet.Cip30SignTxSummary['outputs'] = externalOutputs.reduce((acc, txOut) => { - // Don't show withdrawl tx's etc - if (txOut.address.toString() === walletInfo.addresses[0].address.toString()) return acc; - - return [ - ...acc, - { - coins: Wallet.util.lovelacesToAdaString(txOut.value.coins.toString()), - recipient: addressToNameMap?.get(txOut.address.toString()) || txOut.address.toString(), - ...(txOut.value.assets?.size > 0 && { assets: createAssetList(txOut.value.assets) }) - } - ]; - }, []); - - // eslint-disable-next-line consistent-return - return { - fee: Wallet.util.lovelacesToAdaString(tx.body.fee.toString()), - outputs: txSummaryOutputs, - type: txType, - mintedAssets: createMintedList(minted), - burnedAssets: createMintedList(burned) - }; - }, [tx, walletInfo.addresses, createAssetList, createMintedList, addressToNameMap]); - - const onConfirm = () => { - analytics.sendEventToPostHog(PostHogAction.SendTransactionSummaryConfirmClick, { - [TX_CREATION_TYPE_KEY]: TxCreationType.External - }); - - txSubmitted$.next({ - id: tx?.id.toString(), - date: new Date().toString(), - creationType: TxCreationType.External - }); - - isUsingHardwareWallet ? signWithHardwareWallet() : setNextView(); - }; - - return ( - - {tx && txSummary ? ( - - ) : ( - - )} -
- - -
-
- ); -}); diff --git a/apps/browser-extension-wallet/src/features/dapp/components/__tests__/ConfirmTransaction.test.tsx b/apps/browser-extension-wallet/src/features/dapp/components/__tests__/ConfirmTransaction.test.tsx deleted file mode 100644 index 7acabf64d1..0000000000 --- a/apps/browser-extension-wallet/src/features/dapp/components/__tests__/ConfirmTransaction.test.tsx +++ /dev/null @@ -1,144 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable import/imports-first */ -import * as CurrencyProvider from '@providers/currency'; - -const mockGetKeyAgentType = jest.fn(); -const mockUseWalletStore = jest.fn(); -const error = 'error in getSignTxData'; -const mockConsumeRemoteApi = jest.fn().mockReturnValue({ - getSignTxData: async () => await Promise.reject(error) -}); -const mockCreateTxInspector = jest.fn().mockReturnValue(() => ({ minted: [] as any, burned: [] as any })); -const mockUseCurrencyStore = jest.fn().mockReturnValue({ fiatCurrency: { code: 'usd', symbol: '$' } }); -import * as React from 'react'; -import { cleanup, render, waitFor } from '@testing-library/react'; -import { ConfirmTransaction } from '../ConfirmTransaction'; -import '@testing-library/jest-dom'; -import { I18nextProvider } from 'react-i18next'; -import { StoreProvider } from '@src/stores'; -import { - AnalyticsProvider, - AppSettingsProvider, - BackgroundServiceAPIProvider, - BackgroundServiceAPIProviderProps, - DatabaseProvider, - ViewFlowProvider -} from '@src/providers'; -import { APP_MODE_BROWSER } from '@src/utils/constants'; -import i18n from '@lib/i18n'; -import { BehaviorSubject } from 'rxjs'; -import { act } from 'react-dom/test-utils'; -import { sendViewsFlowState } from '../../config'; -import { PostHogClientProvider } from '@providers/PostHogClientProvider'; -import { postHogClientMocks } from '@src/utils/mocks/test-helpers'; - -const assetInfo$ = new BehaviorSubject(new Map()); -const available$ = new BehaviorSubject([]); -const tokenPrices$ = new BehaviorSubject({}); -const adaPrices$ = new BehaviorSubject({}); - -const assetProvider = { - getAsset: () => ({}), - getAssets: (): any[] => [] -}; -const inMemoryWallet = { - assetInfo$, - balance: { - utxo: { - available$ - } - } -}; - -jest.mock('@src/stores', () => ({ - ...jest.requireActual('@src/stores'), - useWalletStore: mockUseWalletStore -})); - -jest.mock('@providers/currency', (): typeof CurrencyProvider => ({ - ...jest.requireActual('@providers/currency'), - useCurrencyStore: mockUseCurrencyStore -})); - -jest.mock('@cardano-sdk/web-extension', () => { - const original = jest.requireActual('@cardano-sdk/web-extension'); - return { - __esModule: true, - ...original, - consumeRemoteApi: mockConsumeRemoteApi - }; -}); - -jest.mock('@cardano-sdk/core', () => { - const original = jest.requireActual('@cardano-sdk/core'); - return { - __esModule: true, - ...original, - createTxInspector: mockCreateTxInspector - }; -}); - -const testIds = { - dappTransactionConfirm: 'dapp-transaction-confirm' -}; - -const backgroundService = { - getBackgroundStorage: jest.fn(), - setBackgroundStorage: jest.fn(), - coinPrices: { tokenPrices$, adaPrices$ } -} as unknown as BackgroundServiceAPIProviderProps['value']; - -const getWrapper = - () => - ({ children }: { children: React.ReactNode }) => - ( - - - - - - - - {children} - - - - - - - - ); - -describe('Testing ConfirmTransaction component', () => { - window.ResizeObserver = ResizeObserver; - describe('Testing errors', () => { - beforeEach(() => { - mockUseWalletStore.mockImplementation(() => ({ - getKeyAgentType: mockGetKeyAgentType, - inMemoryWallet, - walletUI: {}, - walletInfo: {}, - blockchainProvider: { assetProvider } - })); - }); - - afterEach(() => { - jest.resetModules(); - jest.resetAllMocks(); - cleanup(); - }); - - test('should disable confirm button and show proper error if getSignTxData throws', async () => { - let queryByTestId: any; - act(() => { - ({ queryByTestId } = render(, { - wrapper: getWrapper() - })); - }); - - await waitFor(async () => { - expect(queryByTestId(testIds.dappTransactionConfirm).closest('button')).toHaveAttribute('disabled'); - }); - }); - }); -}); 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 ba6ea45711..f90eea8d72 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,6 +1,6 @@ /* eslint-disable no-console */ import React, { useMemo } from 'react'; -import { Button } from '@lace/common'; +import { Button, PostHogAction } from '@lace/common'; import { useTranslation } from 'react-i18next'; import { Layout } from '../Layout'; import { useViewsFlowContext } from '@providers/ViewFlowProvider'; @@ -14,6 +14,9 @@ import { DAPP_CHANNELS } from '@src/utils/constants'; import { runtime } from 'webextension-polyfill'; import { getTitleKey, getTxType } from './utils'; import { ConfirmTransactionContent } from './ConfirmTransactionContent'; +import { TX_CREATION_TYPE_KEY, TxCreationType } from '@providers/AnalyticsProvider/analyticsTracker'; +import { txSubmitted$ } from '@providers/AnalyticsProvider/onChain'; +import { useAnalyticsContext } from '@providers'; export const ConfirmTransaction = (): React.ReactElement => { const { t } = useTranslation(); @@ -34,6 +37,7 @@ export const ConfirmTransaction = (): React.ReactElement => { [] ); const { getKeyAgentType } = useWalletStore(); + const analytics = useAnalyticsContext(); const { signTxData, errorMessage } = useSignTxData(dappDataApi.getSignTxData); const keyAgentType = getKeyAgentType(); const isUsingHardwareWallet = keyAgentType !== Wallet.KeyManagement.KeyAgentType.InMemory; @@ -41,6 +45,19 @@ export const ConfirmTransaction = (): React.ReactElement => { const { isConfirmingTx, signWithHardwareWallet } = useSignWithHardwareWallet(); const txType = signTxData ? getTxType(signTxData.tx) : undefined; const title = txType ? t(getTitleKey(txType)) : ''; + const onConfirm = () => { + analytics.sendEventToPostHog(PostHogAction.SendTransactionSummaryConfirmClick, { + [TX_CREATION_TYPE_KEY]: TxCreationType.External + }); + + txSubmitted$.next({ + id: signTxData.tx?.id.toString(), + date: new Date().toString(), + creationType: TxCreationType.External + }); + + isUsingHardwareWallet ? signWithHardwareWallet() : setNextView(); + }; useOnBeforeUnload(disallowSignTx); @@ -49,9 +66,7 @@ export const ConfirmTransaction = (): React.ReactElement => {