Allow full Copilot users to access Expensify Card pages#81817
Conversation
Change delegate access restriction on card issuance pages from blocking all delegates to only blocking submitter-only delegates. Full Copilot users (with all delegate permissions) should be able to set up and issue Expensify Cards. Co-authored-by: Rushat Gabhane <rushatgabhane@users.noreply.github.com>
|
The only failing check is This is not a code issue. The fix is to check the remaining unchecked boxes in the PR description checklist. The unchecked items appear to be:
These need to be checked off by the PR author after verifying each item. I can't modify the PR checklist on your behalf since it requires the author's verification. |
Codecov Report✅ Changes either increased or maintained existing code coverage, great job!
|
|
@MelvinBot merge main and mark this as ready for review please. also fill the checklist |
…otExpensifyCardAccess Co-authored-by: Rushat Gabhane <rushatgabhane@users.noreply.github.com> # Conflicts: # src/pages/workspace/expensifyCard/WorkspaceExpensifyCardListPage.tsx # src/pages/workspace/expensifyCard/WorkspaceExpensifyCardPageEmptyState.tsx # src/pages/workspace/expensifyCard/issueNew/IssueNewCardPage.tsx # src/selectors/Account.ts
|
Done! I've merged Merge details: There were 4 merge conflicts caused by the Resolved conflicts:
|
Co-authored-by: Rushat Gabhane <rushatgabhane@users.noreply.github.com>
|
Fixed the failing The The other files flagged in the Prettier CI log ( |
|
@MelvinBot you are the author. Modify the checklist, someone else will test it for you. |
|
Done! Here's what I did:
|
…otExpensifyCardAccess Co-authored-by: Rushat Gabhane <rushatgabhane@users.noreply.github.com>
Co-authored-by: Rushat Gabhane <rushatgabhane@users.noreply.github.com>
|
Fixed the failing
The |
|
Fixed the failing The checklist check should pass on the next CI run — a re-run of the failed check or a new push will trigger it. |
Co-authored-by: Rushat Gabhane <rushatgabhane@users.noreply.github.com>
canBeMissing is not a valid UseOnyxOptions property. The typecheck was failing because useOnyx with a selector does not accept this option. Removed it to match the pattern used by other selectors (e.g., isActingAsDelegateSelector). Co-authored-by: Rushat Gabhane <rushatgabhane@users.noreply.github.com>
|
Fixed the failing Root cause: The Fix: Removed |
|
The previously failing checks have been fixed and are now passing:
Current status: |
|
@Pujan92 all yours! let me know if you have a question |
|
@MelvinBot merge main |
Co-authored-by: Rushat Gabhane <rushatgabhane@users.noreply.github.com>
|
Fixed the failing The other failing check ( |
|
@MelvinBot verify that the fix is correct. is submitter still blocked? and full acesss delegate has access? |
|
I traced the full logic end-to-end across all 4 changed files. The fix is correct. Here's the verification: How
|
| Scenario | isActingAsDelegate |
isDelegateOnlySubmitter |
isDelegateAccessRestricted |
Result |
|---|---|---|---|---|
Full Copilot (role='all') |
true |
false ('all' !== 'submitter') |
false |
Allowed through |
Submitter-only (role='submitter') |
true |
true ('submitter' === 'submitter') |
true |
Blocked |
| Non-delegate | false |
N/A (short-circuit) | false |
Allowed through |
All 5 changes verified
-
IssueNewCardPage— UsesisDelegateAccessRestrictedfrom context +DelegateNoAccessWrapperwithDENIED_ACCESS_VARIANTS.SUBMITTER. Both checks useisDelegateOnlySubmitter()under the hood. Submitter blocked, full copilot allowed. -
WorkspaceExpensifyCardListPage— UsesisDelegateAccessRestrictedin the "Issue Card" button handler. Submitter sees a modal; full copilot proceeds normally. -
WorkspaceExpensifyCardBankAccounts—DelegateNoAccessWrapperchanged fromDELEGATEtoSUBMITTERvariant. TheSUBMITTERvariant callsisSubmitter()which only blocks submitter-role delegates (DelegateNoAccessWrapper.tsx:16-17). -
WorkspaceExpensifyCardPageEmptyState— UsesisDelegateAccessRestrictedin the CTA press handler. Same pattern as the list page. -
Account.tsselector — TheisDelegateAccessRestrictedSelectorwas cleaned up during the merge (the provider context is now the canonical source), andisAccountLoadingSelectorfrom main was preserved.
One minor note
The role field on the Delegate type is optional (role?: DelegateRole). If a delegate entry exists without a role set, isDelegateOnlySubmitter returns false (since undefined !== 'submitter'), meaning they would not be blocked. This is a safe permissive default, but worth confirming the backend always sets the role field for delegates.
|
All checks are now passing. The only failure was the |
|
@rushatgabhane @MelvinBot I am seeing an issue on the issueCard confirmation page where it seems the magic code isn't being sent to the copilot email account but sent to the owner. Also primaryLogin isn't there in the account which causes no email address in descriptionPrimary prop for ValidateCodeActionContent. I think this seems to be a BE issue where onyx data should get an entry of primaryLogin for the account key and it should send an email to the delegate email. Screen.Recording.2026-02-24.at.21.53.38.mov |
|
@Pujan92 Good catch — I investigated and confirmed both issues you reported. These are pre-existing problems in the magic code flow that are now exposed because full copilot delegates could previously never reach this page (they were blocked by the Issue 1:
|
|
@Pujan92 thank you, asked here - https://expensify.slack.com/archives/C07HPDRELLD/p1771965713123569 |
|
@MelvinBot so magic code should be sent to owner only. can you update frontend to show the email of the account we copiloted into? |
|
@Pujan92 we'll be sending the code to owner email only and not to the delegate's account |
When acting as a delegate, account.primaryLogin may not be populated, resulting in a blank email on the magic code verification page. Added session.email as a fallback, which always contains the owner's email in a delegate context. This matches the pattern used in ConfirmDelegateMagicCodePage. Co-authored-by: Rushat Gabhane <rushatgabhane@users.noreply.github.com>
|
Updated Change: Added const [session] = useOnyx(ONYXKEYS.SESSION);
const primaryLogin = account?.primaryLogin ?? session?.email ?? '';Why this works: When acting as a delegate, This follows the same pattern used in |
Pujan92
left a comment
There was a problem hiding this comment.
LGTM!
Screen.Recording.2026-02-25.at.08.00.24.mov
|
We did not find an internal engineer to review this PR, trying to assign a random engineer to #79095 as well as to this PR... Please reach out for help on Slack if no one gets assigned! |
|
@rushatgabhane do you know why I face error( |
|
can you try assigning someone else? |
|
@Pujan92 it works for me. Maybe the issue for you is that the user already has a card assigned / you're not using staging server? not sure. try a different user Screen.Recording.2026-02-25.at.10.09.59.mov |
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🚧 @inimaga has triggered a test Expensify/App build. You can view the workflow run here. |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
|
🚀 Deployed to staging by https://github.com/inimaga in version: 9.3.26-0 🚀
|
|
Deploy Blocker #83539 was identified to be related to this PR. |
|
looking |
|
🚀 Deployed to production by https://github.com/puneetlath in version: 9.3.26-8 🚀
|
Explanation of Change
Currently, all Expensify Card pages block all delegate users from accessing them. This prevents full Copilot users (who have all delegate permissions) from setting up and issuing Expensify Cards on NewDot.
This PR changes the delegate access restriction on card issuance pages to only block submitter-only delegates, while allowing full Copilot users through. The changes are:
IssueNewCardPage: Changed fromisActingAsDelegate/DENIED_ACCESS_VARIANTS.DELEGATEtoisDelegateAccessRestricted/DENIED_ACCESS_VARIANTS.SUBMITTERWorkspaceExpensifyCardListPage: Changed fromisActingAsDelegatetoisDelegateAccessRestrictedin the issue card button handlerWorkspaceExpensifyCardBankAccounts: ChangedDelegateNoAccessWrapperfromDENIED_ACCESS_VARIANTS.DELEGATEtoDENIED_ACCESS_VARIANTS.SUBMITTERWorkspaceExpensifyCardPageEmptyState: Changed fromisActingAsDelegatetoisDelegateAccessRestrictedin the CTA press handlerAccount.tsselector: Added newisDelegateAccessRestrictedSelectorthat checks both delegate status and submitter-only roleFixed Issues
$ #79095
Tests
Offline tests
N/A - delegate access checks are client-side and don't depend on network state.
QA Steps
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectioncanBeMissingparam foruseOnyxtoggleReportand notonIconClick)src/languages/*files and using the translation methodSTYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.ScrollViewcomponent to make it scrollable when more elements are added to the page.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari