Skip to content

[Company Card] Update RBR logic of admin/member#83159

Merged
carlosmiceli merged 12 commits intoExpensify:mainfrom
hungvu193:fix-81724
Mar 11, 2026
Merged

[Company Card] Update RBR logic of admin/member#83159
carlosmiceli merged 12 commits intoExpensify:mainfrom
hungvu193:fix-81724

Conversation

@hungvu193
Copy link
Contributor

@hungvu193 hungvu193 commented Feb 22, 2026

Correct the logic of RBR on the Wallet page. Per #78305 (comment):

The RBR should direct workspace admins with a broken connection to the Company cards page, as that's where they fix it. Workspace admins with an assigned card should also see the error on that card on Wallet when there's a broken connection, but no RBR leading there. Non-admin employees with an assigned card should see an RBR leading them to the Wallet error, which tells them to contact an admin to fix the broken connection.

Explanation of Change

Fixed Issues

$ #81724
PROPOSAL: N/A

Tests

Same as QA Tests.

  • Verify that no errors appear in the JS console

Offline tests

N/A

QA Steps

// TODO: These must be filled out, or the issue title must include "[No QA]."

  1. Login as User A > Create a workspace with at least one company card.
  2. Invite User B to the workspace, change User B role to admin and assign them a card.
  3. Break the card connection. (Check this slack thread for more ℹ️ ).
  4. Login with User B > go to workspace that User A has just invited > Company Card > Verify there is RBR on company card page.
  5. Go to Account > Wallet > Verify that there is no RBR display on Wallet page.
  6. Verify that there is an error under the broken company card.
  7. Login with User A again > Change the role of User B to Member.
  8. Login with User B > Go to Account > Wallet.
  9. Verify that there's RBR display on the Wallet page and Account tab.
  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I verified there are no new alerts related to the canBeMissing param for useOnyx
  • I followed proper code patterns (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text shown in the product is localized by adding it to src/languages/* files and using the translation method
      • If any non-english text was added/modified, I used JaimeGPT to get English > Spanish translation. I then posted it in #expensify-open-source and it was approved by an internal Expensify engineer. Link to Slack message:
    • I verified all numbers, amounts, dates and phone numbers shown in the product are using the localization methods
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.ts or at the top of the file that uses the constant) are defined as such
  • I verified that if a function's arguments changed that all usages have also been updated correctly
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If new assets were added or existing ones were modified, I verified that:
    • The assets are optimized and compressed (for SVG files, run npm run compress-svg)
    • The assets load correctly across all supported platforms.
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • If a new page is added, I verified it's using the ScrollView component to make it scrollable when more elements are added to the page.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

Screenshots/Videos

MacOS: Chrome / Safari
Screen.Recording.2026-02-23.at.13.53.00.mov

@codecov
Copy link

codecov bot commented Feb 22, 2026

Codecov Report

✅ Changes either increased or maintained existing code coverage, great job!

Files with missing lines Coverage Δ
src/components/Navigation/DebugTabView.tsx 35.22% <100.00%> (ø)
src/hooks/useNavigationTabBarIndicatorChecks.ts 100.00% <100.00%> (ø)
src/pages/settings/InitialSettingsPage.tsx 0.00% <0.00%> (ø)
src/libs/actions/PaymentMethods.ts 26.95% <58.33%> (+1.48%) ⬆️
... and 11 files with indirect coverage changes

@hungvu193
Copy link
Contributor Author

@MelvinBot please review it in the meantime

@MelvinBot
Copy link
Contributor

Code Review

Overall the approach is sound — filtering company card errors by workspace membership in hasPaymentMethodError so admins only see wallet RBR for cards on workspaces they're a member of (not admin of), and routing admin-level feed errors through the policy checks path instead. All CI checks pass.

A few observations:

1. Redundant null check (minor)

In src/libs/actions/PaymentMethods.ts:442-444, there's a redundant guard:

if (!currentUserLogin || !policies) {
    return true;
}

This code is inside the .some() callback, but currentUserLogin and policies are already guaranteed to be truthy by the early return at lines 431-433. This is dead code — it will never be reached. Consider removing it to keep the logic clear.

2. Fallback behavior when session/policies not loaded

When session or policies are not yet available (lines 431-433), the function returns hasBankOrFundError only — card errors are completely ignored. The old behavior counted all card errors unconditionally. This means during initial app load (before session/policies are hydrated from Onyx), card errors won't show in the wallet RBR temporarily.

This is probably fine in practice since session loads early, but worth being aware of. If you want to preserve the old behavior as a fallback, you could include all card errors when session/policies aren't available:

if (!currentUserLogin || !policies) {
    const hasAnyCardError = Object.values(cardList ?? {}).some(
        (card) => Object.keys(card?.errors ?? {}).length > 0
    );
    return hasBankOrFundError || hasAnyCardError;
}

Up to you whether this edge case matters.

3. Number.isNaN(workspaceAccountID) fallback returns true

At line 446-448, if card.fundID converts to NaN, the card error is counted. This is a safe conservative default (show the error rather than hide it), but it might be worth adding a brief comment explaining why this defaults to true — i.e., "if we can't determine the workspace, show the error to be safe."

4. All checks pass ✓

  • ESLint, TypeScript, Prettier, Jest, Perf tests, CLA, Storybook — all green.

Summary

The fix correctly addresses the issue: admins no longer see competing RBR paths for broken company card feeds. The fundIDworkspaceAccountID mapping is consistent with established patterns in the codebase (TimeSensitiveSection, OnyxDerived/configs/cardFeedErrors.ts, CardFeedUtils). The isPersonalCard check and isPolicyMember usage are both appropriate.

The main actionable item is removing the redundant null check at lines 442-444.

@hungvu193
Copy link
Contributor Author

Note:
There are 2 lines of errors, it's on main too.

Screenshot 2026-02-23 at 13 21 20

@hungvu193
Copy link
Contributor Author

Member:

  • Display RBR under Wallet page, Account tab indicator.
  • Error under the broken connection card.

Admin:

  • No RBR under Wallet page, Account tab indicator.
  • Only display error under the broken connection card.
Member

Member

Admin

Admin

@hungvu193 hungvu193 marked this pull request as ready for review February 23, 2026 07:17
@hungvu193 hungvu193 requested review from a team as code owners February 23, 2026 07:17
@melvin-bot melvin-bot bot requested review from ikevin127 and joekaufmanexpensify and removed request for a team February 23, 2026 07:17
@melvin-bot
Copy link

melvin-bot bot commented Feb 23, 2026

@ikevin127 Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@hungvu193 hungvu193 changed the title [Company Card] Update RBR of admin/member logic [Company Card] Update RBR logic of admin/member Feb 23, 2026
@github-actions
Copy link
Contributor

Code Review - PR #83159

Issue 1: fundID may not be the correct field for workspace matching (PaymentMethods.ts:440)

card.fundID is typed as a string and represents a payment funding source identifier. The new code does Number(card?.fundID) and matches it against policy.workspaceAccountID (a number). Are these actually the same value for company cards? If not, this lookup would never match, and company card errors would always be suppressed for non-personal cards.

Please verify this mapping is correct or use the appropriate card field that maps to the workspace.

Issue 2: isPolicyUser only matches the "user" role (PaymentMethods.ts:442)

isPolicyUser() returns true only when getPolicyRole() === CONST.POLICY.ROLE.USER. This excludes auditors and any other non-admin roles. The PR description says "Non-admin employees with an assigned card should see an RBR" — if auditors should also see RBR, this check is too narrow. Consider using a negated admin check instead (e.g., \!isPolicyAdmin(policy, currentUserLogin)) to capture all non-admin members.

Issue 3: allCards differs between callers (InitialSettingsPage.tsx:163)

In InitialSettingsPage.tsx, allCards comes from useNonPersonalCardList() which filters out personal cards. But inside hasPaymentMethodError, there is a CardUtils.isPersonalCard(card) check — that branch will never execute when called from InitialSettingsPage because personal cards are already filtered out.

In contrast, useNavigationTabBarIndicatorChecks.ts passes the full allCards (including personal cards) so the check works there. This means the RBR behavior for personal card errors will differ between the tab bar indicator and the wallet menu item.

Issue 4: Fallback silently drops card errors (PaymentMethods.ts:427)

When \!currentUserLogin || \!policies is true, the function returns only hasBankOrFundError, completely ignoring card errors. The old behavior checked all cards unconditionally. During app startup or if session/policies have not loaded yet, card errors will be silently suppressed. Consider falling back to the old behavior (checking all cards) rather than dropping them.

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: acc3a7ec1c

ℹ️ 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 +431 to +432
if (!currentUserLogin || !policies) {
return hasBankOrFundError;

Choose a reason for hiding this comment

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

P2 Badge Preserve card errors when policy data is missing

Returning early with only hasBankOrFundError when policies is missing drops all card-list errors. useNavigationTabBarIndicatorChecks reads policies with canBeMissing: true, so users can temporarily or permanently have policies === undefined (for example accounts without workspace policies), and in that state personal/company card errors no longer trigger HAS_PAYMENT_METHOD_ERROR. Before this change, card errors were always included, so this regresses account-tab error visibility.

Useful? React with 👍 / 👎.

@hungvu193
Copy link
Contributor Author

@codex review

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: 624cece03a

ℹ️ 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".

@github-actions
Copy link
Contributor

🚧 @joekaufmanexpensify has triggered a test Expensify/App build. You can view the workflow run here.

@github-actions

This comment has been minimized.

@joekaufmanexpensify
Copy link
Contributor

BUG: errant RBRs on wallet page and workspace page

image image

@joekaufmanexpensify
Copy link
Contributor

Also seeing the errant RBR thing for the non-admin employee view

image

@ShridharGoel
Copy link
Contributor

ShridharGoel commented Feb 24, 2026

Taking this for review based on Slack

@ShridharGoel
Copy link
Contributor

@m-natarajan
Copy link

@ShridharGoel Break connection option not displayed
Snip - (18) New Expensify - Google Chrome (3)

@kavimuru
Copy link

@ShridharGoel @hungvu193 can you please help us?

@hungvu193
Copy link
Contributor Author

Do you toggle "Use Staging Server" option?

@hungvu193
Copy link
Contributor Author

hungvu193 commented Mar 12, 2026

@kavimuru I've just checked the code and the condition to show the Break connection (Testing) button is that your card should be mock card and the environment is not production. So can you please add the Mock card and turn off then turn on the Use Staging Server option? If it still doesn't work, please comment here

@hungvu193
Copy link
Contributor Author

hungvu193 commented Mar 12, 2026

Oh I think I found the issue. This check is always false on Staging:

const isNonProductionBackend = CONFIG.EXPENSIFY.EXPENSIFY_URL.includes('.dev') || CONFIG.EXPENSIFY.EXPENSIFY_URL.includes('staging') || !!shouldUseStagingServer;

CONFIG.EXPENSIFY.EXPENSIFY_URL value is always https://www.expensify.com/ and I couldn't find shouldUseStagingServer inside Onyx DB, we should use !isProduction here instead.

@amyevans Do we allow Break connection (testing) on Staging?

@amyevans
Copy link
Contributor

Toggle User Staging Server off and then on again and it will work:
staging-server

It's a small bug - we need to update the logic to match the implementation here so that the toggling is not required:

@OSBotify
Copy link
Contributor

🚀 Deployed to production by https://github.com/luacmartins in version: 9.3.36-10 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

@joekaufmanexpensify
Copy link
Contributor

I just retested this as part of something else, and it's already not working again/. Do we think this is related to another PR @carlosmiceli @hungvu193 @ShridharGoel

2026-03-13_09-18-13.mp4

@hungvu193
Copy link
Contributor Author

Weird. I think there is another PR removed the RBR logic. I'll take a look later today

@joekaufmanexpensify
Copy link
Contributor

TY!

@hungvu193
Copy link
Contributor Author

@MelvinBot Find the PR that removed the RBR from WorkspaceInitialPage when there's company card with broken connection.

@MelvinBot
Copy link
Contributor

The RBR on WorkspaceInitialPage for company cards with broken connections was restructured (not explicitly removed) by PR #78305 (fix: Handle company Cards errors and initial loading states), merged on 2026-01-26.

That PR replaced the old useHasWorkspaceCompanyCardErrors hook with the new useCardFeedErrors derived value system on WorkspaceInitialPage. The Company Cards menu item still uses shouldShowRbrForWorkspaceAccountID[workspaceAccountID] from the derived value, and the underlying logic in cardFeedErrors.ts still includes isFeedConnectionBroken as a trigger for shouldShowRBR — so the RBR should still appear for broken connections.

If the RBR is currently not appearing as expected, the more likely culprit is PR #84803 (Add 434 to broken connection ignored statuses for card feeds), merged on 2026-03-11. That PR added 434 to BROKEN_CONNECTION_IGNORED_STATUSES in src/CONST/index.ts, which causes isCardConnectionBroken() in CardUtils.ts to return false for cards with lastScrapeResult = 434. If the test card's broken connection has that scrape status, the entire RBR chain would break — no RBR on WorkspaceInitialPage.

TL;DR:

@hungvu193
Copy link
Contributor Author

I think #84803 is the offending.

@carlosmiceli Can you confirm?

@situchan
Copy link
Contributor

It's expected as per #84803 (comment)

@hungvu193
Copy link
Contributor Author

@joekaufmanexpensify #84803 added 434 status (broken connection card status) to the ignore list so we don't see the RBR anymore.

@joekaufmanexpensify
Copy link
Contributor

I see. I think this might just be an issue with the Mock bank then. I tested my real Amex card and the broken connection doesn't scrape 434, so the flow worked correctly. We might need to update the error code we throw when breaking a connection for the mock bank cc @amyevans

@joekaufmanexpensify
Copy link
Contributor

It's expected as per #84803 (comment)

If we aren't supposed to show the error for this case, why does the error momentarily appear below the card @situchan ? See here.

@joekaufmanexpensify
Copy link
Contributor

I'm also seeing a double error with my Amex card on the company cards page. Something definitely is not working 100% correctly here...
image

@hungvu193
Copy link
Contributor Author

I'm also seeing a double error with my Amex card on the company cards page. Something definitely is not working 100% correctly here...

The RBR under the card looks redundant. How do you reproduce it btw?

I think when the feed connection is broken (RBR in Company Cards and Workspace), we shouldn't show the RBR under the card.

@joekaufmanexpensify
Copy link
Contributor

The RBR under the card looks redundant. How do you reproduce it btw?
I think when the feed connection is broken (RBR in Company Cards and Workspace), we shouldn't show the RBR under the card.

Agreed. We shouldn't show this second error on the company cards page ever really IMO. We didn't when this PR originally went out. I'm wondering if that other PR caused this? For reproducing, I just broke the feed connection to see it.

@amyevans amyevans mentioned this pull request Mar 17, 2026
52 tasks
@hungvu193
Copy link
Contributor Author

@MelvinBot Can you check the PR that added the RBR under the card inside WorkspaceCompanyCardPage?

@MelvinBot
Copy link
Contributor

Here's the history of PRs that added RBR under cards on the WorkspaceCompanyCards page:

PR Merged What it did
#48176 2024-09-03 First created WorkspaceCompanyCardsList.tsx with OfflineWithFeedback wrapping each card row, showing errors={item.errors} — the original RBR under cards
#78069 2025-12-20 Replaced the old list with the new WorkspaceCompanyCardsTableItem.tsx (Table component). Added failed card assignment errors + OfflineWithFeedback with errors/pendingAction. Included sub-PRs: #77788 (table item), #78062 (failed assignment error states), #78178 (added useCardFeedErrors hook and brickRoadIndicator on feed selector page)
#78305 2026-01-26 Centralized all card error checking into cardFeedErrors.ts Onyx derived value. Added isFeedConnectionBroken, shouldShowRbrForWorkspaceAccountID, and shouldShowRbrForFeedNameWithDomainID. Updated WorkspaceCompanyCardsTableItem to use assignedCard?.errors directly

If you're specifically asking about the broken connection RBR under cards, PR #78305 is the one that wired up isFeedConnectionBroken into the centralized error system that feeds into the card-level and feed-level RBR indicators.

@hungvu193
Copy link
Contributor Author

For reproducing, I just broke the feed connection to see it.

After broke card connection, I only see the RBR under the card 🤔

Screen.Recording.2026-03-18.at.10.17.53.mov

@joekaufmanexpensify
Copy link
Contributor

I think that's because of this and you using the mock bank. The mock bank uses 434 to show the broken feed error, but we don't show it for that anymore. I think it's incorrect that you're seeing that error at all.

@joekaufmanexpensify
Copy link
Contributor

IMO, we need to:

  1. Investigate why we're showing this error below individual cards on Company cards. We should show the feed-level error only.
  2. Change the mock bank to use a different error code for broken connection, so it can still be used to test this feature.

@hungvu193
Copy link
Contributor Author

hungvu193 commented Mar 18, 2026

For the 2, I think @amyevans had a draft PR for it.

Ideally, we can wait for that PR to be merged so I can reproduce and find the RCA

@joekaufmanexpensify
Copy link
Contributor

Works for me!

@hungvu193
Copy link
Contributor Author

#85591 is merged. I'll try to reproduce the bug and find the offending/solution.

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.

10 participants