mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-25 03:20:23 +01:00
[MMI] Confirm-add-institutional-feature page (#18321)
* Added confirm add institutional feature page * Finished implementing component * Added all institutional ducks * Fixed tests * Removed ducks and console log * Fixed snapshots * Fixed messages json * Changed method name and using useEffect * Replace useEffect hook with a simple if statement to check if connectRequest exists and add null return statement to avoid warnings * Remove unneeded dependency * Added back useEffect and added a extra check to return null if connectRequest is false * Fixed eslint problem * Fixed all issues commented in the pr
This commit is contained in:
parent
b9c5120332
commit
c52d2131d3
24
app/_locales/en/messages.json
generated
24
app/_locales/en/messages.json
generated
@ -654,6 +654,12 @@
|
||||
"coingecko": {
|
||||
"message": "CoinGecko"
|
||||
},
|
||||
"complianceActivatedDesc": {
|
||||
"message": "You can now use compliance in MetaMask Institutional. Receiving AML/CFT analysis within the confirmation screen on all the addresses you interact with."
|
||||
},
|
||||
"complianceActivatedTitle": {
|
||||
"message": "Your compliance feature is activated"
|
||||
},
|
||||
"complianceBlurb0": {
|
||||
"message": "DeFi raises AML/CFT risk for institutions, given the decentralised pools and pseudonymous counterparties."
|
||||
},
|
||||
@ -780,6 +786,9 @@
|
||||
"connectingToSepolia": {
|
||||
"message": "Connecting to Sepolia test network"
|
||||
},
|
||||
"connectionError": {
|
||||
"message": "Connection error"
|
||||
},
|
||||
"contactUs": {
|
||||
"message": "Contact us"
|
||||
},
|
||||
@ -1707,6 +1716,9 @@
|
||||
"holdToRevealTitle": {
|
||||
"message": "Keep your SRP safe"
|
||||
},
|
||||
"id": {
|
||||
"message": "Id"
|
||||
},
|
||||
"ignoreAll": {
|
||||
"message": "Ignore all"
|
||||
},
|
||||
@ -1812,6 +1824,9 @@
|
||||
"install": {
|
||||
"message": "Install"
|
||||
},
|
||||
"institutionalFeatures": {
|
||||
"message": "Institutional Features"
|
||||
},
|
||||
"insufficientBalance": {
|
||||
"message": "Insufficient balance."
|
||||
},
|
||||
@ -2141,6 +2156,9 @@
|
||||
"mmiAddToken": {
|
||||
"message": "The page at $1 would like to authorise the following custodian token in MetaMask Institutional"
|
||||
},
|
||||
"mmiAuthenticate": {
|
||||
"message": "The page at $1 would like to authorise the following project’s compliance settings in MetaMask Institutional"
|
||||
},
|
||||
"mobileSyncWarning": {
|
||||
"message": "The 'Sync with extension' feature is temporarily disabled. If you want to use your extension wallet on MetaMask mobile, then on your mobile app: go back to the wallet setup options and select the 'Import with Secret Recovery Phrase' option. Use your extension wallet's secret phrase to then import your wallet into mobile."
|
||||
},
|
||||
@ -3065,6 +3083,12 @@
|
||||
"proceedWithTransaction": {
|
||||
"message": "I want to proceed anyway"
|
||||
},
|
||||
"projectIdInvalid": {
|
||||
"message": "Provided Project ID is invalid"
|
||||
},
|
||||
"projectName": {
|
||||
"message": "Project Name"
|
||||
},
|
||||
"proposedApprovalLimit": {
|
||||
"message": "Proposed approval limit"
|
||||
},
|
||||
|
@ -32,6 +32,9 @@ const CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE = '/confirm-add-suggested-token';
|
||||
const NEW_ACCOUNT_ROUTE = '/new-account';
|
||||
const IMPORT_ACCOUNT_ROUTE = '/new-account/import';
|
||||
const CONNECT_HARDWARE_ROUTE = '/new-account/connect';
|
||||
///: BEGIN:ONLY_INCLUDE_IN(mmi)
|
||||
const INSTITUTIONAL_FEATURES_DONE_ROUTE = '/institutional-features/done';
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
const SEND_ROUTE = '/send';
|
||||
const TOKEN_DETAILS = '/token-details';
|
||||
const CONNECT_ROUTE = '/connect';
|
||||
@ -124,6 +127,9 @@ const PATH_NAME_MAP = {
|
||||
[NEW_ACCOUNT_ROUTE]: 'New Account Page',
|
||||
[IMPORT_ACCOUNT_ROUTE]: 'Import Account Page',
|
||||
[CONNECT_HARDWARE_ROUTE]: 'Connect Hardware Wallet Page',
|
||||
///: BEGIN:ONLY_INCLUDE_IN(mmi)
|
||||
[INSTITUTIONAL_FEATURES_DONE_ROUTE]: 'Institutional features done',
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
[SEND_ROUTE]: 'Send Page',
|
||||
[`${TOKEN_DETAILS}/:address`]: 'Token Details Page',
|
||||
[`${CONNECT_ROUTE}/:id`]: 'Connect To Site Confirmation Page',
|
||||
@ -180,6 +186,9 @@ export {
|
||||
NEW_ACCOUNT_ROUTE,
|
||||
IMPORT_ACCOUNT_ROUTE,
|
||||
CONNECT_HARDWARE_ROUTE,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(mmi)
|
||||
INSTITUTIONAL_FEATURES_DONE_ROUTE,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
SEND_ROUTE,
|
||||
TOKEN_DETAILS,
|
||||
CONFIRM_TRANSACTION_ROUTE,
|
||||
|
@ -0,0 +1,67 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Confirm Add Institutional Feature opens confirm institutional sucessfully 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="box page-container box--flex-direction-row"
|
||||
data-testid="confirm-add-institutional-feature"
|
||||
>
|
||||
<div
|
||||
class="box page-container__header box--flex-direction-row"
|
||||
>
|
||||
<p
|
||||
class="box mm-text page-container__title mm-text--body-md box--flex-direction-row box--color-text-default"
|
||||
>
|
||||
Institutional Features
|
||||
</p>
|
||||
<p
|
||||
class="box mm-text page-container__subtitle mm-text--body-md box--flex-direction-row box--color-text-default"
|
||||
>
|
||||
The page at origin would like to authorise the following project’s compliance settings in MetaMask Institutional
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="box page-container__content box--flex-direction-row"
|
||||
>
|
||||
<p
|
||||
class="box mm-text mm-text--body-sm box--margin-top-3 box--margin-right-8 box--margin-left-8 box--flex-direction-row box--color-text-default"
|
||||
>
|
||||
Project Name
|
||||
</p>
|
||||
<p
|
||||
class="box mm-text mm-text--body-lg-medium mm-text--overflow-wrap-break-word box--margin-top-1 box--margin-right-8 box--margin-bottom-1 box--margin-left-8 box--flex-direction-row box--color-text-default"
|
||||
>
|
||||
projectName
|
||||
</p>
|
||||
<p
|
||||
class="box mm-text mm-text--body-xs mm-text--overflow-wrap-break-word box--margin-right-8 box--margin-left-8 box--flex-direction-row box--color-text-muted"
|
||||
>
|
||||
Id
|
||||
:
|
||||
projectId
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="box page-container__footer box--flex-direction-row"
|
||||
>
|
||||
<footer>
|
||||
<button
|
||||
class="button btn--rounded btn-default btn--large"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
class="button btn--rounded btn-primary btn--large"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Confirm
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
@ -0,0 +1,208 @@
|
||||
import React, { useState, useContext, useEffect } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
import Button from '../../../components/ui/button';
|
||||
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 {
|
||||
TextColor,
|
||||
TextVariant,
|
||||
OVERFLOW_WRAP,
|
||||
TEXT_ALIGN,
|
||||
} from '../../../helpers/constants/design-system';
|
||||
import Box from '../../../components/ui/box';
|
||||
import { mmiActionsFactory } from '../../../store/institutional/institution-background';
|
||||
|
||||
export default function ConfirmAddInstitutionalFeature({ history }) {
|
||||
const t = useI18nContext();
|
||||
const dispatch = useDispatch();
|
||||
const mmiActions = mmiActionsFactory();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [connectError, setConnectError] = useState('');
|
||||
const mostRecentOverviewPage = useSelector(getMostRecentOverviewPage);
|
||||
const connectRequests = useSelector(
|
||||
(state) => state.metamask.institutionalFeatures?.connectRequests,
|
||||
);
|
||||
|
||||
const trackEvent = useContext(MetaMetricsContext);
|
||||
const connectRequest = connectRequests[0];
|
||||
|
||||
useEffect(() => {
|
||||
if (!connectRequest) {
|
||||
history.push(mostRecentOverviewPage);
|
||||
}
|
||||
}, [connectRequest, history, mostRecentOverviewPage]);
|
||||
|
||||
if (!connectRequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const serviceLabel = connectRequest.labels.find(
|
||||
(label) => label.key === 'service',
|
||||
);
|
||||
|
||||
const sendEvent = ({ actions, service }) => {
|
||||
trackEvent({
|
||||
category: 'MMI',
|
||||
event: 'Institutional feature connection',
|
||||
properties: {
|
||||
actions,
|
||||
service,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleConnectError = ({ message }) => {
|
||||
let error = message;
|
||||
if (message.startsWith('401')) {
|
||||
error = t('projectIdInvalid');
|
||||
}
|
||||
|
||||
if (!error) {
|
||||
error = t('connectionError');
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
setConnectError(error);
|
||||
sendEvent({ actions: 'Institutional feature RPC error' });
|
||||
};
|
||||
|
||||
const removeConnectInstitutionalFeature = ({ actions, service, push }) => {
|
||||
dispatch(
|
||||
mmiActions.removeConnectInstitutionalFeature({
|
||||
origin: connectRequest.origin,
|
||||
projectId: connectRequest.token.projectId,
|
||||
}),
|
||||
);
|
||||
sendEvent({ actions, service });
|
||||
history.push(push);
|
||||
};
|
||||
|
||||
const confirmAddInstitutionalFeature = async () => {
|
||||
setIsLoading(true);
|
||||
setConnectError('');
|
||||
|
||||
try {
|
||||
await dispatch(
|
||||
mmiActions.setComplianceAuthData({
|
||||
clientId: connectRequest.token.clientId,
|
||||
projectId: connectRequest.token.projectId,
|
||||
}),
|
||||
);
|
||||
removeConnectInstitutionalFeature({
|
||||
actions: 'Institutional feature RPC confirm',
|
||||
service: serviceLabel.value,
|
||||
push: {
|
||||
pathname: INSTITUTIONAL_FEATURES_DONE_ROUTE,
|
||||
state: {
|
||||
imgSrc: 'images/compliance-logo.png',
|
||||
title: t('complianceActivatedTitle'),
|
||||
description: t('complianceActivatedDesc'),
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
handleConnectError(e);
|
||||
}
|
||||
};
|
||||
|
||||
sendEvent({
|
||||
actions: 'Institutional feature RPC request',
|
||||
service: serviceLabel.value,
|
||||
});
|
||||
|
||||
return (
|
||||
<Box
|
||||
className="page-container"
|
||||
data-testid="confirm-add-institutional-feature"
|
||||
>
|
||||
<Box className="page-container__header">
|
||||
<Text className="page-container__title">
|
||||
{t('institutionalFeatures')}
|
||||
</Text>
|
||||
<Text className="page-container__subtitle">
|
||||
{t('mmiAuthenticate', [connectRequest.origin, serviceLabel.value])}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box className="page-container__content">
|
||||
<Text
|
||||
variant={TextVariant.bodySm}
|
||||
marginTop={3}
|
||||
marginRight={8}
|
||||
marginBottom={0}
|
||||
marginLeft={8}
|
||||
>
|
||||
{t('projectName')}
|
||||
</Text>
|
||||
<Text
|
||||
variant={TextVariant.bodyLgMedium}
|
||||
color={TextColor.textDefault}
|
||||
marginTop={1}
|
||||
marginRight={8}
|
||||
marginBottom={1}
|
||||
marginLeft={8}
|
||||
overflowWrap={OVERFLOW_WRAP.BREAK_WORD}
|
||||
>
|
||||
{connectRequest?.token?.projectName}
|
||||
</Text>
|
||||
<Text
|
||||
variant={TextVariant.bodyXs}
|
||||
marginRight={8}
|
||||
marginLeft={8}
|
||||
overflowWrap={OVERFLOW_WRAP.BREAK_WORD}
|
||||
color={TextColor.textMuted}
|
||||
>
|
||||
{t('id')}: {connectRequest?.token?.projectId}
|
||||
</Text>
|
||||
</Box>
|
||||
{connectError && (
|
||||
<Text
|
||||
textAlign={TEXT_ALIGN.CENTER}
|
||||
marginTop={4}
|
||||
data-testid="connect-error-message"
|
||||
>
|
||||
{connectError}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<Box className="page-container__footer">
|
||||
{isLoading ? (
|
||||
<footer>
|
||||
<PulseLoader />
|
||||
</footer>
|
||||
) : (
|
||||
<footer>
|
||||
<Button
|
||||
type="default"
|
||||
large
|
||||
onClick={() => {
|
||||
removeConnectInstitutionalFeature({
|
||||
actions: 'Institutional feature RPC cancel',
|
||||
service: serviceLabel.value,
|
||||
push: mostRecentOverviewPage,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t('cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
large
|
||||
onClick={confirmAddInstitutionalFeature}
|
||||
>
|
||||
{t('confirm')}
|
||||
</Button>
|
||||
</footer>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
ConfirmAddInstitutionalFeature.propTypes = {
|
||||
history: PropTypes.object,
|
||||
};
|
@ -0,0 +1,57 @@
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import configureStore from '../../../store/store';
|
||||
import testData from '../../../../.storybook/test-data';
|
||||
import ConfirmAddInstitutionalFeature from '.';
|
||||
|
||||
const customData = {
|
||||
...testData,
|
||||
metamask: {
|
||||
provider: {
|
||||
type: 'test',
|
||||
},
|
||||
institutionalFeatures: {
|
||||
complianceProjectId: '',
|
||||
connectRequests: [
|
||||
{
|
||||
labels: [
|
||||
{
|
||||
key: 'service',
|
||||
value: 'test',
|
||||
},
|
||||
],
|
||||
origin: 'origin',
|
||||
token: {
|
||||
projectName: 'projectName',
|
||||
projectId: 'projectId',
|
||||
clientId: 'clientId',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
preferences: {
|
||||
useNativeCurrencyAsPrimaryCurrency: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const store = configureStore(customData);
|
||||
|
||||
export default {
|
||||
title: 'Pages/Institutional/ConfirmAddInstitutionalFeature',
|
||||
decorators: [(story) => <Provider store={store}>{story()}</Provider>],
|
||||
component: ConfirmAddInstitutionalFeature,
|
||||
args: {
|
||||
history: {
|
||||
push: () => {
|
||||
/**/
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const DefaultStory = (args) => (
|
||||
<ConfirmAddInstitutionalFeature {...args} />
|
||||
);
|
||||
|
||||
DefaultStory.storyName = 'ConfirmAddInstitutionalFeature';
|
@ -0,0 +1,122 @@
|
||||
import React from 'react';
|
||||
import { fireEvent, screen } from '@testing-library/react';
|
||||
import configureMockStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
import { renderWithProvider } from '../../../../test/lib/render-helpers';
|
||||
import mockState from '../../../../test/data/mock-state.json';
|
||||
import ConfirmAddInstitutionalFeature from '.';
|
||||
|
||||
const mockRemoveConnectInstitutionalFeature = jest
|
||||
.fn()
|
||||
.mockReturnValue({ type: 'TYPE' });
|
||||
|
||||
let mockSetComplianceAuthData = jest.fn().mockReturnValue({ type: 'TYPE' });
|
||||
|
||||
jest.mock('../../../store/institutional/institution-background', () => ({
|
||||
mmiActionsFactory: () => ({
|
||||
setComplianceAuthData: mockSetComplianceAuthData,
|
||||
removeConnectInstitutionalFeature: mockRemoveConnectInstitutionalFeature,
|
||||
}),
|
||||
}));
|
||||
|
||||
const connectRequests = [
|
||||
{
|
||||
labels: [
|
||||
{
|
||||
key: 'service',
|
||||
value: 'test',
|
||||
},
|
||||
],
|
||||
origin: 'origin',
|
||||
token: {
|
||||
projectName: 'projectName',
|
||||
projectId: 'projectId',
|
||||
clientId: 'clientId',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const props = {
|
||||
history: {
|
||||
push: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
const render = ({ newState } = {}) => {
|
||||
const state = {
|
||||
...mockState,
|
||||
metamask: {
|
||||
provider: {
|
||||
type: 'test',
|
||||
},
|
||||
institutionalFeatures: {
|
||||
complianceProjectId: '',
|
||||
connectRequests,
|
||||
},
|
||||
preferences: {
|
||||
useNativeCurrencyAsPrimaryCurrency: true,
|
||||
},
|
||||
...newState,
|
||||
},
|
||||
};
|
||||
const middlewares = [thunk];
|
||||
const mockStore = configureMockStore(middlewares);
|
||||
const store = mockStore(state);
|
||||
|
||||
return renderWithProvider(
|
||||
<ConfirmAddInstitutionalFeature {...props} />,
|
||||
store,
|
||||
);
|
||||
};
|
||||
|
||||
describe('Confirm Add Institutional Feature', function () {
|
||||
it('opens confirm institutional sucessfully', () => {
|
||||
const { container } = render();
|
||||
expect(container).toMatchSnapshot();
|
||||
expect(
|
||||
screen.getByText(`Id: ${connectRequests[0].token.projectId}`),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('runs removeConnectInstitutionalFeature on cancel click', () => {
|
||||
render();
|
||||
fireEvent.click(screen.queryByText('Cancel'));
|
||||
expect(mockRemoveConnectInstitutionalFeature).toHaveBeenCalledTimes(1);
|
||||
expect(mockRemoveConnectInstitutionalFeature).toHaveBeenCalledWith({
|
||||
origin: connectRequests[0].origin,
|
||||
projectId: connectRequests[0].token.projectId,
|
||||
});
|
||||
expect(props.history.push).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('runs setComplianceAuthData on confirm click', () => {
|
||||
render();
|
||||
fireEvent.click(screen.queryByText('Confirm'));
|
||||
expect(mockSetComplianceAuthData).toHaveBeenCalledTimes(1);
|
||||
expect(mockSetComplianceAuthData).toHaveBeenCalledWith({
|
||||
clientId: connectRequests[0].token.clientId,
|
||||
projectId: connectRequests[0].token.projectId,
|
||||
});
|
||||
});
|
||||
|
||||
it('handles error', () => {
|
||||
mockSetComplianceAuthData = jest
|
||||
.fn()
|
||||
.mockReturnValue(new Error('Async error message'));
|
||||
const { queryByTestId } = render();
|
||||
fireEvent.click(screen.queryByText('Confirm'));
|
||||
expect(queryByTestId('connect-error-message')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not render without connectRequest', () => {
|
||||
const newState = {
|
||||
institutionalFeatures: {
|
||||
connectRequests: [],
|
||||
},
|
||||
};
|
||||
const { queryByTestId } = render({ newState });
|
||||
expect(
|
||||
queryByTestId('confirm-add-institutional-feature'),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
@ -0,0 +1,3 @@
|
||||
import ConfirmAddInstitutionalFeature from './confirm-add-institutional-feature';
|
||||
|
||||
export default ConfirmAddInstitutionalFeature;
|
Loading…
Reference in New Issue
Block a user