From 5736e670f75b48afde71d35f236643420f7c20e2 Mon Sep 17 00:00:00 2001 From: Vinicius Stevam <45455812+vinistevam@users.noreply.github.com> Date: Wed, 12 Jul 2023 09:29:54 +0100 Subject: [PATCH] Approval flow adding success and error pages (#19778) --- app/_locales/en/messages.json | 12 ++ app/scripts/metamask-controller.js | 6 + lavamoat/browserify/beta/policy.json | 3 + lavamoat/browserify/desktop/policy.json | 3 + lavamoat/browserify/flask/policy.json | 3 + lavamoat/browserify/main/policy.json | 3 + lavamoat/browserify/mmi/policy.json | 3 + package.json | 4 +- test/e2e/tests/switch-custom-network.spec.js | 2 +- .../safe-component-list.js | 16 +- .../__snapshots__/error.test.js.snap | 69 ++++++++ .../__snapshots__/success.test.js.snap | 60 +++++++ ui/pages/confirmation/templates/error.js | 94 +++++++++++ ui/pages/confirmation/templates/error.test.js | 66 ++++++++ ui/pages/confirmation/templates/index.js | 5 + ui/pages/confirmation/templates/success.js | 94 +++++++++++ .../confirmation/templates/success.test.js | 66 ++++++++ ui/pages/confirmation/util.test.ts | 60 +++++++ ui/pages/confirmation/util.ts | 150 ++++++++++++++++++ yarn.lock | 10 +- 20 files changed, 715 insertions(+), 14 deletions(-) create mode 100644 ui/pages/confirmation/templates/__snapshots__/error.test.js.snap create mode 100644 ui/pages/confirmation/templates/__snapshots__/success.test.js.snap create mode 100644 ui/pages/confirmation/templates/error.js create mode 100644 ui/pages/confirmation/templates/error.test.js create mode 100644 ui/pages/confirmation/templates/success.js create mode 100644 ui/pages/confirmation/templates/success.test.js create mode 100644 ui/pages/confirmation/util.test.ts create mode 100644 ui/pages/confirmation/util.ts diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 328441d1e..39d8216b7 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -3504,6 +3504,18 @@ "restoreUserDataDescription": { "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": { "message": "Retry transaction" }, diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 66326a010..018e39aff 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -3963,6 +3963,12 @@ export default class MetamaskController extends EventEmitter { this.approvalController.setFlowLoadingText.bind( this.approvalController, ), + showApprovalSuccess: this.approvalController.success.bind( + this.approvalController, + ), + showApprovalError: this.approvalController.error.bind( + this.approvalController, + ), sendMetrics: this.metaMetricsController.trackEvent.bind( this.metaMetricsController, ), diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index f3373a420..24f626987 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -775,6 +775,9 @@ } }, "@metamask/approval-controller": { + "globals": { + "console.info": true + }, "packages": { "@metamask/approval-controller>nanoid": true, "@metamask/base-controller": true, diff --git a/lavamoat/browserify/desktop/policy.json b/lavamoat/browserify/desktop/policy.json index e9b8572ae..1ff496ed2 100644 --- a/lavamoat/browserify/desktop/policy.json +++ b/lavamoat/browserify/desktop/policy.json @@ -775,6 +775,9 @@ } }, "@metamask/approval-controller": { + "globals": { + "console.info": true + }, "packages": { "@metamask/approval-controller>nanoid": true, "@metamask/base-controller": true, diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index e9b8572ae..1ff496ed2 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -775,6 +775,9 @@ } }, "@metamask/approval-controller": { + "globals": { + "console.info": true + }, "packages": { "@metamask/approval-controller>nanoid": true, "@metamask/base-controller": true, diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index f3373a420..24f626987 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -775,6 +775,9 @@ } }, "@metamask/approval-controller": { + "globals": { + "console.info": true + }, "packages": { "@metamask/approval-controller>nanoid": true, "@metamask/base-controller": true, diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json index a097c6d55..46ea1ba04 100644 --- a/lavamoat/browserify/mmi/policy.json +++ b/lavamoat/browserify/mmi/policy.json @@ -996,6 +996,9 @@ } }, "@metamask/approval-controller": { + "globals": { + "console.info": true + }, "packages": { "@metamask/approval-controller>nanoid": true, "@metamask/base-controller": true, diff --git a/package.json b/package.json index b4ec6bc10..88754b540 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,7 @@ "resolutions": { "@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", - "@metamask/approval-controller": "^3.3.0", + "@metamask/approval-controller": "^3.4.0", "@types/react": "^16.9.53", "analytics-node/axios": "^0.21.2", "ganache-core/lodash": "^4.17.21", @@ -226,7 +226,7 @@ "@metamask-institutional/transaction-update": "^0.1.21", "@metamask/address-book-controller": "^3.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/base-controller": "^3.0.0", "@metamask/browser-passworder": "^4.1.0", diff --git a/test/e2e/tests/switch-custom-network.spec.js b/test/e2e/tests/switch-custom-network.spec.js index 9e0cf6857..67c3ad1fc 100644 --- a/test/e2e/tests/switch-custom-network.spec.js +++ b/test/e2e/tests/switch-custom-network.spec.js @@ -2,7 +2,7 @@ const { strict: assert } = require('assert'); const FixtureBuilder = require('../fixture-builder'); const { convertToHexValue, withFixtures, openDapp } = require('../helpers'); -describe('Swtich ethereum chain', function () { +describe('Switch ethereum chain', function () { const ganacheOptions = { accounts: [ { diff --git a/ui/components/app/metamask-template-renderer/safe-component-list.js b/ui/components/app/metamask-template-renderer/safe-component-list.js index f8ae5650e..68101cfb3 100644 --- a/ui/components/app/metamask-template-renderer/safe-component-list.js +++ b/ui/components/app/metamask-template-renderer/safe-component-list.js @@ -12,6 +12,8 @@ import TextField from '../../ui/text-field'; import ConfirmationNetworkSwitch from '../../../pages/confirmation/components/confirmation-network-switch'; import UrlIcon from '../../ui/url-icon'; import Tooltip from '../../ui/tooltip/tooltip'; +import { AvatarIcon } from '../../component-library'; +import ActionableMessage from '../../ui/actionable-message/actionable-message'; ///: BEGIN:ONLY_INCLUDE_IN(snaps) import { SnapDelineator } from '../snaps/snap-delineator'; import { Copyable } from '../snaps/copyable'; @@ -21,19 +23,21 @@ import { SnapUIMarkdown } from '../snaps/snap-ui-markdown'; export const safeComponentList = { a: 'a', + ActionableMessage, + AvatarIcon, b: 'b', - i: 'i', - p: 'p', - div: 'div', - span: 'span', Box, Button, Chip, ConfirmationNetworkSwitch, DefinitionList, + div: 'div', + i: 'i', MetaMaskTranslation, NetworkDisplay, + p: 'p', Popover, + span: 'span', TextArea, TextField, Tooltip, @@ -41,9 +45,9 @@ export const safeComponentList = { Typography, UrlIcon, ///: BEGIN:ONLY_INCLUDE_IN(snaps) - SnapDelineator, Copyable, - Spinner, + SnapDelineator, SnapUIMarkdown, + Spinner, ///: END:ONLY_INCLUDE_IN }; diff --git a/ui/pages/confirmation/templates/__snapshots__/error.test.js.snap b/ui/pages/confirmation/templates/__snapshots__/error.test.js.snap new file mode 100644 index 000000000..30d96b667 --- /dev/null +++ b/ui/pages/confirmation/templates/__snapshots__/error.test.js.snap @@ -0,0 +1,69 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`error template matches the snapshot 1`] = ` +
+
+
+
+

+ Error mock +

+
+
+ +
+

+ Error +

+
+
+ +
+ The operation failed. +
+
+
+
+
+
+ +
+
+`; diff --git a/ui/pages/confirmation/templates/__snapshots__/success.test.js.snap b/ui/pages/confirmation/templates/__snapshots__/success.test.js.snap new file mode 100644 index 000000000..2c473b06d --- /dev/null +++ b/ui/pages/confirmation/templates/__snapshots__/success.test.js.snap @@ -0,0 +1,60 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`success template matches the snapshot 1`] = ` +
+
+
+
+

+ Success mock +

+
+
+ +
+

+ Success +

+
+ Success message +
+
+
+
+ +
+
+`; diff --git a/ui/pages/confirmation/templates/error.js b/ui/pages/confirmation/templates/error.js new file mode 100644 index 000000000..d0c41acfe --- /dev/null +++ b/ui/pages/confirmation/templates/error.js @@ -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; diff --git a/ui/pages/confirmation/templates/error.test.js b/ui/pages/confirmation/templates/error.test.js new file mode 100644 index 000000000..3c726eb0b --- /dev/null +++ b/ui/pages/confirmation/templates/error.test.js @@ -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( + , + store, + ); + await waitFor(() => { + expect(getByText('Error mock')).toBeInTheDocument(); + expect(container).toMatchSnapshot(); + }); + }); +}); diff --git a/ui/pages/confirmation/templates/index.js b/ui/pages/confirmation/templates/index.js index 36f1e8e05..5839ae47c 100644 --- a/ui/pages/confirmation/templates/index.js +++ b/ui/pages/confirmation/templates/index.js @@ -8,6 +8,8 @@ import { } from '../../../store/actions'; import addEthereumChain from './add-ethereum-chain'; import switchEthereumChain from './switch-ethereum-chain'; +import success from './success'; +import error from './error'; ///: BEGIN:ONLY_INCLUDE_IN(snaps) import snapAlert from './snaps/snap-alert/snap-alert'; import snapConfirmation from './snaps/snap-confirmation/snap-confirmation'; @@ -17,6 +19,9 @@ import snapPrompt from './snaps/snap-prompt/snap-prompt'; const APPROVAL_TEMPLATES = { [ApprovalType.AddEthereumChain]: addEthereumChain, [ApprovalType.SwitchEthereumChain]: switchEthereumChain, + // Use ApprovalType from utils controller + [ApprovalType.ResultSuccess]: success, + [ApprovalType.ResultError]: error, ///: BEGIN:ONLY_INCLUDE_IN(snaps) [ApprovalType.SnapDialogAlert]: snapAlert, [ApprovalType.SnapDialogConfirmation]: snapConfirmation, diff --git a/ui/pages/confirmation/templates/success.js b/ui/pages/confirmation/templates/success.js new file mode 100644 index 000000000..3d324642b --- /dev/null +++ b/ui/pages/confirmation/templates/success.js @@ -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; diff --git a/ui/pages/confirmation/templates/success.test.js b/ui/pages/confirmation/templates/success.test.js new file mode 100644 index 000000000..b3c22d33e --- /dev/null +++ b/ui/pages/confirmation/templates/success.test.js @@ -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( + , + store, + ); + await waitFor(() => { + expect(getByText('Success mock')).toBeInTheDocument(); + expect(container).toMatchSnapshot(); + }); + }); +}); diff --git a/ui/pages/confirmation/util.test.ts b/ui/pages/confirmation/util.test.ts new file mode 100644 index 000000000..7a0e85efa --- /dev/null +++ b/ui/pages/confirmation/util.test.ts @@ -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); + }); +}); diff --git a/ui/pages/confirmation/util.ts b/ui/pages/confirmation/util.ts new file mode 100644 index 000000000..ca75ee665 --- /dev/null +++ b/ui/pages/confirmation/util.ts @@ -0,0 +1,150 @@ +import { ResultComponent } from '@metamask/approval-controller'; + +type TemplateRendererComponent = { + key: string; + element: string; + props?: Record; + 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), + }; +} diff --git a/yarn.lock b/yarn.lock index 6ecd9d1d5..70031d4d3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3857,16 +3857,16 @@ __metadata: languageName: node linkType: hard -"@metamask/approval-controller@npm:^3.3.0": - version: 3.3.0 - resolution: "@metamask/approval-controller@npm:3.3.0" +"@metamask/approval-controller@npm:^3.4.0": + version: 3.4.0 + resolution: "@metamask/approval-controller@npm:3.4.0" dependencies: "@metamask/base-controller": ^3.0.0 "@metamask/utils": ^5.0.2 eth-rpc-errors: ^4.0.2 immer: ^9.0.6 nanoid: ^3.1.31 - checksum: 1fa6111a897d6f4aa369fd1a669fb5e16558277019f9d6c61449aea0d7b2672a62c189e5b3d9e84ab6e4d5826932c78d2bdb0f05aafb184a4ff07903c46abf2c + checksum: 153136800fbd8cd50e2d6012740526c55e74e3f8e5aa99a05d5788e8bae04ede622608a1adf67dcf0986162e9e7d1d11b6f6f0df438904b65db7435b881c2e3f languageName: node linkType: hard @@ -24596,7 +24596,7 @@ __metadata: "@metamask-institutional/transaction-update": ^0.1.21 "@metamask/address-book-controller": ^3.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/auto-changelog": ^2.1.0 "@metamask/base-controller": ^3.0.0