diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 382dd314a..5d7a113e0 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -929,6 +929,21 @@ "custodianAccount": { "message": "Custodian account" }, + "custodyRefreshTokenModalDescription": { + "message": "Please go to $1 and click the 'Connect to MMI' button within their user interface to connect your accounts to MMI again." + }, + "custodyRefreshTokenModalDescription1": { + "message": "Your custodian issues a token that authenticates the MetaMask Institutional extension, allowing you to connect your accounts." + }, + "custodyRefreshTokenModalDescription2": { + "message": "This token expires after a certain period for security reasons. This requires you to reconnect to MMI." + }, + "custodyRefreshTokenModalSubtitle": { + "message": "Why am I seeing this?" + }, + "custodyRefreshTokenModalTitle": { + "message": "Your custodian session has expired" + }, "custom": { "message": "Advanced" }, diff --git a/ui/components/institutional/interactive-replacement-token-modal/index.js b/ui/components/institutional/interactive-replacement-token-modal/index.js new file mode 100644 index 000000000..1f1b5f7f6 --- /dev/null +++ b/ui/components/institutional/interactive-replacement-token-modal/index.js @@ -0,0 +1 @@ +export { default } from './interactive-replacement-token-modal'; diff --git a/ui/components/institutional/interactive-replacement-token-modal/interactive-replacement-token-modal.js b/ui/components/institutional/interactive-replacement-token-modal/interactive-replacement-token-modal.js new file mode 100644 index 000000000..0f40723d4 --- /dev/null +++ b/ui/components/institutional/interactive-replacement-token-modal/interactive-replacement-token-modal.js @@ -0,0 +1,154 @@ +import React, { useContext } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import Modal from '../../app/modal'; +import { useI18nContext } from '../../../hooks/useI18nContext'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; +import { hideModal } from '../../../store/actions'; +import { getSelectedAddress } from '../../../selectors/selectors'; +import { toChecksumHexAddress } from '../../../../shared/modules/hexstring-utils'; +import { Text } from '../../component-library'; +import Box from '../../ui/box'; +import { + BLOCK_SIZES, + BackgroundColor, + DISPLAY, + FLEX_WRAP, + FLEX_DIRECTION, + BorderRadius, + FONT_WEIGHT, + TEXT_ALIGN, + AlignItems, +} from '../../../helpers/constants/design-system'; + +const InteractiveReplacementTokenModal = () => { + const t = useI18nContext(); + const trackEvent = useContext(MetaMetricsContext); + const dispatch = useDispatch(); + + const { url } = useSelector( + (state) => state.metamask.interactiveReplacementToken || {}, + ); + + const { custodians } = useSelector( + (state) => state.metamask.mmiConfiguration, + ); + const address = useSelector(getSelectedAddress); + const custodyAccountDetails = useSelector( + (state) => + state.metamask.custodyAccountDetails[toChecksumHexAddress(address)], + ); + + const custodianName = custodyAccountDetails?.custodianName; + const custodian = + custodians.find((item) => item.name === custodianName) || {}; + + const renderCustodyInfo = () => { + let img; + + if (custodian.iconUrl) { + img = ( + + + {custodian.displayName} + + + ); + } else { + img = ( + + {custodian.displayName} + + ); + } + + return ( + <> + {img} + + {t('custodyRefreshTokenModalTitle')} + + + {t('custodyRefreshTokenModalDescription', [custodian.displayName])} + + + {t('custodyRefreshTokenModalSubtitle')} + + + {t('custodyRefreshTokenModalDescription1')} + + + {t('custodyRefreshTokenModalDescription2')} + + + ); + }; + + const handleSubmit = () => { + global.platform.openTab({ + url, + }); + + trackEvent({ + category: 'MMI', + event: 'User clicked refresh token link', + }); + }; + + const handleClose = () => { + dispatch(hideModal()); + }; + + return ( + + + {renderCustodyInfo(custodian)} + + + ); +}; + +export default InteractiveReplacementTokenModal; diff --git a/ui/components/institutional/interactive-replacement-token-modal/interactive-replacement-token-modal.stories.js b/ui/components/institutional/interactive-replacement-token-modal/interactive-replacement-token-modal.stories.js new file mode 100644 index 000000000..86c03504c --- /dev/null +++ b/ui/components/institutional/interactive-replacement-token-modal/interactive-replacement-token-modal.stories.js @@ -0,0 +1,72 @@ +import React from 'react'; +import { Provider } from 'react-redux'; +import configureStore from '../../../store/store'; +import testData from '../../../../.storybook/test-data'; +import InteractiveReplacementTokenModal from '.'; + +const customData = { + ...testData, + metamask: { + ...testData.metamask, + mmiConfiguration: { + portfolio: { + enabled: true, + url: 'https://dev.metamask-institutional.io/', + }, + features: { + websocketApi: true, + }, + custodians: [ + { + refreshTokenUrl: + 'https://saturn-custody.dev.metamask-institutional.io/oauth/token', + name: 'saturn-dev', + displayName: 'Saturn Custody', + enabled: true, + mmiApiUrl: 'https://api.dev.metamask-institutional.io/v1', + websocketApiUrl: + 'wss://websocket.dev.metamask-institutional.io/v1/ws', + apiBaseUrl: + 'https://saturn-custody.dev.metamask-institutional.io/eth', + iconUrl: + 'https://saturn-custody-ui.dev.metamask-institutional.io/saturn.svg', + isNoteToTraderSupported: true, + }, + ], + }, + custodyAccountDetails: { + '0xAddress': { + address: '0xAddress', + details: 'details', + custodyType: 'testCustody - Saturn', + custodianName: 'saturn-dev', + }, + }, + provider: { + type: 'test', + }, + selectedAddress: '0xAddress', + isUnlocked: true, + interactiveReplacementToken: { + oldRefreshToken: 'abc', + url: 'https://saturn-custody-ui.dev.metamask-institutional.io', + }, + preferences: { + useNativeCurrencyAsPrimaryCurrency: true, + }, + }, +}; + +const store = configureStore(customData); + +export default { + title: 'Components/Institutional/InteractiveReplacementToken-Modal', + decorators: [(story) => {story()}], + component: InteractiveReplacementTokenModal, +}; + +export const DefaultStory = (args) => ( + +); + +DefaultStory.storyName = 'InteractiveReplacementTokenModal'; diff --git a/ui/components/institutional/interactive-replacement-token-modal/interactive-replacement-token-modal.test.js b/ui/components/institutional/interactive-replacement-token-modal/interactive-replacement-token-modal.test.js new file mode 100644 index 000000000..4a78a2237 --- /dev/null +++ b/ui/components/institutional/interactive-replacement-token-modal/interactive-replacement-token-modal.test.js @@ -0,0 +1,91 @@ +import React from 'react'; +import sinon from 'sinon'; +import configureMockStore from 'redux-mock-store'; +import { fireEvent, waitFor } from '@testing-library/react'; +import { renderWithProvider } from '../../../../test/lib/render-helpers'; +import testData from '../../../../.storybook/test-data'; +import InteractiveReplacementTokenModal from '.'; + +describe('Interactive Replacement Token Modal', function () { + const mockStore = { + ...testData, + metamask: { + ...testData.metamask, + mmiConfiguration: { + portfolio: { + enabled: true, + url: 'https://dev.metamask-institutional.io/', + }, + features: { + websocketApi: true, + }, + custodians: [ + { + refreshTokenUrl: + 'https://saturn-custody.dev.metamask-institutional.io/oauth/token', + name: 'saturn-dev', + displayName: 'Saturn Custody', + enabled: true, + mmiApiUrl: 'https://api.dev.metamask-institutional.io/v1', + websocketApiUrl: + 'wss://websocket.dev.metamask-institutional.io/v1/ws', + apiBaseUrl: + 'https://saturn-custody.dev.metamask-institutional.io/eth', + iconUrl: + 'https://saturn-custody-ui.dev.metamask-institutional.io/saturn.svg', + isNoteToTraderSupported: true, + }, + ], + }, + custodyAccountDetails: { + '0xAddress': { + address: '0xAddress', + details: 'details', + custodyType: 'testCustody - Saturn', + custodianName: 'saturn-dev', + }, + }, + provider: { + type: 'test', + }, + selectedAddress: '0xAddress', + isUnlocked: true, + interactiveReplacementToken: { + oldRefreshToken: 'abc', + url: 'https://saturn-custody-ui.dev.metamask-institutional.io', + }, + preferences: { + useNativeCurrencyAsPrimaryCurrency: true, + }, + }, + }; + + const store = configureMockStore()(mockStore); + + it('should render the interactive-replacement-token-modal', () => { + const { getByText, getByTestId } = renderWithProvider( + , + store, + ); + + expect(getByTestId('interactive-replacement-token-modal')).toBeVisible(); + expect(getByText('Your custodian session has expired')).toBeInTheDocument(); + }); + + it('opens new tab on Open Codefi Compliance click', async () => { + global.platform = { openTab: sinon.spy() }; + + const { container } = renderWithProvider( + , + store, + ); + + const button = container.getElementsByClassName('btn-primary')[0]; + + fireEvent.click(button); + + await waitFor(() => { + expect(global.platform.openTab.calledOnce).toStrictEqual(true); + }); + }); +}); diff --git a/ui/pages/institutional/confirm-add-institutional-feature/__snapshots__/confirm-add-institutional-feature.test.js.snap b/ui/pages/institutional/confirm-add-institutional-feature/__snapshots__/confirm-add-institutional-feature.test.js.snap index 2d68b5531..10ee36b9b 100644 --- a/ui/pages/institutional/confirm-add-institutional-feature/__snapshots__/confirm-add-institutional-feature.test.js.snap +++ b/ui/pages/institutional/confirm-add-institutional-feature/__snapshots__/confirm-add-institutional-feature.test.js.snap @@ -42,26 +42,28 @@ exports[`Confirm Add Institutional Feature opens confirm institutional sucessful

- + + `; diff --git a/ui/pages/institutional/confirm-add-institutional-feature/confirm-add-institutional-feature.js b/ui/pages/institutional/confirm-add-institutional-feature/confirm-add-institutional-feature.js index 25a52fc16..0e89910e2 100644 --- a/ui/pages/institutional/confirm-add-institutional-feature/confirm-add-institutional-feature.js +++ b/ui/pages/institutional/confirm-add-institutional-feature/confirm-add-institutional-feature.js @@ -7,12 +7,17 @@ import PulseLoader from '../../../components/ui/pulse-loader'; import { INSTITUTIONAL_FEATURES_DONE_ROUTE } from '../../../helpers/constants/routes'; import { MetaMetricsContext } from '../../../contexts/metametrics'; import { getMostRecentOverviewPage } from '../../../ducks/history/history'; -import { Text } from '../../../components/component-library'; +import { + Text, + BUTTON_SIZES, + BUTTON_TYPES, +} from '../../../components/component-library'; import { TextColor, TextVariant, OVERFLOW_WRAP, TEXT_ALIGN, + DISPLAY, } from '../../../helpers/constants/design-system'; import Box from '../../../components/ui/box'; import { mmiActionsFactory } from '../../../store/institutional/institution-background'; @@ -169,16 +174,15 @@ export default function ConfirmAddInstitutionalFeature({ history }) { )} - + {isLoading ? ( -
- -
+ ) : ( - +
)}