Skip to content

feat: update send router#1364

Merged
kushagrasarathe merged 4 commits intopeanut-wallet-devfrom
feat/send-router
Oct 27, 2025
Merged

feat: update send router#1364
kushagrasarathe merged 4 commits intopeanut-wallet-devfrom
feat/send-router

Conversation

@kushagrasarathe
Copy link
Contributor

@kushagrasarathe kushagrasarathe commented Oct 27, 2025

  • contributes to TASK-15472 : send router updates, enable bank/mepa/ens sends
image

@vercel
Copy link

vercel bot commented Oct 27, 2025

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

Project Deployment Preview Comments Updated (UTC)
peanut-wallet Ready Ready Preview Comment Oct 27, 2025 0:48am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 27, 2025

Walkthrough

Renames SearchResultCard → ActionListCard and updates all usages; removes RouterViewWrapper and SearchResults; adds geolocation-based payment filtering hook and integrates it into ActionList and SendRouter; adds avatar tiny size; adjusts withdraw/send flows to propagate ?method send-flow param; minor icon and constant updates.

Changes

Cohort / File(s) Change Summary
Component Rename
src/components/ActionListCard/index.tsx
Renamed component and exported types: SearchResultCardActionListCard, SearchResultCardPropsActionListCardProps.
Card Usages (bulk replace)
src/components/AddMoney/components/CryptoSourceListCard.tsx, src/components/AddMoney/components/DepositMethodList.tsx, src/components/AddMoney/views/TokenSelection.view.tsx, src/components/AddWithdraw/AddWithdrawCountriesList.tsx, src/components/Common/ActionListDaimoPayButton.tsx, src/components/Common/CountryList.tsx, src/components/Common/CountryListSkeleton.tsx, src/components/Common/SavedAccountsView.tsx
Replaced imports/usages of SearchResultCard with ActionListCard; props preserved.
ActionList refactor & geofiltering
src/components/Common/ActionList.tsx, src/constants/actionlist.consts.ts
Replaced card component usage, removed old geolocation logic, added useGeoFilteredPaymentOptions, introduced requiresVerification logic, and added optional identifierIcon to PaymentMethod.
SendRouter restructuring
src/components/Send/views/SendRouter.view.tsx
Reworked layout to use NavHeader/Card/ActionListCard, added query-param-driven sub-views (view=link/contacts), recent users avatars, geolocation-filtered methods, and updated navigation handlers.
Add/Withdraw flow updates
src/components/AddWithdraw/AddWithdrawCountriesList.tsx, src/components/AddWithdraw/AddWithdrawRouterView.tsx, src/app/(mobile-ui)/withdraw/page.tsx, src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx
Introduced useSearchParams-derived send-flow detection (?method=bank), propagated method param through navigation, conditioned titles/steps/back behavior on send-flow, and wired enforceSupportedCountries to CountryList.
Removed components/hooks
src/components/RouterViewWrapper/index.tsx, src/components/SearchUsers/SearchResults.tsx, src/hooks/useTranslationViewTransition.ts
Deleted RouterViewWrapper, SearchResults, and useTranslationViewTransition (including their prop interfaces/exports).
New hooks / Hook API changes
src/hooks/useGeoFilteredPaymentOptions.ts, src/hooks/useRecentUsers.ts
Added useGeoFilteredPaymentOptions (filter/sort methods by country); changed useRecentUsers() return from RecentUser[] to { recentTransactions, isFetchingRecentUsers }.
Icon & avatar changes
src/components/Global/Icons/user.tsx, src/components/Profile/AvatarWithBadge.tsx
Adjusted user SVG dimensions/paths; added 'tiny' to AvatarSize and corresponding sizing maps.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Areas needing focused review:
    • SendRouter.view.tsx: navigation state, query-param flows (view/link/contacts) and recent-users rendering.
    • useGeoFilteredPaymentOptions + ActionList.tsx: filtering rules for pix/mercadopago and unavailable-method sorting.
    • useRecentUsers signature change: verify all consumers now expect the object shape.
    • Deleted components (RouterViewWrapper, SearchResults): ensure no remaining callers and that replacements preserve UX/navigation.
    • AddWithdrawRouterView / withdraw pages: correct propagation of ?method across routes and back-navigation behavior.

Possibly related PRs

Suggested reviewers

  • jjramirezn
  • Zishan-7
  • FacuBozzi

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The PR title "feat: update send router" directly refers to a substantial and well-documented change in the pull request: the rewrite of SendRouter.view.tsx (marked as high code review effort), which replaces the RouterViewWrapper-based layout with a new design using NavHeader, Card, Divider, and ActionListCard, along with enhanced route/view state handling via query parameters. While the changeset also includes other significant modifications such as component renaming (SearchResultCard to ActionListCard across multiple files), withdraw flow integration with send flow context, and new filtering hooks, the title appropriately highlights the primary objective of updating the send router as stated in the PR description ("send router updates, enable bank/mepa/ens sends").
Description Check ✅ Passed The PR description "contributes to TASK-15472 : send router updates, enable bank/mepa/ens sends" is directly related to the changeset. It references the task identifier, mentions send router updates which align with the SendRouter.view.tsx rewrite, and indicates the goal of enabling bank, Mercado Pago, and other payment method sends, which corresponds to the routing logic, payment method filtering, and UI enhancements visible throughout the modified files. The description is adequately specific for the scope of work demonstrated in the raw summary.
✨ 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 feat/send-router

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

@coderabbitai coderabbitai bot added the enhancement New feature or request label Oct 27, 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: 4

🧹 Nitpick comments (5)
src/components/Global/Icons/user.tsx (1)

4-6: Optional: prefer props-driven sizing and add a11y defaults.

To avoid hard-coding size and to keep decorative icons out of the a11y tree, consider:

  • Let consumer control width/height entirely.
  • Add aria-hidden and focusable defaults.
-    <svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
+    <svg viewBox="0 0 10 10" fill="none" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" {...props}>
src/hooks/useRecentUsers.ts (1)

21-25: Defensive null-safety for username comparison.

Guard against undefined account.username before toLowerCase to avoid edge-case crashes.

-            const isDuplicate = acc.some(
-                (user) =>
-                    user.userId === account.userId || user.username.toLowerCase() === account.username.toLowerCase()
-            )
+            const accountUsernameLc = account.username?.toLowerCase()
+            const isDuplicate =
+                acc.some((user) => user.userId === account.userId) ||
+                (accountUsernameLc
+                    ? acc.some((user) => user.username.toLowerCase() === accountUsernameLc)
+                    : false)
src/components/Send/views/SendRouter.view.tsx (2)

194-199: IconStack size consistency.

iconSize={80} seems large for a right adornment; elsewhere you use 24. Consider 24–32 for visual consistency.

-                                            iconSize={80}
+                                            iconSize={24}

Also confirm AvatarWithBadge size="tiny" is a supported token; otherwise switch to "extra-small".


91-128: Centralize identifierIcon mapping.

Embedding identifierIcon switch in the view duplicates concerns. Prefer enriching ACTION_METHODS (or a factory util) so all consumers render consistent identifiers.

src/hooks/useGeoFilteredPaymentOptions.ts (1)

54-70: Document the need for stable isMethodUnavailable callback.

The isMethodUnavailable function is included in the useMemo dependency array (line 70). If callers pass an inline function or don't memoize it with useCallback, the memoization will be invalidated on every render.

Apply this diff to document this requirement:

 /**
  * hook to filter and sort payment options based on user's geolocation
  *
  * filters pix to show only in brazil (BR) and mercado pago to show everywhere except brazil
  * optionally sorts payment methods to move unavailable ones to the end of the list
  *
  * @param options - configuration object
  * @param options.methods - array of payment methods to filter (defaults to ACTION_METHODS)
  * @param options.sortUnavailable - whether to sort unavailable methods to the end (defaults to false)
- * @param options.isMethodUnavailable - optional function to determine if a method is unavailable for custom sorting logic
+ * @param options.isMethodUnavailable - optional function to determine if a method is unavailable for custom sorting logic (should be memoized with useCallback to avoid unnecessary recalculations)
  * @returns object containing filtered and sorted payment methods and loading state
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b3ed0f6 and b1c3f29.

📒 Files selected for processing (19)
  • src/components/ActionListCard/index.tsx (3 hunks)
  • src/components/AddMoney/components/CryptoSourceListCard.tsx (2 hunks)
  • src/components/AddMoney/components/DepositMethodList.tsx (2 hunks)
  • src/components/AddMoney/views/TokenSelection.view.tsx (2 hunks)
  • src/components/AddWithdraw/AddWithdrawCountriesList.tsx (2 hunks)
  • src/components/Common/ActionList.tsx (3 hunks)
  • src/components/Common/ActionListDaimoPayButton.tsx (3 hunks)
  • src/components/Common/CountryList.tsx (4 hunks)
  • src/components/Common/CountryListSkeleton.tsx (2 hunks)
  • src/components/Common/SavedAccountsView.tsx (2 hunks)
  • src/components/Global/Icons/user.tsx (1 hunks)
  • src/components/Profile/AvatarWithBadge.tsx (2 hunks)
  • src/components/RouterViewWrapper/index.tsx (0 hunks)
  • src/components/SearchUsers/SearchResults.tsx (0 hunks)
  • src/components/Send/views/SendRouter.view.tsx (2 hunks)
  • src/constants/actionlist.consts.ts (2 hunks)
  • src/hooks/useGeoFilteredPaymentOptions.ts (1 hunks)
  • src/hooks/useRecentUsers.ts (2 hunks)
  • src/hooks/useTranslationViewTransition.ts (0 hunks)
💤 Files with no reviewable changes (3)
  • src/components/RouterViewWrapper/index.tsx
  • src/components/SearchUsers/SearchResults.tsx
  • src/hooks/useTranslationViewTransition.ts
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-05-22T15:38:48.586Z
Learnt from: kushagrasarathe
PR: peanutprotocol/peanut-ui#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/components/AddWithdraw/AddWithdrawCountriesList.tsx
📚 Learning: 2025-08-26T17:38:37.055Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#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/components/Common/ActionList.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
📚 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/views/TokenSelection.view.tsx
🧬 Code graph analysis (12)
src/components/Send/views/SendRouter.view.tsx (7)
src/redux/hooks.ts (1)
  • useAppDispatch (5-5)
src/hooks/useRecentUsers.ts (1)
  • useRecentUsers (6-37)
src/utils/general.utils.ts (1)
  • getInitialsFromName (1271-1278)
src/constants/actionlist.consts.ts (2)
  • ACTION_METHODS (14-47)
  • PaymentMethod (5-12)
src/components/Global/Icons/Icon.tsx (1)
  • Icon (209-218)
src/hooks/useGeoFilteredPaymentOptions.ts (1)
  • useGeoFilteredPaymentOptions (30-77)
src/components/ActionListCard/index.tsx (1)
  • ActionListCard (19-70)
src/components/AddWithdraw/AddWithdrawCountriesList.tsx (1)
src/components/ActionListCard/index.tsx (1)
  • ActionListCard (19-70)
src/hooks/useRecentUsers.ts (1)
src/hooks/useTransactionHistory.ts (1)
  • useTransactionHistory (51-130)
src/hooks/useGeoFilteredPaymentOptions.ts (2)
src/constants/actionlist.consts.ts (2)
  • PaymentMethod (5-12)
  • ACTION_METHODS (14-47)
src/hooks/useGeoLocation.ts (1)
  • useGeoLocation (17-71)
src/components/AddMoney/components/DepositMethodList.tsx (1)
src/components/ActionListCard/index.tsx (1)
  • ActionListCard (19-70)
src/components/Common/ActionList.tsx (2)
src/hooks/useGeoFilteredPaymentOptions.ts (1)
  • useGeoFilteredPaymentOptions (30-77)
src/components/ActionListCard/index.tsx (1)
  • ActionListCard (19-70)
src/components/Common/CountryListSkeleton.tsx (1)
src/components/ActionListCard/index.tsx (1)
  • ActionListCard (19-70)
src/components/Common/ActionListDaimoPayButton.tsx (1)
src/components/ActionListCard/index.tsx (1)
  • ActionListCard (19-70)
src/components/Common/SavedAccountsView.tsx (1)
src/components/ActionListCard/index.tsx (1)
  • ActionListCard (19-70)
src/components/AddMoney/components/CryptoSourceListCard.tsx (1)
src/components/ActionListCard/index.tsx (1)
  • ActionListCard (19-70)
src/components/Common/CountryList.tsx (1)
src/components/ActionListCard/index.tsx (1)
  • ActionListCard (19-70)
src/components/AddMoney/views/TokenSelection.view.tsx (1)
src/components/ActionListCard/index.tsx (1)
  • ActionListCard (19-70)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Deploy-Preview
🔇 Additional comments (25)
src/components/Profile/AvatarWithBadge.tsx (3)

9-9: LGTM! Type definition correctly extended.

The 'tiny' size option is properly added to the type union and will be enforced by TypeScript across all usages.


40-40: Verify accessibility for 10px text size.

The implementation correctly follows the existing pattern. However, the 10px text size for initials is quite small and may not meet WCAG accessibility guidelines (typically minimum 12px for body text). Please ensure this size is appropriate for your use case and won't be used in contexts where readability is critical.


48-48: LGTM! Icon size correctly proportioned.

The 12px icon size maintains the 50% icon-to-container ratio (12/24), consistent with the pattern used in other sizes.

src/hooks/useRecentUsers.ts (2)

7-8: Confirm react-query version for isLoading.

If using TanStack Query v5, the pending flag is isPending (not isLoading). Please confirm the version and adjust to avoid stale API usage.


36-36: Return type change verified - all consumers updated correctly.

Verification confirms there is only one call site of useRecentUsers in the codebase (src/components/Send/views/SendRouter.view.tsx:28), and it correctly uses the new object destructuring pattern: const { recentTransactions, isFetchingRecentUsers } = useRecentUsers(). No outdated array-based usage patterns remain.

src/components/AddWithdraw/AddWithdrawCountriesList.tsx (1)

287-336: LGTM: ActionListCard migration is correct.

Props mapping (title/description/leftIcon/rightContent/position) matches the new component API; disabled state preserved.

src/components/Common/ActionListDaimoPayButton.tsx (1)

165-173: LGTM: Card swap with consistent right content.

ActionListCard usage is correct and keeps IconStack on the right; disabled state respects loading. Also aligns with prior learning to avoid surfacing DB-update errors to users.

Based on learnings

src/components/AddMoney/views/TokenSelection.view.tsx (1)

8-8: LGTM! Clean component migration.

The update from SearchResultCard to ActionListCard is well-executed, with all props correctly mapped to the new component interface.

Also applies to: 28-45

src/components/AddMoney/components/CryptoSourceListCard.tsx (1)

6-6: LGTM! Consistent refactoring.

The migration to ActionListCard maintains all existing functionality with correct prop mapping.

Also applies to: 25-57

src/components/AddMoney/components/DepositMethodList.tsx (1)

7-7: LGTM! Proper component migration.

The switch to ActionListCard preserves all props including the descriptionClassName customization. Well done.

Also applies to: 63-94

src/components/Common/CountryListSkeleton.tsx (1)

2-2: LGTM! Skeleton component updated correctly.

The migration to ActionListCard in the skeleton maintains the loading state UI appropriately.

Also applies to: 14-20

src/constants/actionlist.consts.ts (2)

7-7: New field added for future extensibility.

The optional identifierIcon field extends the PaymentMethod interface. This field isn't used in the files under review but may be leveraged elsewhere or reserved for future functionality.


43-43: Verify removal of "Coinbase" from description.

The description was updated to remove "Coinbase" from the list of supported exchanges. Please confirm this is intentional (e.g., Coinbase integration removed or temporarily disabled).

src/components/Common/SavedAccountsView.tsx (2)

3-3: LGTM! Good cleanup of unused import.

Removing the unused shortenStringLong import while adding ActionListCard keeps the imports clean.

Also applies to: 11-11


104-126: LGTM! Consistent migration to ActionListCard.

The saved accounts rendering correctly uses ActionListCard with proper flag and bank icon composition in the leftIcon prop.

src/components/Common/CountryList.tsx (3)

10-10: LGTM! Import paths updated appropriately.

The SearchInput import path has been simplified and ActionListCard is now correctly imported. This aligns with the broader component reorganization.

Also applies to: 20-20


109-123: LGTM! Crypto option card migrated correctly.

The crypto deposit/withdraw option now uses ActionListCard with appropriate description and icon. Props are correctly mapped.


162-198: LGTM! Country list rendering migrated successfully.

Each country item now renders with ActionListCard, preserving:

  • Flag loading optimization (priority/lazy loading)
  • Loading state per clicked item
  • Disabled state logic
  • Custom right content via getRightContent
src/components/ActionListCard/index.tsx (1)

7-17: LGTM! Clean component and interface rename.

The rename from SearchResultCard to ActionListCard (and corresponding props interface) is well-executed. The new name better reflects the component's purpose as a generic action list item rather than being tied to search results.

All consuming components across the codebase have been updated consistently.

Also applies to: 19-29

src/hooks/useGeoFilteredPaymentOptions.ts (2)

1-4: LGTM!

The imports are clean and the 'use client' directive is correctly placed for this client-side hook.


72-76: Remove unnecessary countryCode from return value—it's unused by all consumers.

The countryCode is returned from this hook but never destructured by either consumer (ActionList.tsx and SendRouter.view.tsx), which only use filteredMethods and isLoading. The JSDoc examples similarly never reference countryCode. Since no consumer accesses this value, it should be removed from the return type rather than kept as dead code.

Likely an incorrect or invalid review comment.

src/components/Common/ActionList.tsx (4)

24-24: LGTM!

The new imports follow the established pattern of separating constants and using dedicated components. The PaymentMethod type import and useGeoFilteredPaymentOptions hook align well with the geolocation-aware filtering approach.

Based on learnings

Also applies to: 32-33


88-96: LGTM!

The requiresVerification logic correctly identifies cases where KYC is needed for bank transactions, with appropriate memoization dependencies.


292-322: LGTM!

The MethodCard component is well-structured and correctly integrates the new ActionListCard component. The requiresVerification state is properly displayed with a status badge, and the disabled state is appropriately handled.


195-201: I need to examine the full context of these files to understand the complete flow:

Let me check the complete ActionList component to see how it handles the error state and what happens when geolocation fails:

Based on my verification, I've confirmed the concerns in the original review comment. The useGeoLocation hook properly sets an error state when geolocation fails, and when that happens, isLoading becomes false while countryCode remains null. This causes the loading state check to pass and render the component without any error feedback to the user. While the filtering logic (countryCode === 'BR') safely handles null values by returning false, the silent failure provides poor UX. The error state is returned by the hook but never checked in ActionList.

Check the ActionList component for error state handling and add appropriate error UI feedback.

When geolocation fails, the error state from useGeoLocation is set but not displayed. Add a check for the error state to show user feedback before rendering the normal UI. Provide a meaningful message or fallback UI when an error occurs to maintain positive user experience. Consider whether to display the error message inline or handle it through a toast/notification system.

@notion-workspace
Copy link

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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx (1)

72-80: Preserve ?method=bank on redirects to keep send-flow context.

When amount/bankAccount guards redirect, you drop the method=bank param, breaking the send context.

Apply:

-            router.replace('/withdraw')
+            router.replace(fromSendFlow ? '/withdraw?method=bank' : '/withdraw')
         } else if (!bankAccount && amountToWithdraw) {
-            router.replace(`/withdraw/${country}`)
+            router.replace(`/withdraw/${country}${fromSendFlow ? '?method=bank' : ''}`)
♻️ Duplicate comments (1)
src/components/Send/views/SendRouter.view.tsx (1)

94-97: Double navigation creates history entries and flicker.

This function calls router.push on Line 95 and then redirectToSendByLink() on Line 96, which also calls router.push (Line 79) to the same URL. This creates two history entries and potential UI flicker.

Apply this diff to remove the redundant navigation:

     const handleLinkCtaClick = () => {
-        router.push(`${window.location.pathname}?view=link`)
         redirectToSendByLink()
     }

The redirectToSendByLink function already handles both state reset and navigation.

🧹 Nitpick comments (10)
src/components/Send/views/SendRouter.view.tsx (3)

35-44: Include fallbackInitials in the dependency array.

The useCallback references fallbackInitials but doesn't include it in the dependency array. While fallbackInitials is a constant and unlikely to change, this violates React's hooks exhaustive-deps rule.

Apply this diff:

-    }, [recentTransactions])
+    }, [recentTransactions, fallbackInitials])

Alternatively, move fallbackInitials inside the callback to avoid the dependency:

     const recentUsersAvatarInitials = useCallback(() => {
+        const fallbackInitials = ['PE', 'AN', 'UT']
         // if we have recent transactions, use them (max 3)
         if (recentTransactions.length > 0) {

76-80: Use usePathname hook instead of window.location.pathname.

Accessing window.location.pathname directly is not idiomatic for Next.js and can cause issues during SSR or hydration.

Import usePathname from next/navigation:

-import { useRouter, useSearchParams } from 'next/navigation'
+import { useRouter, useSearchParams, usePathname } from 'next/navigation'

Then update the function:

+    const pathname = usePathname()
+
     const redirectToSendByLink = () => {
         // reset send flow state when entering link creation flow
         dispatch(sendFlowActions.resetSendFlow())
-        router.push(`${window.location.pathname}?view=link`)
+        router.push(`${pathname}?view=link`)
     }

198-275: LGTM! Contacts view is well-structured.

The contacts view properly handles loading, list rendering with recent transactions, and an empty state. The onClick handlers are correctly wired to handleUserSelect.

Optional: The link CTA card (Lines 249-270) is duplicated from the main view (Lines 281-296). Consider extracting this into a shared component like <LinkCtaCard onClick={handleLinkCtaClick} /> to reduce duplication.

src/components/Common/CountryList.tsx (2)

113-126: Avoid non-null assertion on flow; make flow required for add-withdraw or guard at runtime.

onClick uses flow! which can be undefined at runtime if the caller forgets flow for add-withdraw usage.

Minimal guard:

-                                onClick={() => onCryptoClick(flow!)}
+                                onClick={() => flow && onCryptoClick(flow)}

Better: make props a discriminated union so flow is required when viewMode is 'add-withdraw'.

type CountryListViewProps =
  | {
      viewMode: 'add-withdraw'
      inputTitle: string
      onCountryClick: (c: CountryData) => void
      onCryptoClick?: (flow: 'add' | 'withdraw') => void
      flow: 'add' | 'withdraw'
      getRightContent?: (c: CountryData, isSupported: boolean) => ReactNode
      enforceSupportedCountries?: boolean
    }
  | {
      viewMode: 'claim-request' | 'general-verification'
      inputTitle: string
      onCountryClick: (c: CountryData) => void
      getRightContent?: (c: CountryData, isSupported: boolean) => ReactNode
      onCryptoClick?: never
      flow?: never
      enforceSupportedCountries?: never
    }

135-143: ISO code mix can misclassify Bridge support; normalize once.

You compare country.id directly against a mixed set of alpha-3 keys and alpha-2 values (plus 'US','MX'), which risks mismatches. Normalize both representations before testing.

Apply:

-                            const isBridgeSupportedCountry = [
-                                'US',
-                                'MX',
-                                ...Object.keys(BRIDGE_ALPHA3_TO_ALPHA2),
-                                ...Object.values(BRIDGE_ALPHA3_TO_ALPHA2),
-                            ].includes(country.id)
+                            const idA3 = country.id.toUpperCase()
+                            const idA2 = (ALL_COUNTRIES_ALPHA3_TO_ALPHA2[idA3] ?? idA3).toUpperCase()
+                            const bridgeA3 = new Set(Object.keys(BRIDGE_ALPHA3_TO_ALPHA2))
+                            const bridgeA2 = new Set(Object.values(BRIDGE_ALPHA3_TO_ALPHA2))
+                            const isBridgeSupportedCountry = bridgeA3.has(idA3) || bridgeA2.has(idA2)

Optionally lift the two Sets into a useMemo to avoid re-allocating per item.

src/components/AddWithdraw/AddWithdrawCountriesList.tsx (3)

209-212: Encode dynamic query params for Manteca navigation.

destination and country can contain spaces/special chars. Encode to avoid malformed URLs.

Apply:

-                        router.push(
-                            `/withdraw/manteca?country=${account.details.countryName}&destination=${account.identifier}${additionalParams}`
-                        )
+                        const countryQP = encodeURIComponent(account.details.countryName ?? '')
+                        const destQP = encodeURIComponent(account.identifier ?? '')
+                        router.push(`/withdraw/manteca?country=${countryQP}&destination=${destQP}${additionalParams}`)

41-44: Remove unused isFromSendFlow (or use it).

isFromSendFlow is computed but unused; this will trip linters.

-    const isFromSendFlow = !!(methodParam && ['bank', 'crypto'].includes(methodParam))
-    const isBankFromSend = methodParam === 'bank' && isFromSendFlow
+    const isBankFromSend = methodParam === 'bank'

171-179: Centralize query param building to avoid subtle bugs.

Manual concatenation with '?'/'&' is repeated and error-prone. A tiny helper improves safety and readability.

Example utility:

function withQuery(path: string, qs: Record<string, string | undefined>) {
  const url = new URL(path, 'https://dummy.base') // base ignored by Next router
  Object.entries(qs).forEach(([k, v]) => v !== undefined && url.searchParams.set(k, v))
  return `${url.pathname}${url.search}${url.hash}`
}

Usage:

router.push(withQuery(method.path, { method: isBankFromSend ? methodParam : undefined }))

Also applies to: 193-201, 203-206, 281-289, 292-294, 308-310

src/app/(mobile-ui)/withdraw/page.tsx (1)

206-234: De-duplicate query string assembly; prefer URLSearchParams/utility.

You build method query strings in several branches; a helper would reduce mistakes and keep behavior consistent.

Sketch:

const qp = isFromSendFlow ? { method: methodParam! } : undefined
const to = (p: string) => withQuery(p, qp)

router.push(to(`/withdraw/${country.path}/bank`))
router.push(to(`/withdraw/crypto`))
router.push(withQuery(`/withdraw/manteca`, { method: selectedMethod.title?.toLowerCase().replace(/\s+/g, '-'), country: selectedMethod.countryPath, ...(isFromSendFlow && { method: methodParam! }) }))
src/components/AddWithdraw/AddWithdrawRouterView.tsx (1)

209-212: Encode saved-account params for Manteca route.

Same encoding concern as elsewhere: country and destination should be URL-encoded.

Apply:

-                        const additionalParams = isBankFromSend ? `&method=${methodParam}` : ''
-                        router.push(
-                            `/withdraw/manteca?country=${account.details.countryName}&destination=${account.identifier}${additionalParams}`
-                        )
+                        const additionalParams = isBankFromSend ? `&method=${encodeURIComponent(methodParam ?? '')}` : ''
+                        const countryQP = encodeURIComponent(account.details.countryName ?? '')
+                        const destQP = encodeURIComponent(account.identifier ?? '')
+                        router.push(`/withdraw/manteca?country=${countryQP}&destination=${destQP}${additionalParams}`)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b1c3f29 and 59623d8.

📒 Files selected for processing (6)
  • src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx (4 hunks)
  • src/app/(mobile-ui)/withdraw/page.tsx (8 hunks)
  • src/components/AddWithdraw/AddWithdrawCountriesList.tsx (9 hunks)
  • src/components/AddWithdraw/AddWithdrawRouterView.tsx (4 hunks)
  • src/components/Common/CountryList.tsx (7 hunks)
  • src/components/Send/views/SendRouter.view.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2025-05-22T15:38:48.586Z
Learnt from: kushagrasarathe
PR: peanutprotocol/peanut-ui#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/app/(mobile-ui)/withdraw/[country]/bank/page.tsx
  • src/components/AddWithdraw/AddWithdrawCountriesList.tsx
  • src/components/AddWithdraw/AddWithdrawRouterView.tsx
  • src/components/Common/CountryList.tsx
  • src/app/(mobile-ui)/withdraw/page.tsx
📚 Learning: 2025-09-18T09:30:42.901Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#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/app/(mobile-ui)/withdraw/[country]/bank/page.tsx
  • src/components/AddWithdraw/AddWithdrawRouterView.tsx
  • src/app/(mobile-ui)/withdraw/page.tsx
📚 Learning: 2025-10-02T15:23:01.513Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1266
File: src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx:46-57
Timestamp: 2025-10-02T15:23:01.513Z
Learning: In the withdraw flow at src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx, the points calculation query intentionally uses crypto.randomUUID() in the queryKey dependency array to bypass React Query caching, ensuring fresh points estimates on every render. This is the intended behavior.

Applied to files:

  • src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx
  • src/app/(mobile-ui)/withdraw/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/components/Send/views/SendRouter.view.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)/withdraw/page.tsx
📚 Learning: 2025-10-24T13:44:39.443Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1332
File: src/components/Global/TokenAmountInput/index.tsx:141-150
Timestamp: 2025-10-24T13:44:39.443Z
Learning: In the `TokenAmountInput` component (`src/components/Global/TokenAmountInput/index.tsx`), the slider feature (controlled by `showSlider` prop) is only shown for USD input mode. When the slider is used with `maxAmount`, the `selectedAmount` is computed in USD and `isInputUsd` is always `true`, so the conversion in `onChange` handles it correctly.

Applied to files:

  • src/app/(mobile-ui)/withdraw/page.tsx
🧬 Code graph analysis (4)
src/components/AddWithdraw/AddWithdrawCountriesList.tsx (2)
src/redux/slices/bank-form-slice.ts (1)
  • bankFormActions (36-36)
src/components/ActionListCard/index.tsx (1)
  • ActionListCard (19-70)
src/components/Send/views/SendRouter.view.tsx (9)
src/redux/hooks.ts (1)
  • useAppDispatch (5-5)
src/hooks/useRecentUsers.ts (1)
  • useRecentUsers (6-37)
src/utils/general.utils.ts (1)
  • getInitialsFromName (1271-1278)
src/redux/slices/send-flow-slice.ts (1)
  • sendFlowActions (101-101)
src/constants/actionlist.consts.ts (2)
  • ACTION_METHODS (14-47)
  • PaymentMethod (5-12)
src/components/Global/Icons/Icon.tsx (1)
  • Icon (209-218)
src/hooks/useGeoFilteredPaymentOptions.ts (1)
  • useGeoFilteredPaymentOptions (30-77)
src/components/ActionListCard/index.tsx (1)
  • ActionListCard (19-70)
src/components/0_Bruddle/Button.tsx (1)
  • Button (76-267)
src/components/Common/CountryList.tsx (1)
src/components/ActionListCard/index.tsx (1)
  • ActionListCard (19-70)
src/app/(mobile-ui)/withdraw/page.tsx (2)
src/context/tokenSelector.context.tsx (1)
  • tokenSelectorContext (22-45)
src/utils/bridge.utils.ts (1)
  • getCountryFromAccount (83-95)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Deploy-Preview
🔇 Additional comments (7)
src/components/Send/views/SendRouter.view.tsx (5)

28-29: LGTM! Backward compatibility maintained.

The dual check for both view=link and createLink=true maintains backward compatibility while transitioning to the new query param pattern. This is a reasonable approach.


132-169: LGTM! Clean icon extension logic.

The useMemo correctly extends ACTION_METHODS with method-specific identifier icons. The empty dependency array is appropriate since ACTION_METHODS is an imported constant.


171-192: LGTM! Geo-filtering and contacts option properly integrated.

The geolocation-based filtering and prepending of the peanut-contacts option is well-implemented. The useMemo correctly depends on geoFilteredMethods.


300-335: LGTM! Send options list properly implemented.

The send options list correctly renders each payment method with appropriate right-side content and functional onClick handlers. This resolves the previous issue where onClick handlers were empty.


100-125: Routes verified and parameters correctly handled.

All routes used in handleMethodClick exist and properly handle query parameters:

  • /withdraw page uses searchParams.get('method') to determine flow
  • /withdraw/manteca page uses both searchParams.get('method') and searchParams.get('country')
  • Country defaults to 'argentina' when not provided

No issues detected.

src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx (1)

53-63: Points query lost the intended cache-bypass; confirm behavior.

Previous behavior deliberately added crypto.randomUUID() to the queryKey to force fresh estimates every render. Current key omits it, which may serve stale estimates.

Based on learnings

If you still need fresh points per render, reintroduce the nonce:

-    queryKey: ['calculate-points', 'withdraw', bankAccount?.id, amountToWithdraw],
+    queryKey: ['calculate-points', 'withdraw', bankAccount?.id, amountToWithdraw, crypto.randomUUID()],

Alternatively, document and adjust cache policy (e.g., staleTime: 0, refetchOnMount: 'always') if the change was intentional.

src/components/AddWithdraw/AddWithdrawRouterView.tsx (1)

277-289: Nice: flow-aware gating via enforceSupportedCountries.

Passing enforceSupportedCountries={isBankFromSend} aligns the list with send->bank constraints.

Copy link
Contributor

@Zishan-7 Zishan-7 left a comment

Choose a reason for hiding this comment

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

lgtm

@kushagrasarathe kushagrasarathe merged commit f43f691 into peanut-wallet-dev Oct 27, 2025
5 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Dec 23, 2025
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