mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +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"]',
|
'[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.clickElement({ text: 'Create', tag: 'button' });
|
||||||
const accountName = await driver.waitForSelector({
|
const accountName = await driver.waitForSelector({
|
||||||
css: '[data-testid="account-menu-icon"]',
|
css: '[data-testid="account-menu-icon"]',
|
||||||
@ -86,7 +86,7 @@ describe('Add account', function () {
|
|||||||
await driver.clickElement(
|
await driver.clickElement(
|
||||||
'[data-testid="multichain-account-menu-add-account"]',
|
'[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.clickElement({ text: 'Create', tag: 'button' });
|
||||||
await waitForAccountRendered(driver);
|
await waitForAccountRendered(driver);
|
||||||
|
|
||||||
@ -190,8 +190,7 @@ describe('Add account', function () {
|
|||||||
await driver.clickElement(
|
await driver.clickElement(
|
||||||
'[data-testid="multichain-account-menu-add-account"]',
|
'[data-testid="multichain-account-menu-add-account"]',
|
||||||
);
|
);
|
||||||
|
await driver.fill('[placeholder="Account 2"]', '2nd account');
|
||||||
await driver.fill('.new-account-create-form input', '2nd account');
|
|
||||||
await driver.clickElement({ text: 'Create', tag: 'button' });
|
await driver.clickElement({ text: 'Create', tag: 'button' });
|
||||||
|
|
||||||
// Wait for 2nd account to be created
|
// Wait for 2nd account to be created
|
||||||
@ -220,7 +219,10 @@ describe('Add account', function () {
|
|||||||
await driver.clickElement('.menu__background');
|
await driver.clickElement('.menu__background');
|
||||||
await driver.clickElement({ text: 'Import account', tag: 'button' });
|
await driver.clickElement({ text: 'Import account', tag: 'button' });
|
||||||
await driver.fill('#private-key-box', testPrivateKey);
|
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
|
// Wait for 3rd account to be created
|
||||||
await waitForAccountRendered(driver);
|
await waitForAccountRendered(driver);
|
||||||
|
@ -86,7 +86,7 @@ describe('MetaMask Import UI', function () {
|
|||||||
await driver.clickElement({ text: 'Add account', tag: 'button' });
|
await driver.clickElement({ text: 'Add account', tag: 'button' });
|
||||||
|
|
||||||
// set account name
|
// 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.delay(regularDelayMs);
|
||||||
await driver.clickElement({ text: 'Create', tag: 'button' });
|
await driver.clickElement({ text: 'Create', tag: 'button' });
|
||||||
|
|
||||||
@ -203,7 +203,9 @@ describe('MetaMask Import UI', function () {
|
|||||||
// enter private key
|
// enter private key
|
||||||
await driver.findClickableElement('#private-key-box');
|
await driver.findClickableElement('#private-key-box');
|
||||||
await driver.fill('#private-key-box', testPrivateKey1);
|
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
|
// should show the correct account name
|
||||||
const importedAccountName = await driver.findElement(
|
const importedAccountName = await driver.findElement(
|
||||||
@ -230,8 +232,9 @@ describe('MetaMask Import UI', function () {
|
|||||||
// enter private key
|
// enter private key
|
||||||
await driver.findClickableElement('#private-key-box');
|
await driver.findClickableElement('#private-key-box');
|
||||||
await driver.fill('#private-key-box', testPrivateKey2);
|
await driver.fill('#private-key-box', testPrivateKey2);
|
||||||
await driver.findClickableElement({ text: 'Import', tag: 'button' });
|
await driver.clickElement(
|
||||||
await driver.clickElement({ text: 'Import', tag: 'button' });
|
'[data-testid="import-account-confirm-button"]',
|
||||||
|
);
|
||||||
|
|
||||||
// should see new account in account menu
|
// should see new account in account menu
|
||||||
const importedAccount2Name = await driver.findElement(
|
const importedAccount2Name = await driver.findElement(
|
||||||
@ -303,8 +306,9 @@ describe('MetaMask Import UI', function () {
|
|||||||
fileInput.sendKeys(importJsonFile);
|
fileInput.sendKeys(importJsonFile);
|
||||||
|
|
||||||
await driver.fill('#json-password-box', 'foobarbazqux');
|
await driver.fill('#json-password-box', 'foobarbazqux');
|
||||||
|
await driver.clickElement(
|
||||||
await driver.clickElement({ text: 'Import', tag: 'button' });
|
'[data-testid="import-account-confirm-button"]',
|
||||||
|
);
|
||||||
|
|
||||||
// should show the correct account name
|
// should show the correct account name
|
||||||
const importedAccountName = await driver.findElement(
|
const importedAccountName = await driver.findElement(
|
||||||
@ -358,8 +362,9 @@ describe('MetaMask Import UI', function () {
|
|||||||
// enter private key
|
// enter private key
|
||||||
await driver.findClickableElement('#private-key-box');
|
await driver.findClickableElement('#private-key-box');
|
||||||
await driver.fill('#private-key-box', testPrivateKey);
|
await driver.fill('#private-key-box', testPrivateKey);
|
||||||
await driver.findClickableElement({ text: 'Import', tag: 'button' });
|
await driver.clickElement(
|
||||||
await driver.clickElement({ text: 'Import', tag: 'button' });
|
'[data-testid="import-account-confirm-button"]',
|
||||||
|
);
|
||||||
|
|
||||||
// error should occur
|
// error should occur
|
||||||
await driver.waitForSelector({
|
await driver.waitForSelector({
|
||||||
|
@ -38,7 +38,7 @@ async function loadNewAccount() {
|
|||||||
await driver.clickElement(
|
await driver.clickElement(
|
||||||
'[data-testid="multichain-account-menu-add-account"]',
|
'[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.clickElement({ text: 'Create', tag: 'button' });
|
||||||
await driver.waitForSelector({
|
await driver.waitForSelector({
|
||||||
css: '.currency-display-component__text',
|
css: '.currency-display-component__text',
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
TextFieldSearch,
|
TextFieldSearch,
|
||||||
Text,
|
Text,
|
||||||
} from '../../component-library';
|
} from '../../component-library';
|
||||||
import { AccountListItem } from '..';
|
import { AccountListItem, CreateAccount, ImportAccount } from '..';
|
||||||
import {
|
import {
|
||||||
BLOCK_SIZES,
|
BLOCK_SIZES,
|
||||||
Size,
|
Size,
|
||||||
@ -38,8 +38,6 @@ import {
|
|||||||
MetaMetricsEventName,
|
MetaMetricsEventName,
|
||||||
} from '../../../../shared/constants/metametrics';
|
} from '../../../../shared/constants/metametrics';
|
||||||
import {
|
import {
|
||||||
IMPORT_ACCOUNT_ROUTE,
|
|
||||||
NEW_ACCOUNT_ROUTE,
|
|
||||||
CONNECT_HARDWARE_ROUTE,
|
CONNECT_HARDWARE_ROUTE,
|
||||||
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
CUSTODY_ACCOUNT_ROUTE,
|
CUSTODY_ACCOUNT_ROUTE,
|
||||||
@ -66,6 +64,7 @@ export const AccountListMenu = ({ onClose }) => {
|
|||||||
///: END:ONLY_INCLUDE_IN
|
///: END:ONLY_INCLUDE_IN
|
||||||
|
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
const [actionMode, setActionMode] = useState('');
|
||||||
|
|
||||||
let searchResults = accounts;
|
let searchResults = accounts;
|
||||||
if (searchQuery) {
|
if (searchQuery) {
|
||||||
@ -88,13 +87,48 @@ export const AccountListMenu = ({ onClose }) => {
|
|||||||
}
|
}
|
||||||
}, [inputRef]);
|
}, [inputRef]);
|
||||||
|
|
||||||
|
let title = t('selectAnAccount');
|
||||||
|
if (actionMode === 'add') {
|
||||||
|
title = t('addAccount');
|
||||||
|
} else if (actionMode === 'import') {
|
||||||
|
title = t('importAccount');
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover
|
<Popover
|
||||||
title={t('selectAnAccount')}
|
title={title}
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
centerTitle
|
centerTitle
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
|
onBack={actionMode === '' ? null : () => setActionMode('')}
|
||||||
>
|
>
|
||||||
|
{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">
|
<Box className="multichain-account-menu">
|
||||||
{/* Search box */}
|
{/* Search box */}
|
||||||
{accounts.length > 1 ? (
|
{accounts.length > 1 ? (
|
||||||
@ -164,7 +198,6 @@ export const AccountListMenu = ({ onClose }) => {
|
|||||||
size={Size.SM}
|
size={Size.SM}
|
||||||
startIconName={IconName.Add}
|
startIconName={IconName.Add}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
dispatch(toggleAccountMenu());
|
|
||||||
trackEvent({
|
trackEvent({
|
||||||
category: MetaMetricsEventCategory.Navigation,
|
category: MetaMetricsEventCategory.Navigation,
|
||||||
event: MetaMetricsEventName.AccountAddSelected,
|
event: MetaMetricsEventName.AccountAddSelected,
|
||||||
@ -173,7 +206,7 @@ export const AccountListMenu = ({ onClose }) => {
|
|||||||
location: 'Main Menu',
|
location: 'Main Menu',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
history.push(NEW_ACCOUNT_ROUTE);
|
setActionMode('add');
|
||||||
}}
|
}}
|
||||||
data-testid="multichain-account-menu-add-account"
|
data-testid="multichain-account-menu-add-account"
|
||||||
>
|
>
|
||||||
@ -185,7 +218,6 @@ export const AccountListMenu = ({ onClose }) => {
|
|||||||
size={Size.SM}
|
size={Size.SM}
|
||||||
startIconName={IconName.Import}
|
startIconName={IconName.Import}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
dispatch(toggleAccountMenu());
|
|
||||||
trackEvent({
|
trackEvent({
|
||||||
category: MetaMetricsEventCategory.Navigation,
|
category: MetaMetricsEventCategory.Navigation,
|
||||||
event: MetaMetricsEventName.AccountAddSelected,
|
event: MetaMetricsEventName.AccountAddSelected,
|
||||||
@ -194,7 +226,7 @@ export const AccountListMenu = ({ onClose }) => {
|
|||||||
location: 'Main Menu',
|
location: 'Main Menu',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
history.push(IMPORT_ACCOUNT_ROUTE);
|
setActionMode('import');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('importAccount')}
|
{t('importAccount')}
|
||||||
@ -257,7 +289,8 @@ export const AccountListMenu = ({ onClose }) => {
|
|||||||
dispatch(toggleAccountMenu());
|
dispatch(toggleAccountMenu());
|
||||||
trackEvent({
|
trackEvent({
|
||||||
category: MetaMetricsEventCategory.Navigation,
|
category: MetaMetricsEventCategory.Navigation,
|
||||||
event: MetaMetricsEventName.UserClickedPortfolioButton,
|
event:
|
||||||
|
MetaMetricsEventName.UserClickedPortfolioButton,
|
||||||
});
|
});
|
||||||
window.open(mmiPortfolioUrl, '_blank');
|
window.open(mmiPortfolioUrl, '_blank');
|
||||||
}}
|
}}
|
||||||
@ -291,6 +324,7 @@ export const AccountListMenu = ({ onClose }) => {
|
|||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
) : null}
|
||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -4,11 +4,7 @@ import reactRouterDom from 'react-router-dom';
|
|||||||
import { fireEvent, renderWithProvider } from '../../../../test/jest';
|
import { fireEvent, renderWithProvider } from '../../../../test/jest';
|
||||||
import configureStore from '../../../store/store';
|
import configureStore from '../../../store/store';
|
||||||
import mockState from '../../../../test/data/mock-state.json';
|
import mockState from '../../../../test/data/mock-state.json';
|
||||||
import {
|
import { CONNECT_HARDWARE_ROUTE } from '../../../helpers/constants/routes';
|
||||||
NEW_ACCOUNT_ROUTE,
|
|
||||||
IMPORT_ACCOUNT_ROUTE,
|
|
||||||
CONNECT_HARDWARE_ROUTE,
|
|
||||||
} from '../../../helpers/constants/routes';
|
|
||||||
import { AccountListMenu } from '.';
|
import { AccountListMenu } from '.';
|
||||||
|
|
||||||
const render = (props = { onClose: () => jest.fn() }) => {
|
const render = (props = { onClose: () => jest.fn() }) => {
|
||||||
@ -48,16 +44,24 @@ describe('AccountListMenu', () => {
|
|||||||
expect(getByText('Hardware wallet')).toBeInTheDocument();
|
expect(getByText('Hardware wallet')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('navigates to new account screen when clicked', () => {
|
it('shows the account creation UI when Add Account is clicked', () => {
|
||||||
const { getByText } = render();
|
const { getByText, getByPlaceholderText } = render();
|
||||||
fireEvent.click(getByText('Add account'));
|
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', () => {
|
it('shows the account import UI when Import Account is clicked', () => {
|
||||||
const { getByText } = render();
|
const { getByText, getByPlaceholderText } = render();
|
||||||
fireEvent.click(getByText('Import account'));
|
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', () => {
|
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>
|
||||||
<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"
|
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=""
|
disabled=""
|
||||||
>
|
>
|
||||||
Import
|
Import
|
@ -5,30 +5,32 @@ import {
|
|||||||
ButtonPrimary,
|
ButtonPrimary,
|
||||||
ButtonSecondary,
|
ButtonSecondary,
|
||||||
BUTTON_SECONDARY_SIZES,
|
BUTTON_SECONDARY_SIZES,
|
||||||
} from '../../../components/component-library';
|
} from '../../component-library';
|
||||||
import Box from '../../../components/ui/box/box';
|
import Box from '../../ui/box/box';
|
||||||
import { DISPLAY } from '../../../helpers/constants/design-system';
|
import { Display } from '../../../helpers/constants/design-system';
|
||||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||||
import * as actions from '../../../store/actions';
|
import * as actions from '../../../store/actions';
|
||||||
|
|
||||||
BottomButtons.propTypes = {
|
BottomButtons.propTypes = {
|
||||||
importAccountFunc: PropTypes.func.isRequired,
|
importAccountFunc: PropTypes.func.isRequired,
|
||||||
isPrimaryDisabled: PropTypes.bool.isRequired,
|
isPrimaryDisabled: PropTypes.bool.isRequired,
|
||||||
|
onActionComplete: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function BottomButtons({
|
export default function BottomButtons({
|
||||||
importAccountFunc,
|
importAccountFunc,
|
||||||
isPrimaryDisabled,
|
isPrimaryDisabled,
|
||||||
|
onActionComplete,
|
||||||
}) {
|
}) {
|
||||||
const t = useI18nContext();
|
const t = useI18nContext();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box display={DISPLAY.FLEX} gap={4}>
|
<Box display={Display.Flex} gap={4}>
|
||||||
<ButtonSecondary
|
<ButtonSecondary
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
dispatch(actions.hideWarning());
|
dispatch(actions.hideWarning());
|
||||||
window.history.back();
|
onActionComplete();
|
||||||
}}
|
}}
|
||||||
size={BUTTON_SECONDARY_SIZES.LG}
|
size={BUTTON_SECONDARY_SIZES.LG}
|
||||||
block
|
block
|
||||||
@ -36,9 +38,19 @@ export default function BottomButtons({
|
|||||||
{t('cancel')}
|
{t('cancel')}
|
||||||
</ButtonSecondary>
|
</ButtonSecondary>
|
||||||
<ButtonPrimary
|
<ButtonPrimary
|
||||||
onClick={importAccountFunc}
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
const result = await importAccountFunc();
|
||||||
|
if (result) {
|
||||||
|
onActionComplete(true);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Take no action
|
||||||
|
}
|
||||||
|
}}
|
||||||
disabled={isPrimaryDisabled}
|
disabled={isPrimaryDisabled}
|
||||||
size={BUTTON_SECONDARY_SIZES.LG}
|
size={BUTTON_SECONDARY_SIZES.LG}
|
||||||
|
data-testid="import-account-confirm-button"
|
||||||
block
|
block
|
||||||
>
|
>
|
||||||
{t('import')}
|
{t('import')}
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import BottomButtons from './bottom-buttons';
|
import BottomButtons from './bottom-buttons';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/CreateAccount/ImportAccount/BottomButtons',
|
title: 'Components/Multichain/BottomButtons',
|
||||||
component: BottomButtons,
|
component: BottomButtons,
|
||||||
argTypes: {
|
argTypes: {
|
||||||
isPrimaryDisabled: {
|
isPrimaryDisabled: {
|
@ -1,4 +1,5 @@
|
|||||||
import React, { useContext, useState } from 'react';
|
import React, { useContext, useState } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
MetaMetricsEventAccountImportType,
|
MetaMetricsEventAccountImportType,
|
||||||
@ -6,54 +7,56 @@ import {
|
|||||||
MetaMetricsEventCategory,
|
MetaMetricsEventCategory,
|
||||||
MetaMetricsEventName,
|
MetaMetricsEventName,
|
||||||
} from '../../../../shared/constants/metametrics';
|
} from '../../../../shared/constants/metametrics';
|
||||||
import { ButtonLink, Label, Text } from '../../../components/component-library';
|
import { ButtonLink, Label, Text } from '../../component-library';
|
||||||
import Box from '../../../components/ui/box';
|
import Box from '../../ui/box';
|
||||||
import Dropdown from '../../../components/ui/dropdown';
|
import Dropdown from '../../ui/dropdown';
|
||||||
import { MetaMetricsContext } from '../../../contexts/metametrics';
|
import { MetaMetricsContext } from '../../../contexts/metametrics';
|
||||||
import {
|
import {
|
||||||
BLOCK_SIZES,
|
BlockSize,
|
||||||
BorderColor,
|
FontWeight,
|
||||||
FONT_WEIGHT,
|
|
||||||
JustifyContent,
|
JustifyContent,
|
||||||
Size,
|
Size,
|
||||||
TextVariant,
|
TextVariant,
|
||||||
} from '../../../helpers/constants/design-system';
|
} from '../../../helpers/constants/design-system';
|
||||||
import ZENDESK_URLS from '../../../helpers/constants/zendesk-url';
|
import ZENDESK_URLS from '../../../helpers/constants/zendesk-url';
|
||||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||||
import { useRouting } from '../../../hooks/useRouting';
|
|
||||||
import * as actions from '../../../store/actions';
|
import * as actions from '../../../store/actions';
|
||||||
|
|
||||||
// Subviews
|
// Subviews
|
||||||
import JsonImportView from './json';
|
import JsonImportView from './json';
|
||||||
import PrivateKeyImportView from './private-key';
|
import PrivateKeyImportView from './private-key';
|
||||||
|
|
||||||
export default function NewAccountImportForm() {
|
export const ImportAccount = ({ onActionComplete }) => {
|
||||||
const t = useI18nContext();
|
const t = useI18nContext();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const trackEvent = useContext(MetaMetricsContext);
|
const trackEvent = useContext(MetaMetricsContext);
|
||||||
const { navigateToMostRecentOverviewPage } = useRouting();
|
|
||||||
|
|
||||||
const menuItems = [t('privateKey'), t('jsonFile')];
|
const menuItems = [t('privateKey'), t('jsonFile')];
|
||||||
|
|
||||||
const [type, setType] = useState(menuItems[0]);
|
const [type, setType] = useState(menuItems[0]);
|
||||||
|
|
||||||
function importAccount(strategy, importArgs) {
|
async function importAccount(strategy, importArgs) {
|
||||||
const loadingMessage = getLoadingMessage(strategy);
|
const loadingMessage = getLoadingMessage(strategy);
|
||||||
|
|
||||||
dispatch(actions.importNewAccount(strategy, importArgs, loadingMessage))
|
try {
|
||||||
.then(({ selectedAddress }) => {
|
const { selectedAddress } = await dispatch(
|
||||||
|
actions.importNewAccount(strategy, importArgs, loadingMessage),
|
||||||
|
);
|
||||||
if (selectedAddress) {
|
if (selectedAddress) {
|
||||||
trackImportEvent(strategy, true);
|
trackImportEvent(strategy, true);
|
||||||
dispatch(actions.hideWarning());
|
dispatch(actions.hideWarning());
|
||||||
navigateToMostRecentOverviewPage();
|
onActionComplete(true);
|
||||||
} else {
|
} else {
|
||||||
dispatch(actions.displayWarning(t('importAccountError')));
|
dispatch(actions.displayWarning(t('importAccountError')));
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
})
|
} catch (error) {
|
||||||
.catch((error) => {
|
|
||||||
trackImportEvent(strategy, error.message);
|
trackImportEvent(strategy, error.message);
|
||||||
translateWarning(error.message);
|
translateWarning(error.message);
|
||||||
});
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function trackImportEvent(strategy, wasSuccessful) {
|
function trackImportEvent(strategy, wasSuccessful) {
|
||||||
@ -79,13 +82,14 @@ export default function NewAccountImportForm() {
|
|||||||
function getLoadingMessage(strategy) {
|
function getLoadingMessage(strategy) {
|
||||||
if (strategy === 'JSON File') {
|
if (strategy === 'JSON File') {
|
||||||
return (
|
return (
|
||||||
<Text width={BLOCK_SIZES.THREE_FOURTHS} fontWeight={FONT_WEIGHT.BOLD}>
|
<>
|
||||||
<br />
|
<Text width={BlockSize.ThreeFourths} fontWeight={FontWeight.Bold}>
|
||||||
{t('importAccountJsonLoading1')}
|
{t('importAccountJsonLoading1')}
|
||||||
<br />
|
</Text>
|
||||||
<br />
|
<Text width={BlockSize.ThreeFourths} fontWeight={FontWeight.Bold}>
|
||||||
{t('importAccountJsonLoading2')}
|
{t('importAccountJsonLoading2')}
|
||||||
</Text>
|
</Text>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,24 +116,8 @@ export default function NewAccountImportForm() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function PrivateKeyOrJson() {
|
|
||||||
switch (type) {
|
|
||||||
case menuItems[0]:
|
|
||||||
return <PrivateKeyImportView importAccountFunc={importAccount} />;
|
|
||||||
case menuItems[1]:
|
|
||||||
default:
|
|
||||||
return <JsonImportView importAccountFunc={importAccount} />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
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}>
|
<Text variant={TextVariant.bodySm} marginTop={2}>
|
||||||
{t('importAccountMsg')}{' '}
|
{t('importAccountMsg')}{' '}
|
||||||
<ButtonLink
|
<ButtonLink
|
||||||
@ -141,11 +129,9 @@ export default function NewAccountImportForm() {
|
|||||||
{t('here')}
|
{t('here')}
|
||||||
</ButtonLink>
|
</ButtonLink>
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
<Box paddingTop={4} paddingBottom={8}>
|
||||||
|
|
||||||
<Box padding={4} paddingBottom={8} paddingLeft={4} paddingRight={4}>
|
|
||||||
<Label
|
<Label
|
||||||
width={BLOCK_SIZES.FULL}
|
width={BlockSize.Full}
|
||||||
marginBottom={4}
|
marginBottom={4}
|
||||||
justifyContent={JustifyContent.spaceBetween}
|
justifyContent={JustifyContent.spaceBetween}
|
||||||
>
|
>
|
||||||
@ -159,8 +145,22 @@ export default function NewAccountImportForm() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Label>
|
</Label>
|
||||||
<PrivateKeyOrJson />
|
{type === menuItems[0] ? (
|
||||||
|
<PrivateKeyImportView
|
||||||
|
importAccountFunc={importAccount}
|
||||||
|
onActionComplete={onActionComplete}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<JsonImportView
|
||||||
|
importAccountFunc={importAccount}
|
||||||
|
onActionComplete={onActionComplete}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Box>
|
</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,
|
||||||
TEXT_FIELD_SIZES,
|
TEXT_FIELD_SIZES,
|
||||||
TEXT_FIELD_TYPES,
|
TEXT_FIELD_TYPES,
|
||||||
} from '../../../components/component-library';
|
} from '../../component-library';
|
||||||
import {
|
import {
|
||||||
Size,
|
Size,
|
||||||
TextVariant,
|
TextVariant,
|
||||||
@ -21,9 +21,13 @@ import BottomButtons from './bottom-buttons';
|
|||||||
|
|
||||||
JsonImportSubview.propTypes = {
|
JsonImportSubview.propTypes = {
|
||||||
importAccountFunc: PropTypes.func.isRequired,
|
importAccountFunc: PropTypes.func.isRequired,
|
||||||
|
onActionComplete: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function JsonImportSubview({ importAccountFunc }) {
|
export default function JsonImportSubview({
|
||||||
|
importAccountFunc,
|
||||||
|
onActionComplete,
|
||||||
|
}) {
|
||||||
const t = useI18nContext();
|
const t = useI18nContext();
|
||||||
const warning = useSelector((state) => state.appState.warning);
|
const warning = useSelector((state) => state.appState.warning);
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
@ -95,6 +99,7 @@ export default function JsonImportSubview({ importAccountFunc }) {
|
|||||||
<BottomButtons
|
<BottomButtons
|
||||||
importAccountFunc={_importAccountFunc}
|
importAccountFunc={_importAccountFunc}
|
||||||
isPrimaryDisabled={isPrimaryDisabled}
|
isPrimaryDisabled={isPrimaryDisabled}
|
||||||
|
onActionComplete={onActionComplete}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import JsonImportSubview from './json';
|
import JsonImportSubview from './json';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/CreateAccount/ImportAccount/JsonImportSubview',
|
title: 'Components/Multichain/JsonImportSubview',
|
||||||
component: JsonImportSubview,
|
component: JsonImportSubview,
|
||||||
};
|
};
|
||||||
|
|
@ -7,11 +7,16 @@ import messages from '../../../../app/_locales/en/messages.json';
|
|||||||
import Json from './json';
|
import Json from './json';
|
||||||
|
|
||||||
const mockImportFunc = jest.fn();
|
const mockImportFunc = jest.fn();
|
||||||
|
const mockOnActionComplete = jest.fn();
|
||||||
|
|
||||||
describe('Json', () => {
|
describe('Json', () => {
|
||||||
const mockStore = configureMockStore()(mockState);
|
const mockStore = configureMockStore()(mockState);
|
||||||
it('should match snapshot', () => {
|
it('should match snapshot', () => {
|
||||||
const { asFragment } = renderWithProvider(
|
const { asFragment } = renderWithProvider(
|
||||||
<Json importAccountFunc={mockImportFunc} />,
|
<Json
|
||||||
|
importAccountFunc={mockImportFunc}
|
||||||
|
onActionComplete={mockOnActionComplete}
|
||||||
|
/>,
|
||||||
mockStore,
|
mockStore,
|
||||||
);
|
);
|
||||||
expect(asFragment()).toMatchSnapshot();
|
expect(asFragment()).toMatchSnapshot();
|
||||||
@ -19,7 +24,10 @@ describe('Json', () => {
|
|||||||
|
|
||||||
it('should render', () => {
|
it('should render', () => {
|
||||||
const { getByText } = renderWithProvider(
|
const { getByText } = renderWithProvider(
|
||||||
<Json importAccountFunc={mockImportFunc} />,
|
<Json
|
||||||
|
importAccountFunc={mockImportFunc}
|
||||||
|
onActionComplete={mockOnActionComplete}
|
||||||
|
/>,
|
||||||
mockStore,
|
mockStore,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -29,7 +37,10 @@ describe('Json', () => {
|
|||||||
|
|
||||||
it('should import file without password', async () => {
|
it('should import file without password', async () => {
|
||||||
const { getByText, getByTestId } = renderWithProvider(
|
const { getByText, getByTestId } = renderWithProvider(
|
||||||
<Json importAccountFunc={mockImportFunc} />,
|
<Json
|
||||||
|
importAccountFunc={mockImportFunc}
|
||||||
|
onActionComplete={mockOnActionComplete}
|
||||||
|
/>,
|
||||||
mockStore,
|
mockStore,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -58,7 +69,10 @@ describe('Json', () => {
|
|||||||
|
|
||||||
it('should import file with password', async () => {
|
it('should import file with password', async () => {
|
||||||
const { getByText, getByTestId, getByPlaceholderText } = renderWithProvider(
|
const { getByText, getByTestId, getByPlaceholderText } = renderWithProvider(
|
||||||
<Json importAccountFunc={mockImportFunc} />,
|
<Json
|
||||||
|
importAccountFunc={mockImportFunc}
|
||||||
|
onActionComplete={mockOnActionComplete}
|
||||||
|
/>,
|
||||||
mockStore,
|
mockStore,
|
||||||
);
|
);
|
||||||
|
|
@ -5,15 +5,19 @@ import {
|
|||||||
FormTextField,
|
FormTextField,
|
||||||
TEXT_FIELD_SIZES,
|
TEXT_FIELD_SIZES,
|
||||||
TEXT_FIELD_TYPES,
|
TEXT_FIELD_TYPES,
|
||||||
} from '../../../components/component-library';
|
} from '../../component-library';
|
||||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||||
import BottomButtons from './bottom-buttons';
|
import BottomButtons from './bottom-buttons';
|
||||||
|
|
||||||
PrivateKeyImportView.propTypes = {
|
PrivateKeyImportView.propTypes = {
|
||||||
importAccountFunc: PropTypes.func.isRequired,
|
importAccountFunc: PropTypes.func.isRequired,
|
||||||
|
onActionComplete: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function PrivateKeyImportView({ importAccountFunc }) {
|
export default function PrivateKeyImportView({
|
||||||
|
importAccountFunc,
|
||||||
|
onActionComplete,
|
||||||
|
}) {
|
||||||
const t = useI18nContext();
|
const t = useI18nContext();
|
||||||
const [privateKey, setPrivateKey] = useState('');
|
const [privateKey, setPrivateKey] = useState('');
|
||||||
|
|
||||||
@ -51,6 +55,7 @@ export default function PrivateKeyImportView({ importAccountFunc }) {
|
|||||||
<BottomButtons
|
<BottomButtons
|
||||||
importAccountFunc={_importAccountFunc}
|
importAccountFunc={_importAccountFunc}
|
||||||
isPrimaryDisabled={privateKey === ''}
|
isPrimaryDisabled={privateKey === ''}
|
||||||
|
onActionComplete={onActionComplete}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
@ -13,3 +13,5 @@ export { NetworkListItem } from './network-list-item';
|
|||||||
export { NetworkListMenu } from './network-list-menu';
|
export { NetworkListMenu } from './network-list-menu';
|
||||||
export { ProductTour } from './product-tour-popover';
|
export { ProductTour } from './product-tour-popover';
|
||||||
export { AccountDetails } from './account-details';
|
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 IMPORT_TOKEN_ROUTE = '/import-token';
|
||||||
const CONFIRM_IMPORT_TOKEN_ROUTE = '/confirm-import-token';
|
const CONFIRM_IMPORT_TOKEN_ROUTE = '/confirm-import-token';
|
||||||
const CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE = '/confirm-add-suggested-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';
|
const CONNECT_HARDWARE_ROUTE = '/new-account/connect';
|
||||||
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
const CUSTODY_ACCOUNT_ROUTE = '/new-account/custody';
|
const CUSTODY_ACCOUNT_ROUTE = '/new-account/custody';
|
||||||
@ -129,8 +127,6 @@ const PATH_NAME_MAP = {
|
|||||||
[IMPORT_TOKEN_ROUTE]: 'Import Token Page',
|
[IMPORT_TOKEN_ROUTE]: 'Import Token Page',
|
||||||
[CONFIRM_IMPORT_TOKEN_ROUTE]: 'Confirm Import Token Page',
|
[CONFIRM_IMPORT_TOKEN_ROUTE]: 'Confirm Import Token Page',
|
||||||
[CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE]: 'Confirm Add Suggested 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',
|
[CONNECT_HARDWARE_ROUTE]: 'Connect Hardware Wallet Page',
|
||||||
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
[INSTITUTIONAL_FEATURES_DONE_ROUTE]: 'Institutional Features Done Page',
|
[INSTITUTIONAL_FEATURES_DONE_ROUTE]: 'Institutional Features Done Page',
|
||||||
@ -194,8 +190,6 @@ export {
|
|||||||
IMPORT_TOKEN_ROUTE,
|
IMPORT_TOKEN_ROUTE,
|
||||||
CONFIRM_IMPORT_TOKEN_ROUTE,
|
CONFIRM_IMPORT_TOKEN_ROUTE,
|
||||||
CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE,
|
CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE,
|
||||||
NEW_ACCOUNT_ROUTE,
|
|
||||||
IMPORT_ACCOUNT_ROUTE,
|
|
||||||
CONNECT_HARDWARE_ROUTE,
|
CONNECT_HARDWARE_ROUTE,
|
||||||
SEND_ROUTE,
|
SEND_ROUTE,
|
||||||
TOKEN_DETAILS,
|
TOKEN_DETAILS,
|
||||||
|
@ -4,8 +4,6 @@ import Box from '../../components/ui/box';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
CONNECT_HARDWARE_ROUTE,
|
CONNECT_HARDWARE_ROUTE,
|
||||||
IMPORT_ACCOUNT_ROUTE,
|
|
||||||
NEW_ACCOUNT_ROUTE,
|
|
||||||
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
CUSTODY_ACCOUNT_ROUTE,
|
CUSTODY_ACCOUNT_ROUTE,
|
||||||
///: END:ONLY_INCLUDE_IN
|
///: END:ONLY_INCLUDE_IN
|
||||||
@ -14,23 +12,11 @@ import {
|
|||||||
import CustodyPage from '../institutional/custody';
|
import CustodyPage from '../institutional/custody';
|
||||||
///: END:ONLY_INCLUDE_IN
|
///: END:ONLY_INCLUDE_IN
|
||||||
import ConnectHardwareForm from './connect-hardware';
|
import ConnectHardwareForm from './connect-hardware';
|
||||||
import NewAccountImportForm from './import-account';
|
|
||||||
import NewAccountCreateForm from './new-account.container';
|
|
||||||
|
|
||||||
export default function CreateAccountPage() {
|
export default function CreateAccountPage() {
|
||||||
return (
|
return (
|
||||||
<Box className="new-account">
|
<Box className="new-account">
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route
|
|
||||||
exact
|
|
||||||
path={NEW_ACCOUNT_ROUTE}
|
|
||||||
component={NewAccountCreateForm}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
exact
|
|
||||||
path={IMPORT_ACCOUNT_ROUTE}
|
|
||||||
component={NewAccountImportForm}
|
|
||||||
/>
|
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
path={CONNECT_HARDWARE_ROUTE}
|
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-sites/index';
|
||||||
@import 'connected-accounts/index';
|
@import 'connected-accounts/index';
|
||||||
@import 'connected-sites/index';
|
@import 'connected-sites/index';
|
||||||
@import 'create-account/index';
|
|
||||||
@import "institutional/connect-custody/index";
|
@import "institutional/connect-custody/index";
|
||||||
@import "institutional/custody/index";
|
@import "institutional/custody/index";
|
||||||
@import "institutional/institutional-entity-done-page/index";
|
@import "institutional/institutional-entity-done-page/index";
|
||||||
|
@ -22,7 +22,6 @@ import ImportTokenPage from '../import-token';
|
|||||||
import AddNftPage from '../add-nft';
|
import AddNftPage from '../add-nft';
|
||||||
import ConfirmImportTokenPage from '../confirm-import-token';
|
import ConfirmImportTokenPage from '../confirm-import-token';
|
||||||
import ConfirmAddSuggestedTokenPage from '../confirm-add-suggested-token';
|
import ConfirmAddSuggestedTokenPage from '../confirm-add-suggested-token';
|
||||||
import CreateAccountPage from '../create-account';
|
|
||||||
import Loading from '../../components/ui/loading-screen';
|
import Loading from '../../components/ui/loading-screen';
|
||||||
import LoadingNetwork from '../../components/app/loading-network-screen';
|
import LoadingNetwork from '../../components/app/loading-network-screen';
|
||||||
import { Modal } from '../../components/app/modals';
|
import { Modal } from '../../components/app/modals';
|
||||||
@ -63,7 +62,6 @@ import {
|
|||||||
CONNECT_ROUTE,
|
CONNECT_ROUTE,
|
||||||
DEFAULT_ROUTE,
|
DEFAULT_ROUTE,
|
||||||
LOCK_ROUTE,
|
LOCK_ROUTE,
|
||||||
NEW_ACCOUNT_ROUTE,
|
|
||||||
RESTORE_VAULT_ROUTE,
|
RESTORE_VAULT_ROUTE,
|
||||||
REVEAL_SEED_ROUTE,
|
REVEAL_SEED_ROUTE,
|
||||||
SEND_ROUTE,
|
SEND_ROUTE,
|
||||||
@ -326,7 +324,6 @@ export default class Routes extends Component {
|
|||||||
{
|
{
|
||||||
///: END:ONLY_INCLUDE_IN
|
///: END:ONLY_INCLUDE_IN
|
||||||
}
|
}
|
||||||
<Authenticated path={NEW_ACCOUNT_ROUTE} component={CreateAccountPage} />
|
|
||||||
<Authenticated
|
<Authenticated
|
||||||
path={`${CONNECT_ROUTE}/:id`}
|
path={`${CONNECT_ROUTE}/:id`}
|
||||||
component={PermissionsConnect}
|
component={PermissionsConnect}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user