Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 3 additions & 66 deletions src/app/(mobile-ui)/home/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ export default function Home() {
const username = user?.user.username

const [showIOSPWAInstallModal, setShowIOSPWAInstallModal] = useState(false)
const [showAddMoneyPromptModal, setShowAddMoneyPromptModal] = useState(false)
const [showBalanceWarningModal, setShowBalanceWarningModal] = useState(false)
// const [showReferralCampaignModal, setShowReferralCampaignModal] = useState(false)
const [isPostSignupActionModalVisible, setIsPostSignupActionModalVisible] = useState(false)
Expand Down Expand Up @@ -155,75 +154,12 @@ export default function Home() {
balanceInUsd > BALANCE_WARNING_THRESHOLD &&
!hasSeenBalanceWarning &&
!showIOSPWAInstallModal &&
!showAddMoneyPromptModal &&
!isPostSignupActionModalVisible
) {
setShowBalanceWarningModal(true)
}
}
}, [
balance,
isFetchingBalance,
showIOSPWAInstallModal,
showAddMoneyPromptModal,
isPostSignupActionModalVisible,
user,
])

// effect for showing add money prompt modal
useEffect(() => {
if (typeof window === 'undefined' || isFetchingBalance || !user || !address) return

// Don't show modal if balance is still loading (undefined)
if (balance === undefined) return

const hasSeenAddMoneyPromptThisSession = sessionStorage.getItem('hasSeenAddMoneyPromptThisSession')
const showNoMoreJailModal = sessionStorage.getItem('showNoMoreJailModal')

// determine if we should show the add money modal based on all conditions
// show if:
// 1. balance is zero.
// 2. user hasn't seen this prompt in the current session.
// 3. setup notifications modal is not visible (priority: setup modal > add money prompt)
// 4. the iOS PWA install modal is not currently active.
// 5. the balance warning modal is not currently active.
// 6. no other post-signup modal is active
const shouldShow =
balance === 0n &&
!hasSeenAddMoneyPromptThisSession &&
!showPermissionModal &&
!showIOSPWAInstallModal &&
!showBalanceWarningModal &&
!isPostSignupActionModalVisible &&
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 && !showAddMoneyPromptModal) {
// Only set state, don't set sessionStorage here to avoid race conditions
setShowAddMoneyPromptModal(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
setShowAddMoneyPromptModal(false)
}
}, [
balance,
isFetchingBalance,
showPermissionModal,
showIOSPWAInstallModal,
showBalanceWarningModal,
isPostSignupActionModalVisible,
showAddMoneyPromptModal,
user,
address,
])

// Set sessionStorage flag when modal becomes visible to prevent showing again
useEffect(() => {
if (showAddMoneyPromptModal) {
sessionStorage.setItem('hasSeenAddMoneyPromptThisSession', 'true')
}
}, [showAddMoneyPromptModal])
}, [balance, isFetchingBalance, showIOSPWAInstallModal, isPostSignupActionModalVisible, user])

if (isLoading) {
return <PeanutLoading coverFullScreen />
Expand Down Expand Up @@ -283,7 +219,8 @@ export default function Home() {
<IOSInstallPWAModal visible={showIOSPWAInstallModal} onClose={() => setShowIOSPWAInstallModal(false)} />

{/* Add Money Prompt Modal */}
<AddMoneyPromptModal visible={showAddMoneyPromptModal} onClose={() => setShowAddMoneyPromptModal(false)} />
{/* TODO @dev Disabling this, re-enable after properly fixing */}
{/* <AddMoneyPromptModal visible={showAddMoneyPromptModal} onClose={() => setShowAddMoneyPromptModal(false)} /> */}

<NoMoreJailModal />

Expand Down
3 changes: 2 additions & 1 deletion src/components/Global/DirectSendQR/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { twMerge } from 'tailwind-merge'
import ActionModal from '../ActionModal'
import { Icon, type IconName } from '../Icons/Icon'
import { EQrType, NAME_BY_QR_TYPE, parseEip681, recognizeQr } from './utils'
import { useQrCodeContext } from '@/context/QrCodeContext'

const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL!

Expand Down Expand Up @@ -180,7 +181,6 @@ export default function DirectSendQr({
icon?: IconName
disabled?: boolean
}) {
const [isQRScannerOpen, setIsQRScannerOpen] = useState(false)
const [isModalOpen, setIsModalOpen] = useState(false)
const [showPermissionModal, setShowPermissionModal] = useState(false)
const [qrType, setQrType] = useState<EQrType | undefined>(undefined)
Expand All @@ -196,6 +196,7 @@ export default function DirectSendQr({
if (!user?.user.username) return ''
return `${BASE_URL}/pay/${user.user.username}`
}, [user?.user.username])
const { isQRScannerOpen, setIsQRScannerOpen } = useQrCodeContext()

const startScanner = () => {
setIsQRScannerOpen(true)
Expand Down
2 changes: 1 addition & 1 deletion src/components/Global/Icons/qr-code.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { type FC, type SVGProps } from 'react'

export const QrCodeIcon: FC<SVGProps<SVGSVGElement>> = (props) => (
<svg className={props.className} viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg className={props.className} viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M12 18H10V16H12V18ZM10 11H8V16H10V11ZM18 9H16V13H18V9ZM16 7H14V9H16V7ZM4 9H2V11H4V9ZM2 7H0V9H2V7ZM9 2H11V0H9V2ZM1.5 1.5V4.5H4.5V1.5H1.5ZM5 6H1C0.45 6 0 5.55 0 5V1C0 0.45 0.45 0 1 0H5C5.55 0 6 0.45 6 1V5C6 5.55 5.55 6 5 6ZM1.5 13.5V16.5H4.5V13.5H1.5ZM5 18H1C0.45 18 0 17.55 0 17V13C0 12.45 0.45 12 1 12H5C5.55 12 6 12.45 6 13V17C6 17.55 5.55 18 5 18ZM13.5 1.5V4.5H16.5V1.5H13.5ZM17 6H13C12.45 6 12 5.55 12 5V1C12 0.45 12.45 0 13 0H17C17.55 0 18 0.45 18 1V5C18 5.55 17.55 6 17 6ZM16 16V13H12V15H14V18H18V16H16ZM14 9H10V11H14V9ZM10 7H4V9H6V11H8V9H10V7ZM11 6V4H9V2H7V6H11ZM3.75 2.25H2.25V3.75H3.75V2.25ZM3.75 14.25H2.25V15.75H3.75V14.25ZM15.75 2.25H14.25V3.75H15.75V2.25Z"
fill="black"
Expand Down
4 changes: 3 additions & 1 deletion src/components/Home/HomeCarouselCTA/CarouselCTA.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ interface CarouselCTAProps {
// Notification-specific props
isPermissionDenied?: boolean
secondaryIcon?: StaticImageData | string
iconSize?: number
}

const CarouselCTA = ({
Expand All @@ -32,6 +33,7 @@ const CarouselCTA = ({
iconContainerClassName,
isPermissionDenied,
secondaryIcon,
iconSize = 22,
}: CarouselCTAProps) => {
const [showPermissionDeniedModal, setShowPermissionDeniedModal] = useState(false)

Expand Down Expand Up @@ -95,7 +97,7 @@ const CarouselCTA = ({
)}
>
{/* Show icon only if logo isn't provided. Logo takes precedence over icon. */}
{!logo && <Icon name={icon} size={22} />}
{!logo && <Icon name={icon} size={iconSize} />}
{logo && (
<Image src={logo} alt={typeof title === 'string' ? title : 'logo'} width={36} height={36} />
)}
Expand Down
1 change: 1 addition & 0 deletions src/components/Home/HomeCarouselCTA/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const HomeCarouselCTA = () => {
iconContainerClassName={cta.iconContainerClassName}
isPermissionDenied={cta.isPermissionDenied}
secondaryIcon={cta.secondaryIcon}
iconSize={16}
/>
))}
</Carousel>
Expand Down
23 changes: 23 additions & 0 deletions src/context/QrCodeContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use client'

import { createContext, useContext, useState, type ReactNode } from 'react'

interface QrCodeContextType {
isQRScannerOpen: boolean
setIsQRScannerOpen: (isOpen: boolean) => void
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: lets adopt the habbit to add comments for hooks and components, jsdoc styles, easier to get context

const QrCodeContext = createContext<QrCodeContextType | undefined>(undefined)

export function QrCodeProvider({ children }: { children: ReactNode }) {
const [isQRScannerOpen, setIsQRScannerOpen] = useState(false)
return <QrCodeContext.Provider value={{ isQRScannerOpen, setIsQRScannerOpen }}>{children}</QrCodeContext.Provider>
}

export function useQrCodeContext() {
const context = useContext(QrCodeContext)
if (context === undefined) {
throw new Error('useQrCodeContext must be used within a QrCodeProvider')
}
return context
}
5 changes: 4 additions & 1 deletion src/context/contextProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { WithdrawFlowContextProvider } from './WithdrawFlowContext'
import { ClaimBankFlowContextProvider } from './ClaimBankFlowContext'
import { RequestFulfilmentFlowContextProvider } from './RequestFulfillmentFlowContext'
import { SupportModalProvider } from './SupportModalContext'
import { QrCodeProvider } from './QrCodeContext'

export const ContextProvider = ({ children }: { children: React.ReactNode }) => {
return (
Expand All @@ -22,7 +23,9 @@ export const ContextProvider = ({ children }: { children: React.ReactNode }) =>
<RequestFulfilmentFlowContextProvider>
<WithdrawFlowContextProvider>
<OnrampFlowContextProvider>
<SupportModalProvider>{children}</SupportModalProvider>
<SupportModalProvider>
<QrCodeProvider>{children}</QrCodeProvider>
</SupportModalProvider>
</OnrampFlowContextProvider>
</WithdrawFlowContextProvider>
</RequestFulfilmentFlowContextProvider>
Expand Down
45 changes: 27 additions & 18 deletions src/hooks/useHomeCarouselCTAs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { useNotifications } from './useNotifications'
import { useRouter } from 'next/navigation'
import useKycStatus from './useKycStatus'
import type { StaticImageData } from 'next/image'
import { PIX } from '@/assets'
import { useQrCodeContext } from '@/context/QrCodeContext'

export type CarouselCTA = {
id: string
Expand All @@ -21,6 +21,7 @@ export type CarouselCTA = {
isPermissionDenied?: boolean
iconContainerClassName?: string
secondaryIcon?: StaticImageData | string
iconSize?: number
}

export const useHomeCarouselCTAs = () => {
Expand All @@ -29,27 +30,35 @@ export const useHomeCarouselCTAs = () => {
const { showReminderBanner, requestPermission, snoozeReminderBanner, afterPermissionAttempt, isPermissionDenied } =
useNotifications()
const router = useRouter()
const { isUserKycApproved, isUserBridgeKycUnderReview } = useKycStatus()
const { isUserKycApproved, isUserBridgeKycUnderReview, isUserMantecaKycApproved } = useKycStatus()

const { setIsQRScannerOpen } = useQrCodeContext()

const generateCarouselCTAs = useCallback(() => {
const _carouselCTAs: CarouselCTA[] = []

_carouselCTAs.push({
id: 'merchant-map-pix',
title: 'Up to 10% cashback for Tier 2 users with PIX Payments',
description: 'Click to explore participating merchants. Pay with PIX QR, save instantly, earn points.',
iconContainerClassName: 'bg-secondary-1',
icon: 'shield',
onClick: () => {
window.open(
'https://peanutprotocol.notion.site/Peanut-Foodie-Guide-29a83811757980e79896f2a610d6591a',
'_blank',
'noopener,noreferrer'
)
},
logo: PIX,
secondaryIcon: 'https://flagcdn.com/w320/br.png',
})
// Show QR code payment prompt if user's Bridge or Manteca KYC is approved.
if (isUserKycApproved || isUserMantecaKycApproved) {
_carouselCTAs.push({
id: 'qr-payment',
title: (
<p>
Pay with <b>QR code payments</b>
</p>
),
description: (
<p>
Get the best exchange rate, pay like a <b>local</b> and earn <b>points</b>.
</p>
),
iconContainerClassName: 'bg-secondary-1',
icon: 'qr-code',
onClick: () => {
setIsQRScannerOpen(true)
},
iconSize: 16,
})
}

// add notification prompt as first item if it should be shown
if (showReminderBanner) {
Expand Down
Loading