From 7bb71075723b1b11d4be449c1212dae57bed5661 Mon Sep 17 00:00:00 2001 From: MananTank Date: Tue, 31 Dec 2024 22:18:30 +0000 Subject: [PATCH] [TOOL-2876] Improve Transaction error toast messages (#5853) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem solved Short description of the bug fixed or feature added --- ## PR-Codex overview This PR focuses on enhancing user feedback for NFT and token transactions by integrating `useTxNotifications` for success and error notifications across various components. ### Detailed summary - Added `useTxNotifications` to multiple components for transaction feedback. - Replaced toast notifications with `onSuccess` and `onError` methods from `useTxNotifications`. - Updated success and error messages for lazy minting, airdropping, minting, claiming, and listing NFTs/tokens. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` --- .../(marketplace)/components/list-form.tsx | 33 +++++++----- .../nfts/[tokenId]/components/airdrop-tab.tsx | 16 +++--- .../components/update-metadata-form.tsx | 16 +++--- .../nfts/components/claim-button.tsx | 15 +++--- .../nfts/components/lazy-mint-form.tsx | 16 +++--- .../nfts/components/mint-form.tsx | 17 +++--- .../nfts/components/shared-metadata-form.tsx | 18 ++++--- .../tokens/components/airdrop-form.tsx | 20 +++---- .../tokens/components/claim-button.tsx | 54 +++++++++---------- .../src/app/drops/[slug]/mint-ui.tsx | 12 +++-- 10 files changed, 119 insertions(+), 98 deletions(-) diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/components/list-form.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/components/list-form.tsx index c330fdc0bcf..b864b976426 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/components/list-form.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/components/list-form.tsx @@ -18,6 +18,7 @@ import { CurrencySelector } from "components/shared/CurrencySelector"; import { SolidityInput } from "contract-ui/components/solidity-inputs"; import { useTrack } from "hooks/analytics/useTrack"; import { useAllChainsData } from "hooks/chains/allChains"; +import { useTxNotifications } from "hooks/useTxNotifications"; import { isAlchemySupported } from "lib/wallet/nfts/alchemy"; import { isMoralisSupported } from "lib/wallet/nfts/moralis"; import { isSimpleHashSupported } from "lib/wallet/nfts/simpleHash"; @@ -125,6 +126,15 @@ export const CreateListingsForm: React.FC = ({ walletAddress: account?.address, }); const sendAndConfirmTx = useSendAndConfirmTransaction(); + const listingNotifications = useTxNotifications( + "NFT listed Successfully", + "Failed to list NFT", + ); + + const auctionNotifications = useTxNotifications( + "Auction created successfully", + "Failed to create an auction", + ); const form = useForm({ defaultValues: @@ -351,14 +361,11 @@ export const CreateListingsForm: React.FC = ({ endTimestamp, }); - const promise = sendAndConfirmTx.mutateAsync(transaction, { + await sendAndConfirmTx.mutateAsync(transaction, { onSuccess: () => setOpen(false), }); - toast.promise(promise, { - loading: "Listing NFT", - success: "NFT listed successfully", - error: "Failed to list NFT", - }); + + listingNotifications.onSuccess(); } else if (formData.listingType === "auction") { let minimumBidAmountWei: bigint; let buyoutBidAmountWei: bigint; @@ -403,7 +410,7 @@ export const CreateListingsForm: React.FC = ({ buyoutBidAmountWei: buyoutBidAmountWei * selectedQuantity, }); - const promise = sendAndConfirmTx.mutateAsync(transaction, { + await sendAndConfirmTx.mutateAsync(transaction, { onSuccess: () => { trackEvent({ category: "marketplace", @@ -423,15 +430,15 @@ export const CreateListingsForm: React.FC = ({ }); }, }); - toast.promise(promise, { - loading: "Creating auction", - success: "Auction created successfully", - error: "Failed to create auction", - }); + auctionNotifications.onSuccess(); } } catch (err) { console.error(err); - toast.error("Failed to list NFT"); + if (formData.listingType === "auction") { + auctionNotifications.onError(err); + } else { + listingNotifications.onError(err); + } } setIsFormLoading(false); diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/airdrop-tab.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/airdrop-tab.tsx index 4f517975475..dd336d4f168 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/airdrop-tab.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/airdrop-tab.tsx @@ -12,6 +12,7 @@ import { cn } from "@/lib/utils"; import type { Account } from "@3rdweb-sdk/react/hooks/useApi"; import { TransactionButton } from "components/buttons/TransactionButton"; import { useTrack } from "hooks/analytics/useTrack"; +import { useTxNotifications } from "hooks/useTxNotifications"; import { UploadIcon } from "lucide-react"; import { useState } from "react"; import { useForm } from "react-hook-form"; @@ -49,6 +50,10 @@ const AirdropTab: React.FC = ({ const sendAndConfirmTx = useSendAndConfirmTransaction(); const addresses = watch("addresses"); const [open, setOpen] = useState(false); + const airdropNotifications = useTxNotifications( + "NFTs airdropped successfully", + "Failed to airdrop NFTs", + ); return (
@@ -86,7 +91,7 @@ const AirdropTab: React.FC = ({ }), ); const transaction = multicall({ contract, data }); - const promise = sendAndConfirmTx.mutateAsync(transaction, { + await sendAndConfirmTx.mutateAsync(transaction, { onSuccess: () => { trackEvent({ category: "nft", @@ -108,14 +113,11 @@ const AirdropTab: React.FC = ({ }); }, }); - toast.promise(promise, { - loading: "Airdropping NFTs", - success: "Airdropped successfully", - error: "Failed to airdrop", - }); + + airdropNotifications.onSuccess(); } catch (err) { console.error(err); - toast.error("Failed to airdrop NFTs"); + airdropNotifications.onError(err); } })} > diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/update-metadata-form.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/update-metadata-form.tsx index b513f01fdcf..78a305d8823 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/update-metadata-form.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/update-metadata-form.tsx @@ -18,6 +18,7 @@ import { PropertiesFormControl } from "components/contract-pages/forms/propertie import { FileInput } from "components/shared/FileInput"; import { useTrack } from "hooks/analytics/useTrack"; import { useImageFileOrUrl } from "hooks/useImageFileOrUrl"; +import { useTxNotifications } from "hooks/useTxNotifications"; import { type Dispatch, type SetStateAction, useMemo } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; @@ -166,12 +167,16 @@ export const UpdateNftMetadata: React.FC = ({ watch("animation_url") instanceof File || watch("external_url") instanceof File; const sendAndConfirmTx = useSendAndConfirmTransaction(); + const updateMetadataNotifications = useTxNotifications( + "NFT metadata updated successfully", + "Failed to update NFT metadata", + ); return (
{ + onSubmit={handleSubmit(async (data) => { if (!address) { toast.error("Please connect your wallet to update metadata."); return; @@ -217,7 +222,7 @@ export const UpdateNftMetadata: React.FC = ({ tokenId: BigInt(nft.id), newMetadata, }); - const promise = sendAndConfirmTx.mutateAsync(transaction, { + await sendAndConfirmTx.mutateAsync(transaction, { onSuccess: () => { trackEvent({ category: "nft", @@ -237,13 +242,10 @@ export const UpdateNftMetadata: React.FC = ({ }, }); - toast.promise(promise, { - error: "Failed to update NFT metadata", - success: "NFT metadata updated successfully", - }); + updateMetadataNotifications.onSuccess(); } catch (err) { console.error(err); - toast.error("Failed to update NFT metadata"); + updateMetadataNotifications.onError(err); } })} > diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/claim-button.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/claim-button.tsx index 153ed209fb5..33d31e35650 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/claim-button.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/claim-button.tsx @@ -12,6 +12,7 @@ import type { Account } from "@3rdweb-sdk/react/hooks/useApi"; import { FormControl, Input } from "@chakra-ui/react"; import { TransactionButton } from "components/buttons/TransactionButton"; import { useTrack } from "hooks/analytics/useTrack"; +import { useTxNotifications } from "hooks/useTxNotifications"; import { GemIcon } from "lucide-react"; import { useState } from "react"; import { useForm } from "react-hook-form"; @@ -46,6 +47,10 @@ export const NFTClaimButton: React.FC = ({ const sendAndConfirmTx = useSendAndConfirmTransaction(); const account = useActiveAccount(); const [open, setOpen] = useState(false); + const claimNFTNotifications = useTxNotifications( + "NFT claimed successfully", + "Failed to claim NFT", + ); return ( @@ -135,7 +140,7 @@ export const NFTClaimButton: React.FC = ({ await promise; } - const promise = sendAndConfirmTx.mutateAsync(transaction, { + await sendAndConfirmTx.mutateAsync(transaction, { onSuccess: () => { trackEvent({ category: "nft", @@ -154,14 +159,10 @@ export const NFTClaimButton: React.FC = ({ }, }); - toast.promise(promise, { - loading: "Claiming NFT(s)", - success: "NFT(s) claimed successfully", - error: "Failed to claim NFT(s)", - }); + claimNFTNotifications.onSuccess(); } catch (error) { console.error(error); - toast.error((error as Error).message || "Error claiming NFT"); + claimNFTNotifications.onError(error); trackEvent({ category: "nft", action: "claim", diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/lazy-mint-form.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/lazy-mint-form.tsx index f6a257d59d1..507bd9f016d 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/lazy-mint-form.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/lazy-mint-form.tsx @@ -19,6 +19,7 @@ import { PropertiesFormControl } from "components/contract-pages/forms/propertie import { FileInput } from "components/shared/FileInput"; import { useTrack } from "hooks/analytics/useTrack"; import { useImageFileOrUrl } from "hooks/useImageFileOrUrl"; +import { useTxNotifications } from "hooks/useTxNotifications"; import type { Dispatch, SetStateAction } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; @@ -134,6 +135,11 @@ export const LazyMintNftForm: React.FC = ({ watch("animation_url") instanceof File || watch("external_url") instanceof File; + const lazyMintNotifications = useTxNotifications( + "NFT lazy minted successfully", + "Failed to lazy mint NFT", + ); + return ( <> = ({ ? lazyMint721({ contract, nfts }) : lazyMint1155({ contract, nfts }); - const promise = sendAndConfirmTx.mutateAsync(transaction, { + await sendAndConfirmTx.mutateAsync(transaction, { onSuccess: () => { trackEvent({ category: "nft", @@ -174,14 +180,10 @@ export const LazyMintNftForm: React.FC = ({ }, }); - toast.promise(promise, { - loading: "Lazy minting NFT", - error: "Failed to lazy mint NFT", - success: "Lazy minted successfully", - }); + lazyMintNotifications.onSuccess(); } catch (err) { console.error(err); - toast.error("Failed to lazy mint NFT"); + lazyMintNotifications.onError(err); } })} > diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/mint-form.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/mint-form.tsx index c76644f9bcf..d252aeba2d3 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/mint-form.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/mint-form.tsx @@ -18,6 +18,7 @@ import { PropertiesFormControl } from "components/contract-pages/forms/propertie import { FileInput } from "components/shared/FileInput"; import { useTrack } from "hooks/analytics/useTrack"; import { useImageFileOrUrl } from "hooks/useImageFileOrUrl"; +import { useTxNotifications } from "hooks/useTxNotifications"; import type { Dispatch, SetStateAction } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; @@ -133,13 +134,17 @@ export const NFTMintForm: React.FC = ({ watch("external_url") instanceof File; const sendAndConfirmTx = useSendAndConfirmTransaction(); + const nftMintNotifications = useTxNotifications( + "NFT minted successfully", + "Failed to mint NFT", + ); return ( <> { + onSubmit={handleSubmit(async (data) => { if (!address) { toast.error("Please connect your wallet to mint."); return; @@ -166,7 +171,7 @@ export const NFTMintForm: React.FC = ({ nft, supply: BigInt(data.supply), }); - const promise = sendAndConfirmTx.mutateAsync(transaction, { + await sendAndConfirmTx.mutateAsync(transaction, { onSuccess: () => { trackEvent({ category: "nft", @@ -186,14 +191,10 @@ export const NFTMintForm: React.FC = ({ }, }); - toast.promise(promise, { - loading: "Minting NFT", - success: "NFT minted successfully", - error: "Failed to mint NFT", - }); + nftMintNotifications.onSuccess(); } catch (err) { + nftMintNotifications.onError(err); console.error(err); - toast.error("Failed to mint NFT"); } })} > diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/shared-metadata-form.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/shared-metadata-form.tsx index 2f0f72e9ab2..b2681a97eb7 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/shared-metadata-form.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/shared-metadata-form.tsx @@ -16,6 +16,7 @@ import { TransactionButton } from "components/buttons/TransactionButton"; import { FileInput } from "components/shared/FileInput"; import { useTrack } from "hooks/analytics/useTrack"; import { useImageFileOrUrl } from "hooks/useImageFileOrUrl"; +import { useTxNotifications } from "hooks/useTxNotifications"; import type { Dispatch, SetStateAction } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; @@ -116,12 +117,17 @@ export const SharedMetadataForm: React.FC<{ watch("animation_url") instanceof File || watch("external_url") instanceof File; + const setSharedMetaNotifications = useTxNotifications( + "Shared metadata updated successfully", + "Failed to update shared metadata", + ); + return ( <> { + onSubmit={handleSubmit(async (data) => { if (!address) { toast.error("Please connect your wallet."); return; @@ -143,7 +149,7 @@ export const SharedMetadataForm: React.FC<{ contract, nft: parseAttributes(dataWithCustom), }); - const promise = sendAndConfirmTx.mutateAsync(transaction, { + await sendAndConfirmTx.mutateAsync(transaction, { onSuccess: () => { trackEvent({ category: "nft", @@ -163,14 +169,10 @@ export const SharedMetadataForm: React.FC<{ }, }); - toast.promise(promise, { - loading: "Setting shared metadata", - error: "Error setting NFT metadata", - success: "Shared metadata updated successfully", - }); + setSharedMetaNotifications.onSuccess(); } catch (err) { console.error(err); - toast.error("Failed to set shared metadata"); + setSharedMetaNotifications.onError(err); } })} > diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/tokens/components/airdrop-form.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/tokens/components/airdrop-form.tsx index 3920342c2bd..1216c4ae6c2 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/tokens/components/airdrop-form.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/tokens/components/airdrop-form.tsx @@ -3,10 +3,10 @@ import type { Account } from "@3rdweb-sdk/react/hooks/useApi"; import { TransactionButton } from "components/buttons/TransactionButton"; import { useTrack } from "hooks/analytics/useTrack"; +import { useTxNotifications } from "hooks/useTxNotifications"; import { CircleCheck, Upload } from "lucide-react"; import { type Dispatch, type SetStateAction, useState } from "react"; import { useForm } from "react-hook-form"; -import { toast } from "sonner"; import type { ThirdwebContract } from "thirdweb"; import { transferBatch } from "thirdweb/extensions/erc20"; import { useSendAndConfirmTransaction } from "thirdweb/react"; @@ -37,11 +37,17 @@ export const TokenAirdropForm: React.FC = ({ // The real number should be slightly higher since there's a lil bit of overhead cost const estimateGasCost = GAS_COST_PER_ERC20_TRANSFER * (addresses || []).length; + + const airdropNotifications = useTxNotifications( + "Tokens airdropped successfully", + "Failed to airdrop tokens", + ); + return ( <>
{ + onSubmit={handleSubmit(async (data) => { try { trackEvent({ category: "token", @@ -58,7 +64,7 @@ export const TokenAirdropForm: React.FC = ({ amount: address.quantity, })), }); - const promise = sendTransaction.mutateAsync(tx, { + await sendTransaction.mutateAsync(tx, { onSuccess: () => { trackEvent({ category: "token", @@ -82,14 +88,10 @@ export const TokenAirdropForm: React.FC = ({ console.error(error); }, }); - toast.promise(promise, { - loading: "Airdropping tokens", - success: "Tokens airdropped successfully", - error: "Failed to airdrop tokens", - }); + airdropNotifications.onSuccess(); } catch (err) { + airdropNotifications.onError(err); console.error(err); - toast.error("Failed to airdrop tokens"); } })} > diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/tokens/components/claim-button.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/tokens/components/claim-button.tsx index 7f3aa5fd1a3..abe4ac1beb3 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/tokens/components/claim-button.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/tokens/components/claim-button.tsx @@ -13,6 +13,7 @@ import type { Account } from "@3rdweb-sdk/react/hooks/useApi"; import { FormControl, Input } from "@chakra-ui/react"; import { TransactionButton } from "components/buttons/TransactionButton"; import { useTrack } from "hooks/analytics/useTrack"; +import { useTxNotifications } from "hooks/useTxNotifications"; import { GemIcon } from "lucide-react"; import { useState } from "react"; import { useForm } from "react-hook-form"; @@ -48,6 +49,10 @@ export const TokenClaimButton: React.FC = ({ const { data: _decimals, isPending } = useReadContract(ERC20Ext.decimals, { contract, }); + const claimTokensNotifications = useTxNotifications( + "Tokens claimed successfully", + "Failed to claim tokens", + ); return ( @@ -140,38 +145,31 @@ export const TokenClaimButton: React.FC = ({ await promise; } - const promise = sendAndConfirmTransaction.mutateAsync( - transaction, - { - onSuccess: () => { - trackEvent({ - category: "token", - action: "claim", - label: "success", - }); - form.reset({ amount: "0", to: account?.address }); - setOpen(false); - }, - onError: (error) => { - trackEvent({ - category: "token", - action: "claim", - label: "error", - error, - }); - console.error(error); - }, + await sendAndConfirmTransaction.mutateAsync(transaction, { + onSuccess: () => { + trackEvent({ + category: "token", + action: "claim", + label: "success", + }); + form.reset({ amount: "0", to: account?.address }); + setOpen(false); + }, + onError: (error) => { + trackEvent({ + category: "token", + action: "claim", + label: "error", + error, + }); + console.error(error); }, - ); - - toast.promise(promise, { - loading: "Claiming tokens", - success: "Token claimed successfully", - error: "Failed to claim tokens", }); + + claimTokensNotifications.onSuccess(); } catch (error) { console.error(error); - toast.error("Failed to claim tokens"); + claimTokensNotifications.onError(error); trackEvent({ category: "token", action: "claim", diff --git a/apps/dashboard/src/app/drops/[slug]/mint-ui.tsx b/apps/dashboard/src/app/drops/[slug]/mint-ui.tsx index a596a6073fa..ff2b24b1e70 100644 --- a/apps/dashboard/src/app/drops/[slug]/mint-ui.tsx +++ b/apps/dashboard/src/app/drops/[slug]/mint-ui.tsx @@ -8,10 +8,10 @@ import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; import { useThirdwebClient } from "@/constants/thirdweb.client"; import { CustomConnectWallet } from "@3rdweb-sdk/react/components/connect-wallet"; +import { useTxNotifications } from "hooks/useTxNotifications"; import { MinusIcon, PlusIcon } from "lucide-react"; import { useState } from "react"; import type React from "react"; -import { toast } from "sonner"; import type { ThirdwebContract } from "thirdweb"; import { balanceOf as balanceOfERC721 } from "thirdweb/extensions/erc721"; import { balanceOf as balanceOfERC1155 } from "thirdweb/extensions/erc1155"; @@ -89,6 +89,11 @@ export function NftMint(props: Props) { props.noActiveClaimCondition === false && props.quantityLimitPerWallet === ownedAmount; + const mintNotifications = useTxNotifications( + "NFT minted successfully", + "Failed to mint NFT", + ); + return (
@@ -201,15 +206,14 @@ export function NftMint(props: Props) { isMinting || props.noActiveClaimCondition || fullyMinted } onTransactionSent={() => { - toast.loading("Minting NFT", { id: "toastId" }); setIsMinting(true); }} onTransactionConfirmed={() => { - toast.success("Minted successfully", { id: "toastId" }); + mintNotifications.onSuccess(); setIsMinting(false); }} onError={(err) => { - toast.error(err.message, { id: "toastId" }); + mintNotifications.onError(err); setIsMinting(false); }} >