diff --git a/src/app/[...recipient]/client.tsx b/src/app/[...recipient]/client.tsx index 007190da8..bbb5fd15c 100644 --- a/src/app/[...recipient]/client.tsx +++ b/src/app/[...recipient]/client.tsx @@ -26,7 +26,6 @@ import { useEffect, useMemo, useRef, useState } from 'react' import { twMerge } from 'tailwind-merge' import { fetchTokenPrice } from '@/app/actions/tokens' import { RequestFulfillmentBankFlowStep, useRequestFulfillmentFlow } from '@/context/RequestFulfillmentFlowContext' -import ExternalWalletFulfilManager from '@/components/Request/views/ExternalWalletFulfilManager' import ActionList from '@/components/Common/ActionList' import NavHeader from '@/components/Global/NavHeader' import { ReqFulfillBankFlowManager } from '@/components/Request/views/ReqFulfillBankFlowManager' @@ -67,7 +66,6 @@ export default function PaymentPage({ recipient, flow = 'request_pay' }: Props) const { isDrawerOpen, selectedTransaction, openTransactionDetails } = useTransactionDetailsDrawer() const [isLinkCancelling, setisLinkCancelling] = useState(false) const { - showExternalWalletFulfillMethods, showRequestFulfilmentBankFlowManager, setShowRequestFulfilmentBankFlowManager, setFlowStep: setRequestFulfilmentBankFlowStep, @@ -524,11 +522,6 @@ export default function PaymentPage({ recipient, flow = 'request_pay' }: Props) ) } - // render external wallet fulfilment methods - if (showExternalWalletFulfillMethods) { - return - } - // render request fulfilment bank flow manager if (showRequestFulfilmentBankFlowManager) { return diff --git a/src/components/Common/ActionList.tsx b/src/components/Common/ActionList.tsx index 657e439ee..1aecc768e 100644 --- a/src/components/Common/ActionList.tsx +++ b/src/components/Common/ActionList.tsx @@ -5,7 +5,7 @@ import IconStack from '../Global/IconStack' import { ClaimBankFlowStep, useClaimBankFlow } from '@/context/ClaimBankFlowContext' import { type ClaimLinkData } from '@/services/sendLinks' import { formatUnits } from 'viem' -import { useContext, useMemo, useState } from 'react' +import { useContext, useMemo, useState, useRef } from 'react' import ActionModal from '@/components/Global/ActionModal' import Divider from '../0_Bruddle/Divider' import { Button } from '../0_Bruddle' @@ -86,7 +86,6 @@ export default function ActionList({ const { addParamStep } = useClaimLink() const { setShowRequestFulfilmentBankFlowManager, - setShowExternalWalletFulfillMethods, setFlowStep: setRequestFulfilmentBankFlowStep, setFulfillUsingManteca, setRegionalMethodType: setRequestFulfillmentRegionalMethodType, @@ -109,6 +108,8 @@ export default function ActionList({ const { initiatePayment, loadingStep } = usePaymentInitiator() const { isUserMantecaKycApproved } = useKycStatus() const isPaymentInProgress = loadingStep !== 'Idle' && loadingStep !== 'Error' && loadingStep !== 'Success' + // ref to store daimo button click handler for triggering from balance modal + const daimoButtonClickRef = useRef<(() => void) | null>(null) const dispatch = useAppDispatch() @@ -255,9 +256,7 @@ export default function ActionList({ setRequestFulfillmentRegionalMethodType(method.id) setFulfillUsingManteca(true) break - case 'exchange-or-wallet': - setShowExternalWalletFulfillMethods(true) - break + // 'exchange-or-wallet' case removed - handled by ActionListDaimoPayButton } } } @@ -329,6 +328,7 @@ export default function ActionList({ return true // Proceed with Daimo }} isDisabled={!isAmountEntered} + clickHandlerRef={daimoButtonClickRef} /> ) @@ -427,7 +427,16 @@ export default function ActionList({ setIsUsePeanutBalanceModalShown(true) // Proceed with the method the user originally selected if (selectedPaymentMethod) { - handleMethodClick(selectedPaymentMethod, true) // true = bypass modal check + // for exchange-or-wallet, trigger daimo button after state updates + if (selectedPaymentMethod.id === 'exchange-or-wallet' && daimoButtonClickRef.current) { + // use setTimeout to ensure state updates are processed before triggering daimo + setTimeout(() => { + daimoButtonClickRef.current?.() + }, 0) + } else { + // for other methods, use handleMethodClick + handleMethodClick(selectedPaymentMethod, true) // true = bypass modal check + } } setSelectedPaymentMethod(null) }, diff --git a/src/components/Common/ActionListDaimoPayButton.tsx b/src/components/Common/ActionListDaimoPayButton.tsx index a2683aeea..609978550 100644 --- a/src/components/Common/ActionListDaimoPayButton.tsx +++ b/src/components/Common/ActionListDaimoPayButton.tsx @@ -17,6 +17,7 @@ interface ActionListDaimoPayButtonProps { showConfirmModal: boolean onBeforeShow?: () => boolean | Promise isDisabled?: boolean + clickHandlerRef?: React.MutableRefObject<(() => void) | null> } const ActionListDaimoPayButton = ({ @@ -24,6 +25,7 @@ const ActionListDaimoPayButton = ({ showConfirmModal, onBeforeShow, isDisabled, + clickHandlerRef, }: ActionListDaimoPayButtonProps) => { const dispatch = useAppDispatch() const searchParams = useSearchParams() @@ -112,10 +114,18 @@ const ActionListDaimoPayButton = ({ if (chargeDetails) { dispatch(paymentActions.setIsDaimoPaymentProcessing(true)) try { + // validate and parse destination chain id with proper fallback + // use chargeDetails chainId if it's a valid non-negative integer, otherwise use daimo response + const parsedChainId = Number(chargeDetails.chainId) + const destinationChainId = + Number.isInteger(parsedChainId) && parsedChainId >= 0 + ? parsedChainId + : Number(daimoPaymentResponse.payment.destination.chainId) + const result = await completeDaimoPayment({ chargeDetails: chargeDetails, txHash: daimoPaymentResponse.txHash as string, - destinationchainId: daimoPaymentResponse.payment.destination.chainId, + destinationchainId: destinationChainId, payerAddress: peanutWalletAddress ?? daimoPaymentResponse.payment.source.payerAddress, sourceChainId: daimoPaymentResponse.payment.source.chainId, sourceTokenAddress: daimoPaymentResponse.payment.source.tokenAddress, @@ -148,6 +158,8 @@ const ActionListDaimoPayButton = ({ { // First check if parent wants to intercept (e.g. show balance modal) @@ -178,6 +190,10 @@ const ActionListDaimoPayButton = ({ {({ onClick, loading }) => { // Store the onClick function so we can trigger it from elsewhere daimoPayButtonClickRef.current = onClick + // also store in parent ref if provided (for balance modal in ActionList) + if (clickHandlerRef) { + clickHandlerRef.current = onClick + } return ( t.symbol.toLowerCase() === 'usdc') + if (defaultToken) { + setSelectedTokenAddress(defaultToken.address) + } } setInitialSetupDone(true) - }, [chain, token, amount, initialSetupDone, requestDetails, showRequestPotInitialView, isRequestPotLink]) + }, [ + chain, + token, + amount, + initialSetupDone, + requestDetails, + showRequestPotInitialView, + isRequestPotLink, + recipient?.recipientType, + selectedChainID, + selectedTokenAddress, + supportedSquidChainsAndTokens, + ]) // reset error when component mounts or recipient changes useEffect(() => { @@ -244,12 +264,14 @@ export const PaymentForm = ({ } } else { // regular send/pay + const isExternalRecipient = recipient?.recipientType === 'ADDRESS' || recipient?.recipientType === 'ENS' + if ( !showRequestPotInitialView && // don't apply balance check on request pot payment initial view isActivePeanutWallet && - areEvmAddressesEqual(selectedTokenAddress, PEANUT_WALLET_TOKEN) + (areEvmAddressesEqual(selectedTokenAddress, PEANUT_WALLET_TOKEN) || !isExternalRecipient) ) { - // peanut wallet payment + // peanut wallet payment (for USERNAME or default token) const walletNumeric = parseFloat(String(peanutWalletBalance).replace(/,/g, '')) if (walletNumeric < parsedInputAmount) { dispatch(paymentActions.setError('Insufficient balance')) @@ -274,6 +296,9 @@ export const PaymentForm = ({ } else { dispatch(paymentActions.setError(null)) } + } else if (isExternalRecipient && isActivePeanutWallet) { + // for external recipients with peanut wallet, balance will be checked via cross-chain route + dispatch(paymentActions.setError(null)) } else { dispatch(paymentActions.setError(null)) } @@ -304,6 +329,7 @@ export const PaymentForm = ({ currentView, isProcessing, hasPendingTransactions, + recipient?.recipientType, ]) // Calculate USD value when requested token price is available @@ -339,7 +365,12 @@ export const PaymentForm = ({ (!!inputTokenAmount && parseFloat(inputTokenAmount) > 0) || (!!usdValue && parseFloat(usdValue) > 0) } - const tokenSelected = !!selectedTokenAddress && !!selectedChainID + const isExternalRecipient = recipient?.recipientType === 'ADDRESS' || recipient?.recipientType === 'ENS' + // for external recipients, token selection is required + // for USERNAME recipients, token is always PEANUT_WALLET_TOKEN + const tokenSelected = isExternalRecipient + ? !!selectedTokenAddress && !!selectedChainID + : !!selectedTokenAddress && !!selectedChainID const recipientExists = !!recipient const walletConnected = isConnected @@ -678,11 +709,7 @@ export const PaymentForm = ({ }, [recipient]) const handleGoBack = () => { - if (isExternalWalletFlow) { - setShowExternalWalletFulfillMethods(true) - setExternalWalletFulfillMethod(null) - return - } else if (window.history.length > 1) { + if (window.history.length > 1) { router.back() } else { router.push('/') @@ -809,30 +836,25 @@ export const PaymentForm = ({ defaultSliderSuggestedAmount={defaultSliderValue.suggestedAmount} /> - {/* - Url request flow (peanut.me/
) - If we are paying from peanut wallet we only need to - select a token if it's not included in the url - From other wallets we always need to select a token - */} - {/* we dont need this as daimo will handle token selection */} - {/* {!(chain && isPeanutWalletConnected) && isConnected && !isAddMoneyFlow && ( -
- {!isPeanutWalletUSDC && !selectedTokenAddress && !selectedChainID && ( -
Select token and chain to receive
- )} - - {!isPeanutWalletUSDC && selectedTokenAddress && selectedChainID && ( -
- Use USDC on Arbitrum for free transactions! -
- )} -
- )} */} - - {/* {isExternalWalletConnected && isAddMoneyFlow && ( - - )} */} + {/* Token selector for external ADDRESS/ENS recipients */} + {!isExternalWalletFlow && + !showRequestPotInitialView && + (recipient?.recipientType === 'ADDRESS' || recipient?.recipientType === 'ENS') && + isConnected && ( +
+ + {selectedTokenAddress && + selectedChainID && + !( + areEvmAddressesEqual(selectedTokenAddress, PEANUT_WALLET_TOKEN) && + selectedChainID === PEANUT_WALLET_CHAIN.id.toString() + ) && ( +
+ Use USDC on Arbitrum for free transactions! +
+ )} +
+ )} {isDirectUsdPayment && ( - ) - case 'CONFIRM': - return - case 'STATUS': - return ( - - ) - default: - break - } - } - - if (externalWalletFulfillMethod === 'exchange') { - return ( - { - setExternalWalletFulfillMethod(null) - setShowExternalWalletFulfillMethods(true) - }} - /> - ) - } - - if (showExternalWalletFulfillMethods) { - return setShowExternalWalletFulfillMethods(false)} /> - } - - return null -} diff --git a/src/components/Request/views/ExternalWalletFulfilMethods.tsx b/src/components/Request/views/ExternalWalletFulfilMethods.tsx deleted file mode 100644 index 94acb7fd8..000000000 --- a/src/components/Request/views/ExternalWalletFulfilMethods.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { BINANCE_LOGO, LEMON_LOGO, RIPIO_LOGO } from '@/assets' -import { METAMASK_LOGO, RAINBOW_LOGO, TRUST_WALLET_SMALL_LOGO } from '@/assets/wallets' -import { MethodCard } from '@/components/Common/ActionList' -import NavHeader from '@/components/Global/NavHeader' -import { type PaymentMethod } from '@/constants/actionlist.consts' -import { type ExternalWalletFulfilMethod, useRequestFulfillmentFlow } from '@/context/RequestFulfillmentFlowContext' - -const methods: PaymentMethod[] = [ - { - id: 'exchange', - title: 'Exchange', - description: 'Lemon, Binance, Ripio and more', - icons: [RIPIO_LOGO, BINANCE_LOGO, LEMON_LOGO], - soon: false, - }, - { - id: 'wallet', - title: 'Crypto Wallet', - description: 'Metamask, Trustwallet and more', - icons: [RAINBOW_LOGO, TRUST_WALLET_SMALL_LOGO, METAMASK_LOGO], - soon: false, - }, -] - -export default function ExternalWalletFulfilMethods({ onBack }: { onBack: () => void }) { - const { setExternalWalletFulfillMethod } = useRequestFulfillmentFlow() - - return ( -
- -
-
Where will you send from?
- {methods.map((method) => ( - { - setExternalWalletFulfillMethod(method.id as ExternalWalletFulfilMethod) - }} - /> - ))} -
-
- ) -} diff --git a/src/context/RequestFulfillmentFlowContext.tsx b/src/context/RequestFulfillmentFlowContext.tsx index 82ee98a12..d9400aa24 100644 --- a/src/context/RequestFulfillmentFlowContext.tsx +++ b/src/context/RequestFulfillmentFlowContext.tsx @@ -5,8 +5,6 @@ import { type CountryData } from '@/components/AddMoney/consts' import { type IOnrampData } from './OnrampFlowContext' import { type User } from '@/interfaces' -export type ExternalWalletFulfilMethod = 'exchange' | 'wallet' - export enum RequestFulfillmentBankFlowStep { BankCountryList = 'bank-country-list', DepositBankDetails = 'deposit-bank-details', @@ -16,12 +14,8 @@ export enum RequestFulfillmentBankFlowStep { interface RequestFulfillmentFlowContextType { resetFlow: () => void - showExternalWalletFulfillMethods: boolean - setShowExternalWalletFulfillMethods: (showExternalWalletFulfillMethods: boolean) => void showRequestFulfilmentBankFlowManager: boolean setShowRequestFulfilmentBankFlowManager: (showRequestFulfilmentBankFlowManager: boolean) => void - externalWalletFulfillMethod: ExternalWalletFulfilMethod | null - setExternalWalletFulfillMethod: (externalWalletFulfillMethod: ExternalWalletFulfilMethod | null) => void flowStep: RequestFulfillmentBankFlowStep | null setFlowStep: (step: RequestFulfillmentBankFlowStep | null) => void selectedCountry: CountryData | null @@ -43,10 +37,6 @@ interface RequestFulfillmentFlowContextType { const RequestFulfillmentFlowContext = createContext(undefined) export const RequestFulfilmentFlowContextProvider: React.FC<{ children: ReactNode }> = ({ children }) => { - const [showExternalWalletFulfillMethods, setShowExternalWalletFulfillMethods] = useState(false) - const [externalWalletFulfillMethod, setExternalWalletFulfillMethod] = useState( - null - ) const [showRequestFulfilmentBankFlowManager, setShowRequestFulfilmentBankFlowManager] = useState(false) const [flowStep, setFlowStep] = useState(null) const [selectedCountry, setSelectedCountry] = useState(null) @@ -58,8 +48,6 @@ export const RequestFulfilmentFlowContextProvider: React.FC<{ children: ReactNod const [triggerPayWithPeanut, setTriggerPayWithPeanut] = useState(false) // To trigger the pay with peanut from Action List const resetFlow = useCallback(() => { - setExternalWalletFulfillMethod(null) - setShowExternalWalletFulfillMethods(false) setFlowStep(null) setShowRequestFulfilmentBankFlowManager(false) setSelectedCountry(null) @@ -73,10 +61,6 @@ export const RequestFulfilmentFlowContextProvider: React.FC<{ children: ReactNod const value = useMemo( () => ({ resetFlow, - externalWalletFulfillMethod, - setExternalWalletFulfillMethod, - showExternalWalletFulfillMethods, - setShowExternalWalletFulfillMethods, flowStep, setFlowStep, showRequestFulfilmentBankFlowManager, @@ -98,8 +82,6 @@ export const RequestFulfilmentFlowContextProvider: React.FC<{ children: ReactNod }), [ resetFlow, - externalWalletFulfillMethod, - showExternalWalletFulfillMethods, flowStep, showRequestFulfilmentBankFlowManager, selectedCountry,