mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-22 17:33:23 +01:00
UX: Multichain: Move Add Account and Import Account into Account Menu Popover (#19346)
* UX: Multichain: Move Add Account and Import Account into Account Menu Popover * Create a new CreateAccount component for the Account Menu * Add actions for import form * Use separate actions for cancel vs. submit * Fix jest tests * Remove commented route navigation * Accommodate for failing import * Fix tests * Remove routes for new account and import * Remove old create account page * Move import-account files to multichain directory * Fix paths on the import files * Remove deprecated component library variables * Fix error property of add form * Fix user-actions-benchmark
This commit is contained in:
parent
abd2a5559e
commit
28137798b6
@ -45,7 +45,7 @@ describe('Add account', function () {
|
||||
'[data-testid="multichain-account-menu-add-account"]',
|
||||
);
|
||||
|
||||
await driver.fill('.new-account-create-form input', '2nd account');
|
||||
await driver.fill('[placeholder="Account 2"]', '2nd account');
|
||||
await driver.clickElement({ text: 'Create', tag: 'button' });
|
||||
const accountName = await driver.waitForSelector({
|
||||
css: '[data-testid="account-menu-icon"]',
|
||||
@ -86,7 +86,7 @@ describe('Add account', function () {
|
||||
await driver.clickElement(
|
||||
'[data-testid="multichain-account-menu-add-account"]',
|
||||
);
|
||||
await driver.fill('.new-account-create-form input', '2nd account');
|
||||
await driver.fill('[placeholder="Account 2"]', '2nd account');
|
||||
await driver.clickElement({ text: 'Create', tag: 'button' });
|
||||
await waitForAccountRendered(driver);
|
||||
|
||||
@ -190,8 +190,7 @@ describe('Add account', function () {
|
||||
await driver.clickElement(
|
||||
'[data-testid="multichain-account-menu-add-account"]',
|
||||
);
|
||||
|
||||
await driver.fill('.new-account-create-form input', '2nd account');
|
||||
await driver.fill('[placeholder="Account 2"]', '2nd account');
|
||||
await driver.clickElement({ text: 'Create', tag: 'button' });
|
||||
|
||||
// Wait for 2nd account to be created
|
||||
@ -220,7 +219,10 @@ describe('Add account', function () {
|
||||
await driver.clickElement('.menu__background');
|
||||
await driver.clickElement({ text: 'Import account', tag: 'button' });
|
||||
await driver.fill('#private-key-box', testPrivateKey);
|
||||
await driver.clickElement({ text: 'Import', tag: 'button' });
|
||||
|
||||
await driver.clickElement(
|
||||
'[data-testid="import-account-confirm-button"]',
|
||||
);
|
||||
|
||||
// Wait for 3rd account to be created
|
||||
await waitForAccountRendered(driver);
|
||||
|
@ -86,7 +86,7 @@ describe('MetaMask Import UI', function () {
|
||||
await driver.clickElement({ text: 'Add account', tag: 'button' });
|
||||
|
||||
// set account name
|
||||
await driver.fill('.new-account-create-form input', '2nd account');
|
||||
await driver.fill('[placeholder="Account 2"]', '2nd account');
|
||||
await driver.delay(regularDelayMs);
|
||||
await driver.clickElement({ text: 'Create', tag: 'button' });
|
||||
|
||||
@ -203,7 +203,9 @@ describe('MetaMask Import UI', function () {
|
||||
// enter private key
|
||||
await driver.findClickableElement('#private-key-box');
|
||||
await driver.fill('#private-key-box', testPrivateKey1);
|
||||
await driver.clickElement({ text: 'Import', tag: 'button' });
|
||||
await driver.clickElement(
|
||||
'[data-testid="import-account-confirm-button"]',
|
||||
);
|
||||
|
||||
// should show the correct account name
|
||||
const importedAccountName = await driver.findElement(
|
||||
@ -230,8 +232,9 @@ describe('MetaMask Import UI', function () {
|
||||
// enter private key
|
||||
await driver.findClickableElement('#private-key-box');
|
||||
await driver.fill('#private-key-box', testPrivateKey2);
|
||||
await driver.findClickableElement({ text: 'Import', tag: 'button' });
|
||||
await driver.clickElement({ text: 'Import', tag: 'button' });
|
||||
await driver.clickElement(
|
||||
'[data-testid="import-account-confirm-button"]',
|
||||
);
|
||||
|
||||
// should see new account in account menu
|
||||
const importedAccount2Name = await driver.findElement(
|
||||
@ -303,8 +306,9 @@ describe('MetaMask Import UI', function () {
|
||||
fileInput.sendKeys(importJsonFile);
|
||||
|
||||
await driver.fill('#json-password-box', 'foobarbazqux');
|
||||
|
||||
await driver.clickElement({ text: 'Import', tag: 'button' });
|
||||
await driver.clickElement(
|
||||
'[data-testid="import-account-confirm-button"]',
|
||||
);
|
||||
|
||||
// should show the correct account name
|
||||
const importedAccountName = await driver.findElement(
|
||||
@ -358,8 +362,9 @@ describe('MetaMask Import UI', function () {
|
||||
// enter private key
|
||||
await driver.findClickableElement('#private-key-box');
|
||||
await driver.fill('#private-key-box', testPrivateKey);
|
||||
await driver.findClickableElement({ text: 'Import', tag: 'button' });
|
||||
await driver.clickElement({ text: 'Import', tag: 'button' });
|
||||
await driver.clickElement(
|
||||
'[data-testid="import-account-confirm-button"]',
|
||||
);
|
||||
|
||||
// error should occur
|
||||
await driver.waitForSelector({
|
||||
|
@ -38,7 +38,7 @@ async function loadNewAccount() {
|
||||
await driver.clickElement(
|
||||
'[data-testid="multichain-account-menu-add-account"]',
|
||||
);
|
||||
await driver.fill('.new-account-create-form input', '2nd account');
|
||||
await driver.fill('[placeholder="Account 2"]', '2nd account');
|
||||
await driver.clickElement({ text: 'Create', tag: 'button' });
|
||||
await driver.waitForSelector({
|
||||
css: '.currency-display-component__text',
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
TextFieldSearch,
|
||||
Text,
|
||||
} from '../../component-library';
|
||||
import { AccountListItem } from '..';
|
||||
import { AccountListItem, CreateAccount, ImportAccount } from '..';
|
||||
import {
|
||||
BLOCK_SIZES,
|
||||
Size,
|
||||
@ -38,8 +38,6 @@ import {
|
||||
MetaMetricsEventName,
|
||||
} from '../../../../shared/constants/metametrics';
|
||||
import {
|
||||
IMPORT_ACCOUNT_ROUTE,
|
||||
NEW_ACCOUNT_ROUTE,
|
||||
CONNECT_HARDWARE_ROUTE,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||
CUSTODY_ACCOUNT_ROUTE,
|
||||
@ -66,6 +64,7 @@ export const AccountListMenu = ({ onClose }) => {
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [actionMode, setActionMode] = useState('');
|
||||
|
||||
let searchResults = accounts;
|
||||
if (searchQuery) {
|
||||
@ -88,209 +87,244 @@ export const AccountListMenu = ({ onClose }) => {
|
||||
}
|
||||
}, [inputRef]);
|
||||
|
||||
let title = t('selectAnAccount');
|
||||
if (actionMode === 'add') {
|
||||
title = t('addAccount');
|
||||
} else if (actionMode === 'import') {
|
||||
title = t('importAccount');
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover
|
||||
title={t('selectAnAccount')}
|
||||
title={title}
|
||||
ref={inputRef}
|
||||
centerTitle
|
||||
onClose={onClose}
|
||||
onBack={actionMode === '' ? null : () => setActionMode('')}
|
||||
>
|
||||
<Box className="multichain-account-menu">
|
||||
{/* Search box */}
|
||||
{accounts.length > 1 ? (
|
||||
<Box
|
||||
paddingLeft={4}
|
||||
paddingRight={4}
|
||||
paddingBottom={4}
|
||||
paddingTop={0}
|
||||
>
|
||||
<TextFieldSearch
|
||||
size={Size.SM}
|
||||
width={BLOCK_SIZES.FULL}
|
||||
placeholder={t('searchAccounts')}
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
clearButtonOnClick={() => setSearchQuery('')}
|
||||
clearButtonProps={{
|
||||
size: Size.SM,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
) : null}
|
||||
{/* Account list block */}
|
||||
<Box className="multichain-account-menu__list">
|
||||
{searchResults.length === 0 && searchQuery !== '' ? (
|
||||
<Text
|
||||
{actionMode === 'add' ? (
|
||||
<Box paddingLeft={4} paddingRight={4} paddingBottom={4} paddingTop={0}>
|
||||
<CreateAccount
|
||||
onActionComplete={(confirmed) => {
|
||||
if (confirmed) {
|
||||
dispatch(toggleAccountMenu());
|
||||
} else {
|
||||
setActionMode('');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
) : null}
|
||||
{actionMode === 'import' ? (
|
||||
<Box paddingLeft={4} paddingRight={4} paddingBottom={4} paddingTop={0}>
|
||||
<ImportAccount
|
||||
onActionComplete={(confirmed) => {
|
||||
if (confirmed) {
|
||||
dispatch(toggleAccountMenu());
|
||||
} else {
|
||||
setActionMode('');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
) : null}
|
||||
{actionMode === '' ? (
|
||||
<Box className="multichain-account-menu">
|
||||
{/* Search box */}
|
||||
{accounts.length > 1 ? (
|
||||
<Box
|
||||
paddingLeft={4}
|
||||
paddingRight={4}
|
||||
color={TextColor.textMuted}
|
||||
data-testid="multichain-account-menu-no-results"
|
||||
paddingBottom={4}
|
||||
paddingTop={0}
|
||||
>
|
||||
{t('noAccountsFound')}
|
||||
</Text>
|
||||
<TextFieldSearch
|
||||
size={Size.SM}
|
||||
width={BLOCK_SIZES.FULL}
|
||||
placeholder={t('searchAccounts')}
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
clearButtonOnClick={() => setSearchQuery('')}
|
||||
clearButtonProps={{
|
||||
size: Size.SM,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
) : null}
|
||||
{searchResults.map((account) => {
|
||||
const connectedSite = connectedSites[account.address]?.find(
|
||||
({ origin }) => origin === currentTabOrigin,
|
||||
);
|
||||
{/* Account list block */}
|
||||
<Box className="multichain-account-menu__list">
|
||||
{searchResults.length === 0 && searchQuery !== '' ? (
|
||||
<Text
|
||||
paddingLeft={4}
|
||||
paddingRight={4}
|
||||
color={TextColor.textMuted}
|
||||
data-testid="multichain-account-menu-no-results"
|
||||
>
|
||||
{t('noAccountsFound')}
|
||||
</Text>
|
||||
) : null}
|
||||
{searchResults.map((account) => {
|
||||
const connectedSite = connectedSites[account.address]?.find(
|
||||
({ origin }) => origin === currentTabOrigin,
|
||||
);
|
||||
|
||||
return (
|
||||
<AccountListItem
|
||||
return (
|
||||
<AccountListItem
|
||||
onClick={() => {
|
||||
dispatch(toggleAccountMenu());
|
||||
trackEvent({
|
||||
category: MetaMetricsEventCategory.Navigation,
|
||||
event: MetaMetricsEventName.NavAccountSwitched,
|
||||
properties: {
|
||||
location: 'Main Menu',
|
||||
},
|
||||
});
|
||||
dispatch(setSelectedAccount(account.address));
|
||||
}}
|
||||
identity={account}
|
||||
key={account.address}
|
||||
selected={selectedAccount.address === account.address}
|
||||
closeMenu={onClose}
|
||||
connectedAvatar={connectedSite?.iconUrl}
|
||||
connectedAvatarName={connectedSite?.name}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
{/* Add / Import / Hardware */}
|
||||
<Box padding={4}>
|
||||
<Box marginBottom={4}>
|
||||
<ButtonLink
|
||||
size={Size.SM}
|
||||
startIconName={IconName.Add}
|
||||
onClick={() => {
|
||||
trackEvent({
|
||||
category: MetaMetricsEventCategory.Navigation,
|
||||
event: MetaMetricsEventName.AccountAddSelected,
|
||||
properties: {
|
||||
account_type: MetaMetricsEventAccountType.Default,
|
||||
location: 'Main Menu',
|
||||
},
|
||||
});
|
||||
setActionMode('add');
|
||||
}}
|
||||
data-testid="multichain-account-menu-add-account"
|
||||
>
|
||||
{t('addAccount')}
|
||||
</ButtonLink>
|
||||
</Box>
|
||||
<Box marginBottom={4}>
|
||||
<ButtonLink
|
||||
size={Size.SM}
|
||||
startIconName={IconName.Import}
|
||||
onClick={() => {
|
||||
trackEvent({
|
||||
category: MetaMetricsEventCategory.Navigation,
|
||||
event: MetaMetricsEventName.AccountAddSelected,
|
||||
properties: {
|
||||
account_type: MetaMetricsEventAccountType.Imported,
|
||||
location: 'Main Menu',
|
||||
},
|
||||
});
|
||||
setActionMode('import');
|
||||
}}
|
||||
>
|
||||
{t('importAccount')}
|
||||
</ButtonLink>
|
||||
</Box>
|
||||
<Box>
|
||||
<ButtonLink
|
||||
size={Size.SM}
|
||||
startIconName={IconName.Hardware}
|
||||
onClick={() => {
|
||||
dispatch(toggleAccountMenu());
|
||||
trackEvent({
|
||||
category: MetaMetricsEventCategory.Navigation,
|
||||
event: MetaMetricsEventName.NavAccountSwitched,
|
||||
event: MetaMetricsEventName.AccountAddSelected,
|
||||
properties: {
|
||||
account_type: MetaMetricsEventAccountType.Hardware,
|
||||
location: 'Main Menu',
|
||||
},
|
||||
});
|
||||
dispatch(setSelectedAccount(account.address));
|
||||
if (getEnvironmentType() === ENVIRONMENT_TYPE_POPUP) {
|
||||
global.platform.openExtensionInBrowser(
|
||||
CONNECT_HARDWARE_ROUTE,
|
||||
);
|
||||
} else {
|
||||
history.push(CONNECT_HARDWARE_ROUTE);
|
||||
}
|
||||
}}
|
||||
identity={account}
|
||||
key={account.address}
|
||||
selected={selectedAccount.address === account.address}
|
||||
closeMenu={onClose}
|
||||
connectedAvatar={connectedSite?.iconUrl}
|
||||
connectedAvatarName={connectedSite?.name}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
{/* Add / Import / Hardware */}
|
||||
<Box padding={4}>
|
||||
<Box marginBottom={4}>
|
||||
<ButtonLink
|
||||
size={Size.SM}
|
||||
startIconName={IconName.Add}
|
||||
onClick={() => {
|
||||
dispatch(toggleAccountMenu());
|
||||
trackEvent({
|
||||
category: MetaMetricsEventCategory.Navigation,
|
||||
event: MetaMetricsEventName.AccountAddSelected,
|
||||
properties: {
|
||||
account_type: MetaMetricsEventAccountType.Default,
|
||||
location: 'Main Menu',
|
||||
},
|
||||
});
|
||||
history.push(NEW_ACCOUNT_ROUTE);
|
||||
}}
|
||||
data-testid="multichain-account-menu-add-account"
|
||||
>
|
||||
{t('addAccount')}
|
||||
</ButtonLink>
|
||||
</Box>
|
||||
<Box marginBottom={4}>
|
||||
<ButtonLink
|
||||
size={Size.SM}
|
||||
startIconName={IconName.Import}
|
||||
onClick={() => {
|
||||
dispatch(toggleAccountMenu());
|
||||
trackEvent({
|
||||
category: MetaMetricsEventCategory.Navigation,
|
||||
event: MetaMetricsEventName.AccountAddSelected,
|
||||
properties: {
|
||||
account_type: MetaMetricsEventAccountType.Imported,
|
||||
location: 'Main Menu',
|
||||
},
|
||||
});
|
||||
history.push(IMPORT_ACCOUNT_ROUTE);
|
||||
}}
|
||||
>
|
||||
{t('importAccount')}
|
||||
</ButtonLink>
|
||||
</Box>
|
||||
<Box>
|
||||
<ButtonLink
|
||||
size={Size.SM}
|
||||
startIconName={IconName.Hardware}
|
||||
onClick={() => {
|
||||
dispatch(toggleAccountMenu());
|
||||
trackEvent({
|
||||
category: MetaMetricsEventCategory.Navigation,
|
||||
event: MetaMetricsEventName.AccountAddSelected,
|
||||
properties: {
|
||||
account_type: MetaMetricsEventAccountType.Hardware,
|
||||
location: 'Main Menu',
|
||||
},
|
||||
});
|
||||
if (getEnvironmentType() === ENVIRONMENT_TYPE_POPUP) {
|
||||
global.platform.openExtensionInBrowser(
|
||||
CONNECT_HARDWARE_ROUTE,
|
||||
);
|
||||
} else {
|
||||
history.push(CONNECT_HARDWARE_ROUTE);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t('hardwareWallet')}
|
||||
</ButtonLink>
|
||||
{
|
||||
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||
<>
|
||||
<ButtonLink
|
||||
size={Size.SM}
|
||||
startIconName={IconName.Custody}
|
||||
onClick={() => {
|
||||
dispatch(toggleAccountMenu());
|
||||
trackEvent({
|
||||
category: MetaMetricsEventCategory.Navigation,
|
||||
event:
|
||||
MetaMetricsEventName.UserClickedConnectCustodialAccount,
|
||||
});
|
||||
if (getEnvironmentType() === ENVIRONMENT_TYPE_POPUP) {
|
||||
global.platform.openExtensionInBrowser(
|
||||
CUSTODY_ACCOUNT_ROUTE,
|
||||
);
|
||||
} else {
|
||||
history.push(CUSTODY_ACCOUNT_ROUTE);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t('connectCustodialAccountMenu')}
|
||||
</ButtonLink>
|
||||
{mmiPortfolioEnabled && (
|
||||
>
|
||||
{t('hardwareWallet')}
|
||||
</ButtonLink>
|
||||
{
|
||||
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||
<>
|
||||
<ButtonLink
|
||||
size={Size.SM}
|
||||
startIconName={IconName.MmmiPortfolioDashboard}
|
||||
startIconName={IconName.Custody}
|
||||
onClick={() => {
|
||||
dispatch(toggleAccountMenu());
|
||||
trackEvent({
|
||||
category: MetaMetricsEventCategory.Navigation,
|
||||
event: MetaMetricsEventName.UserClickedPortfolioButton,
|
||||
event:
|
||||
MetaMetricsEventName.UserClickedConnectCustodialAccount,
|
||||
});
|
||||
window.open(mmiPortfolioUrl, '_blank');
|
||||
if (getEnvironmentType() === ENVIRONMENT_TYPE_POPUP) {
|
||||
global.platform.openExtensionInBrowser(
|
||||
CUSTODY_ACCOUNT_ROUTE,
|
||||
);
|
||||
} else {
|
||||
history.push(CUSTODY_ACCOUNT_ROUTE);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t('portfolioDashboard')}
|
||||
{t('connectCustodialAccountMenu')}
|
||||
</ButtonLink>
|
||||
)}
|
||||
<ButtonLink
|
||||
size={Size.SM}
|
||||
startIconName={IconName.Compliance}
|
||||
onClick={() => {
|
||||
dispatch(toggleAccountMenu());
|
||||
trackEvent({
|
||||
category: MetaMetricsEventCategory.Navigation,
|
||||
event: MetaMetricsEventName.UserClickedCompliance,
|
||||
});
|
||||
if (getEnvironmentType() === ENVIRONMENT_TYPE_POPUP) {
|
||||
global.platform.openExtensionInBrowser(
|
||||
COMPLIANCE_FEATURE_ROUTE,
|
||||
);
|
||||
} else {
|
||||
history.push(COMPLIANCE_FEATURE_ROUTE);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t('compliance')}
|
||||
</ButtonLink>
|
||||
</>
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
}
|
||||
{mmiPortfolioEnabled && (
|
||||
<ButtonLink
|
||||
size={Size.SM}
|
||||
startIconName={IconName.MmmiPortfolioDashboard}
|
||||
onClick={() => {
|
||||
dispatch(toggleAccountMenu());
|
||||
trackEvent({
|
||||
category: MetaMetricsEventCategory.Navigation,
|
||||
event:
|
||||
MetaMetricsEventName.UserClickedPortfolioButton,
|
||||
});
|
||||
window.open(mmiPortfolioUrl, '_blank');
|
||||
}}
|
||||
>
|
||||
{t('portfolioDashboard')}
|
||||
</ButtonLink>
|
||||
)}
|
||||
<ButtonLink
|
||||
size={Size.SM}
|
||||
startIconName={IconName.Compliance}
|
||||
onClick={() => {
|
||||
dispatch(toggleAccountMenu());
|
||||
trackEvent({
|
||||
category: MetaMetricsEventCategory.Navigation,
|
||||
event: MetaMetricsEventName.UserClickedCompliance,
|
||||
});
|
||||
if (getEnvironmentType() === ENVIRONMENT_TYPE_POPUP) {
|
||||
global.platform.openExtensionInBrowser(
|
||||
COMPLIANCE_FEATURE_ROUTE,
|
||||
);
|
||||
} else {
|
||||
history.push(COMPLIANCE_FEATURE_ROUTE);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t('compliance')}
|
||||
</ButtonLink>
|
||||
</>
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
) : null}
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
@ -4,11 +4,7 @@ import reactRouterDom from 'react-router-dom';
|
||||
import { fireEvent, renderWithProvider } from '../../../../test/jest';
|
||||
import configureStore from '../../../store/store';
|
||||
import mockState from '../../../../test/data/mock-state.json';
|
||||
import {
|
||||
NEW_ACCOUNT_ROUTE,
|
||||
IMPORT_ACCOUNT_ROUTE,
|
||||
CONNECT_HARDWARE_ROUTE,
|
||||
} from '../../../helpers/constants/routes';
|
||||
import { CONNECT_HARDWARE_ROUTE } from '../../../helpers/constants/routes';
|
||||
import { AccountListMenu } from '.';
|
||||
|
||||
const render = (props = { onClose: () => jest.fn() }) => {
|
||||
@ -48,16 +44,24 @@ describe('AccountListMenu', () => {
|
||||
expect(getByText('Hardware wallet')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('navigates to new account screen when clicked', () => {
|
||||
const { getByText } = render();
|
||||
it('shows the account creation UI when Add Account is clicked', () => {
|
||||
const { getByText, getByPlaceholderText } = render();
|
||||
fireEvent.click(getByText('Add account'));
|
||||
expect(historyPushMock).toHaveBeenCalledWith(NEW_ACCOUNT_ROUTE);
|
||||
expect(getByText('Create')).toBeInTheDocument();
|
||||
expect(getByText('Cancel')).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(getByText('Cancel'));
|
||||
expect(getByPlaceholderText('Search accounts')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('navigates to import account screen when clicked', () => {
|
||||
const { getByText } = render();
|
||||
it('shows the account import UI when Import Account is clicked', () => {
|
||||
const { getByText, getByPlaceholderText } = render();
|
||||
fireEvent.click(getByText('Import account'));
|
||||
expect(historyPushMock).toHaveBeenCalledWith(IMPORT_ACCOUNT_ROUTE);
|
||||
expect(getByText('Import')).toBeInTheDocument();
|
||||
expect(getByText('Cancel')).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(getByText('Cancel'));
|
||||
expect(getByPlaceholderText('Search accounts')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('navigates to hardware wallet connection screen when clicked', () => {
|
||||
|
109
ui/components/multichain/create-account/create-account.js
Normal file
109
ui/components/multichain/create-account/create-account.js
Normal file
@ -0,0 +1,109 @@
|
||||
import React, { useContext, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import {
|
||||
ButtonPrimary,
|
||||
ButtonSecondary,
|
||||
FormTextField,
|
||||
} from '../../component-library';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
import { getAccountNameErrorMessage } from '../../../helpers/utils/accounts';
|
||||
import {
|
||||
getMetaMaskAccountsOrdered,
|
||||
getMetaMaskIdentities,
|
||||
} from '../../../selectors';
|
||||
import { addNewAccount, setAccountLabel } from '../../../store/actions';
|
||||
import { getMostRecentOverviewPage } from '../../../ducks/history/history';
|
||||
import Box from '../../ui/box/box';
|
||||
import {
|
||||
MetaMetricsEventAccountType,
|
||||
MetaMetricsEventCategory,
|
||||
MetaMetricsEventName,
|
||||
} from '../../../../shared/constants/metametrics';
|
||||
import { MetaMetricsContext } from '../../../contexts/metametrics';
|
||||
import { Display } from '../../../helpers/constants/design-system';
|
||||
|
||||
export const CreateAccount = ({ onActionComplete }) => {
|
||||
const t = useI18nContext();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const history = useHistory();
|
||||
const trackEvent = useContext(MetaMetricsContext);
|
||||
|
||||
const accounts = useSelector(getMetaMaskAccountsOrdered);
|
||||
const identities = useSelector(getMetaMaskIdentities);
|
||||
const mostRecentOverviewPage = useSelector(getMostRecentOverviewPage);
|
||||
|
||||
const newAccountNumber = Object.keys(identities).length + 1;
|
||||
const defaultAccountName = t('newAccountNumberName', [newAccountNumber]);
|
||||
|
||||
const [newAccountName, setNewAccountName] = useState('');
|
||||
|
||||
const { isValidAccountName, errorMessage } = getAccountNameErrorMessage(
|
||||
accounts,
|
||||
{ t },
|
||||
newAccountName,
|
||||
defaultAccountName,
|
||||
);
|
||||
|
||||
const onCreateAccount = async (name) => {
|
||||
const newAccountAddress = await dispatch(addNewAccount());
|
||||
if (name) {
|
||||
dispatch(setAccountLabel(newAccountAddress, name));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
as="form"
|
||||
onSubmit={async (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
try {
|
||||
await onCreateAccount(newAccountName || defaultAccountName);
|
||||
onActionComplete(true);
|
||||
trackEvent({
|
||||
category: MetaMetricsEventCategory.Accounts,
|
||||
event: MetaMetricsEventName.AccountAdded,
|
||||
properties: {
|
||||
account_type: MetaMetricsEventAccountType.Default,
|
||||
location: 'Home',
|
||||
},
|
||||
});
|
||||
history.push(mostRecentOverviewPage);
|
||||
} catch (error) {
|
||||
trackEvent({
|
||||
category: MetaMetricsEventCategory.Accounts,
|
||||
event: MetaMetricsEventName.AccountAddFailed,
|
||||
properties: {
|
||||
account_type: MetaMetricsEventAccountType.Default,
|
||||
error: error.message,
|
||||
},
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<FormTextField
|
||||
autoFocus
|
||||
label={t('accountName')}
|
||||
placeholder={defaultAccountName}
|
||||
onChange={(event) => setNewAccountName(event.target.value)}
|
||||
helpText={errorMessage}
|
||||
error={!isValidAccountName}
|
||||
/>
|
||||
<Box display={Display.Flex} marginTop={6} gap={2}>
|
||||
<ButtonSecondary onClick={() => onActionComplete()} block>
|
||||
{t('cancel')}
|
||||
</ButtonSecondary>
|
||||
<ButtonPrimary type="submit" disabled={!isValidAccountName} block>
|
||||
{t('create')}
|
||||
</ButtonPrimary>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
CreateAccount.propTypes = {
|
||||
onActionComplete: PropTypes.func.isRequired,
|
||||
};
|
@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
import { CreateAccount } from '.';
|
||||
|
||||
export default {
|
||||
title: 'Components/Multichain/CreateAccount',
|
||||
component: CreateAccount,
|
||||
};
|
||||
|
||||
export const DefaultStory = (args) => <CreateAccount {...args} />;
|
||||
DefaultStory.storyName = 'Default';
|
@ -0,0 +1,67 @@
|
||||
/* eslint-disable jest/require-top-level-describe */
|
||||
import React from 'react';
|
||||
import { fireEvent, renderWithProvider, waitFor } from '../../../../test/jest';
|
||||
import configureStore from '../../../store/store';
|
||||
import mockState from '../../../../test/data/mock-state.json';
|
||||
import { CreateAccount } from '.';
|
||||
|
||||
const render = (props = { onActionComplete: () => jest.fn() }) => {
|
||||
const store = configureStore(mockState);
|
||||
return renderWithProvider(<CreateAccount {...props} />, store);
|
||||
};
|
||||
|
||||
const mockAddNewAccount = jest.fn().mockReturnValue({ type: 'TYPE' });
|
||||
const mockSetAccountLabel = jest.fn().mockReturnValue({ type: 'TYPE' });
|
||||
|
||||
jest.mock('../../../store/actions', () => ({
|
||||
addNewAccount: (...args) => mockAddNewAccount(...args),
|
||||
setAccountLabel: (...args) => mockSetAccountLabel(...args),
|
||||
}));
|
||||
|
||||
describe('CreateAccount', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('displays account name input and suggests name', () => {
|
||||
const { getByPlaceholderText } = render();
|
||||
|
||||
expect(getByPlaceholderText('Account 5')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('fires onActionComplete when clicked', async () => {
|
||||
const onActionComplete = jest.fn();
|
||||
const { getByText, getByPlaceholderText } = render({ onActionComplete });
|
||||
|
||||
const input = getByPlaceholderText('Account 5');
|
||||
const newAccountName = 'New Account Name';
|
||||
|
||||
fireEvent.change(input, {
|
||||
target: { value: newAccountName },
|
||||
});
|
||||
fireEvent.click(getByText('Create'));
|
||||
|
||||
await waitFor(() => expect(mockAddNewAccount).toHaveBeenCalled());
|
||||
await waitFor(() =>
|
||||
expect(mockSetAccountLabel).toHaveBeenCalledWith(
|
||||
{ type: 'TYPE' },
|
||||
newAccountName,
|
||||
),
|
||||
);
|
||||
await waitFor(() => expect(onActionComplete).toHaveBeenCalled());
|
||||
});
|
||||
|
||||
it(`doesn't allow duplicate account names`, async () => {
|
||||
const { getByText, getByPlaceholderText } = render();
|
||||
|
||||
const input = getByPlaceholderText('Account 5');
|
||||
const usedAccountName = 'Account 4';
|
||||
|
||||
fireEvent.change(input, {
|
||||
target: { value: usedAccountName },
|
||||
});
|
||||
|
||||
const submitButton = getByText('Create');
|
||||
expect(submitButton).toHaveAttribute('disabled');
|
||||
});
|
||||
});
|
1
ui/components/multichain/create-account/index.js
Normal file
1
ui/components/multichain/create-account/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { CreateAccount } from './create-account';
|
@ -49,6 +49,7 @@ exports[`Json should match snapshot 1`] = `
|
||||
</button>
|
||||
<button
|
||||
class="box mm-text mm-button-base mm-button-base--size-lg mm-button-base--disabled mm-button-base--block mm-button-primary mm-button-primary--disabled 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="import-account-confirm-button"
|
||||
disabled=""
|
||||
>
|
||||
Import
|
@ -5,30 +5,32 @@ import {
|
||||
ButtonPrimary,
|
||||
ButtonSecondary,
|
||||
BUTTON_SECONDARY_SIZES,
|
||||
} from '../../../components/component-library';
|
||||
import Box from '../../../components/ui/box/box';
|
||||
import { DISPLAY } from '../../../helpers/constants/design-system';
|
||||
} from '../../component-library';
|
||||
import Box from '../../ui/box/box';
|
||||
import { Display } from '../../../helpers/constants/design-system';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
import * as actions from '../../../store/actions';
|
||||
|
||||
BottomButtons.propTypes = {
|
||||
importAccountFunc: PropTypes.func.isRequired,
|
||||
isPrimaryDisabled: PropTypes.bool.isRequired,
|
||||
onActionComplete: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default function BottomButtons({
|
||||
importAccountFunc,
|
||||
isPrimaryDisabled,
|
||||
onActionComplete,
|
||||
}) {
|
||||
const t = useI18nContext();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
return (
|
||||
<Box display={DISPLAY.FLEX} gap={4}>
|
||||
<Box display={Display.Flex} gap={4}>
|
||||
<ButtonSecondary
|
||||
onClick={() => {
|
||||
dispatch(actions.hideWarning());
|
||||
window.history.back();
|
||||
onActionComplete();
|
||||
}}
|
||||
size={BUTTON_SECONDARY_SIZES.LG}
|
||||
block
|
||||
@ -36,9 +38,19 @@ export default function BottomButtons({
|
||||
{t('cancel')}
|
||||
</ButtonSecondary>
|
||||
<ButtonPrimary
|
||||
onClick={importAccountFunc}
|
||||
onClick={async () => {
|
||||
try {
|
||||
const result = await importAccountFunc();
|
||||
if (result) {
|
||||
onActionComplete(true);
|
||||
}
|
||||
} catch (e) {
|
||||
// Take no action
|
||||
}
|
||||
}}
|
||||
disabled={isPrimaryDisabled}
|
||||
size={BUTTON_SECONDARY_SIZES.LG}
|
||||
data-testid="import-account-confirm-button"
|
||||
block
|
||||
>
|
||||
{t('import')}
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||
import BottomButtons from './bottom-buttons';
|
||||
|
||||
export default {
|
||||
title: 'Pages/CreateAccount/ImportAccount/BottomButtons',
|
||||
title: 'Components/Multichain/BottomButtons',
|
||||
component: BottomButtons,
|
||||
argTypes: {
|
||||
isPrimaryDisabled: {
|
@ -1,4 +1,5 @@
|
||||
import React, { useContext, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import {
|
||||
MetaMetricsEventAccountImportType,
|
||||
@ -6,54 +7,56 @@ import {
|
||||
MetaMetricsEventCategory,
|
||||
MetaMetricsEventName,
|
||||
} from '../../../../shared/constants/metametrics';
|
||||
import { ButtonLink, Label, Text } from '../../../components/component-library';
|
||||
import Box from '../../../components/ui/box';
|
||||
import Dropdown from '../../../components/ui/dropdown';
|
||||
import { ButtonLink, Label, Text } from '../../component-library';
|
||||
import Box from '../../ui/box';
|
||||
import Dropdown from '../../ui/dropdown';
|
||||
import { MetaMetricsContext } from '../../../contexts/metametrics';
|
||||
import {
|
||||
BLOCK_SIZES,
|
||||
BorderColor,
|
||||
FONT_WEIGHT,
|
||||
BlockSize,
|
||||
FontWeight,
|
||||
JustifyContent,
|
||||
Size,
|
||||
TextVariant,
|
||||
} from '../../../helpers/constants/design-system';
|
||||
import ZENDESK_URLS from '../../../helpers/constants/zendesk-url';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
import { useRouting } from '../../../hooks/useRouting';
|
||||
import * as actions from '../../../store/actions';
|
||||
|
||||
// Subviews
|
||||
import JsonImportView from './json';
|
||||
import PrivateKeyImportView from './private-key';
|
||||
|
||||
export default function NewAccountImportForm() {
|
||||
export const ImportAccount = ({ onActionComplete }) => {
|
||||
const t = useI18nContext();
|
||||
const dispatch = useDispatch();
|
||||
const trackEvent = useContext(MetaMetricsContext);
|
||||
const { navigateToMostRecentOverviewPage } = useRouting();
|
||||
|
||||
const menuItems = [t('privateKey'), t('jsonFile')];
|
||||
|
||||
const [type, setType] = useState(menuItems[0]);
|
||||
|
||||
function importAccount(strategy, importArgs) {
|
||||
async function importAccount(strategy, importArgs) {
|
||||
const loadingMessage = getLoadingMessage(strategy);
|
||||
|
||||
dispatch(actions.importNewAccount(strategy, importArgs, loadingMessage))
|
||||
.then(({ selectedAddress }) => {
|
||||
if (selectedAddress) {
|
||||
trackImportEvent(strategy, true);
|
||||
dispatch(actions.hideWarning());
|
||||
navigateToMostRecentOverviewPage();
|
||||
} else {
|
||||
dispatch(actions.displayWarning(t('importAccountError')));
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
trackImportEvent(strategy, error.message);
|
||||
translateWarning(error.message);
|
||||
});
|
||||
try {
|
||||
const { selectedAddress } = await dispatch(
|
||||
actions.importNewAccount(strategy, importArgs, loadingMessage),
|
||||
);
|
||||
if (selectedAddress) {
|
||||
trackImportEvent(strategy, true);
|
||||
dispatch(actions.hideWarning());
|
||||
onActionComplete(true);
|
||||
} else {
|
||||
dispatch(actions.displayWarning(t('importAccountError')));
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
trackImportEvent(strategy, error.message);
|
||||
translateWarning(error.message);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function trackImportEvent(strategy, wasSuccessful) {
|
||||
@ -79,13 +82,14 @@ export default function NewAccountImportForm() {
|
||||
function getLoadingMessage(strategy) {
|
||||
if (strategy === 'JSON File') {
|
||||
return (
|
||||
<Text width={BLOCK_SIZES.THREE_FOURTHS} fontWeight={FONT_WEIGHT.BOLD}>
|
||||
<br />
|
||||
{t('importAccountJsonLoading1')}
|
||||
<br />
|
||||
<br />
|
||||
{t('importAccountJsonLoading2')}
|
||||
</Text>
|
||||
<>
|
||||
<Text width={BlockSize.ThreeFourths} fontWeight={FontWeight.Bold}>
|
||||
{t('importAccountJsonLoading1')}
|
||||
</Text>
|
||||
<Text width={BlockSize.ThreeFourths} fontWeight={FontWeight.Bold}>
|
||||
{t('importAccountJsonLoading2')}
|
||||
</Text>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -112,40 +116,22 @@ export default function NewAccountImportForm() {
|
||||
}
|
||||
}
|
||||
|
||||
function PrivateKeyOrJson() {
|
||||
switch (type) {
|
||||
case menuItems[0]:
|
||||
return <PrivateKeyImportView importAccountFunc={importAccount} />;
|
||||
case menuItems[1]:
|
||||
default:
|
||||
return <JsonImportView importAccountFunc={importAccount} />;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
padding={4}
|
||||
className="bottom-border-1px" // There is no way to do just a bottom border in the Design System
|
||||
borderColor={BorderColor.borderDefault}
|
||||
>
|
||||
<Text variant={TextVariant.headingLg}>{t('importAccount')}</Text>
|
||||
<Text variant={TextVariant.bodySm} marginTop={2}>
|
||||
{t('importAccountMsg')}{' '}
|
||||
<ButtonLink
|
||||
size={Size.inherit}
|
||||
href={ZENDESK_URLS.IMPORTED_ACCOUNTS}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t('here')}
|
||||
</ButtonLink>
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Box padding={4} paddingBottom={8} paddingLeft={4} paddingRight={4}>
|
||||
<Text variant={TextVariant.bodySm} marginTop={2}>
|
||||
{t('importAccountMsg')}{' '}
|
||||
<ButtonLink
|
||||
size={Size.inherit}
|
||||
href={ZENDESK_URLS.IMPORTED_ACCOUNTS}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t('here')}
|
||||
</ButtonLink>
|
||||
</Text>
|
||||
<Box paddingTop={4} paddingBottom={8}>
|
||||
<Label
|
||||
width={BLOCK_SIZES.FULL}
|
||||
width={BlockSize.Full}
|
||||
marginBottom={4}
|
||||
justifyContent={JustifyContent.spaceBetween}
|
||||
>
|
||||
@ -159,8 +145,22 @@ export default function NewAccountImportForm() {
|
||||
}}
|
||||
/>
|
||||
</Label>
|
||||
<PrivateKeyOrJson />
|
||||
{type === menuItems[0] ? (
|
||||
<PrivateKeyImportView
|
||||
importAccountFunc={importAccount}
|
||||
onActionComplete={onActionComplete}
|
||||
/>
|
||||
) : (
|
||||
<JsonImportView
|
||||
importAccountFunc={importAccount}
|
||||
onActionComplete={onActionComplete}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
ImportAccount.propTypes = {
|
||||
onActionComplete: PropTypes.func.isRequired,
|
||||
};
|
@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
import { ImportAccount } from '.';
|
||||
|
||||
export default {
|
||||
title: 'Components/Multichain/ImportAccount',
|
||||
component: ImportAccount,
|
||||
};
|
||||
|
||||
export const DefaultStory = (args) => <ImportAccount {...args} />;
|
||||
|
||||
DefaultStory.storyName = 'Default';
|
1
ui/components/multichain/import-account/index.js
Normal file
1
ui/components/multichain/import-account/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { ImportAccount } from './import-account';
|
@ -8,7 +8,7 @@ import {
|
||||
Text,
|
||||
TEXT_FIELD_SIZES,
|
||||
TEXT_FIELD_TYPES,
|
||||
} from '../../../components/component-library';
|
||||
} from '../../component-library';
|
||||
import {
|
||||
Size,
|
||||
TextVariant,
|
||||
@ -21,9 +21,13 @@ import BottomButtons from './bottom-buttons';
|
||||
|
||||
JsonImportSubview.propTypes = {
|
||||
importAccountFunc: PropTypes.func.isRequired,
|
||||
onActionComplete: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default function JsonImportSubview({ importAccountFunc }) {
|
||||
export default function JsonImportSubview({
|
||||
importAccountFunc,
|
||||
onActionComplete,
|
||||
}) {
|
||||
const t = useI18nContext();
|
||||
const warning = useSelector((state) => state.appState.warning);
|
||||
const [password, setPassword] = useState('');
|
||||
@ -95,6 +99,7 @@ export default function JsonImportSubview({ importAccountFunc }) {
|
||||
<BottomButtons
|
||||
importAccountFunc={_importAccountFunc}
|
||||
isPrimaryDisabled={isPrimaryDisabled}
|
||||
onActionComplete={onActionComplete}
|
||||
/>
|
||||
</>
|
||||
);
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||
import JsonImportSubview from './json';
|
||||
|
||||
export default {
|
||||
title: 'Pages/CreateAccount/ImportAccount/JsonImportSubview',
|
||||
title: 'Components/Multichain/JsonImportSubview',
|
||||
component: JsonImportSubview,
|
||||
};
|
||||
|
@ -7,11 +7,16 @@ import messages from '../../../../app/_locales/en/messages.json';
|
||||
import Json from './json';
|
||||
|
||||
const mockImportFunc = jest.fn();
|
||||
const mockOnActionComplete = jest.fn();
|
||||
|
||||
describe('Json', () => {
|
||||
const mockStore = configureMockStore()(mockState);
|
||||
it('should match snapshot', () => {
|
||||
const { asFragment } = renderWithProvider(
|
||||
<Json importAccountFunc={mockImportFunc} />,
|
||||
<Json
|
||||
importAccountFunc={mockImportFunc}
|
||||
onActionComplete={mockOnActionComplete}
|
||||
/>,
|
||||
mockStore,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
@ -19,7 +24,10 @@ describe('Json', () => {
|
||||
|
||||
it('should render', () => {
|
||||
const { getByText } = renderWithProvider(
|
||||
<Json importAccountFunc={mockImportFunc} />,
|
||||
<Json
|
||||
importAccountFunc={mockImportFunc}
|
||||
onActionComplete={mockOnActionComplete}
|
||||
/>,
|
||||
mockStore,
|
||||
);
|
||||
|
||||
@ -29,7 +37,10 @@ describe('Json', () => {
|
||||
|
||||
it('should import file without password', async () => {
|
||||
const { getByText, getByTestId } = renderWithProvider(
|
||||
<Json importAccountFunc={mockImportFunc} />,
|
||||
<Json
|
||||
importAccountFunc={mockImportFunc}
|
||||
onActionComplete={mockOnActionComplete}
|
||||
/>,
|
||||
mockStore,
|
||||
);
|
||||
|
||||
@ -58,7 +69,10 @@ describe('Json', () => {
|
||||
|
||||
it('should import file with password', async () => {
|
||||
const { getByText, getByTestId, getByPlaceholderText } = renderWithProvider(
|
||||
<Json importAccountFunc={mockImportFunc} />,
|
||||
<Json
|
||||
importAccountFunc={mockImportFunc}
|
||||
onActionComplete={mockOnActionComplete}
|
||||
/>,
|
||||
mockStore,
|
||||
);
|
||||
|
@ -5,15 +5,19 @@ import {
|
||||
FormTextField,
|
||||
TEXT_FIELD_SIZES,
|
||||
TEXT_FIELD_TYPES,
|
||||
} from '../../../components/component-library';
|
||||
} from '../../component-library';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
import BottomButtons from './bottom-buttons';
|
||||
|
||||
PrivateKeyImportView.propTypes = {
|
||||
importAccountFunc: PropTypes.func.isRequired,
|
||||
onActionComplete: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default function PrivateKeyImportView({ importAccountFunc }) {
|
||||
export default function PrivateKeyImportView({
|
||||
importAccountFunc,
|
||||
onActionComplete,
|
||||
}) {
|
||||
const t = useI18nContext();
|
||||
const [privateKey, setPrivateKey] = useState('');
|
||||
|
||||
@ -51,6 +55,7 @@ export default function PrivateKeyImportView({ importAccountFunc }) {
|
||||
<BottomButtons
|
||||
importAccountFunc={_importAccountFunc}
|
||||
isPrimaryDisabled={privateKey === ''}
|
||||
onActionComplete={onActionComplete}
|
||||
/>
|
||||
</>
|
||||
);
|
@ -13,3 +13,5 @@ export { NetworkListItem } from './network-list-item';
|
||||
export { NetworkListMenu } from './network-list-menu';
|
||||
export { ProductTour } from './product-tour-popover';
|
||||
export { AccountDetails } from './account-details';
|
||||
export { CreateAccount } from './create-account';
|
||||
export { ImportAccount } from './import-account';
|
||||
|
@ -25,8 +25,6 @@ const RESTORE_VAULT_ROUTE = '/restore-vault';
|
||||
const IMPORT_TOKEN_ROUTE = '/import-token';
|
||||
const CONFIRM_IMPORT_TOKEN_ROUTE = '/confirm-import-token';
|
||||
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(build-mmi)
|
||||
const CUSTODY_ACCOUNT_ROUTE = '/new-account/custody';
|
||||
@ -129,8 +127,6 @@ const PATH_NAME_MAP = {
|
||||
[IMPORT_TOKEN_ROUTE]: 'Import Token Page',
|
||||
[CONFIRM_IMPORT_TOKEN_ROUTE]: 'Confirm Import Token Page',
|
||||
[CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE]: 'Confirm Add Suggested Token Page',
|
||||
[NEW_ACCOUNT_ROUTE]: 'New Account Page',
|
||||
[IMPORT_ACCOUNT_ROUTE]: 'Import Account Page',
|
||||
[CONNECT_HARDWARE_ROUTE]: 'Connect Hardware Wallet Page',
|
||||
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||
[INSTITUTIONAL_FEATURES_DONE_ROUTE]: 'Institutional Features Done Page',
|
||||
@ -194,8 +190,6 @@ export {
|
||||
IMPORT_TOKEN_ROUTE,
|
||||
CONFIRM_IMPORT_TOKEN_ROUTE,
|
||||
CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE,
|
||||
NEW_ACCOUNT_ROUTE,
|
||||
IMPORT_ACCOUNT_ROUTE,
|
||||
CONNECT_HARDWARE_ROUTE,
|
||||
SEND_ROUTE,
|
||||
TOKEN_DETAILS,
|
||||
|
@ -4,8 +4,6 @@ import Box from '../../components/ui/box';
|
||||
|
||||
import {
|
||||
CONNECT_HARDWARE_ROUTE,
|
||||
IMPORT_ACCOUNT_ROUTE,
|
||||
NEW_ACCOUNT_ROUTE,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||
CUSTODY_ACCOUNT_ROUTE,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
@ -14,23 +12,11 @@ import {
|
||||
import CustodyPage from '../institutional/custody';
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
import ConnectHardwareForm from './connect-hardware';
|
||||
import NewAccountImportForm from './import-account';
|
||||
import NewAccountCreateForm from './new-account.container';
|
||||
|
||||
export default function CreateAccountPage() {
|
||||
return (
|
||||
<Box className="new-account">
|
||||
<Switch>
|
||||
<Route
|
||||
exact
|
||||
path={NEW_ACCOUNT_ROUTE}
|
||||
component={NewAccountCreateForm}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={IMPORT_ACCOUNT_ROUTE}
|
||||
component={NewAccountImportForm}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={CONNECT_HARDWARE_ROUTE}
|
||||
|
@ -1,11 +0,0 @@
|
||||
import React from 'react';
|
||||
import NewAccountImportForm from '.';
|
||||
|
||||
export default {
|
||||
title: 'Pages/CreateAccount/ImportAccount',
|
||||
component: NewAccountImportForm,
|
||||
};
|
||||
|
||||
export const DefaultStory = (args) => <NewAccountImportForm {...args} />;
|
||||
|
||||
DefaultStory.storyName = 'Default';
|
@ -1 +0,0 @@
|
||||
export { default } from './import-account';
|
@ -1,83 +0,0 @@
|
||||
@import 'connect-hardware/index';
|
||||
|
||||
.new-account {
|
||||
width: 375px;
|
||||
background-color: var(--color-background-default);
|
||||
box-shadow: var(--shadow-size-xs) var(--color-shadow-default);
|
||||
z-index: 25;
|
||||
height: unset;
|
||||
overflow: auto;
|
||||
scrollbar-width: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@include screen-sm-min {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.bottom-border-1px {
|
||||
border-width: 0 0 1px 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.new-account-create-form {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
padding: 30px;
|
||||
|
||||
&__input-label {
|
||||
@include Paragraph;
|
||||
|
||||
color: var(--color-text-alternative);
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
&__input {
|
||||
@include Paragraph;
|
||||
|
||||
height: 54px;
|
||||
width: 315.84px;
|
||||
border: 1px solid var(--color-border-muted);
|
||||
border-radius: 4px;
|
||||
background-color: var(--color-background-default);
|
||||
color: var(--color-text-default);
|
||||
margin-top: 15px;
|
||||
padding: 0 20px;
|
||||
|
||||
&__error {
|
||||
border: 1px solid var(--color-error-alternative);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: var(--color-text-muted);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&__error {
|
||||
@include H7;
|
||||
|
||||
left: 8px;
|
||||
color: var(--color-error-default);
|
||||
}
|
||||
|
||||
&__error-amount {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
&__buttons {
|
||||
margin-top: 22px;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&__button {
|
||||
width: 150px;
|
||||
min-width: initial;
|
||||
}
|
||||
}
|
@ -1,119 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import Button from '../../components/ui/button';
|
||||
import {
|
||||
MetaMetricsEventAccountType,
|
||||
MetaMetricsEventCategory,
|
||||
MetaMetricsEventName,
|
||||
} from '../../../shared/constants/metametrics';
|
||||
import { getAccountNameErrorMessage } from '../../helpers/utils/accounts';
|
||||
|
||||
export default class NewAccountCreateForm extends Component {
|
||||
static defaultProps = {
|
||||
newAccountNumber: 0,
|
||||
};
|
||||
|
||||
state = {
|
||||
newAccountName: '',
|
||||
defaultAccountName: this.context.t('newAccountNumberName', [
|
||||
this.props.newAccountNumber,
|
||||
]),
|
||||
};
|
||||
|
||||
render() {
|
||||
const { newAccountName, defaultAccountName } = this.state;
|
||||
const { history, createAccount, mostRecentOverviewPage, accounts } =
|
||||
this.props;
|
||||
|
||||
const createClick = (event) => {
|
||||
event.preventDefault();
|
||||
createAccount(newAccountName || defaultAccountName)
|
||||
.then(() => {
|
||||
this.context.trackEvent({
|
||||
category: MetaMetricsEventCategory.Accounts,
|
||||
event: MetaMetricsEventName.AccountAdded,
|
||||
properties: {
|
||||
account_type: MetaMetricsEventAccountType.Default,
|
||||
location: 'Home',
|
||||
},
|
||||
});
|
||||
history.push(mostRecentOverviewPage);
|
||||
})
|
||||
.catch((e) => {
|
||||
this.context.trackEvent({
|
||||
category: MetaMetricsEventCategory.Accounts,
|
||||
event: MetaMetricsEventName.AccountAddFailed,
|
||||
properties: {
|
||||
account_type: MetaMetricsEventAccountType.Default,
|
||||
error: e.message,
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const { isValidAccountName, errorMessage } = getAccountNameErrorMessage(
|
||||
accounts,
|
||||
this.context,
|
||||
newAccountName,
|
||||
defaultAccountName,
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="new-account-create-form">
|
||||
<div className="new-account-create-form__input-label">
|
||||
{this.context.t('accountName')}
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
className={classnames({
|
||||
'new-account-create-form__input': true,
|
||||
'new-account-create-form__input__error': !isValidAccountName,
|
||||
})}
|
||||
value={newAccountName}
|
||||
placeholder={defaultAccountName}
|
||||
onChange={(event) =>
|
||||
this.setState({ newAccountName: event.target.value })
|
||||
}
|
||||
autoFocus
|
||||
/>
|
||||
<div className="new-account-create-form__error new-account-create-form__error-amount">
|
||||
{errorMessage}
|
||||
</div>
|
||||
<div className="new-account-create-form__buttons">
|
||||
<Button
|
||||
type="secondary"
|
||||
large
|
||||
className="new-account-create-form__button"
|
||||
onClick={() => history.push(mostRecentOverviewPage)}
|
||||
>
|
||||
{this.context.t('cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
large
|
||||
className="new-account-create-form__button"
|
||||
onClick={createClick}
|
||||
disabled={!isValidAccountName}
|
||||
>
|
||||
{this.context.t('create')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
NewAccountCreateForm.propTypes = {
|
||||
createAccount: PropTypes.func,
|
||||
newAccountNumber: PropTypes.number,
|
||||
history: PropTypes.object,
|
||||
mostRecentOverviewPage: PropTypes.string.isRequired,
|
||||
accounts: PropTypes.array,
|
||||
};
|
||||
|
||||
NewAccountCreateForm.contextTypes = {
|
||||
t: PropTypes.func,
|
||||
trackEvent: PropTypes.func,
|
||||
};
|
@ -1,36 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
import * as actions from '../../store/actions';
|
||||
import { getMostRecentOverviewPage } from '../../ducks/history/history';
|
||||
import { getMetaMaskAccountsOrdered } from '../../selectors';
|
||||
import NewAccountCreateForm from './new-account.component';
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const {
|
||||
metamask: { identities = {} },
|
||||
} = state;
|
||||
const numberOfExistingAccounts = Object.keys(identities).length;
|
||||
const newAccountNumber = numberOfExistingAccounts + 1;
|
||||
|
||||
return {
|
||||
newAccountNumber,
|
||||
mostRecentOverviewPage: getMostRecentOverviewPage(state),
|
||||
accounts: getMetaMaskAccountsOrdered(state),
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
createAccount: (newAccountName) => {
|
||||
return dispatch(actions.addNewAccount()).then((newAccountAddress) => {
|
||||
if (newAccountName) {
|
||||
dispatch(actions.setAccountLabel(newAccountAddress, newAccountName));
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(NewAccountCreateForm);
|
@ -1,22 +0,0 @@
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import React from 'react';
|
||||
import NewAccountCreateForm from './new-account.component';
|
||||
|
||||
export default {
|
||||
title: 'Pages/CreateAccount/NewAccount',
|
||||
component: NewAccountCreateForm,
|
||||
argTypes: {
|
||||
accounts: {
|
||||
control: 'array',
|
||||
},
|
||||
},
|
||||
args: {
|
||||
accounts: [],
|
||||
},
|
||||
};
|
||||
|
||||
export const DefaultStory = (args) => (
|
||||
<NewAccountCreateForm {...args} createAccount={action('Account Created')} />
|
||||
);
|
||||
|
||||
DefaultStory.storyName = 'Default';
|
@ -11,7 +11,6 @@
|
||||
@import 'connected-sites/index';
|
||||
@import 'connected-accounts/index';
|
||||
@import 'connected-sites/index';
|
||||
@import 'create-account/index';
|
||||
@import "institutional/connect-custody/index";
|
||||
@import "institutional/custody/index";
|
||||
@import "institutional/institutional-entity-done-page/index";
|
||||
|
@ -22,7 +22,6 @@ import ImportTokenPage from '../import-token';
|
||||
import AddNftPage from '../add-nft';
|
||||
import ConfirmImportTokenPage from '../confirm-import-token';
|
||||
import ConfirmAddSuggestedTokenPage from '../confirm-add-suggested-token';
|
||||
import CreateAccountPage from '../create-account';
|
||||
import Loading from '../../components/ui/loading-screen';
|
||||
import LoadingNetwork from '../../components/app/loading-network-screen';
|
||||
import { Modal } from '../../components/app/modals';
|
||||
@ -63,7 +62,6 @@ import {
|
||||
CONNECT_ROUTE,
|
||||
DEFAULT_ROUTE,
|
||||
LOCK_ROUTE,
|
||||
NEW_ACCOUNT_ROUTE,
|
||||
RESTORE_VAULT_ROUTE,
|
||||
REVEAL_SEED_ROUTE,
|
||||
SEND_ROUTE,
|
||||
@ -326,7 +324,6 @@ export default class Routes extends Component {
|
||||
{
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
}
|
||||
<Authenticated path={NEW_ACCOUNT_ROUTE} component={CreateAccountPage} />
|
||||
<Authenticated
|
||||
path={`${CONNECT_ROUTE}/:id`}
|
||||
component={PermissionsConnect}
|
||||
|
Loading…
Reference in New Issue
Block a user