diff --git a/src/app/(mobile-ui)/qr-pay/page.tsx b/src/app/(mobile-ui)/qr-pay/page.tsx index aad1f6bf2..6d90fbe77 100644 --- a/src/app/(mobile-ui)/qr-pay/page.tsx +++ b/src/app/(mobile-ui)/qr-pay/page.tsx @@ -2,7 +2,7 @@ import { useSearchParams, useRouter } from 'next/navigation' import { useState, useCallback, useMemo, useEffect, useContext } from 'react' -import { Card } from '@/components/0_Bruddle/Card' + import { Button } from '@/components/0_Bruddle/Button' import { Icon } from '@/components/Global/Icons/Icon' import { mantecaApi } from '@/services/manteca' @@ -29,6 +29,8 @@ import { QrKycState, useQrKycGate } from '@/hooks/useQrKycGate' import ActionModal from '@/components/Global/ActionModal' import { saveRedirectUrl } from '@/utils/general.utils' import { MantecaGeoSpecificKycModal } from '@/components/Kyc/InitiateMantecaKYCModal' +import { PeanutDoesntStoreAnyPersonalInformation } from '@/components/Kyc/KycVerificationInProgressModal' +import Card from '@/components/Global/Card' const MANTECA_DEPOSIT_ADDRESS = '0x959e088a09f61aB01cb83b0eBCc74b2CF6d62053' const MAX_QR_PAYMENT_AMOUNT = '200' @@ -216,18 +218,18 @@ export default function QRPayPage() { if (!!errorInitiatingPayment) { return (
- - - Unable to get QR details - + +
+

Unable to get QR details

+

{errorInitiatingPayment || 'An error occurred while getting the QR details.'} - - - - - +

+
+
+ +
) @@ -273,11 +275,7 @@ export default function QRPayPage() { icon: 'check-circle', }, ]} - footer={ -

- Peanut doesn't store any personal information -

- } + footer={} /> ) diff --git a/src/app/[...recipient]/client.tsx b/src/app/[...recipient]/client.tsx index aef205f4e..b65cfbb49 100644 --- a/src/app/[...recipient]/client.tsx +++ b/src/app/[...recipient]/client.tsx @@ -394,8 +394,7 @@ export default function PaymentPage({ recipient, flow = 'request_pay' }: Props) }, [transactionForDrawer, currentView, dispatch, openTransactionDetails, isExternalWalletFlow, chargeId]) const showActionList = - flow !== 'direct_pay' || // Always show for non-direct-pay flows - (flow === 'direct_pay' && !user) || // Show for direct-pay when user is not logged in + (flow !== 'direct_pay' || (flow === 'direct_pay' && !user)) && // Show for direct-pay when user is not logged in !fulfillUsingManteca // Show when not fulfilling using Manteca // Send to bank step if its mentioned in the URL and guest KYC is not needed useEffect(() => { diff --git a/src/components/Claim/Link/MantecaFlowManager.tsx b/src/components/Claim/Link/MantecaFlowManager.tsx index ead46215f..d32d6556f 100644 --- a/src/components/Claim/Link/MantecaFlowManager.tsx +++ b/src/components/Claim/Link/MantecaFlowManager.tsx @@ -5,12 +5,16 @@ import NavHeader from '@/components/Global/NavHeader' import PeanutActionDetailsCard from '@/components/Global/PeanutActionDetailsCard' import { useClaimBankFlow } from '@/context/ClaimBankFlowContext' import { ClaimLinkData } from '@/services/sendLinks' -import { FC, useState } from 'react' +import { FC, useEffect, useState } from 'react' import MantecaDetailsStep from './views/MantecaDetailsStep.view' import { MercadoPagoStep } from '@/types/manteca.types' import MantecaReviewStep from './views/MantecaReviewStep' import { Button } from '@/components/0_Bruddle' import { useRouter } from 'next/navigation' +import useKycStatus from '@/hooks/useKycStatus' +import { InitiateMantecaKYCModal } from '@/components/Kyc/InitiateMantecaKYCModal' +import { useAuth } from '@/context/authContext' +import { CountryData } from '@/components/AddMoney/consts' interface MantecaFlowManagerProps { claimLinkData: ClaimLinkData @@ -19,12 +23,38 @@ interface MantecaFlowManagerProps { } const MantecaFlowManager: FC = ({ claimLinkData, amount, attachment }) => { - const { setClaimToMercadoPago } = useClaimBankFlow() + const { setClaimToMercadoPago, selectedCountry } = useClaimBankFlow() const [currentStep, setCurrentStep] = useState(MercadoPagoStep.DETAILS) const router = useRouter() const [destinationAddress, setDestinationAddress] = useState('') + const [isKYCModalOpen, setIsKYCModalOpen] = useState(false) + const argentinaCountryData = { + id: 'AR', + type: 'country', + title: 'Argentina', + currency: 'ARS', + path: 'argentina', + iso2: 'AR', + iso3: 'ARG', + } as CountryData + + const { isUserMantecaKycApproved } = useKycStatus() + const { fetchUser } = useAuth() const isSuccess = currentStep === MercadoPagoStep.SUCCESS + const selectedCurrency = selectedCountry?.currency || 'ARS' + const logo = selectedCountry?.id ? undefined : MERCADO_PAGO + + const handleKycCancel = () => { + setIsKYCModalOpen(false) + onPrev() + } + + useEffect(() => { + if (!isUserMantecaKycApproved) { + setIsKYCModalOpen(true) + } + }, [isUserMantecaKycApproved]) const renderStepDetails = () => { if (currentStep === MercadoPagoStep.DETAILS) { @@ -43,6 +73,7 @@ const MantecaFlowManager: FC = ({ claimLinkData, amount claimLink={claimLinkData.link} destinationAddress={destinationAddress} amount={amount} + currency={selectedCurrency} /> ) } @@ -88,10 +119,25 @@ const MantecaFlowManager: FC = ({ claimLinkData, amount tokenSymbol={claimLinkData.tokenSymbol} message={attachment.message} fileUrl={attachment.attachmentUrl} - logo={isSuccess ? undefined : MERCADO_PAGO} + logo={isSuccess ? undefined : logo} + countryCodeForFlag={selectedCountry?.id.toLowerCase()} /> {renderStepDetails()} + + {isKYCModalOpen && ( + { + // close the modal and let the user continue with amount input + setIsKYCModalOpen(false) + fetchUser() + }} + country={selectedCountry || argentinaCountryData} + /> + )} ) diff --git a/src/components/Claim/Link/views/MantecaReviewStep.tsx b/src/components/Claim/Link/views/MantecaReviewStep.tsx index 2ea5bb388..fe1c882a0 100644 --- a/src/components/Claim/Link/views/MantecaReviewStep.tsx +++ b/src/components/Claim/Link/views/MantecaReviewStep.tsx @@ -14,12 +14,19 @@ interface MantecaReviewStepProps { claimLink: string destinationAddress: string amount: string + currency: string } -const MantecaReviewStep: FC = ({ setCurrentStep, claimLink, destinationAddress, amount }) => { +const MantecaReviewStep: FC = ({ + setCurrentStep, + claimLink, + destinationAddress, + amount, + currency, +}) => { const [isSubmitting, setIsSubmitting] = useState(false) const [error, setError] = useState(null) - const { price, isLoading } = useCurrency('ARS') // TODO: change to the currency of the selected Method + const { price, isLoading } = useCurrency(currency) const detailsCardRows: MantecaCardRow[] = [ { @@ -31,7 +38,7 @@ const MantecaReviewStep: FC = ({ setCurrentStep, claimLi { key: 'exchangeRate', label: 'Exchange Rate', - value: `1 USD = ${price?.buy} ARS`, + value: `1 USD = ${price?.buy} ${currency}`, }, { key: 'fee', @@ -55,7 +62,7 @@ const MantecaReviewStep: FC = ({ setCurrentStep, claimLi amount: amount.replace(/,/g, ''), destinationAddress, txHash, - currency: 'ARS', // TODO: source-selected currency + currency, }) if (withdrawError || !data) { setError(withdrawError || 'Something went wrong. Please contact Support') diff --git a/src/components/Common/CountryList.tsx b/src/components/Common/CountryList.tsx index 22ab13a5e..f79635851 100644 --- a/src/components/Common/CountryList.tsx +++ b/src/components/Common/CountryList.tsx @@ -135,7 +135,7 @@ export const CountryList = ({ isSupported = isBridgeSupportedCountry || isMantecaSupportedCountry } else if (viewMode === 'claim-request') { // support all countries - isSupported = isBridgeSupportedCountry + isSupported = isBridgeSupportedCountry || isMantecaSupportedCountry } else { // support all countries isSupported = true diff --git a/src/components/Common/CountryListRouter.tsx b/src/components/Common/CountryListRouter.tsx index e11b4ae85..9f66128d7 100644 --- a/src/components/Common/CountryListRouter.tsx +++ b/src/components/Common/CountryListRouter.tsx @@ -9,7 +9,7 @@ import { formatUnits } from 'viem' import { formatTokenAmount, printableAddress } from '@/utils/general.utils' import { CountryList } from '@/components/Common/CountryList' import { ClaimLinkData } from '@/services/sendLinks' -import { CountryData } from '@/components/AddMoney/consts' +import { CountryData, MantecaSupportedExchanges } from '@/components/AddMoney/consts' import useSavedAccounts from '@/hooks/useSavedAccounts' import { ParsedURL } from '@/lib/url-parser/types/payment' import { useCallback, useMemo } from 'react' @@ -41,12 +41,18 @@ export const CountryListRouter = ({ requestLinkData, inputTitle, }: ICountryListRouterViewProps) => { - const { setFlowStep: setClaimBankFlowStep, setSelectedCountry } = useClaimBankFlow() + const { + setFlowStep: setClaimBankFlowStep, + setSelectedCountry, + setClaimToMercadoPago, + setFlowStep, + } = useClaimBankFlow() const { setFlowStep: setRequestFulfilmentBankFlowStep, setShowRequestFulfilmentBankFlowManager, setSelectedCountry: setSelectedCountryForRequest, setShowVerificationModal, + setFulfillUsingManteca, } = useRequestFulfillmentFlow() const savedAccounts = useSavedAccounts() const { chargeDetails } = usePaymentStore() @@ -54,10 +60,20 @@ export const CountryListRouter = ({ const { user } = useAuth() const handleCountryClick = (country: CountryData) => { + const isMantecaSupportedCountry = Object.keys(MantecaSupportedExchanges).includes(country.id) if (flow === 'claim') { setSelectedCountry(country) - setClaimBankFlowStep(ClaimBankFlowStep.BankDetailsForm) + if (isMantecaSupportedCountry) { + setFlowStep(null) // reset the flow step to initial view first + setClaimToMercadoPago(true) + } else { + setClaimBankFlowStep(ClaimBankFlowStep.BankDetailsForm) + } } else if (flow === 'request') { + if (isMantecaSupportedCountry) { + setShowRequestFulfilmentBankFlowManager(false) + setFulfillUsingManteca(true) + } setSelectedCountryForRequest(country) if (requestType === BankRequestType.PayerKycNeeded) { diff --git a/src/components/Global/PeanutActionDetailsCard/index.tsx b/src/components/Global/PeanutActionDetailsCard/index.tsx index 3b1e875c7..594441e7f 100644 --- a/src/components/Global/PeanutActionDetailsCard/index.tsx +++ b/src/components/Global/PeanutActionDetailsCard/index.tsx @@ -165,13 +165,15 @@ export default function PeanutActionDetailsCard({ const isWithdrawBankAccount = transactionType === 'WITHDRAW_BANK_ACCOUNT' && recipientType === 'BANK_ACCOUNT' const isAddBankAccount = transactionType === 'ADD_MONEY_BANK_ACCOUNT' const isClaimLinkBankAccount = transactionType === 'CLAIM_LINK_BANK_ACCOUNT' && recipientType === 'BANK_ACCOUNT' + const isRegionalMethodClaim = transactionType === 'REGIONAL_METHOD_CLAIM' const withdrawBankIcon = () => { const imgSrc = logo ? logo : `https://flagcdn.com/w320/${countryCodeForFlag}.png` - if (isWithdrawBankAccount || isAddBankAccount || isClaimLinkBankAccount) + + if (isWithdrawBankAccount || isAddBankAccount || isClaimLinkBankAccount || isRegionalMethodClaim) return (
- {countryCodeForFlag && ( + {(countryCodeForFlag || logo) && ( {`${countryCodeForFlag} )} -
- -
+ {!isRegionalMethodClaim && ( +
+ +
+ )}
) return undefined @@ -192,7 +196,8 @@ export default function PeanutActionDetailsCard({
- {viewType !== 'SUCCESS' && (isWithdrawBankAccount || isAddBankAccount || isClaimLinkBankAccount) ? ( + {viewType !== 'SUCCESS' && + (isWithdrawBankAccount || isAddBankAccount || isClaimLinkBankAccount || isRegionalMethodClaim) ? ( withdrawBankIcon() ) : ( { - const { setFulfillUsingManteca } = useRequestFulfillmentFlow() + const { setFulfillUsingManteca, selectedCountry, setSelectedCountry } = useRequestFulfillmentFlow() const { requestDetails, chargeDetails } = usePaymentStore() + const [isKYCModalOpen, setIsKYCModalOpen] = useState(false) + const { isUserMantecaKycApproved } = useKycStatus() + const { fetchUser } = useAuth() const { data: depositData, isLoading: isLoadingDeposit } = useQuery({ queryKey: ['manteca-deposit', chargeDetails?.uuid], queryFn: () => mantecaApi.deposit({ usdAmount: requestDetails?.tokenAmount || chargeDetails?.tokenAmount || '0', - currency: 'ARS', + currency: selectedCountry?.currency || 'ARS', chargeId: chargeDetails?.uuid, }), refetchOnWindowFocus: false, staleTime: Infinity, // don't refetch the data - enabled: Boolean(chargeDetails?.uuid), + enabled: Boolean(chargeDetails?.uuid) && isUserMantecaKycApproved, }) + const argentinaCountryData = { + id: 'AR', + type: 'country', + title: 'Argentina', + currency: 'ARS', + path: 'argentina', + iso2: 'AR', + iso3: 'ARG', + } as CountryData + + const actionCardLogo = selectedCountry?.id + ? `https://flagcdn.com/w320/${selectedCountry?.id.toLowerCase()}.png` + : MERCADO_PAGO + const generateShareText = () => { const textParts = [] textParts.push(`CBU: ${depositData?.data?.depositAddress}`) @@ -35,6 +56,18 @@ const MantecaFulfillment = () => { return textParts.join('\n') } + const handleKycCancel = () => { + setIsKYCModalOpen(false) + setSelectedCountry(null) + setFulfillUsingManteca(false) + } + + useEffect(() => { + if (!isUserMantecaKycApproved) { + setIsKYCModalOpen(true) + } + }, [isUserMantecaKycApproved]) + if (isLoadingDeposit) { return } @@ -44,6 +77,7 @@ const MantecaFulfillment = () => { { + setSelectedCountry(null) setFulfillUsingManteca(false) }} /> @@ -58,7 +92,8 @@ const MantecaFulfillment = () => { tokenSymbol={requestDetails?.tokenSymbol || 'USDC'} message={requestDetails?.reference || chargeDetails?.requestLink?.reference || ''} fileUrl={requestDetails?.attachmentUrl || chargeDetails?.requestLink?.attachmentUrl || ''} - logo={MERCADO_PAGO} + logo={actionCardLogo} + countryCodeForFlag={selectedCountry?.id.toLowerCase()} /> {depositData?.error && } @@ -103,6 +138,19 @@ const MantecaFulfillment = () => { )}
+ {isKYCModalOpen && ( + { + // close the modal and let the user continue with amount input + setIsKYCModalOpen(false) + fetchUser() + }} + country={selectedCountry || argentinaCountryData} + /> + )}
) } diff --git a/src/hooks/useCurrency.ts b/src/hooks/useCurrency.ts index a103fa8dd..d200d7bc4 100644 --- a/src/hooks/useCurrency.ts +++ b/src/hooks/useCurrency.ts @@ -22,7 +22,7 @@ export const useCurrency = (currencyCode: string | null) => { const [code, setCode] = useState(currencyCode?.toUpperCase() ?? null) const [symbol, setSymbol] = useState(null) const [price, setPrice] = useState<{ buy: number; sell: number } | null>(null) - const [isLoading, setIsLoading] = useState(false) + const [isLoading, setIsLoading] = useState(true) useEffect(() => { if (!code) { diff --git a/src/services/manteca.ts b/src/services/manteca.ts index 1b5cbd522..f0f9aaab7 100644 --- a/src/services/manteca.ts +++ b/src/services/manteca.ts @@ -177,6 +177,7 @@ export const mantecaApi = { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${Cookies.get('jwt-token')}`, + 'api-key': 'DRdzuEiRvTtbFYKGahIdFUHuwhYzl1vo', }, body: JSON.stringify({ usdAmount: params.usdAmount,