Skip to content

[TASK-14948] feat: Exchange rate redirect#1304

Merged
Zishan-7 merged 3 commits intopeanut-wallet-devfrom
feat/exchange-rate-widget-redirect
Oct 13, 2025
Merged

[TASK-14948] feat: Exchange rate redirect#1304
Zishan-7 merged 3 commits intopeanut-wallet-devfrom
feat/exchange-rate-widget-redirect

Conversation

@Zishan-7
Copy link
Contributor

@Zishan-7 Zishan-7 commented Oct 9, 2025

@vercel
Copy link

vercel bot commented Oct 9, 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 13, 2025 6:35pm

@notion-workspace
Copy link

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 9, 2025

Walkthrough

Adds currency- and balance-aware CTA routing to the exchange-rate page, changes ExchangeRateWidget’s ctaAction signature to accept source/destination currencies, seeds country search from currencyCode query param, forces all methods in Add/Withdraw when currencyCode is present, and adds path entries for Brazil and Argentina in country mappings.

Changes

Cohort / File(s) Summary
Exchange routing & widget CTA
src/app/(mobile-ui)/profile/exchange-rate/page.tsx, src/components/Global/ExchangeRateWidget/index.tsx
Page now computes CTA navigation based on source/destination currencies and wallet balance (uses countryCurrencyMappings, useWallet, printableUsdc); fetches balance on mount. ExchangeRateWidget's ctaAction signature changed from () => void to (sourceCurrency: string, destinationCurrency: string) => void and is invoked with current selections.
Add/Withdraw & country search query param
src/components/AddWithdraw/AddWithdrawRouterView.tsx, src/components/Common/CountryList.tsx
Both components read currencyCode from URL query params via useSearchParams. AddWithdrawRouterView makes shouldShowAllMethods conditional on currencyCode (forces show when present). CountryList seeds initial search term from currencyCode.
Country mappings update
src/constants/countryCurrencyMapping.ts
Adds path fields for Brazil (brazil) and Argentina (argentina) entries in countryCurrencyMappings; no other mapping changes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • Hugo0
  • jjramirezn

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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 title clearly and succinctly describes the primary feature introduced—an exchange rate redirect—without extraneous details or generic phrasing.
Description Check ✅ Passed The description provides a relevant link to a Discord discussion about the exchange rate redirect feature, establishing contextual relevance even though it lacks implementation details.
✨ 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/exchange-rate-widget-redirect

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

🧹 Nitpick comments (2)
src/components/AddWithdraw/AddWithdrawRouterView.tsx (1)

72-79: Consider refactoring the conditional override pattern.

The current approach of first computing shouldShowAllMethods via ternary (line 72) and then conditionally overriding it (lines 76-79) works but is fragile—this pattern recalculates and reassigns on every render. A cleaner approach would be to compute this value once using useMemo or incorporate the currencyCode check directly into a single conditional expression.

Example refactor using useMemo:

-    // determine if we should show the full list of methods (countries/crypto) instead of the default view
-    let shouldShowAllMethods = flow === 'withdraw' ? showAllWithdrawMethods : localShowAllMethods
-    const setShouldShowAllMethods = flow === 'withdraw' ? setShowAllWithdrawMethods : setLocalShowAllMethods
-    const [isLoadingPreferences, setIsLoadingPreferences] = useState(true)
-
-    // if currencyCode is present, show all methods
-    if (currencyCode) {
-        shouldShowAllMethods = true
-    }
+    // determine if we should show the full list of methods (countries/crypto) instead of the default view
+    const shouldShowAllMethods = useMemo(() => {
+        if (currencyCode) return true
+        return flow === 'withdraw' ? showAllWithdrawMethods : localShowAllMethods
+    }, [currencyCode, flow, showAllWithdrawMethods, localShowAllMethods])
+    const setShouldShowAllMethods = flow === 'withdraw' ? setShowAllWithdrawMethods : setLocalShowAllMethods
+    const [isLoadingPreferences, setIsLoadingPreferences] = useState(true)
src/app/(mobile-ui)/profile/exchange-rate/page.tsx (1)

16-55: Consider refactoring duplicate routing logic.

Cases 1 (lines 21-24) and 3 (lines 43-46) have identical logic: both route to /add-money with the source currency's country path. These could be consolidated into a single condition to reduce duplication and improve maintainability.

Example refactor:

     const handleCtaAction = (sourceCurrency: string, destinationCurrency: string) => {
         let route = '/add-money'
         let countryPath: string | undefined = ''
 
-        // Case 1: source currency is not usd and destination currency is usd -> redirect to add-money/sourceCurrencyCountry page
-        if (sourceCurrency !== 'USD' && destinationCurrency === 'USD') {
-            countryPath = countryCurrencyMappings.find((currency) => currency.currencyCode === sourceCurrency)?.path
-            route = '/add-money'
-        }
-
-        // Case 2: source currency is usd and destination currency is not usd -> redirect to withdraw/destinationCurrencyCountry page
-        if (sourceCurrency === 'USD' && destinationCurrency !== 'USD') {
+        // If source is USD and destination is not USD, check balance to determine flow
+        if (sourceCurrency === 'USD' && destinationCurrency !== 'USD') {
             const formattedBalance = printableUsdc(balance ?? 0n)
 
             // if there is no balance, redirect to add-money
             if (parseFloat(formattedBalance) <= 0) {
-                countryPath = countryCurrencyMappings.find((currency) => currency.currencyCode === sourceCurrency)?.path
+                countryPath = countryCurrencyMappings.find((currency) => currency.currencyCode === 'USD')?.path
                 route = '/add-money'
             } else {
                 countryPath = countryCurrencyMappings.find(
                     (currency) => currency.currencyCode === destinationCurrency
                 )?.path
                 route = '/withdraw'
             }
+        } else if (sourceCurrency !== 'USD') {
+            // For any non-USD source currency, route to add-money with source country
+            countryPath = countryCurrencyMappings.find((currency) => currency.currencyCode === sourceCurrency)?.path
+            route = '/add-money'
         }
-
-        // Case 3: source currency is not usd and destination currency is not usd -> redirect to add-money/sourceCurrencyCountry page
-        if (sourceCurrency !== 'USD' && destinationCurrency !== 'USD') {
-            countryPath = countryCurrencyMappings.find((currency) => currency.currencyCode === sourceCurrency)?.path
-            route = '/add-money'
-        }
 
         if (!countryPath) {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8e486c3 and cf461b2.

📒 Files selected for processing (5)
  • src/app/(mobile-ui)/profile/exchange-rate/page.tsx (1 hunks)
  • src/components/AddWithdraw/AddWithdrawRouterView.tsx (2 hunks)
  • src/components/Common/CountryList.tsx (2 hunks)
  • src/components/Global/ExchangeRateWidget/index.tsx (2 hunks)
  • src/constants/countryCurrencyMapping.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 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/components/AddWithdraw/AddWithdrawRouterView.tsx
📚 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/AddWithdrawRouterView.tsx
🧬 Code graph analysis (1)
src/app/(mobile-ui)/profile/exchange-rate/page.tsx (3)
src/hooks/wallet/useWallet.ts (1)
  • useWallet (14-98)
src/constants/countryCurrencyMapping.ts (1)
  • countryCurrencyMappings (10-48)
src/utils/balance.utils.ts (1)
  • printableUsdc (32-38)
⏰ 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 (5)
src/constants/countryCurrencyMapping.ts (1)

46-47: LGTM!

The addition of path properties for Brazil and Argentina follows the same pattern as other country entries and uses consistent lowercase naming. This enables routing functionality for these LATAM countries.

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

50-54: LGTM!

The URL query parameter handling for currencyCode enables deep-linking to a pre-filtered country list. The initial search term is correctly seeded from the URL parameter, allowing users to land on a page with the relevant currency options already filtered. The existing filtering logic (lines 87-88) will match against both country names and currency codes.

src/components/Global/ExchangeRateWidget/index.tsx (2)

14-14: LGTM!

The updated ctaAction signature correctly passes the selected currencies to the parent component, enabling currency-aware routing logic. This is a breaking change to the component's public API, but it's necessary for the desired functionality.


215-215: LGTM!

The button's onClick handler correctly invokes the ctaAction with the current sourceCurrency and destinationCurrency values.

src/app/(mobile-ui)/profile/exchange-rate/page.tsx (1)

57-60: LGTM!

The empty dependency array ensures fetchBalance is called only on component mount, which is appropriate for initializing the balance state in this flow. This pattern is consistent with similar patterns elsewhere in the codebase.

Based on learnings

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

♻️ Duplicate comments (1)
src/app/(mobile-ui)/profile/exchange-rate/page.tsx (1)

28-34: Balance check has been corrected.

The balance check now correctly uses <= 0 to detect zero or insufficient balance, addressing the previous critical issue where < 0 would never be true for unsigned bigint balances.

🧹 Nitpick comments (1)
src/app/(mobile-ui)/profile/exchange-rate/page.tsx (1)

57-60: Add fetchBalance to the dependency array.

The useEffect calls fetchBalance() but omits it from the dependency array. While fetchBalance is likely stable (wrapped in useCallback per the useWallet implementation), React best practices recommend including all dependencies to avoid subtle bugs and satisfy the exhaustive-deps lint rule.

     useEffect(() => {
         // Fetch latest balance
         fetchBalance()
-    }, [])
+    }, [fetchBalance])
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cf461b2 and 0af35cf.

📒 Files selected for processing (1)
  • src/app/(mobile-ui)/profile/exchange-rate/page.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-07T20:22:11.092Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#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/app/(mobile-ui)/profile/exchange-rate/page.tsx
🧬 Code graph analysis (1)
src/app/(mobile-ui)/profile/exchange-rate/page.tsx (3)
src/hooks/wallet/useWallet.ts (1)
  • useWallet (14-98)
src/constants/countryCurrencyMapping.ts (1)
  • countryCurrencyMappings (10-48)
src/utils/balance.utils.ts (1)
  • printableUsdc (32-38)
⏰ 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

Copy link
Contributor

@kushagrasarathe kushagrasarathe left a comment

Choose a reason for hiding this comment

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

lgtm, resolve cr comments, also codefreeze so do not merge

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

♻️ Duplicate comments (1)
src/app/(mobile-ui)/profile/exchange-rate/page.tsx (1)

28-34: Good fix: zero-balance check and USD→USD case are covered.

  • parseFloat(... ) <= 0 is correct for zero balance.
  • USD→USD routing added with balance-based branch.

Also applies to: 48-54

🧹 Nitpick comments (3)
src/app/(mobile-ui)/profile/exchange-rate/page.tsx (3)

16-63: Avoid hardcoded EUR fallback; use context-specific currencyCode.

If countryPath is missing (e.g., EUR or unmapped), defaulting to ?currencyCode=EUR ignores the user-selected currencies. Track a fallbackCurrencyCode per branch and use it in the query.

-    const handleCtaAction = (sourceCurrency: string, destinationCurrency: string) => {
-        let route = '/add-money'
-        let countryPath: string | undefined = ''
+    const handleCtaAction = (sourceCurrency: string, destinationCurrency: string) => {
+        let route = '/add-money'
+        let countryPath: string | undefined
+        let fallbackCurrencyCode: string | undefined
@@
-        if (sourceCurrency !== 'USD' && destinationCurrency === 'USD') {
+        if (sourceCurrency !== 'USD' && destinationCurrency === 'USD') {
             countryPath = countryCurrencyMappings.find((currency) => currency.currencyCode === sourceCurrency)?.path
             route = '/add-money'
+            fallbackCurrencyCode = sourceCurrency
         }
@@
-        if (sourceCurrency === 'USD' && destinationCurrency !== 'USD') {
-            const formattedBalance = printableUsdc(balance ?? 0n)
+        if (sourceCurrency === 'USD' && destinationCurrency !== 'USD') {
+            const formattedBalance = printableUsdc(balance ?? 0n)
             // if there is no balance, redirect to add-money
             if (parseFloat(formattedBalance) <= 0) {
                 countryPath = countryCurrencyMappings.find((currency) => currency.currencyCode === sourceCurrency)?.path
                 route = '/add-money'
+                fallbackCurrencyCode = sourceCurrency
             } else {
                 countryPath = countryCurrencyMappings.find(
                     (currency) => currency.currencyCode === destinationCurrency
                 )?.path
                 route = '/withdraw'
+                fallbackCurrencyCode = destinationCurrency
             }
         }
@@
-        if (sourceCurrency !== 'USD' && destinationCurrency !== 'USD') {
+        if (sourceCurrency !== 'USD' && destinationCurrency !== 'USD') {
             countryPath = countryCurrencyMappings.find((currency) => currency.currencyCode === sourceCurrency)?.path
             route = '/add-money'
+            fallbackCurrencyCode = sourceCurrency
         }
@@
-        if (sourceCurrency === 'USD' && destinationCurrency === 'USD') {
-            const formattedBalance = parseFloat(printableUsdc(balance ?? 0n))
+        if (sourceCurrency === 'USD' && destinationCurrency === 'USD') {
+            const formattedBalance = parseFloat(printableUsdc(balance ?? 0n))
             // Either redirect to a relevant page or show an error
             // For now, routing to USA add-money page as an example
             countryPath = countryCurrencyMappings.find((currency) => currency.currencyCode === 'USD')?.path
             route = formattedBalance <= 0 ? '/add-money' : '/withdraw'
+            fallbackCurrencyCode = 'USD'
         }
@@
-        if (!countryPath) {
-            const redirectRoute = `${route}?currencyCode=EUR`
+        if (!countryPath) {
+            const redirectRoute = `${route}?currencyCode=${fallbackCurrencyCode ?? 'EUR'}`
             router.push(redirectRoute)
         } else {
             const redirectRoute = `${route}/${countryPath}`
             router.push(redirectRoute)
         }

10-10: Remove redundant balance fetch (or add deps).

useWallet already fetches on address changes. The page-level effect can double-fire and violates exhaustive-deps.

Option A (remove):

-import { useEffect } from 'react'
@@
-    useEffect(() => {
-        // Fetch latest balance
-        fetchBalance()
-    }, [])
+    // Balance is fetched by useWallet; call fetchBalance on demand if needed.

Option B (keep effect, fix deps):

-    useEffect(() => {
-        fetchBalance()
-    }, [])
+    useEffect(() => {
+        fetchBalance()
+    }, [fetchBalance])

Also applies to: 65-68


16-47: Optional: normalize currency codes and simplify branching.

  • Normalize inputs to uppercase to harden against unexpected casing.
  • Use else-if or early returns to make the mutually-exclusive cases explicit.

Example:

  • const src = sourceCurrency.toUpperCase(); const dst = destinationCurrency.toUpperCase();
  • Chain as if (...) { ...; return } else if (...) { ...; return } ...
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0af35cf and 404172f.

📒 Files selected for processing (1)
  • src/app/(mobile-ui)/profile/exchange-rate/page.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-07T20:22:11.092Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#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/app/(mobile-ui)/profile/exchange-rate/page.tsx
🧬 Code graph analysis (1)
src/app/(mobile-ui)/profile/exchange-rate/page.tsx (3)
src/hooks/wallet/useWallet.ts (1)
  • useWallet (14-98)
src/constants/countryCurrencyMapping.ts (1)
  • countryCurrencyMappings (10-48)
src/utils/balance.utils.ts (1)
  • printableUsdc (32-38)
⏰ 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 (1)
src/app/(mobile-ui)/profile/exchange-rate/page.tsx (1)

8-8: No changes needed for printableUsdc import. The barrel at src/utils/index.ts re-exports printableUsdc, so import { printableUsdc } from '@/utils' is correct.

@Zishan-7 Zishan-7 merged commit ca9cb6e into peanut-wallet-dev Oct 13, 2025
5 checks passed
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