Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 51 additions & 6 deletions apps/dashboard/src/components/buttons/MismatchButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,11 @@ import {
useActiveAccount,
useActiveWallet,
useActiveWalletChain,
useActiveWalletConnectionStatus,
useSwitchActiveWalletChain,
useWalletBalance,
} from "thirdweb/react";
import { privateKeyToAccount } from "thirdweb/wallets";
import { type Wallet, privateKeyToAccount } from "thirdweb/wallets";
import { getFaucetClaimAmount } from "../../app/(app)/api/testnet-faucet/claim/claim-amount";
import { useAllChainsData } from "../../hooks/chains/allChains";
import { useV5DashboardChain } from "../../lib/v5-adapter";
Expand Down Expand Up @@ -78,6 +79,7 @@ function useIsNetworkMismatch(txChainId: number) {
type MistmatchButtonProps = React.ComponentProps<typeof Button> & {
txChainId: number;
isLoggedIn: boolean;
isPending: boolean;
};

export const MismatchButton = forwardRef<
Expand All @@ -93,6 +95,7 @@ export const MismatchButton = forwardRef<
const client = useThirdwebClient();
const pathname = usePathname();
const txChain = useV5DashboardChain(txChainId);
const connectionStatus = useActiveWalletConnectionStatus();

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

const networksMismatch = useIsNetworkMismatch(txChainId);
const switchNetwork = useSwitchActiveWalletChain();

const showSwitchChainPopover =
networksMismatch && wallet && !canSwitchNetworkWithoutConfirmation(wallet);

const [isMismatchPopoverOpen, setIsMismatchPopoverOpen] = useState(false);
const trackEvent = useTrack();

const chainId = activeWalletChain?.id;

const switchNetworkMutation = useMutation({
mutationFn: switchNetwork,
});

const eventRef =
useRef<React.MouseEvent<HTMLButtonElement, MouseEvent>>(undefined);

if (connectionStatus === "connecting") {
return (
<Button
className={props.className}
size={props.size}
asChild
variant="outline"
>
<div>
<Spinner className="size-4 shrink-0" />
Connecting Wallet
</div>
</Button>
);
}

if (!wallet || !chainId || !isLoggedIn) {
return (
<Button className={props.className} size={props.size} asChild>
Expand All @@ -129,16 +157,19 @@ export const MismatchButton = forwardRef<

const disabled =
buttonProps.disabled ||
switchNetworkMutation.isPending ||
// if user is about to trigger a transaction on txChain, but txChainBalance is not yet loaded and is required before proceeding
(!networksMismatch && txChainBalance.isPending && isBalanceRequired);
(!showSwitchChainPopover && txChainBalance.isPending && isBalanceRequired);

const showSpinner = props.isPending || switchNetworkMutation.isPending;

return (
<>
<Popover
open={isMismatchPopoverOpen}
onOpenChange={(v) => {
if (v) {
if (networksMismatch) {
if (showSwitchChainPopover) {
setIsMismatchPopoverOpen(true);
}
} else {
Expand All @@ -149,14 +180,17 @@ export const MismatchButton = forwardRef<
<PopoverTrigger asChild>
<Button
{...buttonProps}
className={cn("gap-2 disabled:opacity-100", buttonProps.className)}
disabled={disabled}
type={
networksMismatch || notEnoughBalance ? "button" : buttonProps.type
showSwitchChainPopover || notEnoughBalance
? "button"
: buttonProps.type
}
onClick={(e) => {
onClick={async (e) => {
e.stopPropagation();

if (networksMismatch) {
if (showSwitchChainPopover) {
eventRef.current = e;
return;
}
Expand All @@ -171,13 +205,20 @@ export const MismatchButton = forwardRef<
return;
}

// in case of in-app or smart wallet wallet, the user is not prompted to switch the network
// we have to do it programmatically
if (activeWalletChain?.id !== txChain.id) {
await switchNetworkMutation.mutateAsync(txChain);
}

if (buttonProps.onClick) {
return buttonProps.onClick(e);
}
}}
ref={ref}
>
{buttonProps.children}
{showSpinner && <Spinner className="size-4 shrink-0" />}
</Button>
</PopoverTrigger>
<PopoverContent className="min-w-[350px]" side="top" sideOffset={10}>
Expand Down Expand Up @@ -545,3 +586,7 @@ const GetLocalHostTestnetFunds: React.FC = () => {
</Button>
);
};

function canSwitchNetworkWithoutConfirmation(wallet: Wallet) {
return wallet.id === "inApp" || wallet.id === "smart";
}
41 changes: 27 additions & 14 deletions apps/dashboard/src/components/buttons/TransactionButton.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
"use client";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Button } from "@/components/ui/button";
import {
Popover,
Expand All @@ -17,16 +16,16 @@ import {
useActiveAccount,
useActiveWallet,
useActiveWalletChain,
useConnectedWallets,
} from "thirdweb/react";
import type { WalletId } from "thirdweb/wallets";
import type { Wallet, WalletId } from "thirdweb/wallets";
import { MismatchButton } from "./MismatchButton";

type ButtonProps = React.ComponentProps<typeof Button>;

type TransactionButtonProps = Omit<ButtonProps, "variant"> & {
transactionCount: number | undefined; // support for unknown number of tx count
isPending: boolean;
isGasless?: boolean;
txChainID: number;
variant?: "destructive" | "primary" | "default";
isLoggedIn: boolean;
Expand All @@ -36,19 +35,17 @@ export const TransactionButton: React.FC<TransactionButtonProps> = ({
children,
transactionCount,
isPending,
isGasless,
txChainID,
variant,
isLoggedIn,
...restButtonProps
}) => {
const activeWallet = useActiveWallet();

const connectedWallets = useConnectedWallets();
// all wallets except inApp (aka - embedded) requires external confirmation - either from mobile app or extension
const walletRequiresExternalConfirmation =
activeWallet &&
activeWallet.id !== "inApp" &&
activeWallet.id !== "embedded";
!canSendTransactionWithoutConfirmation(activeWallet, connectedWallets);

const initialFocusRef = useRef<HTMLButtonElement>(null);

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

const ButtonComponent = useMemo(() => {
return isGasless ? Button : MismatchButton;
}, [isGasless]);

const txCountDivWidth = 60;
const disabled = isChainDeprecated || restButtonProps.disabled || isPending;

return (
<Popover open={walletRequiresExternalConfirmation && isPending}>
<PopoverTrigger asChild>
<ButtonComponent
<MismatchButton
isPending={isPending}
variant={variant || "primary"}
isLoggedIn={isLoggedIn}
txChainId={txChainID}
Expand Down Expand Up @@ -108,9 +102,8 @@ export const TransactionButton: React.FC<TransactionButtonProps> = ({

<span className="flex grow items-center justify-center gap-3">
{children}
{isPending && <Spinner className="size-4" />}
</span>
</ButtonComponent>
</MismatchButton>
</PopoverTrigger>

<PopoverContent className="min-w-[300px]" sideOffset={10} side="top">
Expand Down Expand Up @@ -200,3 +193,23 @@ const ExternalApprovalNotice: React.FC<ExternalApprovalNoticeProps> = ({
</div>
);
};

function canSendTransactionWithoutConfirmation(
wallet: Wallet,
connectedWallets: Wallet[],
) {
// inApp wallet
if (wallet.id === "inApp") {
return true;
}

// smart wallet + inApp admin wallet
if (wallet.id === "smart") {
const adminWallet = connectedWallets.find(
(w) => w.getAccount()?.address === wallet.getAdminAccount?.()?.address,
);
return adminWallet?.id === "inApp";
}

return false;
}
Loading