mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 09:57:02 +01:00
Approval flow adding success and error pages (#19778)
This commit is contained in:
parent
2fad10e0a3
commit
5736e670f7
12
app/_locales/en/messages.json
generated
12
app/_locales/en/messages.json
generated
@ -3504,6 +3504,18 @@
|
|||||||
"restoreUserDataDescription": {
|
"restoreUserDataDescription": {
|
||||||
"message": "You can restore user settings containing preferences and account addresses from a previously backed up JSON file."
|
"message": "You can restore user settings containing preferences and account addresses from a previously backed up JSON file."
|
||||||
},
|
},
|
||||||
|
"resultPageError": {
|
||||||
|
"message": "Error"
|
||||||
|
},
|
||||||
|
"resultPageErrorDefaultMessage": {
|
||||||
|
"message": "The operation failed."
|
||||||
|
},
|
||||||
|
"resultPageSuccess": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
|
"resultPageSuccessDefaultMessage": {
|
||||||
|
"message": "The operation completed successfully."
|
||||||
|
},
|
||||||
"retryTransaction": {
|
"retryTransaction": {
|
||||||
"message": "Retry transaction"
|
"message": "Retry transaction"
|
||||||
},
|
},
|
||||||
|
@ -3963,6 +3963,12 @@ export default class MetamaskController extends EventEmitter {
|
|||||||
this.approvalController.setFlowLoadingText.bind(
|
this.approvalController.setFlowLoadingText.bind(
|
||||||
this.approvalController,
|
this.approvalController,
|
||||||
),
|
),
|
||||||
|
showApprovalSuccess: this.approvalController.success.bind(
|
||||||
|
this.approvalController,
|
||||||
|
),
|
||||||
|
showApprovalError: this.approvalController.error.bind(
|
||||||
|
this.approvalController,
|
||||||
|
),
|
||||||
sendMetrics: this.metaMetricsController.trackEvent.bind(
|
sendMetrics: this.metaMetricsController.trackEvent.bind(
|
||||||
this.metaMetricsController,
|
this.metaMetricsController,
|
||||||
),
|
),
|
||||||
|
@ -775,6 +775,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@metamask/approval-controller": {
|
"@metamask/approval-controller": {
|
||||||
|
"globals": {
|
||||||
|
"console.info": true
|
||||||
|
},
|
||||||
"packages": {
|
"packages": {
|
||||||
"@metamask/approval-controller>nanoid": true,
|
"@metamask/approval-controller>nanoid": true,
|
||||||
"@metamask/base-controller": true,
|
"@metamask/base-controller": true,
|
||||||
|
@ -775,6 +775,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@metamask/approval-controller": {
|
"@metamask/approval-controller": {
|
||||||
|
"globals": {
|
||||||
|
"console.info": true
|
||||||
|
},
|
||||||
"packages": {
|
"packages": {
|
||||||
"@metamask/approval-controller>nanoid": true,
|
"@metamask/approval-controller>nanoid": true,
|
||||||
"@metamask/base-controller": true,
|
"@metamask/base-controller": true,
|
||||||
|
@ -775,6 +775,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@metamask/approval-controller": {
|
"@metamask/approval-controller": {
|
||||||
|
"globals": {
|
||||||
|
"console.info": true
|
||||||
|
},
|
||||||
"packages": {
|
"packages": {
|
||||||
"@metamask/approval-controller>nanoid": true,
|
"@metamask/approval-controller>nanoid": true,
|
||||||
"@metamask/base-controller": true,
|
"@metamask/base-controller": true,
|
||||||
|
@ -775,6 +775,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@metamask/approval-controller": {
|
"@metamask/approval-controller": {
|
||||||
|
"globals": {
|
||||||
|
"console.info": true
|
||||||
|
},
|
||||||
"packages": {
|
"packages": {
|
||||||
"@metamask/approval-controller>nanoid": true,
|
"@metamask/approval-controller>nanoid": true,
|
||||||
"@metamask/base-controller": true,
|
"@metamask/base-controller": true,
|
||||||
|
@ -996,6 +996,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@metamask/approval-controller": {
|
"@metamask/approval-controller": {
|
||||||
|
"globals": {
|
||||||
|
"console.info": true
|
||||||
|
},
|
||||||
"packages": {
|
"packages": {
|
||||||
"@metamask/approval-controller>nanoid": true,
|
"@metamask/approval-controller>nanoid": true,
|
||||||
"@metamask/base-controller": true,
|
"@metamask/base-controller": true,
|
||||||
|
@ -100,7 +100,7 @@
|
|||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@babel/core": "patch:@babel/core@npm%3A7.21.5#./.yarn/patches/@babel-core-npm-7.21.5-c72c337956.patch",
|
"@babel/core": "patch:@babel/core@npm%3A7.21.5#./.yarn/patches/@babel-core-npm-7.21.5-c72c337956.patch",
|
||||||
"@babel/runtime": "patch:@babel/runtime@npm%3A7.18.9#./.yarn/patches/@babel-runtime-npm-7.18.9-28ca6b5f61.patch",
|
"@babel/runtime": "patch:@babel/runtime@npm%3A7.18.9#./.yarn/patches/@babel-runtime-npm-7.18.9-28ca6b5f61.patch",
|
||||||
"@metamask/approval-controller": "^3.3.0",
|
"@metamask/approval-controller": "^3.4.0",
|
||||||
"@types/react": "^16.9.53",
|
"@types/react": "^16.9.53",
|
||||||
"analytics-node/axios": "^0.21.2",
|
"analytics-node/axios": "^0.21.2",
|
||||||
"ganache-core/lodash": "^4.17.21",
|
"ganache-core/lodash": "^4.17.21",
|
||||||
@ -226,7 +226,7 @@
|
|||||||
"@metamask-institutional/transaction-update": "^0.1.21",
|
"@metamask-institutional/transaction-update": "^0.1.21",
|
||||||
"@metamask/address-book-controller": "^3.0.0",
|
"@metamask/address-book-controller": "^3.0.0",
|
||||||
"@metamask/announcement-controller": "^4.0.0",
|
"@metamask/announcement-controller": "^4.0.0",
|
||||||
"@metamask/approval-controller": "^3.3.0",
|
"@metamask/approval-controller": "^3.4.0",
|
||||||
"@metamask/assets-controllers": "^9.2.0",
|
"@metamask/assets-controllers": "^9.2.0",
|
||||||
"@metamask/base-controller": "^3.0.0",
|
"@metamask/base-controller": "^3.0.0",
|
||||||
"@metamask/browser-passworder": "^4.1.0",
|
"@metamask/browser-passworder": "^4.1.0",
|
||||||
|
@ -2,7 +2,7 @@ const { strict: assert } = require('assert');
|
|||||||
const FixtureBuilder = require('../fixture-builder');
|
const FixtureBuilder = require('../fixture-builder');
|
||||||
const { convertToHexValue, withFixtures, openDapp } = require('../helpers');
|
const { convertToHexValue, withFixtures, openDapp } = require('../helpers');
|
||||||
|
|
||||||
describe('Swtich ethereum chain', function () {
|
describe('Switch ethereum chain', function () {
|
||||||
const ganacheOptions = {
|
const ganacheOptions = {
|
||||||
accounts: [
|
accounts: [
|
||||||
{
|
{
|
||||||
|
@ -12,6 +12,8 @@ import TextField from '../../ui/text-field';
|
|||||||
import ConfirmationNetworkSwitch from '../../../pages/confirmation/components/confirmation-network-switch';
|
import ConfirmationNetworkSwitch from '../../../pages/confirmation/components/confirmation-network-switch';
|
||||||
import UrlIcon from '../../ui/url-icon';
|
import UrlIcon from '../../ui/url-icon';
|
||||||
import Tooltip from '../../ui/tooltip/tooltip';
|
import Tooltip from '../../ui/tooltip/tooltip';
|
||||||
|
import { AvatarIcon } from '../../component-library';
|
||||||
|
import ActionableMessage from '../../ui/actionable-message/actionable-message';
|
||||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||||
import { SnapDelineator } from '../snaps/snap-delineator';
|
import { SnapDelineator } from '../snaps/snap-delineator';
|
||||||
import { Copyable } from '../snaps/copyable';
|
import { Copyable } from '../snaps/copyable';
|
||||||
@ -21,19 +23,21 @@ import { SnapUIMarkdown } from '../snaps/snap-ui-markdown';
|
|||||||
|
|
||||||
export const safeComponentList = {
|
export const safeComponentList = {
|
||||||
a: 'a',
|
a: 'a',
|
||||||
|
ActionableMessage,
|
||||||
|
AvatarIcon,
|
||||||
b: 'b',
|
b: 'b',
|
||||||
i: 'i',
|
|
||||||
p: 'p',
|
|
||||||
div: 'div',
|
|
||||||
span: 'span',
|
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Chip,
|
Chip,
|
||||||
ConfirmationNetworkSwitch,
|
ConfirmationNetworkSwitch,
|
||||||
DefinitionList,
|
DefinitionList,
|
||||||
|
div: 'div',
|
||||||
|
i: 'i',
|
||||||
MetaMaskTranslation,
|
MetaMaskTranslation,
|
||||||
NetworkDisplay,
|
NetworkDisplay,
|
||||||
|
p: 'p',
|
||||||
Popover,
|
Popover,
|
||||||
|
span: 'span',
|
||||||
TextArea,
|
TextArea,
|
||||||
TextField,
|
TextField,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
@ -41,9 +45,9 @@ export const safeComponentList = {
|
|||||||
Typography,
|
Typography,
|
||||||
UrlIcon,
|
UrlIcon,
|
||||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||||
SnapDelineator,
|
|
||||||
Copyable,
|
Copyable,
|
||||||
Spinner,
|
SnapDelineator,
|
||||||
SnapUIMarkdown,
|
SnapUIMarkdown,
|
||||||
|
Spinner,
|
||||||
///: END:ONLY_INCLUDE_IN
|
///: END:ONLY_INCLUDE_IN
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,69 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`error template matches the snapshot 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="confirmation-page"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="confirmation-page__content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="box box--padding-4 box--flex-direction-column box--align-items-center box--height-full box--display-flex"
|
||||||
|
>
|
||||||
|
<h2
|
||||||
|
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography typography--h2 typography--weight-normal typography--style-normal typography--color-text-default"
|
||||||
|
>
|
||||||
|
Error mock
|
||||||
|
</h2>
|
||||||
|
<div
|
||||||
|
class="box box--padding-top-2 box--padding-bottom-2 box--flex-direction-column box--justify-content-center box--align-items-center box--height-full box--display-flex"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="box mm-text mm-avatar-base mm-avatar-base--size-xl mm-avatar-icon mm-text--body-lg-medium mm-text--text-transform-uppercase box--display-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-error-default box--background-color-error-muted box--rounded-full box--border-color-transparent box--border-style-solid box--border-width-1"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="box mm-icon mm-icon--size-xl box--display-inline-block box--flex-direction-row box--color-inherit"
|
||||||
|
style="mask-image: url('./images/icons/warning.svg');"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h3
|
||||||
|
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography typography--h3 typography--weight-bold typography--style-normal typography--color-text-default"
|
||||||
|
>
|
||||||
|
Error
|
||||||
|
</h3>
|
||||||
|
<div
|
||||||
|
class="box box--flex-direction-row box--align-items-center box--text-align-center box--display-flex"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="actionable-message actionable-message--danger"
|
||||||
|
>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="actionable-message__message"
|
||||||
|
>
|
||||||
|
The operation failed.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="confirmation-footer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="confirmation-footer__actions"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="button btn--rounded btn-primary centered"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Ok
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
@ -0,0 +1,60 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`success template matches the snapshot 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="confirmation-page"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="confirmation-page__content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="box box--padding-4 box--flex-direction-column box--align-items-center box--height-full box--display-flex"
|
||||||
|
>
|
||||||
|
<h2
|
||||||
|
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography typography--h2 typography--weight-normal typography--style-normal typography--color-text-default"
|
||||||
|
>
|
||||||
|
Success mock
|
||||||
|
</h2>
|
||||||
|
<div
|
||||||
|
class="box box--padding-top-2 box--padding-bottom-2 box--flex-direction-column box--justify-content-center box--align-items-center box--height-full box--display-flex"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="box mm-text mm-avatar-base mm-avatar-base--size-xl mm-avatar-icon mm-text--body-lg-medium mm-text--text-transform-uppercase box--display-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-success-default box--background-color-success-muted box--rounded-full box--border-color-transparent box--border-style-solid box--border-width-1"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="box mm-icon mm-icon--size-xl box--display-inline-block box--flex-direction-row box--color-inherit"
|
||||||
|
style="mask-image: url('./images/icons/confirmation.svg');"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h3
|
||||||
|
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography typography--h3 typography--weight-bold typography--style-normal typography--color-text-default"
|
||||||
|
>
|
||||||
|
Success
|
||||||
|
</h3>
|
||||||
|
<div
|
||||||
|
class="box box--flex-direction-row box--align-items-center box--text-align-center box--display-flex"
|
||||||
|
>
|
||||||
|
Success message
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="confirmation-footer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="confirmation-footer__actions"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="button btn--rounded btn-primary centered"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Ok
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
94
ui/pages/confirmation/templates/error.js
Normal file
94
ui/pages/confirmation/templates/error.js
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import { IconName, IconSize } from '../../../components/component-library';
|
||||||
|
import {
|
||||||
|
FontWeight,
|
||||||
|
TextAlign,
|
||||||
|
BlockSize,
|
||||||
|
AlignItems,
|
||||||
|
FlexDirection,
|
||||||
|
JustifyContent,
|
||||||
|
TypographyVariant,
|
||||||
|
IconColor,
|
||||||
|
BackgroundColor,
|
||||||
|
} from '../../../helpers/constants/design-system';
|
||||||
|
import { processError } from '../util';
|
||||||
|
|
||||||
|
function getValues(pendingApproval, t, actions, _history) {
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
key: 'header',
|
||||||
|
element: 'Box',
|
||||||
|
props: {
|
||||||
|
flexDirection: FlexDirection.Column,
|
||||||
|
alignItems: AlignItems.center,
|
||||||
|
height: BlockSize.Full,
|
||||||
|
padding: 4,
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
...(pendingApproval.requestData.header || []),
|
||||||
|
{
|
||||||
|
key: 'content',
|
||||||
|
element: 'Box',
|
||||||
|
props: {
|
||||||
|
flexDirection: FlexDirection.Column,
|
||||||
|
alignItems: AlignItems.center,
|
||||||
|
justifyContent: JustifyContent.center,
|
||||||
|
height: BlockSize.Full,
|
||||||
|
paddingTop: 2,
|
||||||
|
paddingBottom: 2,
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: 'icon',
|
||||||
|
element: 'AvatarIcon',
|
||||||
|
props: {
|
||||||
|
iconName: IconName.Warning,
|
||||||
|
size: IconSize.Xl,
|
||||||
|
iconProps: { size: IconSize.Xl },
|
||||||
|
color: IconColor.errorDefault,
|
||||||
|
backgroundColor: BackgroundColor.errorMuted,
|
||||||
|
},
|
||||||
|
children: 'Icon',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'heading',
|
||||||
|
element: 'Typography',
|
||||||
|
props: {
|
||||||
|
variant: TypographyVariant.H3,
|
||||||
|
fontWeight: FontWeight.Bold,
|
||||||
|
paddingBottom: 2,
|
||||||
|
},
|
||||||
|
children: t('resultPageError'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'message',
|
||||||
|
element: 'Box',
|
||||||
|
props: {
|
||||||
|
alignItems: AlignItems.center,
|
||||||
|
textAlign: TextAlign.Center,
|
||||||
|
},
|
||||||
|
children: processError(
|
||||||
|
pendingApproval.requestData.error,
|
||||||
|
t('resultPageErrorDefaultMessage'),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
submitText: t('ok'),
|
||||||
|
onSubmit: () =>
|
||||||
|
actions.resolvePendingApproval(
|
||||||
|
pendingApproval.id,
|
||||||
|
pendingApproval.requestData,
|
||||||
|
),
|
||||||
|
networkDisplay: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const error = {
|
||||||
|
getValues,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default error;
|
66
ui/pages/confirmation/templates/error.test.js
Normal file
66
ui/pages/confirmation/templates/error.test.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import configureMockStore from 'redux-mock-store';
|
||||||
|
import thunk from 'redux-thunk';
|
||||||
|
import { waitFor } from '@testing-library/react';
|
||||||
|
|
||||||
|
import { ApprovalType } from '@metamask/controller-utils';
|
||||||
|
import { renderWithProvider } from '../../../../test/lib/render-helpers';
|
||||||
|
import Confirmation from '../confirmation';
|
||||||
|
|
||||||
|
jest.mock('../../../../shared/lib/fetch-with-cache');
|
||||||
|
|
||||||
|
const middleware = [thunk];
|
||||||
|
const mockApprovalId = 1;
|
||||||
|
const mockApproval = {
|
||||||
|
id: mockApprovalId,
|
||||||
|
origin: 'https://test-dapp.metamask.io',
|
||||||
|
requestData: {
|
||||||
|
header: [
|
||||||
|
{
|
||||||
|
key: 'headerText',
|
||||||
|
element: 'Typography',
|
||||||
|
children: 'Error mock',
|
||||||
|
props: {
|
||||||
|
variant: 'h2',
|
||||||
|
class: 'header-mock-class',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
message: 'Error message',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockBaseStore = {
|
||||||
|
metamask: {
|
||||||
|
pendingApprovals: {
|
||||||
|
[mockApprovalId]: mockApproval,
|
||||||
|
},
|
||||||
|
approvalFlows: [],
|
||||||
|
subjectMetadata: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('error template', () => {
|
||||||
|
it('matches the snapshot', async () => {
|
||||||
|
const testStore = {
|
||||||
|
metamask: {
|
||||||
|
...mockBaseStore.metamask,
|
||||||
|
pendingApprovals: {
|
||||||
|
[mockApprovalId]: {
|
||||||
|
...mockApproval,
|
||||||
|
type: ApprovalType.ResultError,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const store = configureMockStore(middleware)(testStore);
|
||||||
|
const { getByText, container } = renderWithProvider(
|
||||||
|
<Confirmation />,
|
||||||
|
store,
|
||||||
|
);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(getByText('Error mock')).toBeInTheDocument();
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -8,6 +8,8 @@ import {
|
|||||||
} from '../../../store/actions';
|
} from '../../../store/actions';
|
||||||
import addEthereumChain from './add-ethereum-chain';
|
import addEthereumChain from './add-ethereum-chain';
|
||||||
import switchEthereumChain from './switch-ethereum-chain';
|
import switchEthereumChain from './switch-ethereum-chain';
|
||||||
|
import success from './success';
|
||||||
|
import error from './error';
|
||||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||||
import snapAlert from './snaps/snap-alert/snap-alert';
|
import snapAlert from './snaps/snap-alert/snap-alert';
|
||||||
import snapConfirmation from './snaps/snap-confirmation/snap-confirmation';
|
import snapConfirmation from './snaps/snap-confirmation/snap-confirmation';
|
||||||
@ -17,6 +19,9 @@ import snapPrompt from './snaps/snap-prompt/snap-prompt';
|
|||||||
const APPROVAL_TEMPLATES = {
|
const APPROVAL_TEMPLATES = {
|
||||||
[ApprovalType.AddEthereumChain]: addEthereumChain,
|
[ApprovalType.AddEthereumChain]: addEthereumChain,
|
||||||
[ApprovalType.SwitchEthereumChain]: switchEthereumChain,
|
[ApprovalType.SwitchEthereumChain]: switchEthereumChain,
|
||||||
|
// Use ApprovalType from utils controller
|
||||||
|
[ApprovalType.ResultSuccess]: success,
|
||||||
|
[ApprovalType.ResultError]: error,
|
||||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||||
[ApprovalType.SnapDialogAlert]: snapAlert,
|
[ApprovalType.SnapDialogAlert]: snapAlert,
|
||||||
[ApprovalType.SnapDialogConfirmation]: snapConfirmation,
|
[ApprovalType.SnapDialogConfirmation]: snapConfirmation,
|
||||||
|
94
ui/pages/confirmation/templates/success.js
Normal file
94
ui/pages/confirmation/templates/success.js
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import { IconName, IconSize } from '../../../components/component-library';
|
||||||
|
import {
|
||||||
|
FontWeight,
|
||||||
|
BlockSize,
|
||||||
|
AlignItems,
|
||||||
|
FlexDirection,
|
||||||
|
JustifyContent,
|
||||||
|
TypographyVariant,
|
||||||
|
TextAlign,
|
||||||
|
IconColor,
|
||||||
|
BackgroundColor,
|
||||||
|
} from '../../../helpers/constants/design-system';
|
||||||
|
import { processString } from '../util';
|
||||||
|
|
||||||
|
function getValues(pendingApproval, t, actions, _history) {
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
key: 'header',
|
||||||
|
element: 'Box',
|
||||||
|
props: {
|
||||||
|
flexDirection: FlexDirection.Column,
|
||||||
|
alignItems: AlignItems.center,
|
||||||
|
height: BlockSize.Full,
|
||||||
|
padding: 4,
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
...(pendingApproval.requestData.header || []),
|
||||||
|
{
|
||||||
|
key: 'content',
|
||||||
|
element: 'Box',
|
||||||
|
props: {
|
||||||
|
flexDirection: FlexDirection.Column,
|
||||||
|
alignItems: AlignItems.center,
|
||||||
|
justifyContent: JustifyContent.center,
|
||||||
|
height: BlockSize.Full,
|
||||||
|
paddingTop: 2,
|
||||||
|
paddingBottom: 2,
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: 'icon',
|
||||||
|
element: 'AvatarIcon',
|
||||||
|
props: {
|
||||||
|
iconName: IconName.Confirmation,
|
||||||
|
size: IconSize.Xl,
|
||||||
|
iconProps: { size: IconSize.Xl },
|
||||||
|
color: IconColor.successDefault,
|
||||||
|
backgroundColor: BackgroundColor.successMuted,
|
||||||
|
},
|
||||||
|
children: 'Icon',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'heading',
|
||||||
|
element: 'Typography',
|
||||||
|
props: {
|
||||||
|
variant: TypographyVariant.H3,
|
||||||
|
fontWeight: FontWeight.Bold,
|
||||||
|
paddingBottom: 2,
|
||||||
|
},
|
||||||
|
children: t('resultPageSuccess'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'message',
|
||||||
|
element: 'Box',
|
||||||
|
props: {
|
||||||
|
alignItems: AlignItems.center,
|
||||||
|
textAlign: TextAlign.Center,
|
||||||
|
},
|
||||||
|
children: processString(
|
||||||
|
pendingApproval.requestData.message,
|
||||||
|
t('resultPageSuccessDefaultMessage'),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
submitText: t('ok'),
|
||||||
|
onSubmit: () =>
|
||||||
|
actions.resolvePendingApproval(
|
||||||
|
pendingApproval.id,
|
||||||
|
pendingApproval.requestData,
|
||||||
|
),
|
||||||
|
networkDisplay: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const success = {
|
||||||
|
getValues,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default success;
|
66
ui/pages/confirmation/templates/success.test.js
Normal file
66
ui/pages/confirmation/templates/success.test.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import configureMockStore from 'redux-mock-store';
|
||||||
|
import thunk from 'redux-thunk';
|
||||||
|
import { waitFor } from '@testing-library/react';
|
||||||
|
|
||||||
|
import { ApprovalType } from '@metamask/controller-utils';
|
||||||
|
import { renderWithProvider } from '../../../../test/lib/render-helpers';
|
||||||
|
import Confirmation from '../confirmation';
|
||||||
|
|
||||||
|
jest.mock('../../../../shared/lib/fetch-with-cache');
|
||||||
|
|
||||||
|
const middleware = [thunk];
|
||||||
|
const mockApprovalId = 1;
|
||||||
|
const mockApproval = {
|
||||||
|
id: mockApprovalId,
|
||||||
|
origin: 'https://test-dapp.metamask.io',
|
||||||
|
requestData: {
|
||||||
|
header: [
|
||||||
|
{
|
||||||
|
key: 'headerText',
|
||||||
|
element: 'Typography',
|
||||||
|
children: 'Success mock',
|
||||||
|
props: {
|
||||||
|
variant: 'h2',
|
||||||
|
class: 'header-mock-class',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
message: 'Success message',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockBaseStore = {
|
||||||
|
metamask: {
|
||||||
|
pendingApprovals: {
|
||||||
|
[mockApprovalId]: mockApproval,
|
||||||
|
},
|
||||||
|
approvalFlows: [],
|
||||||
|
subjectMetadata: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('success template', () => {
|
||||||
|
it('matches the snapshot', async () => {
|
||||||
|
const testStore = {
|
||||||
|
metamask: {
|
||||||
|
...mockBaseStore.metamask,
|
||||||
|
pendingApprovals: {
|
||||||
|
[mockApprovalId]: {
|
||||||
|
...mockApproval,
|
||||||
|
type: ApprovalType.ResultSuccess,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const store = configureMockStore(middleware)(testStore);
|
||||||
|
const { getByText, container } = renderWithProvider(
|
||||||
|
<Confirmation />,
|
||||||
|
store,
|
||||||
|
);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(getByText('Success mock')).toBeInTheDocument();
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
60
ui/pages/confirmation/util.test.ts
Normal file
60
ui/pages/confirmation/util.test.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { ResultComponent } from '@metamask/approval-controller';
|
||||||
|
import { processError, processString } from './util';
|
||||||
|
|
||||||
|
const FALLBACK_MESSAGE = 'Fallback Message';
|
||||||
|
const mockResultComponent: ResultComponent = {
|
||||||
|
key: 'mock-key',
|
||||||
|
name: 'mock-component',
|
||||||
|
properties: { message: 'mock1', message2: 'mock2' },
|
||||||
|
children: 'Mock child',
|
||||||
|
};
|
||||||
|
|
||||||
|
const expectedTemplateRendererComponent = {
|
||||||
|
key: 'mock-key',
|
||||||
|
props: {
|
||||||
|
message: 'mock1',
|
||||||
|
message2: 'mock2',
|
||||||
|
},
|
||||||
|
children: 'Mock child',
|
||||||
|
element: 'mock-component',
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('processError', () => {
|
||||||
|
it('returns TemplateRendererComponent when input is not defined', () => {
|
||||||
|
const result = processError(undefined, FALLBACK_MESSAGE);
|
||||||
|
expect(result).toEqual({
|
||||||
|
key: 'error',
|
||||||
|
element: 'ActionableMessage',
|
||||||
|
props: { type: 'danger', message: FALLBACK_MESSAGE },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns TemplateRendererComponent when input is a string', () => {
|
||||||
|
const result = processError('Error Message', FALLBACK_MESSAGE);
|
||||||
|
expect(result).toEqual({
|
||||||
|
key: 'error',
|
||||||
|
element: 'ActionableMessage',
|
||||||
|
props: { type: 'danger', message: 'Error Message' },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns TemplateRendererComponent when input is a ResultComponent', () => {
|
||||||
|
const result = processError(mockResultComponent, FALLBACK_MESSAGE);
|
||||||
|
expect(result).toEqual(expectedTemplateRendererComponent);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('processString', () => {
|
||||||
|
it('returns string when input is not defined', () => {
|
||||||
|
const result = processString(undefined, FALLBACK_MESSAGE);
|
||||||
|
expect(result[0]).toEqual(FALLBACK_MESSAGE);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns TemplateRendererComponent when input is a string', () => {
|
||||||
|
const result = processString('Hello', FALLBACK_MESSAGE);
|
||||||
|
expect(result).toEqual(['Hello']);
|
||||||
|
});
|
||||||
|
it('returns TemplateRendererComponent when input is a ResultComponent', () => {
|
||||||
|
const result = processString(mockResultComponent, FALLBACK_MESSAGE);
|
||||||
|
expect(result).toEqual(expectedTemplateRendererComponent);
|
||||||
|
});
|
||||||
|
});
|
150
ui/pages/confirmation/util.ts
Normal file
150
ui/pages/confirmation/util.ts
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
import { ResultComponent } from '@metamask/approval-controller';
|
||||||
|
|
||||||
|
type TemplateRendererComponent = {
|
||||||
|
key: string;
|
||||||
|
element: string;
|
||||||
|
props?: Record<string, unknown>;
|
||||||
|
children?:
|
||||||
|
| string
|
||||||
|
| TemplateRendererComponent
|
||||||
|
| (string | TemplateRendererComponent)[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes an error message or ResultComponent and returns a TemplateRendererComponent
|
||||||
|
* or an array of strings | TemplateRendererComponents.
|
||||||
|
*
|
||||||
|
* @param input - The message or component to process.
|
||||||
|
* @param fallback - The fallback message to use when the input is not valid.
|
||||||
|
* @returns The processed error component.
|
||||||
|
*/
|
||||||
|
export function processError(
|
||||||
|
input: undefined | string | ResultComponent | ResultComponent[],
|
||||||
|
fallback: string,
|
||||||
|
): TemplateRendererComponent | (string | TemplateRendererComponent)[] {
|
||||||
|
const currentInput = convertResultComponents(input) || fallback;
|
||||||
|
|
||||||
|
if (typeof currentInput !== 'string') {
|
||||||
|
return currentInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: 'error',
|
||||||
|
element: 'ActionableMessage',
|
||||||
|
props: { type: 'danger', message: currentInput },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes a string or ResultComponent and returns a string or TemplateRendererComponent
|
||||||
|
* or an array of strings | TemplateRendererComponents.
|
||||||
|
*
|
||||||
|
* @param input - The message or component to process.
|
||||||
|
* @param fallback - The fallback string to use when the input is not valid.
|
||||||
|
* @returns The processed message.
|
||||||
|
*/
|
||||||
|
export function processString(
|
||||||
|
input: undefined | string | ResultComponent | ResultComponent[],
|
||||||
|
fallback: string,
|
||||||
|
): string | TemplateRendererComponent | (string | TemplateRendererComponent)[] {
|
||||||
|
const currentInput = convertResultComponents(input) || fallback;
|
||||||
|
|
||||||
|
if (typeof currentInput !== 'string') {
|
||||||
|
return currentInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
return applyBold(currentInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies bold formatting to the message.
|
||||||
|
*
|
||||||
|
* @param message - The input message to apply bold formatting to.
|
||||||
|
* @returns The formatted message.
|
||||||
|
*/
|
||||||
|
function applyBold(message: string): (string | TemplateRendererComponent)[] {
|
||||||
|
const boldPattern = /\*\*(.+?)\*\*/gu;
|
||||||
|
|
||||||
|
return findMarkdown(message, boldPattern, (formattedText, index) => ({
|
||||||
|
key: `bold-${index}`,
|
||||||
|
element: 'b',
|
||||||
|
children: formattedText,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds and formats markdown elements in the given text.
|
||||||
|
*
|
||||||
|
* @param text - The input text to search for markdown elements.
|
||||||
|
* @param pattern - The pattern to match the markdown elements.
|
||||||
|
* @param getElement - The callback function to create the formatted elements.
|
||||||
|
* @returns The array of formatted elements.
|
||||||
|
*/
|
||||||
|
function findMarkdown(
|
||||||
|
text: string,
|
||||||
|
pattern: RegExp,
|
||||||
|
getElement: (
|
||||||
|
formattedText: string,
|
||||||
|
index: number,
|
||||||
|
) => TemplateRendererComponent,
|
||||||
|
): (string | TemplateRendererComponent)[] {
|
||||||
|
let position = 0;
|
||||||
|
let index = 0;
|
||||||
|
|
||||||
|
const matches = Array.from(text.matchAll(pattern));
|
||||||
|
const elements = [];
|
||||||
|
|
||||||
|
for (const match of matches) {
|
||||||
|
const rawText = text.substring(position, match.index);
|
||||||
|
|
||||||
|
if (rawText.length) {
|
||||||
|
elements.push(rawText);
|
||||||
|
}
|
||||||
|
|
||||||
|
const formattedText = match[1];
|
||||||
|
const formattedElement = getElement(formattedText, index);
|
||||||
|
|
||||||
|
elements.push(formattedElement);
|
||||||
|
|
||||||
|
position = (match.index as number) + match[0].length;
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalRawText = text.substring(position);
|
||||||
|
|
||||||
|
if (finalRawText.length) {
|
||||||
|
elements.push(finalRawText);
|
||||||
|
}
|
||||||
|
|
||||||
|
return elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertResultComponents(
|
||||||
|
input: undefined | string | ResultComponent | (string | ResultComponent)[],
|
||||||
|
):
|
||||||
|
| undefined
|
||||||
|
| string
|
||||||
|
| TemplateRendererComponent
|
||||||
|
| (string | TemplateRendererComponent)[] {
|
||||||
|
if (input === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof input === 'string') {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(input)) {
|
||||||
|
return input.map(convertResultComponents) as (
|
||||||
|
| string
|
||||||
|
| TemplateRendererComponent
|
||||||
|
)[];
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: input.key,
|
||||||
|
element: input.name,
|
||||||
|
props: input.properties,
|
||||||
|
children: convertResultComponents(input.children),
|
||||||
|
};
|
||||||
|
}
|
10
yarn.lock
10
yarn.lock
@ -3857,16 +3857,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@metamask/approval-controller@npm:^3.3.0":
|
"@metamask/approval-controller@npm:^3.4.0":
|
||||||
version: 3.3.0
|
version: 3.4.0
|
||||||
resolution: "@metamask/approval-controller@npm:3.3.0"
|
resolution: "@metamask/approval-controller@npm:3.4.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@metamask/base-controller": ^3.0.0
|
"@metamask/base-controller": ^3.0.0
|
||||||
"@metamask/utils": ^5.0.2
|
"@metamask/utils": ^5.0.2
|
||||||
eth-rpc-errors: ^4.0.2
|
eth-rpc-errors: ^4.0.2
|
||||||
immer: ^9.0.6
|
immer: ^9.0.6
|
||||||
nanoid: ^3.1.31
|
nanoid: ^3.1.31
|
||||||
checksum: 1fa6111a897d6f4aa369fd1a669fb5e16558277019f9d6c61449aea0d7b2672a62c189e5b3d9e84ab6e4d5826932c78d2bdb0f05aafb184a4ff07903c46abf2c
|
checksum: 153136800fbd8cd50e2d6012740526c55e74e3f8e5aa99a05d5788e8bae04ede622608a1adf67dcf0986162e9e7d1d11b6f6f0df438904b65db7435b881c2e3f
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@ -24596,7 +24596,7 @@ __metadata:
|
|||||||
"@metamask-institutional/transaction-update": ^0.1.21
|
"@metamask-institutional/transaction-update": ^0.1.21
|
||||||
"@metamask/address-book-controller": ^3.0.0
|
"@metamask/address-book-controller": ^3.0.0
|
||||||
"@metamask/announcement-controller": ^4.0.0
|
"@metamask/announcement-controller": ^4.0.0
|
||||||
"@metamask/approval-controller": ^3.3.0
|
"@metamask/approval-controller": ^3.4.0
|
||||||
"@metamask/assets-controllers": ^9.2.0
|
"@metamask/assets-controllers": ^9.2.0
|
||||||
"@metamask/auto-changelog": ^2.1.0
|
"@metamask/auto-changelog": ^2.1.0
|
||||||
"@metamask/base-controller": ^3.0.0
|
"@metamask/base-controller": ^3.0.0
|
||||||
|
Loading…
Reference in New Issue
Block a user