Skip to content

Conversation

@tiankii
Copy link
Contributor

@tiankii tiankii commented Oct 29, 2025

Summary by CodeRabbit

  • New Features
    • Added phone number input functionality with automatic country detection and country picker
    • Added scan button to destination screen for streamlined payment input
    • Implemented phone number validation with specific error messaging
  • Style
    • Extended color theme with new palette tokens

✏️ Tip: You can customize this high-level summary in your review settings.

JMoises-XCode and others added 30 commits September 18, 2025 15:05
@tiankii tiankii marked this pull request as ready for review November 13, 2025 22:36
@coderabbitai
Copy link

coderabbitai bot commented Dec 10, 2025

Walkthrough

This PR introduces a new PhoneInput React Native component with country picker, integrates phone number validation into the send bitcoin destination flow with new validation states, extends localization keys for phone-related UI elements, adds a scan icon to the destination screen navigation, updates GraphQL input types, and adds a grey6 color token to the theme.

Changes

Cohort / File(s) Summary
Phone Input Component
app/components/phone-input/index.ts, app/components/phone-input/phone-input.tsx
New PhoneInput component combining country picker with formatted phone input, using libphonenumber-js for parsing/formatting, auto-detecting default country from device location, exposing PhoneInputInfo and PhoneInputProps types, computing derived info via useMemo, and managing focus flow between picker and input.
Send Bitcoin Destination Phone Integration
app/screens/send-bitcoin-screen/send-bitcoin-destination-screen.tsx, app/screens/send-bitcoin-screen/destination-information.tsx
Introduces multi-input destination handling with phone-based input alongside search, new PhoneInputSection component, dynamic contact filtering based on active input, phone validation logic, clipboard paste handling with error logging, and updated UI rendering paths for phone vs. search inputs.
Destination State Management
app/screens/send-bitcoin-screen/send-bitcoin-reducer.ts
Adds two new destination states (PhoneInvalid, PhoneNotAllowed) and corresponding action types (SetPhoneInvalid, SetPhoneNotAllowed) to extend validation status handling.
Payment Destination Processing
app/screens/send-bitcoin-screen/payment-destination/lnurl.ts
Adds phone-number validation for LNURL processing by preventing phone-number-like usernames from being treated as intra-ledger handles when matching known LNURL domains.
Navigation Updates
app/navigation/root-navigator.tsx, app/navigation/stack-param-lists.ts
Adds ScanIcon to sendBitcoinDestination screen header-right, replaces static title with destinationScreenTitle(), adds scanPressed navigation parameter, and updates TypeScript navigation typing.
Localization
app/i18n/en/index.ts, app/i18n/i18n-types.ts, app/i18n/raw-i18n/source/en.json, app/i18n/raw-i18n/translations/es.json
Adds translation keys for phone validation (invalidPhoneNumber, phoneNotAllowed), destination screen labels (destinationScreenTitle, orBySMS, orSaved), common paste action, and updates English and Spanish translation values including key renames (destinationIsRequired → destinationRequired) and placeholder text changes.
Theme and Configuration
app/rne-theme/colors.ts, app/rne-theme/themed.d.ts, app/config/appinfo.ts
Adds grey6 color token to light ("#E7E7E7") and dark ("#2B2B2B") themes, updates Colors interface with grey6 property, and reorders LNURL_DOMAINS array.
GraphQL Types
app/graphql/generated.ts
Updates CardConsumerApplicationCreateInput with new isL2Verified field and removes sumsubShareToken and walletAddress fields; removes walletAddress from CardConsumerApplicationManualCreateInput and CardConsumerApplicationUpdateInput.

Sequence Diagram

sequenceDiagram
    actor User
    participant DSScreen as Send Bitcoin<br/>Destination Screen
    participant PhoneInput as PhoneInput<br/>Component
    participant CountryPicker as Country<br/>Picker
    participant Input as Phone<br/>Input
    participant Validator as Phone<br/>Validator
    participant Reducer as Redux<br/>Reducer

    User->>DSScreen: Tap Phone Input
    DSScreen->>PhoneInput: Render PhoneInput
    PhoneInput->>PhoneInput: Detect device location<br/>(useDeviceLocation)
    PhoneInput->>CountryPicker: Initialize with default country
    
    User->>CountryPicker: Select Country
    CountryPicker->>PhoneInput: handleCountrySelect
    PhoneInput->>Input: Set focus & country
    
    User->>Input: Enter phone number
    Input->>PhoneInput: setPhoneNumber (onChange)
    PhoneInput->>PhoneInput: Parse & format number<br/>(libphonenumber-js)
    PhoneInput->>PhoneInput: Compute phoneInputInfo<br/>(useMemo)
    PhoneInput->>DSScreen: onChangeInfo callback
    DSScreen->>Validator: Validate phone number
    alt Valid Phone Number
        Validator->>Reducer: Dispatch SetValid
        Reducer->>DSScreen: Update destinationState
        DSScreen->>DSScreen: Filter & show contacts
    else Invalid Phone
        Validator->>Reducer: Dispatch SetPhoneInvalid
        Reducer->>DSScreen: Update destinationState
        DSScreen->>DSScreen: Show error message
    else Phone Not Allowed
        Validator->>Reducer: Dispatch SetPhoneNotAllowed
        Reducer->>DSScreen: Update destinationState
        DSScreen->>DSScreen: Show error message
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

  • PhoneInput component (phone-input.tsx): Multiple hooks (useSupportedCountriesQuery, useDeviceLocation, useMemo), effects managing country sync and default selection, ref handling for focus management, and GraphQL integration require careful review of state dependencies and effect chains.
  • Send Bitcoin Destination Screen (send-bitcoin-destination-screen.tsx): Significant architectural change introducing multi-input handling, new PhoneInputSection component, complex filtering logic with activeInput parameter, phone validation integration, and clipboard error handling spanning ~100+ lines of logic changes.
  • Send Bitcoin Reducer (send-bitcoin-reducer.ts): New destination states and actions require verification that all callers handle the new phone validation states appropriately throughout the codebase.
  • Navigation updates (root-navigator.tsx): Navigation param typing and callback wiring for scanPressed functionality should be verified end-to-end.
  • Phone validation in LNURL (lnurl.ts): Phone detection logic during intra-ledger resolution requires testing to ensure phone-like usernames are correctly distinguished from standard handles without breaking existing flows.

Poem

🐰 A phone input hops into view,
With country pickers and validation too,
Numbers parse and formats align,
Destinations sanctioned, errors refined,
Your transfer flow now touch-screen divine! 📱✨

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'feat: send flow' is vague and generic, failing to convey the specific nature of substantial changes across multiple features including phone input component, destination validation, UI enhancements, and GraphQL updates. Use a more descriptive title that captures the main scope, such as 'feat: add phone input component and enhance send destination flow' or similar.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

Copy link

@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: 2

Caution

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

⚠️ Outside diff range comments (1)
app/screens/send-bitcoin-screen/destination-information.tsx (1)

50-117: Bug in LnurlError / LnurlUnsupported handling—only one case will ever match

In the Invalid switch, this line:

case InvalidDestinationReason.LnurlError ||
  InvalidDestinationReason.LnurlUnsupported:

evaluates the || expression once at runtime, so the case effectively becomes just case InvalidDestinationReason.LnurlError:; LnurlUnsupported will never hit this branch and will fall through to the default.

Recommend splitting the cases explicitly:

-      case InvalidDestinationReason.LnurlError ||
-        InvalidDestinationReason.LnurlUnsupported:
+      case InvalidDestinationReason.LnurlError:
+      case InvalidDestinationReason.LnurlUnsupported:
         return {
           error: translate.SendBitcoinDestinationScreen.lnAddressError(),
           adviceTooltip: {
             text: translate.SendBitcoinDestinationScreen.lnAddressAdvice(),
           },
         }

This preserves the intended behavior for both reasons.

🧹 Nitpick comments (27)
app/rne-theme/colors.ts (1)

32-32: New grey6 token looks consistent with existing palette

The additional grey6 entries for light and dark themes are wired consistently with the rest of the greys and with the type declarations in themed.d.ts. Just make sure any new usages are checked for contrast against backgrounds where accessibility matters.

Also applies to: 81-81

app/navigation/stack-param-lists.ts (1)

45-45: Clarify semantics of scanPressed?: number

The new scanPressed?: number prop is type‑safe, but its meaning isn’t obvious (timestamp vs counter vs boolean flag). Consider either renaming (e.g., scanPressedAt or scanEventId) or adding a short doc comment where it’s used so future readers know how to set and compare it.

app/config/appinfo.ts (1)

16-16: Reordered LNURL_DOMAINS is fine; consider addressing the FIXME

The domain list contents are unchanged, so behavior should remain the same unless some code depends on array index order. Given the existing FIXME, it would be good (when convenient) to centralize this in globals.lightningAddressDomainAliases to avoid future drift between duplicated domain lists.

app/screens/send-bitcoin-screen/payment-destination/lnurl.ts (1)

3-3: Phone-number detection is reasonable; consider explicit default country / intent

Using libphonenumber-js/mobile plus isValid() to gate out phone‑like usernames from intra‑ledger resolution is a good way to keep LNURL phone flows and username flows separate.

A couple of points to double‑check:

  • parsePhoneNumber() (default export) returns a PhoneNumber instance or undefined when it can’t parse the input, and the throwing variant is parsePhoneNumberWithError(), so the try/catch around parsePhoneNumber is mainly defensive rather than required. (github.com)
  • Because you don’t pass a defaultCountry or options, only numbers that libphonenumber-js can recognize as full international numbers (typically with + and country code) will be treated as valid. If you expect local, digits‑only usernames (e.g., "503…" without a +) to be recognized as phone numbers too, you may want to:
    • Pass a defaultCountry / options, or
    • Adjust the helper to match the exact handle format you use for phone‑based lightning addresses.

If the product intent is “only treat E.164‑style handles as phone numbers and leave all other numeric locals as regular usernames”, then the current implementation is fine; otherwise, it’s worth aligning the helper with the desired handle format so some phone‑style usernames don’t slip through and get resolved as intra‑ledger.

Also applies to: 130-137, 148-148

app/i18n/raw-i18n/translations/es.json (5)

2188-2188: Unify tone: prefer “Introduce…” to match the rest of ES copy

Many strings use informal “tú” (e.g., “Estás convirtiendo”, “Añade una nota”). Suggest reverting to “Introduce un destino válido” for consistency.

-        "enterValidDestination": "Introduzca un destino válido",
+        "enterValidDestination": "Introduce un destino válido",

2212-2215: Minor fixes: keep tone consistent for phone validation

Keep “Introduce …” to align with nearby copy. The other two lines look good.

-        "invalidPhoneNumber": "Introduzca un número de teléfono válido",
+        "invalidPhoneNumber": "Introduce un número de teléfono válido",

2225-2225: Match existing validation phrasing (“Se requiere …”)

Other validations use “Se requiere …” (e.g., “Se requiere cantidad”). Suggest aligning.

-        "destinationRequired": "Destino requerido",
+        "destinationRequired": "Se requiere destino",

2228-2228: Placeholder capitalization

Use sentence case to match Spanish style (and other placeholders).

-        "placeholder": "Factura o Dirección",
+        "placeholder": "Factura o dirección",

2252-2256: Fix untranslated and unclear strings in destination flow

  • pendingDecryptionMessage is still in English — translate.
  • orBySMS likely means “by phone number/SMS”; current “O Número De Teléfono” is odd-cased. Prefer “O por número de teléfono” (or “O por SMS” if the UI truly routes via SMS).
  • orSaved is ambiguous; if it means saved contacts/recipients, make it explicit.

Please confirm the intent of “orBySMS” (phone number vs SMS) and “orSaved” (saved contacts vs saved recipients).

-        "pendingDecryptionMessage": "Encrypted message. Waiting for payment confirmation.",
+        "pendingDecryptionMessage": "Mensaje cifrado. Esperando la confirmación del pago.",
         "destinationScreenTitle": "Enviar a",
-        "orBySMS": "O Número De Teléfono",
-        "orSaved": "O Guardado"
+        "orBySMS": "O por número de teléfono",
+        "orSaved": "O contactos guardados"

If “orBySMS” truly refers to SMS, use:

-        "orBySMS": "O Número De Teléfono",
+        "orBySMS": "O por SMS",
app/i18n/raw-i18n/source/en.json (2)

2224-2227: Destination-required and placeholder changes—verify they match actual behavior

Renaming to destinationRequired and changing the placeholder to “Invoice or Address” make sense given the new separate phone and contacts flows, but they also implicitly suggest usernames are no longer valid here. Please double‑check that:

  • All code paths now use destinationRequired instead of any previous key.
  • The destination input truly only accepts invoices/addresses; if usernames are still valid, consider updating the placeholder to reflect that.

2251-2254: New send‑flow labels are reasonable, but might be tightened

destinationScreenTitle, orBySMS, and orSaved map well to the new design. If you iterate on copy later, you might consider slightly more explicit variants like “Or mobile number” / “Or saved contact” for extra clarity, but this is non‑blocking.

app/components/phone-input/phone-input.tsx (3)

66-68: countryCode initialization relies solely on device location

If useDeviceLocation fails or is slow, countryCode stays undefined, and phoneInputInfo remains null, meaning onChangeInfo will receive null until something else sets the country. The UI still falls back to DEFAULT_COUNTRY_CODE for the picker, so state and visuals can be out of sync.

Not strictly broken, but consider defaulting countryCode to DEFAULT_COUNTRY_CODE on mount (or when detectedCountryCode is absent) so:

  • phoneInputInfo is available immediately, and
  • state matches the flag shown in the picker.

97-108: Focus restoration via timeouts—consider more robust handling

Using hard‑coded timeouts (100ms/300ms) to refocus the TextInput after country selection/close works but is brittle across devices and animations. If react-native-country-picker-modal exposes callbacks or props that better signal when the modal is actually dismissed, prefer those over arbitrary delays to avoid occasional missed or premature focus.


120-135: phoneInputInfo contract—raw vs formatted values

formattedPhoneNumber uses AsYouType while rawPhoneNumber is the unmodified value. This is sensible and gives callers both views; just ensure downstream consumers never assume rawPhoneNumber is digits‑only. If they need a normalized E.164 string, they should derive it from parsePhoneNumber instead of relying on either of these fields.

app/screens/send-bitcoin-screen/destination-information.tsx (1)

150-169: adviceTooltip is now dead code—either re‑enable or remove

The adviceTooltip rendering block is commented out, but destinationStateToInformation still computes adviceTooltip for several paths. This leaves unused data and can confuse future maintainers about whether advice tooltips are intentionally hidden.

Either:

  • Re‑enable the block if the design still calls for advice bubbles, or
  • Remove adviceTooltip from the return type and strip the associated branches.

This will keep the view and mapping logic consistent.

app/i18n/i18n-types.ts (1)

8880-8886: Consider reusing a generic “paste” key if one exists

SettingsScreen.paste makes sense if this label is settings‑specific. If there is already a generic common.paste (or similar), consider wiring the UI to that instead to avoid duplicate translation keys.

app/navigation/root-navigator.tsx (2)

6-7: Header scan action wiring looks correct; consider simplifying navigation usage and improving accessibility.

The new ScanIcon header button correctly calls navigation.setParams({ scanPressed: Date.now() }) with a type-safe StackNavigationProp<RootStackParamList, "sendBitcoinDestination">, and the dynamic destinationScreenTitle() is aligned with the new flow.

Two small improvements you might consider:

  • Instead of using useNavigation at the navigator level, use the options={({ navigation }) => ({ ... })} form for sendBitcoinDestination. That avoids relying on a top-level hook and ties the navigation instance directly to that screen’s options:
-      <RootNavigator.Screen
-        name="sendBitcoinDestination"
-        component={SendBitcoinDestinationScreen}
-        options={{
-          title: LL.SendBitcoinScreen.destinationScreenTitle(),
-          headerRight: () => (
-            <TouchableOpacity
-              onPress={() => navigation.setParams({ scanPressed: Date.now() })}
-              style={styles.SendBitcoinScreenScanIcon}
-            >
-              <ScanIcon fill={colors.black} />
-            </TouchableOpacity>
-          ),
-        }}
-      />
+      <RootNavigator.Screen
+        name="sendBitcoinDestination"
+        component={SendBitcoinDestinationScreen}
+        options={({ navigation }) => ({
+          title: LL.SendBitcoinScreen.destinationScreenTitle(),
+          headerRight: () => (
+            <TouchableOpacity
+              onPress={() =>
+                navigation.setParams({ scanPressed: Date.now() })
+              }
+              style={styles.SendBitcoinScreenScanIcon}
+              accessibilityRole="button"
+              accessibilityLabel={LL.HomeScreen.scan()} // or a dedicated string
+            >
+              <ScanIcon fill={colors.black} />
+            </TouchableOpacity>
+          ),
+        })}
+      />
  • Add accessibilityLabel (and optionally testID) on the TouchableOpacity so the scan action is reachable via screen readers and easier to target in tests.

Also applies to: 58-63, 105-107, 117-118, 183-195


754-769: New scan icon style is fine; only minor naming nit if you care about consistency.

SendBitcoinScreenScanIcon: { marginRight: 20 } is functionally fine and gives the header-right icon breathing room. If you want to keep style keys consistent with others in this file (bottomNavigatorStyle, headerStyle, etc.), you could optionally rename it to something like sendBitcoinScreenScanIcon and update usages, but this is purely cosmetic.

app/screens/send-bitcoin-screen/send-bitcoin-reducer.ts (1)

10-12: Phone-specific destination states are a good addition; align state reset behavior to avoid stale destinations.

Adding PhoneInvalid / PhoneNotAllowed and the corresponding actions cleanly extends the destination state machine for phone flows.

One nuance: in the new cases you do:

case SendBitcoinActions.SetPhoneInvalid:
  return {
    ...state,
    destinationState: DestinationState.PhoneInvalid,
  }
case SendBitcoinActions.SetPhoneNotAllowed:
  return {
    ...state,
    destinationState: DestinationState.PhoneNotAllowed,
  }

Whereas in SetInvalid / SetRequiresUsernameConfirmation / SetConfirmed you construct a fresh state object without spreading state, which implicitly clears destination, validDestination, invalidDestination, and confirmationUsernameType.

If SetPhoneInvalid / SetPhoneNotAllowed can be dispatched after a previously valid destination, this pattern will leave those old fields populated while the state reads as phone-invalid, which may be confusing or lead to subtle bugs if any logic keys off destination rather than only destinationState.

To keep invariants simpler, consider clearing derived fields when entering these phone error states, e.g.:

case SendBitcoinActions.SetPhoneInvalid:
-  return {
-    ...state,
-    destinationState: DestinationState.PhoneInvalid,
-  }
+  return {
+    unparsedDestination: state.unparsedDestination,
+    destinationState: DestinationState.PhoneInvalid,
+    destination: undefined,
+    validDestination: undefined,
+    invalidDestination: undefined,
+    confirmationUsernameType: undefined,
+  }
case SendBitcoinActions.SetPhoneNotAllowed:
-  return {
-    ...state,
-    destinationState: DestinationState.PhoneNotAllowed,
-  }
+  return {
+    unparsedDestination: state.unparsedDestination,
+    destinationState: DestinationState.PhoneNotAllowed,
+    destination: undefined,
+    validDestination: undefined,
+    invalidDestination: undefined,
+    confirmationUsernameType: undefined,
+  }

This would mirror how other non-valid states are handled and make the state shape easier to reason about.

Also applies to: 30-40, 45-92, 156-165

app/i18n/en/index.ts (1)

2241-2255: Destination and phone-related copy updates look consistent with the new flow; ensure all call sites use the renamed key.

  • SendBitcoinDestinationScreen.enterValidDestination"Enter a valid destination" matches the more generic UX.
  • New invalidPhoneNumber / phoneNotAllowed messages are clear and align with the new PhoneInvalid / PhoneNotAllowed states in the reducer.
  • In SendBitcoinScreen, the rename from destinationIsRequired to destinationRequired plus the placeholder change to "Invoice or Address" fit the updated input semantics.
  • New keys destinationScreenTitle: "Send to", orBySMS: "Or Mobile Number", and orSaved: "Or Saved" correctly support the redesigned destination screen and header.

Just verify that all usages of the old destinationIsRequired key have been updated (TypeScript/typesafe-i18n should catch any leftovers at compile time).

Also applies to: 2277-2279, 2289-2293, 2323-2325

app/screens/send-bitcoin-screen/send-bitcoin-destination-screen.tsx (7)

115-123: Consider simplifying redundant phone validation logic.

The isValidPhoneNumber check on line 117 already validates the phone number. The subsequent parsePhoneNumber and isValid() call on lines 118-119 is redundant since isValidPhoneNumber internally does the same validation.

 const isPhoneNumber = (handle: string): boolean => {
   try {
-    if (isValidPhoneNumber(handle)) return true
-    const parsed = parsePhoneNumber(handle)
-    return parsed?.isValid() ?? false
+    return isValidPhoneNumber(handle)
   } catch {
     return false
   }
 }

905-922: Review suppressed exhaustive-deps warning.

The eslint-disable comment indicates missing dependencies. The effect reads activeInputRef.current and destinationState.destinationState but they're not in the dependency array. While refs don't trigger re-runs, reading destinationState.destinationState without including it as a dependency could lead to stale closure issues where the effect doesn't re-run when the destination state changes.

Consider adding destinationState.destinationState to the dependency array or restructuring the logic.


961-961: Remove unnecessary static key prop.

The key={1} prop on a single component outside of a list serves no purpose and could be confusing. React's key prop is only meaningful for reconciling lists of sibling elements.

       <PhoneInput
-        key={1}
         rightIcon={

991-991: Consider explicit undefined instead of false for conditional style.

When the condition is false, this expression evaluates to false which gets passed as a style prop. While React Native handles this gracefully, using undefined is more explicit.

-        inputContainerStyle={activeInputRef.current === "phone" && inputContainerStyle}
+        inputContainerStyle={activeInputRef.current === "phone" ? inputContainerStyle : undefined}

254-259: Simplify ListEmptyContent logic.

Multiple branches now return empty fragments (<></>), making the conditional structure unnecessarily complex. Consider simplifying since only the loading state produces meaningful content.

-  } else if (allContacts.length > 0) {
-    ListEmptyContent = <></>
-  } else {
-    ListEmptyContent = <></>
-  }
+  } else {
+    ListEmptyContent = <></>
+  }

810-829: Consider reducing prop count for PhoneInputSection.

The component accepts 19 props, which suggests it might benefit from consolidation (e.g., grouping related state into an object or using a custom hook to encapsulate phone input logic). This would improve maintainability as the component evolves.

That said, for an internal component, the current structure is functional.


170-170: Using ref for UI-affecting state may cause stale renders.

activeInputRef is used to conditionally render UI elements (lines 663-665, 671-674, 695-706, 716-720, 798, etc.), but ref changes don't trigger re-renders. The current implementation works because onFocusedInput calls resetInput(), which updates state and forces a re-render. However, this indirect coupling is fragile.

Consider using useState for activeInput instead, which would make the render dependency explicit and prevent potential bugs if the reset logic changes.

-  const activeInputRef = useRef<TInputType>("search")
+  const [activeInput, setActiveInput] = useState<TInputType>("search")

Then update onFocusedInput and all usages accordingly.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f507e2e and eabde17.

📒 Files selected for processing (16)
  • app/components/phone-input/index.ts (1 hunks)
  • app/components/phone-input/phone-input.tsx (1 hunks)
  • app/config/appinfo.ts (1 hunks)
  • app/graphql/generated.ts (1 hunks)
  • app/i18n/en/index.ts (5 hunks)
  • app/i18n/i18n-types.ts (12 hunks)
  • app/i18n/raw-i18n/source/en.json (5 hunks)
  • app/i18n/raw-i18n/translations/es.json (5 hunks)
  • app/navigation/root-navigator.tsx (6 hunks)
  • app/navigation/stack-param-lists.ts (1 hunks)
  • app/rne-theme/colors.ts (2 hunks)
  • app/rne-theme/themed.d.ts (1 hunks)
  • app/screens/send-bitcoin-screen/destination-information.tsx (2 hunks)
  • app/screens/send-bitcoin-screen/payment-destination/lnurl.ts (3 hunks)
  • app/screens/send-bitcoin-screen/send-bitcoin-destination-screen.tsx (22 hunks)
  • app/screens/send-bitcoin-screen/send-bitcoin-reducer.ts (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
app/screens/send-bitcoin-screen/send-bitcoin-destination-screen.tsx (5)
app/graphql/generated.ts (3)
  • UserContact (2541-2559)
  • Icon (817-869)
  • Icon (871-871)
app/components/phone-input/phone-input.tsx (2)
  • PhoneInputInfo (23-28)
  • PhoneInput (45-194)
app/screens/send-bitcoin-screen/send-bitcoin-reducer.ts (5)
  • SendBitcoinActions (30-40)
  • SendBitcoinActions (42-43)
  • DestinationState (3-12)
  • DestinationState (14-14)
  • SendBitcoinDestinationState (21-28)
app/screens/send-bitcoin-screen/destination-information.tsx (1)
  • DestinationInformation (135-172)
app/i18n/i18n-react.tsx (1)
  • useI18nContext (14-14)
app/screens/send-bitcoin-screen/destination-information.tsx (1)
app/screens/send-bitcoin-screen/send-bitcoin-reducer.ts (2)
  • DestinationState (3-12)
  • DestinationState (14-14)
app/navigation/root-navigator.tsx (1)
app/navigation/stack-param-lists.ts (1)
  • RootStackParamList (17-124)
⏰ 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). (3)
  • GitHub Check: Check Code
  • GitHub Check: Tests
  • GitHub Check: Audit
🔇 Additional comments (26)
app/rne-theme/themed.d.ts (1)

31-32: Theme typings correctly expose grey6

Adding grey6 to the Colors interface aligns the type surface with the new tokens in colors.ts. No issues here.

app/components/phone-input/index.ts (1)

1-1: Barrel export for phone-input is straightforward

Re-exporting from "./phone-input" simplifies imports for consumers and matches common module layout patterns. Looks good.

app/graphql/generated.ts (1)

454-469: New required isL2Verified field in CardConsumerApplicationCreateInput

The new non-nullable isL2Verified: Scalars["Boolean"]["input"] field is now required. Ensure that any client mutations using CardConsumerApplicationCreateInput are updated to pass this flag. Verify this file was regenerated via codegen rather than edited manually.

app/i18n/raw-i18n/translations/es.json (1)

2714-2716: LGTM: new common keys

“profiles”: “Perfiles” and “paste”: “Pegar” are correct and consistent.

app/i18n/raw-i18n/source/en.json (3)

2187-2187: Generic fallback message for invalid destination looks fine

The updated enterValidDestination string is clear and matches its use as a generic fallback when more specific reasons are not available. No issues from a UX or i18n standpoint.


2211-2213: Phone error strings are clear and specific

pastedClipboardSuccess, invalidPhoneNumber, and phoneNotAllowed read well and should surface unambiguously in the UI. Wording is concise and consistent with nearby messages.


2713-2715: New common strings profiles and paste

Both labels are simple and consistent with existing naming. No further changes needed.

app/components/phone-input/phone-input.tsx (4)

139-170: CountryPicker configuration looks consistent with supported countries

Passing countryCodes={allSupportedCountries} and rendering the flag + calling code based on the selected country aligns well with the new GraphQL‑driven country list and theming. Once the loading/null‑guard on allSupportedCountries is fixed, this block should behave correctly.


171-191: Input wiring and test id look solid

The main Input is correctly wired with testProps("telephoneNumber"), phone keyboard, and the custom setPhoneNumber handler, and supports all expected callbacks (onFocus, onBlur, onSubmitEditing, rightIcon). Besides the disabled behavior noted earlier, this is ready to use.


110-118: isDisabled only affects styles—field remains interactive

The isDisabled prop currently only lowers opacity via styles.disabledInput, but both the CountryPicker trigger and the Input remain fully interactive. This can confuse users and automated tests expecting a non‑editable field.

Consider wiring isDisabled through to behavior:

-  const setPhoneNumber = (number: string) => {
-    if (!keepCountryCode) {
+  const setPhoneNumber = (number: string) => {
+    if (isDisabled) return
+    if (!keepCountryCode) {
       const parsedPhoneNumber = parsePhoneNumber(number, countryCode)
@@
-    onChangeText(number)
+    onChangeText(number)
   }

And on the UI side:

-      <CountryPicker
+      <CountryPicker
+        disabled={isDisabled}
@@
-      <Input
+      <Input
         {...testProps("telephoneNumber")}
         ref={phoneInputRef}
@@
-        onChangeText={setPhoneNumber}
+        onChangeText={setPhoneNumber}
+        editable={!isDisabled}

(or equivalent, depending on the APIs you prefer). This keeps visuals and interactivity aligned.


70-78: The current code is safe and does not have a crash risk

data?.globals?.supportedCountries.map(...) safely short-circuits to undefined when supportedCountries is absent, and the || [] fallback handles this correctly. Optional chaining prevents the crash you described—there is no need to change this code.

The dependency array could be more precise ([data?.globals?.supportedCountries] instead of [data?.globals]) for performance optimization, but the current dependency is functionally safe.

app/screens/send-bitcoin-screen/destination-information.tsx (1)

39-48: New phone‑related destination states are correctly surfaced

The PhoneInvalid and PhoneNotAllowed branches cleanly map to the new i18n keys and are checked before the generic Invalid state, which avoids accidentally falling back to “enter a valid destination”. This integrates well with the new phone flow.

app/i18n/i18n-types.ts (11)

6982-6986: Docstring for enterValidDestination is fine and keeps types unchanged

The updated description text is clear and the enterValidDestination: string type remains consistent with the rest of the i18n shape.


7088-7098: Phone-number validation error keys are well named and scoped

invalidPhoneNumber and phoneNotAllowed as string fields fit nicely with the surrounding error messages and read cleanly for the send-flow validation states.


7133-7138: destinationRequired key adds the missing validation state

Adding destinationRequired: string alongside destination completes the destination field’s validation surface for this screen.


7145-7150: Placeholder copy change to “Invoice or Address” looks correct

Only the doc comment is updated; the placeholder: string type remains unchanged and aligned with usage.


7248-7260: New destination screen copy keys align with the redesigned send flow

destinationScreenTitle, orBySMS, and orSaved are clearly named and group all the “send to / or …” variants in one place for the destination screen.


16370-16374: Function wrapper for enterValidDestination matches the string key

The comment and enterValidDestination: () => LocalizedString function signature are consistent with the string definition above.


16464-16474: Function types for phone validation messages mirror the string keys

invalidPhoneNumber and phoneNotAllowed now exist both as raw strings and as () => LocalizedString functions, keeping the typesafe-i18n contract in sync.


16507-16512: destinationRequired function type is consistent with the new string field

The destinationRequired: () => LocalizedString entry correctly mirrors the string key and follows the existing naming pattern.


16519-16524: Placeholder docstring update mirrors the string-side copy change

The function placeholder: () => LocalizedString remains unchanged; only the description text is updated to “Invoice or Address”, staying consistent with the string definition.


16620-16634: New destination screen function keys line up with the string keys

destinationScreenTitle, orBySMS, and orSaved as () => LocalizedString keep parity with the string keys and give the send flow all necessary localized labels for the new UI sections.


18226-18232: Settings paste function mirrors the new string key

paste: () => LocalizedString correctly pairs with SettingsScreen.paste on the string side; no type or naming issues here.

app/i18n/en/index.ts (1)

2817-2818: New common paste label is fine and consistent.

The common.paste: "Paste" string is straightforward and matches existing capitalization of other common actions; no issues here.

app/screens/send-bitcoin-screen/send-bitcoin-destination-screen.tsx (2)

493-512: Effect may re-run unintentionally when parseValidPhone reference changes.

The parseValidPhone callback depends on defaultPhoneInputInfo, which changes during user interaction. When it changes, this effect will re-run. Although the early return on route.params?.payment should prevent issues in most cases, if route.params?.payment is truthy and hasn't changed, the effect logic will execute again unnecessarily.

Consider memoizing the phone parsing logic differently or using a ref for the parser to avoid unnecessary effect triggers.


329-337: Verify if blocking numeric inputs in search mode is intended.

The isInt(rawInput) check will reject purely numeric strings like "12345" as PhoneNotAllowed even when entered in the search input. If numeric usernames are valid in the system, this could incorrectly block legitimate destinations.


const activeInputRef = useRef<TInputType>("search")

const [rawPhoneNumber, setRawPhoneNumber] = useState<string>("null")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Bug: State initialized with string literal "null" instead of empty string.

The rawPhoneNumber state is initialized with the string "null", which will likely cause display issues in the phone input and incorrect validation behavior.

-  const [rawPhoneNumber, setRawPhoneNumber] = useState<string>("null")
+  const [rawPhoneNumber, setRawPhoneNumber] = useState<string>("")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const [rawPhoneNumber, setRawPhoneNumber] = useState<string>("null")
const [rawPhoneNumber, setRawPhoneNumber] = useState<string>("")
🤖 Prompt for AI Agents
In app/screens/send-bitcoin-screen/send-bitcoin-destination-screen.tsx around
line 172, the rawPhoneNumber state is incorrectly initialized to the literal
string "null"; change its initial value to an empty string (use
useState<string>("")) so the phone input and validation treat it as empty rather
than the text "null", and verify any downstream checks expecting null/undefined
are updated to handle an empty string.

Copy link
Contributor

Choose a reason for hiding this comment

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

Done

Comment on lines +606 to +608
setTimeout(() => {
setKeepCountryCode(true)
}, 100)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Timing hack with setTimeout may cause race conditions.

Using setTimeout with a 100ms delay to reset keepCountryCode is fragile and may not work reliably across all devices or under varying load conditions. This pattern appears again in handlePastePhone (lines 881-883).

Consider using a callback-based approach or leveraging React's state update batching to synchronize these state changes more reliably.

Copy link
Contributor

Choose a reason for hiding this comment

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

The setTimeout with 100ms delay for keepCountryCode is intentionally benign and does not pose a race condition risk.

Using React's state batching or callbacks would add unnecessary complexity for what is essentially a "fire-and-forget" UI polish feature. The current implementation is simple, readable, and fit-for-purpose.

If this were controlling validation logic, API calls, or navigation—then yes, a more robust approach would be needed. But for a UI smoothing timeout, this pattern is appropriate.

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.

3 participants