Skip to content

Commit fee560a

Browse files
committed
[NEB-196] Tx button improvements: hide switch chain and approve tx popovers when possible (#6863)
<!-- ## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes" If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000): ## Notes for the reviewer Anything important to call out? Be sure to also clarify these in your comments. ## How to test Unit tests, playground, etc. --> <!-- start pr-codex --> --- ## PR-Codex overview This PR focuses on enhancing the `TransactionButton` and `MismatchButton` components by incorporating wallet connection status and improving transaction handling. It introduces logic for determining if a transaction requires external confirmation based on the connected wallets. ### Detailed summary - Added `useConnectedWallets` to `TransactionButton`. - Introduced `canSendTransactionWithoutConfirmation` function to check if a wallet can send transactions without confirmation. - Removed `isGasless` prop from `TransactionButton`. - Updated `MismatchButton` to include `isPending` prop. - Added connection status handling in `MismatchButton`. - Enhanced transaction handling based on network mismatch and wallet conditions. - Implemented `canSwitchNetworkWithoutConfirmation` function to determine switching requirements. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent 2d27c4f commit fee560a

File tree

2 files changed

+78
-20
lines changed

2 files changed

+78
-20
lines changed

apps/dashboard/src/components/buttons/MismatchButton.tsx

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,11 @@ import {
4242
useActiveAccount,
4343
useActiveWallet,
4444
useActiveWalletChain,
45+
useActiveWalletConnectionStatus,
4546
useSwitchActiveWalletChain,
4647
useWalletBalance,
4748
} from "thirdweb/react";
48-
import { privateKeyToAccount } from "thirdweb/wallets";
49+
import { type Wallet, privateKeyToAccount } from "thirdweb/wallets";
4950
import { getFaucetClaimAmount } from "../../app/(app)/api/testnet-faucet/claim/claim-amount";
5051
import { useAllChainsData } from "../../hooks/chains/allChains";
5152
import { useV5DashboardChain } from "../../lib/v5-adapter";
@@ -78,6 +79,7 @@ function useIsNetworkMismatch(txChainId: number) {
7879
type MistmatchButtonProps = React.ComponentProps<typeof Button> & {
7980
txChainId: number;
8081
isLoggedIn: boolean;
82+
isPending: boolean;
8183
};
8284

8385
export const MismatchButton = forwardRef<
@@ -93,6 +95,7 @@ export const MismatchButton = forwardRef<
9395
const client = useThirdwebClient();
9496
const pathname = usePathname();
9597
const txChain = useV5DashboardChain(txChainId);
98+
const connectionStatus = useActiveWalletConnectionStatus();
9699

97100
const txChainBalance = useWalletBalance({
98101
address: account?.address,
@@ -101,14 +104,39 @@ export const MismatchButton = forwardRef<
101104
});
102105

103106
const networksMismatch = useIsNetworkMismatch(txChainId);
107+
const switchNetwork = useSwitchActiveWalletChain();
108+
109+
const showSwitchChainPopover =
110+
networksMismatch && wallet && !canSwitchNetworkWithoutConfirmation(wallet);
111+
104112
const [isMismatchPopoverOpen, setIsMismatchPopoverOpen] = useState(false);
105113
const trackEvent = useTrack();
106114

107115
const chainId = activeWalletChain?.id;
108116

117+
const switchNetworkMutation = useMutation({
118+
mutationFn: switchNetwork,
119+
});
120+
109121
const eventRef =
110122
useRef<React.MouseEvent<HTMLButtonElement, MouseEvent>>(undefined);
111123

124+
if (connectionStatus === "connecting") {
125+
return (
126+
<Button
127+
className={props.className}
128+
size={props.size}
129+
asChild
130+
variant="outline"
131+
>
132+
<div>
133+
<Spinner className="size-4 shrink-0" />
134+
Connecting Wallet
135+
</div>
136+
</Button>
137+
);
138+
}
139+
112140
if (!wallet || !chainId || !isLoggedIn) {
113141
return (
114142
<Button className={props.className} size={props.size} asChild>
@@ -129,16 +157,19 @@ export const MismatchButton = forwardRef<
129157

130158
const disabled =
131159
buttonProps.disabled ||
160+
switchNetworkMutation.isPending ||
132161
// if user is about to trigger a transaction on txChain, but txChainBalance is not yet loaded and is required before proceeding
133-
(!networksMismatch && txChainBalance.isPending && isBalanceRequired);
162+
(!showSwitchChainPopover && txChainBalance.isPending && isBalanceRequired);
163+
164+
const showSpinner = props.isPending || switchNetworkMutation.isPending;
134165

135166
return (
136167
<>
137168
<Popover
138169
open={isMismatchPopoverOpen}
139170
onOpenChange={(v) => {
140171
if (v) {
141-
if (networksMismatch) {
172+
if (showSwitchChainPopover) {
142173
setIsMismatchPopoverOpen(true);
143174
}
144175
} else {
@@ -149,14 +180,17 @@ export const MismatchButton = forwardRef<
149180
<PopoverTrigger asChild>
150181
<Button
151182
{...buttonProps}
183+
className={cn("gap-2 disabled:opacity-100", buttonProps.className)}
152184
disabled={disabled}
153185
type={
154-
networksMismatch || notEnoughBalance ? "button" : buttonProps.type
186+
showSwitchChainPopover || notEnoughBalance
187+
? "button"
188+
: buttonProps.type
155189
}
156-
onClick={(e) => {
190+
onClick={async (e) => {
157191
e.stopPropagation();
158192

159-
if (networksMismatch) {
193+
if (showSwitchChainPopover) {
160194
eventRef.current = e;
161195
return;
162196
}
@@ -171,13 +205,20 @@ export const MismatchButton = forwardRef<
171205
return;
172206
}
173207

208+
// in case of in-app or smart wallet wallet, the user is not prompted to switch the network
209+
// we have to do it programmatically
210+
if (activeWalletChain?.id !== txChain.id) {
211+
await switchNetworkMutation.mutateAsync(txChain);
212+
}
213+
174214
if (buttonProps.onClick) {
175215
return buttonProps.onClick(e);
176216
}
177217
}}
178218
ref={ref}
179219
>
180220
{buttonProps.children}
221+
{showSpinner && <Spinner className="size-4 shrink-0" />}
181222
</Button>
182223
</PopoverTrigger>
183224
<PopoverContent className="min-w-[350px]" side="top" sideOffset={10}>
@@ -545,3 +586,7 @@ const GetLocalHostTestnetFunds: React.FC = () => {
545586
</Button>
546587
);
547588
};
589+
590+
function canSwitchNetworkWithoutConfirmation(wallet: Wallet) {
591+
return wallet.id === "inApp" || wallet.id === "smart";
592+
}

apps/dashboard/src/components/buttons/TransactionButton.tsx

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
"use client";
2-
import { Spinner } from "@/components/ui/Spinner/Spinner";
32
import { Button } from "@/components/ui/button";
43
import {
54
Popover,
@@ -17,16 +16,16 @@ import {
1716
useActiveAccount,
1817
useActiveWallet,
1918
useActiveWalletChain,
19+
useConnectedWallets,
2020
} from "thirdweb/react";
21-
import type { WalletId } from "thirdweb/wallets";
21+
import type { Wallet, WalletId } from "thirdweb/wallets";
2222
import { MismatchButton } from "./MismatchButton";
2323

2424
type ButtonProps = React.ComponentProps<typeof Button>;
2525

2626
type TransactionButtonProps = Omit<ButtonProps, "variant"> & {
2727
transactionCount: number | undefined; // support for unknown number of tx count
2828
isPending: boolean;
29-
isGasless?: boolean;
3029
txChainID: number;
3130
variant?: "destructive" | "primary" | "default";
3231
isLoggedIn: boolean;
@@ -36,19 +35,17 @@ export const TransactionButton: React.FC<TransactionButtonProps> = ({
3635
children,
3736
transactionCount,
3837
isPending,
39-
isGasless,
4038
txChainID,
4139
variant,
4240
isLoggedIn,
4341
...restButtonProps
4442
}) => {
4543
const activeWallet = useActiveWallet();
46-
44+
const connectedWallets = useConnectedWallets();
4745
// all wallets except inApp (aka - embedded) requires external confirmation - either from mobile app or extension
4846
const walletRequiresExternalConfirmation =
4947
activeWallet &&
50-
activeWallet.id !== "inApp" &&
51-
activeWallet.id !== "embedded";
48+
!canSendTransactionWithoutConfirmation(activeWallet, connectedWallets);
5249

5350
const initialFocusRef = useRef<HTMLButtonElement>(null);
5451

@@ -58,17 +55,14 @@ export const TransactionButton: React.FC<TransactionButtonProps> = ({
5855
[chain],
5956
);
6057

61-
const ButtonComponent = useMemo(() => {
62-
return isGasless ? Button : MismatchButton;
63-
}, [isGasless]);
64-
6558
const txCountDivWidth = 60;
6659
const disabled = isChainDeprecated || restButtonProps.disabled || isPending;
6760

6861
return (
6962
<Popover open={walletRequiresExternalConfirmation && isPending}>
7063
<PopoverTrigger asChild>
71-
<ButtonComponent
64+
<MismatchButton
65+
isPending={isPending}
7266
variant={variant || "primary"}
7367
isLoggedIn={isLoggedIn}
7468
txChainId={txChainID}
@@ -108,9 +102,8 @@ export const TransactionButton: React.FC<TransactionButtonProps> = ({
108102

109103
<span className="flex grow items-center justify-center gap-3">
110104
{children}
111-
{isPending && <Spinner className="size-4" />}
112105
</span>
113-
</ButtonComponent>
106+
</MismatchButton>
114107
</PopoverTrigger>
115108

116109
<PopoverContent className="min-w-[300px]" sideOffset={10} side="top">
@@ -200,3 +193,23 @@ const ExternalApprovalNotice: React.FC<ExternalApprovalNoticeProps> = ({
200193
</div>
201194
);
202195
};
196+
197+
function canSendTransactionWithoutConfirmation(
198+
wallet: Wallet,
199+
connectedWallets: Wallet[],
200+
) {
201+
// inApp wallet
202+
if (wallet.id === "inApp") {
203+
return true;
204+
}
205+
206+
// smart wallet + inApp admin wallet
207+
if (wallet.id === "smart") {
208+
const adminWallet = connectedWallets.find(
209+
(w) => w.getAccount()?.address === wallet.getAdminAccount?.()?.address,
210+
);
211+
return adminWallet?.id === "inApp";
212+
}
213+
214+
return false;
215+
}

0 commit comments

Comments
 (0)