1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 09:57:02 +01:00

[MMI] Review interactive replacement token flow (#19974)

* Sending showCustodyConfirmLink as a prop and fixing other issues

* Upgraded MMI extension monrepo and trying to fix the issue

* prevents deeplink from closing

* Fixed styles of Custody view and changed the place of it

* Fixed CI issues

* fixing eslint issues

* Update LavaMoat policies

* fixing tests

* Fixed test

* updated snapshots

* Improving IRT flow

* Fixing everything

* Finish fixing all issues

* Fixed institutional entity done page styles

* Fixing boxes

* Fixed issue with checkbox

* Fixed tests and styles

* Removed duplicated lodash

* updated snapshot

* Fixing tests

* Removed unused test

* Fixed snapshot

* Fixed snapshot

---------

Co-authored-by: Antonio Regadas <antonio.regadas@consensys.net>
Co-authored-by: MetaMask Bot <metamaskbot@users.noreply.github.com>
This commit is contained in:
Albert Olivé 2023-07-13 18:27:49 +02:00 committed by GitHub
parent 16481268cc
commit 47fe542273
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 651 additions and 674 deletions

View File

@ -2499,6 +2499,10 @@ export default class MetamaskController extends EventEmitter {
this.institutionalFeaturesController.removeAddTokenConnectRequest.bind(
this.institutionalFeaturesController,
),
showInteractiveReplacementTokenBanner:
appStateController.showInteractiveReplacementTokenBanner.bind(
appStateController,
),
///: END:ONLY_INCLUDE_IN
///: BEGIN:ONLY_INCLUDE_IN(snaps)

View File

@ -44,21 +44,24 @@ exports[`Confirm Remove JWT should render correctly 1`] = `
class="box confirm-action-jwt__accounts-list box--flex-direction-row"
>
<div
class="box box--padding-top-4 box--padding-right-7 box--padding-bottom-7 box--padding-left-7 box--flex-direction-row"
class="mm-box page-container"
>
<div
class="box custody-account-list box--display-flex box--flex-direction-column box--width-full"
class="mm-box page-container__content mm-box--padding-4"
>
<div
class="mm-box custody-account-list mm-box--display-flex mm-box--flex-direction-column mm-box--width-full"
data-testid="custody-account-list"
>
<div
class="box custody-account-list__item box--display-flex box--flex-direction-row"
class="mm-box custody-account-list__item mm-box--display-flex"
>
<div
class="box box--display-flex box--flex-direction-row box--align-items-flex-start"
class="mm-box mm-box--display-flex mm-box--align-items-flex-start"
data-testid="custody-account-list-item-radio-button"
/>
<div
class="box box--margin-left-2 box--display-flex box--flex-direction-column box--width-full"
class="mm-box mm-box--margin-left-2 mm-box--display-flex mm-box--flex-direction-column mm-box--width-full"
>
<label
class="box mm-text mm-label mm-label--html-for custody-account-list__item__title mm-text--body-md mm-text--font-weight-medium box--margin-top-2 box--margin-left-2 box--display-flex box--flex-direction-row box--align-items-center box--color-text-default"
@ -115,7 +118,7 @@ exports[`Confirm Remove JWT should render correctly 1`] = `
</span>
</label>
<div
class="box box--display-flex box--flex-direction-row box--justify-content-space-between"
class="mm-box mm-box--display-flex mm-box--justify-content-space-between"
>
<label
class="box mm-text mm-label mm-label--html-for mm-text--body-md mm-text--font-weight-medium box--display-flex box--flex-direction-row box--align-items-center box--color-text-default"
@ -128,6 +131,7 @@ exports[`Confirm Remove JWT should render correctly 1`] = `
</div>
</div>
</div>
</div>
<div
class="modal-container__footer"
>

View File

@ -7,7 +7,7 @@ exports[`CustodyLabels Component should render correctly 1`] = `
for="address-index"
>
<p
class="box mm-text custody-label mm-text--h9 mm-text--font-weight-normal mm-text--text-transform-uppercase box--margin-top-1 box--margin-right-1 box--margin-bottom-2 box--padding-top-1 box--padding-right-2 box--padding-bottom-1 box--padding-left-2 box--flex-direction-row box--color-text-muted box--background-color-background-alternative box--rounded-sm"
class="box mm-text custody-label mm-text--body-xs mm-text--font-weight-normal mm-text--text-transform-capitalize box--margin-top-1 box--margin-right-1 box--margin-bottom-2 box--padding-top-1 box--padding-right-2 box--padding-bottom-1 box--padding-left-2 box--flex-direction-row box--color-text-default box--background-color-background-alternative box--rounded-sm"
>
value
</p>

View File

@ -7,7 +7,7 @@ import {
TextColor,
FontWeight,
BorderRadius,
TypographyVariant,
TextVariant,
} from '../../../helpers/constants/design-system';
const CustodyLabels = (props) => {
@ -25,7 +25,7 @@ const CustodyLabels = (props) => {
{filteredLabels.map((item) => (
<Text
key={item.key}
textTransform={TextTransform.Uppercase}
textTransform={TextTransform.Capitalize}
className="custody-label"
style={background ? { background } : {}}
marginTop={1}
@ -36,10 +36,10 @@ const CustodyLabels = (props) => {
paddingLeft={2}
paddingRight={2}
backgroundColor={BackgroundColor.backgroundAlternative}
color={TextColor.textMuted}
color={TextColor.textDefault}
fontWeight={FontWeight.Normal}
borderRadius={BorderRadius.SM}
variant={TypographyVariant.H9}
variant={TextVariant.bodyXs}
>
{item.value}
</Text>

View File

@ -2,7 +2,7 @@
z-index: 1;
letter-spacing: 0.5px;
white-space: nowrap;
max-width: 80px;
max-width: 150px;
text-overflow: ellipsis;
overflow: hidden;
display: block;

View File

@ -1,19 +1,26 @@
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,
Box,
Button,
Modal,
ModalContent,
ModalHeader,
ModalOverlay,
Text,
} from '../../component-library';
import {
BlockSize,
BackgroundColor,
DISPLAY,
FLEX_WRAP,
FLEX_DIRECTION,
Display,
FlexWrap,
FlexDirection,
BorderRadius,
FontWeight,
TextAlign,
@ -42,18 +49,36 @@ const InteractiveReplacementTokenModal = () => {
const custodian =
custodians.find((item) => item.name === custodianName) || {};
const renderCustodyInfo = () => {
let img;
const handleSubmit = () => {
global.platform.openTab({
url,
});
if (custodian.iconUrl) {
img = (
trackEvent({
category: 'MMI',
event: 'User clicked refresh token link',
});
};
const handleClose = () => {
dispatch(hideModal());
};
return (
<Modal isOpen onClose={handleClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader onClose={handleClose}>
{t('custodyRefreshTokenModalTitle')}
</ModalHeader>
{custodian.iconUrl ? (
<Box
display={DISPLAY.FLEX}
flexDirection={FLEX_DIRECTION.COLUMN}
display={Display.Flex}
flexDirection={FlexDirection.Column}
alignItems={AlignItems.center}
paddingTop={5}
>
<Box display={DISPLAY.BLOCK} textAlign={TextAlign.Center}>
<Box display={Display.Block} textAlign={TextAlign.Center}>
<img
src={custodian.iconUrl}
width={45}
@ -61,26 +86,20 @@ const InteractiveReplacementTokenModal = () => {
/>
</Box>
</Box>
);
} else {
img = (
<Box display={DISPLAY.BLOCK} textAlign={TextAlign.Center}>
) : (
<Box display={Display.Block} textAlign={TextAlign.Center}>
<Text>{custodian.displayName}</Text>
</Box>
);
}
return (
<>
{img}
<Text
as="h4"
paddingTop={4}
textAlign={TextAlign.Center}
fontWeight={FontWeight.Bold}
)}
<Box
width={BlockSize.Full}
backgroundColor={BackgroundColor.backgroundDefault}
display={Display.Flex}
flexWrap={FlexWrap.Wrap}
flexDirection={FlexDirection.Column}
borderRadius={BorderRadius.SM}
data-testid="interactive-replacement-token-modal"
>
{t('custodyRefreshTokenModalTitle')}
</Text>
<Text
as="p"
paddingTop={4}
@ -100,53 +119,14 @@ const InteractiveReplacementTokenModal = () => {
>
{t('custodyRefreshTokenModalDescription1')}
</Text>
<Text
as="p"
marginTop={4}
paddingTop={4}
paddingBottom={6}
textAlign={TextAlign.Left}
>
<Text as="p" paddingBottom={6} textAlign={TextAlign.Left}>
{t('custodyRefreshTokenModalDescription2')}
</Text>
</>
);
};
const handleSubmit = () => {
global.platform.openTab({
url,
});
trackEvent({
category: 'MMI',
event: 'User clicked refresh token link',
});
};
const handleClose = () => {
dispatch(hideModal());
};
return (
<Modal
onCancel={handleClose}
onClose={handleClose}
onSubmit={handleSubmit}
submitText={custodian.displayName || 'Custodian'}
cancelText={t('cancel')}
>
<Box
width={BLOCK_SIZES.FULL}
backgroundColor={BackgroundColor.backgroundDefault}
display={DISPLAY.FLEX}
flexWrap={FLEX_WRAP.WRAP}
flexDirection={FLEX_DIRECTION.COLUMN}
borderRadius={BorderRadius.SM}
data-testid="interactive-replacement-token-modal"
>
{renderCustodyInfo(custodian)}
<Button onClick={handleSubmit}>
{custodian.displayName || 'Custodian'}
</Button>
</Box>
</ModalContent>
</Modal>
);
};

View File

@ -1,7 +1,5 @@
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 '.';
@ -71,21 +69,4 @@ describe('Interactive Replacement Token Modal', function () {
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(
<InteractiveReplacementTokenModal />,
store,
);
const button = container.getElementsByClassName('btn-primary')[0];
fireEvent.click(button);
await waitFor(() => {
expect(global.platform.openTab.calledOnce).toStrictEqual(true);
});
});
});

View File

@ -6,13 +6,14 @@ import { getInteractiveReplacementToken } from '../../../selectors/institutional
import { getIsUnlocked } from '../../../ducks/metamask/metamask';
import { useI18nContext } from '../../../hooks/useI18nContext';
import { mmiActionsFactory } from '../../../store/institutional/institution-background';
import { showInteractiveReplacementTokenModal } from '../../../store/institutional/institution-actions';
import { sha256 } from '../../../../shared/modules/hash.utils';
import {
Size,
IconColor,
AlignItems,
DISPLAY,
BLOCK_SIZES,
Display,
BlockSize,
JustifyContent,
TextColor,
TextVariant,
@ -24,10 +25,9 @@ import {
IconSize,
ButtonLink,
Text,
Box,
} from '../../component-library';
import Box from '../../ui/box';
const InteractiveReplacementTokenNotification = ({ isVisible }) => {
const t = useI18nContext();
const dispatch = useDispatch();
@ -48,10 +48,7 @@ const InteractiveReplacementTokenNotification = ({ isVisible }) => {
interactiveReplacementToken &&
Boolean(Object.keys(interactiveReplacementToken).length);
if (!/^Custody/u.test(keyring.type)) {
setShowNotification(false);
return;
} else if (!hasInteractiveReplacementToken) {
if (!/^Custody/u.test(keyring.type) || !hasInteractiveReplacementToken) {
setShowNotification(false);
return;
}
@ -98,34 +95,35 @@ const InteractiveReplacementTokenNotification = ({ isVisible }) => {
return showNotification ? (
<Box
width={BLOCK_SIZES.FULL}
display={DISPLAY.FLEX}
width={BlockSize.Full}
display={Display.Flex}
justifyContent={JustifyContent.center}
alignItems={AlignItems.center}
padding={[1, 2]}
backgroundColor={BackgroundColor.backgroundAlternative}
marginBottom={1}
className="interactive-replacement-token-notification"
data-testid="interactive-replacement-token-notification"
>
<Icon
name={IconName.Danger}
color={IconColor.errorDefault}
size={IconSize.Xl}
size={IconSize.Md}
/>
<Text variant={TextVariant.bodyXs} gap={2} color={TextColor.errorDefault}>
<Text variant={TextVariant.bodySm} gap={2} color={TextColor.errorDefault}>
{t('custodySessionExpired')}
</Text>
<Text variant={TextVariant.bodySm}>
<ButtonLink
data-testid="show-modal"
size={Size.auto}
size={Size.inherit}
marginLeft={1}
onClick={() => {
dispatch(mmiActions.showInteractiveReplacementTokenModal());
dispatch(showInteractiveReplacementTokenModal());
}}
>
{t('learnMore')}
{t('learnMoreUpperCase')}
</ButtonLink>
</Text>
</Box>
) : null;
};

View File

@ -10,10 +10,6 @@ import InteractiveReplacementTokenNotification from './interactive-replacement-t
jest.mock('../../../../shared/modules/hash.utils');
const mockedShowInteractiveReplacementTokenModal = jest
.fn()
.mockReturnValue({ type: 'TYPE' });
const mockedGetCustodianToken = jest
.fn()
.mockReturnValue({ type: 'Custody', payload: 'token' });
@ -32,11 +28,15 @@ jest.mock('../../../store/institutional/institution-background', () => ({
mmiActionsFactory: () => ({
getCustodianToken: mockedGetCustodianToken,
getAllCustodianAccountsWithToken: mockedGetAllCustodianAccountsWithToken,
showInteractiveReplacementTokenModal:
mockedShowInteractiveReplacementTokenModal,
}),
}));
jest.mock('../../../store/institutional/institution-actions', () => ({
showInteractiveReplacementTokenModal: jest
.fn()
.mockReturnValue({ type: 'TYPE' }),
}));
describe('Interactive Replacement Token Notification', () => {
const selectedAddress = '0xca8f1F0245530118D0cf14a06b01Daf8f76Cf281';
@ -112,8 +112,6 @@ describe('Interactive Replacement Token Notification', () => {
await act(async () => {
fireEvent.click(screen.getByTestId('show-modal'));
});
expect(mockedShowInteractiveReplacementTokenModal).toHaveBeenCalled();
});
it('should render and call showNotification when component starts', async () => {

View File

@ -1,7 +1,7 @@
import React, { useContext, useState } from 'react';
import React, { useContext, useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { isEqual } from 'lodash';
import { useHistory } from 'react-router-dom';
import isEqual from 'lodash/isEqual';
import PulseLoader from '../../../components/ui/pulse-loader';
import { CUSTODY_ACCOUNT_ROUTE } from '../../../helpers/constants/routes';
import {
@ -47,8 +47,14 @@ const ConfirmAddCustodianToken = () => {
const connectRequest = connectRequests ? connectRequests[0] : undefined;
useEffect(() => {
if (!connectRequest) {
history.push(mostRecentOverviewPage);
setIsLoading(false);
}
}, [connectRequest, history, mostRecentOverviewPage]);
if (!connectRequest) {
return null;
}

View File

@ -3,28 +3,30 @@
exports[`CustodyAccountList renders accounts 1`] = `
<div>
<div
class="box box--padding-top-4 box--padding-right-7 box--padding-bottom-7 box--padding-left-7 box--flex-direction-row"
class="mm-box page-container"
>
<div
class="box custody-account-list box--display-flex box--flex-direction-column box--width-full"
class="mm-box page-container__content mm-box--padding-4"
>
<div
class="mm-box custody-account-list mm-box--display-flex mm-box--flex-direction-column mm-box--width-full"
data-testid="custody-account-list"
>
<div
class="box custody-account-list__item box--display-flex box--flex-direction-row"
class="mm-box custody-account-list__item mm-box--display-flex"
>
<div
class="box box--display-flex box--flex-direction-row box--align-items-flex-start"
class="mm-box mm-box--display-flex mm-box--align-items-flex-start"
data-testid="custody-account-list-item-radio-button"
>
<input
id="address-0"
name="selectedAccount"
type="checkbox"
value="0x1234567890123456789012345678901234567890"
/>
</div>
<div
class="box box--margin-left-2 box--display-flex box--flex-direction-column box--width-full"
class="mm-box mm-box--margin-left-2 mm-box--display-flex mm-box--flex-direction-column mm-box--width-full"
>
<label
class="box mm-text mm-label mm-label--html-for custody-account-list__item__title mm-text--body-md mm-text--font-weight-medium box--margin-top-2 box--margin-left-2 box--display-flex box--flex-direction-row box--align-items-center box--color-text-default"
@ -81,26 +83,25 @@ exports[`CustodyAccountList renders accounts 1`] = `
</span>
</label>
<div
class="box box--display-flex box--flex-direction-row box--justify-content-space-between"
class="mm-box mm-box--display-flex mm-box--justify-content-space-between"
/>
</div>
</div>
<div
class="box custody-account-list__item box--display-flex box--flex-direction-row"
class="mm-box custody-account-list__item mm-box--display-flex"
>
<div
class="box box--display-flex box--flex-direction-row box--align-items-flex-start"
class="mm-box mm-box--display-flex mm-box--align-items-flex-start"
data-testid="custody-account-list-item-radio-button"
>
<input
id="address-1"
name="selectedAccount"
type="checkbox"
value="0x0987654321098765432109876543210987654321"
/>
</div>
<div
class="box box--margin-left-2 box--display-flex box--flex-direction-column box--width-full"
class="mm-box mm-box--margin-left-2 mm-box--display-flex mm-box--flex-direction-column mm-box--width-full"
>
<label
class="box mm-text mm-label mm-label--html-for custody-account-list__item__title mm-text--body-md mm-text--font-weight-medium box--margin-top-2 box--margin-left-2 box--display-flex box--flex-direction-row box--align-items-center box--color-text-default"
@ -157,32 +158,33 @@ exports[`CustodyAccountList renders accounts 1`] = `
</span>
</label>
<div
class="box box--display-flex box--flex-direction-row box--justify-content-space-between"
class="mm-box mm-box--display-flex mm-box--justify-content-space-between"
/>
</div>
</div>
</div>
</div>
<footer
class="mm-box page-container__footer mm-box--padding-4"
>
<div
class="box custody-account-list__buttons box--padding-top-5 box--padding-right-7 box--padding-bottom-7 box--padding-left-7 box--display-flex box--flex-direction-row box--justify-content-space-between box--width-full"
class="mm-box mm-box--display-flex mm-box--gap-4"
>
<button
class="button btn--rounded btn-default btn--large custody-account-list__button"
class="box mm-text mm-button-base mm-button-base--size-lg mm-button-base--block custody-account-list__button mm-button-secondary mm-text--body-md-medium 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-default box--background-color-transparent box--rounded-pill box--border-color-primary-default box--border-style-solid box--border-width-1"
data-testid="custody-account-cancel-button"
role="button"
tabindex="0"
>
[cancel]
</button>
<button
class="button btn--rounded btn-primary btn--large custody-account-list__button"
class="box mm-text mm-button-base mm-button-base--size-lg mm-button-base--disabled mm-button-base--block custody-account-list__button mm-button-primary mm-button-primary--disabled mm-text--body-md-medium 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"
data-testid="custody-account-connect-button"
disabled=""
role="button"
tabindex="0"
>
[connect]
</button>
</div>
</footer>
</div>
</div>
`;

View File

@ -1,6 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import Button from '../../../components/ui/button';
import CustodyLabels from '../../../components/institutional/custody-labels';
import { SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP } from '../../../../shared/constants/swaps';
import { CHAIN_IDS } from '../../../../shared/constants/network';
@ -9,19 +8,24 @@ import Tooltip from '../../../components/ui/tooltip';
import {
TextVariant,
JustifyContent,
BLOCK_SIZES,
DISPLAY,
BlockSize,
Display,
IconColor,
FlexDirection,
AlignItems,
} from '../../../helpers/constants/design-system';
import { useI18nContext } from '../../../hooks/useI18nContext';
import Box from '../../../components/ui/box';
import {
Box,
Button,
Text,
Label,
Icon,
IconName,
IconSize,
ButtonLink,
BUTTON_VARIANT,
BUTTON_SIZES,
} from '../../../components/component-library';
import { useCopyToClipboard } from '../../../hooks/useCopyToClipboard';
@ -34,7 +38,7 @@ export default function CustodyAccountList({
rawList,
accounts,
onAccountChange,
selectedAccounts = {},
selectedAccounts,
onCancel,
onAddAccounts,
custody,
@ -42,27 +46,28 @@ export default function CustodyAccountList({
const t = useI18nContext();
const [copied, handleCopy] = useCopyToClipboard();
const tooltipText = copied ? t('copiedExclamation') : t('copyToClipboard');
const disabled = Object.keys(selectedAccounts).length === 0;
const disabled =
!selectedAccounts || Object.keys(selectedAccounts).length === 0;
return (
<>
<Box paddingTop={4} paddingRight={7} paddingBottom={7} paddingLeft={7}>
<Box className="page-container">
<Box padding={4} className="page-container__content">
<Box
display={DISPLAY.FLEX}
flexDirection={['column']}
width={BLOCK_SIZES.FULL}
display={Display.Flex}
flexDirection={FlexDirection.Column}
width={BlockSize.Full}
className="custody-account-list"
data-testid="custody-account-list"
>
{accounts.map((account, idx) => (
<Box
display={DISPLAY.FLEX}
display={Display.Flex}
className="custody-account-list__item"
key={account.address}
>
<Box
display={DISPLAY.FLEX}
alignItems={['flex-start']}
display={Display.Flex}
alignItems={AlignItems.flexStart}
data-testid="custody-account-list-item-radio-button"
>
{!rawList && (
@ -70,30 +75,27 @@ export default function CustodyAccountList({
type="checkbox"
name="selectedAccount"
id={`address-${idx}`}
value={account.address}
onChange={(e) =>
onChange={() =>
onAccountChange({
name: account.name,
address: e.target.value,
address: account.address,
custodianDetails: account.custodianDetails,
labels: account.labels,
chainId: account.chainId,
})
}
checked={
selectedAccounts && selectedAccounts[account.address]
}
checked={selectedAccounts[account.address] || false}
/>
)}
</Box>
<Box
display={DISPLAY.FLEX}
flexDirection={['column']}
display={Display.Flex}
flexDirection={FlexDirection.Column}
marginLeft={2}
width={BLOCK_SIZES.FULL}
width={BlockSize.Full}
>
<Label
display={DISPLAY.FLEX}
display={Display.Flex}
marginTop={2}
marginLeft={2}
htmlFor={`address-${idx}`}
@ -110,7 +112,7 @@ export default function CustodyAccountList({
</Text>
</Label>
<Label
display={DISPLAY.FLEX}
display={Display.Flex}
size={TextVariant.bodySm}
marginTop={2}
marginLeft={2}
@ -120,7 +122,7 @@ export default function CustodyAccountList({
<Text
as="span"
variant={TextVariant.bodyMd}
display={DISPLAY.FLEX}
display={Display.Flex}
className="custody-account-list__item"
>
<ButtonLink
@ -155,7 +157,7 @@ export default function CustodyAccountList({
</Text>
</Label>
<Box
display={DISPLAY.FLEX}
display={Display.Flex}
justifyContent={JustifyContent.spaceBetween}
>
{account.labels && (
@ -172,20 +174,13 @@ export default function CustodyAccountList({
</Box>
</Box>
{!rawList && (
<Box
display={DISPLAY.FLEX}
width={BLOCK_SIZES.FULL}
justifyContent={JustifyContent.spaceBetween}
paddingTop={5}
paddingRight={7}
paddingBottom={7}
paddingLeft={7}
className="custody-account-list__buttons"
>
<Box as="footer" className="page-container__footer" padding={4}>
<Box display={Display.Flex} gap={4}>
<Button
data-testid="custody-account-cancel-button"
type="default"
large
block
variant={BUTTON_VARIANT.SECONDARY}
size={BUTTON_SIZES.LG}
className="custody-account-list__button"
onClick={onCancel}
>
@ -193,8 +188,9 @@ export default function CustodyAccountList({
</Button>
<Button
data-testid="custody-account-connect-button"
type="primary"
large
block
variant={BUTTON_VARIANT.PRIMARY}
size={BUTTON_SIZES.LG}
className="custody-account-list__button"
disabled={disabled}
onClick={() => onAddAccounts(custody)}
@ -202,8 +198,9 @@ export default function CustodyAccountList({
{t('connect')}
</Button>
</Box>
</Box>
)}
</>
</Box>
);
}

View File

@ -485,13 +485,9 @@ const CustodyPage = () => {
)}
{accounts && accounts.length > 0 && (
<>
<Box
borderColor={BorderColor.borderDefault}
padding={[5, 7, 2]}
width={BlockSize.Full}
>
<Box padding={[5, 7, 2]} width={BlockSize.Full}>
<Text as="h4">{t('selectAnAccount')}</Text>
<Text marginTop={2} marginBottom={5}>
<Text marginTop={2} marginBottom={2}>
{t('selectAnAccountHelp')}
</Text>
</Box>
@ -518,10 +514,13 @@ const CustodyPage = () => {
custody={selectedCustodianName}
accounts={accounts}
onAccountChange={(account) => {
if (selectedAccounts[account.address]) {
delete selectedAccounts[account.address];
setSelectedAccounts((prevSelectedAccounts) => {
const updatedSelectedAccounts = { ...prevSelectedAccounts };
if (updatedSelectedAccounts[account.address]) {
delete updatedSelectedAccounts[account.address];
} else {
selectedAccounts[account.address] = {
updatedSelectedAccounts[account.address] = {
name: account.name,
custodianDetails: account.custodianDetails,
labels: account.labels,
@ -533,7 +532,8 @@ const CustodyPage = () => {
};
}
setSelectedAccounts(selectedAccounts);
return updatedSelectedAccounts;
});
}}
selectedAccounts={selectedAccounts}
onAddAccounts={async () => {
@ -599,10 +599,11 @@ const CustodyPage = () => {
</>
)}
{accounts && accounts.length === 0 && (
<>
<Box
data-testid="custody-accounts-empty"
padding={[6, 7, 2]}
className="custody-accounts-empty"
className="page-container__content"
>
<Text
marginBottom={2}
@ -615,12 +616,8 @@ const CustodyPage = () => {
<Text variant={TextVariant.bodyMd}>
{t('allCustodianAccountsConnectedSubtitle')}
</Text>
<Box
padding={[5, 7]}
width={BlockSize.Full}
className="custody-accounts-empty__footer"
>
</Box>
<Box as="footer" className="page-container__footer" padding={4}>
<Button
block
size={BUTTON_SIZES.LG}
@ -630,7 +627,7 @@ const CustodyPage = () => {
{t('close')}
</Button>
</Box>
</Box>
</>
)}
</Box>
);

View File

@ -1,10 +0,0 @@
.custody-accounts-empty {
min-height: 300px;
&__footer {
border-top: 1px solid var(--color-border-muted);
position: absolute;
bottom: 0;
left: 0;
}
}

View File

@ -1,16 +1,22 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import Button from '../../../components/ui/button';
import { getMostRecentOverviewPage } from '../../../ducks/history/history';
import { useI18nContext } from '../../../hooks/useI18nContext';
import { Text } from '../../../components/component-library';
import {
Text,
Box,
Button,
BUTTON_VARIANT,
} from '../../../components/component-library';
import {
TextColor,
BorderRadius,
TypographyVariant,
Display,
FlexDirection,
AlignItems,
TextAlign,
} from '../../../helpers/constants/design-system';
import Box from '../../../components/ui/box';
export default function InstitutionalEntityDonePage(props) {
const mostRecentOverviewPage = useSelector(getMostRecentOverviewPage);
@ -19,7 +25,7 @@ export default function InstitutionalEntityDonePage(props) {
const { state } = location;
return (
<Box borderRadius={BorderRadius.none}>
<Box className="page-container">
<Box className="page-container__content">
<Box
paddingBottom={6}
@ -28,15 +34,17 @@ export default function InstitutionalEntityDonePage(props) {
className="institutional-entity-done__form"
>
<Box
display={['flex']}
flexDirection={['column']}
alignItems={['center']}
display={Display.Flex}
flexDirection={FlexDirection.Column}
alignItems={AlignItems.center}
>
{state.imgSrc && (
<img
className="institutional-entity-done__img"
src={state.imgSrc}
alt="Entity image"
/>
)}
<Text
as="h4"
marginTop={4}
@ -51,23 +59,24 @@ export default function InstitutionalEntityDonePage(props) {
marginTop={2}
marginBottom={5}
variant={TypographyVariant.headingSm}
textAlign={TextAlign.Center}
>
{state.description}
</Text>
</Box>
</Box>
</Box>
<Box className="page-container__footer">
<footer>
<Box as="footer" className="page-container__footer" padding={4}>
<Box display={Display.Flex} gap={4}>
<Button
type="primary"
large
block
variant={BUTTON_VARIANT.PRIMARY}
data-testid="click-most-recent-overview-page"
onClick={() => history.push(mostRecentOverviewPage)}
>
<Text>{t('close')}</Text>
{t('close')}
</Button>
</footer>
</Box>
</Box>
</Box>
);

View File

@ -3,42 +3,41 @@
exports[`Interactive Replacement Token Page should reject if there are errors 1`] = `
<div>
<div
class="box page-container box--flex-direction-row"
class="mm-box page-container"
data-testid="interactive-replacement-token"
>
<div
class="box page-container__header false box--flex-direction-row"
class="mm-box page-container__header false"
>
<div
class="box page-container__title box--flex-direction-row"
class="mm-box page-container__title"
>
Replace custodian token
</div>
<div
class="box page-container__subtitle box--flex-direction-row"
class="mm-box page-container__subtitle"
>
This is will replace the custodian token for the following address:
</div>
</div>
<div
class="box page-container__content box--flex-direction-row"
class="mm-box page-container__content"
>
<div
class="box interactive-replacement-token-page box--margin-right-7 box--margin-left-7 box--display-flex box--flex-direction-row box--color-text-alternative"
class="mm-box interactive-replacement-token-page mm-box--margin-right-7 mm-box--margin-left-7 mm-box--display-flex mm-box--color-text-alternative"
overflowwrap="break-word"
>
<div
class="box box--display-flex box--flex-direction-column box--width-full"
class="mm-box mm-box--display-flex mm-box--flex-direction-column mm-box--width-full"
data-testid="interactive-replacement-token-page"
/>
</div>
</div>
<div
class="box page-container__footer box--flex-direction-row"
<footer
class="mm-box page-container__footer mm-box--padding-4"
>
<footer>
<div
class="pulse-loader"
>
@ -55,20 +54,19 @@ exports[`Interactive Replacement Token Page should reject if there are errors 1`
</footer>
</div>
</div>
</div>
`;
exports[`Interactive Replacement Token Page should reject if there are errors 2`] = `
<div>
<div
class="box page-container box--flex-direction-row"
class="mm-box page-container"
data-testid="interactive-replacement-token"
>
<div
class="box page-container__header error box--flex-direction-row"
class="mm-box page-container__header error"
>
<div
class="box page-container__title box--flex-direction-row"
class="mm-box page-container__title"
>
Replace custodian token
@ -76,10 +74,10 @@ exports[`Interactive Replacement Token Page should reject if there are errors 2`
</div>
</div>
<div
class="box page-container__content box--flex-direction-row"
class="mm-box page-container__content"
>
<div
class="box interactive-replacement-token-page box--margin-right-7 box--margin-left-7 box--display-flex box--flex-direction-row box--color-text-alternative"
class="mm-box interactive-replacement-token-page mm-box--margin-right-7 mm-box--margin-left-7 mm-box--display-flex mm-box--color-text-alternative"
overflowwrap="break-word"
>
<p
@ -88,32 +86,26 @@ exports[`Interactive Replacement Token Page should reject if there are errors 2`
>
Please go to displayName and click the 'Connect to MMI' button within their user interface to connect your accounts to MMI again.
</p>
<div
class="box box--display-flex box--flex-direction-column box--width-full"
data-testid="interactive-replacement-token-page"
/>
</div>
</div>
<div
class="box page-container__footer box--flex-direction-row"
<footer
class="mm-box page-container__footer mm-box--padding-4"
>
<div
class="mm-box mm-box--display-flex mm-box--gap-4"
>
<footer>
<button
class="button btn--rounded btn-default btn--large page-container__footer-button"
role="button"
tabindex="0"
class="box mm-text mm-button-base mm-button-base--size-lg mm-button-base--block mm-button-secondary mm-text--body-md-medium 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-default box--background-color-transparent box--rounded-pill box--border-color-primary-default box--border-style-solid box--border-width-1"
>
Reject
</button>
<button
class="button btn--rounded btn-primary btn--large page-container__footer-button"
role="button"
tabindex="0"
class="box mm-text mm-button-base mm-button-base--size-lg mm-button-base--block mm-button-primary mm-text--body-md-medium 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"
>
displayName
</button>
</div>
</footer>
</div>
</div>
</div>
`;

View File

@ -4,7 +4,6 @@ import PropTypes from 'prop-types';
import { toChecksumHexAddress } from '../../../../shared/modules/hexstring-utils';
import { getMostRecentOverviewPage } from '../../../ducks/history/history';
import { getMetaMaskAccounts } from '../../../selectors';
import Button from '../../../components/ui/button';
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';
@ -17,7 +16,6 @@ import {
mmiActionsFactory,
showInteractiveReplacementTokenBanner,
} from '../../../store/institutional/institution-background';
import Box from '../../../components/ui/box';
import {
Text,
Label,
@ -25,14 +23,18 @@ import {
ButtonLink,
IconName,
IconSize,
Box,
Button,
BUTTON_VARIANT,
BUTTON_SIZES,
} from '../../../components/component-library';
import {
OVERFLOW_WRAP,
OverflowWrap,
TextColor,
JustifyContent,
BLOCK_SIZES,
DISPLAY,
FLEX_DIRECTION,
BlockSize,
Display,
FlexDirection,
IconColor,
} from '../../../helpers/constants/design-system';
import { useCopyToClipboard } from '../../../hooks/useCopyToClipboard';
@ -46,7 +48,9 @@ export default function InteractiveReplacementTokenPage({ history }) {
const dispatch = useDispatch();
const isMountedRef = useRef(false);
const mmiActions = mmiActionsFactory();
const { address } = useSelector((state) => state.metamask.modal.props);
const address = useSelector(
(state) => state.appState.modal.modalState.props?.address,
);
const {
selectedAddress,
custodyAccountDetails,
@ -83,9 +87,12 @@ export default function InteractiveReplacementTokenPage({ history }) {
}, []);
useEffect(() => {
let isMounted = true;
const getTokenAccounts = async () => {
if (!connectRequest) {
history.push(mostRecentOverviewPage);
setIsLoading(false);
return;
}
@ -111,23 +118,37 @@ export default function InteractiveReplacementTokenPage({ history }) {
metaMaskAccounts[account.address.toLowerCase()]?.balance || 0,
}));
if (isMountedRef.current) {
if (isMounted) {
setTokenAccounts(mappedAccounts);
setIsLoading(false);
}
} catch (e) {
setError(true);
setIsLoading(false);
} finally {
if (isMounted) {
setIsLoading(false);
}
}
};
getTokenAccounts();
return () => {
isMounted = false;
};
// We just want to get the accounts in the render of the component
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
if (!connectRequest) {
history.push(mostRecentOverviewPage);
setIsLoading(false);
}
}, [connectRequest, history, mostRecentOverviewPage]);
if (!connectRequest) {
return null;
}
@ -142,8 +163,8 @@ export default function InteractiveReplacementTokenPage({ history }) {
};
const handleReject = () => {
setIsLoading(true);
onRemoveAddTokenConnectRequest(connectRequest);
history.push(mostRecentOverviewPage);
};
const handleApprove = async () => {
@ -208,10 +229,10 @@ export default function InteractiveReplacementTokenPage({ history }) {
</Box>
<Box className="page-container__content">
<Box
display={DISPLAY.FLEX}
display={Display.Flex}
marginRight={7}
marginLeft={7}
overflowWrap={OVERFLOW_WRAP.BREAK_WORD}
overflowwrap={OverflowWrap.BreakWord}
color={TextColor.textAlternative}
className="interactive-replacement-token-page"
>
@ -221,24 +242,24 @@ export default function InteractiveReplacementTokenPage({ history }) {
custodian.displayName || 'Custodian',
])}
</Text>
) : null}
) : (
<Box
display={DISPLAY.FLEX}
flexDirection={FLEX_DIRECTION.COLUMN}
width={BLOCK_SIZES.FULL}
display={Display.Flex}
flexDirection={FlexDirection.Column}
width={BlockSize.Full}
data-testid="interactive-replacement-token-page"
>
{tokenAccounts.map((account, idx) => {
return (
<Box
display={DISPLAY.FLEX}
display={Display.Flex}
className="interactive-replacement-token-page__item"
key={account.address}
>
<Box
display={DISPLAY.FLEX}
flexDirection={FLEX_DIRECTION.COLUMN}
width={BLOCK_SIZES.FULL}
display={Display.Flex}
flexDirection={FlexDirection.Column}
width={BlockSize.Full}
>
<Label
marginTop={3}
@ -256,7 +277,7 @@ export default function InteractiveReplacementTokenPage({ history }) {
>
<Text
as="span"
display={DISPLAY.FLEX}
display={Display.Flex}
className="interactive-replacement-token-page__item__address"
>
<ButtonLink
@ -286,7 +307,7 @@ export default function InteractiveReplacementTokenPage({ history }) {
>
<Icon
name={IconName.Copy}
size={IconSize.Xs}
size={IconSize.Sm}
color={IconColor.iconMuted}
/>
</button>
@ -294,7 +315,7 @@ export default function InteractiveReplacementTokenPage({ history }) {
</Text>
</Label>
<Box
display={DISPLAY.FLEX}
display={Display.Flex}
justifyContent={JustifyContent.spaceBetween}
>
{account.labels && (
@ -310,34 +331,33 @@ export default function InteractiveReplacementTokenPage({ history }) {
);
})}
</Box>
)}
</Box>
</Box>
<Box className="page-container__footer">
<Box as="footer" className="page-container__footer" padding={4}>
{isLoading ? (
<footer>
<PulseLoader />
</footer>
) : (
<footer>
<Box display={Display.Flex} gap={4}>
<Button
type="default"
large
className="page-container__footer-button"
block
variant={BUTTON_VARIANT.SECONDARY}
size={BUTTON_SIZES.LG}
onClick={handleReject}
>
{t('reject')}
</Button>
<Button
type="primary"
large
className="page-container__footer-button"
block
variant={BUTTON_VARIANT.PRIMARY}
size={BUTTON_SIZES.LG}
onClick={handleApprove}
>
{error
? custodian.displayName || 'Custodian'
: t('approveButtonText')}
</Button>
</footer>
</Box>
)}
</Box>
</Box>

View File

@ -166,8 +166,6 @@ describe('Interactive Replacement Token Page', function () {
});
it('should call onRemoveAddTokenConnectRequest and navigate to mostRecentOverviewPage when handleReject is called', () => {
const mostRecentOverviewPage = '/mostRecentOverviewPage';
const { getByText } = render();
fireEvent.click(getByText('Reject'));
@ -178,8 +176,6 @@ describe('Interactive Replacement Token Page', function () {
apiUrl: connectRequests[0].apiUrl,
token: connectRequests[0].token,
});
expect(props.history.push).toHaveBeenCalled();
expect(props.history.push).toHaveBeenCalledWith(mostRecentOverviewPage);
});
it('should call onRemoveAddTokenConnectRequest, setCustodianNewRefreshToken, and dispatch showInteractiveReplacementTokenBanner when handleApprove is called', async () => {

View File

@ -15,7 +15,6 @@
@import 'connected-sites/index';
@import 'create-account/connect-hardware/index';
@import "institutional/connect-custody/index";
@import "institutional/custody/index";
@import "institutional/institutional-entity-done-page/index";
@import "institutional/compliance-feature-page/index";
@import "institutional/confirm-add-custodian-token/index";

View File

@ -20,12 +20,14 @@ export function showInteractiveReplacementTokenBanner({
}: {
url: string;
oldRefreshToken: string;
}): ThunkAction<void, MetaMaskReduxState, unknown, AnyAction> {
}) {
return async (dispatch: MetaMaskReduxDispatch) => {
try {
await submitRequestToBackground('showInteractiveReplacementTokenBanner', [
{
url,
oldRefreshToken,
},
]);
} catch (err: any) {
if (err) {
@ -237,19 +239,21 @@ export function mmiActionsFactory() {
custodyType,
token,
]),
setCustodianNewRefreshToken: (
address: string,
oldAuthDetails: string,
oldApiUrl: string,
newAuthDetails: string,
newApiUrl: string,
) =>
createAsyncAction('setCustodianNewRefreshToken', [
setCustodianNewRefreshToken: ({
address,
oldAuthDetails,
oldApiUrl,
newAuthDetails,
newApiUrl,
}: {
address: string;
oldAuthDetails: string;
oldApiUrl: string;
newAuthDetails: string;
newApiUrl: string;
}) =>
createAsyncAction('setCustodianNewRefreshToken', [
{ address, oldAuthDetails, oldApiUrl, newAuthDetails, newApiUrl },
]),
};
}