diff --git a/packages/ui/cypress/fixtures/landingData.ts b/packages/ui/cypress/fixtures/landingData.ts index da1a86313..13b70bae7 100644 --- a/packages/ui/cypress/fixtures/landingData.ts +++ b/packages/ui/cypress/fixtures/landingData.ts @@ -1,12 +1,16 @@ export const baseUrl = 'http://localhost:3333' export const defaultNetwork = 'paseo' const WATCH_ACCOUNT_ANCHOR = 'watched-accounts' +const HIDDEN_ACCOUNTS_ANCHOR = 'hidden-accounts' + export const landingPageNetwork = (networkName: string) => `${baseUrl}?network=${networkName}` export const landingPageUrl = landingPageNetwork(defaultNetwork) export const getSettingsPageUrl = (network = defaultNetwork) => `${baseUrl}/settings?network=${network}` export const getSettingsPageWatchAccountUrl = (network = defaultNetwork) => `${getSettingsPageUrl(network)}#${WATCH_ACCOUNT_ANCHOR}` +export const getSettingsPageHiddenAccountUrl = (network = defaultNetwork) => + `${getSettingsPageUrl(network)}#${HIDDEN_ACCOUNTS_ANCHOR}` export const landingPageNetworkAddress = ({ network, address diff --git a/packages/ui/cypress/fixtures/westendAccounts.ts b/packages/ui/cypress/fixtures/westendAccounts.ts new file mode 100644 index 000000000..3ac4fe43a --- /dev/null +++ b/packages/ui/cypress/fixtures/westendAccounts.ts @@ -0,0 +1,24 @@ +import { InjectedAccountWitMnemonic } from './testAccounts' + +export const westendMemberAccount = { + // this is the member of a multisig and a multisig with Pure + // use in hidden-accounts + hidden: { + account: { + address: '5CPG8FMciJBBE47YwSQHte23tTz91egixJw514g6BCt5nHPz', + publicKey: '0x0e270b0984354e0f6033dee93293dbeaea366220ca59e584ba7614d8bf393040', + name: 'hidden', + type: 'sr25519', + mnemonic: '' + } as InjectedAccountWitMnemonic, + expectedSingleMultisig: { + westEndAddress: '5CvCLBVHufgqTDUVJL3xY6Pd7TVaYtaTGzvYRfGeaAPJLdDS', + paseoAddress: '1rVUWkMmSxJtkV1Fy6xgFDmy5VEFC8bMVf2axG18FQpX7hE', + pubKey: '0x25bee0c82d1a5ea1ef4f75b4cb517286a78ed51ab934b1636ac4d8b018811b1b' + }, + expectedPure: { + address: '5DqS9vsnXotmczKu87xb5KMUARCVF5JUUVveZz9R8UvXKExK', + pubKey: '0x4e596aec4922957174ba3f86860cca88fa4664006b511f11260cc34ca303d0dd' + } + } +} diff --git a/packages/ui/cypress/support/commands.ts b/packages/ui/cypress/support/commands.ts index 8ac7d1470..bb8be265b 100644 --- a/packages/ui/cypress/support/commands.ts +++ b/packages/ui/cypress/support/commands.ts @@ -7,6 +7,7 @@ import '@chainsafe/cypress-polkadot-wallet' const LOCALSTORAGE_ACCOUNT_NAMES_KEY = 'multix.accountNames' const LOCALSTORAGE_WATCHED_ACCOUNTS_KEY = 'multix.watchedAccount' +const LOCALSTORAGE_HIDDEN_ACCOUNTS_KEY = 'multix.hiddenAccounts' const LOCALSTORAGE_EXTENSION_CONNECTION_KEY = '@reactive-dot/wallet/injected/polkadot-js/connected' const LOCALSTORAGE_ALLOWED_CONNECTION_KEY = 'multix.canConnectToExtension' export const MULTIX_DAPP_NAME = 'Multix' @@ -28,6 +29,7 @@ Cypress.Commands.add('connectAccounts', (accountAddresses: string[]) => { interface IsetupAndVisit { url: string watchedAccounts?: string[] + hiddenAccounts?: Array<{ network: string; pubKey: string }> accountNames?: Record extensionConnectionAllowed?: boolean injectExtensionWithAccounts?: InjectedAccountWitMnemonic[] @@ -38,6 +40,7 @@ Cypress.Commands.add( ({ url, watchedAccounts, + hiddenAccounts, accountNames, extensionConnectionAllowed, injectExtensionWithAccounts @@ -49,6 +52,8 @@ Cypress.Commands.add( LOCALSTORAGE_WATCHED_ACCOUNTS_KEY, JSON.stringify(watchedAccounts) ) + !!hiddenAccounts?.length && + win.localStorage.setItem(LOCALSTORAGE_HIDDEN_ACCOUNTS_KEY, JSON.stringify(hiddenAccounts)) !!accountNames && win.localStorage.setItem(LOCALSTORAGE_ACCOUNT_NAMES_KEY, JSON.stringify(accountNames)) diff --git a/packages/ui/cypress/support/page-objects/modals/hiddenAccountInfoModal.ts b/packages/ui/cypress/support/page-objects/modals/hiddenAccountInfoModal.ts new file mode 100644 index 000000000..d3259eda0 --- /dev/null +++ b/packages/ui/cypress/support/page-objects/modals/hiddenAccountInfoModal.ts @@ -0,0 +1,5 @@ +export const hiddenAccountInfoModal = { + body: () => cy.get('[data-cy=modal-hidden-account-info]'), + gotItButton: () => cy.get('[data-cy=button-hidden-account-info-gotit]'), + checkBoxMessage: () => cy.get('[data-cy=checkbox-dont-show-again]') +} diff --git a/packages/ui/cypress/support/page-objects/multisigPage.ts b/packages/ui/cypress/support/page-objects/multisigPage.ts index a429fffdb..7566c4476 100644 --- a/packages/ui/cypress/support/page-objects/multisigPage.ts +++ b/packages/ui/cypress/support/page-objects/multisigPage.ts @@ -8,6 +8,7 @@ export const multisigPage = { subscanMenuOption: () => cy.get('[data-cy=menu-option-subscan]'), reviewButton: () => cy.get('[data-cy=button-review-tx]'), setIdentityMenuOption: () => cy.get('[data-cy=menu-option-set-identity]'), + hideAccountMenuOption: () => cy.get('[data-cy=menu-option-hide-this-account]'), assetHubBalance: (id: string) => cy.get(`[data-cy=asset-balance-${id}]`), nativeBalance: () => cy.get('[data-cy=asset-balance-native]'), diff --git a/packages/ui/cypress/support/page-objects/settingsPage.ts b/packages/ui/cypress/support/page-objects/settingsPage.ts index 8e28b6992..6e2e92cca 100644 --- a/packages/ui/cypress/support/page-objects/settingsPage.ts +++ b/packages/ui/cypress/support/page-objects/settingsPage.ts @@ -1,16 +1,23 @@ export const settingsPage = { // watch account section watchedAccountsAccordion: () => cy.get('[data-cy=accordion-title-watched-accounts]'), + watchedAccountsInputsWrapper: () => cy.get('[data-cy=wrapper-watched-accounts-inputs]'), + hiddenAccountsAccordion: () => cy.get('[data-cy=accordion-title-hidden-accounts]'), + hiddenAccountsInputsWrapper: () => cy.get('[data-cy=wrapper-hidden-accounts-inputs]'), accountAddressInput: () => cy.get('[data-cy=input-account-address]'), accountNameInput: () => cy.get('[data-cy=input-account-name]'), addButton: () => cy.get('[data-cy=button-add-account]'), - accountContainer: () => cy.get('[data-cy=container-account-details]', { timeout: 20000 }), - accountDeleteButton: () => cy.get('[data-cy=button-delete-watched-account]'), + watchedAccountsContainer: () => cy.get('[data-cy=container-account-details]', { timeout: 20000 }), + hiddenAccountsContainer: () => + cy.get('[data-cy=container-hidden-account-details]', { timeout: 20000 }), + watchedAccountDeleteButton: () => cy.get('[data-cy=button-delete-watched-account]'), + hiddenAccountDeleteButton: () => cy.get('[data-cy=button-delete-hidden-account]'), errorLabel: () => cy.get('[data-cy=label-add-account-error]'), // wallet connect section wallectConnectAccordion: () => cy.get('[data-cy=accordion-title-wallet-connect]'), walletConnectAlert: () => cy.get('[data-cy=alert-wallet-connect-warning]'), connectDappButton: () => cy.get('[data-cy=button-connect-dapp]'), walletConnectKeyInput: () => cy.get('[data-cy=input-wallet-connect-key]'), - tooltipInfoIcon: () => cy.get('[data-cy=tooltip-wallet-connect-info]') + tooltipInfoIcon: () => cy.get('[data-cy=tooltip-wallet-connect-info]'), + hiddenAccountWatchedWarning: () => cy.get('[data-cy=alert-removed-watched-account]') } diff --git a/packages/ui/cypress/tests/hidden-accounts.cy.ts b/packages/ui/cypress/tests/hidden-accounts.cy.ts new file mode 100644 index 000000000..8a5b967c7 --- /dev/null +++ b/packages/ui/cypress/tests/hidden-accounts.cy.ts @@ -0,0 +1,297 @@ +import { accountDisplay } from '../support/page-objects/components/accountDisplay' +import { + getSettingsPageHiddenAccountUrl, + landingPageNetwork, + landingPageNetworkAddress +} from '../fixtures/landingData' +import { settingsPage } from '../support/page-objects/settingsPage' +import { westendMemberAccount } from '../fixtures/westendAccounts' +import { topMenuItems } from '../support/page-objects/topMenuItems' +import { multisigPage } from '../support/page-objects/multisigPage' +import { landingPage } from '../support/page-objects/landingPage' +import { hiddenAccountInfoModal } from '../support/page-objects/modals/hiddenAccountInfoModal' + +const randomAddress = 'HeVswqunza8rP2hEWDCThfiB5v2Jxng91yX2oGAZnCKtsgS' +const westendWatchedAccount = { + pubKey: '0xc94fccf2736cad38e169025ed308fdb6ae09185350b05e6193d0f5343ce7362f', + address: '5GcfBqhnXDMsUwHaebV9zHew1A6aVHbt4tH2XtdKi7mM48hC' +} + +const addHiddenAccount = (address: string) => { + settingsPage.hiddenAccountsInputsWrapper().within(() => { + settingsPage.accountAddressInput().type(`${address}{enter}`, { delay: 20, timeout: 8000 }) + + settingsPage.addButton().should('be.enabled') + settingsPage.addButton().click() + }) +} + +const goToHiddenAccountSettings = () => { + topMenuItems.settingsButton().click() + settingsPage.hiddenAccountsAccordion().click() +} + +describe('Hidden Accounts', () => { + it('adds an account with a name to the hidden list', () => { + cy.setupAndVisit({ + url: landingPageNetwork('westend'), + extensionConnectionAllowed: true, + injectExtensionWithAccounts: [westendMemberAccount.hidden.account] + }) + goToHiddenAccountSettings() + topMenuItems.multiproxySelectorDesktop().should('be.visible').click() + topMenuItems.multiproxySelectorOptionDesktop().should('have.length', 2) + + //hide random account + addHiddenAccount(randomAddress) + topMenuItems.multiproxySelectorDesktop().should('be.visible').click() + topMenuItems.multiproxySelectorOptionDesktop().should('have.length', 2) + settingsPage.hiddenAccountsContainer().should('be.visible') + settingsPage.hiddenAccountsContainer().within(() => { + accountDisplay.identicon().should('be.visible') + accountDisplay.addressLabel().should('be.visible') + settingsPage.hiddenAccountDeleteButton().should('be.visible') + }) + + //hide the multisig account + addHiddenAccount(westendMemberAccount.hidden.expectedSingleMultisig.westEndAddress) + settingsPage.hiddenAccountsContainer().should('have.length', 2) + topMenuItems.multiproxySelectorDesktop().should('be.visible').click() + // the multisig should be hidden + topMenuItems.multiproxySelectorOptionDesktop().should('have.length', 1) + topMenuItems + .multiproxySelectorOptionDesktop() + .should('contain', westendMemberAccount.hidden.expectedPure.address.slice(0, 6)) + }) + + it('hides all accounts sequentially and we switches to the available accounts if any', () => { + cy.setupAndVisit({ + url: landingPageNetworkAddress({ + network: 'westend', + address: westendMemberAccount.hidden.expectedPure.address + }), + extensionConnectionAllowed: true, + injectExtensionWithAccounts: [westendMemberAccount.hidden.account] + }) + //land on the pure + multisigPage.accountHeader().within(() => { + accountDisplay + .addressLabel() + .should('contain.text', westendMemberAccount.hidden.expectedPure.address.slice(0, 6)) + }) + cy.url().should('include', westendMemberAccount.hidden.expectedPure.address) + goToHiddenAccountSettings() + addHiddenAccount(westendMemberAccount.hidden.expectedPure.address) + // we should now have only the single multisig and have it selected + cy.url().should('include', westendMemberAccount.hidden.expectedSingleMultisig.westEndAddress) + topMenuItems.homeButton().click() + multisigPage.accountHeader().within(() => { + accountDisplay + .addressLabel() + .should( + 'contain.text', + westendMemberAccount.hidden.expectedSingleMultisig.westEndAddress.slice(0, 6) + ) + }) + + topMenuItems.multiproxySelectorDesktop().should('be.visible').click() + topMenuItems.multiproxySelectorOptionDesktop().should('have.length', 1) + goToHiddenAccountSettings() + settingsPage + .hiddenAccountsContainer() + .should('have.length', 1) + .within(() => { + accountDisplay.identicon().should('be.visible') + accountDisplay + .addressLabel() + .should('contain.text', westendMemberAccount.hidden.expectedPure.address.slice(0, 6)) + settingsPage.hiddenAccountDeleteButton().should('be.visible') + }) + + // hide all accounts and expect an error + addHiddenAccount(westendMemberAccount.hidden.expectedSingleMultisig.westEndAddress) + topMenuItems.multiproxySelectorDesktop().should('not.exist') + topMenuItems.homeButton().click() + landingPage + .noMultisigFoundError() + .should('contain.text', 'No multisig found for your accounts or watched accounts on westend.') + }) + + it('hides accounts per network only', () => { + cy.setupAndVisit({ + url: landingPageNetworkAddress({ + network: 'westend', + address: westendMemberAccount.hidden.expectedPure.address + }), + extensionConnectionAllowed: true, + injectExtensionWithAccounts: [westendMemberAccount.hidden.account], + hiddenAccounts: [ + { network: 'westend', pubKey: westendMemberAccount.hidden.expectedPure.pubKey }, + { network: 'westend', pubKey: westendMemberAccount.hidden.expectedSingleMultisig.pubKey } + ] + }) + + landingPage.linkedAddressNotFound().should('be.visible') + // change network paseo should have 1 multisig + topMenuItems.desktopMenu().within(() => topMenuItems.networkSelector().click()) + topMenuItems.networkSelectorOption('paseo').click() + + topMenuItems.multiproxySelectorDesktop().should('be.visible').click() + topMenuItems + .multiproxySelectorOptionDesktop() + .should('have.length', 1) + .should( + 'contain', + westendMemberAccount.hidden.expectedSingleMultisig.paseoAddress.slice(0, 6) + ) + + // there should be no account in the list + // since it's per network + goToHiddenAccountSettings() + settingsPage.hiddenAccountsContainer().should('not.exist') + }) + + it('can see error when attempting to add same address more than once', () => { + // add an account first + cy.visit(getSettingsPageHiddenAccountUrl()) + addHiddenAccount(randomAddress) + settingsPage.hiddenAccountsContainer().should('have.length', 1) + // attempt to add the same account again + addHiddenAccount(randomAddress) + settingsPage.errorLabel().should('be.visible').should('have.text', 'Account already added') + settingsPage.hiddenAccountsContainer().should('have.length', 1) + settingsPage.addButton().should('be.disabled') + }) + + it('can see error when attempting to add an invalid address', () => { + cy.visit(getSettingsPageHiddenAccountUrl()) + addHiddenAccount('123') + settingsPage.errorLabel().should('be.visible').should('have.text', 'Invalid address') + settingsPage.hiddenAccountsContainer().should('have.length', 0) + settingsPage.addButton().should('be.disabled') + }) + + it('can hide an account from the 3 dots menu', () => { + cy.setupAndVisit({ + url: landingPageNetworkAddress({ + network: 'westend', + address: westendMemberAccount.hidden.expectedPure.address + }), + extensionConnectionAllowed: true, + injectExtensionWithAccounts: [westendMemberAccount.hidden.account] + }) + + multisigPage.optionsMenuButton().click() + multisigPage.hideAccountMenuOption().should('exist').click() + hiddenAccountInfoModal.body().should('be.visible') + hiddenAccountInfoModal.checkBoxMessage().should('not.be.checked') + hiddenAccountInfoModal.gotItButton().should('be.visible').click() + cy.url().should('include', westendMemberAccount.hidden.expectedSingleMultisig.westEndAddress) + topMenuItems.multiproxySelectorDesktop().should('be.visible').click() + topMenuItems.multiproxySelectorOptionDesktop().should('have.length', 1) + goToHiddenAccountSettings() + settingsPage + .hiddenAccountsContainer() + .should('have.length', 1) + .within(() => { + accountDisplay + .addressLabel() + .should('contain.text', westendMemberAccount.hidden.expectedPure.address.slice(0, 6)) + }) + + // remove the hidden account + settingsPage.hiddenAccountDeleteButton().should('be.visible').click() + settingsPage.hiddenAccountsContainer().should('not.exist') + topMenuItems.multiproxySelectorDesktop().should('be.visible').click() + topMenuItems.multiproxySelectorOptionDesktop().should('have.length', 2) + topMenuItems.homeButton().click() + + // hide it again but say to not view the message again + multisigPage.optionsMenuButton().click() + multisigPage.hideAccountMenuOption().should('exist').click() + hiddenAccountInfoModal.body().should('be.visible') + hiddenAccountInfoModal.checkBoxMessage().should('not.be.checked').click() + hiddenAccountInfoModal.gotItButton().should('be.visible').click() + + topMenuItems.multiproxySelectorDesktop().should('be.visible').click() + topMenuItems.multiproxySelectorOptionDesktop().should('have.length', 1) + goToHiddenAccountSettings() + settingsPage.hiddenAccountsContainer().should('have.length', 1) + + // remove the hidden account + settingsPage.hiddenAccountDeleteButton().should('be.visible').click() + + topMenuItems.homeButton().click() + topMenuItems.multiproxySelectorDesktop().should('be.visible').click() + topMenuItems.multiproxySelectorOptionDesktop().should('have.length', 2) + + multisigPage.optionsMenuButton().click() + multisigPage.hideAccountMenuOption().should('exist').click() + hiddenAccountInfoModal.body().should('not.exist') + topMenuItems.multiproxySelectorDesktop().should('be.visible').click() + topMenuItems.multiproxySelectorOptionDesktop().should('have.length', 1) + }) + + it('removes a watched account if hidding a watched account', () => { + cy.setupAndVisit({ + url: landingPageNetworkAddress({ + network: 'westend', + address: westendWatchedAccount.address + }), + extensionConnectionAllowed: true, + injectExtensionWithAccounts: [westendMemberAccount.hidden.account], + watchedAccounts: [westendWatchedAccount.pubKey] + }) + + topMenuItems.multiproxySelectorDesktop().click() + topMenuItems.multiproxySelectorOptionDesktop().should('have.length', 3) + + // hide the watched account + multisigPage.optionsMenuButton().click() + multisigPage.hideAccountMenuOption().should('exist').click() + hiddenAccountInfoModal.body().should('be.visible') + hiddenAccountInfoModal.gotItButton().click() + + topMenuItems.multiproxySelectorDesktop().click() + topMenuItems.multiproxySelectorOptionDesktop().should('have.length', 2) + goToHiddenAccountSettings() + settingsPage.hiddenAccountsContainer().should('not.exist') + settingsPage.watchedAccountsAccordion().click() + settingsPage.watchedAccountsContainer().should('not.exist') + }) + + it('shows a warning if hidding a watched account', () => { + cy.setupAndVisit({ + url: landingPageNetworkAddress({ + network: 'westend', + address: westendWatchedAccount.address + }), + extensionConnectionAllowed: true, + injectExtensionWithAccounts: [westendMemberAccount.hidden.account], + watchedAccounts: [westendWatchedAccount.pubKey] + }) + + // topMenuItems.multiproxySelectorDesktop().click() + // topMenuItems.multiproxySelectorOptionDesktop().should('have.length', 3) + + // // hide the watched account + // multisigPage.optionsMenuButton().click() + // multisigPage.hideAccountMenuOption().should('exist').click() + // hiddenAccountInfoModal.body().should('be.visible') + // hiddenAccountInfoModal.gotItButton().click() + + // topMenuItems.multiproxySelectorDesktop().click() + // topMenuItems.multiproxySelectorOptionDesktop().should('have.length', 2) + goToHiddenAccountSettings() + settingsPage.hiddenAccountsContainer().should('not.exist') + settingsPage.watchedAccountsAccordion().click() + settingsPage.watchedAccountsContainer().should('have.length', 1) + + settingsPage.hiddenAccountsAccordion().click() + addHiddenAccount(westendWatchedAccount.address) + settingsPage.hiddenAccountWatchedWarning().should('be.visible') + settingsPage.hiddenAccountsContainer().should('not.exist') + settingsPage.watchedAccountsAccordion().click() + settingsPage.watchedAccountsContainer().should('not.exist') + }) +}) diff --git a/packages/ui/cypress/tests/name-edition-display.cy.ts b/packages/ui/cypress/tests/name-edition-display.cy.ts index ae3be2d9c..a91de4c70 100644 --- a/packages/ui/cypress/tests/name-edition-display.cy.ts +++ b/packages/ui/cypress/tests/name-edition-display.cy.ts @@ -15,7 +15,7 @@ describe('Name Edition and Display', () => { watchedAccounts: [purePublicKey] }) - settingsPage.accountContainer().within(() => { + settingsPage.watchedAccountsContainer().within(() => { accountDisplay.identicon().should('be.visible') accountDisplay.addressLabel().should('be.visible') accountDisplay.noNameLabel().should('have.text', 'No Name') @@ -32,7 +32,7 @@ describe('Name Edition and Display', () => { watchedAccounts: [purePublicKey] }) - settingsPage.accountContainer().within(() => { + settingsPage.watchedAccountsContainer().within(() => { accountDisplay.nameEditButton().click() accountDisplay.nameEditionInput().should('be.focused') // editing with enter @@ -62,7 +62,7 @@ describe('Name Edition and Display', () => { accountNames: { [purePublicKey]: originalName } }) - settingsPage.accountContainer().within(() => { + settingsPage.watchedAccountsContainer().within(() => { //edit and cancel with Escape accountDisplay.nameEditButton().click() accountDisplay.nameEditionInput().should('have.value', originalName) diff --git a/packages/ui/cypress/tests/network-switch.cy.ts b/packages/ui/cypress/tests/network-switch.cy.ts index 709383f0b..be4091fbc 100644 --- a/packages/ui/cypress/tests/network-switch.cy.ts +++ b/packages/ui/cypress/tests/network-switch.cy.ts @@ -26,7 +26,7 @@ describe('Network can be switched', () => { cy.url().should('contain', 'network=paseo') cy.url().should('contain', `address=${multisigPureAddress}`) - settingsPage.accountContainer().within(() => { + settingsPage.watchedAccountsContainer().within(() => { accountDisplay.identicon().should('be.visible') accountDisplay.nameLabel().should('contain', multisigName) accountDisplay.addressLabel().contains(multisigAddress.slice(0, 5)) @@ -45,7 +45,7 @@ describe('Network can be switched', () => { .desktopMenu() .within(() => topMenuItems.multiproxySelectorDesktop().should('not.exist')) - settingsPage.accountContainer().within(() => { + settingsPage.watchedAccountsContainer().within(() => { accountDisplay.identicon().should('be.visible') accountDisplay.nameLabel().should('contain', multisigName) accountDisplay.addressLabel().contains(kusamaAddress.slice(0, 5)) diff --git a/packages/ui/cypress/tests/watched-accounts.cy.ts b/packages/ui/cypress/tests/watched-accounts.cy.ts index 3f3765628..cba1f831f 100644 --- a/packages/ui/cypress/tests/watched-accounts.cy.ts +++ b/packages/ui/cypress/tests/watched-accounts.cy.ts @@ -14,14 +14,15 @@ import { knownMultisigs } from '../fixtures/knownMultisigs' import { getShortAddress } from '../utils/getShortAddress' const addWatchAccount = (address: string, name?: string) => { - settingsPage.accountAddressInput().type(`${address}{enter}`, { delay: 20, timeout: 8000 }) - - if (name) { - settingsPage.accountNameInput().type(name) - } + settingsPage.watchedAccountsInputsWrapper().within(() => { + settingsPage.accountAddressInput().type(`${address}{enter}`, { delay: 20, timeout: 8000 }) + if (name) { + settingsPage.accountNameInput().type(name) + } - settingsPage.addButton().should('be.enabled') - settingsPage.addButton().click() + settingsPage.addButton().should('be.enabled') + settingsPage.addButton().click() + }) } const { name: testAccountName, address: testAccountAddress } = @@ -32,12 +33,12 @@ describe('Watched Accounts', () => { cy.visit(landingPageUrl) landingPage.watchAccountButton().click() addWatchAccount(testAccountAddress, testAccountName) - settingsPage.accountContainer().should('be.visible') - settingsPage.accountContainer().within(() => { + settingsPage.watchedAccountsContainer().should('be.visible') + settingsPage.watchedAccountsContainer().within(() => { accountDisplay.identicon().should('be.visible') accountDisplay.addressLabel().should('be.visible') accountDisplay.nameLabel().should('be.visible') - settingsPage.accountDeleteButton().should('be.visible') + settingsPage.watchedAccountDeleteButton().should('be.visible') }) }) @@ -45,26 +46,26 @@ describe('Watched Accounts', () => { // add an account first cy.visit(getSettingsPageWatchAccountUrl()) addWatchAccount(testAccountAddress) - settingsPage.accountContainer().should('be.visible') + settingsPage.watchedAccountsContainer().should('be.visible') // now remove it - settingsPage.accountContainer().within(() => { - settingsPage.accountDeleteButton().click() + settingsPage.watchedAccountsContainer().within(() => { + settingsPage.watchedAccountDeleteButton().click() accountDisplay.identicon().should('not.exist') accountDisplay.addressLabel().should('not.exist') }) - settingsPage.accountContainer().should('have.length', 0) + settingsPage.watchedAccountsContainer().should('have.length', 0) }) it('can see error when attempting to add same address more than once', () => { // add an account first cy.visit(getSettingsPageWatchAccountUrl()) addWatchAccount(testAccountAddress) - settingsPage.accountContainer().should('have.length', 1) + settingsPage.watchedAccountsContainer().should('have.length', 1) // attempt to add the same account again addWatchAccount(testAccountAddress) settingsPage.errorLabel().should('be.visible').should('have.text', 'Account already added') - settingsPage.accountContainer().should('have.length', 1) + settingsPage.watchedAccountsContainer().should('have.length', 1) settingsPage.addButton().should('be.disabled') }) @@ -72,7 +73,7 @@ describe('Watched Accounts', () => { cy.visit(getSettingsPageWatchAccountUrl()) addWatchAccount('123') settingsPage.errorLabel().should('be.visible').should('have.text', 'Invalid address') - settingsPage.accountContainer().should('have.length', 0) + settingsPage.watchedAccountsContainer().should('have.length', 0) settingsPage.addButton().should('be.disabled') }) @@ -88,7 +89,7 @@ describe('Watched Accounts', () => { watchedAccounts: [multisigPublicKey] }) // ensure the multisig name is displayed in the settings account container - settingsPage.accountContainer().within(() => { + settingsPage.watchedAccountsContainer().within(() => { accountDisplay.identicon().should('be.visible') accountDisplay.nameLabel().should('be.visible').should('have.text', multisigName) }) @@ -123,7 +124,7 @@ describe('Watched Accounts', () => { watchedAccounts: [purePublicKey] }) // ensure the multisig name is displayed in the settings account container - settingsPage.accountContainer().within(() => { + settingsPage.watchedAccountsContainer().within(() => { accountDisplay.identicon().should('be.visible') accountDisplay.nameLabel().should('be.visible').should('have.text', pureName) }) @@ -172,7 +173,7 @@ describe('Watched Accounts', () => { // navigate to settings and ensure the edited name is displayed topMenuItems.settingsButton().click() settingsPage.watchedAccountsAccordion().click() - settingsPage.accountContainer().within(() => { + settingsPage.watchedAccountsContainer().within(() => { accountDisplay.nameLabel().should('have.text', 'Edited Name Test') }) }) diff --git a/packages/ui/src/App.tsx b/packages/ui/src/App.tsx index a031d73db..394b950c9 100644 --- a/packages/ui/src/App.tsx +++ b/packages/ui/src/App.tsx @@ -18,6 +18,7 @@ import { ReactiveDotProvider } from '@reactive-dot/react' import { config } from './walletConfigs' import { Suspense } from 'react' import { AssetsContextProvider } from './contexts/AssetsContext' +import { HiddenAccountsContextProvider } from './contexts/HiddenAccountsContext' const App = () => { const queryClient = new QueryClient() @@ -26,7 +27,7 @@ const App = () => { - Loading...}> + Loading...}> @@ -34,17 +35,19 @@ const App = () => { - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/packages/ui/src/components/modals/HiddenAccountInfo.tsx b/packages/ui/src/components/modals/HiddenAccountInfo.tsx new file mode 100644 index 000000000..eb60d078b --- /dev/null +++ b/packages/ui/src/components/modals/HiddenAccountInfo.tsx @@ -0,0 +1,94 @@ +import { + Checkbox, + Dialog, + DialogContent, + DialogTitle, + FormControlLabel, + Grid2 as Grid +} from '@mui/material' +import { Button } from '../library' +import { styled } from '@mui/material/styles' +import { ModalCloseButton } from '../library/ModalCloseButton' +import { useCallback, useState } from 'react' + +interface Props { + onClose: () => void + className?: string +} + +export const LOCALSTORAGE_KEY = 'multix.dontShowHiddenAccountInfo' + +const HiddenAccountInfo = ({ onClose, className }: Props) => { + const [dontShow, setDontShow] = useState(false) + const onClick = useCallback(() => { + onClose() + if (dontShow) { + localStorage.setItem(LOCALSTORAGE_KEY, 'true') + } + }, [dontShow, onClose]) + + return ( + + + Account hidden! + + + + This account is now hidden for this network. To show it again go to Settings > Hidden + Accounts. + + + { + setDontShow(!dontShow) + }} + // @ts-expect-error + inputProps={{ 'data-cy': 'checkbox-dont-show-again' }} + /> + } + /> + + + + + + ) +} + +const FooterContainerStyled = styled(Grid)` + margin-top: 1rem; + + button:last-child { + margin-left: auto; + } + + .closeButton { + margin-left: auto; + } +` + +export default styled(HiddenAccountInfo)` + .accountEdition { + margin-bottom: 1rem; + align-items: end; + } +` diff --git a/packages/ui/src/contexts/HiddenAccountsContext.tsx b/packages/ui/src/contexts/HiddenAccountsContext.tsx new file mode 100644 index 000000000..86f9baf5c --- /dev/null +++ b/packages/ui/src/contexts/HiddenAccountsContext.tsx @@ -0,0 +1,153 @@ +import { + createContext, + ReactNode, + useCallback, + useContext, + useEffect, + useMemo, + useState +} from 'react' +import { useApi } from './ApiContext' +import { getPubKeyFromAddress } from '../utils/getPubKeyFromAddress' +import { useNetwork } from './NetworkContext' +import { HexString } from 'polkadot-api' +import { useGetEncodedAddress } from '../hooks/useGetEncodedAddress' +import { useSearchParams } from 'react-router' +import { useWatchedAddresses } from './WatchedAddressesContext' + +const LOCALSTORAGE_HIDDEN_ACCOUNTS_KEY = 'multix.hiddenAccounts' + +type HiddenAccountsProps = { + children: ReactNode | ReactNode[] +} + +export interface IHiddenAccountsContext { + addHiddenAccount: (address: string) => { + removedWatchedAccount: boolean + } + removeHiddenAccount: (address: string) => void + hiddenAccounts: HiddenAccount[] + networkHiddenAccounts: string[] + isInitialized: boolean +} + +export interface HiddenAccount { + pubKey: HexString + network: string +} + +const HiddenAccountsContext = createContext(undefined) + +const HiddenAccountsContextProvider = ({ children }: HiddenAccountsProps) => { + const [hiddenAccounts, setHiddenAccounts] = useState([]) + const [isInitialized, setIsInitialized] = useState(false) + const { chainInfo } = useApi() + const { selectedNetwork } = useNetwork() + const getEncodedAddress = useGetEncodedAddress() + const [searchParams, setSearchParams] = useSearchParams({ address: '' }) + const { watchedAddresses, removeWatchedAccount } = useWatchedAddresses() + + const networkHiddenAccounts = useMemo(() => { + if (!selectedNetwork) return [] + + return hiddenAccounts + .map(({ pubKey, network }) => { + if (network !== selectedNetwork) return null + + return getEncodedAddress(pubKey) + }) + .filter(Boolean) as string[] + }, [getEncodedAddress, hiddenAccounts, selectedNetwork]) + + const addHiddenAccount = useCallback( + (address: string) => { + const pubKey = getPubKeyFromAddress(address) + const searchParamsAddress = searchParams.get('address') + const urlAddressPubKey = searchParamsAddress && getPubKeyFromAddress(searchParamsAddress) + + // if the currently selected account is being hidden + if (urlAddressPubKey === pubKey) { + setSearchParams((prev) => { + prev.delete('address') + return prev + }) + } + + // if we are hiding a watched account + // just remove it from the watch list + if (watchedAddresses.includes(address)) { + removeWatchedAccount(address) + return { removedWatchedAccount: true } + } else { + selectedNetwork && + pubKey && + setHiddenAccounts((prev) => [ + ...prev, + { pubKey, network: selectedNetwork } as HiddenAccount + ]) + return { removedWatchedAccount: false } + } + }, + [removeWatchedAccount, searchParams, selectedNetwork, setSearchParams, watchedAddresses] + ) + + const removeHiddenAccount = useCallback( + (addressToRemove: string) => { + const pubKeyToRemove = getPubKeyFromAddress(addressToRemove) + const filtered = hiddenAccounts.filter( + ({ pubKey, network }) => pubKey !== pubKeyToRemove && network === selectedNetwork + ) + setHiddenAccounts([...filtered]) + }, + [hiddenAccounts, selectedNetwork] + ) + + const loadHiddenAccounts = useCallback(() => { + if (!chainInfo) { + return + } + + const localStorageHiddenAccount = localStorage.getItem(LOCALSTORAGE_HIDDEN_ACCOUNTS_KEY) + const hiddenArray: HiddenAccount[] = localStorageHiddenAccount + ? JSON.parse(localStorageHiddenAccount) + : [] + + setHiddenAccounts(hiddenArray) + setIsInitialized(true) + }, [chainInfo]) + + useEffect(() => { + !isInitialized && loadHiddenAccounts() + }, [isInitialized, loadHiddenAccounts]) + + // persist the accounts hidden every time there's a change + useEffect(() => { + if (!isInitialized) return + + localStorage.setItem(LOCALSTORAGE_HIDDEN_ACCOUNTS_KEY, JSON.stringify(hiddenAccounts)) + }, [isInitialized, hiddenAccounts]) + + return ( + + {children} + + ) +} + +const useHiddenAccounts = () => { + const context = useContext(HiddenAccountsContext) + if (context === undefined) { + throw new Error('useHiddenAccounts must be used within a HiddenAccountsContextProvider') + } + return context +} + +export { HiddenAccountsContextProvider, useHiddenAccounts } diff --git a/packages/ui/src/contexts/ModalsContext.tsx b/packages/ui/src/contexts/ModalsContext.tsx index cf7632a4e..20faef21e 100644 --- a/packages/ui/src/contexts/ModalsContext.tsx +++ b/packages/ui/src/contexts/ModalsContext.tsx @@ -8,6 +8,7 @@ import WCSessionProposal from '../components/modals/WalletConnectSessionProposal import ProposalSigningModal, { SigningModalProps } from '../components/modals/ProposalSigning' import WalletConnectSigning from '../components/modals/WalletConnectSigning' import { useMultiProxy } from './MultiProxyContext' +import HiddenAccountInfo from '../components/modals/HiddenAccountInfo' interface ModalsContextProps { setIsEditModalOpen: (isOpen: boolean) => void @@ -17,6 +18,7 @@ interface ModalsContextProps { openWalletConnectSessionModal: ({ sessionProposal }: OpenWCModalParams) => void onOpenSigningModal: (info: SigningInfo) => void onOpenWalletConnectSigning: (request: SignClientTypes.EventArguments['session_request']) => void + onOpenHiddenAccountInfoModal: () => void } interface OpenWCModalParams { @@ -38,6 +40,7 @@ const ModalsContextProvider = ({ children }: React.PropsWithChildren) => { const [sendModalPreselection, setSendModalPreselection] = useState( DEFAULT_EASY_SETUP_SELECTION ) + const [isHiddenAccountInfoModalOpen, setIsHiddenAccountInfoModalOpen] = useState(false) const [walletConnectRequest, setWalletConnectRequest] = useState< SignClientTypes.EventArguments['session_request'] | undefined >() @@ -107,6 +110,10 @@ const ModalsContextProvider = ({ children }: React.PropsWithChildren) => { setIsOpenWalletConnectSigning(false) }, []) + const onOpenHiddenAccountInfoModal = useCallback(() => { + setIsHiddenAccountInfoModalOpen(true) + }, []) + return ( { onCloseSendModal, openWalletConnectSessionModal, onOpenSigningModal, - onOpenWalletConnectSigning + onOpenWalletConnectSigning, + onOpenHiddenAccountInfoModal }} > {children} @@ -150,6 +158,9 @@ const ModalsContextProvider = ({ children }: React.PropsWithChildren) => { request={walletConnectRequest} /> )} + {isHiddenAccountInfoModalOpen && ( + setIsHiddenAccountInfoModalOpen(false)} /> + )} ) } diff --git a/packages/ui/src/contexts/MultiProxyContext.tsx b/packages/ui/src/contexts/MultiProxyContext.tsx index c6bee0c4c..ab9e669bc 100644 --- a/packages/ui/src/contexts/MultiProxyContext.tsx +++ b/packages/ui/src/contexts/MultiProxyContext.tsx @@ -8,6 +8,7 @@ import { useAccountId } from '../hooks/useAccountId' import { getMultiProxyAddress } from '../utils/getMultiProxyAddress' import { useSearchParams } from 'react-router' import { useNetwork } from './NetworkContext' +import { useHiddenAccounts } from './HiddenAccountsContext' interface MultisigContextProps { children: React.ReactNode | React.ReactNode[] @@ -57,19 +58,22 @@ const getSignatoriesFromAccount = ( } const MultiProxyContextProvider = ({ children }: MultisigContextProps) => { + const { networkHiddenAccounts } = useHiddenAccounts() const [refetchMultisigTimeoutMinutes, setRefetchMultisigTimeoutMinutes] = useState(0) const [shouldPollMultisigs, setShouldPollMultisigs] = useState(false) const [canFindMultiProxyFromUrl, setCanFindMultiProxyFromUrl] = useState(false) const [selectedMultiProxyAddress, setSelectedMultiProxyAddress] = useState('') // if set to null, it means that it hasn't been initialized yet - const [pureProxyList, setPureProxyList] = useState( - null - ) - // if set to null, it means that it hasn't been initialized yet const [multisigList, setMultisigList] = useState(null) const multiProxyList = useMemo(() => { - return [...(pureProxyList || []), ...(multisigList || [])] - }, [multisigList, pureProxyList]) + const filteredMulti = multisigList?.filter(({ proxy, multisigs }) => { + if (proxy) return !networkHiddenAccounts.includes(proxy) + + const firstMultisig = multisigs[0].address + return !!firstMultisig && !networkHiddenAccounts.includes(firstMultisig) + }) + return filteredMulti || [] + }, [multisigList, networkHiddenAccounts]) const { ownAddressList } = useAccounts() const { watchedAddresses } = useWatchedAddresses() const { selectedNetwork } = useNetwork() @@ -127,7 +131,6 @@ const MultiProxyContextProvider = ({ children }: MultisigContextProps) => { const resetLists = useCallback(() => { setMultisigList(null) - setPureProxyList(null) }, []) const setAddressInUrl = useCallback( diff --git a/packages/ui/src/hooks/useGetIdentity.tsx b/packages/ui/src/hooks/useGetIdentity.tsx index 294780bfd..3a5188f9e 100644 --- a/packages/ui/src/hooks/useGetIdentity.tsx +++ b/packages/ui/src/hooks/useGetIdentity.tsx @@ -43,19 +43,22 @@ export const useGetIdentity = () => { } ).then((val) => { const id: IdentityInfo = { judgements: [], sub } - val?.[0].judgements.forEach(([, judgement]) => { - id.judgements.push(judgement.type) - }) - Object.entries(val?.[0]?.info || {}).forEach(([key, value]) => { - if ((value as IdentityData)?.type !== 'None') { - // console.log('key', JSONprint(key)); - // console.log('value', JSONprint(value)); - const text = (value as IdentityData)?.value as FixedSizeBinary<2> | undefined - if (text) { - id[key] = text.asText() + + if (val?.[0]) { + val?.[0].judgements.forEach(([, judgement]) => { + id.judgements.push(judgement.type) + }) + Object.entries(val?.[0]?.info || {}).forEach(([key, value]) => { + if ((value as IdentityData)?.type !== 'None') { + // console.log('key', JSONprint(key)); + // console.log('value', JSONprint(value)); + const text = (value as IdentityData)?.value as FixedSizeBinary<2> | undefined + if (text) { + id[key] = text.asText() + } } - } - }) + }) + } return id }) diff --git a/packages/ui/src/pages/Home/MultisigActionMenu.tsx b/packages/ui/src/pages/Home/MultisigActionMenu.tsx index 54afd6cd7..a1aa3ac35 100644 --- a/packages/ui/src/pages/Home/MultisigActionMenu.tsx +++ b/packages/ui/src/pages/Home/MultisigActionMenu.tsx @@ -1,17 +1,19 @@ import { Button } from '../../components/library' import OptionsMenu, { MenuOption } from '../../components/OptionsMenu' -import { useMemo } from 'react' +import { useCallback, useMemo } from 'react' import { MdOutlineLockReset as LockResetIcon } from 'react-icons/md' import { useMultiProxy } from '../../contexts/MultiProxyContext' import { useModals } from '../../contexts/ModalsContext' import { HiOutlineArrowTopRightOnSquare as LaunchIcon, HiOutlineUserPlus as IdentityIcon, - HiOutlinePencilSquare as PencilIcon + HiOutlinePencilSquare as PencilIcon, + HiOutlineEyeSlash as EyeSlashIcon } from 'react-icons/hi2' import { useGetSubscanLinks } from '../../hooks/useSubscanLink' import { EasyTransferTitle } from '../../components/modals/Send' import { useHasIdentityFeature } from '../../hooks/useHasIdentityFeature' +import { useHiddenAccounts } from '../../contexts/HiddenAccountsContext' interface MultisigActionMenuProps { withNewTransactionButton?: boolean @@ -22,10 +24,20 @@ const MultisigActionMenu = ({ withNewTransactionButton = true, menuButtonBorder }: MultisigActionMenuProps) => { - const { selectedHasProxy, selectedIsWatched, selectedMultiProxy } = useMultiProxy() + const { selectedHasProxy, selectedIsWatched, selectedMultiProxy, selectedMultiProxyAddress } = + useMultiProxy() const { setIsEditModalOpen, setIsChangeMultiModalOpen, onOpenSendModal } = useModals() const { getSubscanAccountLink } = useGetSubscanLinks() const { hasIdentityPallet, hasPplChain } = useHasIdentityFeature() + const { addHiddenAccount } = useHiddenAccounts() + const { onOpenHiddenAccountInfoModal } = useModals() + + const onHideAccount = useCallback(() => { + if (!selectedMultiProxyAddress) return + + onOpenHiddenAccountInfoModal() + addHiddenAccount(selectedMultiProxyAddress) + }, [addHiddenAccount, onOpenHiddenAccountInfoModal, selectedMultiProxyAddress]) const options: MenuOption[] = useMemo(() => { const opts = [ @@ -34,6 +46,11 @@ const MultisigActionMenu = ({ icon: , onClick: () => setIsEditModalOpen(true) }, + { + text: 'Hide this account', + icon: , + onClick: onHideAccount + }, { text: 'Subscan', icon: , @@ -63,19 +80,9 @@ const MultisigActionMenu = ({ onClick: () => setIsChangeMultiModalOpen(true) }) - // If we are NOT rendering "new transaction button" and is it's NOT a Watched account, - // the "Send transaction" item will appear in the list menu - // TODO: Individual transaction button for each mulisig - // if (!withNewTransactionButton && !selectedIsWatched) { - // opts.unshift({ - // text: 'Send transaction', - // icon: , - // onClick: () => setIsSendModalOpen(true) - // }) - // } - return opts }, [ + onHideAccount, selectedIsWatched, hasPplChain, hasIdentityPallet, diff --git a/packages/ui/src/pages/Settings/HiddenAccounts.tsx b/packages/ui/src/pages/Settings/HiddenAccounts.tsx new file mode 100644 index 000000000..7731b47e5 --- /dev/null +++ b/packages/ui/src/pages/Settings/HiddenAccounts.tsx @@ -0,0 +1,161 @@ +import { styled } from '@mui/material/styles' +import { Alert, Box, Grid2 as Grid, IconButton, Paper } from '@mui/material' +import AccountDisplay from '../../components/AccountDisplay/AccountDisplay' +import { HiOutlineXMark } from 'react-icons/hi2' +import AccountSelection from '../../components/select/AccountSelection' +import { useCallback, useEffect, useMemo, useState } from 'react' +import { useHiddenAccounts } from '../../contexts/HiddenAccountsContext' +import { useNetwork } from '../../contexts/NetworkContext' + +const HiddenAccounts = () => { + const { addHiddenAccount, networkHiddenAccounts, removeHiddenAccount } = useHiddenAccounts() + const { selectedNetwork } = useNetwork() + const [showWatchedAccountRemovedWarning, setShowWatchedAccountRemovedWarning] = useState(false) + + useEffect(() => { + if (showWatchedAccountRemovedWarning) { + setTimeout(() => { + setShowWatchedAccountRemovedWarning(false) + }, 10000) + } + }, [showWatchedAccountRemovedWarning]) + + const hasHiddenAddresses = useMemo( + () => networkHiddenAccounts.length > 0, + [networkHiddenAccounts] + ) + + const onHide = useCallback( + (address: string) => { + const shouldWhoWarning = addHiddenAccount(address) + shouldWhoWarning.removedWatchedAccount && setShowWatchedAccountRemovedWarning(true) + }, + [addHiddenAccount] + ) + + return ( + <> + {showWatchedAccountRemovedWarning && ( + + This account was a "Watched accounts". It has been removed from the watched list + + )} + {hasHiddenAddresses && ( + + Hidden accounts for {selectedNetwork}: + + )} + + {hasHiddenAddresses && ( + + + {networkHiddenAccounts.map((address) => { + return ( + + + removeHiddenAccount(address)} + data-cy="button-delete-hidden-account" + > + + + + ) + })} + + + )} + + + + + + + + ) +} + +const HiddenAccountsHeaderStyled = styled('h3')` + color: ${({ theme }) => theme.custom.gray[800]}; + font-size: 1rem; + font-weight: 400; + margin: 0 0 0.5rem 0; +` + +const PaperStyled = styled(Paper)` + border-radius: ${({ theme }) => theme.custom.borderRadius}; + border: 1px solid ${({ theme }) => theme.custom.text.borderColor}; + box-shadow: none; + + & > :last-of-type { + border-bottom: none; + } +` + +const IconButtonDeleteStyled = styled(IconButton)` + margin-left: 1rem; + height: 2.5rem; + align-self: center; +` + +const AccountDisplayStyled = styled(AccountDisplay)` + flex: 1; +` + +const AccountItemWrapperStyled = styled(Box)` + display: flex; + align-items: center; + padding: 0.75rem 1rem; + border-bottom: 1px solid ${({ theme }) => theme.custom.text.borderColor}; +` + +const AccountSelectionWrapperStyled = styled(Box)` + display: flex; + margin-bottom: 2rem; + + .MuiAutocomplete-root { + margin-right: 0 !important; + } + + .accountDropdown { + display: flex; + flex-direction: column; + + & > * { + width: 100%; + } + + & > :last-child { + margin-top: 0.5rem; + } + } +` +const AlertStryled = styled(Alert)` + margin-bottom: 1rem; +` + +export default HiddenAccounts diff --git a/packages/ui/src/pages/Settings/Settings.tsx b/packages/ui/src/pages/Settings/Settings.tsx index 7ea00cfc2..4b146c2f7 100644 --- a/packages/ui/src/pages/Settings/Settings.tsx +++ b/packages/ui/src/pages/Settings/Settings.tsx @@ -3,17 +3,27 @@ import { css, styled } from '@mui/material/styles' import WatchedAccounts from './WatchedAccounts' import { WalletConnectSession } from '../../components/WalletConnect/WalletConnectSession' import { WalletConnectActiveSessions } from '../../components/WalletConnect/WalletConnectActiveSessions' -import { HiOutlineChevronDown as ExpandMoreIcon, HiOutlineEye } from 'react-icons/hi2' +import { + HiOutlineChevronDown as ExpandMoreIcon, + HiOutlineEye, + HiOutlineEyeSlash +} from 'react-icons/hi2' import { theme } from '../../styles/theme' import { useCallback, useEffect, useState } from 'react' import { useLocation } from 'react-router' import WalletConnectSVG from '../../logos/walletConnectSVG.svg?react' +import HiddenAccounts from './HiddenAccounts' const ACCORDION_WATCHED_ACCOUNTS = 'panel-watched-accounts' const ACCORDION_WALLET_CONNECT = 'panel-wallet-connect' +const ACCORDION_HIDDEN_ACCOUNTS = 'panel-hidden-accounts' export const WATCH_ACCOUNT_ANCHOR = 'watched-accounts' +export const HIDDEN_ACCOUNTS_ANCHOR = 'hidden-accounts' -type AccordionNames = typeof ACCORDION_WATCHED_ACCOUNTS | typeof ACCORDION_WALLET_CONNECT +type AccordionNames = + | typeof ACCORDION_WATCHED_ACCOUNTS + | typeof ACCORDION_WALLET_CONNECT + | typeof ACCORDION_HIDDEN_ACCOUNTS const Settings = () => { const { hash } = useLocation() @@ -29,6 +39,10 @@ const Settings = () => { if (hash === `#${WATCH_ACCOUNT_ANCHOR}`) { onToggle(ACCORDION_WATCHED_ACCOUNTS, true) } + + if (hash === `#${HIDDEN_ACCOUNTS_ANCHOR}`) { + onToggle(ACCORDION_HIDDEN_ACCOUNTS, true) + } }, [hash, onToggle]) return ( @@ -49,6 +63,21 @@ const Settings = () => { + onToggle(ACCORDION_HIDDEN_ACCOUNTS)} + > + } + data-cy="accordion-title-hidden-accounts" + > + + Hidden accounts + + + + + onToggle(ACCORDION_WALLET_CONNECT)} @@ -122,6 +151,8 @@ const WalletConnectSVGStyled = styled(WalletConnectSVG)(commonCssImgs) const HiOutlineEyeStyled = styled(HiOutlineEye)(commonCssImgs) +const HiOutlineEyeSlashStyled = styled(HiOutlineEyeSlash)(commonCssImgs) + const SummaryLabelStyled = styled('div')` color: ${({ theme }) => theme.custom.gray[900]}; font-size: 1.25rem; diff --git a/packages/ui/src/pages/Settings/WatchedAccounts.tsx b/packages/ui/src/pages/Settings/WatchedAccounts.tsx index b1f1e4f51..fc7a93d3f 100644 --- a/packages/ui/src/pages/Settings/WatchedAccounts.tsx +++ b/packages/ui/src/pages/Settings/WatchedAccounts.tsx @@ -47,7 +47,7 @@ const WatchedAccounts = () => { )} - +