From 5ba5762c0bfbfa46107c964420aa252f1c342767 Mon Sep 17 00:00:00 2001 From: BrickheadJohnny Date: Tue, 12 Nov 2024 15:00:22 +0100 Subject: [PATCH] feat(TransactionStatusModal): handle TX timeout --- .../components/TransactionStatusContext.tsx | 14 +++-- .../TransactionStatusModal.stories.tsx | 54 ++++++++++++------- .../TransactionStatusModal.tsx | 17 +++--- .../components/TxError.tsx | 48 ++++++++++++----- .../GuildCheckout/hooks/useMintGuildPin.tsx | 4 +- .../[guild]/collect/hooks/useCollectNft.ts | 11 ++-- src/hooks/useSubmitTransaction.ts | 7 ++- 7 files changed, 100 insertions(+), 55 deletions(-) diff --git a/src/components/[guild]/Requirements/components/GuildCheckout/components/TransactionStatusContext.tsx b/src/components/[guild]/Requirements/components/GuildCheckout/components/TransactionStatusContext.tsx index e7bb3469cf..eb1f84684f 100644 --- a/src/components/[guild]/Requirements/components/GuildCheckout/components/TransactionStatusContext.tsx +++ b/src/components/[guild]/Requirements/components/GuildCheckout/components/TransactionStatusContext.tsx @@ -10,17 +10,21 @@ import { useState, } from "react" -const TransactionStatusContext = createContext<{ +type TransactionStatusContextType = { isTxModalOpen: boolean onTxModalOpen: () => void onTxModalClose: () => void txHash: string setTxHash: Dispatch> - txError: boolean - setTxError: Dispatch> + txError: Error | null + setTxError: Dispatch> txSuccess: boolean setTxSuccess: Dispatch> -}>(undefined) +} + +const TransactionStatusContext = createContext( + undefined as unknown as TransactionStatusContextType +) const TransactionStatusProvider = ({ children, @@ -32,7 +36,7 @@ const TransactionStatusProvider = ({ } = useDisclosure() const [txHash, setTxHash] = useState("") - const [txError, setTxError] = useState(false) + const [txError, setTxError] = useState(null) const [txSuccess, setTxSuccess] = useState(false) const { confettiPlayer } = useConfetti() diff --git a/src/components/[guild]/Requirements/components/GuildCheckout/components/TransactionStatusModal/TransactionStatusModal.stories.tsx b/src/components/[guild]/Requirements/components/GuildCheckout/components/TransactionStatusModal/TransactionStatusModal.stories.tsx index 0e1e1dc9d7..eba5afd8a1 100644 --- a/src/components/[guild]/Requirements/components/GuildCheckout/components/TransactionStatusModal/TransactionStatusModal.stories.tsx +++ b/src/components/[guild]/Requirements/components/GuildCheckout/components/TransactionStatusModal/TransactionStatusModal.stories.tsx @@ -5,8 +5,8 @@ import { Anchor } from "@/components/ui/Anchor" import { FuelProvider } from "@fuels/react" import { QueryClient, QueryClientProvider } from "@tanstack/react-query" import { fuelConfig } from "fuelConfig" -import { ThemeProvider } from "next-themes" import { useEffect } from "react" +import { WaitForTransactionReceiptTimeoutError } from "viem" import { WagmiProvider } from "wagmi" import { wagmiConfig } from "wagmiConfig" import { @@ -17,6 +17,8 @@ import { TransactionStatusModal } from "./TransactionStatusModal" const queryClient = new QueryClient() +const HASH = "0xbb2da2efbfc465f63c100036d25c626ac96a1167d48f80646e91be3361179160" + const TransactionStatusDialogStory = () => ( <> ( successText={"This is the success text!"} successLinkComponent={ = { component: TransactionStatusDialogStory, decorators: [ (Story) => ( - - - - - - - - - - - - - + + + + + + + + + + + ), ], } @@ -82,9 +82,23 @@ export const Progress: Story = { const { onTxModalOpen, setTxHash } = useTransactionStatusContext() useEffect(() => { - setTxHash( - "0xbb2da2efbfc465f63c100036d25c626ac96a1167d48f80646e91be3361179160" - ) + setTxHash(HASH) + onTxModalOpen() + }, []) + + return + }, + ], +} + +export const Error_: Story = { + name: "Error", + decorators: [ + (Story) => { + const { onTxModalOpen, setTxError } = useTransactionStatusContext() + + useEffect(() => { + setTxError(new Error("TX ERROR")) onTxModalOpen() }, []) @@ -93,13 +107,13 @@ export const Progress: Story = { ], } -export const Error: Story = { +export const Timeout: Story = { decorators: [ (Story) => { const { onTxModalOpen, setTxError } = useTransactionStatusContext() useEffect(() => { - setTxError(true) + setTxError(new WaitForTransactionReceiptTimeoutError({ hash: HASH })) onTxModalOpen() }, []) diff --git a/src/components/[guild]/Requirements/components/GuildCheckout/components/TransactionStatusModal/TransactionStatusModal.tsx b/src/components/[guild]/Requirements/components/GuildCheckout/components/TransactionStatusModal/TransactionStatusModal.tsx index 06e48f8392..88435e00d6 100644 --- a/src/components/[guild]/Requirements/components/GuildCheckout/components/TransactionStatusModal/TransactionStatusModal.tsx +++ b/src/components/[guild]/Requirements/components/GuildCheckout/components/TransactionStatusModal/TransactionStatusModal.tsx @@ -5,6 +5,7 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/Dialog" +import { WaitForTransactionReceiptTimeoutError } from "viem" import { useTransactionStatusContext } from "../TransactionStatusContext" import { TxError } from "./components/TxError" import { TxInProgress } from "./components/TxInProgress" @@ -55,13 +56,15 @@ const TransactionStatusModal = ({ - {txError - ? "Transaction failed" - : txSuccess - ? (successTitle ?? "Successful payment") - : txHash - ? "Transaction is processing..." - : title} + {txError instanceof WaitForTransactionReceiptTimeoutError + ? "Timeout" + : txError + ? "Transaction failed" + : txSuccess + ? (successTitle ?? "Successful payment") + : txHash + ? "Transaction is processing..." + : title} diff --git a/src/components/[guild]/Requirements/components/GuildCheckout/components/TransactionStatusModal/components/TxError.tsx b/src/components/[guild]/Requirements/components/GuildCheckout/components/TransactionStatusModal/components/TxError.tsx index 9cf7144bf6..10c351c83b 100644 --- a/src/components/[guild]/Requirements/components/GuildCheckout/components/TransactionStatusModal/components/TxError.tsx +++ b/src/components/[guild]/Requirements/components/GuildCheckout/components/TransactionStatusModal/components/TxError.tsx @@ -1,22 +1,42 @@ import { DialogBody, DialogFooter } from "@/components/ui/Dialog" -import { XCircle } from "@phosphor-icons/react/dist/ssr" +import { Timer, XCircle } from "@phosphor-icons/react/dist/ssr" import { PropsWithChildren } from "react" +import { WaitForTransactionReceiptTimeoutError } from "viem" +import { useTransactionStatusContext } from "../../TransactionStatusContext" import { TransactionModalCloseButton } from "./TransactionModalCloseButton" -const TxError = ({ children }: PropsWithChildren): JSX.Element => ( - <> - -
- -
+const TxError = ({ children }: PropsWithChildren): JSX.Element => { + const { txError } = useTransactionStatusContext() - {children} -
+ const isTimeout = txError instanceof WaitForTransactionReceiptTimeoutError - - - - -) + return ( + <> + +
+ {isTimeout ? ( + + ) : ( + + )} +
+ + {isTimeout ? ( +

+ Your transaction is processing. Due to high network traffic, it may take + longer than usual. Check your wallet later for status updates. For + persistent issues, please contact our support. +

+ ) : ( + children + )} +
+ + + + + + ) +} export { TxError } diff --git a/src/components/[guild]/Requirements/components/GuildCheckout/hooks/useMintGuildPin.tsx b/src/components/[guild]/Requirements/components/GuildCheckout/hooks/useMintGuildPin.tsx index 1d5ed9c743..86fe48cd48 100644 --- a/src/components/[guild]/Requirements/components/GuildCheckout/hooks/useMintGuildPin.tsx +++ b/src/components/[guild]/Requirements/components/GuildCheckout/hooks/useMintGuildPin.tsx @@ -71,7 +71,7 @@ const useMintGuildPin = () => { const { triggerMembershipUpdate } = useMembershipUpdate() const mintGuildPin = async () => { - setTxError?.(false) + setTxError?.(null) setTxSuccess?.(false) setLoadingText("Uploading metadata") @@ -205,7 +205,7 @@ const useMintGuildPin = () => { ...useSubmit(mintGuildPin, { onError: (error) => { setLoadingText("") - setTxError?.(true) + setTxError?.(error) const prettyError = error.correlationId ? error diff --git a/src/components/[guild]/collect/hooks/useCollectNft.ts b/src/components/[guild]/collect/hooks/useCollectNft.ts index 815fb81524..dc9c82cc6e 100644 --- a/src/components/[guild]/collect/hooks/useCollectNft.ts +++ b/src/components/[guild]/collect/hooks/useCollectNft.ts @@ -103,9 +103,12 @@ const useCollectNft = () => { const claimAmount = claimAmountFromForm ?? 1 const mint = async () => { - setTxError(false) + setTxError(null) setTxSuccess(false) + if (!publicClient) return Promise.reject("Couldn't find publicClient") + if (!walletClient) return Promise.reject("Couldn't find walletClient") + if (shouldSwitchChain) return Promise.reject("Please switch network before minting") @@ -189,7 +192,7 @@ const useCollectNft = () => { mutateTopCollectors( (prevValue) => { - const lowerCaseUserAddress = userAddress.toLowerCase() + const lowerCaseUserAddress = userAddress?.toLowerCase() const alreadyCollected = !!prevValue?.topCollectors?.find( (collector) => collector.address.toLowerCase() === lowerCaseUserAddress ) @@ -241,13 +244,15 @@ const useCollectNft = () => { }, onError: (error) => { setLoadingText("") - setTxError(true) + setTxError(error) const prettyError = error.correlationId ? error : processViemContractError(error, (errorName) => { if (errorName === "AlreadyClaimed") return "You've already collected this NFT" + + return errorName }) if (isUserRejectedError(prettyError)) { diff --git a/src/hooks/useSubmitTransaction.ts b/src/hooks/useSubmitTransaction.ts index b68f31ee36..e66e6ee772 100644 --- a/src/hooks/useSubmitTransaction.ts +++ b/src/hooks/useSubmitTransaction.ts @@ -65,7 +65,7 @@ const useSubmitTransaction = ( ) const setTxError = useCallback( - (newState: boolean) => { + (newState: Error | null) => { if (options?.setContext === false || typeof setTxErrorInContext !== "function") return setTxErrorInContext(newState) @@ -162,8 +162,7 @@ const useSubmitTransaction = ( } if (error) { - setTxError(true) - + setTxError(rawError) onError?.(error, rawError) reset() } @@ -174,7 +173,7 @@ const useSubmitTransaction = ( return { onSubmitTransaction: () => { setTxHash("") - setTxError(false) + setTxError(null) setTxSuccess(false) if (!writeContract && error) {