Skip to content

Commit 33ffe79

Browse files
committed
Show pending revocation on click and clear after transaction window shows
1 parent 7a7ec74 commit 33ffe79

File tree

2 files changed

+103
-31
lines changed

2 files changed

+103
-31
lines changed

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ type ReviewGatorPermissionItemProps = {
7373
* The function to call when the revoke is clicked
7474
*/
7575
onRevokeClick: () => void;
76+
77+
/**
78+
* Whether this permission has a pending revoke click (temporary UI state)
79+
*/
80+
isPendingRevokeClick?: boolean;
7681
};
7782

7883
type PermissionExpandedDetails = Record<
@@ -102,6 +107,7 @@ export const ReviewGatorPermissionItem = ({
102107
networkName,
103108
gatorPermission,
104109
onRevokeClick,
110+
isPendingRevokeClick = false,
105111
}: ReviewGatorPermissionItemProps) => {
106112
const t = useI18nContext();
107113
const { permissionResponse, siteOrigin } = gatorPermission;
@@ -151,10 +157,13 @@ export const ReviewGatorPermissionItem = ({
151157
}, [tokensByChain, chainId, tokenAddress, nativeTokenMetadata]);
152158

153159
const isPendingRevocation = useMemo(() => {
154-
return pendingRevocations.some(
155-
(revocation) => revocation.permissionContext === permissionContext,
160+
return (
161+
isPendingRevokeClick ||
162+
pendingRevocations.some(
163+
(revocation) => revocation.permissionContext === permissionContext,
164+
)
156165
);
157-
}, [pendingRevocations, permissionContext]);
166+
}, [pendingRevocations, permissionContext, isPendingRevokeClick]);
158167

159168
/**
160169
* Handles the click event for the expand/collapse button

ui/components/multichain/pages/gator-permissions/review-permissions/review-gator-permissions-page.tsx

Lines changed: 91 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import React, { useEffect, useMemo, useState } from 'react';
1+
import React, {
2+
useCallback,
3+
useEffect,
4+
useMemo,
5+
useRef,
6+
useState,
7+
} from 'react';
28
import { useNavigate, useParams } from 'react-router-dom-v5-compat';
39
import { useSelector } from 'react-redux';
410
import { Hex } from '@metamask/utils';
@@ -58,7 +64,38 @@ export const ReviewGatorPermissionsPage = ({
5864
const [, evmNetworks] = useSelector(
5965
getMultichainNetworkConfigurationsByChainId,
6066
);
61-
const [totalGatorPermissions, setTotalGatorPermissions] = useState(0);
67+
const [pendingRevokeClicks, setPendingRevokeClicks] = useState<Set<string>>(
68+
new Set(),
69+
);
70+
const revokeTimeoutsRef = useRef<Map<string, ReturnType<typeof setTimeout>>>(
71+
new Map(),
72+
);
73+
74+
// Cleanup all pending timeouts on unmount
75+
useEffect(() => {
76+
const timeouts = revokeTimeoutsRef.current;
77+
return () => {
78+
timeouts.forEach((timeout) => clearTimeout(timeout));
79+
timeouts.clear();
80+
};
81+
}, []);
82+
83+
// Helper functions for managing pending state
84+
const addPendingContext = useCallback((context: string) => {
85+
setPendingRevokeClicks((prev) => {
86+
const next = new Set(prev);
87+
next.add(context);
88+
return next;
89+
});
90+
}, []);
91+
92+
const removePendingContext = useCallback((context: string) => {
93+
setPendingRevokeClicks((prev) => {
94+
const next = new Set(prev);
95+
next.delete(context);
96+
return next;
97+
});
98+
}, []);
6299

63100
const networkName: string = useMemo(() => {
64101
if (!chainId) {
@@ -89,39 +126,65 @@ export const ReviewGatorPermissionsPage = ({
89126
chainId: (chainId ?? '') as Hex,
90127
});
91128

92-
useEffect(() => {
93-
setTotalGatorPermissions(gatorPermissions.length);
94-
}, [chainId, gatorPermissions]);
129+
const handleRevokeClick = useCallback(
130+
async (
131+
permission: StoredGatorPermissionSanitized<
132+
Signer,
133+
PermissionTypesWithCustom
134+
>,
135+
) => {
136+
const { context } = permission.permissionResponse;
95137

96-
const handleRevokeClick = async (
97-
permission: StoredGatorPermissionSanitized<
98-
Signer,
99-
PermissionTypesWithCustom
100-
>,
101-
) => {
102-
try {
103-
await revokeGatorPermission(permission);
104-
} catch (error) {
105-
console.error('Error revoking gator permission:', error);
106-
}
107-
};
138+
// Set pending state immediately to disable button and show "Pending..." text
139+
addPendingContext(context);
140+
141+
try {
142+
await revokeGatorPermission(permission);
143+
144+
// Clear any existing timeout for this context
145+
const existingTimeout = revokeTimeoutsRef.current.get(context);
146+
clearTimeout(existingTimeout);
147+
revokeTimeoutsRef.current.delete(context);
148+
149+
// Delay clearing to prevent visual flash before transaction window shows
150+
const timeoutId = setTimeout(() => {
151+
removePendingContext(context);
152+
revokeTimeoutsRef.current.delete(context);
153+
}, 800); // 800ms delay to prevent visual flash before transaction window shows
154+
155+
revokeTimeoutsRef.current.set(context, timeoutId);
156+
} catch (error) {
157+
console.error('Error revoking gator permission:', error);
158+
159+
// Clean up any pending timeout
160+
const existingTimeout = revokeTimeoutsRef.current.get(context);
161+
clearTimeout(existingTimeout);
162+
revokeTimeoutsRef.current.delete(context);
163+
164+
// Clear pending state immediately on error
165+
removePendingContext(context);
166+
}
167+
},
168+
[revokeGatorPermission, addPendingContext, removePendingContext],
169+
);
108170

109171
const renderGatorPermissions = (
110172
permissions: StoredGatorPermissionSanitized<
111173
Signer,
112174
PermissionTypesWithCustom
113175
>[],
114176
) =>
115-
permissions.map((permission) => {
116-
return (
117-
<ReviewGatorPermissionItem
118-
key={`${permission.siteOrigin}-${permission.permissionResponse.context}`}
119-
networkName={networkName}
120-
gatorPermission={permission}
121-
onRevokeClick={() => handleRevokeClick(permission)}
122-
/>
123-
);
124-
});
177+
permissions.map((permission) => (
178+
<ReviewGatorPermissionItem
179+
key={`${permission.siteOrigin}-${permission.permissionResponse.context}`}
180+
networkName={networkName}
181+
gatorPermission={permission}
182+
onRevokeClick={() => handleRevokeClick(permission)}
183+
isPendingRevokeClick={pendingRevokeClicks.has(
184+
permission.permissionResponse.context,
185+
)}
186+
/>
187+
));
125188

126189
return (
127190
<Page
@@ -151,7 +214,7 @@ export const ReviewGatorPermissionsPage = ({
151214
</Text>
152215
</Header>
153216
<Content padding={0}>
154-
{totalGatorPermissions > 0 ? (
217+
{gatorPermissions.length > 0 ? (
155218
renderGatorPermissions(gatorPermissions)
156219
) : (
157220
<Box

0 commit comments

Comments
 (0)