diff --git a/src/app/[...recipient]/client.tsx b/src/app/[...recipient]/client.tsx index c749489bd..39be8e635 100644 --- a/src/app/[...recipient]/client.tsx +++ b/src/app/[...recipient]/client.tsx @@ -33,9 +33,10 @@ import ActionList from '@/components/Common/ActionList' import NavHeader from '@/components/Global/NavHeader' import { ReqFulfillBankFlowManager } from '@/components/Request/views/ReqFulfillBankFlowManager' +export type PaymentFlow = 'request_pay' | 'external_wallet' | 'direct_pay' | 'withdraw' interface Props { recipient: string[] - flow?: 'request_pay' | 'external_wallet' | 'direct_pay' | 'withdraw' + flow?: PaymentFlow } export default function PaymentPage({ recipient, flow = 'request_pay' }: Props) { @@ -455,6 +456,7 @@ export default function PaymentPage({ recipient, flow = 'request_pay' }: Props) {currentView === 'INITIAL' && (
- + {flow !== 'direct_pay' && ( + + )}
)} diff --git a/src/components/Global/SoundPlayer.tsx b/src/components/Global/SoundPlayer.tsx index 6a517a49d..e09a326e4 100644 --- a/src/components/Global/SoundPlayer.tsx +++ b/src/components/Global/SoundPlayer.tsx @@ -1,6 +1,6 @@ 'use client' -import { useEffect } from 'react' +import { useEffect, useRef } from 'react' const soundMap = { success: '/sounds/success.mp3', @@ -16,13 +16,79 @@ type SoundPlayerProps = { * @returns null */ export const SoundPlayer = ({ sound }: SoundPlayerProps) => { + const audioRef = useRef(null) + useEffect(() => { const audioSrc = soundMap[sound] - if (audioSrc) { - const audio = new Audio(audioSrc) - audio.play().catch((error) => { - console.error(`Audio play failed for sound "${sound}":`, error) - }) + if (!audioSrc) return + + // create audio element and prepare to play, hint browser to load audio early, keep a ref so we can clean up later + const audio = new Audio(audioSrc) + audio.preload = 'auto' + audioRef.current = audio + + // track if we attached one-time unlock listeners and the unlock handler + let listenersAdded = false + let unlock: (() => void) | null = null + + // clean up any unlock listeners if they were added + const removeListeners = () => { + if (!listenersAdded || !unlock) return + document.removeEventListener('pointerdown', unlock) + document.removeEventListener('touchstart', unlock) + document.removeEventListener('click', unlock) + listenersAdded = false + unlock = null + } + + // try to play immediately, if blocked by autoplay policies, wait for user interaction + const tryPlay = () => + audio + .play() + .then(() => { + // success, nothing else to do + }) + .catch((error: any) => { + // detect common autoplay restriction errors + const message = String(error?.message || '') + const isAutoplayBlocked = + error?.name === 'NotAllowedError' || /gesture|autoplay|play\(\) failed/i.test(message) + + if (isAutoplayBlocked) { + // defer playback until next user interaction (mobile/pwa autoplay policies) + unlock = () => { + audio + .play() + .catch((e) => { + console.error(`Audio play failed after user interaction for sound "${sound}":`, e) + }) + .finally(() => { + removeListeners() + }) + } + // attach one-time listeners so the first tap/click resumes playback + document.addEventListener('pointerdown', unlock, { once: true }) + document.addEventListener('touchstart', unlock, { once: true }) + document.addEventListener('click', unlock, { once: true }) + listenersAdded = true + } else { + console.error(`Audio play failed for sound "${sound}":`, error) + } + }) + + tryPlay() + + return () => { + // on unmount, remove listeners and stop audio if needed + removeListeners() + if (audioRef.current) { + try { + audioRef.current.pause() + } catch (_) { + // ignore + } + } + audioRef.current = null } }, [sound]) diff --git a/src/components/Payment/PaymentForm/index.tsx b/src/components/Payment/PaymentForm/index.tsx index 0de5a7f3b..124cebc40 100644 --- a/src/components/Payment/PaymentForm/index.tsx +++ b/src/components/Payment/PaymentForm/index.tsx @@ -17,14 +17,7 @@ import TokenAmountInput from '@/components/Global/TokenAmountInput' import BeerInput from '@/components/PintaReqPay/BeerInput' import PintaReqViewWrapper from '@/components/PintaReqPay/PintaReqViewWrapper' import UserCard from '@/components/User/UserCard' -import { - PEANUT_WALLET_CHAIN, - PEANUT_WALLET_TOKEN, - PEANUT_WALLET_TOKEN_DECIMALS, - PEANUT_WALLET_TOKEN_SYMBOL, - PINTA_WALLET_CHAIN, - PINTA_WALLET_TOKEN, -} from '@/constants' +import { PEANUT_WALLET_TOKEN, PEANUT_WALLET_TOKEN_DECIMALS, PINTA_WALLET_CHAIN, PINTA_WALLET_TOKEN } from '@/constants' import { tokenSelectorContext } from '@/context' import { useAuth } from '@/context/authContext' import { useRequestFulfillmentFlow } from '@/context/RequestFulfillmentFlowContext' @@ -35,7 +28,6 @@ import { useAppDispatch, usePaymentStore } from '@/redux/hooks' import { paymentActions } from '@/redux/slices/payment-slice' import { walletActions } from '@/redux/slices/wallet-slice' import { areEvmAddressesEqual, ErrorHandler, formatAmount } from '@/utils' -import { useDaimoPayUI } from '@daimo/pay' import { useAppKit, useDisconnect } from '@reown/appkit/react' import Image from 'next/image' import { useRouter, useSearchParams } from 'next/navigation' @@ -44,6 +36,7 @@ import { formatUnits } from 'viem' import { useAccount } from 'wagmi' import { useUserInteractions } from '@/hooks/useUserInteractions' import { useUserByUsername } from '@/hooks/useUserByUsername' +import { PaymentFlow } from '@/app/[...recipient]/client' export type PaymentFlowProps = { isPintaReq?: boolean @@ -58,6 +51,7 @@ export type PaymentFlowProps = { currencyAmount?: string setCurrencyAmount?: (currencyvalue: string | undefined) => void headerTitle?: string + flow?: PaymentFlow } export type PaymentFormProps = ParsedURL & PaymentFlowProps @@ -74,6 +68,7 @@ export const PaymentForm = ({ isExternalWalletFlow, isDirectUsdPayment, headerTitle, + flow, }: PaymentFormProps) => { const dispatch = useAppDispatch() const router = useRouter() @@ -632,6 +627,7 @@ export const PaymentForm = ({ } const daimoButton = () => { + if (flow === 'direct_pay') return null return ( { - return ( - selectedTokenData?.symbol === PEANUT_WALLET_TOKEN_SYMBOL && - Number(selectedChainID) === PEANUT_WALLET_CHAIN.id - ) - }, [selectedTokenData, selectedChainID]) - const handleGoBack = () => { if (isExternalWalletFlow) { setShowExternalWalletFulfilMethods(true)