Skip to content

Commit 6182154

Browse files
feat: disable revoke button while tx pending (#37559)
## **Description** This PR extends the gatar permission revocation UI to dynamically determine if the revoke CTA should be disabled when a transaction to revoke said permission is pending. - Extends the gator permission selector file to read `pendingRevocations` from `GatorPermissionsController` - Extends the `ReviewGatorPermissionItem` component props to include `pendingRevocations` values - Extends the `ReviewGatorPermissionItem` logic to render button text and determine button disabled status dynamically ## **Related issues** Relates to: #37209 Requires: MetaMask/core#7055 ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** https://github.com/user-attachments/assets/5b65766b-8e8b-4d4e-b7a4-26f52fb60f19 ### **After** https://github.com/user-attachments/assets/92ff11a6-a01d-43b8-bcdf-6b3242252408 ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/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-extension/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. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Disables the Gator permission "Revoke" action when a matching revocation tx is pending via a new selector, with updated UI text and tests. > > - **UI (Gator Permissions)**: > - `ReviewGatorPermissionItem`: use `getPendingRevocations` to compute `isPendingRevocation` by `permissionResponse.context` and: > - Disable `Revoke` button and switch label to `t('gatorPermissionsRevocationPending')` with muted color when pending. > - Replace wrapper with `Button` for the revoke CTA. > - **Selectors**: > - Add `getPendingRevocations` in `ui/selectors/gator-permissions/gator-permissions.ts` to read `metamask.pendingRevocations`. > - **i18n**: > - Add messages: `gatorPermissionsRevocationPending`, `gatorPermissionsRevoke` in `app/_locales/en*.json`. > - **Tests/Snapshots**: > - Update `review-gator-permission-item.test.tsx` to mock `getPendingRevocations`; adjust snapshots for button changes. > - Add unit tests for `getPendingRevocations` in `gator-permissions.test.ts`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit ff6c481. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: jeffsmale90 <[email protected]>
1 parent 0525939 commit 6182154

File tree

7 files changed

+112
-28
lines changed

7 files changed

+112
-28
lines changed

app/_locales/en/messages.json

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/_locales/en_GB/messages.json

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ui/components/multichain/pages/gator-permissions/components/__snapshots__/review-gator-permission-item.test.tsx.snap

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,17 @@ exports[`Permission List Item render ERC20 token permissions renders erc20 token
1818
>
1919
localhost:8000
2020
</p>
21-
<div
22-
class="flex flex-row gap-2 justify-end"
23-
style="flex: 1; align-self: center; cursor: pointer;"
21+
<button
22+
class="inline-flex items-center justify-center rounded-xl px-4 font-medium min-w-20 overflow-hidden relative h-12 transition-all duration-100 ease-linear active:scale-[0.97] active:ease-[cubic-bezier(0.3,0.8,0.3,1)] bg-icon-default text-primary-inverse hover:bg-icon-default-hover active:bg-icon-default-pressed focus-visible:ring-0 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-default"
23+
role="button"
24+
style="background-color: transparent; padding: 0px;"
2425
>
2526
<p
2627
class="text-error-default text-s-body-md leading-s-body-md tracking-s-body-md md:text-l-body-md md:leading-l-body-md md:tracking-l-body-md font-regular font-default"
2728
>
2829
Revoke
2930
</p>
30-
</div>
31+
</button>
3132
</div>
3233
</div>
3334
<div
@@ -196,16 +197,17 @@ exports[`Permission List Item render ERC20 token permissions renders erc20 token
196197
>
197198
localhost:8000
198199
</p>
199-
<div
200-
class="flex flex-row gap-2 justify-end"
201-
style="flex: 1; align-self: center; cursor: pointer;"
200+
<button
201+
class="inline-flex items-center justify-center rounded-xl px-4 font-medium min-w-20 overflow-hidden relative h-12 transition-all duration-100 ease-linear active:scale-[0.97] active:ease-[cubic-bezier(0.3,0.8,0.3,1)] bg-icon-default text-primary-inverse hover:bg-icon-default-hover active:bg-icon-default-pressed focus-visible:ring-0 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-default"
202+
role="button"
203+
style="background-color: transparent; padding: 0px;"
202204
>
203205
<p
204206
class="text-error-default text-s-body-md leading-s-body-md tracking-s-body-md md:text-l-body-md md:leading-l-body-md md:tracking-l-body-md font-regular font-default"
205207
>
206208
Revoke
207209
</p>
208-
</div>
210+
</button>
209211
</div>
210212
</div>
211213
<div
@@ -374,16 +376,17 @@ exports[`Permission List Item render ERC20 token permissions renders erc20 token
374376
>
375377
localhost:8000
376378
</p>
377-
<div
378-
class="flex flex-row gap-2 justify-end"
379-
style="flex: 1; align-self: center; cursor: pointer;"
379+
<button
380+
class="inline-flex items-center justify-center rounded-xl px-4 font-medium min-w-20 overflow-hidden relative h-12 transition-all duration-100 ease-linear active:scale-[0.97] active:ease-[cubic-bezier(0.3,0.8,0.3,1)] bg-icon-default text-primary-inverse hover:bg-icon-default-hover active:bg-icon-default-pressed focus-visible:ring-0 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-default"
381+
role="button"
382+
style="background-color: transparent; padding: 0px;"
380383
>
381384
<p
382385
class="text-error-default text-s-body-md leading-s-body-md tracking-s-body-md md:text-l-body-md md:leading-l-body-md md:tracking-l-body-md font-regular font-default"
383386
>
384387
Revoke
385388
</p>
386-
</div>
389+
</button>
387390
</div>
388391
</div>
389392
<div
@@ -552,16 +555,17 @@ exports[`Permission List Item render NATIVE token permissions renders native tok
552555
>
553556
localhost:8000
554557
</p>
555-
<div
556-
class="flex flex-row gap-2 justify-end"
557-
style="flex: 1; align-self: center; cursor: pointer;"
558+
<button
559+
class="inline-flex items-center justify-center rounded-xl px-4 font-medium min-w-20 overflow-hidden relative h-12 transition-all duration-100 ease-linear active:scale-[0.97] active:ease-[cubic-bezier(0.3,0.8,0.3,1)] bg-icon-default text-primary-inverse hover:bg-icon-default-hover active:bg-icon-default-pressed focus-visible:ring-0 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-default"
560+
role="button"
561+
style="background-color: transparent; padding: 0px;"
558562
>
559563
<p
560564
class="text-error-default text-s-body-md leading-s-body-md tracking-s-body-md md:text-l-body-md md:leading-l-body-md md:tracking-l-body-md font-regular font-default"
561565
>
562566
Revoke
563567
</p>
564-
</div>
568+
</button>
565569
</div>
566570
</div>
567571
<div
@@ -730,16 +734,17 @@ exports[`Permission List Item render NATIVE token permissions renders native tok
730734
>
731735
localhost:8000
732736
</p>
733-
<div
734-
class="flex flex-row gap-2 justify-end"
735-
style="flex: 1; align-self: center; cursor: pointer;"
737+
<button
738+
class="inline-flex items-center justify-center rounded-xl px-4 font-medium min-w-20 overflow-hidden relative h-12 transition-all duration-100 ease-linear active:scale-[0.97] active:ease-[cubic-bezier(0.3,0.8,0.3,1)] bg-icon-default text-primary-inverse hover:bg-icon-default-hover active:bg-icon-default-pressed focus-visible:ring-0 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-default"
739+
role="button"
740+
style="background-color: transparent; padding: 0px;"
736741
>
737742
<p
738743
class="text-error-default text-s-body-md leading-s-body-md tracking-s-body-md md:text-l-body-md md:leading-l-body-md md:tracking-l-body-md font-regular font-default"
739744
>
740745
Revoke
741746
</p>
742-
</div>
747+
</button>
743748
</div>
744749
</div>
745750
<div

ui/components/multichain/pages/gator-permissions/components/review-gator-permission-item.test.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ const store = configureStore({
2222
},
2323
});
2424

25+
jest.mock(
26+
'../../../../../selectors/gator-permissions/gator-permissions',
27+
() => ({
28+
getPendingRevocations: jest.fn().mockReturnValue([]),
29+
}),
30+
);
31+
2532
describe('Permission List Item', () => {
2633
beforeAll(() => {
2734
// Set Luxon to use UTC as the default timezone for consistent test results

ui/components/multichain/pages/gator-permissions/components/review-gator-permission-item.tsx

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
AvatarNetworkSize,
1818
ButtonIconSize,
1919
IconName,
20+
Button,
2021
} from '@metamask/design-system-react';
2122
import {
2223
Erc20TokenPeriodicPermission,
@@ -46,6 +47,7 @@ import {
4647
selectERC20TokensByChain,
4748
} from '../../../../../selectors/selectors';
4849
import { getTokenMetadata } from '../../../../../helpers/utils/token-util';
50+
import { getPendingRevocations } from '../../../../../selectors/gator-permissions/gator-permissions';
4951

5052
type TokenMetadata = {
5153
symbol: string;
@@ -105,6 +107,7 @@ export const ReviewGatorPermissionItem = ({
105107
const { permissionResponse, siteOrigin } = gatorPermission;
106108
const { chainId } = permissionResponse;
107109
const permissionType = permissionResponse.permission.type;
110+
const permissionContext = permissionResponse.context;
108111
const permissionAccount = permissionResponse.address || '0x';
109112
const tokenAddress = permissionResponse.permission.data.tokenAddress as
110113
| Hex
@@ -115,6 +118,7 @@ export const ReviewGatorPermissionItem = ({
115118
const nativeTokenMetadata = useSelector((state) =>
116119
getNativeTokenInfo(state, chainId),
117120
) as TokenMetadata;
121+
const pendingRevocations = useSelector(getPendingRevocations);
118122

119123
const tokenMetadata: TokenMetadata = useMemo(() => {
120124
if (tokenAddress) {
@@ -146,6 +150,12 @@ export const ReviewGatorPermissionItem = ({
146150
};
147151
}, [tokensByChain, chainId, tokenAddress, nativeTokenMetadata]);
148152

153+
const isPendingRevocation = useMemo(() => {
154+
return pendingRevocations.some(
155+
(revocation) => revocation.permissionContext === permissionContext,
156+
);
157+
}, [pendingRevocations, permissionContext]);
158+
149159
/**
150160
* Handles the click event for the expand/collapse button
151161
*/
@@ -354,17 +364,28 @@ export const ReviewGatorPermissionItem = ({
354364
>
355365
{getURLHost(siteOrigin)}
356366
</Text>
357-
<Box
358-
flexDirection={BoxFlexDirection.Row}
359-
justifyContent={BoxJustifyContent.End}
360-
style={{ flex: '1', alignSelf: 'center', cursor: 'pointer' }}
361-
gap={2}
367+
<Button
362368
onClick={onRevokeClick}
369+
disabled={isPendingRevocation}
370+
style={{
371+
backgroundColor: 'transparent',
372+
border: 'none',
373+
padding: 0,
374+
}}
363375
>
364-
<Text color={TextColor.ErrorDefault} variant={TextVariant.BodyMd}>
365-
Revoke
376+
<Text
377+
color={
378+
isPendingRevocation
379+
? TextColor.TextMuted
380+
: TextColor.ErrorDefault
381+
}
382+
variant={TextVariant.BodyMd}
383+
>
384+
{isPendingRevocation
385+
? t('gatorPermissionsRevocationPending')
386+
: t('gatorPermissionsRevoke')}
366387
</Text>
367-
</Box>
388+
</Button>
368389
</Box>
369390
</Box>
370391

ui/selectors/gator-permissions/gator-permissions.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
AppState,
1717
getPermissionGroupMetaData,
1818
getPermissionMetaDataByOrigin,
19+
getPendingRevocations,
1920
} from './gator-permissions';
2021

2122
const MOCK_CHAIN_ID_MAINNET = '0x1' as Hex;
@@ -1502,4 +1503,27 @@ describe('Gator Permissions Selectors', () => {
15021503
expect(result).toEqual([]);
15031504
});
15041505
});
1506+
1507+
describe('getPendingRevocations', () => {
1508+
it('should return the list of gator permissions pending a revocation transaction', () => {
1509+
const result = getPendingRevocations({
1510+
...mockState,
1511+
metamask: {
1512+
...mockState.metamask,
1513+
pendingRevocations: [
1514+
{
1515+
txId: '1',
1516+
permissionContext: '0x1',
1517+
},
1518+
],
1519+
},
1520+
});
1521+
expect(result).toEqual([
1522+
{
1523+
txId: '1',
1524+
permissionContext: '0x1',
1525+
},
1526+
]);
1527+
});
1528+
});
15051529
});

ui/selectors/gator-permissions/gator-permissions.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,3 +413,14 @@ export const getAggregatedGatorPermissionByChainId = createSelector(
413413
}
414414
},
415415
);
416+
417+
/**
418+
* Get the list of gator permissions pending a revocation transaction.
419+
*
420+
* @param state - The current state
421+
* @returns The list of gator permissions pending a revocation transaction
422+
*/
423+
export const getPendingRevocations = createSelector(
424+
[getMetamask],
425+
(metamask) => metamask.pendingRevocations,
426+
);

0 commit comments

Comments
 (0)