From 7cd38ed8a0359db2cbcd24eca1a7d99158bef1f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Oliv=C3=A9?= Date: Mon, 29 Jul 2024 12:41:01 +0200 Subject: [PATCH] chore: migrating interactive-replacement-token-page to ts (#26115) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/26115?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- ...tive-replacement-token-page.test.tsx.snap} | 4 +- .../{index.js => index.ts} | 0 ...active-replacement-token-page.stories.tsx} | 0 ...teractive-replacement-token-page.test.tsx} | 51 +++-- ...=> interactive-replacement-token-page.tsx} | 192 +++++++++++------- .../institutional/institution-background.ts | 4 +- 6 files changed, 154 insertions(+), 97 deletions(-) rename ui/pages/institutional/interactive-replacement-token-page/__snapshots__/{interactive-replacement-token-page.test.js.snap => interactive-replacement-token-page.test.tsx.snap} (96%) rename ui/pages/institutional/interactive-replacement-token-page/{index.js => index.ts} (100%) rename ui/pages/institutional/interactive-replacement-token-page/{interactive-replacement-token-page.stories.js => interactive-replacement-token-page.stories.tsx} (100%) rename ui/pages/institutional/interactive-replacement-token-page/{interactive-replacement-token-page.test.js => interactive-replacement-token-page.test.tsx} (85%) rename ui/pages/institutional/interactive-replacement-token-page/{interactive-replacement-token-page.js => interactive-replacement-token-page.tsx} (76%) diff --git a/ui/pages/institutional/interactive-replacement-token-page/__snapshots__/interactive-replacement-token-page.test.js.snap b/ui/pages/institutional/interactive-replacement-token-page/__snapshots__/interactive-replacement-token-page.test.tsx.snap similarity index 96% rename from ui/pages/institutional/interactive-replacement-token-page/__snapshots__/interactive-replacement-token-page.test.js.snap rename to ui/pages/institutional/interactive-replacement-token-page/__snapshots__/interactive-replacement-token-page.test.tsx.snap index 65cf56afe8ee..b7ac99b3e361 100644 --- a/ui/pages/institutional/interactive-replacement-token-page/__snapshots__/interactive-replacement-token-page.test.js.snap +++ b/ui/pages/institutional/interactive-replacement-token-page/__snapshots__/interactive-replacement-token-page.test.tsx.snap @@ -27,7 +27,6 @@ exports[`Interactive Replacement Token Page should reject if there are errors 1` >

Please go to displayName and click the 'Connect to MMI' button within their user interface to connect your accounts to MMI again. diff --git a/ui/pages/institutional/interactive-replacement-token-page/index.js b/ui/pages/institutional/interactive-replacement-token-page/index.ts similarity index 100% rename from ui/pages/institutional/interactive-replacement-token-page/index.js rename to ui/pages/institutional/interactive-replacement-token-page/index.ts diff --git a/ui/pages/institutional/interactive-replacement-token-page/interactive-replacement-token-page.stories.js b/ui/pages/institutional/interactive-replacement-token-page/interactive-replacement-token-page.stories.tsx similarity index 100% rename from ui/pages/institutional/interactive-replacement-token-page/interactive-replacement-token-page.stories.js rename to ui/pages/institutional/interactive-replacement-token-page/interactive-replacement-token-page.stories.tsx diff --git a/ui/pages/institutional/interactive-replacement-token-page/interactive-replacement-token-page.test.js b/ui/pages/institutional/interactive-replacement-token-page/interactive-replacement-token-page.test.tsx similarity index 85% rename from ui/pages/institutional/interactive-replacement-token-page/interactive-replacement-token-page.test.js rename to ui/pages/institutional/interactive-replacement-token-page/interactive-replacement-token-page.test.tsx index 1aaa788a20af..83ece34b5a36 100644 --- a/ui/pages/institutional/interactive-replacement-token-page/interactive-replacement-token-page.test.js +++ b/ui/pages/institutional/interactive-replacement-token-page/interactive-replacement-token-page.test.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { screen, act, fireEvent, waitFor } from '@testing-library/react'; import configureMockStore from 'redux-mock-store'; +import { useHistory } from 'react-router-dom'; import thunk from 'redux-thunk'; import { renderWithProvider } from '../../../../test/lib/render-helpers'; import mockState from '../../../../test/data/mock-state.json'; @@ -10,6 +11,15 @@ import { shortenAddress } from '../../../helpers/utils/util'; import { getSelectedInternalAccountFromMockState } from '../../../../test/jest/mocks'; import InteractiveReplacementTokenPage from '.'; +const mockHistoryPush = jest.fn(); + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useHistory: () => ({ + push: mockHistoryPush, + }), +})); + const custodianAccounts = [ { address: '0x9d0ba4ddac06032527b140912ec808ab9451b788', @@ -73,17 +83,13 @@ const connectRequests = [ labels, origin: 'origin', environment: 'environment', + token: 'token1', }, ]; -const props = { - history: { - push: jest.fn(), - }, -}; - -const render = ({ newState } = {}) => { +const render = ({ newState = {} } = {}) => { const mockSelectedInternalAccount = { + // @ts-expect-error: todo: fix mockState ts(2345) missing properties expected by the function ...getSelectedInternalAccountFromMockState(mockState), address, }; @@ -129,10 +135,7 @@ const render = ({ newState } = {}) => { const mockStore = configureMockStore(middlewares); const store = mockStore(state); - return renderWithProvider( - , - store, - ); + return renderWithProvider(, store); }; describe('Interactive Replacement Token Page', function () { @@ -145,7 +148,9 @@ describe('Interactive Replacement Token Page', function () { SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP[CHAIN_IDS.MAINNET] }address/${custodianAddress}`; - await act(async () => await render()); + await act(async () => { + await render(); + }); expect(screen.getByText(accountName)).toBeInTheDocument(); const link = screen.getByRole('link', { @@ -189,14 +194,13 @@ describe('Interactive Replacement Token Page', function () { }); it('should call onRemoveAddTokenConnectRequest, setCustodianNewRefreshToken, and dispatch showInteractiveReplacementTokenBanner when handleApprove is called', async () => { - const mostRecentOverviewPage = { - pathname: '/institutional-features/done', - state: { - description: - 'You can now use your custodian accounts in MetaMask Institutional.', - imgSrc: 'iconUrl', - title: 'Your custodian token has been refreshed', - }, + const history = useHistory(); + const mostRecentOverviewPage = '/institutional-features/done'; + const mostRecentOverviewPageState = { + description: + 'You can now use your custodian accounts in MetaMask Institutional.', + imgSrc: 'iconUrl', + title: 'Your custodian token has been refreshed', }; await act(async () => { @@ -211,8 +215,11 @@ describe('Interactive Replacement Token Page', function () { environment: connectRequests[0].environment, token: connectRequests[0].token, }); - expect(props.history.push).toHaveBeenCalled(); - expect(props.history.push).toHaveBeenCalledWith(mostRecentOverviewPage); + expect(history.push).toHaveBeenCalled(); + expect(history.push).toHaveBeenCalledWith( + mostRecentOverviewPage, + mostRecentOverviewPageState, + ); }); it('should reject if there are errors', async () => { diff --git a/ui/pages/institutional/interactive-replacement-token-page/interactive-replacement-token-page.js b/ui/pages/institutional/interactive-replacement-token-page/interactive-replacement-token-page.tsx similarity index 76% rename from ui/pages/institutional/interactive-replacement-token-page/interactive-replacement-token-page.js rename to ui/pages/institutional/interactive-replacement-token-page/interactive-replacement-token-page.tsx index 14f98047e4b5..6b9feec1ec72 100644 --- a/ui/pages/institutional/interactive-replacement-token-page/interactive-replacement-token-page.js +++ b/ui/pages/institutional/interactive-replacement-token-page/interactive-replacement-token-page.tsx @@ -1,78 +1,119 @@ import React, { useEffect, useRef, useState } from 'react'; -import { useSelector, useDispatch } from 'react-redux'; -import PropTypes from 'prop-types'; -import { toChecksumHexAddress } from '../../../../shared/modules/hexstring-utils'; -import { getMostRecentOverviewPage } from '../../../ducks/history/history'; -import { getInstitutionalConnectRequests } from '../../../ducks/institutional/institutional'; +import { useDispatch, useSelector } from 'react-redux'; +import { useHistory } from 'react-router-dom'; import { - getMetaMaskAccounts, - getSelectedInternalAccount, -} from '../../../selectors'; -import CustodyLabels from '../../../components/institutional/custody-labels/custody-labels'; -import PulseLoader from '../../../components/ui/pulse-loader'; -import { INSTITUTIONAL_FEATURES_DONE_ROUTE } from '../../../helpers/constants/routes'; -import { SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP } from '../../../../shared/constants/swaps'; -import { CHAIN_IDS } from '../../../../shared/constants/network'; -import { shortenAddress } from '../../../helpers/utils/util'; -import Tooltip from '../../../components/ui/tooltip'; -import { useI18nContext } from '../../../hooks/useI18nContext'; -import { - mmiActionsFactory, - showInteractiveReplacementTokenBanner, -} from '../../../store/institutional/institution-background'; -import { - Label, - Icon, + Box, + Button, ButtonLink, + ButtonSize, + ButtonVariant, + Icon, IconName, IconSize, - Box, - Button, - BUTTON_VARIANT, - BUTTON_SIZES, + Label, Text, } from '../../../components/component-library'; import { - OverflowWrap, - TextColor, - JustifyContent, BlockSize, Display, FlexDirection, IconColor, + JustifyContent, + OverflowWrap, + TextColor, } from '../../../helpers/constants/design-system'; +import { useI18nContext } from '../../../hooks/useI18nContext'; import { useCopyToClipboard } from '../../../hooks/useCopyToClipboard'; +import { getMetaMaskAccounts } from '../../../selectors'; +import { getInstitutionalConnectRequests } from '../../../ducks/institutional/institutional'; +import { getSelectedInternalAccount } from '../../../selectors/selectors'; +import { toChecksumHexAddress } from '../../../../shared/modules/hexstring-utils'; +import { SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP } from '../../../../shared/constants/swaps'; +import { CHAIN_IDS } from '../../../../shared/constants/network'; +import { + mmiActionsFactory, + showInteractiveReplacementTokenBanner, +} from '../../../store/institutional/institution-background'; +import CustodyLabels from '../../../components/institutional/custody-labels'; +import { INSTITUTIONAL_FEATURES_DONE_ROUTE } from '../../../helpers/constants/routes'; +import PulseLoader from '../../../components/ui/pulse-loader'; +import Tooltip from '../../../components/ui/tooltip'; +import { getMostRecentOverviewPage } from '../../../ducks/history/history'; +import { shortenAddress } from '../../../helpers/utils/util'; -const getButtonLinkHref = ({ address }) => { +const getButtonLinkHref = ({ address }: { address: string }) => { const url = SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP[CHAIN_IDS.MAINNET]; return `${url}address/${address}`; }; -export default function InteractiveReplacementTokenPage({ history }) { +type LabelItem = { + key: string; + value: string; +}; + +type TokenAccount = { + address: string; + name: string; + labels: LabelItem[]; + balance: number; +}; + +type ConnectRequest = { + origin: string; + environment: string; + token: string; + service: string; + labels: LabelItem[]; +}; + +type Custodian = { + envName: string; + iconUrl?: string; + displayName: string; +}; + +type State = { + metamask: { + custodyAccountDetails: { [address: string]: { custodianName?: string } }; + interactiveReplacementToken?: { url: string }; + mmiConfiguration: { custodians: Custodian[] }; + }; + appState: { + modal: { + modalState: { + props?: { address: string }; + }; + }; + }; +}; + +const InteractiveReplacementTokenPage: React.FC = () => { const dispatch = useDispatch(); + const history = useHistory(); const isMountedRef = useRef(false); const mmiActions = mmiActionsFactory(); const address = useSelector( - (state) => state.appState.modal.modalState.props?.address, + (state: State) => state.appState.modal.modalState.props?.address, ); const { custodyAccountDetails, interactiveReplacementToken, mmiConfiguration, - } = useSelector((state) => state.metamask); + } = useSelector((state: State) => state.metamask); const { address: selectedAddress } = useSelector(getSelectedInternalAccount); const { custodianName } = custodyAccountDetails[toChecksumHexAddress(address || selectedAddress)] || {}; const { url } = interactiveReplacementToken || {}; const { custodians } = mmiConfiguration; - const custodian = - custodians.find((item) => item.envName === custodianName) || {}; + const custodian: Custodian | undefined = custodians.find( + (item) => item.envName === custodianName, + ); const mostRecentOverviewPage = useSelector(getMostRecentOverviewPage); const metaMaskAccounts = useSelector(getMetaMaskAccounts); const connectRequests = useSelector(getInstitutionalConnectRequests); const [isLoading, setIsLoading] = useState(false); - const [tokenAccounts, setTokenAccounts] = useState([]); + const [tokenAccounts, setTokenAccounts] = useState([]); const [error, setError] = useState(false); const t = useI18nContext(); const [copied, handleCopy] = useCopyToClipboard(); @@ -85,7 +126,9 @@ export default function InteractiveReplacementTokenPage({ history }) { useEffect(() => { isMountedRef.current = true; - return () => (isMountedRef.current = false); + return () => { + isMountedRef.current = false; + }; }, []); useEffect(() => { @@ -99,26 +142,29 @@ export default function InteractiveReplacementTokenPage({ history }) { } try { - const custodianAccounts = await dispatch( + const custodianAccounts = (await dispatch( getCustodianAccounts( connectRequest.token, connectRequest.environment, connectRequest.service, false, ), - ); + )) as unknown as TokenAccount[]; const filteredAccounts = custodianAccounts.filter( - (account) => metaMaskAccounts[account.address.toLowerCase()], + (account: TokenAccount) => + metaMaskAccounts[account.address.toLowerCase()], ); - const mappedAccounts = filteredAccounts.map((account) => ({ - address: account.address, - name: account.name, - labels: account.labels, - balance: - metaMaskAccounts[account.address.toLowerCase()]?.balance || 0, - })); + const mappedAccounts = filteredAccounts.map( + (account: TokenAccount) => ({ + address: account.address, + name: account.name, + labels: account.labels, + balance: + metaMaskAccounts[account.address.toLowerCase()]?.balance || 0, + }), + ); if (isMounted) { setTokenAccounts(mappedAccounts); @@ -154,7 +200,11 @@ export default function InteractiveReplacementTokenPage({ history }) { return null; } - const onRemoveAddTokenConnectRequest = ({ origin, environment, token }) => { + const onRemoveAddTokenConnectRequest = ({ + origin, + environment, + token, + }: ConnectRequest) => { dispatch( removeAddTokenConnectRequest({ origin, @@ -172,7 +222,7 @@ export default function InteractiveReplacementTokenPage({ history }) { const handleApprove = async () => { if (error) { global.platform.openTab({ - url, + url: url || '', }); handleReject(); return; @@ -196,13 +246,10 @@ export default function InteractiveReplacementTokenPage({ history }) { onRemoveAddTokenConnectRequest(connectRequest); - history.push({ - pathname: INSTITUTIONAL_FEATURES_DONE_ROUTE, - state: { - imgSrc: custodian?.iconUrl, - title: t('custodianReplaceRefreshTokenChangedTitle'), - description: t('custodianReplaceRefreshTokenChangedSubtitle'), - }, + history.push(INSTITUTIONAL_FEATURES_DONE_ROUTE, { + imgSrc: custodian?.iconUrl, + title: t('custodianReplaceRefreshTokenChangedTitle'), + description: t('custodianReplaceRefreshTokenChangedSubtitle'), }); if (isMountedRef.current) { @@ -231,14 +278,16 @@ export default function InteractiveReplacementTokenPage({ history }) { display={Display.Flex} marginRight={7} marginLeft={7} - overflowwrap={OverflowWrap.BreakWord} color={TextColor.textAlternative} className="interactive-replacement-token-page" > {error ? ( - + {t('custodianReplaceRefreshTokenChangedFailed', [ - custodian.displayName || 'Custodian', + custodian?.displayName || 'Custodian', ])} ) : ( @@ -265,7 +314,11 @@ export default function InteractiveReplacementTokenPage({ history }) { marginRight={2} htmlFor={`address-${idx}`} > - + {account.name} @@ -278,6 +331,7 @@ export default function InteractiveReplacementTokenPage({ history }) { as="span" display={Display.Flex} className="interactive-replacement-token-page__item__address" + overflowWrap={OverflowWrap.BreakWord} > @@ -361,8 +415,6 @@ export default function InteractiveReplacementTokenPage({ history }) { ); -} - -InteractiveReplacementTokenPage.propTypes = { - history: PropTypes.object, }; + +export default InteractiveReplacementTokenPage; diff --git a/ui/store/institutional/institution-background.ts b/ui/store/institutional/institution-background.ts index 5cc9e91c6436..d1fa5cc074ff 100644 --- a/ui/store/institutional/institution-background.ts +++ b/ui/store/institutional/institution-background.ts @@ -19,8 +19,8 @@ export function showInteractiveReplacementTokenBanner({ url, oldRefreshToken, }: { - url: string; - oldRefreshToken: string; + url?: string; + oldRefreshToken?: string; }) { return async (dispatch: MetaMaskReduxDispatch) => { try {