From 212d37c3b825c6f0d1a7bb6186ed1569bae4a629 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Ram=C3=ADrez?= Date: Wed, 22 Oct 2025 20:05:32 -0300 Subject: [PATCH 1/2] feat: support waiting for merchant amount --- src/app/(mobile-ui)/qr-pay/page.tsx | 118 ++++++++++++++---- src/components/Global/DirectSendQR/index.tsx | 7 +- src/components/Global/Icons/qr-code.tsx | 20 +-- src/components/Global/PeanutLoading/index.tsx | 2 +- src/components/Home/RewardsCardModal.tsx | 49 -------- 5 files changed, 96 insertions(+), 100 deletions(-) delete mode 100644 src/components/Home/RewardsCardModal.tsx diff --git a/src/app/(mobile-ui)/qr-pay/page.tsx b/src/app/(mobile-ui)/qr-pay/page.tsx index 60fb24f84..48f48105a 100644 --- a/src/app/(mobile-ui)/qr-pay/page.tsx +++ b/src/app/(mobile-ui)/qr-pay/page.tsx @@ -36,12 +36,14 @@ import { QrKycState, useQrKycGate } from '@/hooks/useQrKycGate' import ActionModal from '@/components/Global/ActionModal' import { MantecaGeoSpecificKycModal } from '@/components/Kyc/InitiateMantecaKYCModal' import { SoundPlayer } from '@/components/Global/SoundPlayer' -import { useQueryClient } from '@tanstack/react-query' +import { useQueryClient, useQuery } from '@tanstack/react-query' import { shootDoubleStarConfetti } from '@/utils/confetti' import { STAR_STRAIGHT_ICON } from '@/assets' import { useAuth } from '@/context/authContext' import { useWebSocket } from '@/hooks/useWebSocket' import type { HistoryEntry } from '@/hooks/useTransactionHistory' +import { useSupportModalContext } from '@/context/SupportModalContext' +import chillPeanutAnim from '@/animations/GIF_ALPHA_BACKGORUND/512X512_ALPHA_GIF_konradurban_01.gif' const MAX_QR_PAYMENT_AMOUNT = '2000' @@ -83,6 +85,10 @@ export default function QRPayPage() { const { user } = useAuth() const [pendingSimpleFiPaymentId, setPendingSimpleFiPaymentId] = useState(null) const [isWaitingForWebSocket, setIsWaitingForWebSocket] = useState(false) + const [shouldRetry, setShouldRetry] = useState(true) + const { setIsSupportModalOpen } = useSupportModalContext() + const [waitingForMerchantAmount, setWaitingForMerchantAmount] = useState(false) + const retryCount = useRef(0) const paymentProcessor: PaymentProcessor | null = useMemo(() => { switch (qrType) { @@ -131,12 +137,6 @@ export default function QRPayPage() { } }, []) - // Reset SimpleFi payment state - const resetSimpleFiState = () => { - setPendingSimpleFiPaymentId(null) - setIsWaitingForWebSocket(false) - } - const handleSimpleFiStatusUpdate = useCallback( (entry: HistoryEntry) => { if (!pendingSimpleFiPaymentId || entry.uuid !== pendingSimpleFiPaymentId) { @@ -344,15 +344,26 @@ export default function QRPayPage() { useEffect(() => { if (paymentProcessor !== 'MANTECA') return if (!qrCode || !isPaymentProcessorQR(qrCode)) return - if (!!paymentLock) return + if (!!paymentLock || !shouldRetry) return + setShouldRetry(false) setLoadingState('Fetching details') mantecaApi .initiateQrPayment({ qrCode }) - .then((pl) => setPaymentLock(pl)) - .catch((error) => setErrorInitiatingPayment(error.message)) + .then((pl) => { + setWaitingForMerchantAmount(false) + setPaymentLock(pl) + }) + .catch((error) => { + if (error.message.includes("provider can't decode it")) { + setWaitingForMerchantAmount(true) + } else { + setErrorInitiatingPayment(error.message) + setWaitingForMerchantAmount(false) + } + }) .finally(() => setLoadingState('Idle')) - }, [paymentLock, qrCode, setLoadingState, paymentProcessor]) + }, [paymentLock, qrCode, setLoadingState, paymentProcessor, shouldRetry]) const merchantName = useMemo(() => { if (paymentProcessor === 'SIMPLEFI') { @@ -680,7 +691,7 @@ export default function QRPayPage() { } }, [isSuccess]) - const handleOrderNotReadyRetry = useCallback(async () => { + const handleSimplefiRetry = useCallback(async () => { setShowOrderNotReadyModal(false) if (!simpleFiQrData || simpleFiQrData.type !== 'SIMPLEFI_STATIC') return @@ -710,6 +721,27 @@ export default function QRPayPage() { } }, [simpleFiQrData, setLoadingState]) + useEffect(() => { + if (paymentProcessor !== 'SIMPLEFI') return + if (!shouldRetry) return + setShouldRetry(false) + handleSimplefiRetry() + }, [shouldRetry, handleSimplefiRetry]) + + useEffect(() => { + if (waitingForMerchantAmount && !shouldRetry) { + if (retryCount.current < 3) { + retryCount.current++ + setTimeout(() => { + setShouldRetry(true) + }, 3000) + } else { + setWaitingForMerchantAmount(false) + setShowOrderNotReadyModal(true) + } + } + }, [waitingForMerchantAmount, shouldRetry]) + if (!!errorInitiatingPayment) { return (
@@ -797,25 +829,57 @@ export default function QRPayPage() { ) } - if (showOrderNotReadyModal) { + if (waitingForMerchantAmount) { return ( -
- -
-

Order Not Ready

-

Please notify the cashier that you're ready to pay, then tap Retry.

-
-
+
+
+ Peanut Mascot + +
+ +
+

Waiting for the merchant to set the amount

+
+
+
+ ) + } -
- - + if (showOrderNotReadyModal) { + return ( +
+ +
+
+ We couldn't get the amount +

+ Ask the merchant to enter it and scan the QR again. +

+ +
) } diff --git a/src/components/Global/DirectSendQR/index.tsx b/src/components/Global/DirectSendQR/index.tsx index 644f4e28b..459085d36 100644 --- a/src/components/Global/DirectSendQR/index.tsx +++ b/src/components/Global/DirectSendQR/index.tsx @@ -173,13 +173,11 @@ export default function DirectSendQr({ icon = 'qr-code', className = '', ctaTitle, - iconClassName, disabled = false, }: { className?: string ctaTitle?: string icon?: IconName - iconClassName?: string disabled?: boolean }) { const [isQRScannerOpen, setIsQRScannerOpen] = useState(false) @@ -407,13 +405,12 @@ export default function DirectSendQr({ shadowSize="4" shadowType="primary" className={twMerge( - 'mx-auto h-20 w-20 cursor-pointer justify-center rounded-full p-0 hover:bg-primary-1/100', + 'mx-auto h-20 w-20 cursor-pointer justify-center rounded-full p-3 hover:bg-primary-1/100', className )} disabled={disabled} > - - {ctaTitle && ctaTitle} + > = (props) => ( - + - - - - - - - - - - ) diff --git a/src/components/Global/PeanutLoading/index.tsx b/src/components/Global/PeanutLoading/index.tsx index 96e17cc84..12da1fc98 100644 --- a/src/components/Global/PeanutLoading/index.tsx +++ b/src/components/Global/PeanutLoading/index.tsx @@ -11,7 +11,7 @@ export default function PeanutLoading({ coverFullScreen = false }: { coverFullSc )} >
- logo + logo Loading...
diff --git a/src/components/Home/RewardsCardModal.tsx b/src/components/Home/RewardsCardModal.tsx deleted file mode 100644 index 7d418bbbc..000000000 --- a/src/components/Home/RewardsCardModal.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { Icon } from '@/components/Global/Icons/Icon' -import Modal from '@/components/Global/Modal' -import DirectSendQr from '../Global/DirectSendQR' -import { PartnerBarLocation } from '../Global/RewardsModal' - -interface RewardsCardModalProps { - visible: boolean - onClose: () => void -} - -export default function RewardsCardModal({ visible, onClose }: RewardsCardModalProps) { - return ( - -
- {/* Icon */} -
- -
- - {/* Title */} -

You've got Beer tokens!

- - {/* Text */} -

- Scan the QR code at the bar's counter to claim your free beers at{' '} - -

- - {/* Button */} -
- -
-
-
- ) -} From d6972ebc64ef726387400ce6bd2b725882804836 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Ram=C3=ADrez?= Date: Wed, 22 Oct 2025 20:29:46 -0300 Subject: [PATCH 2/2] fix: remove useQuery --- src/app/(mobile-ui)/qr-pay/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/(mobile-ui)/qr-pay/page.tsx b/src/app/(mobile-ui)/qr-pay/page.tsx index 48f48105a..44b21c31d 100644 --- a/src/app/(mobile-ui)/qr-pay/page.tsx +++ b/src/app/(mobile-ui)/qr-pay/page.tsx @@ -36,7 +36,7 @@ import { QrKycState, useQrKycGate } from '@/hooks/useQrKycGate' import ActionModal from '@/components/Global/ActionModal' import { MantecaGeoSpecificKycModal } from '@/components/Kyc/InitiateMantecaKYCModal' import { SoundPlayer } from '@/components/Global/SoundPlayer' -import { useQueryClient, useQuery } from '@tanstack/react-query' +import { useQueryClient } from '@tanstack/react-query' import { shootDoubleStarConfetti } from '@/utils/confetti' import { STAR_STRAIGHT_ICON } from '@/assets' import { useAuth } from '@/context/authContext'