From 154241ad0c782aaa9f1facfb8b66e777e1bc2262 Mon Sep 17 00:00:00 2001 From: Mithat Akbulut Date: Wed, 17 Sep 2025 17:57:13 +0300 Subject: [PATCH 01/34] feat: enhance FallbackPurchaseUI with WaaS fee options integration - Added support for WaaS fee options in the FallbackPurchaseUI component. - Integrated `useSelectWaasFeeOptions` and `selectWaasFeeOptionsStore` for managing fee visibility and selection. - Updated the buy button label dynamically based on the selected fee option. - Improved transaction handling by showing fee options when applicable, particularly for non-testnet environments. - Refactored conditional rendering for the buy button and fee options to enhance user experience. --- .../components/FallbackPurchaseUI.tsx | 68 +++++++++++++++---- 1 file changed, 56 insertions(+), 12 deletions(-) diff --git a/sdk/src/react/ui/modals/BuyModal/components/FallbackPurchaseUI.tsx b/sdk/src/react/ui/modals/BuyModal/components/FallbackPurchaseUI.tsx index 5d55f0244..ba78b8b94 100644 --- a/sdk/src/react/ui/modals/BuyModal/components/FallbackPurchaseUI.tsx +++ b/sdk/src/react/ui/modals/BuyModal/components/FallbackPurchaseUI.tsx @@ -20,6 +20,16 @@ import { Media } from '../../../components/media/Media'; import { useBuyModalData } from '../hooks/useBuyModalData'; import { useHasSufficientBalance } from '../hooks/useHasSufficientBalance'; import { FallbackPurchaseUISkeleton } from './FallbackPurchaseUISkeleton'; +import { + selectWaasFeeOptionsStore, + useSelectWaasFeeOptionsStore, +} from '../../_internal/components/selectWaasFeeOptions/store'; +import { useSelectWaasFeeOptions } from '../../_internal/hooks/useSelectWaasFeeOptions'; +import { FeeOption } from '../../../../../types/waas-types'; +import SelectWaasFeeOptions from '../../_internal/components/selectWaasFeeOptions'; +import { useConnectorMetadata } from '../../../../hooks'; +import { getNetwork } from '@0xsequence/connect'; +import { NetworkType } from '@0xsequence/network'; export interface FallbackPurchaseUIProps { chainId: number; @@ -35,6 +45,7 @@ export const FallbackPurchaseUI = ({ const [isExecuting, setIsExecuting] = useState(false); const [isApproving, setIsApproving] = useState(false); const [isSwitchingChain, setIsSwitchingChain] = useState(false); + const { isWaaS } = useConnectorMetadata(); const buyStep = steps.find((step) => step.id === StepType.buy); if (!buyStep) throw new Error('Buy step not found'); @@ -63,6 +74,24 @@ export const FallbackPurchaseUI = ({ ? currencyAddress : (salePrice?.currencyAddress as Address); + const { isVisible: feeOptionsVisible, selectedFeeOption } = + useSelectWaasFeeOptionsStore(); + const isAnyTransactionPending = + isApproving || isExecuting || isSwitchingChain; + + const network = getNetwork(Number(chainId)); + const isTestnet = network.type === NetworkType.TESTNET; + + const { + shouldHideActionButton: shouldHideBuyButton, + waasFeeOptionsShown, + getActionLabel, + } = useSelectWaasFeeOptions({ + isProcessing: isAnyTransactionPending, + feeOptionsVisible, + selectedFeeOption: selectedFeeOption as FeeOption, + }); + const { data, isLoading: isLoadingBalance } = useHasSufficientBalance({ chainId, value: BigInt(priceAmount || 0), @@ -81,7 +110,7 @@ export const FallbackPurchaseUI = ({ const to = step.to as Address; const value = BigInt(step.value); - if (!data || !to || !value) { + if (!data || !to) { toast({ title: 'Invalid step', variant: 'error', @@ -129,6 +158,10 @@ export const FallbackPurchaseUI = ({ const executeBuy = async () => { setIsExecuting(true); try { + if (isWaaS && !isTestnet) { + selectWaasFeeOptionsStore.send({ type: 'show' }); + } + const hash = await executeTransaction(buyStep); onSuccess(hash); @@ -190,8 +223,6 @@ export const FallbackPurchaseUI = ({ return `${formattedPrice} ${currency?.symbol}`; }; - const isAnyTransactionPending = - isApproving || isExecuting || isSwitchingChain; const canApprove = hasSufficientBalance && !isLoadingBalance && @@ -203,6 +234,7 @@ export const FallbackPurchaseUI = ({ !isLoadingBuyModalData && !approvalStep && isOnCorrectChain; + const buyButtonLabel = getActionLabel('Buy now'); if (isLoadingBuyModalData || isLoadingBalance) { return ( @@ -307,15 +339,27 @@ export const FallbackPurchaseUI = ({ /> )} - + + {showFullError &&
} + + {showFullError && ( +
+ + {error.message} + {error.stack && ( + <> + {'\n\nStack trace:\n'} + {error.stack} + + )} + +
+ )} +
+ )} + + + {onDismiss && ( + + )} + + + ); +}; diff --git a/sdk/src/react/ui/modals/BuyModal/components/FallbackPurchaseUI.tsx b/sdk/src/react/ui/modals/BuyModal/components/FallbackPurchaseUI.tsx index 2da9abf4d..09f096d0c 100644 --- a/sdk/src/react/ui/modals/BuyModal/components/FallbackPurchaseUI.tsx +++ b/sdk/src/react/ui/modals/BuyModal/components/FallbackPurchaseUI.tsx @@ -7,7 +7,6 @@ import { Spinner, Text, Tooltip, - useToast, } from '@0xsequence/design-system'; import { useState } from 'react'; import type { Address, Hex } from 'viem'; @@ -22,6 +21,7 @@ import { Media } from '../../../components/media/Media'; import { selectWaasFeeOptionsStore } from '../../_internal/components/selectWaasFeeOptions/store'; import { useBuyModalData } from '../hooks/useBuyModalData'; import { useHasSufficientBalance } from '../hooks/useHasSufficientBalance'; +import { ErrorLogBox } from './ErrorLogBox'; import { FallbackPurchaseUISkeleton } from './FallbackPurchaseUISkeleton'; import { useExecutePurchaseWithWaas } from './hook/useExecutePurchaseWithWaas'; @@ -39,6 +39,11 @@ export const FallbackPurchaseUI = ({ const [isExecuting, setIsExecuting] = useState(false); const [isApproving, setIsApproving] = useState(false); const [isSwitchingChain, setIsSwitchingChain] = useState(false); + const [error, setError] = useState<{ + title: string; + message: string; + details?: Error; + } | null>(null); const { isWaaS } = useConnectorMetadata(); const buyStep = steps.find((step) => step.id === StepType.buy); @@ -55,7 +60,6 @@ export const FallbackPurchaseUI = ({ isLoading: isLoadingBuyModalData, } = useBuyModalData(); const sdkConfig = useConfig(); - const toast = useToast(); const { ensureCorrectChainAsync, currentChainId } = useEnsureCorrectChain(); const isOnCorrectChain = currentChainId === chainId; @@ -96,17 +100,20 @@ export const FallbackPurchaseUI = ({ const value = BigInt(step.value); if (!data || !to) { - toast({ - title: 'Invalid step', - variant: 'error', - description: 'data, to and value are required', - }); - throw new Error(`Invalid step. data, to and value are required: + const errorDetails = + new Error(`Invalid step. data, to and value are required: data: ${data} to: ${to} value: ${value} ${JSON.stringify(step)}`); + + setError({ + title: 'Invalid step', + message: 'data, to and value are required', + details: errorDetails, + }); + throw errorDetails; } await ensureCorrectChainAsync(chainId); @@ -129,6 +136,7 @@ export const FallbackPurchaseUI = ({ const executeApproval = async () => { if (!approvalStep) throw new Error('Approval step not found'); + setError(null); // Clear any previous errors setIsApproving(true); try { if (isWaaS) { @@ -145,6 +153,7 @@ export const FallbackPurchaseUI = ({ }; const executeBuy = async () => { + setError(null); // Clear any previous errors setIsExecuting(true); try { if (isWaaS) { @@ -173,21 +182,20 @@ export const FallbackPurchaseUI = ({ }; const handleBalanceInsufficientForWaasFeeOption = (error: Error) => { - toast({ + setError({ title: 'Insufficient balance for fee option', - variant: 'error', - description: - 'You do not have enough balance to purchase this item. See console for more details.', + message: 'You do not have enough balance to purchase this item.', + details: error, }); console.error('Balance insufficient for fee option:', error); }; const handleTransactionFailed = (error: Error) => { - toast({ + setError({ title: 'Transaction failed', - variant: 'error', - description: error.message, + message: error.message, + details: error, }); console.error('Transaction failed:', error); @@ -357,8 +365,9 @@ export const FallbackPurchaseUI = ({ {canBuy && (