Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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: 29 additions & 40 deletions src/app/(mobile-ui)/add-money/[country]/bank/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ import { getLimitsWarningCardProps } from '@/features/limits/utils'
import { useExchangeRate } from '@/hooks/useExchangeRate'
import { useMultiPhaseKycFlow } from '@/hooks/useMultiPhaseKycFlow'
import { SumsubKycModals } from '@/components/Kyc/SumsubKycModals'
import { InitiateKycModal } from '@/components/Kyc/InitiateKycModal'

// Step type for URL state
type BridgeBankStep = 'inputAmount' | 'kyc' | 'showDetails'
type BridgeBankStep = 'inputAmount' | 'showDetails'

export default function OnrampBankPage() {
const router = useRouter()
Expand All @@ -42,7 +43,7 @@ export default function OnrampBankPage() {
// Example: /add-money/mexico/bank?step=inputAmount&amount=500
const [urlState, setUrlState] = useQueryStates(
{
step: parseAsStringEnum<BridgeBankStep>(['inputAmount', 'kyc', 'showDetails']),
step: parseAsStringEnum<BridgeBankStep>(['inputAmount', 'showDetails']),
amount: parseAsString,
},
{ history: 'push' }
Expand All @@ -53,6 +54,7 @@ export default function OnrampBankPage() {

// Local UI state (not URL-appropriate - transient)
const [showWarningModal, setShowWarningModal] = useState<boolean>(false)
const [showKycModal, setShowKycModal] = useState<boolean>(false)
const [isRiskAccepted, setIsRiskAccepted] = useState<boolean>(false)
const [liveKycStatus, setLiveKycStatus] = useState<BridgeKycStatus | undefined>(undefined)
const { setError, error, setOnrampData, onrampData } = useOnrampFlow()
Expand Down Expand Up @@ -152,30 +154,12 @@ export default function OnrampBankPage() {
currency: 'USD',
})

// Determine initial step based on KYC status (only when URL has no step)
// Default to inputAmount step when no step in URL
useEffect(() => {
// If URL already has a step, respect it (allows deep linking)
if (urlState.step) return

// Wait for user to be fetched before determining initial step
if (user === null) return

const currentKycStatus = liveKycStatus || user?.user.bridgeKycStatus
const isUserKycVerified = currentKycStatus === 'approved'

if (!isUserKycVerified) {
setUrlState({ step: 'kyc' })
} else {
setUrlState({ step: 'inputAmount' })
}
}, [liveKycStatus, user, urlState.step, setUrlState])

// Handle KYC completion
useEffect(() => {
if (urlState.step === 'kyc' && liveKycStatus === 'approved') {
setUrlState({ step: 'inputAmount' })
}
}, [liveKycStatus, urlState.step, setUrlState])
setUrlState({ step: 'inputAmount' })
}, [user, urlState.step, setUrlState])

const validateAmount = useCallback(
(amountStr: string): boolean => {
Expand Down Expand Up @@ -217,9 +201,17 @@ export default function OnrampBankPage() {
}, [rawTokenAmount, validateAmount, setError])

const handleAmountContinue = () => {
if (validateAmount(rawTokenAmount)) {
setShowWarningModal(true)
if (!validateAmount(rawTokenAmount)) return

const currentKycStatus = liveKycStatus || user?.user.bridgeKycStatus
const isUserKycVerified = currentKycStatus === 'approved'

if (!isUserKycVerified) {
setShowKycModal(true)
return
}

setShowWarningModal(true)
}

const handleWarningConfirm = async () => {
Expand Down Expand Up @@ -271,12 +263,6 @@ export default function OnrampBankPage() {
}
}

useEffect(() => {
if (urlState.step === 'kyc') {
sumsubFlow.handleInitiateKyc('STANDARD')
}
}, [urlState.step]) // eslint-disable-line react-hooks/exhaustive-deps

// Redirect to inputAmount if showDetails is accessed without required data (deep link / back navigation)
useEffect(() => {
if (urlState.step === 'showDetails' && !onrampData?.transferId) {
Expand All @@ -303,15 +289,6 @@ export default function OnrampBankPage() {
return <PeanutLoading />
}

if (urlState.step === 'kyc') {
return (
<div className="flex flex-col justify-start space-y-8">
<NavHeader title="Identity Verification" onPrev={handleBack} />
<SumsubKycModals flow={sumsubFlow} autoStartSdk />
</div>
)
}

if (urlState.step === 'showDetails') {
// Show loading while useEffect redirects if data is missing
if (!onrampData?.transferId) {
Expand Down Expand Up @@ -408,6 +385,18 @@ export default function OnrampBankPage() {
amount={rawTokenAmount}
currency={getCurrencySymbol(getCurrencyConfig(selectedCountry.id, 'onramp').currency)}
/>

<InitiateKycModal
visible={showKycModal}
onClose={() => setShowKycModal(false)}
onVerify={async () => {
await sumsubFlow.handleInitiateKyc('STANDARD')
setShowKycModal(false)
}}
isLoading={sumsubFlow.isLoading}
/>

<SumsubKycModals flow={sumsubFlow} autoStartSdk />
</div>
)
}
Expand Down
2 changes: 1 addition & 1 deletion src/app/(mobile-ui)/points/invites/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ const InvitesPage = () => {
{invites?.invitees?.map((invite: PointsInvite, i: number) => {
const username = invite.username
const fullName = invite.fullName
const isVerified = invite.kycStatus === 'approved'
const isVerified = invite.kycVerified
const pointsEarned = invite.contributedPoints ?? 0
// respect user's showFullName preference for avatar and display name
const displayName = invite.showFullName && fullName ? fullName : username
Expand Down
2 changes: 1 addition & 1 deletion src/app/(mobile-ui)/points/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ const PointsPage = () => {
{invites.invitees?.slice(0, 5).map((invite: PointsInvite, i: number) => {
const username = invite.username
const fullName = invite.fullName
const isVerified = invite.kycStatus === 'approved'
const isVerified = invite.kycVerified
const pointsEarned = invite.contributedPoints ?? 0
// respect user's showFullName preference for avatar and display name
const displayName = invite.showFullName && fullName ? fullName : username
Expand Down
2 changes: 1 addition & 1 deletion src/app/(mobile-ui)/withdraw/manteca/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -461,8 +461,8 @@ export default function MantecaWithdrawFlow() {
visible={showKycModal}
onClose={() => setShowKycModal(false)}
onVerify={async () => {
setShowKycModal(false)
await sumsubFlow.handleInitiateKyc('LATAM')
setShowKycModal(false)
}}
isLoading={sumsubFlow.isLoading}
/>
Expand Down
1 change: 1 addition & 0 deletions src/app/actions/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ export const confirmBridgeTos = async (): Promise<{ data?: { accepted: boolean }
Authorization: `Bearer ${jwtToken}`,
'api-key': API_KEY,
},
body: JSON.stringify({}),
})
const responseJson = await response.json()
if (!response.ok) {
Expand Down
2 changes: 1 addition & 1 deletion src/components/AddMoney/components/MantecaAddMoney.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,8 @@ const MantecaAddMoney: FC = () => {
visible={showKycModal}
onClose={() => setShowKycModal(false)}
onVerify={async () => {
setShowKycModal(false)
await sumsubFlow.handleInitiateKyc('LATAM')
setShowKycModal(false)
}}
isLoading={sumsubFlow.isLoading}
/>
Expand Down
13 changes: 7 additions & 6 deletions src/components/AddWithdraw/AddWithdrawCountriesList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,16 @@ const AddWithdrawCountriesList = ({ flow }: AddWithdrawCountriesListProps) => {
payload: AddBankAccountPayload,
rawData: IBankAccountDetails
): Promise<{ error?: string }> => {
const currentKycStatus = liveKycStatus || user?.user.bridgeKycStatus
// re-fetch user to ensure we have the latest KYC status
// (the multi-phase flow may have completed but websocket/state not yet propagated)
const freshUser = await fetchUser()
const currentKycStatus = freshUser?.user?.bridgeKycStatus || liveKycStatus || user?.user.bridgeKycStatus
const isUserKycVerified = currentKycStatus === 'approved'

const hasEmailOnLoad = !!user?.user.email

// scenario (1): happy path: if the user has already completed kyc, we can add the bank account directly
// note: we no longer check for fullName as account owner name is now always collected from the form
if (isUserKycVerified && (hasEmailOnLoad || rawData.email)) {
const currentAccountIds = new Set(user?.accounts.map((acc) => acc.id) ?? [])
// email and name are now collected by sumsub — no need to check them here
if (isUserKycVerified) {
const currentAccountIds = new Set((freshUser?.accounts ?? user?.accounts ?? []).map((acc) => acc.id))

const result = await addBankAccount(payload)
if (result.error) {
Expand Down
6 changes: 0 additions & 6 deletions src/components/AddWithdraw/DynamicBankAccountForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -430,12 +430,6 @@ export const DynamicBankAccountForm = forwardRef<{ handleSubmit: () => void }, D
})}
</div>
)}
{flow !== 'claim' &&
!hideEmailInput &&
!user?.user?.email &&
renderInput('email', 'E-mail', {
required: 'Email is required',
})}

{isMx
? renderInput('clabe', 'CLABE', {
Expand Down
5 changes: 3 additions & 2 deletions src/components/Claim/Claim.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
getTokenLogo,
getChainLogo,
} from '@/utils/general.utils'
import { isUserKycVerified } from '@/constants/kyc.consts'
import * as Sentry from '@sentry/nextjs'
import { useQuery } from '@tanstack/react-query'
import type { Hash } from 'viem'
Expand Down Expand Up @@ -189,7 +190,7 @@ export const Claim = ({}) => {
peanutFeeDetails: {
amountDisplay: '$ 0.00',
},
isVerified: claimLinkData.sender?.bridgeKycStatus === 'approved',
isVerified: isUserKycVerified(claimLinkData.sender),
haveSentMoneyToUser: claimLinkData.sender?.userId
? interactions[claimLinkData.sender?.userId] || false
: false,
Expand Down Expand Up @@ -396,7 +397,7 @@ export const Claim = ({}) => {
// redirect to bank flow if user is KYC approved and step is bank
useEffect(() => {
const stepFromURL = searchParams.get('step')
if (user?.user.bridgeKycStatus === 'approved' && stepFromURL === 'bank') {
if (isUserKycVerified(user?.user) && stepFromURL === 'bank') {
setClaimBankFlowStep(ClaimBankFlowStep.BankCountryList)
}
}, [user])
Expand Down
2 changes: 1 addition & 1 deletion src/components/Claim/Link/MantecaFlowManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@ const MantecaFlowManager: FC<MantecaFlowManagerProps> = ({ claimLinkData, amount
visible={showKycModal}
onClose={() => setShowKycModal(false)}
onVerify={async () => {
setShowKycModal(false)
await sumsubFlow.handleInitiateKyc('LATAM')
setShowKycModal(false)
}}
isLoading={sumsubFlow.isLoading}
/>
Expand Down
3 changes: 2 additions & 1 deletion src/components/Global/PostSignupActionManager/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import ActionModal from '../ActionModal'
import { POST_SIGNUP_ACTIONS } from './post-signup-action.consts'
import { type IconName } from '../Icons/Icon'
import { useAuth } from '@/context/authContext'
import { isUserKycVerified } from '@/constants/kyc.consts'

export const PostSignupActionManager = ({
onActionModalVisibilityChange,
Expand All @@ -26,7 +27,7 @@ export const PostSignupActionManager = ({

const checkClaimModalAfterKYC = () => {
const redirectUrl = getRedirectUrl()
if (user?.user.bridgeKycStatus === 'approved' && redirectUrl) {
if (isUserKycVerified(user?.user) && redirectUrl) {
const matchedAction = POST_SIGNUP_ACTIONS.find((action) => action.pathPattern.test(redirectUrl))
if (matchedAction) {
setActionConfig({
Expand Down
19 changes: 17 additions & 2 deletions src/components/Home/HomeCarouselCTA/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ import { type IconName } from '@/components/Global/Icons/Icon'
import { useHomeCarouselCTAs, type CarouselCTA as CarouselCTAType } from '@/hooks/useHomeCarouselCTAs'
import { perksApi, type PendingPerk } from '@/services/perks'
import { useAuth } from '@/context/authContext'
import { BridgeTosStep } from '@/components/Kyc/BridgeTosStep'
import { useWebSocket } from '@/hooks/useWebSocket'
import { extractInviteeName } from '@/utils/general.utils'
import PerkClaimModal from '../PerkClaimModal'
import underMaintenanceConfig from '@/config/underMaintenance.config'

const HomeCarouselCTA = () => {
const { carouselCTAs, setCarouselCTAs } = useHomeCarouselCTAs()
const { user } = useAuth()
const { carouselCTAs, setCarouselCTAs, showBridgeTos, setShowBridgeTos } = useHomeCarouselCTAs()
const { user, fetchUser } = useAuth()
const queryClient = useQueryClient()

// Perk claim modal state
Expand Down Expand Up @@ -89,6 +90,17 @@ const HomeCarouselCTA = () => {
setSelectedPerk(null)
}, [])

// bridge ToS handlers
const handleTosComplete = useCallback(async () => {
setShowBridgeTos(false)
setCarouselCTAs((prev) => prev.filter((c) => c.id !== 'bridge-tos'))
await fetchUser()
}, [setShowBridgeTos, setCarouselCTAs, fetchUser])

const handleTosSkip = useCallback(() => {
setShowBridgeTos(false)
}, [setShowBridgeTos])

// don't render carousel if there are no CTAs
if (!allCTAs.length) return null

Expand Down Expand Up @@ -130,6 +142,9 @@ const HomeCarouselCTA = () => {
onClaimed={handlePerkClaimed}
/>
)}

{/* Bridge ToS iframe */}
<BridgeTosStep visible={showBridgeTos} onComplete={handleTosComplete} onSkip={handleTosSkip} />
</>
)
}
Expand Down
8 changes: 0 additions & 8 deletions src/components/Home/HomeHistory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ import { type CardPosition, getCardPosition } from '../Global/Card/card.utils'
import EmptyState from '../Global/EmptyStates/EmptyState'
import { KycStatusItem, isKycStatusItem, type KycHistoryEntry } from '../Kyc/KycStatusItem'
import { groupKycByRegion } from '@/utils/kyc-grouping.utils'
import { BridgeTosReminder } from '../Kyc/BridgeTosReminder'
import { useBridgeTosStatus } from '@/hooks/useBridgeTosStatus'
import { useWallet } from '@/hooks/wallet/useWallet'
import { BadgeStatusItem } from '@/components/Badges/BadgeStatusItem'
import { isBadgeHistoryItem } from '@/components/Badges/badge.types'
Expand Down Expand Up @@ -45,8 +43,6 @@ const HomeHistory = ({ username, hideTxnAmount = false }: { username?: string; h
const { fetchBalance } = useWallet()
const { triggerHaptic } = useHaptic()
const { fetchUser } = useAuth()
const { needsBridgeTos } = useBridgeTosStatus()

const isViewingOwnHistory = useMemo(
() => (isLoggedIn && !username) || (isLoggedIn && username === user?.user.username),
[isLoggedIn, username, user?.user.username]
Expand Down Expand Up @@ -251,7 +247,6 @@ const HomeHistory = ({ username, hideTxnAmount = false }: { username?: string; h
return (
<div className="mx-auto mt-6 w-full space-y-3 md:max-w-2xl">
<h2 className="text-base font-bold">Activity</h2>
{isViewingOwnHistory && needsBridgeTos && <BridgeTosReminder />}
{isViewingOwnHistory &&
user?.user &&
(() => {
Expand Down Expand Up @@ -290,9 +285,6 @@ const HomeHistory = ({ username, hideTxnAmount = false }: { username?: string; h

return (
<div className={twMerge('mx-auto w-full space-y-3 md:max-w-2xl md:space-y-3', isLoggedIn ? 'pb-4' : 'pb-0')}>
{/* bridge ToS reminder for users who haven't accepted yet */}
{isViewingOwnHistory && needsBridgeTos && <BridgeTosReminder />}

{/* link to the full history page */}
{pendingRequests.length > 0 && (
<>
Expand Down
Loading
Loading