1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-10-22 03:12:42 +02:00

[MMI] Created custody-confirm-link-modal component (#18632)

* Created custody-confirm-link component

* Removed selector

* Finished component

* Changed button to use design system

* Fixed eslint issue

* updated snapshot

* fixed eslint problems

* Added more tests and added color design systems

* Fixed comments

* Removed unnecessary code

* Removed unneeded code and fixed eslint issues
This commit is contained in:
Albert Olivé 2023-04-25 15:39:16 +02:00 committed by GitHub
parent cbe438e704
commit 4df363b251
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 485 additions and 0 deletions

View File

@ -466,6 +466,9 @@
"average": {
"message": "Average"
},
"awaitingApproval": {
"message": "Awaiting approval..."
},
"back": {
"message": "Back"
},
@ -932,6 +935,9 @@
"custodianAccount": {
"message": "Custodian account"
},
"custodyDeeplinkDescription": {
"message": "Approve the transaction in the $1 app. Once all required custody approvals have been performed the transaction will complete. Check your $1 app for status."
},
"custodyRefreshTokenModalDescription": {
"message": "Please go to $1 and click the 'Connect to MMI' button within their user interface to connect your accounts to MMI again."
},

View File

@ -538,6 +538,9 @@ export enum MetaMetricsEventName {
OnboardingWalletVideoPlay = 'SRP Intro Video Played',
OnboardingTwitterClick = 'External Link Clicked',
ServiceWorkerRestarted = 'Service Worker Restarted',
///: BEGIN:ONLY_INCLUDE_IN(mmi)
UserClickedDeepLink = 'User clicked deeplink',
///: END:ONLY_INCLUDE_IN
}
export enum MetaMetricsEventAccountType {
@ -577,6 +580,9 @@ export enum MetaMetricsEventCategory {
Wallet = 'Wallet',
Desktop = 'Desktop',
ServiceWorkers = 'service_workers',
///: BEGIN:ONLY_INCLUDE_IN(mmi)
MMI = 'Institutional',
///: END:ONLY_INCLUDE_IN
}
export enum MetaMetricsEventLinkType {

View File

@ -105,5 +105,6 @@
@import 'approve-content-card/index';
@import 'transaction-alerts/transaction-alerts';
///: BEGIN:ONLY_INCLUDE_IN(mmi)
@import '../institutional/custody-confirm-link-modal/index';
@import '../institutional/transaction-failed-modal/index';
///: END:ONLY_INCLUDE_IN

View File

@ -0,0 +1,40 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Custody Confirm Link should match snapshot 1`] = `
<div>
<div
class="box box--display-flex box--flex-direction-column"
>
<div
class="box box--padding-top-5 box--display-flex box--flex-direction-row box--justify-content-center box--align-items-center"
>
<img
alt="MMI logo"
class="custody-confirm-link__img"
src="/images/logo/mmi-logo.svg"
/>
&gt;
<img
alt="saturn-dev"
class="custody-confirm-link__img"
src="https://saturn-custody-ui.dev.metamask-institutional.io/saturn.svg"
/>
</div>
<h4
class="box mm-text mm-text--heading-lg mm-text--text-align-center box--padding-top-4 box--flex-direction-row box--color-text-default"
>
Awaiting approval...
</h4>
<p
class="box mm-text custody-confirm-link__description mm-text--body-sm mm-text--text-align-center box--padding-top-4 box--padding-right-5 box--padding-bottom-10 box--padding-left-5 box--flex-direction-row box--color-text-default"
>
Approve the transaction in the Saturn Custody app. Once all required custody approvals have been performed the transaction will complete. Check your Saturn Custody app for status.
</p>
<button
class="box mm-text mm-button-base mm-button-base--size-md custody-confirm-link__btn mm-button-primary mm-text--body-md box--padding-right-4 box--padding-left-4 box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-primary-inverse box--background-color-primary-default box--rounded-pill"
>
Close
</button>
</div>
</div>
`;

View File

@ -0,0 +1,135 @@
import React, { useContext } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { MetaMetricsContext } from '../../../contexts/metametrics';
import {
MetaMetricsEventName,
MetaMetricsEventCategory,
} from '../../../../shared/constants/metametrics';
import { useI18nContext } from '../../../hooks/useI18nContext';
import withModalProps from '../../../helpers/higher-order-components/with-modal-props';
import { toChecksumHexAddress } from '../../../../shared/modules/hexstring-utils';
import { mmiActionsFactory } from '../../../store/institutional/institution-background';
import { hideModal, setSelectedAddress } from '../../../store/actions';
import { getMetaMaskAccountsRaw } from '../../../selectors';
import {
getMMIAddressFromModalOrAddress,
getCustodyAccountDetails,
getMMIConfiguration,
} from '../../../selectors/institutional/selectors';
import Box from '../../ui/box/box';
import {
AlignItems,
DISPLAY,
FLEX_DIRECTION,
FontWeight,
JustifyContent,
TextAlign,
TextColor,
TextVariant,
} from '../../../helpers/constants/design-system';
import { Text, Button } from '../../component-library';
const CustodyConfirmLink = () => {
const t = useI18nContext();
const dispatch = useDispatch();
const mmiActions = mmiActionsFactory();
const trackEvent = useContext(MetaMetricsContext);
const mmiAccounts = useSelector(getMetaMaskAccountsRaw);
const address = useSelector(getMMIAddressFromModalOrAddress);
const custodyAccountDetails = useSelector(getCustodyAccountDetails);
const { custodians } = useSelector(getMMIConfiguration);
const { custodianName } =
custodyAccountDetails[toChecksumHexAddress(address)] || {};
const { displayName, iconUrl } = custodians.find(
(item) => item.name === custodianName || {},
);
const { url, ethereum, text, action } = useSelector(
(state) => state.appState.modal.modalState.props.link || {},
);
const onClick = () => {
if (url) {
global.platform.openTab({ url });
}
if (ethereum) {
const ethAccount = Object.keys(mmiAccounts).find((account) =>
ethereum.accounts.includes(account.toLowerCase()),
);
ethAccount && dispatch(setSelectedAddress(ethAccount.toLowerCase()));
}
trackEvent({
category: MetaMetricsEventCategory.MMI,
event: MetaMetricsEventName.UserClickedDeepLink,
});
dispatch(mmiActions.setWaitForConfirmDeepLinkDialog(false));
dispatch(hideModal());
};
return (
<Box display={DISPLAY.FLEX} flexDirection={FLEX_DIRECTION.COLUMN}>
{iconUrl ? (
<Box
display={DISPLAY.FLEX}
alignItems={AlignItems.center}
justifyContent={JustifyContent.center}
paddingTop={5}
>
<img
className="custody-confirm-link__img"
src="/images/logo/mmi-logo.svg"
alt="MMI logo"
/>
{'>'}
<img
className="custody-confirm-link__img"
src={iconUrl}
alt={custodianName}
/>
</Box>
) : (
<Box
display={DISPLAY.FLEX}
alignItems={AlignItems.center}
justifyContent={JustifyContent.center}
paddingTop={5}
>
<span>{custodianName}</span>
</Box>
)}
<Text
as="h4"
paddingTop={4}
variant={TextVariant.headingLg}
textAlign={TextAlign.Center}
fontWeight={FontWeight.bold}
>
{t('awaitingApproval')}
</Text>
<Text
as="p"
paddingTop={4}
paddingRight={5}
paddingLeft={5}
paddingBottom={10}
textAlign={TextAlign.Center}
color={TextColor.textDefault}
variant={TextVariant.bodySm}
className="custody-confirm-link__description"
>
{text || t('custodyDeeplinkDescription', [displayName])}
</Text>
<Button
type="primary"
className="custody-confirm-link__btn"
onClick={onClick}
>
{action || (action ? t('openCustodianApp', [displayName]) : t('close'))}
</Button>
</Box>
);
};
export default withModalProps(CustodyConfirmLink);

View File

@ -0,0 +1,73 @@
import React from 'react';
import { Provider } from 'react-redux';
import configureStore from '../../../store/store';
import testData from '../../../../.storybook/test-data';
import CustodyConfirmLink from '.';
const customData = {
...testData,
appState: {
...testData.appState,
modal: {
modalState: {
props: {
link: {
url: 'test-url',
ethereum: {
accounts: [{}],
},
text: '',
action: '',
},
},
},
},
},
metamask: {
...testData.metamask,
mmiConfiguration: {
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',
},
};
const store = configureStore(customData);
export default {
title: 'Components/Institutional/CustodyConfirmLink',
decorators: [(story) => <Provider store={store}>{story()}</Provider>],
component: CustodyConfirmLink,
args: {},
};
export const DefaultStory = (args) => <CustodyConfirmLink {...args} />;
DefaultStory.storyName = 'CustodyConfirmLink';

View File

@ -0,0 +1,192 @@
import React from 'react';
import { fireEvent } from '@testing-library/react';
import configureStore from 'redux-mock-store';
import { renderWithProvider } from '../../../../test/lib/render-helpers';
import testData from '../../../../.storybook/test-data';
import { hideModal } from '../../../store/actions';
import CustodyConfirmLink from '.';
const mockedSetWaitForConfirmDeepLinkDialog = jest
.fn()
.mockReturnValue({ type: 'TYPE' });
jest.mock('../../../store/institutional/institution-background', () => ({
mmiActionsFactory: () => ({
setWaitForConfirmDeepLinkDialog: mockedSetWaitForConfirmDeepLinkDialog,
}),
}));
jest.mock('../../../store/actions', () => ({
hideModal: jest.fn().mockReturnValue({ type: 'TYPE' }),
}));
const mockedCustodianName = 'saturn-dev';
describe('Custody Confirm Link', () => {
const mockStore = {
...testData,
appState: {
...testData.appState,
modal: {
modalState: {
props: {
link: {
url: 'test-url',
ethereum: {
accounts: [{}],
},
text: '',
action: '',
},
},
},
},
},
metamask: {
...testData.metamask,
mmiConfiguration: {
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: mockedCustodianName,
},
},
provider: {
type: 'test',
},
selectedAddress: '0xAddress',
},
};
let store = configureStore()(mockStore);
it('tries to open new tab with deeplink URL', () => {
global.platform = { openTab: jest.fn() };
const { getByRole } = renderWithProvider(<CustodyConfirmLink />, store);
fireEvent.click(getByRole('button'));
expect(global.platform.openTab).toHaveBeenCalledWith({
url: 'test-url',
});
expect(mockedSetWaitForConfirmDeepLinkDialog).toHaveBeenCalledWith(false);
expect(hideModal).toHaveBeenCalledTimes(1);
});
it('should match snapshot', () => {
const { container } = renderWithProvider(<CustodyConfirmLink />, store);
expect(container).toMatchSnapshot();
});
it('shows custodian name when iconUrl is undefined', () => {
const customMockStore = {
...mockStore,
metamask: {
...testData.metamask,
...mockStore.metamask,
mmiConfiguration: {
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: null,
isNoteToTraderSupported: true,
},
],
},
},
};
store = configureStore()(customMockStore);
const { getByText } = renderWithProvider(<CustodyConfirmLink />, store);
expect(getByText(mockedCustodianName)).toBeVisible();
});
it('shows text that comes from modal state if defined', () => {
const mockModalStateText = 'test modal state text';
const customMockStore = {
...mockStore,
appState: {
...testData.appState,
modal: {
modalState: {
props: {
link: {
url: 'test-url',
ethereum: {
accounts: [{}],
},
text: mockModalStateText,
action: '',
},
},
},
},
},
};
store = configureStore()(customMockStore);
const { getByText } = renderWithProvider(<CustodyConfirmLink />, store);
expect(getByText(mockModalStateText)).toBeVisible();
});
it('shows action text that comes from modal state if defined', () => {
const mockModalStateActionText = 'test modal state action text';
const customMockStore = {
...mockStore,
appState: {
...testData.appState,
modal: {
modalState: {
props: {
link: {
url: 'test-url',
ethereum: {
accounts: [{}],
},
text: '',
action: mockModalStateActionText,
},
},
},
},
},
};
store = configureStore()(customMockStore);
const { getByText } = renderWithProvider(<CustodyConfirmLink />, store);
expect(getByText(mockModalStateActionText)).toBeVisible();
});
});

View File

@ -0,0 +1,3 @@
import CustodyConfirmLink from './custody-confirm-link-modal';
export default CustodyConfirmLink;

View File

@ -0,0 +1,18 @@
.custody-confirm-link {
&__description {
border-bottom: 1px solid var(--brand-colors-grey-grey200);
}
&__img {
margin: 0 20px;
display: block;
padding: 20px 0;
width: 45px;
text-align: center;
}
&__btn {
width: 300px !important;
margin: 20px auto;
}
}

View File

@ -63,6 +63,17 @@ export function getIsCustodianSupportedChain(state) {
: true;
}
export function getMMIAddressFromModalOrAddress(state) {
return (
state.appState.modal.modalState.props.address ||
state.metamask.selectedAddress
);
}
export function getMMIConfiguration(state) {
return state.metamask.mmiConfiguration;
}
export function getInteractiveReplacementToken(state) {
return state.metamask.interactiveReplacementToken || {};
}