Skip to content

Commit e440985

Browse files
authored
chore(runway): cherry-pick fix: cp-7.56.0 metamask pay alert button flicker (#20238)
- fix: cp-7.56.0 metamask pay alert button flicker (#20103) ## **Description** Prevent confirm button showing alert state during initial page and quotes loading. ## **Changelog** CHANGELOG entry: null ## **Related issues** Fixes: [#5851](MetaMask/MetaMask-planning#5851) ## **Manual testing steps** ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [x] 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). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] 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. [4232322](4232322)
2 parents 9546adf + ce502ac commit e440985

File tree

8 files changed

+106
-37
lines changed

8 files changed

+106
-37
lines changed

app/components/Views/confirmations/components/edit-amount/edit-amount.test.tsx

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,28 @@
11
import React from 'react';
22
import { merge } from 'lodash';
33
import renderWithProvider from '../../../../../util/test/renderWithProvider';
4-
import { simpleSendTransactionControllerMock } from '../../__mocks__/controllers/transaction-controller-mock';
4+
import {
5+
simpleSendTransactionControllerMock,
6+
transactionIdMock,
7+
} from '../../__mocks__/controllers/transaction-controller-mock';
58
import { EditAmount, EditAmountProps } from './edit-amount';
69
import { transactionApprovalControllerMock } from '../../__mocks__/controllers/approval-controller-mock';
710
import { useTokenAmount } from '../../hooks/useTokenAmount';
811
import { act, fireEvent } from '@testing-library/react-native';
912
import { useTransactionPayToken } from '../../hooks/pay/useTransactionPayToken';
1013
import { useTokenFiatRate } from '../../hooks/tokens/useTokenFiatRates';
1114
import { otherControllersMock } from '../../__mocks__/controllers/other-controllers-mock';
15+
import { setTransactionBridgeQuotesLoading } from '../../../../../core/redux/slices/confirmationMetrics';
1216

1317
jest.mock('../../hooks/useTokenAmount');
1418
jest.mock('../../hooks/pay/useTransactionPayToken');
1519
jest.mock('../../hooks/tokens/useTokenFiatRates');
1620

21+
jest.mock('../../../../../core/redux/slices/confirmationMetrics', () => ({
22+
...jest.requireActual('../../../../../core/redux/slices/confirmationMetrics'),
23+
setTransactionBridgeQuotesLoading: jest.fn(),
24+
}));
25+
1726
jest.useFakeTimers();
1827

1928
const FIAT_RATE_MOCK = 2;
@@ -35,6 +44,10 @@ describe('EditAmount', () => {
3544
const useTokenFiatRateMock = jest.mocked(useTokenFiatRate);
3645
const updateTokenAmountMock = jest.fn();
3746

47+
const setTransactionBridgeQuotesLoadingMock = jest.mocked(
48+
setTransactionBridgeQuotesLoading,
49+
);
50+
3851
beforeEach(() => {
3952
jest.resetAllMocks();
4053

@@ -48,6 +61,10 @@ describe('EditAmount', () => {
4861
} as ReturnType<typeof useTransactionPayToken>);
4962

5063
useTokenFiatRateMock.mockReturnValue(FIAT_RATE_MOCK);
64+
65+
setTransactionBridgeQuotesLoadingMock.mockReturnValue({
66+
type: 'test',
67+
} as never);
5168
});
5269

5370
it('calls updateTokenAmount with token amount when done button pressed', async () => {
@@ -74,6 +91,33 @@ describe('EditAmount', () => {
7491
expect(updateTokenAmountMock).toHaveBeenCalledWith('26.5');
7592
});
7693

94+
it('sets quotes loading when done button pressed', async () => {
95+
const { getByTestId, getByText } = render();
96+
97+
await act(async () => {
98+
fireEvent.press(getByTestId('edit-amount-input'));
99+
});
100+
101+
await act(async () => {
102+
fireEvent.press(getByText('5'));
103+
});
104+
105+
await act(async () => {
106+
fireEvent.press(getByText('3'));
107+
});
108+
109+
await act(async () => {
110+
fireEvent.press(getByTestId('deposit-keyboard-done-button'));
111+
});
112+
113+
await jest.runAllTimersAsync();
114+
115+
expect(setTransactionBridgeQuotesLoadingMock).toHaveBeenCalledWith({
116+
transactionId: transactionIdMock,
117+
isLoading: true,
118+
});
119+
});
120+
77121
it('updates amount when input changes', async () => {
78122
const { getByTestId, getByText } = render();
79123

app/components/Views/confirmations/components/edit-amount/edit-amount.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import Text, {
1818
TextVariant,
1919
} from '../../../../../component-library/components/Texts/Text';
2020
import { Skeleton } from '../../../../../component-library/components/Skeleton';
21+
import { useDispatch } from 'react-redux';
22+
import { setTransactionBridgeQuotesLoading } from '../../../../../core/redux/slices/confirmationMetrics';
2123

2224
const MAX_LENGTH = 28;
2325

@@ -46,8 +48,9 @@ export function EditAmount({
4648
onKeyboardShow,
4749
onKeyboardHide,
4850
onKeyboardDone,
49-
}: EditAmountProps) {
51+
}: Readonly<EditAmountProps>) {
5052
const fiatCurrency = PERPS_CURRENCY;
53+
const dispatch = useDispatch();
5154
const [showKeyboard, setShowKeyboard] = useState<boolean>(false);
5255
const [inputChanged, setInputChanged] = useState<boolean>(false);
5356
const { setIsFooterVisible } = useConfirmationContext();
@@ -56,6 +59,7 @@ export function EditAmount({
5659
const transactionMeta = useTransactionMetadataRequest();
5760
const [amountFiat, setAmountFiat] = useState<string>('0');
5861

62+
const transactionId = transactionMeta?.id as string;
5963
const tokenAddress = transactionMeta?.txParams?.to as Hex;
6064
const chainId = transactionMeta?.chainId as Hex;
6165
const fiatRate = useTokenFiatRate(tokenAddress, chainId, fiatCurrency);
@@ -128,6 +132,10 @@ export function EditAmount({
128132
}, [amountHuman, inputChanged, onChange]);
129133

130134
const handleKeyboardDone = useCallback(() => {
135+
dispatch(
136+
setTransactionBridgeQuotesLoading({ transactionId, isLoading: true }),
137+
);
138+
131139
updateTokenAmount(amountHuman);
132140
inputRef.current?.blur();
133141
setShowKeyboard(false);
@@ -136,10 +144,12 @@ export function EditAmount({
136144
onKeyboardDone?.();
137145
}, [
138146
amountHuman,
147+
dispatch,
139148
inputRef,
140149
onKeyboardDone,
141150
onKeyboardHide,
142151
setIsFooterVisible,
152+
transactionId,
143153
updateTokenAmount,
144154
]);
145155

@@ -207,9 +217,9 @@ export function EditAmount({
207217

208218
export function EditAmountSkeleton({
209219
children,
210-
}: {
220+
}: Readonly<{
211221
children: React.ReactNode;
212-
}) {
222+
}>) {
213223
const { styles } = useStyles(styleSheet, {
214224
amountLength: 1,
215225
hasAlert: false,

app/components/Views/confirmations/components/footer/footer.test.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,5 +357,29 @@ describe('Footer', () => {
357357
});
358358
expect(mockTrackAlertMetrics).toHaveBeenCalledTimes(1);
359359
});
360+
361+
it('renders standard button label even if alerts if quotes loading', async () => {
362+
(useAlerts as jest.Mock).mockReturnValue({
363+
...baseMockUseAlerts,
364+
hasUnconfirmedDangerAlerts: true,
365+
});
366+
367+
const { getByText } = renderWithProvider(<Footer />, {
368+
state: merge(
369+
{},
370+
simpleSendTransactionControllerMock,
371+
transactionApprovalControllerMock,
372+
{
373+
confirmationMetrics: {
374+
isTransactionBridgeQuotesLoadingById: {
375+
[transactionIdMock]: true,
376+
},
377+
},
378+
},
379+
),
380+
});
381+
382+
expect(getByText('Confirm')).toBeDefined();
383+
});
360384
});
361385
});

app/components/Views/confirmations/components/footer/footer.tsx

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import Routes from '../../../../../constants/navigation/Routes';
3333
import { selectIsTransactionBridgeQuotesLoadingById } from '../../../../../core/redux/slices/confirmationMetrics';
3434
import { useSelector } from 'react-redux';
3535
import { RootState } from '../../../../../reducers';
36+
import { TransactionType } from '@metamask/transaction-controller';
3637

3738
export const Footer = () => {
3839
const {
@@ -52,12 +53,10 @@ export const Footer = () => {
5253
const transactionMetadata = useTransactionMetadataRequest();
5354
const { trackAlertMetrics } = useConfirmationAlertMetrics();
5455
const { isFullScreenConfirmation } = useFullScreenConfirmation();
56+
const transactionType = transactionMetadata?.type as TransactionType;
57+
const isStakingConfirmationBool = isStakingConfirmation(transactionType);
5558

56-
const isStakingConfirmationBool = isStakingConfirmation(
57-
transactionMetadata?.type as string,
58-
);
59-
60-
const { isFooterVisible, isTransactionValueUpdating } =
59+
const { isFooterVisible: isFooterVisibleFlag, isTransactionValueUpdating } =
6160
useConfirmationContext();
6261

6362
const navigation = useNavigation();
@@ -89,7 +88,7 @@ export const Footer = () => {
8988
hideConfirmAlertModal();
9089
try {
9190
await onConfirm();
92-
} catch (error) {
91+
} catch {
9392
navigation.navigate(Routes.TRANSACTIONS_VIEW);
9493
}
9594
}, [hideConfirmAlertModal, onConfirm, navigation]);
@@ -116,18 +115,29 @@ export const Footer = () => {
116115
if (isQRSigningInProgress) {
117116
return strings('confirm.qr_get_sign');
118117
}
118+
119+
if (isQuotesLoading) {
120+
return strings('confirm.confirm');
121+
}
122+
119123
if (hasUnconfirmedDangerAlerts) {
120124
return fieldAlerts.length > 1
121125
? strings('alert_system.review_alerts')
122126
: strings('alert_system.review_alert');
123127
}
128+
124129
if (hasBlockingAlerts) {
125130
return strings('alert_system.review_alerts');
126131
}
132+
127133
return strings('confirm.confirm');
128134
};
129135

130136
const getStartIcon = () => {
137+
if (isQuotesLoading) {
138+
return undefined;
139+
}
140+
131141
if (hasUnconfirmedDangerAlerts) {
132142
return IconName.SecuritySearch;
133143
}
@@ -153,8 +163,9 @@ export const Footer = () => {
153163
{
154164
variant: ButtonVariants.Primary,
155165
isDanger:
156-
securityAlertResponse?.result_type === ResultType.Malicious ||
157-
hasDangerAlerts,
166+
!isQuotesLoading &&
167+
(securityAlertResponse?.result_type === ResultType.Malicious ||
168+
hasDangerAlerts),
158169
isDisabled: isConfirmDisabled,
159170
label: confirmButtonLabel(),
160171
size: ButtonSize.Lg,
@@ -164,6 +175,9 @@ export const Footer = () => {
164175
},
165176
];
166177

178+
const isFooterVisible =
179+
isFooterVisibleFlag ?? transactionType !== TransactionType.perpsDeposit;
180+
167181
if (!isFooterVisible) {
168182
return null;
169183
}

app/components/Views/confirmations/context/confirmation-context/confirmation-context.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ describe('ConfirmationContext', () => {
1414
const { result } = renderHook(() => useConfirmationContext(), { wrapper });
1515

1616
expect(result.current).toStrictEqual({
17-
isFooterVisible: true,
17+
isFooterVisible: undefined,
1818
isTransactionValueUpdating: false,
1919
setIsFooterVisible: expect.any(Function),
2020
setIsTransactionValueUpdating: expect.any(Function),

app/components/Views/confirmations/context/confirmation-context/confirmation-context.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export const ConfirmationContextProvider: React.FC<
2727
const [isTransactionValueUpdating, setIsTransactionValueUpdating] =
2828
useState(false);
2929

30-
const [isFooterVisible, setIsFooterVisible] = useState(true);
30+
const [isFooterVisible, setIsFooterVisible] = useState<boolean>();
3131

3232
const contextValue = useMemo(
3333
() => ({

app/components/Views/confirmations/external/perps-temp/hooks/usePerpsDepositView.test.ts

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -110,23 +110,6 @@ describe('usePerpsDepositView', () => {
110110
expect(result.current.isFullView).toBe(false);
111111
});
112112

113-
it('returns isFullView as false if amount is zero', () => {
114-
useTokenAmountMock.mockReturnValue({
115-
amountUnformatted: '0',
116-
} as ReturnType<typeof useTokenAmount>);
117-
118-
const { result } = runHook(
119-
{
120-
isKeyboardVisible: false,
121-
},
122-
{
123-
quotes: [{}],
124-
},
125-
);
126-
127-
expect(result.current.isFullView).toBe(false);
128-
});
129-
130113
it('returns isFullView as false if quotes are undefined', () => {
131114
const { result } = runHook(
132115
{

app/components/Views/confirmations/external/perps-temp/hooks/usePerpsDepositView.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
import { CHAIN_IDS } from '@metamask/transaction-controller';
22
import { useAutomaticTransactionPayToken } from '../../../hooks/pay/useAutomaticTransactionPayToken';
3-
import { useTokenAmount } from '../../../hooks/useTokenAmount';
43
import { useTransactionMetadataOrThrow } from '../../../hooks/transactions/useTransactionMetadataRequest';
54
import { useSelector } from 'react-redux';
65
import { RootState } from '../../../../../../reducers';
76
import {
87
selectIsTransactionBridgeQuotesLoadingById,
98
selectTransactionBridgeQuotesById,
109
} from '../../../../../../core/redux/slices/confirmationMetrics';
11-
import { BigNumber } from 'bignumber.js';
1210
import { usePerpsDepositInit } from './usePerpsDepositInit';
1311
import { useTransactionPayToken } from '../../../hooks/pay/useTransactionPayToken';
1412
import { useTransactionPayTokenAmounts } from '../../../hooks/pay/useTransactionPayTokenAmounts';
@@ -25,12 +23,9 @@ export function usePerpsDepositView({
2523
usePerpsDepositInit();
2624

2725
const { id: transactionId } = useTransactionMetadataOrThrow();
28-
const { amountUnformatted } = useTokenAmount();
2926
const { payToken } = useTransactionPayToken();
3027
const { amounts: sourceAmounts } = useTransactionPayTokenAmounts();
3128

32-
const amountValue = new BigNumber(amountUnformatted ?? '0');
33-
3429
const quotes = useSelector((state: RootState) =>
3530
selectTransactionBridgeQuotesById(state, transactionId),
3631
);
@@ -41,7 +36,6 @@ export function usePerpsDepositView({
4136

4237
const isFullView =
4338
!isKeyboardVisible &&
44-
!amountValue.isZero() &&
4539
(isQuotesLoading || Boolean(quotes?.length) || sourceAmounts?.length === 0);
4640

4741
useAutomaticTransactionPayToken({

0 commit comments

Comments
 (0)