From d044c1c680096446c7c75cd656e2d342fabd172b Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Thu, 16 Jan 2025 08:20:48 -0800 Subject: [PATCH] feat: "Unlimited" value Decoding Simulation and account and message modal UI/UX updates (#13030) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Feat: - Support "Unlimited" display values in decoding simulation - Update account detail and message modals to have transparent backgrounds and enable click to close on background Note: It might be helpful to walk through the commits to review ## **Related issues** Fixes: https://github.com/MetaMask/metamask-mobile/issues/13022 Relates to: https://github.com/MetaMask/metamask-mobile/pull/12994 ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ![CleanShot 2025-01-16 at 03 23 30](https://github.com/user-attachments/assets/fd192b87-c5b7-4bb8-9d35-509b8c6bd55a) ![CleanShot 2025-01-16 at 04 12 30](https://github.com/user-attachments/assets/3468d59b-f045-4f5a-8669-09de78b7b7d1) ### **After** ![CleanShot 2025-01-16 at 03 22 54](https://github.com/user-attachments/assets/4a02032e-92bb-4579-aebf-311dab8e3184) ![CleanShot 2025-01-16 at 03 20 50](https://github.com/user-attachments/assets/9dd7b37c-8ced-4f50-b247-fdd7cd698247) ### **Before without "Unlimited" support** ### **After "Unlimited" support** ## **Pre-merge author checklist** - [ ] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- .../TypedSignV3V4/Simulation/Simulation.tsx | 4 +-- .../TypedSignDecoded.test.tsx | 13 +++++++ .../TypedSignDecoded/TypedSignDecoded.tsx | 13 ++++--- .../ValueDisplay/ValueDisplay.test.tsx | 34 ++++++++++++------- .../components/ValueDisplay/ValueDisplay.tsx | 15 ++++++-- .../ExpandableSection/ExpandableSection.tsx | 2 +- .../Views/confirmations/utils/confirm.ts | 3 ++ locales/languages/en.json | 3 +- 8 files changed, 62 insertions(+), 25 deletions(-) diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/Simulation.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/Simulation.tsx index 981a9355911..37265d0d0cd 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/Simulation.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/Simulation.tsx @@ -16,12 +16,12 @@ const TypedSignV3V4Simulation: React.FC = () => { } const { decodingData, decodingLoading } = signatureRequest; - const hasDecodingData = !( + const hasValidDecodingData = !( (!decodingLoading && decodingData === undefined) || decodingData?.error ); - if (!hasDecodingData && isPermit) { + if (!hasValidDecodingData && isPermit) { return ; } diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/TypedSignDecoded.test.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/TypedSignDecoded.test.tsx index c37fc11bc81..b974d951443 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/TypedSignDecoded.test.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/TypedSignDecoded.test.tsx @@ -97,6 +97,19 @@ describe('DecodedSimulation', () => { expect(await getByText('12,345')).toBeDefined(); }); + it('renders "Unlimited" for large values', async () => { + const { getByText } = renderWithProvider(, { + state: mockState([{ + ...stateChangesApprove[0], + amount: '1461501637330902918203684832716283019655932542975', + }]), + }); + + expect(await getByText('Estimated changes')).toBeDefined(); + expect(await getByText('Spending cap')).toBeDefined(); + expect(await getByText('Unlimited')).toBeDefined(); + }); + it('renders for ERC712 token', async () => { const { getByText } = renderWithProvider(, { state: mockState(stateChangesNftListing), diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/TypedSignDecoded.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/TypedSignDecoded.tsx index 3e712edf440..2adc5d40fec 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/TypedSignDecoded.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/TypedSignDecoded.tsx @@ -104,11 +104,11 @@ const StateChangeRow = ({ stateChange; const nftTransactionType = getStateChangeType(stateChangeList, stateChange); const tooltip = shouldDisplayLabel ? getStateChangeToolip(nftTransactionType) : undefined; - // todo: add - // const canDisplayValueAsUnlimited = - // assetType === TokenStandard.ERC20 && - // (changeType === DecodingDataChangeType.Approve || - // changeType === DecodingDataChangeType.Revoke); + + const canDisplayValueAsUnlimited = + assetType === TokenStandard.ERC20 && + (changeType === DecodingDataChangeType.Approve || + changeType === DecodingDataChangeType.Revoke); const changeLabel = shouldDisplayLabel ? getStateChangeLabelMap(changeType, nftTransactionType) @@ -133,8 +133,7 @@ const StateChangeRow = ({ changeType === DecodingDataChangeType.Receive } debit={changeType === DecodingDataChangeType.Transfer} - // todo: add - // canDisplayValueAsUnlimited={canDisplayValueAsUnlimited} + canDisplayValueAsUnlimited={canDisplayValueAsUnlimited} /> )} {assetType === 'NATIVE' && ( diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.test.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.test.tsx index 8decf038ea1..04d399656f5 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.test.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.test.tsx @@ -23,7 +23,6 @@ const mockTrackEvent = jest.fn(); jest.mock('../../../../../../../../../hooks/useMetrics'); jest.mock('../../../../../../../hooks/useGetTokenStandardAndDetails'); - jest.mock('../../../../../../../../../../util/address', () => ({ getTokenDetails: jest.fn(), renderShortAddress: jest.requireActual('../../../../../../../../../../util/address').renderShortAddress @@ -72,11 +71,30 @@ describe('SimulationValueDisplay', () => { { state: mockInitialState }, ); - await act(async () => { - await Promise.resolve(); + expect(await findByText('0.432')).toBeDefined(); + }); + + it('renders "Unlimited" for large values when canDisplayValueAsUnlimited is true', async () => { + (useGetTokenStandardAndDetails as jest.MockedFn).mockReturnValue({ + symbol: 'TST', + decimals: '4', + balance: undefined, + standard: TokenStandard.ERC20, + decimalsNumber: 4, }); - expect(await findByText('0.432')).toBeDefined(); + const { findByText } = renderWithProvider( + , + { state: mockInitialState }, + ); + + expect(await findByText('Unlimited')).toBeDefined(); }); it('should invoke method to track missing decimal information for ERC20 tokens only once', async () => { @@ -98,10 +116,6 @@ describe('SimulationValueDisplay', () => { { state: mockInitialState }, ); - await act(async () => { - await Promise.resolve(); - }); - expect(mockTrackEvent).toHaveBeenCalledTimes(1); }); @@ -124,10 +138,6 @@ describe('SimulationValueDisplay', () => { { state: mockInitialState }, ); - await act(async () => { - await Promise.resolve(); - }); - expect(mockTrackEvent).not.toHaveBeenCalled(); }); diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.tsx index dddcc95d8de..44622075f16 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.tsx @@ -29,10 +29,12 @@ import { calcTokenAmount } from '../../../../../../../../../../util/transactions import useGetTokenStandardAndDetails from '../../../../../../../hooks/useGetTokenStandardAndDetails'; import useTrackERC20WithoutDecimalInformation from '../../../../../../../hooks/useTrackERC20WithoutDecimalInformation'; +import { TOKEN_VALUE_UNLIMITED_THRESHOLD } from '../../../../../../../utils/confirm'; import { TokenDetailsERC20 } from '../../../../../../../utils/token'; import BottomModal from '../../../../../../UI/BottomModal'; import styleSheet from './ValueDisplay.styles'; +import { strings } from '../../../../../../../../../../../locales/i18n'; interface SimulationValueDisplayParams { /** ID of the associated chain. */ @@ -53,6 +55,9 @@ interface SimulationValueDisplayParams { // Optional + /** Whether a large amount can be substituted by "Unlimited" */ + canDisplayValueAsUnlimited?: boolean; + /** True if value is being credited to wallet */ credit?: boolean; @@ -81,6 +86,7 @@ const SimulationValueDisplay: React.FC< value, credit, debit, + canDisplayValueAsUnlimited = false, }) => { const [hasValueModalOpen, setHasValueModalOpen] = useState(false); @@ -112,6 +118,9 @@ const SimulationValueDisplay: React.FC< const tokenValue = isValidTokenAmount ? formatAmount('en-US', tokenAmount) : null; const tokenValueMaxPrecision = isValidTokenAmount ? formatAmountMaxPrecision('en-US', tokenAmount) : null; + const shouldShowUnlimitedValue = canDisplayValueAsUnlimited && + Number(value) > TOKEN_VALUE_UNLIMITED_THRESHOLD; + /** Temporary error capturing as we are building out Permit Simulations */ if (!tokenContract) { Logger.error( @@ -140,8 +149,10 @@ const SimulationValueDisplay: React.FC< {credit && '+ '} {debit && '- '} - {tokenValue !== null && - shortenString(tokenValue || '', { + {shouldShowUnlimitedValue + ? strings('confirm.unlimited') + : tokenValue !== null && + shortenString(tokenValue || '', { truncatedCharLimit: 15, truncatedStartChars: 15, truncatedEndChars: 0, diff --git a/app/components/Views/confirmations/components/UI/ExpandableSection/ExpandableSection.tsx b/app/components/Views/confirmations/components/UI/ExpandableSection/ExpandableSection.tsx index 0417a1c9ce2..4c738c73648 100644 --- a/app/components/Views/confirmations/components/UI/ExpandableSection/ExpandableSection.tsx +++ b/app/components/Views/confirmations/components/UI/ExpandableSection/ExpandableSection.tsx @@ -61,7 +61,7 @@ const ExpandableSection = ({ {expanded && ( - + setExpanded(false)} canCloseOnBackdropClick>