Skip to content

Allow linking an existing card feed to a non-USD workspace - #98509

Open
MelvinBot wants to merge 11 commits into
mainfrom
claude-allowLinkFeedNonUsdWorkspace
Open

Allow linking an existing card feed to a non-USD workspace#98509
MelvinBot wants to merge 11 commits into
mainfrom
claude-allowLinkFeedNonUsdWorkspace

Conversation

@MelvinBot

@MelvinBot MelvinBot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

We only allow onboarding for Expensify card in workspaces that use one of our supported currencies: USD, GBP, EUR. But, if you already have an active E-card feed on a different workspace, you're supposed to be able to link that feed to a workspace of any currency. This PR allows that. It all already worked, but you used to have to set it up on OldDot.

This change skips the currency prompt when the workspace has accessible feeds (hasAccessibleFeeds). In that case the CTA calls startFlow() directly, which already routes to the feed selector so the admin can link an existing feed.

  • In this case, the feed selector hides the button that would normally initiate onboarding a new feed.
  • The USD prompt still applies to the brand-new-program path (no accessible feeds).

Fixed Issues

$ #98503
PROPOSAL: #98503 (comment)

Tests

Enrolling is disallowed on unsupported currencies

  1. On an account with no workspaces, create a workspace on an unsupported currency (CAD, AUD, etc)
  2. Enable Expensify Card under More features
  3. Go to Expensify Card > Issue new card
  4. Verify that you see the USD modal
disallows.enrolling.fresh.on.unsupported.currency.mp4

Linking a USD feed is allowed from a non-USD workspace

  1. Start with an account that is an admin on a workspace with an existing Expensify card feed
  2. Create a new workspace on an unsupported currency (CAD, AUD, etc)
  3. Enable Expensify Card under More features
  4. Go to Expensify Card > Issue new card
  5. Verify that you see the "Select cards" right hand pane, and it lists the USD workspace
  6. Verify that you do not see the entry with the + icon (which would normally let you set up a new feed)
allows.linking.existing.USD.feed.mp4

USD workspaces are allowed to link an existing feed and set up a new one

  1. Start with an account that is an admin on a workspace with an existing Expensify card feed
  2. Create a new USD workspace
  3. Enable Expensify Card under More features
  4. Go to Expensify Card > Issue new card
  5. Verify that you see the "Select cards" right hand pane, and it lists the existing card USD workspace
  6. Verify that you do see the entry with the + icon, and pressing it takes you to the bank account setup flow
allows.setting.up.new.USD.feed.mp4
  • Verify that no errors appear in the JS console

Offline tests

N/A

QA Steps

Same as tests

  • 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 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 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)
  • 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)
  • 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.
  • 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

See tests

Co-authored-by: Chuck Dries <chuckdries@users.noreply.github.com>
@codecov

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

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

Files with missing lines Coverage Δ
...nsifyCard/WorkspaceExpensifyCardPageEmptyState.tsx 0.00% <0.00%> (ø)
src/hooks/useCanEnrollNewExpensifyCardProgram.ts 0.00% <0.00%> (ø)
...ifyCard/WorkspaceExpensifyCardFeedSelectorPage.tsx 0.00% <0.00%> (ø)
... and 228 files with indirect coverage changes

@MelvinBot

This comment was marked as resolved.

@MelvinBot

This comment was marked as resolved.

…elector

Co-authored-by: Chuck Dries <chuckdries@users.noreply.github.com>
@MelvinBot

This comment was marked as resolved.

@MelvinBot

This comment was marked as resolved.

@chuckdries
chuckdries marked this pull request as ready for review August 13, 2026 16:00
@chuckdries
chuckdries requested a review from a team as a code owner August 13, 2026 16:00
@melvin-bot
melvin-bot Bot requested review from Eskalifer1 and removed request for a team August 13, 2026 16:00
@melvin-bot

melvin-bot Bot commented Aug 13, 2026

Copy link
Copy Markdown

@Eskalifer1 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]

@melvin-bot
melvin-bot Bot requested review from trjExpensify and removed request for a team August 13, 2026 16:00
@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 No code fix needed — all real checks now pass.

When the request came in, several checks were still running (the Android builds, perf tests) and the PR Author Checklist / checklist and independent-approval checks were red. I waited for everything to resolve. The builds, perf tests, and checklist have all since gone green.

The only remaining failing check is Check independent approval, which fails at its Verify peer review step. This isn't a code failure — it just enforces that the PR has an independent reviewer approval, which it doesn't have yet. It will clear on its own once a reviewer approves.

Nothing to push here. Once you've filled out the Tests/QA sections and a reviewer approves, this PR should be all green.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 Still nothing to push here — no code failure to fix.

I re-checked all checks: every build, test, perf, and checklist check passes. The only remaining red check is Check independent approval, which fails at its Verify peer review step. That's not a code issue — it just requires an independent reviewer approval, which the PR doesn't have yet. It'll clear on its own once someone reviews and approves.

This PR needs a human reviewer, not a code change.

@Eskalifer1

Copy link
Copy Markdown
Contributor

I will review it today!

trjExpensify
trjExpensify previously approved these changes Aug 13, 2026

@trjExpensify trjExpensify left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

conflict, @chuckdries. Looks great though.. thanks for the quick work. 👍

@Eskalifer1

Copy link
Copy Markdown
Contributor

@codex review

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

Copy link
Copy Markdown

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: 4f8f9c3aa6

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

if (!(policy?.outputCurrency === CONST.CURRENCY.USD || isUkEuCurrencySupported)) {
// The supported currency restriction only applies to enrolling a brand-new card program.
// If hasAccessibleFeeds is true, allow the flow to start in order to link an existing feed
if (!hasAccessibleFeeds && !(policy?.outputCurrency === CONST.CURRENCY.USD || isUkEuCurrencySupported)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Require a linkable feed before bypassing currency prompt

When the only accessible Expensify card feeds are pending deletion, hasAccessibleFeeds is still true even though the selector disables those feeds via isFeedPendingDelete. On an unsupported-currency workspace such as CAD/AUD, this skips the Update-to-USD prompt and opens the selector where no feed can be linked and the new-program CTA is hidden, leaving the admin unable to continue. Please base this bypass on at least one non-deleted/linkable feed, or keep the currency prompt when no usable feed exists.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Frankly this feels like an edge case I'm not particularly worried about. @MelvinBot can you check if the user sees a greyed out entry in the list in this case, or an empty panel, or something else? What happens?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The user sees a greyed-out, non-clickable entry in the selector — not an empty panel. It's a dead end, but a visible one.

Walking the code for the exact scenario Codex described (brand-new CAD/AUD workspace, the only accessible feed is pending deletion on another workspace):

  • hasAccessibleFeeds is true, so the currency prompt is skipped and startFlow() opens the "Select cards" pane.
  • The pending-delete feed isn't linked to this new workspace, so it lands in otherFeeds and renders under the "From other workspaces" heading.
  • toListItem sets isDisabled: isFeedPendingDelete, so the row shows the feed name + card icon but is greyed out and can't be tapped.
  • Because primaryFeeds is empty and the currency is unsupported, shouldShowIssueCardButton is false, so the + "set up new feed" button is also hidden.

So the admin lands on a pane with a header, the "From other workspaces" label, and a single disabled row — nothing they can click. No blank/empty panel, no crash, no error toast; just a greyed row with no way forward.

One nuance: if that pending-delete feed happened to already be linked to this workspace (i.e. it's a primaryFeed), it renders greyed-out in the main list instead, and issueCardFundID would resolve to it, so an "Issue card" button would appear — but it points at the feed being deleted. In the fresh-workspace case Codex flagged, though, it's the "other workspaces" greyed-row path above.

Derived from reading the code on this branch (4f8f9c3) rather than a live repro — the pendingAction: DELETE feed state is transient and impractical to stage reliably. Happy to dig further if you want a runtime confirmation.

@chuckdries
chuckdries requested a review from a team as a code owner August 13, 2026 20:08
@melvin-bot
melvin-bot Bot requested review from chuckdries and suneox and removed request for a team August 13, 2026 20:09
@MelvinBot

This comment was marked as resolved.

@Eskalifer1

Copy link
Copy Markdown
Contributor

but we also eliminate the USD modal in favor of always showing this friendlier empty state.

BTW - We can add the same button(leading to the Workspace currency) to the empty state :)

@dubielzyk-expensify

Copy link
Copy Markdown
Contributor

What if we implement an empty state in this RHP that says like "[illustration] Expensify Card can be issued in USD, GBP, and EUR. Update your workspace currency, or create a different one in a supported currency". We use that in the bugged edge case, but we also eliminate the USD modal in favor of always showing this friendlier empty state.

That all sounds good to me. Though I'd probably say just use a confirmation modal instead of a RHP with illustration. So I'd just update the copy instead on the modal we have. cc @Expensify/design for thoughts

@shawnborton

Copy link
Copy Markdown
Contributor

I like that idea Jon - just updating the existing modal with accurate copy.

@Eskalifer1

Copy link
Copy Markdown
Contributor

Hi @shawnborton @dubielzyk-expensify I think you've overlooked one of the reasons why the approach involving removing the modal window was proposed in the first place.

Currently, with the new changes, the user might see a result like this on the selection page:
image

If we simply refresh the modal window, this bug will still remain

@shawnborton

Copy link
Copy Markdown
Contributor

So just to make sure I am following, we are only solving for this? "But in the rare edge case where someone deletes the workspace while you have that RHP open, it becomes blank"

@Eskalifer1

Copy link
Copy Markdown
Contributor

So just to make sure I am following, we are only solving for this? "But in the rare edge case where someone deletes the workspace while you have that RHP open, it becomes blank"

Basically, yes, this proposal arose as a result of this edge case. But @chuckdries also suggested removing the modal window, since it’s currently inaccurate (because we support currencies other than USD).

@shawnborton

Copy link
Copy Markdown
Contributor

Okay I think maybe I would do both then: update the modal to be accurate, and then in the rare case where the Select feeds RHP could become empty, use some kind of empty state.

@chuckdries

Copy link
Copy Markdown
Contributor

Ok, I think to get this fix out the door let's update the modal and tackle the empty state as a follow up. How does this sound for copy?
image

@OSBotify

This comment has been minimized.

@MelvinBot

This comment was marked as resolved.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 No code fix needed. The fresh CI run (after the main merge in 8e24145b) finished, and the only two red checks are human-gated review steps, not code failures:

  • PR Reviewer Checklist / checklist — waits for an assigned reviewer to post their completed reviewer checklist. (log)
  • Verify peer review / Check independent approval — waits for an independent reviewer's approval. (log)

Everything else is green — typecheck, ESLint, Oxfmt, Bun tests, all 8 unit-test jobs, Storybook, knip, builds, and both Reassure perf tests. There's nothing for me to push; to clear the two reds a reviewer needs to complete their checklist and give an independent approval.

@dubielzyk-expensify

Copy link
Copy Markdown
Contributor

Ok, I think to get this fix out the door let's update the modal and tackle the empty state as a follow up. How does this sound for copy?

cc @jamesdeanexpensify for some copy help. I don't think the dialog headline should be Expensify but something more descriptive

@trjExpensify

trjExpensify commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

My vote on the copy:

Get the Expensify Card
The Expensify Card is available to issue in USD, GBP, and EUR. Please update this workspace to a supported currency or use a different workspace.
[Update workspace currency]
[Cancel]

@jamesdeanexpensify

Copy link
Copy Markdown
Contributor

@trjExpensify I made two tiny tweaks to yours above, but it looks good to me if it works for you!

@chuckdries

Copy link
Copy Markdown
Contributor

Ok! Here's where we're at
image

@MelvinBot

Copy link
Copy Markdown
Contributor Author

⚠️ Something went wrong. This may be a transient issue — try again. If it persists, check status.anthropic.com.

Run details

@jamesdeanexpensify

Copy link
Copy Markdown
Contributor

@chuckdries can you add a period at the end of the second sentence? Thank you!

chuckdries and others added 2 commits August 17, 2026 10:27
…l locales

Co-authored-by: Chuck Dries <chuckdries@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

Fixed the failing test (job 6) check and pushed to this branch.

Root cause: en.ts added the key workspace.bankAccount.updateCurrencyForExpensifyCardTitle, but it was missing from all 10 non-English locale files. tests/unit/TranslateTest.ts ("Does <locale> locale have all the keys") requires every en key to exist in each locale, so it failed (it reported es first, but every locale was missing it).

Fix: Added updateCurrencyForExpensifyCardTitle to all 10 locales (de, el, es, fr, it, ja, nl, pl, pt-BR, zh-hans), placed right after the existing updateCurrencyForExpensifyCard entry. TranslateTest now passes locally (10/10, previously 1 failed).

About the other two failing checks:

  • generateTranslations — this failed on a transient 503 Service Unavailable when the Polyglot Parrot tried to post its advisory diff comment (gh pr comment), not on any code problem. This push re-triggers it and it should clear.
  • Check independent approval — not code-related; it just needs a second reviewer's approval.

Please give the translations a quick sanity check — the values are my best-effort renderings of "Get the Expensify Card" using each locale's existing term for the Expensify Card.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

⚠️ Something went wrong. This may be a transient issue — try again. If it persists, check status.anthropic.com.

Run details

@MelvinBot

Copy link
Copy Markdown
Contributor Author

The earlier fix worked — test (job 6) (TranslateTest) and generateTranslations are now green. The two remaining failures are not code problems and need no branch change:

  • CLA / CLA — failed on a transient GitHub API error, not a real CLA issue: graphql call to get the committers details failed: HttpError: No server is currently available to service your request (run). Just needs a re-run — please click Re-run failed jobs on that check (I don't have permission to re-run it from here).
  • Check independent approval — not code-related; it just needs a second reviewer's approval.

Nothing else is failing (remaining checks are passing or still running), so no further push is needed from me.

@trjExpensify

Copy link
Copy Markdown
Contributor

@trjExpensify I made two tiny tweaks to yours above, but it looks good to me if it works for you!

Forgetting to say please, where are my manners! ;) 👍

@chuckdries

Copy link
Copy Markdown
Contributor

@Eskalifer1 Sounds like we're good to proceed with review on this one

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.

8 participants