-
Notifications
You must be signed in to change notification settings - Fork 13
[TASK-12883] Add manteca QR payments #1190
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughAdds a mobile QR-pay layout and page, a Manteca API client and QR payment types, routes PIX/MERCADO_PAGO to the qr-pay flow, centralizes avatar URL resolution and threads avatarUrl through transaction drawer/receipt, changes currency price shape to {buy,sell} and updates callers, restructures TokenAmountInput UI, and exports a PIX asset. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
Please see the documentation for more information. Example: reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal). Please share your feedback with us on this Discord post. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/hooks/useCurrency.ts (2)
4-9
: Add BRL symbol to support PIX.PIX flows will need
BRL -> R$
.Apply:
const SYMBOLS_BY_CURRENCY_CODE: Record<string, string> = { ARS: 'AR$', USD: '$', EUR: '€', MXN: 'MX$', + BRL: 'R$', }
11-16
: Prop/state sync: update when currencyCode changes.Without syncing, changing
currencyCode
after mount won’t refetch or update the symbol.Apply:
export const useCurrency = (currencyCode: string | null) => { const [code, setCode] = useState<string | null>(currencyCode?.toUpperCase() ?? null) @@ const [isLoading, setIsLoading] = useState<boolean>(false) + useEffect(() => { + setCode(currencyCode?.toUpperCase() ?? null) + }, [currencyCode])src/components/TransactionDetails/TransactionCard.tsx (1)
136-141
: Remove duplicate currency symbol for token display
finalDisplayAmount
already includesdisplaySymbol
. Appending it again yields outputs like$$123.00 ETH
.- finalDisplayAmount = `${displaySymbol}${finalDisplayAmount} ${transaction.tokenSymbol}` + finalDisplayAmount = `${finalDisplayAmount} ${transaction.tokenSymbol}`
🧹 Nitpick comments (15)
src/hooks/useTransactionHistory.ts (1)
156-160
: DTO typing mismatch in map().
data.entries.map((entry: HistoryEntry) => ...)
treats raw JSON as already-normalizedHistoryEntry
. Define a DTO (ISO strings) and map to the strongly-typed model to avoid silent type drift.Apply this minimal refactor:
- entries: data.entries.map((entry: HistoryEntry) => { + type HistoryEntryDTO = Omit<HistoryEntry, 'timestamp'|'cancelledAt'|'createdAt'|'completedAt'> & { + timestamp: string + cancelledAt?: string + createdAt?: string + completedAt?: string + } + entries: (data.entries as HistoryEntryDTO[]).map((entry) => {src/hooks/useCurrency.ts (2)
30-35
: Use key-in check instead of Object.keys().includes().Cleaner and avoids an array allocation.
Apply:
- if (!Object.keys(SYMBOLS_BY_CURRENCY_CODE).includes(code)) { + if (!(code in SYMBOLS_BY_CURRENCY_CODE)) {
36-42
: Set symbol immediately to reduce UI flicker.Pre-set symbol before awaiting price.
Apply:
setIsLoading(true) + setSymbol(SYMBOLS_BY_CURRENCY_CODE[code]) getCurrencyPrice(code) .then((price) => { - setSymbol(SYMBOLS_BY_CURRENCY_CODE[code]) setPrice(price) setIsLoading(false) })src/app/(mobile-ui)/qr-pay/layout.tsx (1)
5-9
: Metadata: OK.Static metadata via helper keeps layout simple. If you plan localized titles/descriptions later, consider feeding i18n here.
src/services/manteca.ts (1)
70-88
: Minor: guard missing auth and prefer explicit Accept header.If
Cookies.get('jwt-token')
is undefined, consider throwing a 401-friendly error before the network call. AddAccept: application/json
for clarity.Apply:
const response = await fetchWithSentry(`${PEANUT_API_URL}/manteca/qr-payment`, { method: 'POST', headers: { - 'Content-Type': 'application/json', + 'Content-Type': 'application/json', + Accept: 'application/json', Authorization: `Bearer ${Cookies.get('jwt-token')}`, }, body: JSON.stringify(data), })src/components/TransactionDetails/TransactionDetailsHeaderCard.tsx (1)
168-170
: Fix Image alt and intrinsic size to avoid layout shift
- alt="Icon" is non-informative; if decorative, prefer empty alt and aria-hidden.
- width/height (35) don’t match CSS size-16 (64px), which can cause CLS.
Apply:
- <div className="flex h-16 w-16 items-center justify-center rounded-full"> - <Image src={avatarUrl} alt="Icon" className="size-16 object-contain" width={35} height={35} /> + <div className="flex h-16 w-16 items-center justify-center rounded-full"> + <Image + src={avatarUrl} + alt="" + aria-hidden + className="size-16 object-contain" + width={64} + height={64} + /> </div>Note: Leveraging past learning on decorative images using empty alt to prevent layout issues.
src/utils/history.utils.ts (1)
5-19
: Make currency code mapping case-insensitiveSome backends send lowercase (e.g., 'brl'). Normalize before switch.
- if (transaction.extraDataForDrawer?.originalType === EHistoryEntryType.MANTECA_QR_PAYMENT) { - switch (transaction.currency?.code) { + if (transaction.extraDataForDrawer?.originalType === EHistoryEntryType.MANTECA_QR_PAYMENT) { + const code = transaction.currency?.code?.toUpperCase() + switch (code) { case 'ARS': return MERCADO_PAGO case 'BRL': return PIX default: return undefined } }src/components/TransactionDetails/TransactionCard.tsx (1)
152-157
: Fix Image alt and intrinsic size to avoid layout shiftMirror the Header fix for consistency.
- <div className={'relative flex h-12 w-12 items-center justify-center rounded-full'}> + <div className="relative flex h-12 w-12 items-center justify-center rounded-full"> <Image src={avatarUrl} - alt="Icon" - className="size-12 object-contain" - width={30} - height={30} + alt="" + aria-hidden + className="size-12 object-contain" + width={48} + height={48} />src/components/Global/TokenAmountInput/index.tsx (2)
210-235
: Add accessible name to the inputScreen readers lack context for the numeric field.
- <input + <input + aria-label="Amount"
249-260
: Use a button for the conversion toggle (keyboard + a11y)Clickable divs are not accessible. Use a button with proper semantics.
- {showConversion && ( - <div - className="absolute right-0 top-1/2 -translate-x-1/2 -translate-y-1/2 transform cursor-pointer" - onClick={(e) => { + {showConversion && ( + <button + type="button" + aria-label="Switch input currency" + aria-pressed={!isInputUsd} + className="absolute right-0 top-1/2 -translate-x-1/2 -translate-y-1/2 transform cursor-pointer" + onClick={(e) => { e.preventDefault() const currentValue = displayValue setDisplayValue(alternativeDisplayValue) setAlternativeDisplayValue(currentValue) setIsInputUsd(!isInputUsd) }} - > - <Icon name={'switch'} className="ml-5 rotate-90 cursor-pointer" width={32} height={32} /> - </div> + > + <Icon name="switch" className="ml-5 rotate-90" width={32} height={32} /> + </button> )}src/app/(mobile-ui)/qr-pay/page.tsx (5)
148-154
: Clear “Insufficient funds” when balance/amount becomes valid.Right now the error latches. Add an else branch to reset it.
useEffect(() => { if (!usdAmount || balance === undefined) return if (parseUnits(usdAmount, PEANUT_WALLET_TOKEN_DECIMALS) > balance) { setErrorMessage('Insufficient funds') + } else if (errorMessage === 'Insufficient funds') { + setErrorMessage(null) } - }, [usdAmount, balance, setErrorMessage]) + }, [usdAmount, balance, errorMessage])
181-184
: Avoid throwing in render; show a safe fallback instead.Throwing here can crash the page in production. Render a friendly error state.
- //Success - if (isSuccess && !qrPayment) { - // This should never happen, if this happens there is dev error - throw new Error('Invalid state, successful payment but no QR payment data') + // Success + if (isSuccess && !qrPayment) { + return ( + <div className="my-auto flex h-full flex-col justify-center space-y-4"> + <Card className="shadow-4"> + <Card.Header> + <Card.Title>Payment completed</Card.Title> + <Card.Description>We couldn’t load the receipt details. Please check your history.</Card.Description> + </Card.Header> + <Card.Content> + <Button onClick={() => router.push('/home')} variant="purple"> + Back to home + </Button> + </Card.Content> + </Card> + </div> + )
258-263
: Null-guard the icon and fix alt text.
methodIcon
can be null for unknown types; also the alt is always “Mercado Pago”.- <div className="flex items-center justify-center rounded-full bg-white"> - <Image src={methodIcon} alt="Mercado Pago" width={50} height={50} /> - </div> + <div className="flex items-center justify-center rounded-full bg-white"> + {methodIcon && ( + <Image + src={methodIcon} + alt={(qrPayment?.type ?? paymentLock?.type) === 'PIX' ? 'PIX' : 'Mercado Pago'} + width={50} + height={50} + /> + )} + </div>
78-93
: Harden currency resolution: catch price errors; consider proper symbols.
getCurrencyPrice
can throw; surface a user-friendly error.- Optional: map code→symbol instead of
symbol: code
for better UX.@@ return { code: currencyCode, - symbol: currencyCode, + symbol: currencyCode, // TODO: replace with symbol mapping if available price, } } @@ - getCurrencyObject().then(setCurrency) + getCurrencyObject() + .then(setCurrency) + .catch((e: any) => { + setErrorMessage(e?.message ?? 'Failed to resolve currency.') + })If you have a symbol map (e.g.,
SYMBOLS_BY_CURRENCY_CODE
), switch to:- symbol: currencyCode, + symbol: SYMBOLS_BY_CURRENCY_CODE[currencyCode] ?? currencyCode,and import the map from your currency utils.
Also applies to: 101-104
58-62
: Query config nit: staleTime=1ms is unusual.Either rely on
refetchOnMount: 'always'
withstaleTime: 0
, or set a sensible window (e.g., 30_000). Current setting adds noise without benefit.- staleTime: 1, + staleTime: 0, refetchOnMount: 'always',
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
src/assets/payment-apps/mercado-pago.svg
is excluded by!**/*.svg
src/assets/payment-apps/pix.svg
is excluded by!**/*.svg
📒 Files selected for processing (16)
src/app/(mobile-ui)/qr-pay/layout.tsx
(1 hunks)src/app/(mobile-ui)/qr-pay/page.tsx
(1 hunks)src/assets/payment-apps/index.ts
(1 hunks)src/components/0_Bruddle/Card.tsx
(1 hunks)src/components/Global/DirectSendQR/index.tsx
(1 hunks)src/components/Global/TokenAmountInput/index.tsx
(4 hunks)src/components/TransactionDetails/TransactionCard.tsx
(4 hunks)src/components/TransactionDetails/TransactionDetailsDrawer.tsx
(3 hunks)src/components/TransactionDetails/TransactionDetailsHeaderCard.tsx
(1 hunks)src/components/TransactionDetails/TransactionDetailsReceipt.tsx
(3 hunks)src/hooks/useCurrency.ts
(3 hunks)src/hooks/useTransactionHistory.ts
(1 hunks)src/services/manteca.ts
(1 hunks)src/services/services.types.ts
(1 hunks)src/utils/history.utils.ts
(1 hunks)src/utils/index.ts
(1 hunks)
🧰 Additional context used
🧠 Learnings (12)
📚 Learning: 2024-10-22T18:11:36.864Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#469
File: src/app/request/pay/page.tsx:32-49
Timestamp: 2024-10-22T18:11:36.864Z
Learning: In `src/app/request/pay/page.tsx`, the `id` parameter is accessed via `searchParams.id` in the `generateMetadata` function.
Applied to files:
src/app/(mobile-ui)/qr-pay/layout.tsx
📚 Learning: 2024-10-23T09:38:27.670Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#469
File: src/app/request/pay/page.tsx:32-64
Timestamp: 2024-10-23T09:38:27.670Z
Learning: In `src/app/request/pay/page.tsx`, if `linkRes` is not OK in the `generateMetadata` function, the desired behavior is to use the standard title and preview image without throwing an error.
Applied to files:
src/app/(mobile-ui)/qr-pay/layout.tsx
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-10-22T18:10:56.955Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#469
File: src/app/request/pay/page.tsx:25-25
Timestamp: 2024-10-22T18:10:56.955Z
Learning: In the `src/app/request/pay/page.tsx` file, the `PreviewType` enum values are strings, so when adding `previewType` to `URLSearchParams`, there's no need to convert them to strings.
Applied to files:
src/components/Global/DirectSendQR/index.tsx
📚 Learning: 2025-07-24T10:57:15.315Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1000
File: src/components/og/ProfileCardOG.tsx:0-0
Timestamp: 2025-07-24T10:57:15.315Z
Learning: In `src/components/og/ProfileCardOG.tsx`, the scribble image should have an empty alt attribute (alt="") to prevent layout issues if the image fails to load. Since it's a decorative element positioned absolutely over the username text, showing alt text would interfere with the layout and username display.
Applied to files:
src/components/TransactionDetails/TransactionDetailsHeaderCard.tsx
📚 Learning: 2025-08-14T14:42:54.411Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1094
File: src/utils/withdraw.utils.ts:181-191
Timestamp: 2025-08-14T14:42:54.411Z
Learning: The countryCodeMap in src/components/AddMoney/consts/index.ts uses uppercase 3-letter country codes as keys (like 'AUT', 'BEL', 'CZE') that map to 2-letter country codes, requiring input normalization to uppercase for proper lookups.
Applied to files:
src/hooks/useCurrency.ts
📚 Learning: 2024-10-29T12:19:41.968Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#495
File: src/components/Global/TokenAmountInput/index.tsx:23-30
Timestamp: 2024-10-29T12:19:41.968Z
Learning: In the `TokenAmountInput` component (`src/components/Global/TokenAmountInput/index.tsx`), when the 'Max' button is clicked, we intentionally set the input denomination to 'TOKEN' because we are setting the value as token.
Applied to files:
src/components/Global/TokenAmountInput/index.tsx
📚 Learning: 2025-08-22T07:25:59.304Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1104
File: src/components/Payment/PaymentForm/index.tsx:596-600
Timestamp: 2025-08-22T07:25:59.304Z
Learning: The `TokenAmountInput` component in `src/components/Global/TokenAmountInput/` always returns decimal strings (e.g., "1,234.56"), not base units. When passing these values to external APIs like Daimo's `toUnits` prop, simply stripping commas with `.replace(/,/g, '')` is sufficient.
Applied to files:
src/components/Global/TokenAmountInput/index.tsx
📚 Learning: 2024-10-29T12:20:47.207Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#495
File: src/components/Create/Link/Input.view.tsx:244-248
Timestamp: 2024-10-29T12:20:47.207Z
Learning: In the `TokenAmountInput` component within `src/components/Global/TokenAmountInput/index.tsx`, when `balance` is undefined, the `maxValue` prop should be set to an empty string `''`.
Applied to files:
src/components/Global/TokenAmountInput/index.tsx
📚 Learning: 2024-10-08T20:13:42.967Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#413
File: src/context/tokenSelector.context.tsx:118-123
Timestamp: 2024-10-08T20:13:42.967Z
Learning: In the `TokenContextProvider` component within `src/context/tokenSelector.context.tsx`, in the TypeScript React application, when data changes and before calling `fetchAndSetTokenPrice`, it is necessary to reset `selectedTokenData`, `selectedTokenPrice`, `selectedTokenDecimals`, and `inputDenomination` to discard stale data.
Applied to files:
src/components/Global/TokenAmountInput/index.tsx
📚 Learning: 2024-10-07T15:25:45.170Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#422
File: src/components/Request/Pay/Views/Initial.view.tsx:76-78
Timestamp: 2024-10-07T15:25:45.170Z
Learning: In `src/components/Request/Pay/Views/Initial.view.tsx`, both `txFee` and `utils.formatTokenAmount(...)` return strings, ensuring that `calculatedFee` consistently returns a string without the need for additional type conversion.
Applied to files:
src/components/Global/TokenAmountInput/index.tsx
📚 Learning: 2024-10-07T15:28:25.280Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#422
File: src/components/Request/Pay/Views/Initial.view.tsx:76-78
Timestamp: 2024-10-07T15:28:25.280Z
Learning: In `src/components/Request/Pay/Views/Initial.view.tsx`, both `txFee` and `utils.formatTokenAmount(estimatedGasCost, 3)` return strings, ensuring consistent return types for `calculatedFee`.
Applied to files:
src/components/Global/TokenAmountInput/index.tsx
📚 Learning: 2024-12-02T17:21:45.515Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#551
File: src/context/walletContext/walletContext.tsx:87-88
Timestamp: 2024-12-02T17:21:45.515Z
Learning: When converting `totalBalance` (in USD) to a `BigInt` balance in `src/context/walletContext/walletContext.tsx`, multiplying by `1e6` is intentional to maintain compatibility with USDC's 6 decimal places. The application displays only 2 decimal places, so this level of precision is sufficient.
Applied to files:
src/components/Global/TokenAmountInput/index.tsx
🧬 Code graph analysis (6)
src/services/services.types.ts (1)
src/services/manteca.ts (1)
QrPaymentResponse
(63-68)
src/utils/history.utils.ts (1)
src/components/TransactionDetails/transactionTransformer.ts (1)
TransactionDetails
(41-122)
src/app/(mobile-ui)/qr-pay/page.tsx (7)
src/hooks/wallet/useWallet.ts (1)
useWallet
(21-123)src/services/manteca.ts (4)
QrPayment
(11-31)QrPaymentCharge
(33-43)QrPaymentLock
(45-61)mantecaApi
(70-88)src/hooks/useTransactionDetailsDrawer.ts (1)
useTransactionDetailsDrawer
(3-22)src/app/actions/currency.ts (1)
getCurrencyPrice
(7-58)src/utils/general.utils.ts (1)
isTxReverted
(1288-1291)src/services/charges.ts (1)
chargesApi
(6-103)src/components/TransactionDetails/TransactionDetailsReceipt.tsx (1)
TransactionDetailsReceipt
(31-1045)
src/services/manteca.ts (3)
src/services/services.types.ts (1)
QrPaymentResponse
(323-331)src/utils/sentry.utils.ts (1)
fetchWithSentry
(26-104)src/constants/general.consts.ts (1)
PEANUT_API_URL
(43-47)
src/components/TransactionDetails/TransactionCard.tsx (1)
src/utils/history.utils.ts (1)
getAvatarUrl
(5-19)
src/components/Global/TokenAmountInput/index.tsx (2)
src/utils/general.utils.ts (1)
formatAmountWithoutComma
(476-482)src/components/Global/Icons/Icon.tsx (1)
Icon
(195-204)
🪛 GitHub Actions: Tests
src/assets/payment-apps/index.ts
[error] 1-1: Jest test run failed due to an SVG asset import. Unexpected token '<' found when importing apple-pay.svg in this file. This indicates the test environment does not transform or mock SVG assets. Add a Jest transformer or moduleNameMapper entry for '*.svg' (e.g., mock SVGs or use SVGR transformer).
🔇 Additional comments (8)
src/hooks/useTransactionHistory.ts (2)
22-23
: Enum addition looks good.New type
MANTECA_QR_PAYMENT
added toEHistoryEntryType
is clear and scoped.
153-233
: EHistoryEntryType missing MANTECA_QR_PAYMENT
The enum EHistoryEntryType has no MANTECA_QR_PAYMENT value, so the proposed case will never execute – confirm the correct entry type or add MANTECA_QR_PAYMENT to the enum before handling its currency symbol.Likely an incorrect or invalid review comment.
src/components/0_Bruddle/Card.tsx (1)
32-33
: Rounded corners: OK.
rounded-sm
as a default is reasonable and can be overridden viaclassName
thanks totwMerge
.src/utils/index.ts (1)
8-8
: Barrel export safe—no import cycle detected.history.utils.ts contains no imports from
@/utils
, so this re-export does not introduce a circular dependency.src/components/TransactionDetails/TransactionDetailsReceipt.tsx (1)
41-53
: LGTM: avatar plumbed cleanly through Drawer → Receipt → HeaderProp is optional and safely forwarded. No behavior regressions spotted.
Also applies to: 298-300
src/components/TransactionDetails/TransactionDetailsDrawer.tsx (1)
12-13
: LGTM: Drawer now accepts and forwards avatarUrlStraightforward prop plumbing; no side-effects.
Also applies to: 24-25, 58-59
src/components/Global/TokenAmountInput/index.tsx (1)
158-165
: Confirm “U$D” labeling is intentional across localesIf this is AR-specific, consider gating by locale or deriving from a currency map to avoid surprising users expecting “USD”/“$”.
Would you like a quick helper to centralize currency display symbols per locale?src/app/(mobile-ui)/qr-pay/page.tsx (1)
100-104
: Nice use of dataUpdatedAt to drive effect updates.This pattern avoids stale refs when TanStack reuses result objects. Good call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/app/(mobile-ui)/qr-pay/page.tsx (1)
33-35
: Prevent double-send and surface failures: add anisPaying
guard and try/catch.Users can tap Send twice before global
isLoading
flips; errors fromsendMoney
also go unhandled.Apply:
@@ - const [isSuccess, setIsSuccess] = useState(false) + const [isSuccess, setIsSuccess] = useState(false) + const [isPaying, setIsPaying] = useState(false) @@ - const payQR = useCallback(async () => { - if (!qrPayment || !charge || !usdAmount) return - const { userOpHash, receipt } = await sendMoney(qrPayment.details.depositAddress, usdAmount) - if (receipt !== null && isTxReverted(receipt)) { - setErrorMessage('Transaction reverted by the network.') - setIsSuccess(false) - return - } - const txHash = receipt?.transactionHash ?? userOpHash - chargesApi.createPayment({ - chargeId: charge.uuid, - chainId: charge.chainId, - hash: txHash, - tokenAddress: charge.tokenAddress, - payerAddress: address, - }) - setIsSuccess(true) - }, [qrPayment, charge, sendMoney, usdAmount]) + const payQR = useCallback(async () => { + if (!qrPayment || !charge || !usdAmount) return + if (isPaying) return + setIsPaying(true) + try { + const { userOpHash, receipt } = await sendMoney(qrPayment.details.depositAddress, usdAmount) + if (receipt !== null && isTxReverted(receipt)) { + setErrorMessage('Transaction reverted by the network.') + setIsSuccess(false) + return + } + const txHash = receipt?.transactionHash ?? userOpHash + // Non-blocking by design per previous discussion + chargesApi.createPayment({ + chargeId: charge.uuid, + chainId: charge.chainId, + hash: txHash, + tokenAddress: charge.tokenAddress, + payerAddress: address, + }) + setIsSuccess(true) + } catch (e: any) { + setErrorMessage(e?.message ?? 'Failed to complete payment.') + setIsSuccess(false) + } finally { + setIsPaying(false) + } + }, [qrPayment, charge, sendMoney, usdAmount, address, isPaying]) @@ - loading={isFetching || isLoading} - disabled={isErrorInitiatingPayment || !!errorMessage || isFetching || !amount || isLoading} + loading={isFetching || isLoading || isPaying} + disabled={isErrorInitiatingPayment || !!errorMessage || isFetching || !amount || isLoading || isPaying}Also applies to: 131-148, 288-303
🧹 Nitpick comments (3)
src/app/(mobile-ui)/qr-pay/page.tsx (3)
151-156
: Auto-clear “Insufficient funds” once balance is adequate.Avoids a sticky error that keeps the button disabled after funds become sufficient.
useEffect(() => { if (!usdAmount || balance === undefined) return - if (parseUnits(usdAmount, PEANUT_WALLET_TOKEN_DECIMALS) > balance) { - setErrorMessage('Insufficient funds') - } -}, [usdAmount, balance, setErrorMessage]) + const needed = parseUnits(usdAmount, PEANUT_WALLET_TOKEN_DECIMALS) + if (needed > balance) { + setErrorMessage('Insufficient funds') + } else { + setErrorMessage((prev) => (prev === 'Insufficient funds' ? null : prev)) + } +}, [usdAmount, balance])
262-264
: Guard Image when icon is null and fix alt text.Next/Image will error on null src; also the alt is hardcoded to “Mercado Pago.”
- <div className="flex items-center justify-center rounded-full bg-white"> - <Image src={methodIcon} alt="Mercado Pago" width={50} height={50} /> - </div> + <div className="flex items-center justify-center rounded-full bg-white"> + {methodIcon ? ( + <Image + src={methodIcon} + alt={(qrPayment?.type ?? paymentLock?.type) === 'PIX' ? 'PIX' : 'Mercado Pago'} + width={50} + height={50} + /> + ) : ( + <Icon name="qr-code" size={32} /> + )} + </div>Also applies to: 114-124
52-64
: Minor: re-evaluate query staleness strategy.
staleTime: 1
withrefetchOnMount: 'always'
forces extra network churn. If you rely on manualrefetch
, considerstaleTime: 0
or removerefetchOnMount
. Not blocking.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/app/(mobile-ui)/qr-pay/page.tsx
(1 hunks)
🧰 Additional context used
🧠 Learnings (12)
📓 Common learnings
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:156-176
Timestamp: 2025-09-08T03:13:09.093Z
Learning: In the peanut-ui mobile app, the `/qr-pay` route is only accessed through the DirectSendQR component which always includes the qrCode parameter in the URL when redirecting users to the QR pay page after scanning MERCADO_PAGO or PIX QR codes.
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:156-176
Timestamp: 2025-09-08T03:13:09.093Z
Learning: In the peanut-ui mobile app, the `/qr-pay` route is only accessed through the DirectSendQR component which always includes the qrCode parameter in the URL when redirecting users to the QR pay page.
📚 Learning: 2025-09-08T03:13:09.093Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:156-176
Timestamp: 2025-09-08T03:13:09.093Z
Learning: In the peanut-ui mobile app, the `/qr-pay` route is only accessed through the DirectSendQR component which always includes the qrCode parameter in the URL when redirecting users to the QR pay page after scanning MERCADO_PAGO or PIX QR codes.
Applied to files:
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-09-08T03:13:09.093Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:156-176
Timestamp: 2025-09-08T03:13:09.093Z
Learning: In the peanut-ui mobile app, the `/qr-pay` route is only accessed through the DirectSendQR component which always includes the qrCode parameter in the URL when redirecting users to the QR pay page.
Applied to files:
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-08-26T15:25:53.328Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1132
File: src/app/[...recipient]/client.tsx:394-397
Timestamp: 2025-08-26T15:25:53.328Z
Learning: In `src/components/Common/ActionListDaimoPayButton.tsx`, the `handleCompleteDaimoPayment` function should not display error messages to users when DB update fails because the Daimo payment itself has succeeded - showing errors would be confusing since the payment was successful.
Applied to files:
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-09-08T03:11:57.201Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:64-73
Timestamp: 2025-09-08T03:11:57.201Z
Learning: In QR payment flows, user-entered amounts should be cleared and overridden by API response amounts to ensure the authoritative payment amount from the API takes precedence over user input.
Applied to files:
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-10-08T20:13:42.967Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#413
File: src/context/tokenSelector.context.tsx:118-123
Timestamp: 2024-10-08T20:13:42.967Z
Learning: In the `TokenContextProvider` component within `src/context/tokenSelector.context.tsx`, in the TypeScript React application, when data changes and before calling `fetchAndSetTokenPrice`, it is necessary to reset `selectedTokenData`, `selectedTokenPrice`, `selectedTokenDecimals`, and `inputDenomination` to discard stale data.
Applied to files:
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-10-23T09:38:27.670Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#469
File: src/app/request/pay/page.tsx:32-64
Timestamp: 2024-10-23T09:38:27.670Z
Learning: In `src/app/request/pay/page.tsx`, if `linkRes` is not OK in the `generateMetadata` function, the desired behavior is to use the standard title and preview image without throwing an error.
Applied to files:
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-10-08T20:13:42.967Z
Learnt from: Hugo0
PR: peanutprotocol/peanut-ui#422
File: src/components/Request/Pay/Pay.tsx:113-123
Timestamp: 2024-10-08T20:13:42.967Z
Learning: In the `PayRequestLink` component (`src/components/Request/Pay/Pay.tsx`), when resolving ENS names, handle errors by displaying an appropriate error message to the user if the ENS cannot be resolved.
Applied to files:
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-09-08T03:11:00.076Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:31-41
Timestamp: 2025-09-08T03:11:00.076Z
Learning: In QR payment flows, the `createPayment` API call for payment tracking can be non-awaited when called after successful transaction execution, as it's a non-blocking logging operation and doesn't affect the user's success state.
Applied to files:
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-10-22T18:11:36.864Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#469
File: src/app/request/pay/page.tsx:32-49
Timestamp: 2024-10-22T18:11:36.864Z
Learning: In `src/app/request/pay/page.tsx`, the `id` parameter is accessed via `searchParams.id` in the `generateMetadata` function.
Applied to files:
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-09-05T07:31:11.396Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1185
File: src/components/Claim/useClaimLink.tsx:14-0
Timestamp: 2025-09-05T07:31:11.396Z
Learning: In the peanut-ui codebase, `window.history.replaceState` is preferred over `router.replace` when immediate/synchronous URL parameter updates are required, as `router.replace` is asynchronous and doesn't guarantee instant URL changes that subsequent code can rely on. This pattern is used consistently across usePaymentInitiator.ts, Confirm.payment.view.tsx, and useClaimLink.tsx.
Applied to files:
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-12-11T10:13:22.806Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#564
File: src/components/Request/Pay/Views/Initial.view.tsx:430-430
Timestamp: 2024-12-11T10:13:22.806Z
Learning: In the React TypeScript file `src/components/Request/Pay/Views/Initial.view.tsx`, when reviewing the `InitialView` component, do not flag potential issues with using non-null assertion `!` on the `slippagePercentage` variable, as handling undefined values in this context is considered out of scope.
Applied to files:
src/app/(mobile-ui)/qr-pay/page.tsx
🧬 Code graph analysis (1)
src/app/(mobile-ui)/qr-pay/page.tsx (8)
src/hooks/wallet/useWallet.ts (1)
useWallet
(21-123)src/services/manteca.ts (4)
QrPayment
(11-31)QrPaymentCharge
(33-43)QrPaymentLock
(45-61)mantecaApi
(70-88)src/hooks/useTransactionDetailsDrawer.ts (1)
useTransactionDetailsDrawer
(3-22)src/app/actions/currency.ts (1)
getCurrencyPrice
(7-58)src/utils/general.utils.ts (1)
isTxReverted
(1288-1291)src/services/charges.ts (1)
chargesApi
(6-103)src/constants/zerodev.consts.ts (1)
PEANUT_WALLET_TOKEN_DECIMALS
(19-19)src/components/TransactionDetails/TransactionDetailsReceipt.tsx (1)
TransactionDetailsReceipt
(31-1045)
🔇 Additional comments (1)
src/app/(mobile-ui)/qr-pay/page.tsx (1)
139-147
: Non-blocking tracking call looks good.Not awaiting
chargesApi.createPayment
matches the intended post-success logging behavior noted earlier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
temp
<div className="flex items-center gap-1 font-bold"> | ||
<label className={`text-2xl ${displayValue ? 'text-black' : 'text-gray-2'}`}>{displaySymbol}</label> | ||
<input | ||
className={`h-12 w-[4ch] max-w-80 bg-transparent text-6xl font-black outline-none transition-colors placeholder:text-h1 focus:border-primary-1 dark:border-white dark:bg-n-1 dark:text-white dark:placeholder:text-white/75 dark:focus:border-primary-1`} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really think, in general, we have wayyy too little reuse and too much component specific code. This applies to both styling (as you can see with 100000 million tailwind classses in FE code) and with logic
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is literally the TokenAmountInput which is reused in a lot of place and is a building block of the application. But in general, I agree
} | ||
| { paymentLock: QrPaymentLock } | ||
|
||
export const mantecaApi = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
jsdoc would be good here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I dont see what JSDoc would add here. We have this pattern all over the app and is pretty self explanatory
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good PR overall! I have various blockers
- currency rate
- doubts on prefunded $
New payment flow where we have to init the lock and then complete it after the user sends the money to the manteca address. Also history items
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
src/components/AddMoney/components/InputAmountStep.tsx (1)
52-58
: Possible crash: non‑null assertion on price when fetch fails.
currencyData.price!.buy
will throw when price fetch fails (hook setsprice
to null on catch). Use a safe fallback per prior patterns.- price: currencyData.price!.buy, + price: currencyData.price?.buy ?? 0,src/app/[...recipient]/client.tsx (1)
472-480
: Possible crash: non‑null assertion on currencyPrice.If price fetch fails,
currencyPrice
may be null; this non‑null assertion will throw in client. Use safe fallback.- price: currencyPrice!.buy, + price: currencyPrice?.buy ?? 0,src/components/Request/views/ReqFulfillBankFlowManager.tsx (1)
22-23
: getCurrencyPrice is a Server Action — do NOT import it into this client component.src/app/actions/currency.ts starts with 'use server' (line 1), so getCurrencyPrice is a server action. Importing it into src/components/Request/views/ReqFulfillBankFlowManager.tsx (lines 22–23), which is a client component, will break the build or expose server internals.
Fix options:
- Move the call into a Server Component (or server-side parent).
- Expose the logic via a client-safe API route/wrapper and fetch from the client.
- Remove 'use client' from this component if it can be server-only.
src/services/manteca.ts (1)
1-146
: Do not expose PEANUT_API_KEY to client bundles (critical)mantecaApi is imported by a client component (src/app/(mobile-ui)/qr-pay/page.tsx — "use client"), while src/services/manteca.ts imports PEANUT_API_KEY from src/constants/general.consts.ts and uses it in getPrices. That top-level import will cause the API key to be present in the client bundle.
Actions to fix:
- Move server-only logic that needs the key (getPrices / any
'api-key'
usage) into a server-only module (e.g., src/services/manteca.server.ts) that imports PEANUT_API_KEY. Update server code (src/app/actions/currency.ts) to call the server module.- Keep client-facing code (initiateQrPayment/completeQrPayment) in a client-safe module that does NOT import PEANUT_API_KEY, or have the client call a server action/route instead.
- Quick mitigation: remove PEANUT_API_KEY import from src/services/manteca.ts and ensure no client files import modules that reference the key.
Locations to change: src/services/manteca.ts, src/app/(mobile-ui)/qr-pay/page.tsx, src/constants/general.consts.ts, src/app/actions/currency.ts.
♻️ Duplicate comments (3)
src/hooks/useCurrency.ts (1)
4-19
: Good: centralized, exported symbol map replaces ad‑hoc defaults.Moving to
SYMBOLS_BY_CURRENCY_CODE
addresses prior feedback about hardcoding$
. Nice.src/app/(mobile-ui)/qr-pay/page.tsx (2)
24-24
: Don’t import server-only action into a client component; use a client-safe wrapper/API.
getCurrencyPrice
is a server action (usesunstable_cache
). Route via an API or a client wrapper instead.-import { getCurrencyPrice } from '@/app/actions/currency' +import { getCurrencyPriceClient } from '@/lib/client/currency' @@ - if (paymentLock.code === '') { - price = (await getCurrencyPrice(currencyCode)).sell + if (paymentLock.code === '') { + price = (await getCurrencyPriceClient(currencyCode)).sellClient wrapper:
// src/lib/client/currency.ts export async function getCurrencyPriceClient(code: string) { const res = await fetch(`/api/currency/price?code=${encodeURIComponent(code)}`, { cache: 'no-store' }) if (!res.ok) throw new Error('Failed to fetch currency price') return res.json() as Promise<{ buy: number; sell: number }> }API route (server):
// src/app/api/currency/price/route.ts import { NextResponse } from 'next/server' import { getCurrencyPrice } from '@/app/actions/currency' export async function GET(req: Request) { const code = new URL(req.url).searchParams.get('code') if (!code) return NextResponse.json({ error: 'Missing code' }, { status: 400 }) try { return NextResponse.json(await getCurrencyPrice(code)) } catch (e: any) { return NextResponse.json({ error: e?.message ?? 'Failed' }, { status: 500 }) } }Also applies to: 96-115
144-187
: Add a submit guard to prevent double-pay taps.Use an
isPaying
flag aroundpayQR
and wire it into the button’s loading/disabled state.+ const [isPaying, setIsPaying] = useState(false) @@ - const payQR = useCallback(async () => { + const payQR = useCallback(async () => { + if (isPaying) return + setIsPaying(true) try { // ...existing logic... - setIsSuccess(true) + setIsSuccess(true) } catch (error) { // ...existing error handling... } finally { - setLoadingState('Idle') + setLoadingState('Idle') + setIsPaying(false) } - }, [paymentLock?.code, sendMoney, usdAmount, qrCode, currencyAmount]) + }, [paymentLock?.code, sendMoney, usdAmount, qrCode, currencyAmount, isPaying]) @@ - loading={isLoading} - disabled={!!errorInitiatingPayment || !!errorMessage || !amount || isLoading || isBalanceError} + loading={isLoading || isPaying} + disabled={!!errorInitiatingPayment || !!errorMessage || !amount || isLoading || isPaying || isBalanceError}Also applies to: 350-358
🧹 Nitpick comments (23)
src/components/Global/DirectSendQR/utils.ts (3)
53-54
: Tighten PIX regex (anchor + CRC) to reduce false positivesBroader
.*
matches can misclassify non-EMV strings. Anchoring to start and requiring CRC aligns with EMVCo payloads and mirrors the stricter MP pattern.Apply this diff:
-const PIX_REGEX = /^.*000201.*0014br\.gov\.bcb\.pix.*5303986.*5802BR.*$/i +const PIX_REGEX = /^000201((?!6304).)*(?:26|27)\d{2}0014br\.gov\.bcb\.pix((?!6304).)*5303986((?!6304).)*5802BR((?!6304).)*6304[0-9A-F]{4}$/i
55-58
: Prefer Partial<Record<...>> for readabilitySame type, clearer intent for a sparse mapping.
-export const QR_PAY_REGEXES: { [key in QrType]?: RegExp } = { +export const QR_PAY_REGEXES: Partial<Record<QrType, RegExp>> = { [EQrType.MERCADO_PAGO]: MP_AR_REGEX, [EQrType.PIX]: PIX_REGEX, }
96-104
: Simplify isQRPay by delegating to recognizeQr (avoid drift/dup regex runs)Leverage the existing detection and just gate on the two QR‑pay types.
-export const isQRPay = (data: string): boolean => { - for (const [_type, regex] of Object.entries(QR_PAY_REGEXES)) { - if (regex.test(data)) { - return true - } - } - return false -} +export const isQRPay = (data: string): boolean => { + const type = recognizeQr(data) + return type === EQrType.MERCADO_PAGO || type === EQrType.PIX +}src/components/Common/ActionListDaimoPayButton.tsx (2)
55-56
: Correct buy/sell handling with safe fallback.Using
currencyPrice?.buy || 0
is fine; consider the nullish coalescing equivalent to avoid treating0
as falsy.- price: currencyPrice?.buy || 0, + price: currencyPrice?.buy ?? 0,
28-33
: Sanitize formatted numbers before parsing/using.
usdAmount
may include commas;parseFloat('1,234.56')
yields1
. Sanitize before validation and before sending.- if (!usdAmount || parseFloat(usdAmount) <= 0) { + const normalizedUsd = (usdAmount ?? '').replace(/,/g, '') + if (!normalizedUsd || parseFloat(normalizedUsd) <= 0) { console.error('Invalid amount entered') dispatch(paymentActions.setError('Invalid amount')) return false }src/hooks/useCurrency.ts (3)
24-26
: isLoading default false can cause a brief stale render.Initialize
isLoading
totrue
whencode
is present to avoid a flash of “not loading” before the effect flips it.- const [isLoading, setIsLoading] = useState<boolean>(false) + const [isLoading, setIsLoading] = useState<boolean>(!!(currencyCode?.toUpperCase()))
40-45
: Avoid stale state when rejecting unsupported codes.When invalid, also clear
symbol
andprice
to prevent consumers from reading previous values.- if (!Object.keys(SYMBOLS_BY_CURRENCY_CODE).includes(code)) { - setCode(null) + if (!(code in SYMBOLS_BY_CURRENCY_CODE)) { + setCode(null) + setSymbol(null) + setPrice(null) setIsLoading(false) return }
46-56
: Add a safe fallback on fetch failure to match consumers expecting numbers.Some callers non‑null assert price (e.g., pages/components). Set a safe fallback object to avoid crashes when the fetch fails.
.catch((err) => { console.error(err) - setIsLoading(false) + // Keep symbol (if set) but ensure price shape exists to avoid null-deref downstream. + setPrice((prev) => prev ?? { buy: 0, sell: 0 }) + setIsLoading(false) })src/app/actions/onramp.ts (1)
126-128
: Minor: remove redundant await on cookies().
cookies()
is synchronous in Next.js App Router. Theawait
is unnecessary.- const cookieStore = cookies() - const jwtToken = (await cookieStore).get('jwt-token')?.value + const jwtToken = cookies().get('jwt-token')?.valuesrc/components/Request/views/ReqFulfillBankFlowManager.tsx (1)
65-66
: Buy leg usage looks right; add sanitize and failure fallback.If
getCurrencyPrice
fails orusdAmount
has commas, this can miscompute. Normalize input and handle{ buy: 0 }
fallback.- const currencyAmount = Number(usdAmount) * price.buy + const currencyAmount = Number(String(usdAmount).replace(/,/g, '')) * (price?.buy ?? 0)src/components/Claim/Link/views/Confirm.bank-claim.view.tsx (5)
74-75
: Make the conversion-failure check numeric-safe.Use a finite-number guard to avoid edge cases (undefined, null, NaN, non-number).
Apply:
- return currencyCode !== 'USD' && !isLoadingCurrency && (!price?.sell || isNaN(price.sell)) + return ( + currencyCode !== 'USD' && + !isLoadingCurrency && + !(typeof price?.sell === 'number' && Number.isFinite(price.sell)) + )
81-83
: Stabilize conversion + formatting.Guard with Number.isFinite and use locale formatting for consistency with other displays.
- if (!price?.sell || isNaN(price.sell)) return usdAmount - const converted = (Number(usdAmount) * price.sell).toFixed(2) - return converted + const rate = price?.sell + if (!(typeof rate === 'number' && Number.isFinite(rate))) return usdAmount + const converted = Number(usdAmount) * rate + return converted.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
91-91
: Unify symbol fallback with utils.Prefer the centralized resolver so unknown codes don’t render raw currency codes as symbols.
- return resolvedSymbol ?? currencyCode + return resolvedSymbol ?? getDisplayCurrencySymbol(currencyCode)Add import at top (outside this hunk):
import { getDisplayCurrencySymbol } from '@/utils/currency'
56-60
: formatUnits fallback should use bigint.formatUnits(value, decimals) expects a bigint value. Passing 0 (number) can be brittle in TS.
- const usdAmount = useMemo( - () => formatUnits(claimLinkData?.amount ?? 0, claimLinkData?.tokenDecimals) || '0.00', + const usdAmount = useMemo( + () => formatUnits((claimLinkData?.amount ?? 0n) as bigint, claimLinkData?.tokenDecimals) || '0.00', [claimLinkData] )
124-124
: Fee currency should match display currency.Currently hard-coded as USD. Use displaySymbol for consistency.
- <PaymentInfoRow hideBottomBorder label="Fee" value={`$ 0.00`} /> + <PaymentInfoRow hideBottomBorder label="Fee" value={`${displaySymbol} 0.00`} />src/components/TransactionDetails/transactionTransformer.ts (2)
74-75
: Propagate avatarUrl through to the drawer.You added the type but don’t set the value. Pass through any provided avatar from history.extraData.
export interface TransactionDetails { ... extraDataForDrawer?: { ... - avatarUrl?: string + avatarUrl?: string ... } } @@ extraDataForDrawer: { addressExplorerUrl, originalType: entry.type as EHistoryEntryType, originalUserRole: entry.userRole as EHistoryUserRole, link: entry.extraData?.link, isLinkTransaction: isLinkTx, transactionCardType, rewardData, fulfillmentType: entry.extraData?.fulfillmentType, bridgeTransferId: entry.extraData?.bridgeTransferId, + avatarUrl: entry.extraData?.avatarUrl, depositInstructions:Also applies to: 456-470
439-442
: Derive currency symbol from currency code, not hard-coded '$'.If entry.currency is non-USD, the UI still shows '$'. Use the resolver for correctness.
- currencySymbol: `${entry.userRole === EHistoryUserRole.SENDER ? '-' : '+'}$`, + currencySymbol: `${entry.userRole === EHistoryUserRole.SENDER ? '-' : '+'}${getDisplayCurrencySymbol(entry.currency?.code ?? 'USD')}`,Add import (top of file, outside hunk):
import { getDisplayCurrencySymbol } from '@/utils/currency'src/utils/currency.ts (1)
7-7
: Prefer fallbackSymbol over echoing unknown codes.Echoing the code (e.g., 'XYZ') as a symbol can look broken. Favor the caller-provided fallback.
- return SYMBOLS_BY_CURRENCY_CODE[upperCode] ?? upperCode + return SYMBOLS_BY_CURRENCY_CODE[upperCode] ?? fallbackSymbolsrc/components/TransactionDetails/TransactionDetailsHeaderCard.tsx (2)
115-122
: Copy for qr_payment titles reads well.Optional: For failed state, “Payment to X failed” may be clearer than “Payment to X”.
- titleText = `Payment to ${displayName}` + titleText = `Payment to ${displayName} failed`
179-181
: Improve avatar image accessibility and sizing consistency.Use a meaningful alt (or empty if decorative) and match width/height to CSS to prevent layout shifts on Next/Image.
- <div className="flex h-16 w-16 items-center justify-center rounded-full"> - <Image src={avatarUrl} alt="Icon" className="size-16 object-contain" width={35} height={35} /> + <div className="flex h-16 w-16 items-center justify-center rounded-full"> + <Image + src={avatarUrl} + alt={userName ? `${userName} logo` : ''} + className="size-16 object-contain" + width={64} + height={64} + /> </div>src/components/TransactionDetails/TransactionDetailsReceipt.tsx (1)
443-461
: Unify currency display for USD line.Use the same symbolization logic used elsewhere to avoid “U$D” inconsistency.
- value={`${formatAmount(transaction.amount.toString())} U$D`} + value={`$ ${formatAmount(Number(transaction.amount))}`}src/app/(mobile-ui)/qr-pay/page.tsx (1)
160-163
: Remove stray no-op expression.
finalPaymentLock
on its own line does nothing; likely a leftover.- if (finalPaymentLock.code === '') { - finalPaymentLock + if (finalPaymentLock.code === '') {src/services/manteca.ts (1)
33-44
: Remove unused types to reduce surface area.
QrPaymentCharge
andQrPaymentResponse
aren’t used with the current API. Delete to avoid confusion.- export type QrPaymentCharge = { - uuid: string - createdAt: string - link: string - chainId: string - tokenAmount: string - tokenAddress: string - tokenDecimals: number - tokenType: string - tokenSymbol: string - } @@ - export type QrPaymentResponse = - | { qrPayment: QrPayment; charge: QrPaymentCharge } - | { paymentLock: QrPaymentLock }Also applies to: 63-69
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (24)
src/app/(mobile-ui)/qr-pay/layout.tsx
(1 hunks)src/app/(mobile-ui)/qr-pay/page.tsx
(1 hunks)src/app/[...recipient]/client.tsx
(1 hunks)src/app/actions/currency.ts
(2 hunks)src/app/actions/onramp.ts
(1 hunks)src/components/AddMoney/components/InputAmountStep.tsx
(1 hunks)src/components/Claim/Link/views/Confirm.bank-claim.view.tsx
(1 hunks)src/components/Common/ActionListDaimoPayButton.tsx
(1 hunks)src/components/Global/DirectSendQR/index.tsx
(1 hunks)src/components/Global/DirectSendQR/utils.ts
(2 hunks)src/components/Global/TokenAmountInput/index.tsx
(4 hunks)src/components/Request/views/ReqFulfillBankFlowManager.tsx
(1 hunks)src/components/TransactionDetails/TransactionCard.tsx
(7 hunks)src/components/TransactionDetails/TransactionDetailsHeaderCard.tsx
(4 hunks)src/components/TransactionDetails/TransactionDetailsReceipt.tsx
(7 hunks)src/components/TransactionDetails/transactionTransformer.ts
(3 hunks)src/constants/general.consts.ts
(1 hunks)src/constants/loadingStates.consts.ts
(2 hunks)src/hooks/useCreateOnramp.ts
(1 hunks)src/hooks/useCurrency.ts
(3 hunks)src/services/manteca.ts
(1 hunks)src/utils/__tests__/bridge.utils.test.ts
(0 hunks)src/utils/currency.ts
(1 hunks)src/utils/index.ts
(1 hunks)
💤 Files with no reviewable changes (1)
- src/utils/tests/bridge.utils.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
- src/utils/index.ts
- src/components/Global/DirectSendQR/index.tsx
- src/app/(mobile-ui)/qr-pay/layout.tsx
- src/components/Global/TokenAmountInput/index.tsx
🧰 Additional context used
🧠 Learnings (25)
📓 Common learnings
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:156-176
Timestamp: 2025-09-08T03:13:09.111Z
Learning: In the peanut-ui mobile app, the `/qr-pay` route is only accessed through the DirectSendQR component which always includes the qrCode parameter in the URL when redirecting users to the QR pay page after scanning MERCADO_PAGO or PIX QR codes.
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:156-176
Timestamp: 2025-09-08T03:13:09.111Z
Learning: In the peanut-ui mobile app, the `/qr-pay` route is only accessed through the DirectSendQR component which always includes the qrCode parameter in the URL when redirecting users to the QR pay page.
📚 Learning: 2024-10-07T15:25:45.170Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#422
File: src/components/Request/Pay/Views/Initial.view.tsx:76-78
Timestamp: 2024-10-07T15:25:45.170Z
Learning: In `src/components/Request/Pay/Views/Initial.view.tsx`, both `txFee` and `utils.formatTokenAmount(...)` return strings, ensuring that `calculatedFee` consistently returns a string without the need for additional type conversion.
Applied to files:
src/components/Request/views/ReqFulfillBankFlowManager.tsx
src/components/Claim/Link/views/Confirm.bank-claim.view.tsx
src/components/AddMoney/components/InputAmountStep.tsx
src/app/[...recipient]/client.tsx
📚 Learning: 2024-10-07T15:28:25.280Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#422
File: src/components/Request/Pay/Views/Initial.view.tsx:76-78
Timestamp: 2024-10-07T15:28:25.280Z
Learning: In `src/components/Request/Pay/Views/Initial.view.tsx`, both `txFee` and `utils.formatTokenAmount(estimatedGasCost, 3)` return strings, ensuring consistent return types for `calculatedFee`.
Applied to files:
src/components/Request/views/ReqFulfillBankFlowManager.tsx
src/components/Claim/Link/views/Confirm.bank-claim.view.tsx
src/app/[...recipient]/client.tsx
📚 Learning: 2024-10-07T13:42:00.443Z
Learnt from: Hugo0
PR: peanutprotocol/peanut-ui#422
File: src/components/Request/Pay/Pay.tsx:103-111
Timestamp: 2024-10-07T13:42:00.443Z
Learning: When the token price cannot be fetched in `src/components/Request/Pay/Pay.tsx` within the `PayRequestLink` component, set `tokenPriceData.price` to 0 to ensure the UI remains functional. Since Squid uses their own price engine for x-chain fulfillment transactions, this approach will not affect the transaction computation.
Applied to files:
src/components/Request/views/ReqFulfillBankFlowManager.tsx
src/components/Common/ActionListDaimoPayButton.tsx
src/components/AddMoney/components/InputAmountStep.tsx
src/app/[...recipient]/client.tsx
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-10-08T20:13:42.967Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#422
File: src/components/Request/Pay/Pay.consts.ts:34-34
Timestamp: 2024-10-08T20:13:42.967Z
Learning: In `src/components/Request/Pay` components, the `tokenPrice` property in the `IPayScreenProps` interface is only relevant to these views. Other components using `IPayScreenProps` do not need to handle `tokenPriceData` when it's updated in these components.
Applied to files:
src/components/Request/views/ReqFulfillBankFlowManager.tsx
src/components/Common/ActionListDaimoPayButton.tsx
src/components/AddMoney/components/InputAmountStep.tsx
src/app/[...recipient]/client.tsx
📚 Learning: 2025-08-26T15:25:53.328Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1132
File: src/app/[...recipient]/client.tsx:394-397
Timestamp: 2025-08-26T15:25:53.328Z
Learning: In `src/components/Common/ActionListDaimoPayButton.tsx`, the `handleCompleteDaimoPayment` function should not display error messages to users when DB update fails because the Daimo payment itself has succeeded - showing errors would be confusing since the payment was successful.
Applied to files:
src/components/Common/ActionListDaimoPayButton.tsx
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-11-18T21:36:11.486Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#535
File: src/components/Claim/Claim.tsx:142-146
Timestamp: 2024-11-18T21:36:11.486Z
Learning: In `src/components/Claim/Claim.tsx`, external calls like token price fetching and cross-chain details retrieval are already encapsulated within existing `try...catch` blocks, so additional error handling may be unnecessary.
Applied to files:
src/components/Claim/Link/views/Confirm.bank-claim.view.tsx
📚 Learning: 2024-10-18T01:51:35.247Z
Learnt from: Hugo0
PR: peanutprotocol/peanut-ui#458
File: src/components/Offramp/Confirm.view.tsx:141-141
Timestamp: 2024-10-18T01:51:35.247Z
Learning: The `handleConfirm` function in `src/components/Create/Link/Confirm.view.tsx` is separate from the one in `src/components/Offramp/Confirm.view.tsx` and does not need to be renamed when refactoring `handleConfirm` in `src/components/Offramp/Confirm.view.tsx`.
Applied to files:
src/components/Claim/Link/views/Confirm.bank-claim.view.tsx
📚 Learning: 2025-08-12T17:47:28.362Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1089
File: src/app/api/bridge/exchange-rate/route.ts:4-19
Timestamp: 2025-08-12T17:47:28.362Z
Learning: In the Bridge exchange rate API route (src/app/api/bridge/exchange-rate/route.ts), the ExchangeRateResponse interface uses numeric types for rates because the route converts string values from the Bridge API to floats using parseFloat() before returning the response.
Applied to files:
src/components/Claim/Link/views/Confirm.bank-claim.view.tsx
src/app/actions/currency.ts
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-10-29T12:19:41.968Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#495
File: src/components/Global/TokenAmountInput/index.tsx:23-30
Timestamp: 2024-10-29T12:19:41.968Z
Learning: In the `TokenAmountInput` component (`src/components/Global/TokenAmountInput/index.tsx`), when the 'Max' button is clicked, we intentionally set the input denomination to 'TOKEN' because we are setting the value as token.
Applied to files:
src/components/AddMoney/components/InputAmountStep.tsx
📚 Learning: 2024-10-08T20:13:42.967Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#413
File: src/context/tokenSelector.context.tsx:118-123
Timestamp: 2024-10-08T20:13:42.967Z
Learning: In the `TokenContextProvider` component within `src/context/tokenSelector.context.tsx`, in the TypeScript React application, when data changes and before calling `fetchAndSetTokenPrice`, it is necessary to reset `selectedTokenData`, `selectedTokenPrice`, `selectedTokenDecimals`, and `inputDenomination` to discard stale data.
Applied to files:
src/components/AddMoney/components/InputAmountStep.tsx
src/app/[...recipient]/client.tsx
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-08-22T07:25:59.304Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1104
File: src/components/Payment/PaymentForm/index.tsx:596-600
Timestamp: 2025-08-22T07:25:59.304Z
Learning: The `TokenAmountInput` component in `src/components/Global/TokenAmountInput/` always returns decimal strings (e.g., "1,234.56"), not base units. When passing these values to external APIs like Daimo's `toUnits` prop, simply stripping commas with `.replace(/,/g, '')` is sufficient.
Applied to files:
src/components/AddMoney/components/InputAmountStep.tsx
📚 Learning: 2024-10-29T12:20:47.207Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#495
File: src/components/Create/Link/Input.view.tsx:244-248
Timestamp: 2024-10-29T12:20:47.207Z
Learning: In the `TokenAmountInput` component within `src/components/Global/TokenAmountInput/index.tsx`, when `balance` is undefined, the `maxValue` prop should be set to an empty string `''`.
Applied to files:
src/components/AddMoney/components/InputAmountStep.tsx
📚 Learning: 2024-12-11T10:13:22.806Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#564
File: src/components/Request/Pay/Views/Initial.view.tsx:430-430
Timestamp: 2024-12-11T10:13:22.806Z
Learning: In the React TypeScript file `src/components/Request/Pay/Views/Initial.view.tsx`, when reviewing the `InitialView` component, do not flag potential issues with using non-null assertion `!` on the `slippagePercentage` variable, as handling undefined values in this context is considered out of scope.
Applied to files:
src/components/AddMoney/components/InputAmountStep.tsx
src/app/[...recipient]/client.tsx
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-09-08T03:13:09.111Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:156-176
Timestamp: 2025-09-08T03:13:09.111Z
Learning: In the peanut-ui mobile app, the `/qr-pay` route is only accessed through the DirectSendQR component which always includes the qrCode parameter in the URL when redirecting users to the QR pay page after scanning MERCADO_PAGO or PIX QR codes.
Applied to files:
src/components/Global/DirectSendQR/utils.ts
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-08-14T14:42:54.411Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1094
File: src/utils/withdraw.utils.ts:181-191
Timestamp: 2025-08-14T14:42:54.411Z
Learning: The countryCodeMap in src/components/AddMoney/consts/index.ts uses uppercase 3-letter country codes as keys (like 'AUT', 'BEL', 'CZE') that map to 2-letter country codes, requiring input normalization to uppercase for proper lookups.
Applied to files:
src/utils/currency.ts
src/hooks/useCurrency.ts
📚 Learning: 2025-07-24T10:57:15.315Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1000
File: src/components/og/ProfileCardOG.tsx:0-0
Timestamp: 2025-07-24T10:57:15.315Z
Learning: In `src/components/og/ProfileCardOG.tsx`, the scribble image should have an empty alt attribute (alt="") to prevent layout issues if the image fails to load. Since it's a decorative element positioned absolutely over the username text, showing alt text would interfere with the layout and username display.
Applied to files:
src/components/TransactionDetails/TransactionDetailsHeaderCard.tsx
📚 Learning: 2025-09-08T03:13:09.111Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:156-176
Timestamp: 2025-09-08T03:13:09.111Z
Learning: In the peanut-ui mobile app, the `/qr-pay` route is only accessed through the DirectSendQR component which always includes the qrCode parameter in the URL when redirecting users to the QR pay page.
Applied to files:
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-09-08T03:11:57.230Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:64-73
Timestamp: 2025-09-08T03:11:57.230Z
Learning: In QR payment flows, user-entered amounts should be cleared and overridden by API response amounts to ensure the authoritative payment amount from the API takes precedence over user input.
Applied to files:
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-10-23T09:38:27.670Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#469
File: src/app/request/pay/page.tsx:32-64
Timestamp: 2024-10-23T09:38:27.670Z
Learning: In `src/app/request/pay/page.tsx`, if `linkRes` is not OK in the `generateMetadata` function, the desired behavior is to use the standard title and preview image without throwing an error.
Applied to files:
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-10-08T20:13:42.967Z
Learnt from: Hugo0
PR: peanutprotocol/peanut-ui#422
File: src/components/Request/Pay/Pay.tsx:113-123
Timestamp: 2024-10-08T20:13:42.967Z
Learning: In the `PayRequestLink` component (`src/components/Request/Pay/Pay.tsx`), when resolving ENS names, handle errors by displaying an appropriate error message to the user if the ENS cannot be resolved.
Applied to files:
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-09-08T03:11:00.114Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:31-41
Timestamp: 2025-09-08T03:11:00.114Z
Learning: In QR payment flows, the `createPayment` API call for payment tracking can be non-awaited when called after successful transaction execution, as it's a non-blocking logging operation and doesn't affect the user's success state.
Applied to files:
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-10-22T18:11:36.864Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#469
File: src/app/request/pay/page.tsx:32-49
Timestamp: 2024-10-22T18:11:36.864Z
Learning: In `src/app/request/pay/page.tsx`, the `id` parameter is accessed via `searchParams.id` in the `generateMetadata` function.
Applied to files:
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-09-05T07:31:11.396Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1185
File: src/components/Claim/useClaimLink.tsx:14-0
Timestamp: 2025-09-05T07:31:11.396Z
Learning: In the peanut-ui codebase, `window.history.replaceState` is preferred over `router.replace` when immediate/synchronous URL parameter updates are required, as `router.replace` is asynchronous and doesn't guarantee instant URL changes that subsequent code can rely on. This pattern is used consistently across usePaymentInitiator.ts, Confirm.payment.view.tsx, and useClaimLink.tsx.
Applied to files:
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-09-11T17:46:12.507Z
Learnt from: Hugo0
PR: peanutprotocol/peanut-ui#1200
File: src/app/(mobile-ui)/recover-funds/page.tsx:9-9
Timestamp: 2025-09-11T17:46:12.507Z
Learning: Functions in Next.js that are not marked with "use server" and contain secrets are unsafe to import in client components, as they get bundled into the client JavaScript and can leak environment variables to the browser.
Applied to files:
src/app/(mobile-ui)/qr-pay/page.tsx
🧬 Code graph analysis (6)
src/app/actions/currency.ts (1)
src/services/manteca.ts (1)
mantecaApi
(89-145)
src/utils/currency.ts (1)
src/hooks/useCurrency.ts (1)
SYMBOLS_BY_CURRENCY_CODE
(4-19)
src/app/(mobile-ui)/qr-pay/page.tsx (9)
src/hooks/wallet/useWallet.ts (1)
useWallet
(21-123)src/services/manteca.ts (3)
QrPaymentLock
(45-61)QrPayment
(11-31)mantecaApi
(89-145)src/hooks/useTransactionDetailsDrawer.ts (1)
useTransactionDetailsDrawer
(3-22)src/components/Global/DirectSendQR/utils.ts (1)
isQRPay
(96-103)src/app/actions/currency.ts (1)
getCurrencyPrice
(7-62)src/utils/general.utils.ts (1)
isTxReverted
(1288-1291)src/constants/zerodev.consts.ts (1)
PEANUT_WALLET_TOKEN_DECIMALS
(19-19)src/components/TransactionDetails/TransactionDetailsDrawer.tsx (1)
TransactionDetailsDrawer
(19-63)src/components/Payment/PaymentInfoRow.tsx (1)
PaymentInfoRow
(7-81)
src/services/manteca.ts (3)
src/services/services.types.ts (1)
QrPaymentResponse
(323-331)src/utils/sentry.utils.ts (1)
fetchWithSentry
(26-104)src/constants/general.consts.ts (2)
PEANUT_API_URL
(43-47)PEANUT_API_KEY
(49-49)
src/components/TransactionDetails/TransactionCard.tsx (3)
src/utils/history.utils.ts (1)
getAvatarUrl
(5-19)src/utils/currency.ts (1)
getDisplayCurrencySymbol
(4-8)src/utils/general.utils.ts (1)
formatNumberForDisplay
(348-390)
src/components/TransactionDetails/TransactionDetailsReceipt.tsx (3)
src/utils/general.utils.ts (1)
formatAmount
(400-440)src/components/Payment/PaymentInfoRow.tsx (1)
PaymentInfoRow
(7-81)src/utils/currency.ts (1)
getDisplayCurrencySymbol
(4-8)
🔇 Additional comments (12)
src/app/actions/onramp.ts (1)
78-83
: Use buy leg correctly; ensure consistent rounding.
(Number(params.amount) * price.buy).toFixed(2)
is correct. Consider usingMath.max(0, ...)
guard if negative inputs are possible upstream.Do we ever pass formatted strings (with commas) into
params.amount
? If yes, sanitize beforeNumber(...)
.src/constants/general.consts.ts (1)
49-50
: Do not export PEANUT_API_KEY from shared modules — move it to a server-only module.Exporting the secret from src/constants/general.consts.ts risks bundling it into client code; move the key to a server-only file and consume it only in server actions / API routes.
Apply this diff:
- export const PEANUT_API_KEY = process.env.PEANUT_API_KEY!
Server-only example:
// src/server/config.ts import 'server-only' export const PEANUT_API_KEY = process.env.PEANUT_API_KEY!Update server callers to import from src/server/config.ts and confirm no client components import '@/constants/general.consts'.
src/utils/currency.ts (2)
12-12
: LGTM—centralized symbol resolver used in formatter.
1-1
: Move SYMBOLS_BY_CURRENCY_CODE out of hooks into a shared constants moduleutils importing from hooks is a layering smell and can pull client-only code into server bundles. Extract SYMBOLS_BY_CURRENCY_CODE into src/constants/currencySymbols.consts.ts (export the constant) and update imports in src/utils/currency.ts and src/hooks/useCurrency.ts to import from '@/constants/currencySymbols.consts'.
src/components/TransactionDetails/TransactionDetailsHeaderCard.tsx (2)
26-26
: Direction union updated with 'qr_payment'—good.
140-141
: Icon mapping aligns with outflow—good.src/constants/loadingStates.consts.ts (1)
12-12
: Add exhaustive UI mappings for new loading states
- Ensure state→copy/icon/spinner mappings include 'Fetching details' and 'Paying' so users don't see generic loaders.
- Gate analytics emitted per loading phase to avoid noisy/duplicate events.
- Consider centralizing UI metadata for LoadingStates (label, icon, aria-label) to keep consumers in sync.
Location: src/constants/loadingStates.consts.ts:12. Automated search returned no files — verification inconclusive; manually verify mappings and analytics across consumers.
src/components/TransactionDetails/transactionTransformer.ts (1)
296-301
: QR payment mapping verified — no changes required.Header (src/components/TransactionDetails/TransactionDetailsHeaderCard.tsx) and Receipt (src/components/TransactionDetails/TransactionDetailsReceipt.tsx) explicitly handle 'qr_payment'; transactionTransformer maps MANTECA_QR_PAYMENT → direction 'qr_payment' and transactionCardType 'pay' (src/components/TransactionDetails/transactionTransformer.ts); app/(mobile-ui)/qr-pay/page.tsx opens details with direction 'qr_payment'. No generic fallback observed.
src/app/actions/currency.ts (1)
35-36
: Confirm types from getExchangeRate; drop parseFloat if already numeric.Bridge route returns numeric rates per prior work. If
buy_rate
/sell_rate
are numbers, use them directly to avoid TS errors.Would you like me to scan the repo and surface the
ExchangeRateResponse
type?src/components/TransactionDetails/TransactionDetailsReceipt.tsx (1)
41-53
: Avatar prop + fallback looks good.Forwarded
avatarUrl
with fallback to drawer data is consistent with header usage.Also applies to: 298-300
src/components/TransactionDetails/TransactionCard.tsx (2)
30-31
: Additions for 'pay' type/icon look correct.Type union and icon mapping are coherent with existing patterns.
Also applies to: 240-243
68-71
: Centralized avatar URL resolution is a good improvement.
getAvatarUrl(transaction)
simplifies card and drawer cohesion.
src/app/actions/currency.ts
Outdated
case 'ARS': | ||
case 'BRL': | ||
case 'COP': | ||
case 'CRC': | ||
case 'PUSD': | ||
case 'GTQ': | ||
case 'PHP': | ||
case 'BOB': |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue: not blocking but this doesn't seem very DRY. Don't we have a MANTECA_CURRENCIES or smth somewhere?
src/app/actions/currency.ts
Outdated
sell = parseFloat(data.sell_rate) | ||
} | ||
break | ||
case 'ARS': |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: this switch case notation is a bit confusing to read. Maybe comment sep would be better
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's confusing about it?
} | ||
| { paymentLock: QrPaymentLock } | ||
|
||
export const mantecaApi = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
♻️ Duplicate comments (3)
src/components/TransactionDetails/TransactionDetailsReceipt.tsx (1)
445-463
: Inconsistent currency display format:U$D
vsUSD
.The code displays "Value in USD" but uses the format
{amount} USD
in the value. For consistency with other parts of the codebase, consider using either$ {amount}
or{amount} USD
throughout.Apply this diff for consistency:
- value={`${formatAmount(transaction.amount.toString())} USD`} + value={`$ ${formatAmount(transaction.amount.toString())}`}src/components/Global/TokenAmountInput/index.tsx (1)
159-163
: Currency symbol handling needs consistency.When
isInputUsd
is true in FIAT mode, you display 'USD' as the symbol, but the alternativeDisplaySymbol shows the currency symbol. Consider whether 'USD' or '$' should be used consistently across the application.src/app/(mobile-ui)/qr-pay/page.tsx (1)
1-24
: Server action import in client component will break bundling.The import of
getCurrencyPrice
from a server action file will fail at runtime since server actions use Next.js server-only APIs likeunstable_cache
.#!/bin/bash # Verify that getCurrencyPrice uses server-only APIs cat src/app/actions/currency.ts | head -20
🧹 Nitpick comments (3)
src/components/TransactionDetails/TransactionDetailsReceipt.tsx (1)
464-464
: Address the TODO about snake_case usage.The TODO comment correctly identifies that
exchange_rate
uses snake_case, which is inconsistent with the TypeScript/JavaScript camelCase convention used elsewhere in the codebase.Would you like me to help refactor the codebase to use camelCase consistently for the
exchange_rate
field and update all related references?src/app/(mobile-ui)/qr-pay/page.tsx (2)
162-167
: Unnecessary variable reference after check.Line 162 references
finalPaymentLock
alone without any operation, which appears to be leftover debug code.Apply this diff to remove the unnecessary line:
if (finalPaymentLock.code === '') { - finalPaymentLock setErrorMessage('Could not fetch qr payment details')
231-234
: Dev error case needs proper handling.Returning
null
silently whenisSuccess && !qrPayment
could leave users with a blank screen. Add error tracking.Apply this diff to handle the edge case:
if (isSuccess && !qrPayment) { - // This should never happen, if this happens there is dev error - return null + captureException(new Error('Success state without qrPayment data')) + return <ErrorAlert description="An unexpected error occurred. Please contact support." /> }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
src/app/(mobile-ui)/qr-pay/page.tsx
(1 hunks)src/app/[...recipient]/client.tsx
(1 hunks)src/components/Global/DirectSendQR/utils.ts
(2 hunks)src/components/Global/TokenAmountInput/index.tsx
(4 hunks)src/components/Request/views/ReqFulfillBankFlowManager.tsx
(1 hunks)src/components/TransactionDetails/TransactionDetailsReceipt.tsx
(7 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- src/components/Request/views/ReqFulfillBankFlowManager.tsx
- src/app/[...recipient]/client.tsx
🧰 Additional context used
🧠 Learnings (21)
📓 Common learnings
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:156-176
Timestamp: 2025-09-08T03:13:09.111Z
Learning: In the peanut-ui mobile app, the `/qr-pay` route is only accessed through the DirectSendQR component which always includes the qrCode parameter in the URL when redirecting users to the QR pay page after scanning MERCADO_PAGO or PIX QR codes.
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:156-176
Timestamp: 2025-09-08T03:13:09.111Z
Learning: In the peanut-ui mobile app, the `/qr-pay` route is only accessed through the DirectSendQR component which always includes the qrCode parameter in the URL when redirecting users to the QR pay page.
📚 Learning: 2025-09-08T03:13:09.111Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:156-176
Timestamp: 2025-09-08T03:13:09.111Z
Learning: In the peanut-ui mobile app, the `/qr-pay` route is only accessed through the DirectSendQR component which always includes the qrCode parameter in the URL when redirecting users to the QR pay page after scanning MERCADO_PAGO or PIX QR codes.
Applied to files:
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-09-08T03:13:09.111Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:156-176
Timestamp: 2025-09-08T03:13:09.111Z
Learning: In the peanut-ui mobile app, the `/qr-pay` route is only accessed through the DirectSendQR component which always includes the qrCode parameter in the URL when redirecting users to the QR pay page.
Applied to files:
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-08-26T15:25:53.328Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1132
File: src/app/[...recipient]/client.tsx:394-397
Timestamp: 2025-08-26T15:25:53.328Z
Learning: In `src/components/Common/ActionListDaimoPayButton.tsx`, the `handleCompleteDaimoPayment` function should not display error messages to users when DB update fails because the Daimo payment itself has succeeded - showing errors would be confusing since the payment was successful.
Applied to files:
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-09-08T03:11:57.230Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:64-73
Timestamp: 2025-09-08T03:11:57.230Z
Learning: In QR payment flows, user-entered amounts should be cleared and overridden by API response amounts to ensure the authoritative payment amount from the API takes precedence over user input.
Applied to files:
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-10-08T20:13:42.967Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#413
File: src/context/tokenSelector.context.tsx:118-123
Timestamp: 2024-10-08T20:13:42.967Z
Learning: In the `TokenContextProvider` component within `src/context/tokenSelector.context.tsx`, in the TypeScript React application, when data changes and before calling `fetchAndSetTokenPrice`, it is necessary to reset `selectedTokenData`, `selectedTokenPrice`, `selectedTokenDecimals`, and `inputDenomination` to discard stale data.
Applied to files:
src/app/(mobile-ui)/qr-pay/page.tsx
src/components/Global/TokenAmountInput/index.tsx
📚 Learning: 2024-10-23T09:38:27.670Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#469
File: src/app/request/pay/page.tsx:32-64
Timestamp: 2024-10-23T09:38:27.670Z
Learning: In `src/app/request/pay/page.tsx`, if `linkRes` is not OK in the `generateMetadata` function, the desired behavior is to use the standard title and preview image without throwing an error.
Applied to files:
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-10-08T20:13:42.967Z
Learnt from: Hugo0
PR: peanutprotocol/peanut-ui#422
File: src/components/Request/Pay/Pay.tsx:113-123
Timestamp: 2024-10-08T20:13:42.967Z
Learning: In the `PayRequestLink` component (`src/components/Request/Pay/Pay.tsx`), when resolving ENS names, handle errors by displaying an appropriate error message to the user if the ENS cannot be resolved.
Applied to files:
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-09-08T03:11:00.114Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:31-41
Timestamp: 2025-09-08T03:11:00.114Z
Learning: In QR payment flows, the `createPayment` API call for payment tracking can be non-awaited when called after successful transaction execution, as it's a non-blocking logging operation and doesn't affect the user's success state.
Applied to files:
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-10-22T18:11:36.864Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#469
File: src/app/request/pay/page.tsx:32-49
Timestamp: 2024-10-22T18:11:36.864Z
Learning: In `src/app/request/pay/page.tsx`, the `id` parameter is accessed via `searchParams.id` in the `generateMetadata` function.
Applied to files:
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-09-05T07:31:11.396Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1185
File: src/components/Claim/useClaimLink.tsx:14-0
Timestamp: 2025-09-05T07:31:11.396Z
Learning: In the peanut-ui codebase, `window.history.replaceState` is preferred over `router.replace` when immediate/synchronous URL parameter updates are required, as `router.replace` is asynchronous and doesn't guarantee instant URL changes that subsequent code can rely on. This pattern is used consistently across usePaymentInitiator.ts, Confirm.payment.view.tsx, and useClaimLink.tsx.
Applied to files:
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-12-11T10:13:22.806Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#564
File: src/components/Request/Pay/Views/Initial.view.tsx:430-430
Timestamp: 2024-12-11T10:13:22.806Z
Learning: In the React TypeScript file `src/components/Request/Pay/Views/Initial.view.tsx`, when reviewing the `InitialView` component, do not flag potential issues with using non-null assertion `!` on the `slippagePercentage` variable, as handling undefined values in this context is considered out of scope.
Applied to files:
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-10-07T13:42:00.443Z
Learnt from: Hugo0
PR: peanutprotocol/peanut-ui#422
File: src/components/Request/Pay/Pay.tsx:103-111
Timestamp: 2024-10-07T13:42:00.443Z
Learning: When the token price cannot be fetched in `src/components/Request/Pay/Pay.tsx` within the `PayRequestLink` component, set `tokenPriceData.price` to 0 to ensure the UI remains functional. Since Squid uses their own price engine for x-chain fulfillment transactions, this approach will not affect the transaction computation.
Applied to files:
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-09-11T17:46:12.507Z
Learnt from: Hugo0
PR: peanutprotocol/peanut-ui#1200
File: src/app/(mobile-ui)/recover-funds/page.tsx:9-9
Timestamp: 2025-09-11T17:46:12.507Z
Learning: Functions in Next.js that are not marked with "use server" and contain secrets are unsafe to import in client components, as they get bundled into the client JavaScript and can leak environment variables to the browser.
Applied to files:
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-08-12T17:47:28.362Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1089
File: src/app/api/bridge/exchange-rate/route.ts:4-19
Timestamp: 2025-08-12T17:47:28.362Z
Learning: In the Bridge exchange rate API route (src/app/api/bridge/exchange-rate/route.ts), the ExchangeRateResponse interface uses numeric types for rates because the route converts string values from the Bridge API to floats using parseFloat() before returning the response.
Applied to files:
src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-10-29T12:19:41.968Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#495
File: src/components/Global/TokenAmountInput/index.tsx:23-30
Timestamp: 2024-10-29T12:19:41.968Z
Learning: In the `TokenAmountInput` component (`src/components/Global/TokenAmountInput/index.tsx`), when the 'Max' button is clicked, we intentionally set the input denomination to 'TOKEN' because we are setting the value as token.
Applied to files:
src/components/Global/TokenAmountInput/index.tsx
📚 Learning: 2025-08-22T07:25:59.304Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1104
File: src/components/Payment/PaymentForm/index.tsx:596-600
Timestamp: 2025-08-22T07:25:59.304Z
Learning: The `TokenAmountInput` component in `src/components/Global/TokenAmountInput/` always returns decimal strings (e.g., "1,234.56"), not base units. When passing these values to external APIs like Daimo's `toUnits` prop, simply stripping commas with `.replace(/,/g, '')` is sufficient.
Applied to files:
src/components/Global/TokenAmountInput/index.tsx
📚 Learning: 2024-10-29T12:20:47.207Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#495
File: src/components/Create/Link/Input.view.tsx:244-248
Timestamp: 2024-10-29T12:20:47.207Z
Learning: In the `TokenAmountInput` component within `src/components/Global/TokenAmountInput/index.tsx`, when `balance` is undefined, the `maxValue` prop should be set to an empty string `''`.
Applied to files:
src/components/Global/TokenAmountInput/index.tsx
📚 Learning: 2024-10-07T15:25:45.170Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#422
File: src/components/Request/Pay/Views/Initial.view.tsx:76-78
Timestamp: 2024-10-07T15:25:45.170Z
Learning: In `src/components/Request/Pay/Views/Initial.view.tsx`, both `txFee` and `utils.formatTokenAmount(...)` return strings, ensuring that `calculatedFee` consistently returns a string without the need for additional type conversion.
Applied to files:
src/components/Global/TokenAmountInput/index.tsx
📚 Learning: 2024-10-07T15:28:25.280Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#422
File: src/components/Request/Pay/Views/Initial.view.tsx:76-78
Timestamp: 2024-10-07T15:28:25.280Z
Learning: In `src/components/Request/Pay/Views/Initial.view.tsx`, both `txFee` and `utils.formatTokenAmount(estimatedGasCost, 3)` return strings, ensuring consistent return types for `calculatedFee`.
Applied to files:
src/components/Global/TokenAmountInput/index.tsx
📚 Learning: 2024-12-02T17:21:45.515Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#551
File: src/context/walletContext/walletContext.tsx:87-88
Timestamp: 2024-12-02T17:21:45.515Z
Learning: When converting `totalBalance` (in USD) to a `BigInt` balance in `src/context/walletContext/walletContext.tsx`, multiplying by `1e6` is intentional to maintain compatibility with USDC's 6 decimal places. The application displays only 2 decimal places, so this level of precision is sufficient.
Applied to files:
src/components/Global/TokenAmountInput/index.tsx
🧬 Code graph analysis (3)
src/app/(mobile-ui)/qr-pay/page.tsx (10)
src/hooks/wallet/useWallet.ts (1)
useWallet
(21-123)src/services/manteca.ts (3)
QrPaymentLock
(45-61)QrPayment
(11-31)mantecaApi
(89-145)src/hooks/useTransactionDetailsDrawer.ts (1)
useTransactionDetailsDrawer
(3-22)src/components/Global/DirectSendQR/utils.ts (1)
isPaymentProcessorQR
(100-107)src/app/actions/currency.ts (1)
getCurrencyPrice
(7-62)src/utils/general.utils.ts (1)
isTxReverted
(1303-1306)src/constants/zerodev.consts.ts (1)
PEANUT_WALLET_TOKEN_DECIMALS
(19-19)src/components/Global/PeanutLoading/index.tsx (1)
PeanutLoading
(4-19)src/components/TransactionDetails/TransactionDetailsDrawer.tsx (1)
TransactionDetailsDrawer
(19-63)src/components/Payment/PaymentInfoRow.tsx (1)
PaymentInfoRow
(7-81)
src/components/Global/TokenAmountInput/index.tsx (2)
src/utils/general.utils.ts (1)
formatAmountWithoutComma
(476-482)src/components/Global/Icons/Icon.tsx (1)
Icon
(196-205)
src/components/TransactionDetails/TransactionDetailsReceipt.tsx (3)
src/utils/general.utils.ts (1)
formatAmount
(400-440)src/components/Payment/PaymentInfoRow.tsx (1)
PaymentInfoRow
(7-81)src/utils/currency.ts (1)
getDisplayCurrencySymbol
(4-8)
🔇 Additional comments (9)
src/components/Global/DirectSendQR/utils.ts (2)
53-53
: Pattern broadening looks good for PIX QR detection.The regex pattern update from "00020126" to "000201" provides broader compatibility with PIX QR code variations while maintaining the required structural validation.
55-107
: Clean abstraction for payment processor QR codes.The addition of
PAYMENT_PROCESSOR_REGEXES
mapping andisPaymentProcessorQR
helper function provides a clean separation of concerns, making it easy to identify payment processor QRs independently from the general recognition logic.src/components/TransactionDetails/TransactionDetailsReceipt.tsx (2)
120-124
: Good extension for QR payment exchange rate visibility.Adding
qr_payment
to the exchange rate visibility condition is consistent with the new payment flow requirements.
754-754
: Nice touch with the sponsored fee message!The change from "$ 0" to "Sponsored by Peanut!" provides better user communication about fee sponsorship.
src/components/Global/TokenAmountInput/index.tsx (2)
128-130
: Conversion UI logic is properly gated.The
showConversion
memoization correctly handles the visibility logic for the conversion UI elements.
203-269
: UI restructuring improves mobile UX.The vertical layout with larger text (text-6xl) and centered alignment provides better touch targets and readability on mobile devices. The conversion toggle positioning is well-placed.
src/app/(mobile-ui)/qr-pay/page.tsx (3)
70-73
: Good defensive check for QR code validity.The validation using
isPaymentProcessorQR
provides proper error handling for invalid QR codes.
196-204
: Payment amount validation implemented correctly.Good implementation of both maximum payment limit ($200) and balance checks with clear error messages.
104-108
: Extract the sell price correctly from currency price object.The
getCurrencyPrice
returns{ buy: number; sell: number }
, so accessing.sell
is needed.The code should properly extract the sell price:
if (paymentLock.code === '') { - price = (await getCurrencyPrice(currencyCode)).sell + const priceData = await getCurrencyPrice(currencyCode) + price = priceData.sell } else {Likely an incorrect or invalid review comment.
const MANTECA_DEPOSIT_ADDRESS = '0x959e088a09f61aB01cb83b0eBCc74b2CF6d62053' | ||
const MAX_QR_PAYMENT_AMOUNT = '200' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hardcoded deposit address needs configuration management.
The MANTECA_DEPOSIT_ADDRESS
is hardcoded. This should be managed through environment variables for different environments (dev/staging/prod).
Consider moving to environment configuration:
-const MANTECA_DEPOSIT_ADDRESS = '0x959e088a09f61aB01cb83b0eBCc74b2CF6d62053'
+const MANTECA_DEPOSIT_ADDRESS = process.env.NEXT_PUBLIC_MANTECA_DEPOSIT_ADDRESS || '0x959e088a09f61aB01cb83b0eBCc74b2CF6d62053'
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const MANTECA_DEPOSIT_ADDRESS = '0x959e088a09f61aB01cb83b0eBCc74b2CF6d62053' | |
const MAX_QR_PAYMENT_AMOUNT = '200' | |
const MANTECA_DEPOSIT_ADDRESS = process.env.NEXT_PUBLIC_MANTECA_DEPOSIT_ADDRESS || '0x959e088a09f61aB01cb83b0eBCc74b2CF6d62053' | |
const MAX_QR_PAYMENT_AMOUNT = '200' |
🤖 Prompt for AI Agents
In src/app/(mobile-ui)/qr-pay/page.tsx around lines 29-30, the
MANTECA_DEPOSIT_ADDRESS is hardcoded; replace it with a configurable environment
variable (e.g. use process.env.NEXT_PUBLIC_MANTECA_DEPOSIT_ADDRESS for
client-side code or process.env.MANTECA_DEPOSIT_ADDRESS for server-only), add a
runtime check that validates the env value (log or throw a clear error if
missing/invalid), add the new variable to your .env/.env.local and to
environment configuration for staging/production, and update any
README/deployment docs to instruct setting this env var.
originalUserRole: EHistoryUserRole.SENDER, | ||
avatarUrl: methodIcon, | ||
receipt: { | ||
exchange_rate: currency.price.toString(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Type mismatch: price is an object, not a number.
Based on the getCurrencyPrice
return type { buy: number; sell: number }
, currency.price
is an object, but you're converting it to string. This should use currency.price.sell
or store the sell price separately.
Apply this diff to fix the type issue:
- exchange_rate: currency.price.toString(),
+ exchange_rate: currency.price.sell.toString(),
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
exchange_rate: currency.price.toString(), | |
exchange_rate: currency.price.sell.toString(), |
🤖 Prompt for AI Agents
In src/app/(mobile-ui)/qr-pay/page.tsx around line 295, the code sets
exchange_rate: currency.price.toString() but currency.price is an object ({ buy:
number; sell: number }), causing a type mismatch; replace usage with the numeric
sell price (e.g., exchange_rate: currency.price.sell.toString() or better:
exchange_rate: String(currency.price.sell)) or alternatively store
currency.price.sell in a separate variable and use that where a string is
required, ensuring the value is the sell number converted to string.
<Card className="space-y-0 px-4"> | ||
<PaymentInfoRow | ||
label="Exchange Rate" | ||
value={`1 USD = ${currency.price} ${currency.code.toUpperCase()}`} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exchange rate display uses price incorrectly.
The currency.price
is used directly but should access the specific buy/sell value based on the context.
Apply this diff:
- value={`1 USD = ${currency.price} ${currency.code.toUpperCase()}`}
+ value={`1 USD = ${currency.price.sell || currency.price} ${currency.code.toUpperCase()}`}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
value={`1 USD = ${currency.price} ${currency.code.toUpperCase()}`} | |
value={`1 USD = ${currency.price.sell || currency.price} ${currency.code.toUpperCase()}`} |
🤖 Prompt for AI Agents
In src/app/(mobile-ui)/qr-pay/page.tsx around line 353, the code uses
currency.price directly for the exchange-rate display; update it to reference
the specific buy/sell rate instead (e.g., currency.buyPrice or
currency.sellPrice) based on the current transaction context (use the existing
flag/prop like isBuying/isSelling or transactionType to pick the correct field),
and format the number for display (e.g., toFixed(2)) before interpolating into
the string so it reads "1 USD = <buy|sell> <CURRENCY_CODE>".
/** | ||
* Returns true if the given string is a payment processor QR code. | ||
* For example, Mercado Pago, Pix, etc. | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thx
No description provided.