fix(withdraw): UK GBP + GB-IBAN-EUR + intra-SEPA bank withdrawals (PR B — frontend)#2263
Conversation
Bumps src/content f204f50..5cbbe4f (peanut-content main). Headline change:
scrub the Argentina cardless cash-withdrawal ("Red ATM") marketing across
all 5 locales — it was promoted as a live feature but is being pulled. The
RedATM withdrawal flow in the app is untouched; this is marketing copy only.
Competitor/pain-point ATM copy is intentionally preserved (foreign-ATM fee
comparisons, Western Union, Revolut/BLIK).
Content-only: zero code paths touched. Supersedes the handrolled #2226 and
the stale auto/update-content PRs, which were based off main and showed
phantom code diffs against dev.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rogress bar)
Two cosmetic-but-confusing display bugs in the bill-split / request-pot UI,
both reported by Konrad:
1. Slider "snap to 50% → 49.98%": the slider snaps the thumb to a clean
percentage on drag, but AmountInput re-derives the thumb position from a
cent-floored amount ($33.37 pot → 50% = $16.685 → floored $16.68 → 49.98%),
and the Slider's sync effect then clobbered the snapped value. The amount is
correct (you can't pay half a cent); only the label drifted. The sync effect
now keeps the thumb on a snap point through sub-cent drift.
2. Request-pot progress bar blank for pots that collected >= $1,000: the receipt
passed the comma-grouped display string to Number() (Number("1,234.56") ===
NaN), blanking the bar. Use the raw numeric totalAmountCollected, matching
how `goal` already uses transaction.amount.
Display-only; no amounts or money paths change. Complements api-ts #1027 (the
send-link $0 phantom) as part of the same P2P display-correctness sweep.
Card payments couldn't be split. The "Split this bill" CTA + /request prefill +
auto-create already ship for QR pays (gated on isQRPayment); this extends the
gate to card spends so a Peanut-card purchase can seed a bill-split request
(amount = the spend's USD value, comment = "Bill split for {merchant}").
- isCardSpend predicate: CARD_SPEND_AUTH/CLEAR only — refunds + auth reversals
excluded (you didn't pay those).
- Threaded through useReceiptViewModel; CTA gate is now (isQRPayment || isCardSpend)
and also excludes failed/declined spends.
- Omit the merchant param when userName is the "Card payment" fallback so the
comment isn't "Bill split for Card payment".
FE-only — reuses the existing /request creation + POST /requests.
…eRabbit) A merchant name with reserved characters (e.g. "Tigers & Lions") would break the `/request?amount=…&merchant=…` query string and corrupt the request prefill. encodeURIComponent the merchant value. Addresses CodeRabbit's inline finding on #2235.
fix(p2p): request-pot display bugs — slider 49.98% snap + $1k+ progress bar NaN
normalizePixPhoneNumber returned the raw input untouched when the cleaned value already started with '+', so '+55-11-99999-9999' kept its separators and stayed non-canonical — conflicting with the helper's contract and risking PIX-key mismatches. Always return the separator-free +55 form. From release-PR #2236 CodeRabbit triage; adds the separator-with-+ regression cases the existing +-prefix tests never exercised.
…zation fix(pix): canonicalize +55 phone keys that already carry a + prefix
…uilder The CTA's onClick inlined the /request URL build, including the two bug-prone bits — the "Card payment" merchant fallback and the URL-encode CodeRabbit caught on #2235. Extract to buildSplitBillRequestUrl and lock it with unit tests (encode reserved chars, omit fallback, omit empty merchant). Pure refactor + tests; no behavior change.
Fast-forward the Red ATM scrub bump from the intermediate 5cbbe4f to the current peanut-content main tip 358b6a6, so dev matches mono-main content (and converges with the main-targeted prod bump #2242 — keeps the next dev->main release from re-reverting the Red ATM removal). Content-only, no code paths touched.
Symmetric with the merchant encode — the param type allows string, so encode
amount defensively. No behavior change for the numeric amounts callers pass
(encodeURIComponent("12.5") === "12.5"); existing assertions unchanged.
test(split-bill): lock the Split-this-bill CTA URL builder
Mirrors the main-targeted companions (#2242). The 358b6a6 content bump removes the avalanche deposit + withdraw marketing pages (avalanche is advertised in SEO + DEPOSIT_RAILS but never wired into rhino.consts — a false claim). Sync the code so the content gate passes: - DEPOSIT_RAILS / verify-content RAIL_SLUGS: drop avalanche so the SEO route + sitemap stop emitting /deposit/via-avalanche - redirects.json: 301 old via-avalanche + withdraw/avalanche -> /supported-networks - .verify-content-baseline: 745 -> 740 ETH untouched (we support it). Deeper deposit-flow avalanche/scroll cleanup stays with #2216 (overlaps these files -> trivial rebase).
The rail list lived in two places — DEPOSIT_RAILS (exchanges.ts) and a hand-copied RAIL_SLUGS Set in scripts/verify-content.ts, guarded only by a 'keep in sync' comment. Removing avalanche meant editing both; the lists had already drifted before. Extract to src/data/seo/deposit-rails.ts — an import-free module both the app (routes + sitemap via exchanges.ts) and the standalone content gate (verify-content.ts) import. Add/remove a rail in one place now. No behaviour change: same 9 rails, same classification. (Registering faster-payments + spei as rails is a follow-up — it's a content-input regeneration job, not a code add: the from-→via- flip breaks generated RelatedLinks that must be fixed at the mono input layer and re-mirrored.)
Missing `name` falls back to title-casing; missing `recommended_network` falls back to 'arbitrum' (pickRecommendedNetwork), not title-casing. Per CodeRabbit review.
…-atm chore(content): bump src/content — remove Red ATM from marketing
Faster Payments (UK/GBP) and SPEI (MX/MXN) are live deposit rails but were
served as /deposit/from-{slug} (exchange framing) — inconsistent with
SEPA/ACH/wire. Register both in DEPOSIT_RAILS so the route + sitemap emit
/deposit/via-{slug}, bump src/content to the matching content fix (the 14
RelatedLinks now point to via-), and 301 the old from- URLs to via- for any
indexed/shared links.
Content side: peanut-content 358b6a6 -> 5eb8453 (mono@8a061d5) — flips the
links + adds FP/SPEI to the fiat-rail list in intent-taxonomy.md so
regeneration keeps them as rails. Verified: content gate green, typecheck +
src/data tests pass.
# Conflicts: # src/content
llms.txt was too short and undersold the product. Rewrite (GA-informed: Argentina + Brazil dominate; mercadopago + pix are the top public pages) to lead with the differentiators: pay PIX in Brazil with no CPF, pay MercadoPago in Argentina with no DNI, the Peanut Card, then send/receive/deposit/withdraw, how-it-works, and where-it-works (live vs roadmap). Also fixes stale facts in llms-full.txt: smart accounts are on Arbitrum (not 'Base/Ethereum L2'); the card is virtual Visa only (not physical). No backend provider names; 'ATM cash withdrawal' omitted (Red ATM was just killed from marketing).
…allowlist The card moved from a CARD_PIONEER allowlist (US+18 LATAM+10 African) to a denylist: eligible everywhere except the issuer's ~17 prohibited-issuance countries (China, India, Russia, Turkey, Vietnam, Iran, Israel, ...). Source of truth: peanut-api-ts/src/card/geo-eligibility.ts. The earlier llms copy came from product/card.md which is stale post-rehaul (flagged for update).
feat(seo): register Faster Payments + SPEI as deposit rails (via-, not from-)
…ale Avalanche FAQ Carries forward the still-needed deposit-flow half of #2216 (the avalanche *marketing* half already shipped via #2232): - rhino.consts: remove SCROLL from SUPPORTED_EVM_CHAINS — Rhino's live config (getSupportedConfigs) no longer returns an SDA for it (audit 2026-06-11), and a deposit on an unsupported chain is silently lost. The chainId map keeps 534352->SCROLL so any stray deposit stays identifiable for recovery. - TokenAndNetworkConfirmationModal: compute the '+N EVM' overflow from the list length (was hardcoded '+4') so it stays correct, and drop the now-unused RHINO_SUPPORTED_CHAINS import. - merchants.ts: drop Avalanche from the Argentina funding FAQ (false claim). Supersedes #2216.
…removal fix(deposit): stop offering Scroll deposits (silently lost) + drop stale Avalanche FAQ
Claim-link previews fell back to the generic Peanut image instead of "X sent you $Y via Peanut". The claim page's generateMetadata + force-dynamic were stripped for the native static-export build (d204944), but scripts/native-build.js already strips them at build time and restores the source afterwards — so the source-level removal was redundant and silently degraded only the web build. Restore generateMetadata, extracted into a unit-tested helper (buildClaimMetadata) so the title/OG-image logic can't silently regress again. Native build is unaffected: its P0_TRANSFORMS replaces this page by path.
…y assertion - import Claim from '@/components/Claim' (specific file) instead of the '@/components' barrel (eslint no-restricted-imports) - clear the ENS resolution timeout after Promise.race settles so no timer is left pending per request (CodeRabbit) - assert sendLinksApi.getByParams is never called on the early-return path (CodeRabbit)
fix(claim): restore dynamic social-preview metadata for claim links
The /api/og query-param contract (type/username/amount/token/isReceipt/ isPeanutUsername/isInvite) was hand-built in four places — claim, the [...recipient] payment/profile page, receipts, and invites — so a renamed or added param meant editing N sites with nothing catching a miss. Extract one typed, unit-tested buildOgImageUrl() and route all four through it. Claim + [...recipient] URLs are byte-identical to before; receipt + invite produce the same params (order differs, semantically identical). This also gives the previously-untested catch-all OG logic coverage via the shared helper's tests. Follow-up to #2249.
refactor(og): single buildOgImageUrl() helper for /api/og links
Carries the Sumsub es-es correction into dev so the release ships fully-current content. Clean dev-based bump (the auto-PR #2247 branched off main and couldn't merge — that workflow bug is being fixed separately).
The content auto-bump opened PRs but never merged them — 9 piled up since June 3, and they were branched off the default branch (main), which (a) let code drift pollute them (#2226) and (b) made them conflict with dev once dev's content moved (#2247). Fix: - Branch the bump off **dev** (the PR base) so the PR is content-only by construction and always cleanly mergeable. - Auto-merge the PR, guarded so it only fires when the diff is *solely* the src/content pointer (anything else → left for human review). - Create the PR with CONTENT_BOT_TOKEN (PAT) when present so CI triggers and auto-merge can gate on green checks; falls back to GITHUB_TOKEN (no regression). Requires (repo admin, one-time): set CONTENT_BOT_TOKEN secret (PAT w/ peanut-ui contents+PR write) + enable 'Allow auto-merge'. Takes effect once this reaches main (repository_dispatch runs the workflow from the default branch).
…erge ci(content): base auto-bump on dev + content-only auto-merge (stop the pile-up)
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/utils/__tests__/withdraw.utils.test.ts (1)
35-40: ⚡ Quick winStrengthen IBAN-prefix assertions to truly lock the regression.
Line 35-40 currently checks only
not.toBeNull(), which can still pass if prefixes map to the wrong country. Add a prefix-distinction assertion (e.g., GB result differs from DE result) so the test actually verifies derivation behavior.Suggested test tightening
- it('resolves the country from the IBAN prefix (GB/DE) and is whitespace-tolerant', () => { - // GB IBAN (Revolut-style) — the GB-IBAN-EUR case (Ibrahima). - expect(getCountryFromIban('GB33BUKB20201555555555')).not.toBeNull() - // DE IBAN with spaces — the intra-SEPA mismatch case. - expect(getCountryFromIban('DE89 3704 0044 0532 0130 00')).not.toBeNull() - }) + it('resolves country from IBAN prefix (GB/DE) and is whitespace-tolerant', () => { + const gb = getCountryFromIban('GB33BUKB20201555555555') + const de = getCountryFromIban('DE89 3704 0044 0532 0130 00') + expect(gb).not.toBeNull() + expect(de).not.toBeNull() + expect(gb).not.toBe(de) + })🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/utils/__tests__/withdraw.utils.test.ts` around lines 35 - 40, The test assertions in the IBAN prefix test only verify that getCountryFromIban returns non-null values for both GB and DE IBANs, but do not actually validate that the correct country codes are returned for each prefix. Replace the not.toBeNull() assertions with explicit equality checks that verify getCountryFromIban('GB33BUKB20201555555555') returns 'GB' and getCountryFromIban('DE89 3704 0044 0532 0130 00') returns 'DE', ensuring the test truly validates that different prefixes are correctly distinguished rather than just confirming the function doesn't fail.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/AddWithdraw/DynamicBankAccountForm.tsx`:
- Around line 229-233: The country derivation logic at lines 229-233 uses
cleanedAccountNumber as the IBAN source, but the actual IBAN input can come from
data.iban or getValues('iban') at line 265, causing a mismatch. Ensure a single
normalized IBAN source is used consistently throughout the component when
deriving ibanCountryCode, ibanCountryName, and resolvedCountryCode. Determine
whether to use cleanedAccountNumber or the form value
(data.iban/getValues('iban')) as the authoritative IBAN source, and apply it
uniformly in all country derivation operations to prevent resolvedCountryCode
from becoming empty or incorrect.
---
Nitpick comments:
In `@src/utils/__tests__/withdraw.utils.test.ts`:
- Around line 35-40: The test assertions in the IBAN prefix test only verify
that getCountryFromIban returns non-null values for both GB and DE IBANs, but do
not actually validate that the correct country codes are returned for each
prefix. Replace the not.toBeNull() assertions with explicit equality checks that
verify getCountryFromIban('GB33BUKB20201555555555') returns 'GB' and
getCountryFromIban('DE89 3704 0044 0532 0130 00') returns 'DE', ensuring the
test truly validates that different prefixes are correctly distinguished rather
than just confirming the function doesn't fail.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 7f770d9c-6dea-4fc8-8f51-8107eaa23ba5
📒 Files selected for processing (2)
src/components/AddWithdraw/DynamicBankAccountForm.tsxsrc/utils/__tests__/withdraw.utils.test.ts
|
Thanks @coderabbitai — both addressed in 14188f5:
|
|
Tip For best results, initiate chat on the files or code changes.
|
Advisory Complete now called handleStartAction (start-action), whose
submission never round-trips to Bridge. Switch both add-money and withdraw
bank pages to handleSelfHealResubmit('BRIDGE', advisory.requirementKey),
the path whose webhook completion relays answers to Bridge. Thread an
optional requirementKey through initiateSelfHealResubmission and
handleSelfHealResubmit to target the future-dated advisory requirement.
handleStartAction/startKycAction are now unused (kept for a focused
follow-up cleanup to avoid the isActionFlow cascade in this diff).
…bbit) onCompleteNow now fires a real network call (self-heal resubmit), so rapid clicks before isLoading disables the CTA could launch duplicate requests. Add a completingRef in-flight guard.
…copy fix(kyc): correct DUPLICATE_EMAIL copy — sign in / contact support
…empt feat(kyc): skippable advisory pre-empt at add/withdraw (Bridge future-dated requirements)
The registered-name line added height to the bottom-pinned (mt-auto) value block, pushing the PAN up into the hand artwork. Removing the two-line Expiry/CVV labels (revealed + loading skeleton) reclaims that height; flexbox shifts PAN + name back down. Values and ph-no-capture PII guards untouched.
Addresses CodeRabbit nit. The name is PII; asserting only the text would let a future refactor move it outside ph-no-capture without failing a test. Lock the class so the session-recording guard is regression-proof.
feat(card): show registered cardholder name on reveal
…into-dev-20260623
…o-dev-20260623 chore: back-merge main → dev (pre Sprint 148 release)
2026-06-02 21:24 UTC, PEANUT-API-5P/5M/5N. A user with a UK GBP bank
account tried to offramp USDC → EUR via SEPA. Bridge rejected with
"country is not supported for SEPA" — SEPA only credits EUR-denominated
Eurozone accounts, not GBP/UK.
Root cause was in BankFlowManager.handleCreateOfframpAndClaim:
const destination = getOfframpCurrencyConfig(
account.country ?? selectedCountry!.id
)
When `account.country` is missing, the picker falls back to
`selectedCountry`. If `selectedCountry` doesn't match the saved-account's
actual type (e.g. user picks a random country, or country resolution is
buggy), the helper's "everything unknown → EUR/SEPA" default fires —
even for a clearly GBP/UK account.
The account's own `type` already carries the right answer for every
Bridge destination we support (us/gb/clabe/iban). Adding a tiny helper
`getOfframpConfigFromAccount` that derives currency+rail directly from
account.type closes this class of bug.
- `gb` → gbp/faster_payments
- `us` → usd/ach
- `clabe` → mxn/spei
- `iban` → eur/sepa
- `manteca` → throws (must use the Manteca offramp path, not Bridge)
- type missing → falls back to country-based picking (prior behavior)
The helper also accepts BE Prisma-shape suffixes (`BANK_IBAN`,
`BANK_ACCOUNT_GB`, etc.) so it doesn't matter which side of the API the
account row came from.
7 new unit tests in bridge.utils.test.ts cover:
- The GB regression (was EUR/SEPA, now GBP/faster_payments)
- All 4 supported account types map to the right rail
- BE Prisma-suffix shapes (BANK_IBAN, BANK_ACCOUNT_GB)
- Manteca throws to surface a wrong-path call early
- Missing-type fallback to country picking still works
All 71 bridge.utils tests passing.
Companion PR: peanut-api-ts #964 — surfaces Bridge's actual error
message in Sentry + stops Discord-paging on user-input 4xx errors.
Even after this FE fix, other user-input mistakes (e.g. a Bridge
customer status change mid-flow) shouldn't page on-call.
The bank-details form rejected any IBAN whose country didn't equal the
country selected on the previous screen ("IBAN does not match the selected
country"). SEPA routes by IBAN, so the dropdown is cosmetic for a EUR payout —
this gate blocked two legit cases: a German IBAN with Spain selected, and a UK
user withdrawing EUR to a GB IBAN (Ibrahima / bobbyfresco).
Gate on actual support instead: validateBankAccount() hits the BE
allowedCountries check (SEPA/US/CA), so structurally-valid-but-unsupported
IBANs are still rejected — just with an honest "not supported" message rather
than a false country-mismatch. Drops the now-orphaned getCountryFromIban import.
…ropdown Follow-on to dropping the IBAN-country gate: the add-bank-account payload set countryCode/countryName/address.country from the country picked on the previous screen. With the gate gone, a German IBAN entered under "Spain" would reach Bridge with countryCode=ESP and 400. SEPA routes by IBAN, so derive all three country fields from the IBAN itself for IBAN accounts — keeping the payload internally consistent exactly as the old equality gate guaranteed, just sourced from the IBAN. Unblocks the GB-IBAN-EUR case (GB IBAN -> GBR) too. Adds unit coverage for getCountryCodeForWithdraw (GB->GBR/DE->DEU/ES->ESP/US->USA, idempotent on ISO-3) and getCountryFromIban, the derivation the fix relies on.
…IBAN source CodeRabbit (2 major): - BankFlowManager: the bankDetails objects (new-account, saved-account, guest) dropped `type`, so getOfframpConfigFromAccount() fell back to country routing — defeating the derive-from-type fix. Carry `type` through all three (guest path derives it from the response shape: iban/clabe/sort_code→gb/account→us). - DynamicBankAccountForm: derive the IBAN country from a single normalized source (cleanedAccountNumber, falling back to the `iban` form value) so countryCode is never derived off an empty string → no empty countryCode to Bridge.
T1.4: destinationDetails derived the currency/rail from a country switch whose default returned an empty rail, so a UK account typed anything but GB (pre-BANK_GB mistype, or a Prisma-shaped 'BANK_GB') dead-ended on 'External account ID is missing.'. Derive from the account via getOfframpConfigFromAccount (GB->GBP), consistent with the claim flow. T1.2: harden the freshly-added-account fallback — if the add-bank-account response lacks bridgeAccountId, surface a retryable error instead of navigating to a confirm screen that dead-ends on 'Bank account is missing.'.
92f078b to
48e749d
Compare
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (1)
src/components/Card/CardFace.tsx (1)
141-150: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winPreserve semantic labels for expiry/CVV with
sr-onlytext.Dropping visual labels is okay, but adding hidden labels keeps screen-reader context intact without changing layout.
Suggested patch
- <div className="ph-no-capture font-bold"> + <div className="ph-no-capture font-bold"> + <span className="sr-only">Expiry </span> {String(revealed.expiryMonth).padStart(2, '0')}/ {String(revealed.expiryYear).slice(-2)} </div> @@ - <div className="ph-no-capture font-bold">{revealed.cvv}</div> + <div className="ph-no-capture font-bold"> + <span className="sr-only">CVV </span> + {revealed.cvv} + </div>Also applies to: 183-189
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/Card/CardFace.tsx` around lines 141 - 150, The expiry and CVV fields in CardFace should keep accessible context while remaining visually label-free; update the rendered expiry/CVV sections to include hidden sr-only text instead of removing the labels entirely. Use the existing CardFace markup around the expiry block and the CVV block so screen readers still announce meaningful labels without affecting layout, and apply the same fix to both occurrences referenced in the component.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/update-content.yml:
- Around line 21-23: The checkout step in update-content workflow still uses a
mutable actions/checkout version and persists the default token. Update the
actions/checkout reference in the workflow to a pinned audited commit SHA, and
add persist-credentials: false so the checkout step does not leave write-capable
credentials behind. Keep the change focused on the existing checkout block that
currently sets ref: dev.
In `@src/app/`(mobile-ui)/add-money/[country]/bank/page.tsx:
- Around line 121-129: The bank flow replays a deferred advisory confirmation
without re-validating the current gate state, so a stale ready+advisory snapshot
can still reach createOnramp() after the gate changes. Update
handleWarningConfirm() (and the callback path coming from useAdvisoryPreempt) to
re-check gate before proceeding, and if the gate is no longer ready, route back
through the warning/modal state instead of calling createOnramp(). Use the same
defensive pattern as the withdraw flow’s proceedWithOfframp() so the replayed
callback cannot bypass newly blocking KYC/ToS conditions.
In `@src/app/`(mobile-ui)/home/page.tsx:
- Around line 97-101: Gate the celebration modal in the home page effect on the
user record being loaded: in the useEffect around setShowKycModal, do not treat
user === undefined as “not celebrated yet” while fetchUser() is pending. Update
the logic in home/page.tsx so it only opens the modal when user is present and
isKycApproved is true and activationCelebratedAt is still empty, and also derive
the modal state from that condition on each run instead of only calling
setShowKycModal(true) so it can stay closed once activationCelebratedAt arrives.
In `@src/app/`[...recipient]/page.tsx:
- Around line 103-115: The receipt OG image generation in the page component is
using recipient even after chargeDetails has already resolved the correct
username for paid charges. Update the buildOgImageUrl call in the page route to
pass the resolved username variable for receipt cases (especially when chargeId
and isPaid are set), while preserving recipient for non-receipt request cards.
Use the existing username resolution logic in the same component to locate the
right value.
In `@src/app/receipt/`[entryId]/page.tsx:
- Around line 143-158: The OG preview label is hard-coded to USDC in the receipt
page while the displayed amount is derived from the transaction data, so the
asset name can drift from what generateReceiptTitle() uses. Update the
buildOgImageUrl call in the receipt page to pass the same currency/token label
source used by the title logic (for example the transaction’s currency.code or
tokenSymbol), and only fall back to USDC when that source is unavailable.
In `@src/components/TransactionDetails/TransactionDetailsReceipt.tsx`:
- Around line 321-324: The progress value in TransactionDetailsReceipt should
preserve the zero fallback for empty request pots instead of converting the raw
field directly. Update the progress calculation near the request-pot UI to use
the existing normalized total amount value or otherwise default missing
totalAmountCollected to 0 before calling Number, so brand-new pots do not
produce NaN and blank the progress bar.
In `@src/hooks/useAdvisoryPreempt.ts`:
- Around line 43-53: Keep the advisory retryable if onCompleteNow() fails by
deferring the dismissal and cleanup in completeNow until after the await
succeeds, or restoring visible/dismissed state in the catch path. Update the
useCallback in useAdvisoryPreempt so setDismissed, setVisible, and
pendingProceed.current = null only happen after a successful onCompleteNow()
call, ensuring a failed network request doesn’t permanently skip the advisory
for the session.
In `@src/hooks/useSumsubKycFlow.ts`:
- Around line 410-420: Persist the targeted action context used by
handleSelfHealResubmit so refreshToken() can reuse it after expiry. Store the
incoming requirementKey (and any action key derived in the same flow) in a ref
or state alongside selfHealProviderRef, then update refreshToken() to pass that
saved context into initiateSelfHealResubmission instead of always falling back
to the generic provider-only call or initiateSumsubKyc. Make sure the Sumsub
start flow and the Bridge/self-heal path both read from the same persisted
context so the session refreshes back into the same advisory/action.
---
Nitpick comments:
In `@src/components/Card/CardFace.tsx`:
- Around line 141-150: The expiry and CVV fields in CardFace should keep
accessible context while remaining visually label-free; update the rendered
expiry/CVV sections to include hidden sr-only text instead of removing the
labels entirely. Use the existing CardFace markup around the expiry block and
the CVV block so screen readers still announce meaningful labels without
affecting layout, and apply the same fix to both occurrences referenced in the
component.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 4cac8b3f-32ce-40a6-8e69-0374963ac3f6
📒 Files selected for processing (60)
.github/workflows/update-content.yml.verify-content-baselinee2e/flows/add-money.spec.tseslint.config.jspublic/llms-full.txtpublic/llms.txtredirects.jsonscripts/verify-content.tssrc/app/(mobile-ui)/add-money/[country]/bank/page.tsxsrc/app/(mobile-ui)/claim/page.tsxsrc/app/(mobile-ui)/home/page.tsxsrc/app/(mobile-ui)/withdraw/[country]/bank/page.tsxsrc/app/[...recipient]/page.tsxsrc/app/actions/sumsub.tssrc/app/invite/page.tsxsrc/app/m/[slug]/merchants.tssrc/app/receipt/[entryId]/page.tsxsrc/components/AddMoney/components/MantecaAddMoney.tsxsrc/components/AddWithdraw/AddWithdrawCountriesList.tsxsrc/components/AddWithdraw/DynamicBankAccountForm.tsxsrc/components/Card/CardFace.tsxsrc/components/Card/__tests__/CardFace.test.tsxsrc/components/Claim/Link/views/BankFlowManager.view.tsxsrc/components/Global/NavHeader/index.tsxsrc/components/Global/Slider/__tests__/index.test.tsxsrc/components/Global/Slider/index.tsxsrc/components/Global/TokenAndNetworkConfirmationModal/index.tsxsrc/components/Kyc/AdvisoryPreemptModal.tsxsrc/components/TransactionDetails/TransactionDetailsReceipt.tsxsrc/components/TransactionDetails/__tests__/splitBill.utils.test.tssrc/components/TransactionDetails/__tests__/transaction-predicates.test.tssrc/components/TransactionDetails/splitBill.utils.tssrc/components/TransactionDetails/transaction-predicates.tssrc/components/TransactionDetails/useReceiptViewModel.tssrc/constants/rhino.consts.tssrc/constants/sumsub-reject-labels.consts.tssrc/contentsrc/data/seo/deposit-rails.tssrc/data/seo/exchanges.tssrc/data/seo/index.tssrc/hooks/useAdvisoryPreempt.test.tssrc/hooks/useAdvisoryPreempt.tssrc/hooks/useMultiPhaseKycFlow.tssrc/hooks/useSumsubKycFlow.tssrc/interfaces/interfaces.tssrc/services/rain.tssrc/types/api.generated.tssrc/types/api.openapi.jsonsrc/types/capabilities.tssrc/utils/__tests__/bridge.utils.test.tssrc/utils/__tests__/claim-metadata.utils.test.tssrc/utils/__tests__/og.utils.test.tssrc/utils/__tests__/withdraw.utils.test.tssrc/utils/bridge.utils.tssrc/utils/capability-gate.test.tssrc/utils/capability-gate.tssrc/utils/claim-metadata.utils.tssrc/utils/og.utils.tssrc/utils/sentry.utils.tssrc/utils/withdraw.utils.ts
✅ Files skipped from review due to trivial changes (8)
- src/components/Global/Slider/tests/index.test.tsx
- .verify-content-baseline
- src/types/api.generated.ts
- src/data/seo/deposit-rails.ts
- src/data/seo/index.ts
- src/constants/sumsub-reject-labels.consts.ts
- src/app/m/[slug]/merchants.ts
- src/components/Global/NavHeader/index.tsx
🚧 Files skipped from review as they are similar to previous changes (6)
- src/utils/tests/withdraw.utils.test.ts
- src/utils/tests/bridge.utils.test.ts
- src/components/AddWithdraw/AddWithdrawCountriesList.tsx
- src/utils/bridge.utils.ts
- src/components/AddWithdraw/DynamicBankAccountForm.tsx
- src/components/Claim/Link/views/BankFlowManager.view.tsx
Hotfix to
main· pairs with peanut-api-ts #1042 (BANK_GB type — deploy BE first). Notion: https://app.notion.com/p/3848381175798172a9fbeca0b71a3f20Summary — the why
The withdraw flow keys the destination rail off country, hardcoding one rail per country. The UK has two (GBP Faster Payments + EUR SEPA), so UK users get stranded; ~1,266 users are enrolled in both. Customer bobbyfresco (~17d), also kamilagod96.
Supersedes #2173
Cherry-picked @Hugo0's #2173 commit (its
getOfframpConfigFromAccounthelper + the Claim-flow fix + test) — authorship preserved. #2173 only patched the Claim flow and its GB branch was dead (no account ever surfaced as'gb'until the paired BANK_GB BE PR). Close #2173 as superseded by this PR.Landed
IBAN.country === selectedCountryfalse-reject; gate on real support (validateBankAccount→ BEallowedCountries). Fixes intra-SEPA (German IBAN / Spain selected) and GB-IBAN-EUR.92f078b5) — withdrawdestinationDetailsnow derives currency/rail viagetOfframpConfigFromAccount(account)(GB → GBP, tolerates the Prisma-shapedBANK_GB); removes the empty-defaultbranch that produced "External account ID is missing.".92f078b5) — the freshly-added-account fallback guards onbridgeAccountId, surfacing a retryable error instead of navigating to a confirm screen that dead-ends on "Bank account is missing.". Root cause was on the BE: Copy changes for deposit hotfix from prod #1042 now returns the projected wire shape (bridgeAccountId+ legacytype+details/sortCode), commit210bd6ea, so a fresh account carries what this flow reads.getOfframpConfigFromAccount(projected + Prisma-shapedBANK_GB+ Manteca-throw + country fallback);withdraw.utilsIBAN→country; intra-SEPA / GB-IBAN cases.Deferred
Risk / deploy
add_bank_gb_account_type+ backfill--apply, dry-run prod cohort = 4) first, then this. Until BANK_GB exists + the backfill runs, UK accounts still type asiban.eslintis pre-existing/advisory — every error is on lines this PR didn't touch (@/interfacesbarrels, prioranys, unusedrawData).