Skip to content

✨ app: payments screen revamp#810

Open
dieguezguille wants to merge 8 commits intohomefrom
payments
Open

✨ app: payments screen revamp#810
dieguezguille wants to merge 8 commits intohomefrom
payments

Conversation

@dieguezguille
Copy link
Member

@dieguezguille dieguezguille commented Feb 19, 2026

Summary by CodeRabbit

  • New Features

    • Installments calculator for estimating payment options and APRs
    • New Payments screen with total outstanding, counts, detailed payment cards, and an integrated payment flow
    • In-app info sheets for early repayment discounts, fees, and statements
  • UI/UX Improvements

    • Renamed tab to "Payments" with updated icon and haptic feedback on tab press
    • Redesigned overdue and upcoming payments list with clearer dates, amounts, and processing states
    • Added direct navigation to the installments calculator
  • Refactor

    • Centralized card-mode handling for more consistent behavior
  • Documentation

    • Added English and Spanish translations for payment flows and messages

Open with Devin

Greptile Summary

This PR revamps the Payments screen with per-payment detail cards, early-repayment discount/late-fee info sheets, and a redesigned payment list with clearer dates and amounts. The core payment logic (Repay.tsx, RepayAmountSelector.tsx) is solid, and the cardModeMutationOptions refactor (server.ts) cleanly centralizes optimistic-update logic.

However, three issues require attention:

  1. React anti-pattern in RolloverIntroSheet.tsx: setState is called directly during render (lines 43–46) instead of inside useEffect, inconsistent with PaymentSheet.tsx and risky under concurrent rendering.
  2. Missing isLatestPlugin guard for returning users: The plugin-version check was moved into RolloverIntroSheet but is skipped when rolloverIntroShown=true. Returning users navigate to /roll-debt without the guard, creating a gap if /roll-debt lacks its own check.
  3. Missing Spanish translations: Aria-label keys "Payment due {{date}}, {{amount}}" and "Overdue payment {{date}}, {{amount}}" are used in code but not translated in es.json, affecting screen-reader users.

Confidence Score: 2/5

  • RolloverIntroSheet uses a React anti-pattern (setState during render) that risks correctness issues under concurrent rendering; PaymentSheet/Pay.tsx bypass isLatestPlugin validation for returning users; and critical translations are missing.
  • Three verified issues prevent safe merging: (1) RolloverIntroSheet violates React hooks rules by calling setState during render instead of in useEffect, inconsistent with PaymentSheet; (2) isLatestPlugin guard was removed for returning users, creating a potential security/correctness gap if /roll-debt lacks its own check; (3) aria-label translations for accessibility are missing from es.json. The first two carry real risk; all three should be addressed.
  • src/components/pay/RolloverIntroSheet.tsx (move setState into useEffect), src/components/pay/PaymentSheet.tsx and src/components/pay/Pay.tsx (restore isLatestPlugin check for all users), src/i18n/es.json (add missing aria-label keys)

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    TAB[Payments Tab] --> PAY[Pay.tsx]

    PAY --> EMPTY{Has payments?}
    EMPTY -- No --> EMPTYVIEW[Empty.tsx]
    EMPTY -- Yes --> HEADER[TotalOutstandingCard]

    HEADER --> FIRST[FirstMaturityCard earliest maturity]
    FIRST --> OVERDUE[OverduePayments excludeMaturity=first]
    OVERDUE --> UPCOMING[UpcomingPayments excludeMaturity=first]

    FIRST -- Pay --> REPAY[Repay.tsx]
    FIRST -- Rollover introShown=false --> ROLLINTRO[RolloverIntroSheet]
    FIRST -- Rollover introShown=true --> ROLLDEBT[roll-debt screen]

    ROLLINTRO -- isLatestPlugin true --> ROLLDEBT
    ROLLINTRO -- isLatestPlugin false --> TOAST[Upgrade toast]

    OVERDUE -- onSelect --> SHEET[PaymentSheet maturity param]
    UPCOMING -- onSelect --> SHEET

    SHEET -- Pay --> REPAY
    SHEET -- Rollover introShown=false --> PAY_ROLLINTRO[onRolloverIntro callback]
    PAY_ROLLINTRO --> ROLLINTRO
    SHEET -- Rollover introShown=true --> ROLLDEBT

    CALC[Calculator.tsx] -.->|entry from InstallmentsSheet| CALC
    PAY --> INFOSHEET[InfoSheet total/discount/fees]
Loading

Comments Outside Diff (3)

  1. src/components/pay/RolloverIntroSheet.tsx, line 43-46 (link)

    setState is called directly during render (lines 43–46) instead of inside useEffect. This is a React anti-pattern that is inconsistent with how the identical pattern is correctly handled in PaymentSheet.tsx (lines 46–51).

    Calling setState during render can cause subtle bugs under React 18's concurrent rendering — the render can be interrupted and retried, potentially triggering the state update more than once.

  2. src/components/pay/PaymentSheet.tsx, line 106-114 (link)

    isLatestPlugin guard was removed from the pre-navigation check for returning users. In the new code, isLatestPlugin is only checked inside RolloverIntroSheet (which is only shown when !rolloverIntroShown). This means returning users (where rolloverIntroShown === true) bypass the plugin guard entirely and navigate directly to /roll-debt without the version check.

    The same gap exists in Pay.tsx's onRollover handler (line 161–167). If /roll-debt does not independently check isLatestPlugin, users with outdated account plugins may encounter runtime errors instead of receiving the "Upgrade account to rollover" message.

    Consider re-adding the isLatestPlugin check to the navigateToRollover callback for all users, not just first-time users:

  3. src/components/pay/UpcomingPayments.tsx, line 115 (link)

    Accessibility aria-label key "Payment due {{date}}, {{amount}}" (used here) is missing from es.json. The same issue exists for "Overdue payment {{date}}, {{amount}}" in OverduePayments.tsx (line 120). Spanish-speaking users relying on screen readers will hear the raw English key string instead of a localized description.

    Add the following entries to es.json:

    "Payment due {{date}}, {{amount}}": "Pago por vencer {{date}}, {{amount}}",
    "Overdue payment {{date}}, {{amount}}": "Pago vencido {{date}}, {{amount}}"

Last reviewed commit: 08feec0

@dieguezguille dieguezguille self-assigned this Feb 19, 2026
@changeset-bot
Copy link

changeset-bot bot commented Feb 19, 2026

🦋 Changeset detected

Latest commit: fa184f4

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link

coderabbitai bot commented Feb 19, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Restructures payment UI: removes legacy pay-mode components, adds a new pay folder (Pay, Repay, Calculator, Overdue/UpcomingPayments, PaymentSheet updates), centralizes card-mode mutation options, updates navigation (tab icons/titles and haptics), adds i18n keys, and updates Maestro subflows and changesets.

Changes

Cohort / File(s) Summary
Changesets
/.changeset/cold-poems-end.md, /.changeset/green-weeks-care.md, /.changeset/jolly-radios-poke.md, /.changeset/purple-lizards-run.md, /.changeset/shiny-badgers-sleep.md
Added five patch changesets for @exactly/mobile (tab bar UI, new pay screen, card mode mutation refactor, installments calculator, overdue payment styling).
Navigation / Layout
src/app/(main)/(home)/_layout.tsx
Tab icon/title updates (Coins→CalendarCheck, FileText→History; "Pay Mode"→"Payments"), added selectionAsync haptic on tab press with error reporting, adjusted tab label emphasis and caption2 styling.
Route re-exports
src/app/(main)/(home)/pay-mode.tsx, src/app/(main)/calculator.tsx, src/app/(main)/pay/index.tsx
Repointed route exports: pay-mode → new Pay, added calculator export, changed pay index to re-export Repay.
Removed pay-mode components
src/components/pay-mode/PayMode.tsx, src/components/pay-mode/PaySelector.tsx, src/components/pay-mode/OverduePayments.tsx, src/components/pay-mode/PaymentsActions.tsx
Deleted legacy pay-mode screen and related selector/actions/overdue components; functionality migrated to new pay/* modules.
New pay components
src/components/pay/Pay.tsx, src/components/pay/Repay.tsx, src/components/pay/Calculator.tsx, src/components/pay/OverduePayments.tsx, src/components/pay/UpcomingPayments.tsx, src/components/pay/Empty.tsx
Added Pay (aggregates maturities, total/first-maturity cards), Repay rename, Calculator (installment rates, BEST APR), rebuilt Overdue/UpcomingPayments with maturity aggregation and processing state, adjusted Empty state layout/text.
PaymentSheet & InfoSheet
src/components/pay/PaymentSheet.tsx, src/components/shared/InfoSheet.tsx
Refactored PaymentSheet to add Frame/NotAvailableView/RolloverIntroView/DetailsView (signature changed to accept onInfoPress), integrated InfoSheet modal component.
Home integration
src/components/home/Home.tsx, src/components/home/InstallmentsSheet.tsx
Home: switched to cardModeMutationOptions via useMutation, added handleModeChange with error reporting. InstallmentsSheet: added router navigation to /calculator.
Server mutation defaults
src/utils/server.ts
Made setCardMode internal, exported cardModeMutationOptions, registered mutation defaults with optimistic update, onError rollback, onSettled invalidation and settings update.
i18n
src/i18n/en.json, src/i18n/es.json
Added English plural keys for "in {{count}} payments" and added/updated many Spanish translations for calculator, payments, discounts, fees, due labels, and empty state text.
Maestro subflows
.maestro/subflows/activateCard.yaml, .maestro/subflows/repay.yaml, .maestro/subflows/rollover.yaml
Expanded activateCard flow to multi-step installments path; sanitized and repositioned debt parsing/taps in repay flow; simplified rollover flow to rely on copiedText-based taps and assertions.
Minor imports
src/components/loans/Loans.tsx
Updated imports to use new pay/* components instead of pay-mode/*.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Home
    participant Calculator
    participant PaymentSheet
    participant Server

    User->>Home: Open Payments screen
    Home->>Server: Fetch market data & maturities
    Server-->>Home: Market data + maturities
    Home->>Home: Aggregate & sort maturities
    User->>Home: Tap "Installments calculator"
    Home->>Calculator: Navigate to /calculator
    User->>Calculator: Enter amount
    Calculator->>Server: Fetch installment rates
    Server-->>Calculator: Rates
    Calculator-->>User: Show installment options (BEST APR)
    User->>Home: Tap payment item
    Home->>PaymentSheet: Open details
    PaymentSheet->>Server: Submit repay/rollover mutation
    Server-->>PaymentSheet: Updated card details
    PaymentSheet->>Home: Refresh & close
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • franm91
  • cruzdanilo
  • itofarina
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '✨ app: payments screen revamp' directly and clearly summarizes the main objective of the pull request, which is a comprehensive redesign of the payments screen component.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch payments

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

❤️ Share

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

@gemini-code-assist
Copy link

Summary of Changes

Hello @dieguezguille, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a comprehensive overhaul of the application's payment management features. It focuses on enhancing the user experience by providing a more intuitive and feature-rich payments screen, including a dedicated calculator for installment planning. The changes also streamline underlying logic for card mode management and refresh the application's navigation aesthetics.

Highlights

  • Payments Screen Revamp: The entire payments screen has been revamped, introducing a new UI and improved functionality for managing card payments and installments.
  • Installments Calculator Added: A new installments calculator feature has been implemented, allowing users to estimate costs for purchases.
  • Tab Bar UI Update: The tab bar user interface has been updated with new icons and labels, changing 'Pay Mode' to 'Payments' and adding haptic feedback on tab presses.
  • Unified Card Mode Mutation: The logic for handling card mode mutations has been unified and centralized, improving consistency and maintainability.
  • Restyled Overdue and Upcoming Payments: The display and styling of overdue and upcoming payments have been restyled for better clarity and user experience.
Changelog
  • .changeset/cold-poems-end.md
    • Updated tab bar UI
  • .changeset/green-weeks-care.md
    • Implemented new pay screen
  • .changeset/jolly-radios-poke.md
    • Unified card mode mutation
  • .changeset/purple-lizards-run.md
    • Added installments calculator
  • .changeset/shiny-badgers-sleep.md
    • Restyled overdue and upcoming payments
  • .maestro/subflows/activateCard.yaml
    • Updated Maestro flow to include tests for installment calculator and info sheets
  • src/app/(main)/(home)/_layout.tsx
    • Updated tab bar icons, labels, and added haptic feedback
  • src/app/(main)/(home)/pay-mode.tsx
    • Updated the default export path for the pay mode screen
  • src/app/(main)/calculator.tsx
    • Added a new route for the installments calculator
  • src/app/(main)/pay/index.tsx
    • Updated the default export path for the pay index screen
  • src/components/home/Home.tsx
    • Updated payment component imports and refactored card mode mutation handling
  • src/components/home/InstallmentsSheet.tsx
    • Integrated router for calculator navigation and updated installment rate data handling
  • src/components/loans/Loans.tsx
    • Updated payment component imports
  • src/components/pay-mode/AssetSelectionSheet.tsx
    • Renamed to src/components/pay/AssetSelectionSheet.tsx
  • src/components/pay-mode/Empty.tsx
    • Renamed to src/components/pay/Empty.tsx and updated content
  • src/components/pay-mode/ManualRepaymentSheet.tsx
    • Renamed to src/components/pay/ManualRepaymentSheet.tsx
  • src/components/pay-mode/OverduePayments.tsx
    • Removed the overdue payments component
  • src/components/pay-mode/Pay.tsx
    • Renamed to src/components/pay/Repay.tsx
  • src/components/pay-mode/PayMode.tsx
    • Removed the main pay mode component
  • src/components/pay-mode/PaySelector.tsx
    • Removed the pay selector component
  • src/components/pay-mode/PaymentSheet.tsx
    • Renamed to src/components/pay/PaymentSheet.tsx and refactored its internal views
  • src/components/pay-mode/PaymentsActions.tsx
    • Removed the payments actions component
  • src/components/pay-mode/RepayAmountSelector.tsx
    • Renamed to src/components/pay/RepayAmountSelector.tsx
  • src/components/pay-mode/UpcomingPayments.tsx
    • Renamed to src/components/pay/UpcomingPayments.tsx and updated its display logic
  • src/components/pay/Calculator.tsx
    • Added a new component for calculating installment costs
  • src/components/pay/OverduePayments.tsx
    • Added a new component to display overdue payments with enhanced UI
  • src/components/pay/Pay.tsx
    • Added a new main payments screen component
  • src/components/shared/InfoSheet.tsx
    • Added a new reusable info sheet component
  • src/i18n/en.json
    • Added new payment-related translation keys
  • src/i18n/es.json
    • Added and updated payment-related translation keys
  • src/utils/server.ts
    • Refactored card mode mutation logic into a reusable option object
Activity
  • New changeset files were added, indicating planned releases for UI updates, new features (pay screen, installments calculator), and code unification.
  • Maestro tests were updated to cover the new installments calculator and various info sheets related to spending limits and credit limits.
  • The tab bar navigation was updated, including new icons and haptic feedback on tab presses.
  • Payment-related components were reorganized into a new 'src/components/pay' directory for better modularity.
  • The 'setCardMode' mutation logic was centralized in 'src/utils/server.ts' for unified handling.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

gemini-code-assist[bot]

This comment was marked as resolved.

@sentry
Copy link

sentry bot commented Feb 19, 2026

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
577 1 576 1
View the top 1 failed test(s) by shortest run time
local::local
Stack Traces | 213s run time
Element not found: Text matching regex: Max

To view more test analytics, go to the Prevent Tests Dashboard

coderabbitai[bot]

This comment was marked as resolved.

@cruzdanilo cruzdanilo changed the title ✨ payments screen revamp ✨ app: payments screen revamp Feb 19, 2026
@dieguezguille dieguezguille force-pushed the home branch 4 times, most recently from 84b9ea9 to 943fa36 Compare February 20, 2026 21:54
@dieguezguille dieguezguille force-pushed the home branch 2 times, most recently from 6e34c64 to 89412ce Compare February 23, 2026 12:50
coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

sentry[bot]

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

@greptile-apps
Copy link

greptile-apps bot commented Mar 3, 2026

Additional Comments (2)

src/components/pay/OverduePayments.tsx, line 128
Missing Spanish translation for accessibility label. The key "{{date}}, {{amount}}" is used as an aria-label in both OverduePayments.tsx (line 128) and UpcomingPayments.tsx (line 123), but it does not exist in es.json. Spanish users relying on screen readers will hear the raw key string instead of a localized label.

Add the missing key to es.json:

"{{date}}, {{amount}}": "{{date}}, {{amount}}"

The same issue exists at src/components/pay/UpcomingPayments.tsx:123.


src/components/pay/Calculator.tsx, line 28
The input parsing can crash at runtime on invalid input. The regex input.replaceAll(/\D/g, ".").replaceAll(/\.(?=.*\.)/g, "") replaces every non-digit character with a dot. If the user types only letters (e.g., "abc"), the result becomes "." after regex cleanup, and calling parseUnits(".", 6) throws a SyntaxError, crashing the component.

Wrap in a try-catch (or validate before calling) to prevent the crash:

  const assets = useMemo(() => {
    try {
      const cleaned = input.replaceAll(/\D/g, ".").replaceAll(/\.(?=.*\.)/g, "");
      if (!cleaned || cleaned === ".") return 0n;
      return parseUnits(cleaned, 6);
    } catch {
      return 0n;
    }
  }, [input]);

@dieguezguille dieguezguille force-pushed the home branch 5 times, most recently from fe79b7b to 536e90b Compare March 4, 2026 13:10
sentry[bot]

This comment was marked as resolved.

@greptile-apps
Copy link

greptile-apps bot commented Mar 4, 2026

Additional Comments (3)

src/components/pay/Pay.tsx, line 324
Potential bigint division by zero crash

positionAmount is the sum of position.principal + position.fee accumulated across all fixed borrow positions for a given maturity. If a position somehow has both principal = 0n and fee = 0n while still having a non-zero previewValue (which the accumulator loop doesn't guard against), (previewValue * WAD) / positionAmount will throw a RangeError: Division by zero since JavaScript bigint division by zero is a thrown error, not Infinity.

  const discount = positionAmount > 0n ? Number(WAD - (previewValue * WAD) / positionAmount) / 1e18 : 0;
  const penaltyPercent = isOverdue ? Math.abs(discount) : discount;

src/i18n/es.json, line 138
Missing Spanish translations for accessibility labels

Two aria-label translation keys used in the redesigned payment lists are absent from es.json, meaning Spanish-language screen reader users will hear the raw English text:

  • "Payment due {{date}}, {{amount}}" — used in src/components/pay/UpcomingPayments.tsx:115 as the aria-label for each upcoming payment row.
  • "Overdue payment {{date}}, {{amount}}" — used in src/components/pay/OverduePayments.tsx:120 as the aria-label for each overdue payment row.

Both need Spanish translations added to es.json.


src/components/pay/Calculator.tsx, line 28
Empty or all-non-digit input produces "." passed to parseUnits

When the user clears the field (empty string), "".replaceAll(/\D/g, ".")".", and then "." is passed to parseUnits, which throws. The old PaySelector.tsx had the same code, so this isn't new to this PR, but it is included here in the new Calculator component. Consider guarding before calling parseUnits:

  const assets = useMemo(() => {
    const cleaned = input.replaceAll(/\D/g, ".").replaceAll(/\.(?=.*\.)/g, "");
    try {
      return parseUnits(cleaned || "0", 6);
    } catch {
      return 0n;
    }
  }, [input]);

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@greptile-apps
Copy link

greptile-apps bot commented Mar 4, 2026

Additional Comments (5)

src/components/pay/Pay.tsx, line 160
The "Rollover" button on FirstMaturityCard opens the same PaymentSheet as "Pay", requiring users to click "Rollover" twice — once on the card and again in the sheet. Both onPay and onRollover call router.setParams({ maturity: ... }), which displays the generic PaymentSheet.

Consider adding an action parameter to distinguish between "pay" and "rollover" intents, allowing PaymentSheet to navigate directly to the rollover flow when appropriate:

onRollover={() => {
  router.setParams({ maturity: String(firstMaturity[0]), action: 'rollover' });
}}

Then in PaymentSheet, check the action parameter to determine whether to show the rollover intro immediately.


src/components/pay/Repay.tsx, line 797
The onPress handler calls repayWithExternalAsset() without catching the returned promise. Although useMutation includes an onError callback that handles errors, the promise returned by mutateAsync() is still rejected if the mutation fails, which will trigger an unhandled promise rejection warning if not explicitly caught.

Add a .catch() to suppress unhandled rejection warnings:

onPress={selectedAsset.external ? () => repayWithExternalAsset().catch(reportError) : handlePayment}

src/components/pay/UpcomingPayments.tsx, line 115
When formattedDate is "Due today" or "Due tomorrow" (from the translation), the aria-label becomes "Payment due Due today, $100.00" — the word "due" appears twice. This redundancy makes the label less clear for screen reader users.

The template string uses "Payment due {{date}}", but formattedDate already contains "Due" in those cases. Strip the "Due " prefix when the date is a relative string:

const ariaDateLabel = formattedDate.startsWith(t("Due ")) ? formattedDate.slice(4) : formattedDate;
// Then use: aria-label={t("Payment due {{date}}, {{amount}}", { date: ariaDateLabel, amount: formattedAmount })}

src/components/pay/UpcomingPayments.tsx, line 61
The dueMaturities map is constructed in the component body without useMemo, causing it to be rebuilt on every render. With refetchInterval: 30_000, the component re-renders every 30 seconds even if the data hasn't changed, triggering unnecessary child re-renders and recalculations.

Wrap the map construction in useMemo to ensure it's only recomputed when its dependencies (exaUSDC or excludeMaturity) actually change:

const dueMaturities = useMemo(() => {
  const map = new Map<bigint, { totalPosition: bigint; totalPreview: bigint }>();
  for (const { maturity, previewValue, position } of exaUSDC?.fixedBorrowPositions ?? []) {
    if (previewValue === 0n) continue;
    if (isBefore(new Date(Number(maturity) * 1000), new Date())) continue;
    if (maturity === excludeMaturity) continue;
    const positionAmount = position.principal + position.fee;
    const existing = map.get(maturity);
    map.set(maturity, {
      totalPreview: (existing?.totalPreview ?? 0n) + previewValue,
      totalPosition: (existing?.totalPosition ?? 0n) + positionAmount,
    });
  }
  return map;
}, [exaUSDC, excludeMaturity]);

src/components/pay/Calculator.tsx, line 28
parseUnits is called without guarding against invalid input strings. When a user types only non-digit characters (e.g., "abc"), the regex transformations result in a single dot ".", and parseUnits(".", 6) throws a parsing error, crashing the component.

Trace: "abc" → replaceAll(/\D/g, ".") → "..." → replaceAll(/\.(?=.*\.)/g, "") → "." → parseUnits(".", 6) throws.

Guard against empty or non-numeric strings before parsing:

const assets = useMemo(() => {
  const cleaned = input.replaceAll(/\D/g, ".").replaceAll(/\.(?=.*\.)/g, "").replace(/^\.*$/, "");
  if (!cleaned || Number.isNaN(Number(cleaned))) return 0n;
  try {
    return parseUnits(cleaned, 6);
  } catch {
    return 0n;
  }
}, [input]);

devin-ai-integration[bot]

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

sentry[bot]

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3cde16ee9e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +121 to +123
if (!rolloverIntroShown && onRolloverIntro && displayMaturity) {
close();
onRolloverIntro(displayMaturity);

Choose a reason for hiding this comment

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

P2 Badge Show rollover intro even when callback prop is absent

The intro gate now depends on onRolloverIntro, so call sites that render PaymentSheet without that prop (for example the loans flow) bypass the first-time rollover intro entirely when rolloverIntroShown is false. Previously the sheet always showed the intro before rollover, so this changes user flow and skips the acknowledgment path in those contexts.

Useful? React with 👍 / 👎.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

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.

2 participants