Keep workspace member details resolvable after an invite completes#96449
Keep workspace member details resolvable after an invite completes#96449wildan-m wants to merge 4 commits into
Conversation
A freshly invited member is listed under an optimistic accountID derived from their login. When the invite request finishes, that optimistic personal-details entry is removed and the backend supplies the real accountID, so a member details page opened during the invite was left holding a stale accountID in its route and rendered the not-found page. Recover the member's login from the employee list when the route accountID no longer resolves, and use the real accountID from the resulting personal details for the rest of the page.
…-details-case-insensitive-lookup
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 57c91c185c
ℹ️ 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".
| const memberLogin = personalDetails?.[accountID]?.login ?? ''; | ||
| const routeAccountID = Number(route.params.accountID); | ||
| const memberLogin = personalDetails?.[routeAccountID]?.login ?? getMemberLoginByOptimisticAccountID(policy, routeAccountID); | ||
| const memberPersonalDetails = personalDetails?.[routeAccountID] ?? getPersonalDetailByEmail(memberLogin); |
There was a problem hiding this comment.
Resolve invited secondary logins before rendering details
When the stale route was created from a secondary login that the backend later adds under the account's primary login, getMemberLoginByOptimisticAccountID() can still return the secondary employee-list key, but getPersonalDetailByEmail(memberLogin) is undefined because personal details are stored under the primary login; this duplicate secondary/primary case is explicitly tracked by policy.primaryLoginsInvited. Since the not-found check only tests member, the RHP renders with empty details and keeps the optimistic accountID, so profile/role/remove actions can target the wrong login/account instead of the real member. Please map the secondary login to the primary login before resolving details, or require resolved personal details before rendering controls.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Good catch — handled in a745907. The recovery now consults the workspace's record of secondary-to-primary invites first, so a route ID derived from an invited secondary login resolves to the primary member's details instead of rendering with empty details or falling back to not-found once the secondary key is gone. Added a test case covering this scenario.
Explanation of Change
Clicking a member right after inviting them opens the Right Hand Panel on "Hmm... it's not here" instead of the member's details. An invited member is initially listed under an optimistic account ID derived from their login, and once the invite request finishes the backend supplies their real account ID and the optimistic entry is discarded. A details page opened before that swap keeps holding the stale ID from its route, so its lookups come up empty and the page falls back to not-found — a deep link or refresh on such a URL fails the same way.
The page now recovers the member from what the stale route still encodes: it identifies the workspace employee whose login produces that optimistic ID (also allowing for the SMS domain that phone logins carry), resolves that login back to the member's current details, and continues with the real account ID, so the role and remove controls, card lookups, and navigation all operate on the member's actual account. A member whose route ID is already real resolves on the first lookup exactly as before, and someone who genuinely isn't in the workspace still gets the not-found page.
Fixed Issues
$ #95725
PROPOSAL: #95725 (comment)
Tests
usera+fresh1@gmail.com), select it, click Next, then click Invite..../members/999999). Verify the "Hmm... it's not here" page shows for a member that doesn't exist.Offline tests
QA Steps
Same as tests.
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectionAvatar, 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.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
Kapture.2026-07-20.at.08.07.00.mp4
Android: mWeb Chrome
Kapture.2026-07-20.at.08.14.30.mp4
iOS: Native
Kapture.2026-07-20.at.08.00.11.mp4
iOS: mWeb Safari
Kapture.2026-07-20.at.08.02.30.mp4
MacOS: Chrome / Safari
Kapture.2026-07-19.at.21.13.01.mp4