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)