Skip to content

fix(fx): stop the add-money and withdraw screens freezing on a rate fetch - #2707

Open
innolope-dev wants to merge 1 commit into
devfrom
fix/fx-rate-failure-freeze
Open

fix(fx): stop the add-money and withdraw screens freezing on a rate fetch#2707
innolope-dev wants to merge 1 commit into
devfrom
fix/fx-rate-failure-freeze

Conversation

@innolope-dev

Copy link
Copy Markdown
Collaborator

Fixes #1848.

What was already handled

Worth stating up front so the diff reads in context — most of the issue's suggested fix already landed:

So the remaining freeze was not a missing timeout.

The actual freeze

useCurrency seeded code from the prop on the first render and then ignored the prop for the rest of its life — there was no sync effect. Both callers derive the currency from useSearchParams():

  • withdraw/manteca/page.tsxuseCurrency(selectedCountry?.currency ?? null)
  • MantecaAddMoney.tsxuseCurrency(selectedCountry?.currency ?? 'ARS')

On the first render of a statically exported page the search params are empty, so the withdraw page passed null, the hook latched it, never fetched, and price stayed null forever. Its gate is if (isCurrencyLoading || !currencyPrice || …) return <PeanutLoading /> — an unresolvable loader with no header and no way out. That's a deterministic hang rather than a slow network, which fits "reproducible on G31 and G23" better than a timeout would.

The same latch also meant add-money would price a Brazil flow in ARS (its fallback) whenever the country arrived a render late.

Changes

useCurrency

  • Sync code when the prop changes, tracking the previous prop so the unsupported-currency branch (which deliberately nulls code) isn't clobbered on the next render.
  • Report the gap between the prop landing and the fetch starting as isLoading, so consumers can't read it as a settled "no rate" and flash an error.
  • Add refetch(). Previously the effect only re-ran on a currency change, so a rate outage stranded the user until they left the screen entirely.
  • Ignore a superseded in-flight response so a slow request can't overwrite a newer currency's rate.

Failure states — new shared RateUnavailable (alert + retry), wired into all three rate-dependent flows:

  • InputAmountStep: retry button instead of a bare alert; header stays mounted while loading so back always works.
  • withdraw/manteca: had no error branch at all — a failed fetch fell into the same infinite loader. Now an error screen with back and retry.
  • MantecaReviewStep: rendered 1 USD = undefined ARS; now shows the retry state.

i18n — moved rateUnavailable from addMoney.errors to the shared errors namespace across all four catalogs rather than duplicating the string.

Verification

  • Full Jest suite: 238 suites / 3068 passing, including 6 new useCurrency tests (late-arriving currency, refetch recovery, out-of-order response) and 2 new InputAmountStep tests.
  • next build succeeds; 1089 static pages generated.
  • tsc, eslint, and prettier clean on the touched files.

Not covered

No page-level test for withdraw/manteca — it has no existing suite and would need a large mock surface. The hook test covers the root cause; the page's new branch is not directly exercised.

…etch

useCurrency seeded its currency code on the first render and then ignored
the prop for the rest of its life. Both callers derive the currency from
useSearchParams(), which is empty on the first render of a statically
exported page, so the hook latched null, never fetched, and left the
withdraw screen on a loader that could never resolve.

Sync the code from the prop, and report the gap before the fetch starts as
loading so consumers don't read it as a settled "no rate" and flash an
error. A superseded in-flight request can no longer overwrite the current
currency's rate either.

The failure states were dead ends on top of that: the hook only refetched
when the currency changed, so a rate outage stranded the user until they
left the screen. Add refetch() and surface it through a shared
RateUnavailable component in all three rate-dependent flows. withdraw had
no error branch at all (a failed fetch fell into the same infinite loader),
and the claim review step rendered "1 USD = undefined".

Keep the header mounted while add-money loads so back always works.

Fixes #1848
@innolope-dev innolope-dev self-assigned this Aug 17, 2026
@vercel

vercel Bot commented Aug 17, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
peanut-wallet Ready Ready Preview Aug 17, 2026 7:34pm

Request Review

@coderabbitai

coderabbitai Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e5d03165-6323-4c8d-9e52-fd06ac25662f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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

@github-actions

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 7159.84 → 7161.12 (+1.28)
Findings: +3 net (+23 new, -20 resolved)

🆕 New findings (23)

  • critical complexity — src/app/(mobile-ui)/withdraw/manteca/page.tsx — CC 162, MI 51.41, SLOC 601
  • high hotspot — src/app/(mobile-ui)/withdraw/manteca/page.tsx — 66 commits, +638/-378 lines since 6 months ago
  • high method-complexity — src/app/(mobile-ui)/withdraw/manteca/page.tsx:101 — MantecaBankWithdrawFlow CC 49 SLOC 252
  • high complexity — src/components/Claim/Link/views/MantecaReviewStep.tsx — CC 16, MI 48.58, SLOC 108
  • medium high-mdd — src/app/(mobile-ui)/withdraw/manteca/page.tsx:101 — MantecaBankWithdrawFlow: MDD 310.5 (uses across many lines from declarations)
  • medium high-dlt — src/app/(mobile-ui)/withdraw/manteca/page.tsx:101 — MantecaBankWithdrawFlow: DLT 101 (calls 101 distinct functions — high context load)
  • medium high-mdd — src/app/(mobile-ui)/withdraw/manteca/page.tsx:363 — handleWithdraw: MDD 46.8 (uses across many lines from declarations)
  • medium high-mdd — src/components/AddMoney/components/InputAmountStep.tsx:39 — InputAmountStep: MDD 44.2 (uses across many lines from declarations)
  • medium high-mdd — src/components/Claim/Link/views/MantecaReviewStep.tsx:24 — MantecaReviewStep: MDD 33.1 (uses across many lines from declarations)
  • medium structural-dup — app/(mobile-ui)/withdraw/manteca/page.tsx:669 — 30 duplicate lines / 146 tokens with components/AddMoney/components/MantecaAddMoney.tsx:275
  • medium structural-dup — app/(mobile-ui)/withdraw/manteca/page.tsx:795 — 21 duplicate lines / 132 tokens with app/(mobile-ui)/withdraw/manteca/page.tsx:906
  • medium method-complexity — src/app/(mobile-ui)/withdraw/manteca/page.tsx:363 — CC 19 SLOC 105
  • medium complexity — src/components/AddMoney/components/InputAmountStep.tsx — CC 17, MI 59.82, SLOC 30
  • medium complexity — src/hooks/useCurrency.ts — CC 16, MI 60.96, SLOC 92
  • medium method-complexity — src/components/AddMoney/components/InputAmountStep.tsx:39 — CC 15 SLOC 26
  • low high-dlt — src/app/(mobile-ui)/withdraw/manteca/page.tsx:363 — handleWithdraw: DLT 18 (calls 18 distinct functions — high context load)
  • low high-mdd — src/hooks/useCurrency.ts:21 — useCurrency: MDD 17.8 (uses across many lines from declarations)
  • low high-dlt — src/components/Claim/Link/views/MantecaReviewStep.tsx:24 — MantecaReviewStep: DLT 17 (calls 17 distinct functions — high context load)
  • low high-dlt — src/hooks/useCurrency.ts:21 — useCurrency: DLT 17 (calls 17 distinct functions — high context load)
  • low high-mdd — src/components/Claim/Link/views/MantecaReviewStep.tsx:58 — handleWithdraw: MDD 10.8 (uses across many lines from declarations)

…and 3 more.

✅ Resolved (20)

  • src/app/(mobile-ui)/withdraw/manteca/page.tsx — CC 158, MI 51.52, SLOC 597
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx — 65 commits, +622/-378 lines since 6 months ago
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx:100 — MantecaBankWithdrawFlow CC 45 SLOC 248
  • src/components/Claim/Link/views/MantecaReviewStep.tsx — CC 15, MI 48.93, SLOC 105
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx:100 — MantecaBankWithdrawFlow: MDD 303.4 (uses across many lines from declarations)
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx:100 — MantecaBankWithdrawFlow: DLT 101 (calls 101 distinct functions — high context load)
  • src/components/AddMoney/components/InputAmountStep.tsx:38 — InputAmountStep: MDD 47.9 (uses across many lines from declarations)
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx:361 — handleWithdraw: MDD 46.8 (uses across many lines from declarations)
  • src/components/Claim/Link/views/MantecaReviewStep.tsx:23 — MantecaReviewStep: MDD 32.1 (uses across many lines from declarations)
  • app/(mobile-ui)/withdraw/manteca/page.tsx:653 — 30 duplicate lines / 146 tokens with components/AddMoney/components/MantecaAddMoney.tsx:275
  • app/(mobile-ui)/withdraw/manteca/page.tsx:779 — 21 duplicate lines / 132 tokens with app/(mobile-ui)/withdraw/manteca/page.tsx:890
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx:361 — CC 19 SLOC 105
  • src/components/AddMoney/components/InputAmountStep.tsx — CC 16, MI 55.73, SLOC 28
  • src/components/AddMoney/components/InputAmountStep.tsx:38 — CC 15 SLOC 25
  • src/hooks/useCurrency.ts — CC 8, MI 59.29, SLOC 64
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx:361 — handleWithdraw: DLT 18 (calls 18 distinct functions — high context load)
  • src/components/Claim/Link/views/MantecaReviewStep.tsx:23 — MantecaReviewStep: DLT 17 (calls 17 distinct functions — high context load)
  • src/components/Claim/Link/views/MantecaReviewStep.tsx:57 — handleWithdraw: MDD 10.8 (uses across many lines from declarations)
  • app/(mobile-ui)/qr-pay/page.tsx:696 — 9 duplicate lines / 52 tokens with app/(mobile-ui)/withdraw/manteca/page.tsx:401
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx:78 — MantecaWithdrawFlow: exported fn missing return type annotation

📈 Painscore deltas (top movers)

File Before After Δ
src/components/Global/RateUnavailable/index.tsx 0.0 4.6 +4.6
src/hooks/useCurrency.ts 8.7 8.2 -0.6
src/components/AddMoney/components/InputAmountStep.tsx 12.1 8.9 -3.2

@github-actions

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 3071 ran, 0 failed, 0 skipped, 55.5s

📊 Coverage (unit)

metric %
statements 66.9%
branches 52.0%
functions 57.3%
lines 67.8%
⏱ 10 slowest test cases
time test
3.7s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
1.2s src/utils/__tests__/demo-api.test.ts › isDemoMode() is false when not running under Capacitor
0.5s src/app/actions/__tests__/api-headers.test.ts › should include Content-Type in validateInviteCode
0.4s src/utils/__tests__/auth-token.test.ts › is none — never guarded — when only the guarded marker is present
0.4s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › every sticker stays within canvas at any count
0.3s src/utils/__tests__/sentry.utils.test.ts › defaults to the client budget under a browser global
0.3s src/app/(mobile-ui)/withdraw/__tests__/withdraw-states.test.tsx › Bank withdrawal keeps the $1 minimum for sub-$1 amounts
0.3s src/utils/__tests__/auth-token.test.ts › authReady does not park — hydrates the plain token without an unlock
0.3s src/utils/__tests__/auth-token.test.ts › ignores the guarded marker and falls back to the plain token
0.3s src/utils/__tests__/sentry.utils.test.ts › still lets a per-call timeoutMs win over the default
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant