From f89b43ce064f71b4847b85992870632c2266914f Mon Sep 17 00:00:00 2001 From: Dread Date: Wed, 23 Sep 2026 13:45:58 -0700 Subject: [PATCH 1/2] fix(bank-accounts): show the server's reason for BANK_ACCOUNT_INVALID The backend sends an allowlisted, customer-safe reason with this code ("Bank is not supported.", "This account number cannot be used..."). The app replaced it with the generic "Some of these details aren't valid" line, so a customer could not tell which detail to fix. Seen on the Max during TEST device testing. Co-Authored-By: Claude Fable 5.1 --- __tests__/screens/bank-account-errors.spec.ts | 46 +++++++++++++++++++ .../bank-accounts/bank-account-errors.ts | 5 +- 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 __tests__/screens/bank-account-errors.spec.ts diff --git a/__tests__/screens/bank-account-errors.spec.ts b/__tests__/screens/bank-account-errors.spec.ts new file mode 100644 index 000000000..9e74d4d5d --- /dev/null +++ b/__tests__/screens/bank-account-errors.spec.ts @@ -0,0 +1,46 @@ +import { i18nObject } from "@app/i18n/i18n-util" +import { loadLocale } from "@app/i18n/i18n-util.sync" +import { bankAccountErrorMessage } from "@app/screens/settings-screen/bank-accounts/bank-account-errors" + +loadLocale("en") +const LL = i18nObject("en") + +describe("bankAccountErrorMessage", () => { + it("shows the server's reason for BANK_ACCOUNT_INVALID", () => { + // Seen on TEST: the server named the problem ("This account number + // cannot be used...") and the app replaced it with the generic line. + const message = + "This account number cannot be used. Please contact support if it is yours." + + expect(bankAccountErrorMessage(LL, { code: "BANK_ACCOUNT_INVALID", message })).toBe( + message, + ) + }) + + it("falls back to the generic invalid line when the server sends no message", () => { + expect( + bankAccountErrorMessage(LL, { code: "BANK_ACCOUNT_INVALID", message: "" }), + ).toBe(LL.BankAccountsScreen.errorInvalid()) + }) + + it("keeps our own copy for codes that have it", () => { + expect( + bankAccountErrorMessage(LL, { + code: "BANK_ACCOUNT_DUPLICATE_NUMBER", + message: "x", + }), + ).toBe(LL.BankAccountsScreen.errorDuplicateNumber()) + expect(bankAccountErrorMessage(LL, { code: "TOO_MANY_REQUEST", message: "x" })).toBe( + LL.BankAccountsScreen.errorTooManyRequests(), + ) + }) + + it("shows the server message for unknown codes, then the generic fallback", () => { + expect(bankAccountErrorMessage(LL, { code: "SOMETHING_NEW", message: "hello" })).toBe( + "hello", + ) + expect(bankAccountErrorMessage(LL, undefined)).toBe( + LL.BankAccountsScreen.errorGeneric(), + ) + }) +}) diff --git a/app/screens/settings-screen/bank-accounts/bank-account-errors.ts b/app/screens/settings-screen/bank-accounts/bank-account-errors.ts index d0cbdd704..dfb984f7e 100644 --- a/app/screens/settings-screen/bank-accounts/bank-account-errors.ts +++ b/app/screens/settings-screen/bank-accounts/bank-account-errors.ts @@ -20,9 +20,12 @@ export const bankAccountErrorMessage = ( return LL.BankAccountsScreen.errorNotFound() case "BANK_ACCOUNT_DUPLICATE_NUMBER": return LL.BankAccountsScreen.errorDuplicateNumber() + // The server sends an allowlisted, customer-safe reason ("Bank is not + // supported.", "This account number cannot be used..."). Show it; the + // generic line only covers an empty message. case "BANK_ACCOUNT_INVALID": case "INVALID_INPUT": - return LL.BankAccountsScreen.errorInvalid() + return error?.message || LL.BankAccountsScreen.errorInvalid() case "TOO_MANY_REQUEST": return LL.BankAccountsScreen.errorTooManyRequests() default: From 57e31e909eb0398bd957aa136f7db05eedc40b95 Mon Sep 17 00:00:00 2001 From: Dread Date: Wed, 23 Sep 2026 13:59:52 -0700 Subject: [PATCH 2/2] fix(bank-accounts): keep the account number field clear of the keyboard On an iPhone 13 Pro Max the number-pad covered the last field and the button. Use KeyboardAwareScrollView (already in the tree) with extra scroll height and bottom padding. The screen spec overrides the repo-wide null mock so the form still renders, and its BANK_ACCOUNT_INVALID / INVALID_INPUT rows now expect the server's message per the mapper change. Co-Authored-By: Claude Fable 5.1 --- __tests__/screens/edit-bank-account.spec.tsx | 13 +++++++++++-- .../edit-bank-account-screen.tsx | 19 ++++++++++++++++--- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/__tests__/screens/edit-bank-account.spec.tsx b/__tests__/screens/edit-bank-account.spec.tsx index 81563a6c8..ddea3814a 100644 --- a/__tests__/screens/edit-bank-account.spec.tsx +++ b/__tests__/screens/edit-bank-account.spec.tsx @@ -31,6 +31,13 @@ import { import { i18nObject } from "@app/i18n/i18n-util" import { loadLocale } from "@app/i18n/i18n-util.sync" +// The repo-wide mock renders nothing, which hides every field. Render the +// children in a plain ScrollView so the form is testable. +jest.mock("react-native-keyboard-aware-scroll-view", () => { + const { ScrollView } = jest.requireActual("react-native") + return { KeyboardAwareScrollView: ScrollView } +}) + jest.mock("@app/i18n/i18n-react", () => { const { i18nObject: i18n } = jest.requireActual("@app/i18n/i18n-util") return { useI18nContext: () => ({ LL: i18n("en") }) } @@ -353,8 +360,10 @@ describe("EditBankAccountScreen — add", () => { const errorCases: [string, string][] = [ ["BANK_ACCOUNT_DUPLICATE_NUMBER", en.BankAccountsScreen.errorDuplicateNumber()], - ["BANK_ACCOUNT_INVALID", en.BankAccountsScreen.errorInvalid()], - ["INVALID_INPUT", en.BankAccountsScreen.errorInvalid()], + // The server names the problem; the app shows it as-is. + ["BANK_ACCOUNT_INVALID", "server message"], + // The server names the problem; the app shows it as-is. + ["INVALID_INPUT", "server message"], ["TOO_MANY_REQUEST", en.BankAccountsScreen.errorTooManyRequests()], ["BANK_ACCOUNT_NOT_FOUND", en.BankAccountsScreen.errorNotFound()], // Unknown codes show the server's own message. diff --git a/app/screens/settings-screen/bank-accounts/edit-bank-account-screen.tsx b/app/screens/settings-screen/bank-accounts/edit-bank-account-screen.tsx index 249e2e519..7b15ce0cd 100644 --- a/app/screens/settings-screen/bank-accounts/edit-bank-account-screen.tsx +++ b/app/screens/settings-screen/bank-accounts/edit-bank-account-screen.tsx @@ -1,5 +1,6 @@ import React, { useMemo, useState } from "react" -import { Alert, Pressable, ScrollView, Switch, View } from "react-native" +import { Alert, Pressable, Switch, View } from "react-native" +import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view" import { useApolloClient } from "@apollo/client" import { makeStyles, Text, useTheme } from "@rneui/themed" import { StackScreenProps } from "@react-navigation/stack" @@ -209,7 +210,16 @@ export const EditBankAccountScreen: React.FC = ({ route, navigation }) => return ( - + {/* The account number is the last field; with a plain ScrollView the + keyboard covered it and the button. Scroll the focused field clear + of the keyboard and leave room under the last row. */} + {isAdd ? LL.BankAccountsScreen.addSubtitle() @@ -302,7 +312,7 @@ export const EditBankAccountScreen: React.FC = ({ route, navigation }) => /> )} - + ({ paddingVertical: 10, paddingHorizontal: 20, }, + content: { + paddingBottom: 32, + }, subtitle: { marginBottom: 16, },