mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 09:57:02 +01:00
[MMI] custody page component (#18688)
This commit is contained in:
parent
c450fae84f
commit
e6455e92ae
30
app/_locales/en/messages.json
generated
30
app/_locales/en/messages.json
generated
@ -333,6 +333,12 @@
|
|||||||
"alerts": {
|
"alerts": {
|
||||||
"message": "Alerts"
|
"message": "Alerts"
|
||||||
},
|
},
|
||||||
|
"allCustodianAccountsConnectedSubtitle": {
|
||||||
|
"message": "You have either already connected all your custodian accounts or don’t have any account to connect to MetaMask Institutional."
|
||||||
|
},
|
||||||
|
"allCustodianAccountsConnectedTitle": {
|
||||||
|
"message": "No accounts available to connect"
|
||||||
|
},
|
||||||
"allOfYour": {
|
"allOfYour": {
|
||||||
"message": "All of your $1",
|
"message": "All of your $1",
|
||||||
"description": "$1 is the symbol or name of the token that the user is approving spending"
|
"description": "$1 is the symbol or name of the token that the user is approving spending"
|
||||||
@ -724,6 +730,12 @@
|
|||||||
"connectAccountOrCreate": {
|
"connectAccountOrCreate": {
|
||||||
"message": "Connect account or create new"
|
"message": "Connect account or create new"
|
||||||
},
|
},
|
||||||
|
"connectCustodialAccountMsg": {
|
||||||
|
"message": "Please choose the custodian you want to connect in order to add or refresh a token."
|
||||||
|
},
|
||||||
|
"connectCustodialAccountTitle": {
|
||||||
|
"message": "Custodial Accounts"
|
||||||
|
},
|
||||||
"connectHardwareWallet": {
|
"connectHardwareWallet": {
|
||||||
"message": "Connect hardware wallet"
|
"message": "Connect hardware wallet"
|
||||||
},
|
},
|
||||||
@ -950,6 +962,9 @@
|
|||||||
"custodianReplaceRefreshTokenTitle": {
|
"custodianReplaceRefreshTokenTitle": {
|
||||||
"message": "Replace custodian token"
|
"message": "Replace custodian token"
|
||||||
},
|
},
|
||||||
|
"custodyApiUrl": {
|
||||||
|
"message": "$1 API URL"
|
||||||
|
},
|
||||||
"custodyDeeplinkDescription": {
|
"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."
|
"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."
|
||||||
},
|
},
|
||||||
@ -1388,6 +1403,9 @@
|
|||||||
"enterANumber": {
|
"enterANumber": {
|
||||||
"message": "Enter a number"
|
"message": "Enter a number"
|
||||||
},
|
},
|
||||||
|
"enterCustodianToken": {
|
||||||
|
"message": "Enter your $1 token or add a new token"
|
||||||
|
},
|
||||||
"enterMaxSpendLimit": {
|
"enterMaxSpendLimit": {
|
||||||
"message": "Enter max spend limit"
|
"message": "Enter max spend limit"
|
||||||
},
|
},
|
||||||
@ -2909,6 +2927,9 @@
|
|||||||
"passwordsDontMatch": {
|
"passwordsDontMatch": {
|
||||||
"message": "Passwords don't match"
|
"message": "Passwords don't match"
|
||||||
},
|
},
|
||||||
|
"pasteJWTToken": {
|
||||||
|
"message": "Paste or drop your token here:"
|
||||||
|
},
|
||||||
"pastePrivateKey": {
|
"pastePrivateKey": {
|
||||||
"message": "Enter your private key string here:",
|
"message": "Enter your private key string here:",
|
||||||
"description": "For importing an account from a private key"
|
"description": "For importing an account from a private key"
|
||||||
@ -3519,18 +3540,27 @@
|
|||||||
"seedPhraseWriteDownHeader": {
|
"seedPhraseWriteDownHeader": {
|
||||||
"message": "Write down your Secret Recovery Phrase"
|
"message": "Write down your Secret Recovery Phrase"
|
||||||
},
|
},
|
||||||
|
"select": {
|
||||||
|
"message": "Select"
|
||||||
|
},
|
||||||
"selectAccounts": {
|
"selectAccounts": {
|
||||||
"message": "Select the account(s) to use on this site"
|
"message": "Select the account(s) to use on this site"
|
||||||
},
|
},
|
||||||
"selectAll": {
|
"selectAll": {
|
||||||
"message": "Select all"
|
"message": "Select all"
|
||||||
},
|
},
|
||||||
|
"selectAllAccounts": {
|
||||||
|
"message": "Select all accounts"
|
||||||
|
},
|
||||||
"selectAnAccount": {
|
"selectAnAccount": {
|
||||||
"message": "Select an account"
|
"message": "Select an account"
|
||||||
},
|
},
|
||||||
"selectAnAccountAlreadyConnected": {
|
"selectAnAccountAlreadyConnected": {
|
||||||
"message": "This account has already been connected to MetaMask"
|
"message": "This account has already been connected to MetaMask"
|
||||||
},
|
},
|
||||||
|
"selectAnAccountHelp": {
|
||||||
|
"message": "Select the custodian accounts to use in MetaMask Institutional."
|
||||||
|
},
|
||||||
"selectHdPath": {
|
"selectHdPath": {
|
||||||
"message": "Select HD path"
|
"message": "Select HD path"
|
||||||
},
|
},
|
||||||
|
@ -17,7 +17,6 @@ exports[`JwtUrlForm shows JWT text area when no jwt token exists 1`] = `
|
|||||||
input text
|
input text
|
||||||
</p>
|
</p>
|
||||||
<textarea
|
<textarea
|
||||||
bordercolor="border-default"
|
|
||||||
class="jwt-url-form__input-jwt"
|
class="jwt-url-form__input-jwt"
|
||||||
data-testid="jwt-input"
|
data-testid="jwt-input"
|
||||||
id="jwt-box"
|
id="jwt-box"
|
||||||
|
@ -4,7 +4,6 @@ import { useI18nContext } from '../../../hooks/useI18nContext';
|
|||||||
import {
|
import {
|
||||||
AlignItems,
|
AlignItems,
|
||||||
DISPLAY,
|
DISPLAY,
|
||||||
BorderColor,
|
|
||||||
BLOCK_SIZES,
|
BLOCK_SIZES,
|
||||||
FLEX_DIRECTION,
|
FLEX_DIRECTION,
|
||||||
} from '../../../helpers/constants/design-system';
|
} from '../../../helpers/constants/design-system';
|
||||||
@ -79,7 +78,6 @@ const JwtUrlForm = (props) => {
|
|||||||
<textarea
|
<textarea
|
||||||
className="jwt-url-form__input-jwt"
|
className="jwt-url-form__input-jwt"
|
||||||
data-testid="jwt-input"
|
data-testid="jwt-input"
|
||||||
borderColor={BorderColor.borderDefault}
|
|
||||||
id="jwt-box"
|
id="jwt-box"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
props.onJwtChange(e.target.value);
|
props.onJwtChange(e.target.value);
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
height: 154px;
|
height: 154px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
border: 1px solid;
|
||||||
background-color: var(--color-background-default);
|
background-color: var(--color-background-default);
|
||||||
color: var(--color-text-default);
|
color: var(--color-text-default);
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
@ -20,10 +20,6 @@ const CONTACT_LIST_ROUTE = '/settings/contact-list';
|
|||||||
const CONTACT_EDIT_ROUTE = '/settings/contact-list/edit-contact';
|
const CONTACT_EDIT_ROUTE = '/settings/contact-list/edit-contact';
|
||||||
const CONTACT_ADD_ROUTE = '/settings/contact-list/add-contact';
|
const CONTACT_ADD_ROUTE = '/settings/contact-list/add-contact';
|
||||||
const CONTACT_VIEW_ROUTE = '/settings/contact-list/view-contact';
|
const CONTACT_VIEW_ROUTE = '/settings/contact-list/view-contact';
|
||||||
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
|
||||||
const CUSTODY_ACCOUNT_ROUTE = '/new-account/custody';
|
|
||||||
const INSTITUTIONAL_FEATURES_DONE_ROUTE = '/institutional-features/done';
|
|
||||||
///: END:ONLY_INCLUDE_IN
|
|
||||||
const REVEAL_SEED_ROUTE = '/seed';
|
const REVEAL_SEED_ROUTE = '/seed';
|
||||||
const RESTORE_VAULT_ROUTE = '/restore-vault';
|
const RESTORE_VAULT_ROUTE = '/restore-vault';
|
||||||
const IMPORT_TOKEN_ROUTE = '/import-token';
|
const IMPORT_TOKEN_ROUTE = '/import-token';
|
||||||
@ -32,6 +28,11 @@ const CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE = '/confirm-add-suggested-token';
|
|||||||
const NEW_ACCOUNT_ROUTE = '/new-account';
|
const NEW_ACCOUNT_ROUTE = '/new-account';
|
||||||
const IMPORT_ACCOUNT_ROUTE = '/new-account/import';
|
const IMPORT_ACCOUNT_ROUTE = '/new-account/import';
|
||||||
const CONNECT_HARDWARE_ROUTE = '/new-account/connect';
|
const CONNECT_HARDWARE_ROUTE = '/new-account/connect';
|
||||||
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
|
const CUSTODY_ACCOUNT_ROUTE = '/new-account/custody';
|
||||||
|
const INSTITUTIONAL_FEATURES_DONE_ROUTE = '/institutional-features/done';
|
||||||
|
const CUSTODY_ACCOUNT_DONE_ROUTE = '/new-account/custody/done';
|
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
const SEND_ROUTE = '/send';
|
const SEND_ROUTE = '/send';
|
||||||
const TOKEN_DETAILS = '/token-details';
|
const TOKEN_DETAILS = '/token-details';
|
||||||
const CONNECT_ROUTE = '/connect';
|
const CONNECT_ROUTE = '/connect';
|
||||||
@ -212,6 +213,7 @@ export {
|
|||||||
CONTACT_ADD_ROUTE,
|
CONTACT_ADD_ROUTE,
|
||||||
CONTACT_VIEW_ROUTE,
|
CONTACT_VIEW_ROUTE,
|
||||||
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
|
CUSTODY_ACCOUNT_DONE_ROUTE,
|
||||||
CUSTODY_ACCOUNT_ROUTE,
|
CUSTODY_ACCOUNT_ROUTE,
|
||||||
INSTITUTIONAL_FEATURES_DONE_ROUTE,
|
INSTITUTIONAL_FEATURES_DONE_ROUTE,
|
||||||
///: END:ONLY_INCLUDE_IN
|
///: END:ONLY_INCLUDE_IN
|
||||||
|
@ -27,7 +27,7 @@ exports[`CustodyAccountList renders accounts 1`] = `
|
|||||||
class="box box--margin-left-2 box--display-flex box--flex-direction-column box--width-full"
|
class="box box--margin-left-2 box--display-flex box--flex-direction-column box--width-full"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
class="box mm-text mm-label mm-label--html-for custody-account-list__item__title mm-text--body-md mm-text--font-weight-bold box--margin-top-2 box--margin-left-2 box--display-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-text-default"
|
class="box mm-text mm-label mm-label--html-for custody-account-list__item__title mm-text--body-md mm-text--font-weight-bold box--margin-top-2 box--margin-left-2 box--display-flex box--flex-direction-row box--align-items-center box--color-text-default"
|
||||||
for="address-0"
|
for="address-0"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@ -37,7 +37,7 @@ exports[`CustodyAccountList renders accounts 1`] = `
|
|||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<label
|
<label
|
||||||
class="box mm-text mm-label mm-label--html-for mm-text--body-md mm-text--font-weight-bold box--margin-top-2 box--margin-right-3 box--display-flex box--flex-direction-row box--align-items-center box--color-text-default"
|
class="box mm-text mm-label mm-label--html-for mm-text--body-md mm-text--font-weight-bold box--margin-top-2 box--margin-right-3 box--margin-left-2 box--display-flex box--flex-direction-row box--align-items-center box--color-text-default"
|
||||||
for="address-0"
|
for="address-0"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@ -103,7 +103,7 @@ exports[`CustodyAccountList renders accounts 1`] = `
|
|||||||
class="box box--margin-left-2 box--display-flex box--flex-direction-column box--width-full"
|
class="box box--margin-left-2 box--display-flex box--flex-direction-column box--width-full"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
class="box mm-text mm-label mm-label--html-for custody-account-list__item__title mm-text--body-md mm-text--font-weight-bold box--margin-top-2 box--margin-left-2 box--display-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-text-default"
|
class="box mm-text mm-label mm-label--html-for custody-account-list__item__title mm-text--body-md mm-text--font-weight-bold box--margin-top-2 box--margin-left-2 box--display-flex box--flex-direction-row box--align-items-center box--color-text-default"
|
||||||
for="address-1"
|
for="address-1"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@ -113,7 +113,7 @@ exports[`CustodyAccountList renders accounts 1`] = `
|
|||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<label
|
<label
|
||||||
class="box mm-text mm-label mm-label--html-for mm-text--body-md mm-text--font-weight-bold box--margin-top-2 box--margin-right-3 box--display-flex box--flex-direction-row box--align-items-center box--color-text-default"
|
class="box mm-text mm-label mm-label--html-for mm-text--body-md mm-text--font-weight-bold box--margin-top-2 box--margin-right-3 box--margin-left-2 box--display-flex box--flex-direction-row box--align-items-center box--color-text-default"
|
||||||
for="address-1"
|
for="address-1"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
@ -1,20 +1,20 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Button from '../../../../components/ui/button';
|
import Button from '../../../components/ui/button';
|
||||||
import CustodyLabels from '../../../../components/institutional/custody-labels';
|
import CustodyLabels from '../../../components/institutional/custody-labels';
|
||||||
import { SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP } from '../../../../../shared/constants/swaps';
|
import { SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP } from '../../../../shared/constants/swaps';
|
||||||
import { CHAIN_IDS } from '../../../../../shared/constants/network';
|
import { CHAIN_IDS } from '../../../../shared/constants/network';
|
||||||
import { shortenAddress } from '../../../../helpers/utils/util';
|
import { shortenAddress } from '../../../helpers/utils/util';
|
||||||
import Tooltip from '../../../../components/ui/tooltip';
|
import Tooltip from '../../../components/ui/tooltip';
|
||||||
import {
|
import {
|
||||||
TextVariant,
|
TextVariant,
|
||||||
JustifyContent,
|
JustifyContent,
|
||||||
BLOCK_SIZES,
|
BLOCK_SIZES,
|
||||||
DISPLAY,
|
DISPLAY,
|
||||||
IconColor,
|
IconColor,
|
||||||
} from '../../../../helpers/constants/design-system';
|
} from '../../../helpers/constants/design-system';
|
||||||
import { useI18nContext } from '../../../../hooks/useI18nContext';
|
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||||
import Box from '../../../../components/ui/box';
|
import Box from '../../../components/ui/box';
|
||||||
import {
|
import {
|
||||||
Text,
|
Text,
|
||||||
Label,
|
Label,
|
||||||
@ -22,8 +22,8 @@ import {
|
|||||||
IconName,
|
IconName,
|
||||||
IconSize,
|
IconSize,
|
||||||
ButtonLink,
|
ButtonLink,
|
||||||
} from '../../../../components/component-library';
|
} from '../../../components/component-library';
|
||||||
import { useCopyToClipboard } from '../../../../hooks/useCopyToClipboard';
|
import { useCopyToClipboard } from '../../../hooks/useCopyToClipboard';
|
||||||
|
|
||||||
const getButtonLinkHref = (account) => {
|
const getButtonLinkHref = (account) => {
|
||||||
const url = SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP[CHAIN_IDS.MAINNET];
|
const url = SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP[CHAIN_IDS.MAINNET];
|
||||||
@ -94,7 +94,6 @@ export default function CustodyAccountList({
|
|||||||
>
|
>
|
||||||
<Label
|
<Label
|
||||||
display={DISPLAY.FLEX}
|
display={DISPLAY.FLEX}
|
||||||
justifyContent={JustifyContent.center}
|
|
||||||
marginTop={2}
|
marginTop={2}
|
||||||
marginLeft={2}
|
marginLeft={2}
|
||||||
htmlFor={`address-${idx}`}
|
htmlFor={`address-${idx}`}
|
||||||
@ -114,6 +113,7 @@ export default function CustodyAccountList({
|
|||||||
display={DISPLAY.FLEX}
|
display={DISPLAY.FLEX}
|
||||||
size={TextVariant.bodySm}
|
size={TextVariant.bodySm}
|
||||||
marginTop={2}
|
marginTop={2}
|
||||||
|
marginLeft={2}
|
||||||
marginRight={3}
|
marginRight={3}
|
||||||
htmlFor={`address-${idx}`}
|
htmlFor={`address-${idx}`}
|
||||||
>
|
>
|
@ -0,0 +1,174 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`CustodyPage renders CustodyPage 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="box box--flex-direction-row"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="box box--sm:padding-7 box--md:padding-2 box--display-flex box--flex-direction-column"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-label="Back"
|
||||||
|
class="box mm-button-icon mm-button-icon--size-sm box--display-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-icon-default box--background-color-transparent box--rounded-lg"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="box mm-icon mm-icon--size-sm box--display-inline-block box--flex-direction-row box--color-inherit"
|
||||||
|
style="mask-image: url('./images/icons/arrow-left.svg');"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<h4
|
||||||
|
class="box mm-text mm-text--body-lg-medium box--margin-top-4 box--margin-bottom-4 box--flex-direction-row box--color-text-default"
|
||||||
|
>
|
||||||
|
Custodial Accounts
|
||||||
|
</h4>
|
||||||
|
<h6
|
||||||
|
class="box mm-text mm-text--body-md box--margin-top-2 box--margin-bottom-5 box--flex-direction-row box--color-text-default"
|
||||||
|
>
|
||||||
|
Please choose the custodian you want to connect in order to add or refresh a token.
|
||||||
|
</h6>
|
||||||
|
<div
|
||||||
|
class="box box--flex-direction-row"
|
||||||
|
>
|
||||||
|
<ul
|
||||||
|
width="full"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="box box--margin-bottom-4 box--padding-3 box--sm:padding-4 box--display-flex box--flex-direction-row box--justify-content-space-between box--align-items-center box--rounded-sm box--border-color-border-default box--border-style-solid box--border-width-1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="box box--display-flex box--flex-direction-row box--align-items-center"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
alt="Saturn Custody"
|
||||||
|
height="32"
|
||||||
|
src="https://saturn-custody-ui.dev.metamask-institutional.io/saturn.svg"
|
||||||
|
width="32"
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
class="box mm-text mm-text--body-md box--margin-left-2 box--flex-direction-row box--color-text-default"
|
||||||
|
>
|
||||||
|
Saturn Custody
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="box mm-text mm-button-base mm-button-base--size-sm 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"
|
||||||
|
data-testid="custody-connect-button"
|
||||||
|
>
|
||||||
|
Select
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`CustodyPage renders CustodyPage 2`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="box box--flex-direction-row"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="box box--sm:padding-7 box--md:padding-2 box--display-flex box--flex-direction-column"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-label="Back"
|
||||||
|
class="box mm-button-icon mm-button-icon--size-sm box--display-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-icon-alternative box--background-color-transparent box--rounded-lg"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="box mm-icon mm-icon--size-sm box--display-inline-block box--flex-direction-row box--color-inherit"
|
||||||
|
style="mask-image: url('./images/icons/arrow-left.svg');"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<h4
|
||||||
|
class="box mm-text mm-text--body-md box--flex-direction-row box--color-text-default"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="box box--display-flex box--flex-direction-row box--align-items-center"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
class="box mm-text mm-text--body-md box--margin-left-2 box--flex-direction-row box--color-text-default"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</h4>
|
||||||
|
<p
|
||||||
|
class="box mm-text mm-text--body-md box--margin-top-4 box--margin-bottom-4 box--flex-direction-row box--color-text-default"
|
||||||
|
>
|
||||||
|
Enter your token or add a new token
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="box box--padding-top-7 box--padding-bottom-7 box--flex-direction-row"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="box jwt-url-form box--margin-bottom-8 box--display-flex box--flex-direction-column box--align-items-flex-start"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="box jwt-url-form__jwt-container box--margin-top-4 box--margin-bottom-6 box--display-flex box--flex-direction-column box--align-items-center box--width-full"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="box box--flex-direction-row"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
class="box mm-text jwt-url-form__instruction mm-text--body-md box--display-block box--flex-direction-row box--color-text-default"
|
||||||
|
>
|
||||||
|
Paste or drop your token here:
|
||||||
|
</p>
|
||||||
|
<textarea
|
||||||
|
class="jwt-url-form__input-jwt"
|
||||||
|
data-testid="jwt-input"
|
||||||
|
id="jwt-box"
|
||||||
|
>
|
||||||
|
token
|
||||||
|
</textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="box box--flex-direction-row box--width-full"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
class="box mm-text jwt-url-form__instruction mm-text--body-md box--display-block box--flex-direction-row box--color-text-default"
|
||||||
|
>
|
||||||
|
API URL
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
class="box box--flex-direction-row"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="jwt-url-form__input"
|
||||||
|
data-testid="jwt-api-url-input"
|
||||||
|
id="api-url-box"
|
||||||
|
value="url"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="box box--padding-4 box--display-flex box--flex-direction-row box--justify-content-center"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="box mm-text mm-button-base mm-button-base--size-md mm-button-primary mm-text--body-md box--margin-right-4 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"
|
||||||
|
type="secondary"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="box mm-text mm-button-base mm-button-base--size-md 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"
|
||||||
|
data-testid="jwt-form-connect-button"
|
||||||
|
>
|
||||||
|
Connect
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`CustodyPage renders CustodyPage 3`] = `<div />`;
|
591
ui/pages/institutional/custody/custody.js
Normal file
591
ui/pages/institutional/custody/custody.js
Normal file
@ -0,0 +1,591 @@
|
|||||||
|
import React, {
|
||||||
|
useState,
|
||||||
|
useMemo,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useContext,
|
||||||
|
} from 'react';
|
||||||
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||||
|
import { mmiActionsFactory } from '../../../store/institutional/institution-background';
|
||||||
|
import { MetaMetricsContext } from '../../../contexts/metametrics';
|
||||||
|
import {
|
||||||
|
ButtonIcon,
|
||||||
|
Button,
|
||||||
|
Text,
|
||||||
|
Label,
|
||||||
|
IconName,
|
||||||
|
IconSize,
|
||||||
|
BUTTON_SIZES,
|
||||||
|
BUTTON_VARIANT,
|
||||||
|
} from '../../../components/component-library';
|
||||||
|
import {
|
||||||
|
AlignItems,
|
||||||
|
DISPLAY,
|
||||||
|
FLEX_DIRECTION,
|
||||||
|
FONT_WEIGHT,
|
||||||
|
Color,
|
||||||
|
JustifyContent,
|
||||||
|
BorderRadius,
|
||||||
|
BorderColor,
|
||||||
|
BLOCK_SIZES,
|
||||||
|
TextColor,
|
||||||
|
TEXT_ALIGN,
|
||||||
|
TextVariant,
|
||||||
|
} from '../../../helpers/constants/design-system';
|
||||||
|
import Box from '../../../components/ui/box';
|
||||||
|
import {
|
||||||
|
CUSTODY_ACCOUNT_DONE_ROUTE,
|
||||||
|
DEFAULT_ROUTE,
|
||||||
|
} from '../../../helpers/constants/routes';
|
||||||
|
import { getCurrentChainId, getProvider } from '../../../selectors';
|
||||||
|
import { getMMIConfiguration } from '../../../selectors/institutional/selectors';
|
||||||
|
import CustodyAccountList from '../connect-custody/account-list';
|
||||||
|
import JwtUrlForm from '../../../components/institutional/jwt-url-form';
|
||||||
|
|
||||||
|
const CustodyPage = () => {
|
||||||
|
const t = useI18nContext();
|
||||||
|
const history = useHistory();
|
||||||
|
const trackEvent = useContext(MetaMetricsContext);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const mmiActions = mmiActionsFactory();
|
||||||
|
const currentChainId = useSelector(getCurrentChainId);
|
||||||
|
const provider = useSelector(getProvider);
|
||||||
|
const { custodians } = useSelector(getMMIConfiguration);
|
||||||
|
|
||||||
|
const [selectedAccounts, setSelectedAccounts] = useState({});
|
||||||
|
const [selectedCustodianName, setSelectedCustodianName] = useState('');
|
||||||
|
const [selectedCustodianImage, setSelectedCustodianImage] = useState(null);
|
||||||
|
const [selectedCustodianDisplayName, setSelectedCustodianDisplayName] =
|
||||||
|
useState('');
|
||||||
|
const [selectedCustodianType, setSelectedCustodianType] = useState('');
|
||||||
|
const [connectError, setConnectError] = useState('');
|
||||||
|
const [currentJwt, setCurrentJwt] = useState('');
|
||||||
|
const [selectError, setSelectError] = useState('');
|
||||||
|
const [jwtList, setJwtList] = useState([]);
|
||||||
|
const [apiUrl, setApiUrl] = useState('');
|
||||||
|
const [addNewTokenClicked, setAddNewTokenClicked] = useState(false);
|
||||||
|
const [chainId, setChainId] = useState(0);
|
||||||
|
const [connectRequest, setConnectRequest] = useState(undefined);
|
||||||
|
const [accounts, setAccounts] = useState();
|
||||||
|
|
||||||
|
const custodianButtons = useMemo(() => {
|
||||||
|
const custodianItems = [];
|
||||||
|
custodians.forEach((custodian) => {
|
||||||
|
if (
|
||||||
|
(!custodian.production &&
|
||||||
|
process.env.METAMASK_ENVIRONMENT === 'production') ||
|
||||||
|
custodian.hidden ||
|
||||||
|
(connectRequest &&
|
||||||
|
Object.keys(connectRequest).length &&
|
||||||
|
custodian.name !== selectedCustodianName)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
custodianItems.push(
|
||||||
|
<Box
|
||||||
|
key={uuidv4()}
|
||||||
|
display={DISPLAY.FLEX}
|
||||||
|
flexDirection={FLEX_DIRECTION.ROW}
|
||||||
|
justifyContent={JustifyContent.spaceBetween}
|
||||||
|
alignItems={AlignItems.center}
|
||||||
|
borderColor={BorderColor.borderDefault}
|
||||||
|
borderRadius={BorderRadius.SM}
|
||||||
|
padding={[3, 4]}
|
||||||
|
marginBottom={4}
|
||||||
|
>
|
||||||
|
<Box display={DISPLAY.FLEX} alignItems={AlignItems.center}>
|
||||||
|
{custodian.iconUrl && (
|
||||||
|
<img
|
||||||
|
width={32}
|
||||||
|
height={32}
|
||||||
|
src={custodian.iconUrl}
|
||||||
|
alt={custodian.displayName}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Text marginLeft={2}>{custodian.displayName}</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
size={BUTTON_SIZES.SM}
|
||||||
|
data-testid="custody-connect-button"
|
||||||
|
onClick={async (_) => {
|
||||||
|
const jwtListValue = await dispatch(
|
||||||
|
mmiActions.getCustodianJWTList(custodian.name),
|
||||||
|
);
|
||||||
|
setSelectedCustodianName(custodian.name);
|
||||||
|
setSelectedCustodianType(custodian.type);
|
||||||
|
setSelectedCustodianImage(custodian.iconUrl);
|
||||||
|
setSelectedCustodianDisplayName(custodian.displayName);
|
||||||
|
setApiUrl(custodian.apiUrl);
|
||||||
|
setCurrentJwt(jwtListValue[0] || '');
|
||||||
|
setJwtList(jwtListValue);
|
||||||
|
trackEvent({
|
||||||
|
category: 'MMI',
|
||||||
|
event: 'Custodian Selected',
|
||||||
|
properties: {
|
||||||
|
custodian: custodian.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('select')}
|
||||||
|
</Button>
|
||||||
|
</Box>,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return custodianItems;
|
||||||
|
}, [connectRequest, custodians, dispatch, selectedCustodianName]);
|
||||||
|
|
||||||
|
const handleConnectError = useCallback(
|
||||||
|
(e) => {
|
||||||
|
let errorMessage;
|
||||||
|
const detailedError = e.message.split(':');
|
||||||
|
|
||||||
|
if (detailedError.length > 1 && !isNaN(parseInt(detailedError[0], 10))) {
|
||||||
|
if (parseInt(detailedError[0], 10) === 401) {
|
||||||
|
// Authentication Error
|
||||||
|
errorMessage =
|
||||||
|
'Authentication error. Please ensure you have entered the correct token';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/Network Error/u.test(e.message)) {
|
||||||
|
errorMessage =
|
||||||
|
'Network error. Please ensure you have entered the correct API URL';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!errorMessage) {
|
||||||
|
errorMessage = e.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
setConnectError(
|
||||||
|
`Something went wrong connecting your custodian account. Error details: ${errorMessage}`,
|
||||||
|
);
|
||||||
|
trackEvent({
|
||||||
|
category: 'MMI',
|
||||||
|
event: 'Connect to custodian error',
|
||||||
|
properties: {
|
||||||
|
custodian: selectedCustodianName,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[selectedCustodianName, trackEvent],
|
||||||
|
);
|
||||||
|
|
||||||
|
const getCustodianAccounts = useCallback(
|
||||||
|
async (token, custody, getNonImportedAccounts) => {
|
||||||
|
return await dispatch(
|
||||||
|
mmiActions.getCustodianAccounts(
|
||||||
|
token,
|
||||||
|
apiUrl,
|
||||||
|
custody || selectedCustodianType,
|
||||||
|
getNonImportedAccounts,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[dispatch, mmiActions, apiUrl, selectedCustodianType],
|
||||||
|
);
|
||||||
|
|
||||||
|
const connect = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
// If you have one JWT already, but no dropdown yet, currentJwt is null!
|
||||||
|
const jwt = currentJwt || jwtList[0];
|
||||||
|
setConnectError('');
|
||||||
|
const accountsValue = await getCustodianAccounts(
|
||||||
|
jwt,
|
||||||
|
apiUrl,
|
||||||
|
selectedCustodianType,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
setAccounts(accountsValue);
|
||||||
|
trackEvent({
|
||||||
|
category: 'MMI',
|
||||||
|
event: 'Connect to custodian',
|
||||||
|
properties: {
|
||||||
|
custodian: selectedCustodianName,
|
||||||
|
apiUrl,
|
||||||
|
rpc: Boolean(connectRequest),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
handleConnectError(e);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
apiUrl,
|
||||||
|
connectRequest,
|
||||||
|
currentJwt,
|
||||||
|
getCustodianAccounts,
|
||||||
|
handleConnectError,
|
||||||
|
jwtList,
|
||||||
|
selectedCustodianName,
|
||||||
|
selectedCustodianType,
|
||||||
|
trackEvent,
|
||||||
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchConnectRequest = async () => {
|
||||||
|
const connectRequestValue = await dispatch(
|
||||||
|
mmiActions.getCustodianConnectRequest(),
|
||||||
|
);
|
||||||
|
setChainId(parseInt(currentChainId, 16));
|
||||||
|
|
||||||
|
// check if it's empty object
|
||||||
|
if (Object.keys(connectRequestValue).length) {
|
||||||
|
setConnectRequest(connectRequestValue);
|
||||||
|
setCurrentJwt(
|
||||||
|
connectRequestValue.token ||
|
||||||
|
(await dispatch(mmiActions.getCustodianToken())),
|
||||||
|
);
|
||||||
|
setSelectedCustodianType(connectRequestValue.custodianType);
|
||||||
|
setSelectedCustodianName(connectRequestValue.custodianName);
|
||||||
|
setApiUrl(connectRequestValue.apiUrl);
|
||||||
|
connect();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// call the function
|
||||||
|
fetchConnectRequest()
|
||||||
|
// make sure to catch any error
|
||||||
|
.catch(console.error);
|
||||||
|
}, [dispatch, connect, currentChainId, mmiActions]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleNetworkChange = async () => {
|
||||||
|
if (!isNaN(chainId)) {
|
||||||
|
const jwt = currentJwt || jwtList[0];
|
||||||
|
|
||||||
|
if (jwt && jwt.length) {
|
||||||
|
setAccounts(
|
||||||
|
await getCustodianAccounts(
|
||||||
|
jwt,
|
||||||
|
apiUrl,
|
||||||
|
selectedCustodianType,
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (parseInt(chainId, 16) !== chainId) {
|
||||||
|
setChainId(parseInt(currentChainId, 16));
|
||||||
|
handleNetworkChange();
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
getCustodianAccounts,
|
||||||
|
apiUrl,
|
||||||
|
currentJwt,
|
||||||
|
jwtList,
|
||||||
|
selectedCustodianType,
|
||||||
|
currentChainId,
|
||||||
|
chainId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const cancelConnectCustodianToken = () => {
|
||||||
|
setSelectedCustodianName('');
|
||||||
|
setSelectedCustodianType('');
|
||||||
|
setSelectedCustodianImage(null);
|
||||||
|
setSelectedCustodianDisplayName('');
|
||||||
|
setApiUrl('');
|
||||||
|
setCurrentJwt('');
|
||||||
|
setConnectError('');
|
||||||
|
setSelectError('');
|
||||||
|
};
|
||||||
|
|
||||||
|
const setSelectAllAccounts = (e) => {
|
||||||
|
const allAccounts = {};
|
||||||
|
|
||||||
|
if (e.currentTarget.checked) {
|
||||||
|
accounts.forEach((account) => {
|
||||||
|
allAccounts[account.address] = {
|
||||||
|
name: account.name,
|
||||||
|
custodianDetails: account.custodianDetails,
|
||||||
|
labels: account.labels,
|
||||||
|
token: currentJwt,
|
||||||
|
apiUrl,
|
||||||
|
chainId: account.chainId,
|
||||||
|
custodyType: selectedCustodianType,
|
||||||
|
custodyName: selectedCustodianName,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
setSelectedAccounts(allAccounts);
|
||||||
|
} else {
|
||||||
|
setSelectedAccounts({});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
{connectError && (
|
||||||
|
<Text textAlign={TEXT_ALIGN.CENTER} marginTop={3} padding={[2, 7, 5]}>
|
||||||
|
{connectError}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{selectError && (
|
||||||
|
<Text textAlign={TEXT_ALIGN.CENTER} marginTop={3} padding={[2, 7, 5]}>
|
||||||
|
{selectError}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!accounts && !selectedCustodianType ? (
|
||||||
|
<Box
|
||||||
|
padding={[0, 7, 2]}
|
||||||
|
display={DISPLAY.FLEX}
|
||||||
|
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||||
|
>
|
||||||
|
<ButtonIcon
|
||||||
|
ariaLabel={t('back')}
|
||||||
|
iconName={IconName.ArrowLeft}
|
||||||
|
size={IconSize.Sm}
|
||||||
|
color={Color.iconDefault}
|
||||||
|
onClick={() => history.push(DEFAULT_ROUTE)}
|
||||||
|
display={DISPLAY.FLEX}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
as="h4"
|
||||||
|
variant={TextVariant.bodyLgMedium}
|
||||||
|
marginTop={4}
|
||||||
|
marginBottom={4}
|
||||||
|
>
|
||||||
|
{t('connectCustodialAccountTitle')}
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
as="h6"
|
||||||
|
color={TextColor.textDefault}
|
||||||
|
marginTop={2}
|
||||||
|
marginBottom={5}
|
||||||
|
>
|
||||||
|
{t('connectCustodialAccountMsg')}
|
||||||
|
</Text>
|
||||||
|
<Box>
|
||||||
|
<ul width={BLOCK_SIZES.FULL}>{custodianButtons}</ul>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{!accounts && selectedCustodianType && (
|
||||||
|
<>
|
||||||
|
<Box
|
||||||
|
padding={[0, 7, 2]}
|
||||||
|
display={DISPLAY.FLEX}
|
||||||
|
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||||
|
>
|
||||||
|
<ButtonIcon
|
||||||
|
ariaLabel={t('back')}
|
||||||
|
iconName={IconName.ArrowLeft}
|
||||||
|
size={IconSize.Sm}
|
||||||
|
color={Color.iconAlternative}
|
||||||
|
onClick={() => cancelConnectCustodianToken()}
|
||||||
|
display={[DISPLAY.FLEX]}
|
||||||
|
/>
|
||||||
|
<Text as="h4">
|
||||||
|
<Box display={DISPLAY.FLEX} alignItems={AlignItems.center}>
|
||||||
|
{selectedCustodianImage && (
|
||||||
|
<img
|
||||||
|
width={32}
|
||||||
|
height={32}
|
||||||
|
src={selectedCustodianImage}
|
||||||
|
alt={selectedCustodianDisplayName}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Text marginLeft={2}>{selectedCustodianDisplayName}</Text>
|
||||||
|
</Box>
|
||||||
|
</Text>
|
||||||
|
<Text marginTop={4} marginBottom={4}>
|
||||||
|
{t('enterCustodianToken', [selectedCustodianDisplayName])}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box paddingTop={7} paddingBottom={7}>
|
||||||
|
<JwtUrlForm
|
||||||
|
jwtList={jwtList}
|
||||||
|
currentJwt={currentJwt}
|
||||||
|
onJwtChange={(jwt) => setCurrentJwt(jwt)}
|
||||||
|
jwtInputText={t('pasteJWTToken')}
|
||||||
|
apiUrl={apiUrl}
|
||||||
|
urlInputText={t('custodyApiUrl', [selectedCustodianDisplayName])}
|
||||||
|
onUrlChange={(url) => setApiUrl(url)}
|
||||||
|
/>
|
||||||
|
<Box
|
||||||
|
display={DISPLAY.FLEX}
|
||||||
|
flexDirection={FLEX_DIRECTION.ROW}
|
||||||
|
justifyContent={JustifyContent.center}
|
||||||
|
padding={[4, 0]}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
type={BUTTON_VARIANT.SECONDARY}
|
||||||
|
marginRight={4}
|
||||||
|
onClick={() => {
|
||||||
|
cancelConnectCustodianToken();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
data-testid="jwt-form-connect-button"
|
||||||
|
onClick={connect}
|
||||||
|
disabled={
|
||||||
|
!selectedCustodianName || (addNewTokenClicked && !currentJwt)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t('connect')}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{accounts && accounts.length > 0 && (
|
||||||
|
<>
|
||||||
|
<Box
|
||||||
|
borderColor={BorderColor.borderDefault}
|
||||||
|
padding={[5, 7, 2]}
|
||||||
|
width={BLOCK_SIZES.FULL}
|
||||||
|
>
|
||||||
|
<Text as="h4">{t('selectAnAccount')}</Text>
|
||||||
|
<Text marginTop={2} marginBottom={5}>
|
||||||
|
{t('selectAnAccountHelp')}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
padding={[5, 7, 0]}
|
||||||
|
display={DISPLAY.FLEX}
|
||||||
|
flexDirection={FLEX_DIRECTION.ROW}
|
||||||
|
justifyContent={JustifyContent.flexStart}
|
||||||
|
alignItems={AlignItems.center}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="selectAllAccounts"
|
||||||
|
name="selectAllAccounts"
|
||||||
|
marginRight={2}
|
||||||
|
marginLeft={2}
|
||||||
|
value={{}}
|
||||||
|
onChange={(e) => setSelectAllAccounts(e)}
|
||||||
|
checked={Object.keys(selectedAccounts).length === accounts.length}
|
||||||
|
/>
|
||||||
|
<Label htmlFor="selectAllAccounts">{t('selectAllAccounts')}</Label>
|
||||||
|
</Box>
|
||||||
|
<CustodyAccountList
|
||||||
|
custody={selectedCustodianName}
|
||||||
|
accounts={accounts}
|
||||||
|
onAccountChange={(account) => {
|
||||||
|
if (selectedAccounts[account.address]) {
|
||||||
|
delete selectedAccounts[account.address];
|
||||||
|
} else {
|
||||||
|
selectedAccounts[account.address] = {
|
||||||
|
name: account.name,
|
||||||
|
custodianDetails: account.custodianDetails,
|
||||||
|
labels: account.labels,
|
||||||
|
token: currentJwt,
|
||||||
|
apiUrl,
|
||||||
|
chainId: account.chainId,
|
||||||
|
custodyType: selectedCustodianType,
|
||||||
|
custodyName: selectedCustodianName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedAccounts(selectedAccounts);
|
||||||
|
}}
|
||||||
|
provider={provider}
|
||||||
|
selectedAccounts={selectedAccounts}
|
||||||
|
onAddAccounts={async () => {
|
||||||
|
try {
|
||||||
|
await dispatch(
|
||||||
|
mmiActions.connectCustodyAddresses(
|
||||||
|
selectedCustodianType,
|
||||||
|
selectedCustodianName,
|
||||||
|
selectedAccounts,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const selectedCustodian = custodians.find(
|
||||||
|
(custodian) => custodian.name === selectedCustodianName,
|
||||||
|
);
|
||||||
|
history.push({
|
||||||
|
pathname: CUSTODY_ACCOUNT_DONE_ROUTE,
|
||||||
|
state: {
|
||||||
|
imgSrc: selectedCustodian.iconUrl,
|
||||||
|
title: t('custodianAccountAddedTitle'),
|
||||||
|
description: t('custodianAccountAddedDesc'),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
trackEvent({
|
||||||
|
category: 'MMI',
|
||||||
|
event: 'Custodial accounts connected',
|
||||||
|
properties: {
|
||||||
|
custodian: selectedCustodianName,
|
||||||
|
numberOfAccounts: Object.keys(selectedAccounts).length,
|
||||||
|
chainId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
setSelectError(e.message);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onCancel={() => {
|
||||||
|
setAccounts(null);
|
||||||
|
setSelectedCustodianName(null);
|
||||||
|
setSelectedCustodianType(null);
|
||||||
|
setSelectedAccounts({});
|
||||||
|
setCurrentJwt('');
|
||||||
|
setApiUrl('');
|
||||||
|
setAddNewTokenClicked(false);
|
||||||
|
|
||||||
|
if (Object.keys(connectRequest).length) {
|
||||||
|
history.push(DEFAULT_ROUTE);
|
||||||
|
}
|
||||||
|
|
||||||
|
trackEvent({
|
||||||
|
category: 'MMI',
|
||||||
|
event: 'Connect to custodian cancel',
|
||||||
|
properties: {
|
||||||
|
custodian: selectedCustodianName,
|
||||||
|
numberOfAccounts: Object.keys(selectedAccounts).length,
|
||||||
|
chainId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{accounts && accounts.length === 0 && (
|
||||||
|
<Box
|
||||||
|
data-testid="custody-accounts-empty"
|
||||||
|
padding={[6, 7, 2]}
|
||||||
|
className="custody-accounts-empty"
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
marginBottom={2}
|
||||||
|
fontWeight={FONT_WEIGHT.BOLD}
|
||||||
|
color={TextColor.textDefault}
|
||||||
|
variant={TextVariant.bodySm}
|
||||||
|
>
|
||||||
|
{t('allCustodianAccountsConnectedTitle')}
|
||||||
|
</Text>
|
||||||
|
<Text variant={TextVariant.bodyXs}>
|
||||||
|
{t('allCustodianAccountsConnectedSubtitle')}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Box padding={[5, 7]} className="custody-accounts-empty__footer">
|
||||||
|
<Button
|
||||||
|
size={BUTTON_SIZES.LG}
|
||||||
|
type={BUTTON_VARIANT.SECONDARY}
|
||||||
|
onClick={() => history.push(DEFAULT_ROUTE)}
|
||||||
|
>
|
||||||
|
{t('close')}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CustodyPage;
|
52
ui/pages/institutional/custody/custody.stories.js
Normal file
52
ui/pages/institutional/custody/custody.stories.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import configureStore from '../../../store/store';
|
||||||
|
import testData from '../../../../.storybook/test-data';
|
||||||
|
import CustodyPage from '.';
|
||||||
|
|
||||||
|
const customData = {
|
||||||
|
...testData,
|
||||||
|
metamask: {
|
||||||
|
...testData.metamask,
|
||||||
|
mmiConfiguration: {
|
||||||
|
portfolio: {
|
||||||
|
enabled: true,
|
||||||
|
url: 'https://portfolio.io',
|
||||||
|
},
|
||||||
|
custodians: [
|
||||||
|
{
|
||||||
|
type: 'Saturn',
|
||||||
|
name: 'saturn',
|
||||||
|
apiUrl: 'https://saturn-custody.dev.metamask-institutional.io',
|
||||||
|
iconUrl:
|
||||||
|
'https://saturn-custody-ui.dev.metamask-institutional.io/saturn.svg',
|
||||||
|
displayName: 'Saturn Custody',
|
||||||
|
production: true,
|
||||||
|
refreshTokenUrl: null,
|
||||||
|
isNoteToTraderSupported: false,
|
||||||
|
version: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const store = configureStore(customData);
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Pages/Institutional/CustodyPage',
|
||||||
|
decorators: [(story) => <Provider store={store}>{story()}</Provider>],
|
||||||
|
component: CustodyPage,
|
||||||
|
argTypes: {
|
||||||
|
onClick: {
|
||||||
|
action: 'onClick',
|
||||||
|
},
|
||||||
|
onChange: {
|
||||||
|
action: 'onChange',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DefaultStory = (args) => <CustodyPage {...args} />;
|
||||||
|
|
||||||
|
DefaultStory.storyName = 'CustodyPage';
|
113
ui/pages/institutional/custody/custody.test.js
Normal file
113
ui/pages/institutional/custody/custody.test.js
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import configureMockStore from 'redux-mock-store';
|
||||||
|
import { fireEvent, waitFor, screen } from '@testing-library/react';
|
||||||
|
import thunk from 'redux-thunk';
|
||||||
|
import { renderWithProvider } from '../../../../test/lib/render-helpers';
|
||||||
|
import CustodyPage from '.';
|
||||||
|
|
||||||
|
const mockedReturnedValue = jest.fn().mockReturnValue({ type: 'TYPE' });
|
||||||
|
const mockedGetCustodianJWTList = jest.fn().mockReturnValue({ type: 'TYPE' });
|
||||||
|
|
||||||
|
const mockedGetCustodianAccounts = jest.fn().mockReturnValue(async () => null);
|
||||||
|
const mockedGetCustodianToken = jest.fn().mockReturnValue('testJWT');
|
||||||
|
|
||||||
|
const mockedGetCustodianConnectRequest = jest.fn().mockReturnValue({
|
||||||
|
type: 'TYPE',
|
||||||
|
custodian: 'saturn',
|
||||||
|
token: 'token',
|
||||||
|
apiUrl: 'url',
|
||||||
|
custodianType: 'JSON-RPC',
|
||||||
|
custodianName: 'Saturn',
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock('../../../store/institutional/institution-background', () => ({
|
||||||
|
mmiActionsFactory: () => ({
|
||||||
|
getCustodianConnectRequest: mockedGetCustodianConnectRequest,
|
||||||
|
getCustodianToken: mockedGetCustodianToken,
|
||||||
|
getCustodianAccounts: mockedGetCustodianAccounts,
|
||||||
|
getCustodianAccountsByAddress: mockedReturnedValue,
|
||||||
|
getCustodianJWTList: mockedGetCustodianJWTList,
|
||||||
|
connectCustodyAddresses: mockedReturnedValue,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('CustodyPage', function () {
|
||||||
|
const mockStore = {
|
||||||
|
metamask: {
|
||||||
|
provider: { chainId: 0x1, type: 'test' },
|
||||||
|
mmiConfiguration: {
|
||||||
|
portfolio: {
|
||||||
|
enabled: true,
|
||||||
|
url: 'https://portfolio.io',
|
||||||
|
},
|
||||||
|
custodians: [
|
||||||
|
{
|
||||||
|
type: 'Saturn',
|
||||||
|
name: 'saturn',
|
||||||
|
apiUrl: 'https://saturn-custody.dev.metamask-institutional.io',
|
||||||
|
iconUrl:
|
||||||
|
'https://saturn-custody-ui.dev.metamask-institutional.io/saturn.svg',
|
||||||
|
displayName: 'Saturn Custody',
|
||||||
|
production: true,
|
||||||
|
refreshTokenUrl: null,
|
||||||
|
isNoteToTraderSupported: false,
|
||||||
|
version: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
preferences: {
|
||||||
|
useNativeCurrencyAsPrimaryCurrency: true,
|
||||||
|
},
|
||||||
|
appState: {
|
||||||
|
isLoading: false,
|
||||||
|
},
|
||||||
|
history: {
|
||||||
|
mostRecentOverviewPage: '/',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const store = configureMockStore([thunk])(mockStore);
|
||||||
|
|
||||||
|
it('renders CustodyPage', async () => {
|
||||||
|
const { container } = renderWithProvider(<CustodyPage />, store);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('opens connect custody without any custody selected', async () => {
|
||||||
|
const { getByTestId } = renderWithProvider(<CustodyPage />, store);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(getByTestId('custody-connect-button')).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls getCustodianJwtList on custody select when connect btn is click', async () => {
|
||||||
|
const { getByTestId } = renderWithProvider(<CustodyPage />, store);
|
||||||
|
|
||||||
|
const custodyBtn = getByTestId('custody-connect-button');
|
||||||
|
await waitFor(() => {
|
||||||
|
fireEvent.click(custodyBtn);
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockedGetCustodianJWTList).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('clicks connect button and shows the jwt form', async () => {
|
||||||
|
const { getByTestId } = renderWithProvider(<CustodyPage />, store);
|
||||||
|
const custodyBtn = getByTestId('custody-connect-button');
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
fireEvent.click(custodyBtn);
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId('jwt-form-connect-button')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
1
ui/pages/institutional/custody/index.js
Normal file
1
ui/pages/institutional/custody/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './custody';
|
14
ui/pages/institutional/custody/index.scss
Normal file
14
ui/pages/institutional/custody/index.scss
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
@import '../../../components/institutional/jwt-dropdown/jwt-dropdown.scss';
|
||||||
|
@import '../../../components/institutional/jwt-url-form/jwt-url-form.scss';
|
||||||
|
|
||||||
|
.custody-accounts-empty {
|
||||||
|
min-height: 300px;
|
||||||
|
|
||||||
|
&__footer {
|
||||||
|
border-top: 1px solid var(--color-border-muted);
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
}
|
@ -13,7 +13,11 @@
|
|||||||
@import 'connected-sites/index';
|
@import 'connected-sites/index';
|
||||||
@import 'create-account/index';
|
@import 'create-account/index';
|
||||||
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
@import "create-account/institutional/connect-custody/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";
|
||||||
@import "institutional/interactive-replacement-token-page/index";
|
@import "institutional/interactive-replacement-token-page/index";
|
||||||
///: END:ONLY_INCLUDE_IN
|
///: END:ONLY_INCLUDE_IN
|
||||||
@import 'error/index';
|
@import 'error/index';
|
||||||
|
@ -71,7 +71,7 @@ export function getMMIAddressFromModalOrAddress(state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getMMIConfiguration(state) {
|
export function getMMIConfiguration(state) {
|
||||||
return state.metamask.mmiConfiguration;
|
return state.metamask.mmiConfiguration || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getInteractiveReplacementToken(state) {
|
export function getInteractiveReplacementToken(state) {
|
||||||
|
Loading…
Reference in New Issue
Block a user