[wip] feat: send flow isolated from payment from #1568
[wip] feat: send flow isolated from payment from #1568kushagrasarathe wants to merge 4 commits intorefactor/payment-flow-setupfrom
Conversation
…InputView, SendSuccessView, and action list for payment options
…with username resolution
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughThis PR introduces a new Send flow architecture while removing the DaimoPay button integration. Changes include: creating SendFlowContext-based state management with supporting components (SendPage, SendPageWrapper, SendInputView, SendSuccessView), removing ActionListDaimoPayButton and FeeDescription components, disabling the 'exchange-or-wallet' payment method, adding wallet balance formatting utilities, and adjusting UI spacing across layout containers. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes
Possibly related PRs
Suggested labels
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 2 inconclusive)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (8)
src/features/payments/shared/components/SendActionList.tsx (3)
29-30: Remove outdated comment.The comment about filtering out
exchange-or-walletis misleading since that entry has already been removed fromACTION_METHODSinsrc/constants/actionlist.consts.ts.Apply this diff:
const { isUserMantecaKycApproved } = useKycStatus() - // filter out exchange-or-wallet since daimo is being killed const availableMethods = ACTION_METHODS
40-52: Consider simplifying the switch statement.Since all cases execute the same logic, the switch can be replaced with a simple conditional for improved readability.
Apply this diff:
const handleMethodClick = (method: PaymentMethod) => { - // for all methods, save current url and redirect to setup with add-money as final destination - // verification will be handled in the add-money flow after login - switch (method.id) { - case 'bank': - case 'mercadopago': - case 'pix': - saveRedirectUrl() - const redirectUri = encodeURIComponent('/add-money') - router.push(`/setup?redirect_uri=${redirectUri}`) - break - } + // save current url and redirect to setup with add-money as final destination + // verification will be handled in the add-money flow after login + saveRedirectUrl() + const redirectUri = encodeURIComponent('/add-money') + router.push(`/setup?redirect_uri=${redirectUri}`) }
66-69: Extract hardcoded verification methods to constant.The list of methods requiring verification is hardcoded. Consider extracting to a constant for maintainability.
Add a constant at the top of the file:
const METHODS_REQUIRING_VERIFICATION = ['mercadopago', 'pix', 'bank'] as constThen apply this diff:
{sortedMethods.map((method) => { // check if method requires verification (for badge display only) const methodRequiresVerification = - ['mercadopago', 'pix', 'bank'].includes(method.id) && !isUserMantecaKycApproved + METHODS_REQUIRING_VERIFICATION.includes(method.id) && !isUserMantecaKycApprovedsrc/features/payments/flows/send/SendPageWrapper.tsx (1)
33-38: Consider validating the address format before casting.The
walletAccount.identifieris cast directly toAddresswithout validation. If the identifier is malformed, this could cause issues downstream when used with viem functions.Consider using viem's
isAddressutility for validation:+import { type Address, isAddress } from 'viem' ... - return { - username: user.username, - address: walletAccount.identifier as Address, - userId: user.userId, - fullName: user.fullName, - } + const address = walletAccount.identifier + if (!isAddress(address)) return null + + return { + username: user.username, + address, + userId: user.userId, + fullName: user.fullName, + }src/features/payments/flows/send/views/SendInputView.tsx (1)
44-51: History length check may not work as expected.
window.history.length > 1doesn't reliably indicate whetherrouter.back()will navigate within your app. The history length includes the full browser session history, not just your app's navigation stack. A user could arrive directly via a link with history length > 1 from other sites.Consider using a simpler fallback approach or tracking navigation state in context:
const handleGoBack = () => { - if (window.history.length > 1) { - router.back() - } else { - router.push('/') - } + router.back() }Or if home fallback is critical, consider using
document.referrercheck or a navigation context.src/features/payments/flows/send/views/SendSuccessView.tsx (1)
34-51: Consider extracting duplicate reset-and-navigate logic.The same
resetSendFlow()+router.push('/home')pattern appears in bothhandleDoneand the NavHeader'sonPrevcallback.const handleDone = () => { resetSendFlow() router.push('/home') } return ( <div className="flex min-h-[inherit] flex-col justify-between gap-8"> <SoundPlayer sound="success" /> <div className="md:hidden"> <NavHeader icon="cancel" - onPrev={() => { - resetSendFlow() - router.push('/home') - }} + onPrev={handleDone} /> </div>src/app/send/[...username]/page.tsx (1)
13-18: Empty username results in confusing error message.When
usernameSegments[0]is undefined,usernamebecomes an empty string. This is passed toSendPageWrapper, which will display "user @ not found or has no peanut wallet" - note the awkward "@ " with no username.Consider handling the missing username case explicitly:
const usernameSegments = params.username ?? [] const username = usernameSegments[0] ? decodeURIComponent(usernameSegments[0]) : '' + if (!username) { + return ( + <PageContainer> + <div className="flex w-full flex-col gap-4"> + <ErrorAlert description="No recipient specified" /> + </div> + </PageContainer> + ) + } + return ( <PageContainer> <SendPageWrapper username={username} /> </PageContainer> )This would require importing
ErrorAlertin this file.src/features/payments/shared/components/SendWithPeanutCta.tsx (1)
53-73: Consider extracting duplicate logo rendering.The same logo images pattern is repeated for both logged-in and logged-out states.
+ const PeanutLogos = () => ( + <div className="flex items-center gap-1"> + <Image src={PEANUTMAN_LOGO} alt="Peanut Logo" className="size-5" /> + <Image src={PEANUT_LOGO_BLACK} alt="Peanut Logo" /> + </div> + ) + return ( <Button ...> {!title ? ( !isLoggedIn ? ( <div className="flex items-center gap-1"> <div>Continue with </div> - <div className="flex items-center gap-1"> - <Image src={PEANUTMAN_LOGO} alt="Peanut Logo" className="size-5" /> - <Image src={PEANUT_LOGO_BLACK} alt="Peanut Logo" /> - </div> + <PeanutLogos /> </div> ) : ( <div className="flex items-center gap-1"> <div>Send with </div> - <div className="flex items-center gap-1"> - <Image src={PEANUTMAN_LOGO} alt="Peanut Logo" className="size-5" /> - <Image src={PEANUT_LOGO_BLACK} alt="Peanut Logo" /> - </div> + <PeanutLogos /> </div> ) ) : ( title )} </Button> )
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (18)
src/app/(mobile-ui)/layout.tsx(1 hunks)src/app/[...recipient]/payment-layout-wrapper.tsx(1 hunks)src/app/send/[...username]/page.tsx(2 hunks)src/components/Common/ActionList.tsx(0 hunks)src/components/Common/ActionListDaimoPayButton.tsx(0 hunks)src/components/Global/FeeDescription/index.tsx(0 hunks)src/constants/actionlist.consts.ts(1 hunks)src/features/payments/flows/send/SendFlowContext.tsx(1 hunks)src/features/payments/flows/send/SendPage.tsx(1 hunks)src/features/payments/flows/send/SendPageWrapper.tsx(1 hunks)src/features/payments/flows/send/send.types.ts(1 hunks)src/features/payments/flows/send/useSendFlow.ts(1 hunks)src/features/payments/flows/send/views/SendInputView.tsx(1 hunks)src/features/payments/flows/send/views/SendSuccessView.tsx(1 hunks)src/features/payments/shared/components/SendActionList.tsx(1 hunks)src/features/payments/shared/components/SendWithPeanutCta.tsx(1 hunks)src/hooks/useUserByUsername.ts(1 hunks)src/hooks/wallet/useWallet.ts(2 hunks)
💤 Files with no reviewable changes (3)
- src/components/Common/ActionList.tsx
- src/components/Common/ActionListDaimoPayButton.tsx
- src/components/Global/FeeDescription/index.tsx
🧰 Additional context used
🧠 Learnings (32)
📓 Common learnings
Learnt from: Zishan-7
Repo: peanutprotocol/peanut-ui PR: 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.
Learnt from: Zishan-7
Repo: peanutprotocol/peanut-ui PR: 1104
File: src/components/Payment/PaymentForm/index.tsx:522-545
Timestamp: 2025-08-22T07:28:32.281Z
Learning: In `src/components/Payment/PaymentForm/index.tsx`, the `handleCompleteDaimoPayment` function is only for updating payment status in the backend after a successful Daimo payment. Payment success/failure is handled by Daimo itself, so try/catch error handling and error display are not needed for backend sync failures - users shouldn't see errors if payment succeeded but database update failed.
📚 Learning: 2024-10-25T11:33:46.776Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 484
File: src/components/Cashout/Components/Initial.view.tsx:273-274
Timestamp: 2024-10-25T11:33:46.776Z
Learning: In the `InitialCashoutView` component (`src/components/Cashout/Components/Initial.view.tsx`), linked bank accounts should not generate error states, and the `ValidatedInput` component will clear any error messages if needed. Therefore, it's unnecessary to manually clear the error state when selecting or clearing linked bank accounts.
Applied to files:
src/features/payments/flows/send/views/SendInputView.tsxsrc/features/payments/shared/components/SendActionList.tsx
📚 Learning: 2024-12-11T10:13:22.806Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 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/features/payments/flows/send/views/SendInputView.tsx
📚 Learning: 2024-10-07T15:25:45.170Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 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/features/payments/flows/send/views/SendInputView.tsxsrc/hooks/wallet/useWallet.tssrc/features/payments/flows/send/views/SendSuccessView.tsx
📚 Learning: 2024-10-07T15:28:25.280Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 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/features/payments/flows/send/views/SendInputView.tsxsrc/hooks/wallet/useWallet.tssrc/features/payments/flows/send/views/SendSuccessView.tsx
📚 Learning: 2024-10-08T20:13:42.967Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 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/features/payments/flows/send/views/SendInputView.tsx
📚 Learning: 2025-09-08T03:13:09.111Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 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/features/payments/flows/send/views/SendInputView.tsx
📚 Learning: 2025-05-22T15:38:48.586Z
Learnt from: kushagrasarathe
Repo: peanutprotocol/peanut-ui PR: 869
File: src/app/(mobile-ui)/withdraw/page.tsx:82-88
Timestamp: 2025-05-22T15:38:48.586Z
Learning: The country-specific withdrawal route exists at src/app/(mobile-ui)/withdraw/[...country]/page.tsx and renders the AddWithdrawCountriesList component with flow="withdraw".
Applied to files:
src/features/payments/flows/send/views/SendInputView.tsxsrc/features/payments/flows/send/SendPage.tsxsrc/app/send/[...username]/page.tsxsrc/features/payments/shared/components/SendActionList.tsx
📚 Learning: 2025-10-29T11:27:59.248Z
Learnt from: Zishan-7
Repo: peanutprotocol/peanut-ui PR: 1368
File: src/components/Common/ActionList.tsx:109-111
Timestamp: 2025-10-29T11:27:59.248Z
Learning: In `src/components/Common/ActionList.tsx`, the `balance` from `useWallet()` hook is always in USDC (as a formatted string), making it directly comparable to USD amounts without conversion. The comparison `Number(balance) >= amountInUsd` is intentional and correct.
Applied to files:
src/hooks/wallet/useWallet.tssrc/features/payments/shared/components/SendActionList.tsx
📚 Learning: 2024-12-02T17:21:45.515Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 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/hooks/wallet/useWallet.ts
📚 Learning: 2025-07-07T19:55:14.380Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 956
File: src/app/actions/tokens.ts:220-227
Timestamp: 2025-07-07T19:55:14.380Z
Learning: In the Mobula API integration within `src/app/actions/tokens.ts`, the `asset.asset.blockchains` array and `asset.contracts_balances` array are synchronized, meaning for every blockchain in the blockchains array, there will be a corresponding entry in the contracts_balances array with matching address. This makes the non-null assertion operator safe to use when accessing `contractInfo!.decimals` in the `fetchWalletBalances` function.
Applied to files:
src/hooks/wallet/useWallet.tssrc/constants/actionlist.consts.ts
📚 Learning: 2025-07-07T20:22:11.092Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 958
File: src/app/actions/tokens.ts:266-266
Timestamp: 2025-07-07T20:22:11.092Z
Learning: In `src/app/actions/tokens.ts`, within the `fetchWalletBalances` function, using the non-null assertion operator `!` on `process.env.MOBULA_API_KEY!` is intentional and correct, and should not be flagged for replacement with explicit validation.
Applied to files:
src/hooks/wallet/useWallet.tssrc/constants/actionlist.consts.ts
📚 Learning: 2024-12-02T17:19:18.532Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 551
File: src/components/Request/Create/Views/Initial.view.tsx:151-156
Timestamp: 2024-12-02T17:19:18.532Z
Learning: In the `InitialView` component at `src/components/Request/Create/Views/Initial.view.tsx`, when setting the default chain and token in the `useEffect` triggered by `isPeanutWallet`, it's acceptable to omit the setters from the dependency array and not include additional error handling for invalid defaults.
Applied to files:
src/hooks/wallet/useWallet.tssrc/features/payments/shared/components/SendWithPeanutCta.tsx
📚 Learning: 2025-01-16T13:14:40.363Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 631
File: src/components/Create/Create.tsx:108-112
Timestamp: 2025-01-16T13:14:40.363Z
Learning: In the Peanut UI codebase, the `resetTokenContextProvider` function from `tokenSelectorContext` is a stable function reference that doesn't change, so it doesn't need to be included in useEffect dependencies.
Applied to files:
src/hooks/wallet/useWallet.ts
📚 Learning: 2025-06-18T19:56:55.443Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 919
File: src/components/Withdraw/views/Initial.withdraw.view.tsx:87-87
Timestamp: 2025-06-18T19:56:55.443Z
Learning: In withdraw flows for Peanut Wallet, the PeanutActionDetailsCard should always display "USDC" as the token symbol because it shows the amount being withdrawn from the Peanut Wallet (which holds USDC), regardless of the destination token/chain selected by the user. The TokenSelector is used for choosing the withdrawal destination, not the source display.
Applied to files:
src/hooks/wallet/useWallet.ts
📚 Learning: 2025-11-13T18:17:06.391Z
Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 0
File: :0-0
Timestamp: 2025-11-13T18:17:06.391Z
Learning: In peanutprotocol/peanut-ui, the Service Worker cannot cache POST requests (including blockchain RPC calls like balanceOf) because the browser Cache Storage API only accepts GET requests as cache keys per the W3C spec. Alternative solutions documented in sw.ts include server-side proxy (POST→GET conversion) or custom IndexedDB caching. Current approach uses TanStack Query in-memory cache (30s staleTime) for balance queries.
Applied to files:
src/hooks/wallet/useWallet.ts
📚 Learning: 2025-09-18T09:30:42.901Z
Learnt from: Zishan-7
Repo: peanutprotocol/peanut-ui PR: 1230
File: src/app/(mobile-ui)/withdraw/page.tsx:92-97
Timestamp: 2025-09-18T09:30:42.901Z
Learning: In src/app/(mobile-ui)/withdraw/page.tsx, the useEffect that calls setShowAllWithdrawMethods(true) when amountFromContext exists is intentionally designed to run only on component mount (empty dependency array), not when amountFromContext changes. This is the correct behavior for the withdraw flow where showing all methods should only happen on initial load when an amount is already present.
Applied to files:
src/features/payments/flows/send/useSendFlow.tssrc/features/payments/flows/send/views/SendSuccessView.tsxsrc/features/payments/flows/send/SendFlowContext.tsxsrc/features/payments/shared/components/SendActionList.tsx
📚 Learning: 2025-08-26T17:38:37.055Z
Learnt from: Zishan-7
Repo: peanutprotocol/peanut-ui PR: 1132
File: src/components/Common/ActionList.tsx:153-156
Timestamp: 2025-08-26T17:38:37.055Z
Learning: In ActionList.tsx, when there are circular dependency concerns with ACTION_METHODS being imported by other components, the preferred solution is to move ACTION_METHODS to a separate constants file (like src/constants/actionlist.consts.ts) rather than using prop drilling. This centralizes constants management and creates a cleaner dependency graph.
Applied to files:
src/constants/actionlist.consts.tssrc/features/payments/shared/components/SendActionList.tsx
📚 Learning: 2025-08-14T09:20:37.231Z
Learnt from: Zishan-7
Repo: peanutprotocol/peanut-ui PR: 1089
File: src/components/LandingPage/hero.tsx:0-0
Timestamp: 2025-08-14T09:20:37.231Z
Learning: In the hero component at src/components/LandingPage/hero.tsx, the height was intentionally reduced from min-h-[100vh] to h-[90vh] to improve scrollability discoverability - so users can see there's more content below to scroll. The overflow-y-hidden is acceptable when other elements are adjusted to prevent clipping.
Applied to files:
src/app/(mobile-ui)/layout.tsx
📚 Learning: 2025-07-24T13:26:10.290Z
Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 1014
File: src/components/Claim/Link/Initial.view.tsx:413-413
Timestamp: 2025-07-24T13:26:10.290Z
Learning: In the peanut-ui repository, the change from `${SQUID_API_URL}/route` to `${SQUID_API_URL}/v2/route` in src/components/Claim/Link/Initial.view.tsx was a typo fix, not an API migration, as the codebase was already using Squid API v2.
Applied to files:
src/app/(mobile-ui)/layout.tsxsrc/features/payments/shared/components/SendWithPeanutCta.tsx
📚 Learning: 2024-10-22T18:10:56.955Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 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/features/payments/flows/send/send.types.tssrc/app/send/[...username]/page.tsx
📚 Learning: 2025-05-23T19:26:58.220Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 873
File: src/components/Withdraw/views/Initial.withdraw.view.tsx:95-95
Timestamp: 2025-05-23T19:26:58.220Z
Learning: The GeneralRecipientInput component supports username validation and resolution through the validateAndResolveRecipient function in src/lib/validation/recipient.ts. The function automatically detects usernames (inputs that don't contain '.' for ENS and don't start with '0x' for addresses), validates them via API HEAD request, fetches user data, and resolves them to Ethereum addresses from the user's PEANUT_WALLET account.
Applied to files:
src/features/payments/flows/send/send.types.tssrc/features/payments/flows/send/SendPageWrapper.tsxsrc/app/send/[...username]/page.tsx
📚 Learning: 2025-04-30T21:31:27.790Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 827
File: src/components/Claim/Link/Initial.view.tsx:120-126
Timestamp: 2025-04-30T21:31:27.790Z
Learning: The `sendLinksApi.claim` function in the Peanut Protocol UI accepts both username and wallet address as the first parameter.
Applied to files:
src/features/payments/flows/send/send.types.tssrc/features/payments/flows/send/SendPageWrapper.tsx
📚 Learning: 2025-09-16T17:27:39.840Z
Learnt from: Zishan-7
Repo: peanutprotocol/peanut-ui PR: 1201
File: src/components/Payment/Views/MantecaFulfillment.view.tsx:56-56
Timestamp: 2025-09-16T17:27:39.840Z
Learning: In the TRequestResponse interface (src/services/services.types.ts), recipientAccount is a required field, not optional. When requestDetails is not null, recipientAccount and its nested user.username properties are guaranteed to exist. Only requestDetails itself can be null (typed as TRequestResponse | null).
Applied to files:
src/features/payments/flows/send/send.types.ts
📚 Learning: 2025-05-13T10:05:24.057Z
Learnt from: kushagrasarathe
Repo: peanutprotocol/peanut-ui PR: 845
File: src/components/Request/link/views/Create.request.link.view.tsx:81-81
Timestamp: 2025-05-13T10:05:24.057Z
Learning: In the peanut-ui project, pages that handle request flows (like Create.request.link.view.tsx) are only accessible to logged-in users who will always have a username, making null checks for user?.user.username unnecessary in these contexts.
Applied to files:
src/features/payments/flows/send/send.types.tssrc/features/payments/flows/send/SendPageWrapper.tsxsrc/app/send/[...username]/page.tsxsrc/hooks/useUserByUsername.ts
📚 Learning: 2025-09-05T07:31:11.396Z
Learnt from: Zishan-7
Repo: peanutprotocol/peanut-ui PR: 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/features/payments/shared/components/SendWithPeanutCta.tsx
📚 Learning: 2025-08-12T17:44:04.268Z
Learnt from: Zishan-7
Repo: peanutprotocol/peanut-ui PR: 1089
File: src/components/LandingPage/dropLink.tsx:35-42
Timestamp: 2025-08-12T17:44:04.268Z
Learning: In the Peanut UI project, opening the `/setup` route in a new tab from landing page CTAs is intentional design behavior to keep users on the marketing page while they start the setup process.
Applied to files:
src/features/payments/shared/components/SendWithPeanutCta.tsx
📚 Learning: 2025-08-26T15:25:53.328Z
Learnt from: Zishan-7
Repo: peanutprotocol/peanut-ui PR: 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/features/payments/flows/send/views/SendSuccessView.tsxsrc/features/payments/shared/components/SendActionList.tsx
📚 Learning: 2025-08-22T07:28:32.281Z
Learnt from: Zishan-7
Repo: peanutprotocol/peanut-ui PR: 1104
File: src/components/Payment/PaymentForm/index.tsx:522-545
Timestamp: 2025-08-22T07:28:32.281Z
Learning: In `src/components/Payment/PaymentForm/index.tsx`, the `handleCompleteDaimoPayment` function is only for updating payment status in the backend after a successful Daimo payment. Payment success/failure is handled by Daimo itself, so try/catch error handling and error display are not needed for backend sync failures - users shouldn't see errors if payment succeeded but database update failed.
Applied to files:
src/features/payments/flows/send/views/SendSuccessView.tsx
📚 Learning: 2024-10-23T09:38:04.446Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 469
File: src/app/request/pay/page.tsx:32-64
Timestamp: 2024-10-23T09:38:04.446Z
Learning: Within `src/app/request/pay/page.tsx`, extracting the `getBaseUrl` function does not add significant readability, and the host URL construction code is expected to change soon.
Applied to files:
src/app/send/[...username]/page.tsx
📚 Learning: 2024-10-22T18:11:36.864Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 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/send/[...username]/page.tsx
📚 Learning: 2024-10-23T09:38:27.670Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 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/send/[...username]/page.tsx
🧬 Code graph analysis (9)
src/features/payments/flows/send/views/SendInputView.tsx (4)
src/context/authContext.tsx (1)
useAuth(247-253)src/features/payments/flows/send/useSendFlow.ts (1)
useSendFlow(15-173)src/features/payments/shared/components/SendWithPeanutCta.tsx (1)
SendWithPeanutCta(25-76)src/features/payments/shared/components/SendActionList.tsx (1)
SendActionList(25-97)
src/features/payments/flows/send/SendPage.tsx (2)
src/features/payments/flows/send/SendFlowContext.tsx (3)
useSendFlowContext(142-148)SendRecipient(14-19)SendFlowProvider(71-140)src/features/payments/flows/send/views/SendInputView.tsx (1)
SendInputView(18-117)
src/hooks/wallet/useWallet.ts (2)
src/utils/general.utils.ts (1)
formatCurrency(282-284)src/constants/zerodev.consts.ts (1)
PEANUT_WALLET_TOKEN_DECIMALS(16-16)
src/features/payments/flows/send/useSendFlow.ts (6)
src/features/payments/flows/send/SendFlowContext.tsx (1)
useSendFlowContext(142-148)src/features/payments/shared/hooks/useChargeManager.ts (1)
useChargeManager(42-182)src/features/payments/shared/hooks/usePaymentRecorder.ts (1)
usePaymentRecorder(33-80)src/hooks/wallet/useWallet.ts (1)
useWallet(16-122)src/constants/zerodev.consts.ts (3)
PEANUT_WALLET_TOKEN(17-17)PEANUT_WALLET_CHAIN(15-15)PEANUT_WALLET_TOKEN_DECIMALS(16-16)src/utils/sdkErrorHandler.utils.tsx (1)
ErrorHandler(9-128)
src/features/payments/shared/components/SendWithPeanutCta.tsx (3)
src/components/0_Bruddle/Button.tsx (2)
ButtonProps(23-42)Button(80-279)src/context/authContext.tsx (1)
useAuth(247-253)src/utils/general.utils.ts (1)
saveRedirectUrl(798-802)
src/features/payments/flows/send/SendPageWrapper.tsx (4)
src/hooks/useUserByUsername.ts (1)
useUserByUsername(9-39)src/features/payments/flows/send/SendFlowContext.tsx (1)
SendRecipient(14-19)src/components/Global/PeanutLoading/index.tsx (1)
PeanutLoading(4-28)src/features/payments/flows/send/SendPage.tsx (1)
SendPage(29-35)
src/features/payments/flows/send/SendFlowContext.tsx (2)
src/services/services.types.ts (2)
TRequestChargeResponse(171-209)PaymentCreationResponse(152-169)src/redux/slices/send-flow-slice.ts (2)
setRecipient(50-52)setTxHash(77-79)
src/app/send/[...username]/page.tsx (1)
src/features/payments/flows/send/SendPageWrapper.tsx (1)
SendPageWrapper(21-64)
src/features/payments/shared/components/SendActionList.tsx (6)
src/hooks/useKycStatus.tsx (1)
useKycStatus(12-36)src/constants/actionlist.consts.ts (2)
ACTION_METHODS(14-48)PaymentMethod(5-12)src/hooks/useGeoFilteredPaymentOptions.ts (1)
useGeoFilteredPaymentOptions(30-77)src/utils/general.utils.ts (1)
saveRedirectUrl(798-802)src/app/quests/explore/loading.tsx (1)
Loading(1-10)src/components/ActionListCard/index.tsx (1)
ActionListCard(23-78)
🪛 Biome (2.1.2)
src/features/payments/shared/components/SendActionList.tsx
[error] 48-48: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
🔇 Additional comments (20)
src/app/(mobile-ui)/layout.tsx (1)
160-160: LGTM! Consistent spacing adjustment.The reduction in bottom padding for non-logged-in users aligns with the same change in
payment-layout-wrapper.tsx, ensuring consistent UI spacing across layouts.src/app/[...recipient]/payment-layout-wrapper.tsx (1)
44-44: LGTM! Consistent with mobile-ui layout.The padding adjustment matches the change in
src/app/(mobile-ui)/layout.tsx(Line 160), maintaining consistent spacing for unauthenticated users across different layout wrappers.src/constants/actionlist.consts.ts (1)
40-47: LGTM! Temporary removal aligned with DaimoPay deprecation.The exchange-or-wallet payment method is appropriately disabled while deposit v2 integration is in progress. The TODO comment provides clear context for future re-enablement.
src/hooks/useUserByUsername.ts (1)
11-11: LGTM! Better initial loading state.Setting
isLoadingto!!usernameprevents a flash of non-loading state when the component mounts with a username. This aligns better with the actual fetch behavior.src/hooks/wallet/useWallet.ts (1)
94-109: LGTM! Well-implemented balance utilities.The new
formattedBalanceandhasSufficientBalanceadditions are well-designed:
- Proper memoization to avoid unnecessary recalculations
- Correct conversion between USD and Wei using
PEANUT_WALLET_TOKEN_DECIMALS- Safe handling of undefined balance and invalid inputs
- Type-safe implementation
src/features/payments/flows/send/send.types.ts (1)
1-15: LGTM! Clean type definitions.The type structure is well-organized:
- Clear documentation about avoiding circular imports
- Convenient re-exports from
SendFlowContextResolveUsernameResultinterface is appropriately defined for username resolution flowssrc/features/payments/flows/send/SendPage.tsx (1)
1-35: LGTM! Clean provider-based architecture.The component properly:
- Wraps the flow in
SendFlowProviderwith the initial recipient- Switches views based on
currentViewstate from context- Follows React best practices with context separation
src/features/payments/flows/send/SendPageWrapper.tsx (1)
21-63: LGTM - Clean component structure with proper state handling.The component correctly handles loading, error, and success states with appropriate UI feedback. The use of
useMemofor recipient derivation is appropriate given the dependency on user data.src/features/payments/flows/send/views/SendInputView.tsx (1)
58-116: Well-structured input view with proper conditional rendering.The component cleanly separates concerns: recipient display, amount input, attachment, action button, and error states. The conditional rendering for guest vs. authenticated users is handled appropriately.
src/features/payments/flows/send/views/SendSuccessView.tsx (1)
19-88: Clean success view implementation with good UX elements.The component provides appropriate feedback via haptics and sound, displays relevant transaction information, and handles navigation cleanly. The conditional attachment message display is well-handled.
src/features/payments/shared/components/SendWithPeanutCta.tsx (1)
25-41: Well-implemented authentication-aware CTA component.The component correctly handles the auth flow by saving the redirect URL before navigating to setup, and properly delegates to the provided onClick handler when auth is not required or user is logged in.
src/features/payments/flows/send/useSendFlow.ts (4)
1-14: LGTM!Imports are well-organized and complete for the hook's functionality. The 'use client' directive is appropriate for this React hook.
15-43: LGTM!Clean destructuring from context and hooks. The centralized error handling in
executePaymentmakes it acceptable to not consume individual hook error states.
45-71: LGTM!Helper functions are well-structured with appropriate memoization. The
canProceedvalidation correctly handles edge cases (NaN, zero/negative amounts).
144-172: LGTM!Clean return API with well-aggregated loading state and properly exposed computed properties.
src/features/payments/flows/send/SendFlowContext.tsx (5)
1-32: LGTM!Type definitions are well-structured and appropriately exported. The
SendFlowViewunion type and interfaces provide good type safety for the flow state.
34-62: LGTM!Comprehensive context type definition with properly typed state and setter functions.
84-95: Verify:resetSendFlowdoes not resetrecipient.The reset function clears all state except
recipient. This appears intentional since the recipient is typically derived from the route/URL (viainitialRecipientprop), but please confirm this is the desired behavior.
97-137: LGTM!Proper memoization pattern for context value. The dependency array correctly includes state values while omitting stable setter functions.
142-148: LGTM!Standard and correct context hook implementation with helpful error messaging for misuse detection.
| // step 2: send money via peanut wallet | ||
| const txResult = await sendMoney(recipient.address, amount) | ||
| const hash = (txResult.receipt?.transactionHash ?? txResult.userOpHash) as Hash | ||
|
|
||
| setTxHash(hash) | ||
|
|
||
| // step 3: record payment to backend | ||
| const paymentResult = await recordPayment({ | ||
| chargeId: chargeResult.uuid, | ||
| chainId: PEANUT_WALLET_CHAIN.id.toString(), | ||
| txHash: hash, | ||
| tokenAddress: PEANUT_WALLET_TOKEN as Address, | ||
| payerAddress: walletAddress as Address, | ||
| }) |
There was a problem hiding this comment.
Potential undefined transaction hash.
If sendMoney returns a result where both receipt?.transactionHash and userOpHash are undefined, the hash variable will be undefined cast as Hash. This would then be passed to recordPayment, potentially causing issues.
Consider adding a validation check:
// step 2: send money via peanut wallet
const txResult = await sendMoney(recipient.address, amount)
const hash = (txResult.receipt?.transactionHash ?? txResult.userOpHash) as Hash
+
+ if (!hash) {
+ throw new Error('Transaction completed but no hash returned')
+ }
setTxHash(hash)📝 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.
| // step 2: send money via peanut wallet | |
| const txResult = await sendMoney(recipient.address, amount) | |
| const hash = (txResult.receipt?.transactionHash ?? txResult.userOpHash) as Hash | |
| setTxHash(hash) | |
| // step 3: record payment to backend | |
| const paymentResult = await recordPayment({ | |
| chargeId: chargeResult.uuid, | |
| chainId: PEANUT_WALLET_CHAIN.id.toString(), | |
| txHash: hash, | |
| tokenAddress: PEANUT_WALLET_TOKEN as Address, | |
| payerAddress: walletAddress as Address, | |
| }) | |
| // step 2: send money via peanut wallet | |
| const txResult = await sendMoney(recipient.address, amount) | |
| const hash = (txResult.receipt?.transactionHash ?? txResult.userOpHash) as Hash | |
| if (!hash) { | |
| throw new Error('Transaction completed but no hash returned') | |
| } | |
| setTxHash(hash) | |
| // step 3: record payment to backend | |
| const paymentResult = await recordPayment({ | |
| chargeId: chargeResult.uuid, | |
| chainId: PEANUT_WALLET_CHAIN.id.toString(), | |
| txHash: hash, | |
| tokenAddress: PEANUT_WALLET_TOKEN as Address, | |
| payerAddress: walletAddress as Address, | |
| }) |
🤖 Prompt for AI Agents
In src/features/payments/flows/send/useSendFlow.ts around lines 101-114, the
code casts a potentially undefined transaction hash to Hash and passes it to
recordPayment; add a validation after computing hash to verify it is
defined/non-empty (throw or handle error, set an error state, and do not call
recordPayment) and log/report the failure so we never call recordPayment with an
undefined hash; ensure any early return or error path cleans up UI state (e.g.,
clear tx hash, set loading=false) and surface an appropriate user-facing error.
| case 'mercadopago': | ||
| case 'pix': | ||
| saveRedirectUrl() | ||
| const redirectUri = encodeURIComponent('/add-money') |
There was a problem hiding this comment.
Fix variable scoping in switch statement.
The redirectUri declaration needs block scope to prevent access from other switch cases.
Apply this diff to wrap the declaration in a block:
const handleMethodClick = (method: PaymentMethod) => {
// for all methods, save current url and redirect to setup with add-money as final destination
// verification will be handled in the add-money flow after login
switch (method.id) {
case 'bank':
case 'mercadopago':
- case 'pix':
+ case 'pix': {
saveRedirectUrl()
const redirectUri = encodeURIComponent('/add-money')
router.push(`/setup?redirect_uri=${redirectUri}`)
break
+ }
}
}🧰 Tools
🪛 Biome (2.1.2)
[error] 48-48: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
🤖 Prompt for AI Agents
In src/features/payments/shared/components/SendActionList.tsx around line 48,
the const redirectUri = encodeURIComponent('/add-money') is declared in a switch
case without block scope allowing it to be accessed by other cases; wrap the
switch case body in braces (or create a new block) so redirectUri is declared
with block scope (const/let) inside that case only, ensuring no name leakage to
other cases.
| } | ||
| } | ||
|
|
||
| // determine button text and state |
There was a problem hiding this comment.
thought: these "states" should be reusable across all Balance Decreasing flows. If we don't reuse them, we have duplication, which is more maintenance and more bugs.
Is there any way to consolidate?
| } | ||
| } | ||
|
|
||
| // handle back navigation |
There was a problem hiding this comment.
nice fn. Could be abstracted across flows
No description provided.