Skip to content

[wip] feat: send flow isolated from payment from #1568

Closed
kushagrasarathe wants to merge 4 commits intorefactor/payment-flow-setupfrom
refactor/payment-flow-direct-send
Closed

[wip] feat: send flow isolated from payment from #1568
kushagrasarathe wants to merge 4 commits intorefactor/payment-flow-setupfrom
refactor/payment-flow-direct-send

Conversation

@kushagrasarathe
Copy link
Contributor

No description provided.

@vercel
Copy link

vercel bot commented Dec 17, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
peanut-wallet Ready Ready Preview, Comment Dec 17, 2025 8:49am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 17, 2025

Walkthrough

This 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

Cohort / File(s) Summary
UI spacing adjustments
src/app/(mobile-ui)/layout.tsx, src/app/[...recipient]/payment-layout-wrapper.tsx
Reduced bottom padding from pb-6 to pb-4 for non-logged-in states; logged-in padding unchanged.
Send flow removal
src/components/Common/ActionListDaimoPayButton.tsx, src/components/Global/FeeDescription/index.tsx
Removed ActionListDaimoPayButton component with DaimoPayWrapper integration and balance-check orchestration; removed expandable FeeDescription component.
Send flow context & state management
src/features/payments/flows/send/SendFlowContext.tsx, src/features/payments/flows/send/send.types.ts, src/features/payments/flows/send/useSendFlow.ts
Introduced SendFlowContext with types (SendFlowView, SendRecipient, SendAttachment, SendFlowErrorState, SendFlowContextType), provider component, and comprehensive useSendFlow hook orchestrating charge creation, peanut transfer, and payment recording.
Send flow UI components
src/features/payments/flows/send/SendPage.tsx, src/features/payments/flows/send/SendPageWrapper.tsx, src/features/payments/flows/send/views/SendInputView.tsx, src/features/payments/flows/send/views/SendSuccessView.tsx
Created SendPage wrapper with provider, SendPageWrapper for username resolution, SendInputView for input/submission, and SendSuccessView for completion UI.
Send flow payment methods
src/features/payments/shared/components/SendActionList.tsx, src/features/payments/shared/components/SendWithPeanutCta.tsx
Added SendActionList for rendering geo-filtered payment methods with status badges and SendWithPeanutCta button component with auth handling.
Configuration & routing
src/constants/actionlist.consts.ts, src/components/Common/ActionList.tsx, src/app/send/[...username]/page.tsx
Removed 'exchange-or-wallet' from ACTION_METHODS; removed ActionListDaimoPayButton branch from request flow; refactored send route to use new SendPageWrapper with username parameter.
Wallet & utility updates
src/hooks/wallet/useWallet.ts, src/hooks/useUserByUsername.ts
Added formattedBalance and hasSufficientBalance to useWallet; updated useUserByUsername initial loading state to depend on username presence.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • useSendFlow: Multi-step orchestration with charge creation, transfer, and payment recording; requires validation of error handling and state transitions across three async operations.
  • SendInputView & SendSuccessView: New UI components with flow control, navigation guards, and balance validation; verify integration with context and hook.
  • SendPageWrapper: User resolution and recipient derivation logic; ensure error handling for missing users and wallet addresses.
  • ActionList refactor: Removal of DaimoPay branch and related imports; verify no dangling references and that exchange-or-wallet handling is properly removed.
  • Wallet hook expansion: New formattedBalance and hasSufficientBalance utilities; confirm decimal handling and balance comparison logic.

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • jjramirezn

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 2 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title is incomplete and vague, ending with 'from' without finishing the thought, making it unclear what was isolated from what. Complete the title to clearly describe what was isolated. For example: 'Isolate send flow from payment flow' or 'Refactor: separate send flow component architecture'.
Description check ❓ Inconclusive No pull request description was provided by the author, making it impossible to assess relatedness to the changeset. Add a description explaining the motivation, approach, and impact of the send flow refactoring, including which components were isolated and why.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/payment-flow-direct-send

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai bot added the enhancement New feature or request label Dec 17, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-wallet is misleading since that entry has already been removed from ACTION_METHODS in src/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 const

Then 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) && !isUserMantecaKycApproved
src/features/payments/flows/send/SendPageWrapper.tsx (1)

33-38: Consider validating the address format before casting.

The walletAccount.identifier is cast directly to Address without validation. If the identifier is malformed, this could cause issues downstream when used with viem functions.

Consider using viem's isAddress utility 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 > 1 doesn't reliably indicate whether router.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.referrer check 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 both handleDone and the NavHeader's onPrev callback.

     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, username becomes an empty string. This is passed to SendPageWrapper, 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 ErrorAlert in 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

📥 Commits

Reviewing files that changed from the base of the PR and between c672744 and b5f27ac.

📒 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.tsx
  • src/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.tsx
  • src/hooks/wallet/useWallet.ts
  • src/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.tsx
  • src/hooks/wallet/useWallet.ts
  • src/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.tsx
  • src/features/payments/flows/send/SendPage.tsx
  • src/app/send/[...username]/page.tsx
  • src/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.ts
  • src/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.ts
  • src/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.ts
  • src/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.ts
  • src/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.ts
  • src/features/payments/flows/send/views/SendSuccessView.tsx
  • src/features/payments/flows/send/SendFlowContext.tsx
  • src/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.ts
  • src/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.tsx
  • src/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.ts
  • src/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.ts
  • src/features/payments/flows/send/SendPageWrapper.tsx
  • src/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.ts
  • src/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.ts
  • src/features/payments/flows/send/SendPageWrapper.tsx
  • src/app/send/[...username]/page.tsx
  • src/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.tsx
  • src/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 isLoading to !!username prevents 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 formattedBalance and hasSufficientBalance additions 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 SendFlowContext
  • ResolveUsernameResult interface is appropriately defined for username resolution flows
src/features/payments/flows/send/SendPage.tsx (1)

1-35: LGTM! Clean provider-based architecture.

The component properly:

  • Wraps the flow in SendFlowProvider with the initial recipient
  • Switches views based on currentView state 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 useMemo for 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 executePayment makes it acceptable to not consume individual hook error states.


45-71: LGTM!

Helper functions are well-structured with appropriate memoization. The canProceed validation 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 SendFlowView union 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: resetSendFlow does not reset recipient.

The reset function clears all state except recipient. This appears intentional since the recipient is typically derived from the route/URL (via initialRecipient prop), 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.

Comment on lines +101 to +114
// 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,
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Suggested change
// 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')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice fn. Could be abstracted across flows

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants