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

feat(874): add tooltip to connect hardwallet and style refactor (#20121)

This commit is contained in:
Danica Shen 2023-07-24 19:00:34 +01:00 committed by GitHub
parent 9b0f8d457b
commit 74a645e957
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 326 additions and 157 deletions

View File

@ -1753,8 +1753,11 @@
"hardwareWallets": {
"message": "Connect a hardware wallet"
},
"hardwareWalletsInfo": {
"message": "Hardware wallet integrations use API calls to external servers, which can see your IP address and the smart contract addresses you interact with."
},
"hardwareWalletsMsg": {
"message": "Select a hardware wallet you'd like to use with MetaMask."
"message": "Select a hardware wallet you would like to use with MetaMask."
},
"here": {
"message": "here",

View File

@ -3,21 +3,35 @@
exports[`ConnectHardwareForm should match snapshot 1`] = `
<div>
<div
class="new-external-account-form"
class="mm-box new-external-account-form mm-box--display-flex mm-box--flex-direction-column mm-box--justify-content-center mm-box--align-items-center"
>
<div
class="hw-connect__header"
class="mm-box hw-connect__header mm-box--display-flex mm-box--flex-direction-column mm-box--align-items-center"
>
<h3
class="hw-connect__header__title"
<div
class="mm-box hw-connect__header__title-wrapper mm-box--margin-top-6 mm-box--display-flex mm-box--flex-direction-row mm-box--justify-content-center mm-box--align-items-center"
>
Connect a hardware wallet
</h3>
<p
class="hw-connect__header__msg"
<h3
class="mm-box mm-text mm-text--heading-md mm-text--font-weight-bold mm-box--margin-left-auto mm-box--color-text-default"
>
Connect a hardware wallet
</h3>
<button
aria-label="Close"
class="box mm-button-icon mm-button-icon--size-sm box--margin-left-auto box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-icon-default box--background-color-transparent box--rounded-lg"
data-testid="hardware-connect-close-btn"
>
<span
class="box mm-icon mm-icon--size-sm box--display-inline-block box--flex-direction-row box--color-inherit"
style="mask-image: url('./images/icons/close.svg');"
/>
</button>
</div>
<h5
class="mm-box mm-text hw-connect__header__msg mm-text--body-md mm-box--margin-top-5 mm-box--margin-bottom-3 mm-box--color-text-default"
>
Select a hardware wallet you'd like to use with MetaMask.
</p>
Select a hardware wallet you would like to use with MetaMask.
</h5>
</div>
<div
class="hw-connect__btn-wrapper"
@ -98,13 +112,16 @@ exports[`ConnectHardwareForm should match snapshot 1`] = `
</button>
</div>
<button
class="button btn--rounded btn-primary btn--large hw-connect__connect-btn"
class="mm-box mm-text mm-button-base mm-button-base--size-lg mm-button-base--disabled hw-connect__connect-btn mm-button-primary mm-button-primary--disabled mm-text--body-md-medium mm-box--padding-right-4 mm-box--padding-left-4 mm-box--display-inline-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-primary-inverse mm-box--background-color-primary-default mm-box--rounded-pill"
disabled=""
role="button"
tabindex="0"
>
Continue
</button>
<h6
class="mm-box mm-text new-external-account-form footer mm-text--body-sm mm-text--text-align-center mm-box--margin-top-4 mm-box--color-text-alternative"
>
Hardware wallet integrations use API calls to external servers, which can see your IP address and the smart contract addresses you interact with.
</h6>
</div>
</div>
`;

View File

@ -263,7 +263,7 @@ AccountList.propTypes = {
onUnlockAccounts: PropTypes.func,
onCancel: PropTypes.func,
onAccountRestriction: PropTypes.func,
hdPaths: PropTypes.array.isRequired,
hdPaths: PropTypes.object.isRequired,
};
AccountList.contextTypes = {

View File

@ -14,7 +14,7 @@ const render = () => {
});
const props = {
selectedPath: TREZOR_HD_PATHS[0].path,
selectedPath: TREZOR_HD_PATHS[0].value,
device: 'trezor',
accounts: [
{

View File

@ -371,6 +371,7 @@ class ConnectHardwareForm extends Component {
connectToHardwareWallet={this.connectToHardwareWallet}
browserSupported={this.state.browserSupported}
ledgerTransportType={this.props.ledgerTransportType}
onCancel={this.onCancel}
/>
);
}

View File

@ -1,5 +1,4 @@
.hw-tutorial {
width: 375px;
overflow: visible;
display: block;
padding: 15px 30px;
@ -8,21 +7,8 @@
.hw-connect {
width: 100%;
&__header {
&__title {
@include H3;
margin-top: 5px;
margin-bottom: 15px;
}
&__msg {
@include H6;
color: var(--color-text-muted);
margin-top: 10px;
margin-bottom: 20px;
}
&__header__title-wrapper {
width: 100%;
}
&__QR-subtitle {
@ -70,8 +56,8 @@
&__btn {
background: var(--color-background-alternative);
border: 1px solid var(--color-border-muted);
height: 100px;
width: 150px;
height: 148px;
width: 199px;
flex: 1;
display: flex;
align-items: center;
@ -81,13 +67,13 @@
margin-right: 15px;
&__img {
width: 95px;
width: 136px;
}
}
&__btn.selected {
border-color: var(--color-primary-default);
width: 149px;
width: 199px;
}
&__btn:first-child {
@ -118,13 +104,6 @@
}
}
&__title {
@include H4;
padding-top: 10px;
font-weight: 400;
}
&__unlock-title {
@include H3;
@ -134,12 +113,6 @@
}
&__msg {
@include H6;
color: var(--color-text-muted);
margin-top: 10px;
margin-bottom: 15px;
&-link {
@include H6;
@ -270,10 +243,9 @@
}
.new-external-account-form {
display: flex;
flex-flow: column;
align-items: center;
padding: 15px 30px 0;
&.footer {
width: 520px;
}
&.unsupported-browser {
height: 210px;

View File

@ -28,19 +28,24 @@ jest.mock('../../../selectors', () => ({
},
}));
const MOCK_RECENT_PAGE = '/home';
jest.mock('../../../ducks/history/history', () => ({
getMostRecentOverviewPage: () => '',
getMostRecentOverviewPage: jest
.fn()
.mockImplementation(() => MOCK_RECENT_PAGE),
}));
const mockTrackEvent = jest.fn();
const mockHistoryPush = jest.fn();
const mockProps = {
forgetDevice: () => jest.fn(),
showAlert: () => jest.fn(),
hideAlert: () => jest.fn(),
unlockHardwareWalletAccount: () => jest.fn(),
setHardwareWalletDefaultHdPath: () => jest.fn(),
history: {},
history: {
push: mockHistoryPush,
},
defaultHdPath: "m/44'/60'/0'/0",
mostRecentOverviewPage: '',
trackEvent: () => mockTrackEvent,
@ -83,6 +88,7 @@ const mockState = {
describe('ConnectHardwareForm', () => {
const mockStore = configureMockStore([thunk])(mockState);
it('should match snapshot', () => {
const { container } = renderWithProvider(
<ConnectHardwareForm {...mockProps} />,
@ -92,6 +98,17 @@ describe('ConnectHardwareForm', () => {
expect(container).toMatchSnapshot();
});
it('should close the form when close button is clicked', () => {
const { getByTestId } = renderWithProvider(
<ConnectHardwareForm {...mockProps} />,
mockStore,
);
const closeButton = getByTestId('hardware-connect-close-btn');
fireEvent.click(closeButton);
expect(mockHistoryPush).toHaveBeenCalledTimes(1);
expect(mockHistoryPush).toHaveBeenCalledWith(MOCK_RECENT_PAGE);
});
describe('U2F Error', () => {
it('should render a U2F error', async () => {
mockConnectHardware.mockRejectedValue(new Error('U2F Error'));

View File

@ -1,7 +1,16 @@
import classnames from 'classnames';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Button from '../../../components/ui/button';
import {
Text,
Box,
IconName,
ButtonIconSize,
ButtonIcon,
Button,
BUTTON_SIZES,
BUTTON_VARIANT,
} from '../../../components/component-library';
import LogoLedger from '../../../components/ui/logo/logo-ledger';
import LogoQRBased from '../../../components/ui/logo/logo-qr-based';
import LogoTrezor from '../../../components/ui/logo/logo-trezor';
@ -17,6 +26,16 @@ import ZENDESK_URLS from '../../../helpers/constants/zendesk-url';
import { MetaMetricsEventCategory } from '../../../../shared/constants/metametrics';
import { isManifestV3 } from '../../../../shared/modules/mv3.utils';
import { openWindow } from '../../../helpers/utils/window';
import {
AlignItems,
Display,
FlexDirection,
FontWeight,
JustifyContent,
TextAlign,
TextColor,
TextVariant,
} from '../../../helpers/constants/design-system';
export default class SelectHardware extends Component {
static contextTypes = {
@ -25,6 +44,7 @@ export default class SelectHardware extends Component {
};
static propTypes = {
onCancel: PropTypes.func.isRequired,
connectToHardwareWallet: PropTypes.func.isRequired,
browserSupported: PropTypes.bool.isRequired,
ledgerTransportType: PropTypes.oneOf(Object.values(LedgerTransportTypes)),
@ -127,8 +147,8 @@ export default class SelectHardware extends Component {
renderContinueButton() {
return (
<Button
type="primary"
large
variant={BUTTON_VARIANT.PRIMARY}
size={BUTTON_SIZES.LG}
className="hw-connect__connect-btn"
onClick={this.connect}
disabled={!this.state.selectedDevice}
@ -138,20 +158,59 @@ export default class SelectHardware extends Component {
);
}
renderFooter() {
return (
<Text
color={TextColor.textAlternative}
variant={TextVariant.bodySm}
textAlign={TextAlign.Center}
as="h6"
marginTop={4}
className="new-external-account-form footer"
>
{this.context.t('hardwareWalletsInfo')}
</Text>
);
}
renderUnsupportedBrowser() {
return (
<div className="new-external-account-form unsupported-browser">
<div className="hw-connect">
<h3 className="hw-connect__title">
<Box
display={Display.Flex}
flexDirection={FlexDirection.Column}
justifyContent={JustifyContent.center}
alignItems={AlignItems.center}
className="new-external-account-form unsupported-browser"
>
<Box
className="hw-connect"
display={Display.Flex}
flexDirection={FlexDirection.Column}
alignItems={AlignItems.center}
>
<Text
className="hw-connect__title"
variant={TextVariant.headingMd}
as="h3"
fontWeight={FontWeight.Bold}
marginTop={6}
marginBottom={3}
>
{this.context.t('browserNotSupported')}
</h3>
<p className="hw-connect__msg">
</Text>
<Text
className="hw-connect__msg"
variant={TextVariant.bodyMd}
as="h5"
marginTop={3}
marginBottom={5}
>
{this.context.t('chromeRequiredForHardwareWallets')}
</p>
</div>
</Text>
</Box>
<Button
type="primary"
large
variant={BUTTON_VARIANT.PRIMARY}
size={BUTTON_SIZES.LG}
onClick={() =>
global.platform.openTab({
url: 'https://google.com/chrome',
@ -160,24 +219,58 @@ export default class SelectHardware extends Component {
>
{this.context.t('downloadGoogleChrome')}
</Button>
</div>
</Box>
);
}
renderHeader() {
return (
<div className="hw-connect__header">
<h3 className="hw-connect__header__title">
{this.context.t('hardwareWallets')}
</h3>
<p className="hw-connect__header__msg">
<Box
className="hw-connect__header"
display={Display.Flex}
flexDirection={FlexDirection.Column}
alignItems={AlignItems.center}
>
<Box
display={Display.Flex}
flexDirection={FlexDirection.Row}
justifyContent={JustifyContent.center}
alignItems={AlignItems.center}
className="hw-connect__header__title-wrapper"
marginTop={6}
>
<Text
variant={TextVariant.headingMd}
as="h3"
fontWeight={FontWeight.Bold}
marginLeft="auto"
>
{this.context.t('hardwareWallets')}
</Text>
<ButtonIcon
iconName={IconName.Close}
ariaLabel={this.context.t('close')}
onClick={this.props.onCancel}
size={ButtonIconSize.Sm}
marginLeft="auto"
data-testid="hardware-connect-close-btn"
/>
</Box>
<Text
className="hw-connect__header__msg"
variant={TextVariant.bodyMd}
as="h5"
marginTop={5}
marginBottom={3}
>
{this.context.t('hardwareWalletsMsg')}
</p>
</div>
</Text>
</Box>
);
}
renderTutorialsteps() {
renderTutorialSteps() {
switch (this.state.selectedDevice) {
case HardwareDeviceNames.ledger:
return this.renderLedgerTutorialSteps();
@ -233,13 +326,24 @@ export default class SelectHardware extends Component {
return (
<div className="hw-tutorial">
{steps.map((step, index) => (
<div className="hw-connect" key={index}>
<Box
display={Display.Flex}
flexDirection={FlexDirection.Column}
alignItems={AlignItems.center}
className="hw-connect"
key={index}
>
<h3 className="hw-connect__title">{step.title}</h3>
{step.renderButtons ? (
<>
<Box
display={Display.Flex}
flexDirection={FlexDirection.Row}
justifyContent={JustifyContent.center}
marginBottom={2}
>
<Button
className="hw-connect__external-btn-first"
type="secondary"
variant={BUTTON_VARIANT.SECONDARY}
onClick={() => {
this.context.trackEvent({
category: MetaMetricsEventCategory.Navigation,
@ -252,7 +356,7 @@ export default class SelectHardware extends Component {
</Button>
<Button
className="hw-connect__external-btn"
type="secondary"
variant={BUTTON_VARIANT.SECONDARY}
onClick={() => {
this.context.trackEvent({
category: MetaMetricsEventCategory.Navigation,
@ -263,7 +367,7 @@ export default class SelectHardware extends Component {
>
{this.context.t('tutorial')}
</Button>
</>
</Box>
) : null}
<p className="hw-connect__msg">{step.message}</p>
{step.asset && (
@ -274,7 +378,7 @@ export default class SelectHardware extends Component {
alt=""
/>
)}
</div>
</Box>
))}
</div>
);
@ -303,34 +407,47 @@ export default class SelectHardware extends Component {
return (
<div className="hw-tutorial">
{steps.map((step, index) => (
<div className="hw-connect" key={index}>
<Box
display={Display.Flex}
flexDirection={FlexDirection.Column}
alignItems={AlignItems.center}
className="hw-connect"
key={index}
>
<h3 className="hw-connect__title">{step.title}</h3>
<Button
className="hw-connect__external-btn-first"
type="secondary"
onClick={() => {
this.context.trackEvent({
category: MetaMetricsEventCategory.Navigation,
event: 'Clicked GridPlus Buy Now',
});
openWindow(HardwareAffiliateLinks.gridplus);
}}
<Box
display={Display.Flex}
flexDirection={FlexDirection.Row}
justifyContent={JustifyContent.center}
marginBottom={2}
>
{this.context.t('buyNow')}
</Button>
<Button
className="hw-connect__external-btn"
type="secondary"
onClick={() => {
this.context.trackEvent({
category: MetaMetricsEventCategory.Navigation,
event: 'Clicked GidPlus Tutorial',
});
openWindow(HardwareAffiliateTutorialLinks.gridplus);
}}
>
{this.context.t('tutorial')}
</Button>
<Button
className="hw-connect__external-btn-first"
variant={BUTTON_VARIANT.SECONDARY}
onClick={() => {
this.context.trackEvent({
category: MetaMetricsEventCategory.Navigation,
event: 'Clicked GridPlus Buy Now',
});
openWindow(HardwareAffiliateLinks.gridplus);
}}
>
{this.context.t('buyNow')}
</Button>
<Button
className="hw-connect__external-btn"
variant={BUTTON_VARIANT.SECONDARY}
onClick={() => {
this.context.trackEvent({
category: MetaMetricsEventCategory.Navigation,
event: 'Clicked GidPlus Tutorial',
});
openWindow(HardwareAffiliateTutorialLinks.gridplus);
}}
>
{this.context.t('tutorial')}
</Button>
</Box>
<p className="hw-connect__msg">{step.message}</p>
{step.asset && (
<img
@ -340,7 +457,7 @@ export default class SelectHardware extends Component {
alt=""
/>
)}
</div>
</Box>
))}
</div>
);
@ -369,34 +486,48 @@ export default class SelectHardware extends Component {
return (
<div className="hw-tutorial">
{steps.map((step, index) => (
<div className="hw-connect" key={index}>
<Box
display={Display.Flex}
flexDirection={FlexDirection.Column}
alignItems={AlignItems.center}
className="hw-connect"
key={index}
>
<h3 className="hw-connect__title">{step.title}</h3>
<Button
className="hw-connect__external-btn-first"
type="secondary"
onClick={() => {
this.context.trackEvent({
category: MetaMetricsEventCategory.Navigation,
event: 'Clicked Trezor Buy Now',
});
openWindow(HardwareAffiliateLinks.trezor);
}}
<Box
display={Display.Flex}
flexDirection={FlexDirection.Row}
justifyContent={JustifyContent.center}
marginBottom={2}
>
{this.context.t('buyNow')}
</Button>
<Button
className="hw-connect__external-btn"
type="secondary"
onClick={() => {
this.context.trackEvent({
category: MetaMetricsEventCategory.Navigation,
event: 'Clicked Trezor Tutorial',
});
openWindow(HardwareAffiliateTutorialLinks.trezor);
}}
>
{this.context.t('tutorial')}
</Button>
<Button
className="hw-connect__external-btn-first"
variant={BUTTON_VARIANT.SECONDARY}
onClick={() => {
this.context.trackEvent({
category: MetaMetricsEventCategory.Navigation,
event: 'Clicked Trezor Buy Now',
});
openWindow(HardwareAffiliateLinks.trezor);
}}
>
{this.context.t('buyNow')}
</Button>
<Button
className="hw-connect__external-btn"
variant={BUTTON_VARIANT.SECONDARY}
onClick={() => {
this.context.trackEvent({
category: MetaMetricsEventCategory.Navigation,
event: 'Clicked Trezor Tutorial',
});
openWindow(HardwareAffiliateTutorialLinks.trezor);
}}
>
{this.context.t('tutorial')}
</Button>
</Box>
<p className="hw-connect__msg">{step.message}</p>
{step.asset && (
<img
@ -406,7 +537,7 @@ export default class SelectHardware extends Component {
alt=""
/>
)}
</div>
</Box>
))}
</div>
);
@ -427,7 +558,7 @@ export default class SelectHardware extends Component {
</p>
<Button
className="hw-connect__external-btn-first"
type="secondary"
variant={BUTTON_VARIANT.SECONDARY}
onClick={() => {
this.context.trackEvent({
category: MetaMetricsEventCategory.Navigation,
@ -440,7 +571,7 @@ export default class SelectHardware extends Component {
</Button>
<Button
className="hw-connect__external-btn"
type="secondary"
variant={BUTTON_VARIANT.SECONDARY}
onClick={() => {
this.context.trackEvent({
category: MetaMetricsEventCategory.Navigation,
@ -462,7 +593,7 @@ export default class SelectHardware extends Component {
</p>
<Button
className="hw-connect__external-btn-first"
type="secondary"
variant={BUTTON_VARIANT.SECONDARY}
onClick={() => {
this.context.trackEvent({
category: MetaMetricsEventCategory.Navigation,
@ -475,7 +606,7 @@ export default class SelectHardware extends Component {
</Button>
<Button
className="hw-connect__external-btn"
type="secondary"
variant={BUTTON_VARIANT.SECONDARY}
onClick={() => {
this.context.trackEvent({
category: MetaMetricsEventCategory.Navigation,
@ -497,7 +628,7 @@ export default class SelectHardware extends Component {
</p>
<Button
className="hw-connect__external-btn-first"
type="secondary"
variant={BUTTON_VARIANT.SECONDARY}
onClick={() => {
this.context.trackEvent({
category: MetaMetricsEventCategory.Navigation,
@ -510,7 +641,7 @@ export default class SelectHardware extends Component {
</Button>
<Button
className="hw-connect__external-btn"
type="secondary"
variant={BUTTON_VARIANT.SECONDARY}
onClick={() => {
this.context.trackEvent({
category: MetaMetricsEventCategory.Navigation,
@ -530,7 +661,7 @@ export default class SelectHardware extends Component {
<p className="hw-connect__QR-subtitle">{this.context.t('dcent')}</p>
<Button
className="hw-connect__external-btn-first"
type="secondary"
variant={BUTTON_VARIANT.SECONDARY}
onClick={() => {
this.context.trackEvent({
category: MetaMetricsEventCategory.Navigation,
@ -543,7 +674,7 @@ export default class SelectHardware extends Component {
</Button>
<Button
className="hw-connect__external-btn"
type="secondary"
variant={BUTTON_VARIANT.SECONDARY}
onClick={() => {
this.context.trackEvent({
category: MetaMetricsEventCategory.Navigation,
@ -587,12 +718,19 @@ export default class SelectHardware extends Component {
renderConnectScreen() {
return (
<div className="new-external-account-form">
<Box
className="new-external-account-form"
display={Display.Flex}
flexDirection={FlexDirection.Column}
alignItems={AlignItems.center}
justifyContent={JustifyContent.center}
>
{this.renderHeader()}
{this.renderButtons()}
{this.state.selectedDevice ? this.renderTutorialsteps() : null}
{this.state.selectedDevice ? this.renderTutorialSteps() : null}
{this.renderContinueButton()}
</div>
{this.renderFooter()}
</Box>
);
}

View File

@ -10,6 +10,7 @@ export default {
export const DefaultStory = () => {
return (
<SelectHardware
onCancel={() => null}
browserSupported
connectToHardwareWallet={(selectedDevice) =>
action(`Continue connect to ${selectedDevice}`)()
@ -24,6 +25,7 @@ DefaultStory.storyName = 'Default';
export const BrowserNotSupported = () => {
return (
<SelectHardware
onCancel={() => null}
browserSupported={false}
connectToHardwareWallet={() => undefined}
ledgerTransportType={LedgerTransportTypes.live}

View File

@ -1,12 +1,12 @@
import React from 'react';
import { Route, Switch } from 'react-router-dom';
import Box from '../../components/ui/box';
import { Box } from '../../components/component-library';
import { CONNECT_HARDWARE_ROUTE } from '../../helpers/constants/routes';
import ConnectHardwareForm from './connect-hardware';
export default function CreateAccountPage() {
return (
<Box className="new-account">
<Box className="new-account-wrapper">
<Switch>
<Route
exact

View File

@ -0,0 +1,15 @@
.new-account-wrapper {
background-color: var(--color-background-default);
@media screen and (min-width: $break-large) {
width: 85vw;
}
@media screen and (min-width: 768px) {
width: 80vw;
}
@media screen and (min-width: 1280px) {
width: 62vw;
}
}

View File

@ -1,6 +1,4 @@
/** Please import your files in alphabetical order **/
@import 'keyring-snaps/index';
@import 'import-token/index';
@import 'asset/asset';
@import 'confirm-import-token/index';
@import 'confirm-add-suggested-token/index';
@ -12,17 +10,23 @@
@import 'connected-sites/index';
@import 'connected-accounts/index';
@import 'connected-sites/index';
@import 'create-account/create-account';
@import 'create-account/connect-hardware/index';
@import 'desktop-pairing/index';
@import 'error/index';
@import 'home/index';
@import 'import-token/index';
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
@import "institutional/connect-custody/index";
@import "institutional/institutional-entity-done-page/index";
@import "institutional/confirm-add-custodian-token/index";
@import "institutional/interactive-replacement-token-page/index";
///: END:ONLY_INCLUDE_IN
@import 'error/index';
@import 'send/gas-display/index';
@import 'home/index';
@import 'keychains/index';
@import 'keyring-snaps/index';
@import 'notifications/index';
@import 'onboarding-flow/index';
@import 'permissions-connect/index';
@import 'send/send';
@import 'settings/index';
@ -30,6 +34,6 @@
@import 'token-allowance/index';
@import 'token-details/index';
@import 'unlock-page/index';
@import 'onboarding-flow/index';
@import 'notifications/index';
@import 'desktop-pairing/index';