diff --git a/src/app/(mobile-ui)/home/page.tsx b/src/app/(mobile-ui)/home/page.tsx index ec161d207..5cca971d8 100644 --- a/src/app/(mobile-ui)/home/page.tsx +++ b/src/app/(mobile-ui)/home/page.tsx @@ -33,11 +33,10 @@ import { useDeviceType, DeviceType } from '@/hooks/useGetDeviceType' import SetupNotificationsModal from '@/components/Notifications/SetupNotificationsModal' import { useNotifications } from '@/hooks/useNotifications' import useKycStatus from '@/hooks/useKycStatus' -import HomeBanners from '@/components/Home/HomeBanners' +import HomeCarouselCTA from '@/components/Home/HomeCarouselCTA' import NoMoreJailModal from '@/components/Global/NoMoreJailModal' import EarlyUserModal from '@/components/Global/EarlyUserModal' -import { STAR_STRAIGHT_ICON } from '@/assets' -import Image from 'next/image' +import InvitesIcon from '@/components/Home/InvitesIcon' import NavigationArrow from '@/components/Global/NavigationArrow' const BALANCE_WARNING_THRESHOLD = parseInt(process.env.NEXT_PUBLIC_BALANCE_WARNING_THRESHOLD ?? '500') @@ -199,9 +198,9 @@ export default function Home() { showNoMoreJailModal !== 'true' && !user?.showEarlyUserModal // Give Early User and No more jail modal precedence, showing two modals together isn't ideal and it messes up their functionality - if (shouldShow) { + if (shouldShow && !showAddMoneyPromptModal) { + // Only set state, don't set sessionStorage here to avoid race conditions setShowAddMoneyPromptModal(true) - sessionStorage.setItem('hasSeenAddMoneyPromptThisSession', 'true') } else if (showAddMoneyPromptModal && showPermissionModal) { // priority enforcement: hide add money modal if notification modal appears // this handles race conditions where both modals try to show simultaneously @@ -219,6 +218,13 @@ export default function Home() { address, ]) + // Set sessionStorage flag when modal becomes visible to prevent showing again + useEffect(() => { + if (showAddMoneyPromptModal) { + sessionStorage.setItem('hasSeenAddMoneyPromptThisSession', 'true') + } + }, [showAddMoneyPromptModal]) + if (isLoading) { return } @@ -228,9 +234,9 @@ export default function Home() {
- - star - Points + + + Points {/* */} @@ -260,7 +266,7 @@ export default function Home() {
- + {showPermissionModal && } diff --git a/src/app/(mobile-ui)/qr-pay/page.tsx b/src/app/(mobile-ui)/qr-pay/page.tsx index daddc3cf6..e94e288ba 100644 --- a/src/app/(mobile-ui)/qr-pay/page.tsx +++ b/src/app/(mobile-ui)/qr-pay/page.tsx @@ -18,6 +18,7 @@ import TokenAmountInput from '@/components/Global/TokenAmountInput' import { useWallet } from '@/hooks/wallet/useWallet' import { clearRedirectUrl, getRedirectUrl, isTxReverted, saveRedirectUrl, formatNumberForDisplay } from '@/utils' import { getShakeClass, type ShakeIntensity } from '@/utils/perk.utils' +import { calculateSavingsInCents, isArgentinaMantecaQrPayment, getSavingsMessage } from '@/utils/qr-payment.utils' import ErrorAlert from '@/components/Global/ErrorAlert' import { PEANUT_WALLET_TOKEN_DECIMALS, TRANSACTIONS, PERK_HOLD_DURATION_MS } from '@/constants' import { MANTECA_DEPOSIT_ADDRESS } from '@/constants/manteca.consts' @@ -41,6 +42,9 @@ import { useQueryClient } from '@tanstack/react-query' import { shootDoubleStarConfetti } from '@/utils/confetti' import { STAR_STRAIGHT_ICON } from '@/assets' import { useAuth } from '@/context/authContext' +import { PointsAction } from '@/services/services.types' +import { usePointsConfetti } from '@/hooks/usePointsConfetti' +import { usePointsCalculation } from '@/hooks/usePointsCalculation' import { useWebSocket } from '@/hooks/useWebSocket' import type { HistoryEntry } from '@/hooks/useTransactionHistory' import { completeHistoryEntry } from '@/utils/history.utils' @@ -313,6 +317,17 @@ export default function QRPayPage() { } }, [paymentProcessor, simpleFiPayment, paymentLock?.code, paymentLock?.paymentAgainstAmount, amount]) + // Fetch points early to avoid latency penalty - fetch as soon as we have usdAmount + // This way points are cached by the time success view shows + // Only Manteca QR payments give points (SimpleFi does not) + // Use timestamp as uniqueId to prevent cache collisions between different QR scans + const { pointsData, pointsDivRef } = usePointsCalculation( + PointsAction.MANTECA_QR_PAYMENT, + usdAmount, + paymentProcessor === 'MANTECA', + timestamp || undefined + ) + const methodIcon = useMemo(() => { switch (qrType) { case EQrType.MERCADO_PAGO: @@ -716,6 +731,12 @@ export default function QRPayPage() { // Check user balance useEffect(() => { + // Skip balance check on success screen (balance may not have updated yet) + if (isSuccess) { + setBalanceErrorMessage(null) + return + } + // Skip balance check if transaction is being processed if (hasPendingTransactions || isWaitingForWebSocket) { return @@ -733,13 +754,16 @@ export default function QRPayPage() { } else { setBalanceErrorMessage(null) } - }, [usdAmount, balance, hasPendingTransactions, isWaitingForWebSocket]) + }, [usdAmount, balance, hasPendingTransactions, isWaitingForWebSocket, isSuccess]) + + // Use points confetti hook for animation - must be called unconditionally + usePointsConfetti(isSuccess && pointsData?.estimatedPoints ? pointsData.estimatedPoints : undefined, pointsDivRef) useEffect(() => { if (isSuccess) { queryClient.invalidateQueries({ queryKey: [TRANSACTIONS] }) } - }, [isSuccess]) + }, [isSuccess, queryClient]) const handleSimplefiRetry = useCallback(async () => { setShowOrderNotReadyModal(false) @@ -947,6 +971,11 @@ export default function QRPayPage() { if (isSuccess && paymentProcessor === 'MANTECA' && !qrPayment) { return null } else if (isSuccess && paymentProcessor === 'MANTECA' && qrPayment) { + // Calculate savings for Argentina Manteca QR payments only + const savingsInCents = calculateSavingsInCents(usdAmount) + const showSavingsMessage = savingsInCents > 0 && isArgentinaMantecaQrPayment(qrType, paymentProcessor) + const savingsMessage = showSavingsMessage ? getSavingsMessage(savingsInCents) : '' + return (
@@ -976,6 +1005,10 @@ export default function QRPayPage() {
≈ {formatNumberForDisplay(usdAmount ?? undefined, { maxDecimals: 2 })} USD
+ {/* Savings Message (Argentina Manteca only) */} + {showSavingsMessage && savingsMessage && ( +

{savingsMessage}

+ )}
)} @@ -1028,6 +1061,17 @@ export default function QRPayPage() { )} + {/* Points Display - ref used for confetti origin point */} + {pointsData?.estimatedPoints && ( +
+ star +

+ You've earned {pointsData.estimatedPoints}{' '} + {pointsData.estimatedPoints === 1 ? 'point' : 'points'}! +

+
+ )} +
{/* Show Claim Perk button if eligible and not claimed yet */} {qrPayment?.perk?.eligible && !perkClaimed && !qrPayment.perk.claimed ? ( @@ -1153,6 +1197,7 @@ export default function QRPayPage() {
+