1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

UX: Multichain: Global Action Menu (#18158)

This commit is contained in:
David Walsh 2023-03-28 14:59:18 -05:00 committed by GitHub
parent 8cba64b993
commit 870415f111
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 267 additions and 6 deletions

View File

@ -1967,6 +1967,9 @@
"lock": {
"message": "Lock"
},
"lockMetaMask": {
"message": "Lock MetaMask"
},
"lockTimeTooGreat": {
"message": "Lock time is too great"
},
@ -2949,6 +2952,9 @@
"portfolio": {
"message": "Portfolio"
},
"portfolioView": {
"message": "Portfolio view"
},
"preferredLedgerConnectionType": {
"message": "Preferred Ledger connection type",
"description": "A header for a dropdown in Settings > Advanced. Appears above the ledgerConnectionPreferenceDescription message"

View File

@ -12,6 +12,7 @@ import { useI18nContext } from '../../../hooks/useI18nContext';
import { getOriginOfCurrentTab } from '../../../selectors';
import { MetaMetricsContext } from '../../../contexts/metametrics';
import { ButtonIcon, ICON_NAMES } from '../../component-library';
import { GlobalMenu } from '../../multichain/global-menu';
import AccountOptionsMenu from './account-options-menu';
export default function MenuBar() {
@ -53,12 +54,18 @@ export default function MenuBar() {
}}
/>
</span>
{accountOptionsMenuOpen ? (
<AccountOptionsMenu
anchorElement={ref.current}
onClose={() => setAccountOptionsMenuOpen(false)}
/>
) : null}
{accountOptionsMenuOpen &&
(process.env.MULTICHAIN ? (
<GlobalMenu
anchorElement={ref.current}
closeMenu={() => setAccountOptionsMenuOpen(false)}
/>
) : (
<AccountOptionsMenu
anchorElement={ref.current}
onClose={() => setAccountOptionsMenuOpen(false)}
/>
))}
</div>
);
}

View File

@ -0,0 +1,155 @@
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import { useHistory } from 'react-router-dom';
import { useDispatch } from 'react-redux';
import {
CONNECTED_ROUTE,
SETTINGS_ROUTE,
DEFAULT_ROUTE,
} from '../../../helpers/constants/routes';
import { lockMetamask } from '../../../store/actions';
import { useI18nContext } from '../../../hooks/useI18nContext';
import { ICON_NAMES } from '../../component-library';
import { Menu, MenuItem } from '../../ui/menu';
import { getEnvironmentType } from '../../../../app/scripts/lib/util';
import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../shared/constants/app';
import { SUPPORT_LINK } from '../../../../shared/lib/ui-utils';
import { MetaMetricsContext } from '../../../contexts/metametrics';
import {
EVENT_NAMES,
EVENT,
CONTEXT_PROPS,
} from '../../../../shared/constants/metametrics';
export const GlobalMenu = ({ closeMenu, anchorElement }) => {
const t = useI18nContext();
const dispatch = useDispatch();
const trackEvent = useContext(MetaMetricsContext);
const history = useHistory();
return (
<Menu anchorElement={anchorElement} onHide={closeMenu}>
<MenuItem
iconName={ICON_NAMES.CONNECT}
onClick={() => {
history.push(CONNECTED_ROUTE);
trackEvent({
event: EVENT_NAMES.NAV_CONNECTED_SITES_OPENED,
category: EVENT.CATEGORIES.NAVIGATION,
properties: {
location: 'Account Options',
},
});
closeMenu();
}}
>
{t('connectedSites')}
</MenuItem>
<MenuItem
iconName={ICON_NAMES.DIAGRAM}
onClick={() => {
const portfolioUrl = process.env.PORTFOLIO_URL;
global.platform.openTab({
url: `${portfolioUrl}?metamaskEntry=ext`,
});
trackEvent(
{
category: EVENT.CATEGORIES.HOME,
event: EVENT_NAMES.PORTFOLIO_LINK_CLICKED,
properties: {
url: portfolioUrl,
},
},
{
contextPropsIntoEventProperties: [CONTEXT_PROPS.PAGE_TITLE],
},
);
closeMenu();
}}
data-testid="global-menu-portfolio"
>
{t('portfolioView')}
</MenuItem>
{getEnvironmentType() === ENVIRONMENT_TYPE_FULLSCREEN ? null : (
<MenuItem
iconName={ICON_NAMES.EXPAND}
onClick={() => {
global.platform.openExtensionInBrowser();
trackEvent({
event: EVENT_NAMES.APP_WINDOW_EXPANDED,
category: EVENT.CATEGORIES.NAVIGATION,
properties: {
location: 'Account Options',
},
});
closeMenu();
}}
data-testid="global-menu-expand"
>
{t('expandView')}
</MenuItem>
)}
<MenuItem
iconName={ICON_NAMES.MESSAGE_QUESTION}
onClick={() => {
global.platform.openTab({ url: SUPPORT_LINK });
trackEvent(
{
category: EVENT.CATEGORIES.HOME,
event: EVENT_NAMES.SUPPORT_LINK_CLICKED,
properties: {
url: SUPPORT_LINK,
},
},
{
contextPropsIntoEventProperties: [CONTEXT_PROPS.PAGE_TITLE],
},
);
closeMenu();
}}
data-testid="global-menu-support"
>
{t('support')}
</MenuItem>
<MenuItem
iconName={ICON_NAMES.SETTING}
onClick={() => {
history.push(SETTINGS_ROUTE);
trackEvent({
category: EVENT.CATEGORIES.NAVIGATION,
event: EVENT_NAMES.NAV_SETTINGS_OPENED,
properties: {
location: 'Main Menu',
},
});
closeMenu();
}}
>
{t('settings')}
</MenuItem>
<MenuItem
iconName={ICON_NAMES.LOCK}
onClick={() => {
dispatch(lockMetamask());
history.push(DEFAULT_ROUTE);
closeMenu();
}}
data-testid="global-menu-lock"
>
{t('lockMetaMask')}
</MenuItem>
</Menu>
);
};
GlobalMenu.propTypes = {
/**
* The element that the menu should display next to
*/
anchorElement: PropTypes.instanceOf(window.Element),
/**
* Function that closes this menu
*/
closeMenu: PropTypes.func.isRequired,
};

View File

@ -0,0 +1,22 @@
import React from 'react';
import { GlobalMenu } from '.';
export default {
title: 'Components/Multichain/GlobalMenu',
component: GlobalMenu,
argTypes: {
closeMenu: {
action: 'closeMenu',
},
anchorElement: {
control: 'func',
},
},
args: {
closeMenu: () => console.log('Closing menu!'),
anchorElement: null,
},
};
export const DefaultStory = (args) => <GlobalMenu {...args} />;
DefaultStory.storyName = 'Default';

View File

@ -0,0 +1,69 @@
import React from 'react';
import { renderWithProvider, fireEvent, waitFor } from '../../../../test/jest';
import configureStore from '../../../store/store';
import mockState from '../../../../test/data/mock-state.json';
import { SUPPORT_LINK } from '../../../../shared/lib/ui-utils';
import { GlobalMenu } from '.';
const render = () => {
const store = configureStore({
metamask: {
...mockState.metamask,
},
});
return renderWithProvider(
<GlobalMenu anchorElement={document.body} closeMenu={() => undefined} />,
store,
);
};
const mockLockMetaMask = jest.fn();
jest.mock('../../../store/actions', () => ({
lockMetamask: () => mockLockMetaMask,
}));
describe('AccountListItem', () => {
it('locks MetaMask when item is clicked', async () => {
render();
fireEvent.click(document.querySelector('[data-testid="global-menu-lock"]'));
await waitFor(() => {
expect(mockLockMetaMask).toHaveBeenCalled();
});
});
it('opens the portfolio site when item is clicked', async () => {
global.platform = { openTab: jest.fn() };
const { getByTestId } = render();
fireEvent.click(getByTestId('global-menu-portfolio'));
await waitFor(() => {
expect(global.platform.openTab).toHaveBeenCalledWith({
url: `${process.env.PORTFOLIO_URL}?metamaskEntry=ext`,
});
});
});
it('opens the support site when item is clicked', async () => {
global.platform = { openTab: jest.fn() };
const { getByTestId } = render();
fireEvent.click(getByTestId('global-menu-support'));
await waitFor(() => {
expect(global.platform.openTab).toHaveBeenCalledWith({
url: SUPPORT_LINK,
});
});
});
it('expands metamask to tab when item is clicked', async () => {
global.platform = { openExtensionInBrowser: jest.fn() };
render();
fireEvent.click(
document.querySelector('[data-testid="global-menu-expand"]'),
);
await waitFor(() => {
expect(global.platform.openExtensionInBrowser).toHaveBeenCalled();
});
});
});

View File

@ -0,0 +1 @@
export { GlobalMenu } from './global-menu';

View File

@ -2,5 +2,6 @@ export { AccountListItem } from './account-list-item';
export { AccountListItemMenu } from './account-list-item-menu';
export { AccountListMenu } from './account-list-menu';
export { DetectedTokensBanner } from './detected-token-banner';
export { GlobalMenu } from './global-menu';
export { MultichainImportTokenLink } from './multichain-import-token-link';
export { MultichainTokenListItem } from './multichain-token-list-item';