diff --git a/src/app/kyc/success/page.tsx b/src/app/kyc/success/page.tsx
new file mode 100644
index 000000000..7e050b722
--- /dev/null
+++ b/src/app/kyc/success/page.tsx
@@ -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 (
+
+
+
+
Verification successful!
+
You can now close this window.
+
+
+ )
+}
diff --git a/src/components/AddMoney/components/InputAmountStep.tsx b/src/components/AddMoney/components/InputAmountStep.tsx
index aae6e9ad8..b2ffe835c 100644
--- a/src/components/AddMoney/components/InputAmountStep.tsx
+++ b/src/components/AddMoney/components/InputAmountStep.tsx
@@ -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
interface InputAmountStepProps {
onSubmit: () => void
- selectedCountry: CountryData
isLoading: boolean
tokenAmount: string
setTokenAmount: React.Dispatch>
setTokenUSDAmount: React.Dispatch>
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
}
diff --git a/src/components/AddMoney/components/RegionalMethods/MercadoPago/index.tsx b/src/components/AddMoney/components/RegionalMethods/MercadoPago/index.tsx
index d5e2e0493..42e44eb6d 100644
--- a/src/components/AddMoney/components/RegionalMethods/MercadoPago/index.tsx
+++ b/src/components/AddMoney/components/RegionalMethods/MercadoPago/index.tsx
@@ -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 {
@@ -14,22 +20,57 @@ type stepType = 'inputAmount' | 'depositDetails'
const MercadoPago: FC = ({ source }) => {
const params = useParams()
+ const router = useRouter()
const [step, setStep] = useState('inputAmount')
const [isCreatingDeposit, setIsCreatingDeposit] = useState(false)
const [tokenAmount, setTokenAmount] = useState('')
const [tokenUSDAmount, setTokenUSDAmount] = useState('')
const [error, setError] = useState(null)
const [depositDetails, setDepositDetails] = useState()
+ 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)
@@ -51,19 +92,41 @@ const MercadoPago: FC = ({ source }) => {
}
}
+ // handle verification modal opening
+ useEffect(() => {
+ if (isMantecaKycRequired) {
+ setIsKycModalOpen(true)
+ }
+ }, [isMantecaKycRequired, countryData])
+
if (!selectedCountry) return null
if (step === 'inputAmount') {
return (
-
+ <>
+
+ {isKycModalOpen && (
+ {
+ // close the modal and let the user continue with amount input
+ setIsKycModalOpen(false)
+ fetchUser()
+ }}
+ country={selectedCountry}
+ />
+ )}
+ >
)
}
diff --git a/src/components/AddMoney/consts/index.ts b/src/components/AddMoney/consts/index.ts
index ac27ca5c9..365c99736 100644
--- a/src/components/AddMoney/consts/index.ts
+++ b/src/components/AddMoney/consts/index.ts
@@ -4,6 +4,20 @@ import { METAMASK_LOGO, RAINBOW_LOGO, TRUST_WALLET_LOGO } from '@/assets/wallets
import { IconName } from '@/components/Global/Icons/Icon'
import { StaticImageData } from 'next/image'
+// ref: https://docs.manteca.dev/cripto/key-concepts/exchanges-multi-country#Available-Exchanges
+export const MantecaSupportedExchanges = {
+ AR: 'ARGENTINA',
+ CL: 'CHILE',
+ BR: 'BRAZIL',
+ CO: 'COLOMBIA',
+ PA: 'PANAMA',
+ CR: 'COSTA_RICA',
+ GT: 'GUATEMALA',
+ // MX: 'MEXICO', // manteca supports MEXICO, but mercado pago doesnt support qr payments for mexico
+ PH: 'PHILIPPINES',
+ BO: 'BOLIVIA',
+}
+
export interface CryptoSource {
id: string
name: string
@@ -2579,7 +2593,7 @@ countryData.forEach((country) => {
// filter add methods: include Mercado Pago only for LATAM countries
const currentAddMethods = UPDATED_DEFAULT_ADD_MONEY_METHODS.filter((method) => {
if (method.id === 'mercado-pago-add') {
- return LATAM_COUNTRY_CODES.includes(countryCode)
+ return !!MantecaSupportedExchanges[countryCode as keyof typeof MantecaSupportedExchanges]
}
return true
}).map((m) => {
@@ -2590,10 +2604,13 @@ countryData.forEach((country) => {
} else if (newMethod.id === 'crypto-add') {
newMethod.path = `/add-money/crypto`
newMethod.isSoon = false
- } else if (newMethod.id === 'mercado-pago-add' && countryCode === 'AR') {
+ } else if (
+ newMethod.id === 'mercado-pago-add' &&
+ MantecaSupportedExchanges[countryCode as keyof typeof MantecaSupportedExchanges]
+ ) {
newMethod.isSoon = false
newMethod.path = `/add-money/${country.path}/mercadopago`
- } else {
+ } else if (newMethod.id === 'mercado-pago-add') {
newMethod.isSoon = true
}
return newMethod
diff --git a/src/components/AddWithdraw/AddWithdrawCountriesList.tsx b/src/components/AddWithdraw/AddWithdrawCountriesList.tsx
index 9614a5394..e67b73bc2 100644
--- a/src/components/AddWithdraw/AddWithdrawCountriesList.tsx
+++ b/src/components/AddWithdraw/AddWithdrawCountriesList.tsx
@@ -12,7 +12,6 @@ import { useParams, useRouter } from 'next/navigation'
import EmptyState from '../Global/EmptyStates/EmptyState'
import { useAuth } from '@/context/authContext'
import { useEffect, useMemo, useRef, useState } from 'react'
-import { InitiateKYCModal } from '@/components/Kyc'
import { DynamicBankAccountForm, IBankAccountDetails } from './DynamicBankAccountForm'
import { addBankAccount, updateUserById } from '@/app/actions/users'
import { BridgeKycStatus } from '@/utils/bridge-accounts.utils'
@@ -26,6 +25,7 @@ import { DeviceType, useDeviceType } from '@/hooks/useGetDeviceType'
import CryptoMethodDrawer from '../AddMoney/components/CryptoMethodDrawer'
import { useAppDispatch } from '@/redux/hooks'
import { bankFormActions } from '@/redux/slices/bank-form-slice'
+import { InitiateBridgeKYCModal } from '../Kyc/InitiateBridgeKYCModal'
interface AddWithdrawCountriesListProps {
flow: 'add' | 'withdraw'
@@ -244,7 +244,7 @@ const AddWithdrawCountriesList = ({ flow }: AddWithdrawCountriesListProps) => {
initialData={{}}
error={null}
/>
- setIsKycModalOpen(false)}
onKycSuccess={handleKycSuccess}
@@ -348,7 +348,7 @@ const AddWithdrawCountriesList = ({ flow }: AddWithdrawCountriesListProps) => {
closeDrawer={() => setIsDrawerOpen(false)}
/>
)}
- setIsKycModalOpen(false)}
onKycSuccess={handleKycSuccess}
diff --git a/src/components/Claim/Link/Initial.view.tsx b/src/components/Claim/Link/Initial.view.tsx
index 471a9cb46..1e8990937 100644
--- a/src/components/Claim/Link/Initial.view.tsx
+++ b/src/components/Claim/Link/Initial.view.tsx
@@ -51,6 +51,7 @@ import { Button } from '@/components/0_Bruddle'
import Image from 'next/image'
import { PEANUT_LOGO_BLACK, PEANUTMAN_LOGO } from '@/assets'
import { GuestVerificationModal } from '@/components/Global/GuestVerificationModal'
+import useKycStatus from '@/hooks/useKycStatus'
import MantecaFlowManager from './MantecaFlowManager'
export const InitialClaimLinkView = (props: IClaimScreenProps) => {
@@ -113,6 +114,7 @@ export const InitialClaimLinkView = (props: IClaimScreenProps) => {
const searchParams = useSearchParams()
const prevRecipientType = useRef(null)
const prevUser = useRef(user)
+ const { isUserBridgeKycApproved } = useKycStatus()
useEffect(() => {
if (!prevUser.current && user) {
@@ -334,7 +336,7 @@ export const InitialClaimLinkView = (props: IClaimScreenProps) => {
recipient: recipient.name ?? recipient.address,
password: '',
})
- if (user?.user.bridgeKycStatus === 'approved') {
+ if (isUserBridgeKycApproved) {
const account = user.accounts.find(
(account) =>
account.identifier.replaceAll(/\s/g, '').toLowerCase() ===
diff --git a/src/components/Claim/Link/views/BankFlowManager.view.tsx b/src/components/Claim/Link/views/BankFlowManager.view.tsx
index a7adde123..403c4ac1d 100644
--- a/src/components/Claim/Link/views/BankFlowManager.view.tsx
+++ b/src/components/Claim/Link/views/BankFlowManager.view.tsx
@@ -24,13 +24,13 @@ import useSavedAccounts from '@/hooks/useSavedAccounts'
import { ConfirmBankClaimView } from './Confirm.bank-claim.view'
import { CountryListRouter } from '@/components/Common/CountryListRouter'
import NavHeader from '@/components/Global/NavHeader'
-import { InitiateKYCModal } from '@/components/Kyc'
import { useWebSocket } from '@/hooks/useWebSocket'
import { BridgeKycStatus } from '@/utils/bridge-accounts.utils'
import { getCountryCodeForWithdraw } from '@/utils/withdraw.utils'
import { useAppDispatch } from '@/redux/hooks'
import { bankFormActions } from '@/redux/slices/bank-form-slice'
import { sendLinksApi } from '@/services/sendLinks'
+import { InitiateBridgeKYCModal } from '@/components/Kyc/InitiateBridgeKYCModal'
type BankAccountWithId = IBankAccountDetails &
(
@@ -484,7 +484,7 @@ export const BankFlowManager = (props: IClaimScreenProps) => {
initialData={{}}
error={error}
/>
- setIsKycModalOpen(false)}
onKycSuccess={handleKycSuccess}
diff --git a/src/components/Common/CountryList.tsx b/src/components/Common/CountryList.tsx
index 4d467357d..22ab13a5e 100644
--- a/src/components/Common/CountryList.tsx
+++ b/src/components/Common/CountryList.tsx
@@ -1,10 +1,10 @@
'use client'
-import { countryCodeMap, CountryData, countryData } from '@/components/AddMoney/consts'
+import { countryCodeMap, CountryData, countryData, MantecaSupportedExchanges } from '@/components/AddMoney/consts'
import EmptyState from '@/components/Global/EmptyStates/EmptyState'
import { SearchInput } from '@/components/SearchUsers/SearchInput'
import { SearchResultCard } from '@/components/SearchUsers/SearchResultCard'
import Image from 'next/image'
-import { useMemo, useState } from 'react'
+import { useMemo, useState, type ReactNode } from 'react'
import { getCardPosition } from '../Global/Card'
import { useGeoLocaion } from '@/hooks/useGeoLocaion'
import { CountryListSkeleton } from './CountryListSkeleton'
@@ -13,10 +13,11 @@ import StatusBadge from '../Global/Badges/StatusBadge'
interface CountryListViewProps {
inputTitle: string
- viewMode: 'claim-request' | 'add-withdraw'
+ viewMode: 'claim-request' | 'add-withdraw' | 'general-verification'
onCountryClick: (country: CountryData) => void
onCryptoClick?: (flow: 'add' | 'withdraw') => void
flow?: 'add' | 'withdraw'
+ getRightContent?: (country: CountryData, isSupported: boolean) => ReactNode
}
/**
@@ -24,19 +25,26 @@ interface CountryListViewProps {
*
* @param {object} props
* @param {string} props.inputTitle The title for the input
- * @param {string} props.viewMode The view mode of the list, either 'claim-request' or 'add-withdraw'
+ * @param {string} props.viewMode The view mode of the list, either 'claim-request' or 'add-withdraw' or 'general-verification'
* @param {function} props.onCountryClick The function to call when a country is clicked
* @param {function} props.onCryptoClick The function to call when the crypto button is clicked
* @param {string} props.flow The flow of the list, either 'add' or 'withdraw', only required for 'add-withdraw' view mode
* @returns {JSX.Element}
*/
-export const CountryList = ({ inputTitle, viewMode, onCountryClick, onCryptoClick, flow }: CountryListViewProps) => {
+export const CountryList = ({
+ inputTitle,
+ viewMode,
+ onCountryClick,
+ onCryptoClick,
+ flow,
+ getRightContent,
+}: CountryListViewProps) => {
const [searchTerm, setSearchTerm] = useState('')
const { countryCode: userGeoLocationCountryCode, isLoading: isGeoLoading } = useGeoLocaion()
const supportedCountries = useMemo(() => {
return countryData.filter((country) => country.type === 'country')
- }, [viewMode])
+ }, [])
// sort countries based on user's geo location, fallback to alphabetical order
const sortedCountries = useMemo(() => {
@@ -105,17 +113,49 @@ export const CountryList = ({ inputTitle, viewMode, onCountryClick, onCryptoClic
const twoLetterCountryCode =
countryCodeMap[country.id.toUpperCase()] ?? country.id.toLowerCase()
const position = getCardPosition(index, filteredCountries.length)
+
+ const isBridgeSupportedCountry = [
+ 'US',
+ 'MX',
+ ...Object.keys(countryCodeMap),
+ ...Object.values(countryCodeMap),
+ ].includes(country.id)
+ const isMantecaSupportedCountry = Object.keys(MantecaSupportedExchanges).includes(
+ country.id
+ )
+
+ // determine if country is supported based on view mode
+ let isSupported = false
+
+ if (viewMode === 'add-withdraw') {
+ // all countries supported for claim-request
+ isSupported = true
+ } else if (viewMode === 'general-verification') {
+ // support all bridge and manteca supported countries
+ isSupported = isBridgeSupportedCountry || isMantecaSupportedCountry
+ } else if (viewMode === 'claim-request') {
+ // support all countries
+ isSupported = isBridgeSupportedCountry
+ } else {
+ // support all countries
+ isSupported = true
+ }
+
// flag used to show soon badge based on the view mode, check country code map keys and values for supported countries
- const isSupported =
- viewMode === 'add-withdraw' ||
- ['US', 'MX', ...Object.keys(countryCodeMap), ...Object.values(countryCodeMap)].includes(
- country.id
- )
+ // const isSupported =
+ // viewMode === 'add-withdraw' ||
+ // viewMode === 'general-verification' ||
+ // ['US', 'MX', ...Object.keys(countryCodeMap), ...Object.values(countryCodeMap)].includes(
+ // country.id
+ // )
+
+ const customRight = getRightContent ? getRightContent(country, isSupported) : undefined
+
return (
}
+ rightContent={customRight ?? (!isSupported && )}
description={country.currency}
onClick={() => onCountryClick(country)}
position={position}
diff --git a/src/components/Global/Footer/index.tsx b/src/components/Global/Footer/index.tsx
deleted file mode 100644
index c03717120..000000000
--- a/src/components/Global/Footer/index.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-'use client'
-
-import Link from 'next/link'
-
-import * as _consts from './consts'
-
-const Footer = () => {
- return (
-
- )
-}
-
-export default Footer
diff --git a/src/components/Global/IconStack.tsx b/src/components/Global/IconStack.tsx
index d08e06de4..5f3e46999 100644
--- a/src/components/Global/IconStack.tsx
+++ b/src/components/Global/IconStack.tsx
@@ -5,9 +5,10 @@ interface IconStackProps {
icons: string[]
iconSize?: number
iconClassName?: string
+ imageClassName?: string
}
-const IconStack: React.FC = ({ icons, iconSize = 24, iconClassName = '' }) => {
+const IconStack: React.FC = ({ icons, iconSize = 24, iconClassName = '', imageClassName }) => {
return (
+ Your identity has already been successfully verified for {userClickedCountry?.title}. You can
+ continue to use features available in this region. No further action is needed.
+