Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
fda8345
feat: manteca onboarding widget service
kushagrasarathe Sep 5, 2025
efe9139
feat: useMantecaKyc hook
kushagrasarathe Sep 5, 2025
3afb9af
feat: update manteca deposit flow to handle kyc
kushagrasarathe Sep 5, 2025
781501a
fix: rename useKycFlow to useBridgeKycFlow
kushagrasarathe Sep 5, 2025
4228d45
feat: handle manteca kyc completion flow
kushagrasarathe Sep 11, 2025
d066eb4
feat: handle geo specific kyc for manteca
kushagrasarathe Sep 11, 2025
7effc01
Merge branch 'feat/manteca-add-flow' of https://github.com/peanutprot…
kushagrasarathe Sep 11, 2025
d7e6c0f
Merge branch 'feat/manteca-add-flow' into feat/manteca-kyc
kushagrasarathe Sep 12, 2025
963b87d
feat: handle geo based user verifciation in user settings
kushagrasarathe Sep 12, 2025
823ec91
fix: use useclient in mepa comp
kushagrasarathe Sep 12, 2025
b6e0026
fix: resolve coderabbit comments
kushagrasarathe Sep 15, 2025
af53312
feat: country row comp
kushagrasarathe Sep 16, 2025
eab8914
feat: handle non-bridge kyc activity ui and states
kushagrasarathe Sep 16, 2025
c32cd1b
feat: handle home and history page entries for kyc
kushagrasarathe Sep 16, 2025
b39e62a
feat: handle retry for manteca
kushagrasarathe Sep 16, 2025
d09105c
chore: remove test comment
kushagrasarathe Sep 16, 2025
52a7b71
Merge branch 'feat/manteca-integration' into feat/manteca-kyc
kushagrasarathe Sep 16, 2025
35a9a66
Merge branch 'feat/manteca-kyc' into feat/kyc-activity
kushagrasarathe Sep 16, 2025
d4226d4
chore: resolve cr comment
kushagrasarathe Sep 16, 2025
95deb7a
Merge branch 'feat/manteca-integration' into feat/manteca-kyc
kushagrasarathe Sep 17, 2025
0f90d9d
Merge pull request #1218 from peanutprotocol/feat/kyc-activity
kushagrasarathe Sep 17, 2025
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
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
'use client'
import MercadoPago from '@/components/AddMoney/components/RegionalMethods/MercadoPago'
import { CountryData, countryData } from '@/components/AddMoney/consts'
import { MantecaSupportedExchanges } from '@/components/AddMoney/consts'
import { useParams } from 'next/navigation'

export default function AddMoneyRegionalMethodPage() {
const params = useParams()
const country = params.country as string
const method = params['regional-method'] as string

if (country === 'argentina' && method === 'mercadopago') {
const countryDetails: CountryData | undefined = countryData.find((c) => c.path === country)

if (
MantecaSupportedExchanges[countryDetails?.id as keyof typeof MantecaSupportedExchanges] &&
method === 'mercadopago'
) {
return <MercadoPago source="regionalMethod" />
}

Expand Down
4 changes: 2 additions & 2 deletions src/app/(mobile-ui)/add-money/[country]/bank/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { useOnrampFlow } from '@/context/OnrampFlowContext'
import { useWallet } from '@/hooks/wallet/useWallet'
import { formatAmount } from '@/utils'
import { countryData } from '@/components/AddMoney/consts'
import { InitiateKYCModal } from '@/components/Kyc'
import { BridgeKycStatus } from '@/utils/bridge-accounts.utils'
import { useWebSocket } from '@/hooks/useWebSocket'
import { useAuth } from '@/context/authContext'
Expand All @@ -26,6 +25,7 @@ import AddMoneyBankDetails from '@/components/AddMoney/components/AddMoneyBankDe
import { getCurrencyConfig, getCurrencySymbol, getMinimumAmount } from '@/utils/bridge.utils'
import { OnrampConfirmationModal } from '@/components/AddMoney/components/OnrampConfirmationModal'
import MercadoPago from '@/components/AddMoney/components/RegionalMethods/MercadoPago'
import { InitiateBridgeKYCModal } from '@/components/Kyc/InitiateBridgeKYCModal'

type AddStep = 'inputAmount' | 'kyc' | 'loading' | 'collectUserDetails' | 'showDetails'

Expand Down Expand Up @@ -304,7 +304,7 @@ export default function OnrampBankPage() {
if (step === 'kyc') {
return (
<div className="flex flex-col justify-start space-y-8">
<InitiateKYCModal
<InitiateBridgeKYCModal
isOpen={isKycModalOpen}
onClose={handleKycModalClose}
onKycSuccess={handleKycSuccess}
Expand Down
45 changes: 30 additions & 15 deletions src/app/(mobile-ui)/history/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,13 @@ import { useTransactionHistory } from '@/hooks/useTransactionHistory'
import { useUserStore } from '@/redux/hooks'
import { formatGroupHeaderDate, getDateGroup, getDateGroupKey } from '@/utils/dateGrouping.utils'
import * as Sentry from '@sentry/nextjs'
import { usePathname } from 'next/navigation'
import { isKycStatusItem } from '@/hooks/useKycFlow'
import { isKycStatusItem } from '@/hooks/useBridgeKycFlow'
import React, { useEffect, useMemo, useRef } from 'react'

/**
* displays the user's transaction history with infinite scrolling and date grouping.
*/
const HistoryPage = () => {
const pathname = usePathname()
const loaderRef = useRef<HTMLDivElement>(null)
const { user } = useUserStore()

Expand Down Expand Up @@ -63,17 +61,27 @@ const HistoryPage = () => {
const allEntries = useMemo(() => historyData?.pages.flatMap((page) => page.entries) ?? [], [historyData])

const combinedAndSortedEntries = useMemo(() => {
if (isLoading) {
return []
}
const entries: Array<any> = [...allEntries]

if (
user?.user?.bridgeKycStatus &&
user.user.bridgeKycStatus !== 'not_started' &&
user.user.bridgeKycStartedAt
) {
entries.push({
isKyc: true,
timestamp: user.user.bridgeKycStartedAt,
uuid: 'kyc-status-item',
if (user) {
if (user.user?.bridgeKycStatus && user.user.bridgeKycStatus !== 'not_started') {
entries.push({
isKyc: true,
timestamp: user.user.bridgeKycStartedAt ?? new Date(0).toISOString(),
uuid: 'bridge-kyc-status-item',
bridgeKycStatus: user.user.bridgeKycStatus,
})
}
user.user.kycVerifications?.forEach((verification) => {
entries.push({
isKyc: true,
timestamp: verification.approvedAt ?? new Date(0).toISOString(),
uuid: verification.providerUserId ?? `${verification.provider}-${verification.mantecaGeo}`,
verification,
})
})
}

Expand All @@ -84,7 +92,7 @@ const HistoryPage = () => {
})

return entries
}, [allEntries, user])
}, [allEntries, user, isLoading])

if (isLoading && combinedAndSortedEntries.length === 0) {
return <PeanutLoading />
Expand All @@ -101,7 +109,7 @@ const HistoryPage = () => {
)
}

if (combinedAndSortedEntries.length === 0) {
if (!isLoading && combinedAndSortedEntries.length === 0) {
return (
<div className="flex h-[80dvh] flex-col items-center justify-center">
<NavHeader title={'Activity'} />
Expand Down Expand Up @@ -151,7 +159,14 @@ const HistoryPage = () => {
</div>
)}
{isKycStatusItem(item) ? (
<KycStatusItem position={position} />
<KycStatusItem
position={position}
verification={item.verification}
bridgeKycStatus={item.bridgeKycStatus}
bridgeKycStartedAt={
item.bridgeKycStatus ? user?.user.bridgeKycStartedAt : undefined
}
/>
) : (
(() => {
const { transactionDetails, transactionCardType } =
Expand Down
8 changes: 3 additions & 5 deletions src/app/(mobile-ui)/home/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { PostSignupActionManager } from '@/components/Global/PostSignupActionMan
import { useWithdrawFlow } from '@/context/WithdrawFlowContext'
import { useClaimBankFlow } from '@/context/ClaimBankFlowContext'
import { useDeviceType, DeviceType } from '@/hooks/useGetDeviceType'
import useKycStatus from '@/hooks/useKycStatus'

const BALANCE_WARNING_THRESHOLD = parseInt(process.env.NEXT_PUBLIC_BALANCE_WARNING_THRESHOLD ?? '500')
const BALANCE_WARNING_EXPIRY = parseInt(process.env.NEXT_PUBLIC_BALANCE_WARNING_EXPIRY ?? '1814400') // 21 days in seconds
Expand All @@ -61,6 +62,7 @@ export default function Home() {

const { isFetchingUser, addAccount } = useAuth()
const { user } = useUserStore()
const { isUserKycApproved } = useKycStatus()
const username = user?.user.username

const [showIOSPWAInstallModal, setShowIOSPWAInstallModal] = useState(false)
Expand Down Expand Up @@ -214,11 +216,7 @@ export default function Home() {
<PageContainer>
<div className="h-full w-full space-y-6 p-5">
<div className="flex items-center justify-between gap-2">
<UserHeader
username={username!}
fullName={userFullName}
isVerified={user?.user.bridgeKycStatus === 'approved'}
/>
<UserHeader username={username!} fullName={userFullName} isVerified={isUserKycApproved} />
<SearchUsers />
</div>
<div className="space-y-4">
Expand Down
26 changes: 26 additions & 0 deletions src/app/kyc/success/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use client'

import { useEffect } from 'react'
import Image from 'next/image'
import { HandThumbsUp } from '@/assets'

/*
This page is just to let users know that their KYC was successful. Incase there's some issue with webosckets closing the modal, ideally this should not happen but added this as fallback guide
*/
export default function KycSuccessPage() {
useEffect(() => {
if (window.parent) {
window.parent.postMessage({ source: 'peanut-kyc-success' }, '*')
}
}, [])

return (
<div className="flex h-screen min-h-full w-full flex-col items-center justify-center gap-4">
<Image src={HandThumbsUp} alt="Peanut HandThumbsUp" className="size-34" />
<div className="space-y-2">
<p className="text-lg font-semibold">Verification successful!</p>
<p className="text-sm text-gray-1">You can now close this window.</p>
</div>
</div>
)
}
9 changes: 4 additions & 5 deletions src/components/AddMoney/components/InputAmountStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,33 @@ import { Icon } from '@/components/Global/Icons/Icon'
import NavHeader from '@/components/Global/NavHeader'
import TokenAmountInput from '@/components/Global/TokenAmountInput'
import { useRouter } from 'next/navigation'
import { CountryData } from '../consts'
import ErrorAlert from '@/components/Global/ErrorAlert'
import { useCurrency } from '@/hooks/useCurrency'
import PeanutLoading from '@/components/Global/PeanutLoading'

type ICurrency = ReturnType<typeof useCurrency>
interface InputAmountStepProps {
onSubmit: () => void
selectedCountry: CountryData
isLoading: boolean
tokenAmount: string
setTokenAmount: React.Dispatch<React.SetStateAction<string>>
setTokenUSDAmount: React.Dispatch<React.SetStateAction<string>>
error: string | null
currencyData?: ICurrency
}

const InputAmountStep = ({
tokenAmount,
setTokenAmount,
onSubmit,
selectedCountry,
isLoading,
error,
setTokenUSDAmount,
currencyData,
}: InputAmountStepProps) => {
const router = useRouter()
const currencyData = useCurrency(selectedCountry.currency ?? 'ARS')

if (currencyData.isLoading) {
if (currencyData?.isLoading) {
return <PeanutLoading />
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import React, { FC, useMemo, useState } from 'react'
'use client'
import { FC, useEffect, useMemo, useState } from 'react'
import MercadoPagoDepositDetails from './MercadoPagoDepositDetails'
import InputAmountStep from '../../InputAmountStep'
import { useParams } from 'next/navigation'
import { countryData } from '@/components/AddMoney/consts'
import { useParams, useRouter } from 'next/navigation'
import { CountryData, countryData } from '@/components/AddMoney/consts'
import { MantecaDepositDetails } from '@/types/manteca.types'
import { InitiateMantecaKYCModal } from '@/components/Kyc/InitiateMantecaKYCModal'
import { useMantecaKycFlow } from '@/hooks/useMantecaKycFlow'
import { useCurrency } from '@/hooks/useCurrency'
import { useAuth } from '@/context/authContext'
import { useWebSocket } from '@/hooks/useWebSocket'
import { mantecaApi } from '@/services/manteca'

interface MercadoPagoProps {
Expand All @@ -14,22 +20,57 @@ type stepType = 'inputAmount' | 'depositDetails'

const MercadoPago: FC<MercadoPagoProps> = ({ source }) => {
const params = useParams()
const router = useRouter()
const [step, setStep] = useState<stepType>('inputAmount')
const [isCreatingDeposit, setIsCreatingDeposit] = useState(false)
const [tokenAmount, setTokenAmount] = useState('')
const [tokenUSDAmount, setTokenUSDAmount] = useState('')
const [error, setError] = useState<string | null>(null)
const [depositDetails, setDepositDetails] = useState<MantecaDepositDetails>()
const [isKycModalOpen, setIsKycModalOpen] = useState(false)

const selectedCountryPath = params.country as string
const selectedCountry = useMemo(() => {
return countryData.find((country) => country.type === 'country' && country.path === selectedCountryPath)
}, [selectedCountryPath])
const { isMantecaKycRequired } = useMantecaKycFlow({ country: selectedCountry as CountryData })
const currencyData = useCurrency(selectedCountry?.currency ?? 'ARS')
const { user, fetchUser } = useAuth()

useWebSocket({
username: user?.user.username ?? undefined,
autoConnect: !!user?.user.username,
onMantecaKycStatusUpdate: (newStatus) => {
// listen for manteca kyc status updates, either when the user is approved or when the widget is finished to continue with the flow
if (newStatus === 'ACTIVE' || newStatus === 'WIDGET_FINISHED') {
fetchUser()
setIsKycModalOpen(false)
}
},
})

const handleKycCancel = () => {
setIsKycModalOpen(false)
if (selectedCountry?.path) {
router.push(`/add-money/${selectedCountry.path}`)
}
}

const handleAmountSubmit = async () => {
if (!selectedCountry?.currency) return
if (isCreatingDeposit) return

// check if we still need to determine KYC status
if (isMantecaKycRequired === null) {
// still loading/determining KYC status, don't proceed yet
return
}

if (isMantecaKycRequired === true) {
setIsKycModalOpen(true)
return
}

try {
setError(null)
setIsCreatingDeposit(true)
Expand All @@ -51,19 +92,41 @@ const MercadoPago: FC<MercadoPagoProps> = ({ source }) => {
}
}

// handle verification modal opening
useEffect(() => {
if (isMantecaKycRequired) {
setIsKycModalOpen(true)
}
}, [isMantecaKycRequired, countryData])

if (!selectedCountry) return null

if (step === 'inputAmount') {
return (
<InputAmountStep
tokenAmount={tokenAmount}
setTokenAmount={setTokenAmount}
onSubmit={handleAmountSubmit}
selectedCountry={selectedCountry}
isLoading={isCreatingDeposit}
error={error}
setTokenUSDAmount={setTokenUSDAmount}
/>
<>
<InputAmountStep
tokenAmount={tokenAmount}
setTokenAmount={setTokenAmount}
onSubmit={handleAmountSubmit}
isLoading={isCreatingDeposit}
error={error}
setTokenUSDAmount={setTokenUSDAmount}
currencyData={currencyData}
/>
{isKycModalOpen && (
<InitiateMantecaKYCModal
isOpen={isKycModalOpen}
onClose={handleKycCancel}
onManualClose={handleKycCancel}
onKycSuccess={() => {
// close the modal and let the user continue with amount input
setIsKycModalOpen(false)
fetchUser()
}}
country={selectedCountry}
/>
)}
</>
)
}

Expand Down
Loading
Loading