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
11 changes: 5 additions & 6 deletions src/app/[...recipient]/client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export default function PaymentPage({ recipient, flow = 'request_pay' }: Props)
showRequestFulfilmentBankFlowManager,
setShowRequestFulfilmentBankFlowManager,
setFlowStep: setRequestFulfilmentBankFlowStep,
fulfillUsingManteca,
} = useRequestFulfillmentFlow()
const { requestType } = useDetermineBankRequestType(chargeDetails?.requestLink.recipientAccount.userId ?? '')

Expand Down Expand Up @@ -392,6 +393,10 @@ 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
!fulfillUsingManteca // Show when not fulfilling using Manteca
// Send to bank step if its mentioned in the URL and guest KYC is not needed
useEffect(() => {
const stepFromURL = searchParams.get('step')
Expand All @@ -407,12 +412,6 @@ export default function PaymentPage({ recipient, flow = 'request_pay' }: Props)
}
}, [searchParams, parsedPaymentData, chargeDetails, requestType])

let showActionList = flow !== 'direct_pay'

if (flow === 'direct_pay' && !user) {
showActionList = true
}

if (error) {
return (
<div className="mx-auto h-full w-full space-y-8 self-center md:w-6/12">
Expand Down
46 changes: 46 additions & 0 deletions src/app/actions/manteca.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
'use server'

import { MantecaWithdrawData, MantecaWithdrawResponse } from '@/types/manteca.types'
import { fetchWithSentry } from '@/utils'
import { cookies } from 'next/headers'

const API_KEY = process.env.PEANUT_API_KEY!

export async function mantecaWithdraw(params: MantecaWithdrawData): Promise<MantecaWithdrawResponse> {
const apiUrl = process.env.PEANUT_API_URL
const cookieStore = cookies()
const jwtToken = (await cookieStore).get('jwt-token')?.value

if (!apiUrl || !API_KEY) {
console.error('API URL or API Key is not configured.')
return { error: 'Server configuration error.' }
}

try {
const response = await fetchWithSentry(`${apiUrl}/manteca/withdraw`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${jwtToken}`,
'api-key': API_KEY,
},
body: JSON.stringify(params),
})

const data = await response.json()

if (!response.ok) {
console.log('error', response)
return { error: data.error || 'Failed to create manteca withdraw.' }
}

return { data }
} catch (error) {
console.log('error', error)
console.error('Error calling create manteca withdraw API:', error)
if (error instanceof Error) {
return { error: error.message }
}
return { error: 'An unexpected error occurred.' }
}
}
49 changes: 0 additions & 49 deletions src/app/actions/onramp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import { cookies } from 'next/headers'
import { fetchWithSentry } from '@/utils'
import { CountryData } from '@/components/AddMoney/consts'
import { MantecaDepositDetails } from '@/types/manteca.types'
import { getCurrencyConfig } from '@/utils/bridge.utils'
import { getCurrencyPrice } from '@/app/actions/currency'

Expand Down Expand Up @@ -113,51 +112,3 @@ export async function createOnrampForGuest(
return { error: 'An unexpected error occurred.' }
}
}

interface CreateMantecaOnrampParams {
usdAmount: string
currency: string
}

export async function createMantecaOnramp(
params: CreateMantecaOnrampParams
): Promise<{ data?: MantecaDepositDetails; error?: string }> {
const apiUrl = process.env.PEANUT_API_URL
const cookieStore = cookies()
const jwtToken = (await cookieStore).get('jwt-token')?.value

if (!apiUrl || !API_KEY) {
console.error('API URL or API Key is not configured.')
return { error: 'Server configuration error.' }
}

try {
const response = await fetchWithSentry(`${apiUrl}/manteca/deposit`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${jwtToken}`,
'api-key': API_KEY,
},
body: JSON.stringify({
usdAmount: params.usdAmount,
currency: params.currency,
}),
})

const data = await response.json()

if (!response.ok) {
console.log('error', response)
return { error: data.error || 'Failed to create on-ramp transfer for guest.' }
}

return { data }
} catch (error) {
console.error('Error calling create manteca on-ramp API:', error)
if (error instanceof Error) {
return { error: error.message }
}
return { error: 'An unexpected error occurred.' }
}
}
25 changes: 17 additions & 8 deletions src/components/AddMoney/components/MantecaDepositCard.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { MERCADO_PAGO } from '@/assets'
import Card from '@/components/Global/Card'
import MantecaDetailsCard from '@/components/Global/MantecaDetailsCard'
import PeanutActionDetailsCard from '@/components/Global/PeanutActionDetailsCard'
import { PaymentInfoRow } from '@/components/Payment/PaymentInfoRow'
import { PEANUT_WALLET_TOKEN_SYMBOL } from '@/constants'

interface MantecaDepositCardProps {
Expand Down Expand Up @@ -40,12 +39,22 @@ const MantecaDepositCard = ({
/>

<h2 className="font-bold">Account details</h2>
<Card className="rounded-sm">
{cbu && <PaymentInfoRow label={'CBU'} value={cbu} allowCopy={true} />}
{alias && <PaymentInfoRow label={'Alias'} value={alias} hideBottomBorder />}
{depositAddress && <PaymentInfoRow label={'Deposit Address'} value={depositAddress} hideBottomBorder />}
{pixKey && <PaymentInfoRow label={'Pix Key'} value={pixKey} hideBottomBorder />}
</Card>
{(() => {
const rows = [
...(cbu
? [{ key: 'cbu', label: 'CBU', value: cbu, allowCopy: true, hideBottomBorder: false }]
: []),
...(alias ? [{ key: 'alias', label: 'Alias', value: alias, hideBottomBorder: false }] : []),
...(depositAddress
? [{ key: 'deposit', label: 'Deposit Address', value: depositAddress, hideBottomBorder: false }]
: []),
...(pixKey ? [{ key: 'pix', label: 'Pix Key', value: pixKey, hideBottomBorder: false }] : []),
]
if (rows.length) {
rows[rows.length - 1].hideBottomBorder = true
}
return <MantecaDetailsCard rows={rows} />
})()}
</div>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { FC, useMemo, useState } from 'react'
import MercadoPagoDepositDetails from './MercadoPagoDepositDetails'
import InputAmountStep from '../../InputAmountStep'
import { createMantecaOnramp } from '@/app/actions/onramp'
import { useParams } from 'next/navigation'
import { countryData } from '@/components/AddMoney/consts'
import { MantecaDepositDetails } from '@/types/manteca.types'
import { mantecaApi } from '@/services/manteca'

interface MercadoPagoProps {
source: 'bank' | 'regionalMethod'
Expand Down Expand Up @@ -33,7 +33,7 @@ const MercadoPago: FC<MercadoPagoProps> = ({ source }) => {
try {
setError(null)
setIsCreatingDeposit(true)
const depositData = await createMantecaOnramp({
const depositData = await mantecaApi.deposit({
usdAmount: tokenUSDAmount.replace(/,/g, ''),
currency: selectedCountry.currency,
})
Expand Down
18 changes: 18 additions & 0 deletions src/components/Claim/Link/Initial.view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 MantecaFlowManager from './MantecaFlowManager'

export const InitialClaimLinkView = (props: IClaimScreenProps) => {
const {
Expand Down Expand Up @@ -91,6 +92,7 @@ export const InitialClaimLinkView = (props: IClaimScreenProps) => {
setShowVerificationModal,
setClaimToExternalWallet,
resetFlow: resetClaimBankFlow,
claimToMercadoPago,
} = useClaimBankFlow()
const { setLoadingState, isLoading } = useContext(loadingStateContext)
const {
Expand Down Expand Up @@ -638,6 +640,22 @@ export const InitialClaimLinkView = (props: IClaimScreenProps) => {
return <BankFlowManager {...props} />
}

if (claimToMercadoPago) {
return (
<MantecaFlowManager
claimLinkData={claimLinkData}
attachment={attachment}
amount={
isReward
? formatTokenAmount(Number(formatUnits(claimLinkData.amount, claimLinkData.tokenDecimals)))!
: (formatTokenAmount(
Number(formatUnits(claimLinkData.amount, claimLinkData.tokenDecimals)) * tokenPrice
) ?? '')
}
/>
)
}

return (
<div className="flex min-h-[inherit] flex-col justify-between gap-8 md:min-h-fit">
{!!user?.user.userId || claimBankFlowStep || claimToExternalWallet ? (
Expand Down
100 changes: 100 additions & 0 deletions src/components/Claim/Link/MantecaFlowManager.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
'use client'

import { MERCADO_PAGO } from '@/assets'
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 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'

interface MantecaFlowManagerProps {
claimLinkData: ClaimLinkData
amount: string
attachment: { message: string | undefined; attachmentUrl: string | undefined }
}

const MantecaFlowManager: FC<MantecaFlowManagerProps> = ({ claimLinkData, amount, attachment }) => {
const { setClaimToMercadoPago } = useClaimBankFlow()
const [currentStep, setCurrentStep] = useState<MercadoPagoStep>(MercadoPagoStep.DETAILS)
const router = useRouter()
const [destinationAddress, setDestinationAddress] = useState('')

const isSuccess = currentStep === MercadoPagoStep.SUCCESS

const renderStepDetails = () => {
if (currentStep === MercadoPagoStep.DETAILS) {
return (
<MantecaDetailsStep
destinationAddress={destinationAddress}
setDestinationAddress={setDestinationAddress}
setCurrentStep={setCurrentStep}
/>
)
}
if (currentStep === MercadoPagoStep.REVIEW) {
return (
<MantecaReviewStep
setCurrentStep={setCurrentStep}
claimLink={claimLinkData.link}
destinationAddress={destinationAddress}
amount={amount}
/>
)
}

if (currentStep === MercadoPagoStep.SUCCESS) {
return (
<Button onClick={() => router.push('/home')} shadowSize="4">
Back to home
</Button>
)
}
return null
}

const onPrev = () => {
if (currentStep === MercadoPagoStep.DETAILS) {
setClaimToMercadoPago(false)
return
}

if (currentStep === MercadoPagoStep.REVIEW) {
setCurrentStep(MercadoPagoStep.DETAILS)
return
}
if (currentStep === MercadoPagoStep.SUCCESS) {
router.push('/home')
return
}
}

return (
<div className="flex min-h-[inherit] flex-col justify-between gap-8 md:min-h-fit">
<NavHeader icon={isSuccess ? 'cancel' : 'chevron-up'} title="Receive" onPrev={onPrev} />

<div className="my-auto space-y-4">
<PeanutActionDetailsCard
viewType={isSuccess ? 'SUCCESS' : 'NORMAL'}
avatarSize="medium"
transactionType="REGIONAL_METHOD_CLAIM"
recipientType="USERNAME"
recipientName={isSuccess ? 'You’ll receive' : 'Receive in Mercado Pago'}
amount={amount}
tokenSymbol={claimLinkData.tokenSymbol}
message={attachment.message}
fileUrl={attachment.attachmentUrl}
logo={isSuccess ? undefined : MERCADO_PAGO}
/>

{renderStepDetails()}
</div>
</div>
)
}

export default MantecaFlowManager
45 changes: 45 additions & 0 deletions src/components/Claim/Link/views/MantecaDetailsStep.view.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
'use client'

import { Button } from '@/components/0_Bruddle'
import BaseInput from '@/components/0_Bruddle/BaseInput'
import { Icon } from '@/components/Global/Icons/Icon'
import { MercadoPagoStep } from '@/types/manteca.types'
import { Dispatch, FC, SetStateAction } from 'react'

interface MantecaDetailsStepProps {
setCurrentStep: Dispatch<SetStateAction<MercadoPagoStep>>
destinationAddress: string
setDestinationAddress: Dispatch<SetStateAction<string>>
}

const MantecaDetailsStep: FC<MantecaDetailsStepProps> = ({
setCurrentStep,
destinationAddress,
setDestinationAddress,
}) => {
const handleOnClick = async () => {
setCurrentStep(MercadoPagoStep.REVIEW)
}

return (
<>
<p className="font-bold">Enter Mercado Pago account details</p>

<BaseInput
value={destinationAddress}
onChange={(e) => setDestinationAddress(e.target.value)}
placeholder="CVU or Alias"
/>
<div className="flex items-center gap-2 text-xs text-grey-1">
<Icon name="info" width={16} height={16} />
<span>You can only withdraw to accounts under your name.</span>
</div>

<Button disabled={!destinationAddress} onClick={() => handleOnClick()} shadowSize="4">
Review
</Button>
</>
)
}

export default MantecaDetailsStep
Loading
Loading