Skip to content

Commit 3b2907b

Browse files
committed
Implement hardware connectivity confirmation screens
1 parent 0e4005b commit 3b2907b

File tree

10 files changed

+289
-29
lines changed

10 files changed

+289
-29
lines changed

app/_locales/en/messages.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1107,6 +1107,30 @@
11071107
"hardwareWalletConnected": {
11081108
"message": "Hardware wallet connected"
11091109
},
1110+
"hardwareWalletConnectivityAdvancedMethodSelect": {
1111+
"message": "Select Connection Method"
1112+
},
1113+
"hardwareWalletConnectivityAdvancedMethodSelectDescription": {
1114+
"message": "Pick your prefered connection method. MetaMask will use the best available."
1115+
},
1116+
"hardwareWalletConnectivityAdvancedPathDescription": {
1117+
"message": "Change the Ledger path to view other accounts. Try switching to “Legacy (MEW / My Crypto)”"
1118+
},
1119+
"hardwareWalletConnectivityConnected": {
1120+
"message": "$1 is ready",
1121+
"description": "$1 represents the name of the device"
1122+
},
1123+
"hardwareWalletConnectivityHelperHeading": {
1124+
"message": "Connect your $1",
1125+
"description": "$1 represents the name of the device"
1126+
},
1127+
"hardwareWalletConnectivityNotConnected": {
1128+
"message": "$1 is not connected.",
1129+
"description": "$1 represents the name of the device"
1130+
},
1131+
"hardwareWalletConnectivityNotConnectedConversion": {
1132+
"message": "Click to connect"
1133+
},
11101134
"hardwareWalletLegacyDescription": {
11111135
"message": "(legacy)",
11121136
"description": "Text representing the MEW path"

ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ export default class ConfirmPageContainerContent extends Component {
3535
disabled: PropTypes.bool,
3636
unapprovedTxCount: PropTypes.number,
3737
rejectNText: PropTypes.string,
38-
hideTitle: PropTypes.boolean,
38+
hideTitle: PropTypes.bool,
39+
showingHardwareConnectionContents: PropTypes.bool,
3940
};
4041

4142
renderContent() {
@@ -91,6 +92,7 @@ export default class ConfirmPageContainerContent extends Component {
9192
origin,
9293
ethGasPriceWarning,
9394
hideTitle,
95+
showingHardwareConnectionContents,
9496
} = this.props;
9597

9698
return (
@@ -99,38 +101,42 @@ export default class ConfirmPageContainerContent extends Component {
99101
{ethGasPriceWarning && (
100102
<ConfirmPageContainerWarning warning={ethGasPriceWarning} />
101103
)}
102-
<ConfirmPageContainerSummary
103-
className={classnames({
104-
'confirm-page-container-summary--border':
105-
!detailsComponent || !dataComponent,
106-
})}
107-
action={action}
108-
title={title}
109-
titleComponent={titleComponent}
110-
subtitleComponent={subtitleComponent}
111-
hideSubtitle={hideSubtitle}
112-
identiconAddress={identiconAddress}
113-
nonce={nonce}
114-
origin={origin}
115-
hideTitle={hideTitle}
116-
/>
104+
{showingHardwareConnectionContents ? null : (
105+
<ConfirmPageContainerSummary
106+
className={classnames({
107+
'confirm-page-container-summary--border':
108+
!detailsComponent || !dataComponent,
109+
})}
110+
action={action}
111+
title={title}
112+
titleComponent={titleComponent}
113+
subtitleComponent={subtitleComponent}
114+
hideSubtitle={hideSubtitle}
115+
identiconAddress={identiconAddress}
116+
nonce={nonce}
117+
origin={origin}
118+
hideTitle={hideTitle}
119+
/>
120+
)}
117121
{this.renderContent()}
118122
{(errorKey || errorMessage) && (
119123
<div className="confirm-page-container-content__error-container">
120124
<ErrorMessage errorMessage={errorMessage} errorKey={errorKey} />
121125
</div>
122126
)}
123-
<PageContainerFooter
124-
onCancel={onCancel}
125-
cancelText={cancelText}
126-
onSubmit={onSubmit}
127-
submitText={submitText}
128-
disabled={disabled}
129-
>
130-
{unapprovedTxCount > 1 ? (
131-
<a onClick={onCancelAll}>{rejectNText}</a>
132-
) : null}
133-
</PageContainerFooter>
127+
{showingHardwareConnectionContents ? null : (
128+
<PageContainerFooter
129+
onCancel={onCancel}
130+
cancelText={cancelText}
131+
onSubmit={onSubmit}
132+
submitText={submitText}
133+
disabled={disabled}
134+
>
135+
{unapprovedTxCount > 1 ? (
136+
<a onClick={onCancelAll}>{rejectNText}</a>
137+
) : null}
138+
</PageContainerFooter>
139+
)}
134140
</div>
135141
);
136142
}

ui/components/app/confirm-page-container/confirm-page-container.component.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { GasFeeContextProvider } from '../../../contexts/gasFee';
88
import ErrorMessage from '../../ui/error-message';
99
import { TRANSACTION_TYPES } from '../../../../shared/constants/transaction';
1010
import Dialog from '../../ui/dialog';
11+
import HardwareConnectivityPopover from '../../../pages/confirm-transaction-base/hardware-connectivity/hardware-connectivity-popover';
1112
import {
1213
ConfirmPageContainerHeader,
1314
ConfirmPageContainerContent,
@@ -72,6 +73,9 @@ export default class ConfirmPageContainer extends Component {
7273
showAddToAddressBookModal: PropTypes.func,
7374
contact: PropTypes.object,
7475
isOwnedAccount: PropTypes.bool,
76+
// Hardware
77+
showingHardwareConnectionContents: PropTypes.bool,
78+
showingHardwareConnectionAdvancedPopover: PropTypes.bool,
7579
};
7680

7781
render() {
@@ -122,6 +126,8 @@ export default class ConfirmPageContainer extends Component {
122126
showAddToAddressBookModal,
123127
contact = {},
124128
isOwnedAccount,
129+
showingHardwareConnectionContents,
130+
showingHardwareConnectionAdvancedPopover,
125131
} = this.props;
126132

127133
const showAddToAddressDialog =
@@ -135,6 +141,11 @@ export default class ConfirmPageContainer extends Component {
135141
currentTransaction.type === TRANSACTION_TYPES.DEPLOY_CONTRACT) &&
136142
currentTransaction.txParams?.value === '0x0';
137143

144+
/* ToDo: We need this method from elevated component */
145+
const onHardwareConnectivityClose = () => {
146+
console.log('Closing hardware connectivity');
147+
};
148+
138149
return (
139150
<GasFeeContextProvider transaction={currentTransaction}>
140151
<div className="page-container">
@@ -203,6 +214,9 @@ export default class ConfirmPageContainer extends Component {
203214
origin={origin}
204215
ethGasPriceWarning={ethGasPriceWarning}
205216
hideTitle={hideTitle}
217+
showingHardwareConnectionContents={
218+
showingHardwareConnectionContents
219+
}
206220
/>
207221
)}
208222
{shouldDisplayWarning && (
@@ -232,6 +246,11 @@ export default class ConfirmPageContainer extends Component {
232246
transaction={currentTransaction}
233247
/>
234248
)}
249+
{showingHardwareConnectionAdvancedPopover ? (
250+
<HardwareConnectivityPopover
251+
onClose={onHardwareConnectivityClose}
252+
/>
253+
) : null}
235254
</div>
236255
</GasFeeContextProvider>
237256
);

ui/pages/confirm-transaction-base/confirm-transaction-base.component.js

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,11 @@ import {
5050

5151
import Typography from '../../components/ui/typography/typography';
5252
import { MIN_GAS_LIMIT_DEC } from '../send/send.constants';
53+
import HardwareConnectivityMessage from './hardware-connectivity/hardware-connectivity-message';
5354

5455
import GasDetailsItem from './gas-details-item';
5556
import LowPriorityMessage from './low-priority-message';
57+
import HardwareConnectivityContent from './hardware-connectivity/hardware-connectivity-content';
5658

5759
// eslint-disable-next-line prefer-destructuring
5860
const EIP_1559_V2 = process.env.EIP_1559_V2;
@@ -143,6 +145,8 @@ export default class ConfirmTransactionBase extends Component {
143145
submitWarning: '',
144146
ethGasPriceWarning: '',
145147
editingGas: false,
148+
showingHardwareConnectionContents: /* false */ true,
149+
showingHardwareConnectionAdvancedPopover: /* false */ true,
146150
};
147151

148152
componentDidUpdate(prevProps) {
@@ -319,6 +323,7 @@ export default class ConfirmTransactionBase extends Component {
319323
isMultiLayerFeeNetwork,
320324
nativeCurrency,
321325
} = this.props;
326+
const { showingHardwareConnectionContents } = this.state;
322327
const { t } = this.context;
323328

324329
const renderTotalMaxAmount = () => {
@@ -411,6 +416,20 @@ export default class ConfirmTransactionBase extends Component {
411416
</div>
412417
) : null;
413418

419+
if (showingHardwareConnectionContents) {
420+
return (
421+
<div className="confirm-page-container-content__details">
422+
<HardwareConnectivityContent
423+
deviceName="Ledger"
424+
onConnectClick={() => {}}
425+
onAdvancedClick={() => {
426+
this.setState({ showingHardwareConnectionAdvancedPopover: true });
427+
}}
428+
/>
429+
</div>
430+
);
431+
}
432+
414433
return (
415434
<div className="confirm-page-container-content__details">
416435
{EIP_1559_V2 && <LowPriorityMessage />}
@@ -581,6 +600,16 @@ export default class ConfirmTransactionBase extends Component {
581600
showDataInstruction={Boolean(txData.txParams?.data)}
582601
/>
583602
) : null}
603+
{
604+
/* showLedgerSteps */ true ? (
605+
<HardwareConnectivityMessage
606+
onClick={() => {
607+
console.log('Opening the modal!');
608+
this.setState({ showingHardwareConnectionContents: true });
609+
}}
610+
/>
611+
) : null
612+
}
584613
</div>
585614
);
586615
}
@@ -765,9 +794,10 @@ export default class ConfirmTransactionBase extends Component {
765794

766795
renderTitleComponent() {
767796
const { title, hexTransactionAmount } = this.props;
797+
const { showingHardwareConnectionContents } = this.state;
768798

769799
// Title string passed in by props takes priority
770-
if (title) {
800+
if (title || showingHardwareConnectionContents) {
771801
return null;
772802
}
773803

@@ -784,6 +814,11 @@ export default class ConfirmTransactionBase extends Component {
784814

785815
renderSubtitleComponent() {
786816
const { subtitleComponent, hexTransactionAmount } = this.props;
817+
const { showingHardwareConnectionContents } = this.state;
818+
819+
if (showingHardwareConnectionContents) {
820+
return null;
821+
}
787822

788823
return (
789824
subtitleComponent || (
@@ -918,6 +953,8 @@ export default class ConfirmTransactionBase extends Component {
918953
submitWarning,
919954
ethGasPriceWarning,
920955
editingGas,
956+
showingHardwareConnectionContents,
957+
showingHardwareConnectionAdvancedPopover,
921958
} = this.state;
922959

923960
const { name } = methodData;
@@ -990,6 +1027,10 @@ export default class ConfirmTransactionBase extends Component {
9901027
origin={txData.origin}
9911028
ethGasPriceWarning={ethGasPriceWarning}
9921029
editingGas={editingGas}
1030+
showingHardwareConnectionContents={showingHardwareConnectionContents}
1031+
showingHardwareConnectionAdvancedPopover={
1032+
showingHardwareConnectionAdvancedPopover
1033+
}
9931034
handleCloseEditGas={() => this.handleCloseEditGas()}
9941035
currentTransaction={txData}
9951036
/>

ui/pages/confirm-transaction-base/confirm-transaction-base.container.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ const mapStateToProps = (state, ownProps) => {
227227
maxPriorityFeePerGas: gasEstimationObject.maxPriorityFeePerGas,
228228
baseFeePerGas: gasEstimationObject.baseFeePerGas,
229229
gasFeeIsCustom,
230-
showLedgerSteps: fromAddressIsLedger,
230+
showLedgerSteps: true /* fromAddressIsLedger */,
231231
nativeCurrency,
232232
hardwareWalletRequiresConnection,
233233
isMultiLayerFeeNetwork,
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
4+
import { useI18nContext } from '../../../hooks/useI18nContext';
5+
6+
import Button from '../../../components/ui/button';
7+
8+
import Typography from '../../../components/ui/typography/typography';
9+
import {
10+
TYPOGRAPHY,
11+
FONT_WEIGHT,
12+
TEXT_ALIGN,
13+
} from '../../../helpers/constants/design-system';
14+
15+
export default function HardwareConnectivityContent({
16+
deviceName,
17+
onConnectClick,
18+
onAdvancedClick,
19+
}) {
20+
const t = useI18nContext();
21+
22+
switch (deviceName) {
23+
case 'Ledger':
24+
return (
25+
<div className="hardware-connectivity-content">
26+
<Typography
27+
variant={TYPOGRAPHY.H3}
28+
fontWeight={FONT_WEIGHT.BOLD}
29+
align={TEXT_ALIGN.CENTER}
30+
>
31+
{t('hardwareWalletConnectivityHelperHeading', [deviceName])}
32+
</Typography>
33+
{/* Image */}
34+
<div className="hardware-connectivity-content-list-container">
35+
<ol>
36+
<li>Plug in your Ledger</li>
37+
<li>Enter your passcode</li>
38+
<li>Open the Ethereum app</li>
39+
</ol>
40+
</div>
41+
<Button type="primary" onClick={onConnectClick}>
42+
{t('connect')}
43+
</Button>
44+
<Button
45+
type="link"
46+
className="hardware-connectivity-content-advanced"
47+
onClick={onAdvancedClick}
48+
>
49+
{t('advancedOptions')}
50+
</Button>
51+
</div>
52+
);
53+
default:
54+
return null;
55+
}
56+
}
57+
58+
HardwareConnectivityContent.propTypes = {
59+
deviceName: PropTypes.string.isRequired,
60+
onConnectClick: PropTypes.func.isRequired,
61+
onAdvancedClick: PropTypes.func.isRequired,
62+
};
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
4+
import { useI18nContext } from '../../../hooks/useI18nContext';
5+
import ActionableMessage from '../../../components/ui/actionable-message/actionable-message';
6+
7+
export default function HardwareConnectivityMessage({
8+
connected = false,
9+
onClick = undefined,
10+
}) {
11+
const t = useI18nContext();
12+
13+
return (
14+
<div className="hardware-connectivity-message">
15+
<ActionableMessage
16+
type={connected ? '' : 'warning'}
17+
message={
18+
connected ? (
19+
t('hardwareWalletConnectivityConnected', ['Ledger'])
20+
) : (
21+
<>
22+
{t('hardwareWalletConnectivityNotConnected', ['Ledger'])}{' '}
23+
<a href="#" onClick={onClick}>
24+
{t('hardwareWalletConnectivityNotConnectedConversion')}
25+
</a>
26+
</>
27+
)
28+
}
29+
/>
30+
</div>
31+
);
32+
}
33+
34+
HardwareConnectivityMessage.propTypes = {
35+
connected: PropTypes.bool.isRequired,
36+
onClick: PropTypes.func.isRequired,
37+
};

0 commit comments

Comments
 (0)