Skip to content

fix: add confirmation popup for cross-context user management action#4977

Open
muditbhutani wants to merge 3 commits into
mainfrom
fix/team-management-switch-to-update-confirmation
Open

fix: add confirmation popup for cross-context user management action#4977
muditbhutani wants to merge 3 commits into
mainfrom
fix/team-management-switch-to-update-confirmation

Conversation

@muditbhutani

@muditbhutani muditbhutani commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

Type of Change

  • Bugfix
  • New feature
  • Enhancement
  • Refactoring
  • Dependency updates
  • Documentation
  • CI/CD

Description

On the Team management → user detail page, rows whose org/merchant/profile lineage differs from the current JWT session show a button that swaps the entire dashboard context (via user/switch/org / user/switch/merchant / user/switch/profile) when clicked.

This PR makes that action explicit and seamless:

Commit 1 — confirmation before switching

  • Renames the button from "Switch to update" to "Manage user", matching the in-context action and its button style (Secondary, Medium).
  • Before switching, shows a confirmation popup (PopUpState.useShowPopUp with (Warning, WithIcon), same pattern as the delete-user confirmation in ManageUserModal) that names the target merchant and profile and tells the user their dashboard will be switched to that context.
  • Merchant/profile display names use the same name-with-value-fallback logic as the table rows on this page.

Commit 2 — auto-open the manage user modal after the switch

  • After the user confirms and the context switch succeeds, the Manage user modal for the same role row opens automatically in the new context, instead of leaving the user to find and click the row's button again.
  • The intent is stored in a Recoil atom (autoOpenManageUserModalAtom) keyed by user email + org/merchant/profile lineage + role id, because the dashboard tree can remount while the switch is in flight (activeProduct becomes UnknownProduct), so component state cannot carry it. UserAction consumes the key once the matching row renders in the new context and passes openModalByDefault to ManageUserModal.

Commit 3 — home page copy update

  • Updates the "Integrate a Processor" card subheading on the home page from "20+ gateways" to "180+ gateways" to reflect the current processor count (HomeUtils.res).

Motivation and Context

The "Switch to update" label was confusing to merchants: it didn't say what would be switched, and clicking it silently replaced the whole session context (sidebar merchant, profile, product) as a side effect of a team-management action, leaving the user to discover the renamed button afterwards. Since user/user/update_role resolves the target user-role from the JWT's scope, the context switch is unavoidable today — so the UI now explains it, asks for confirmation, and completes the journey by opening the manage modal directly after the switch.

How did you test it?

  • npx rescript build compiles cleanly.
  • The popup follows the exact showPopUp pattern already used by DeleteUserRole in the same screen.

Manual verification: open Team management → a user with a role under a different merchant/profile → the row action now reads "Manage user" → clicking it shows the confirmation popup naming the target merchant/profile → Confirm switches context and the Manage user modal for that role opens automatically; Cancel leaves the session untouched. The modal does not reopen on subsequent visits to the page (intent is consumed once).

Where to test it?

  • INTEG
  • SANDBOX
  • PROD

Backend Dependency

  • Yes
  • No

Backend PR URL:

Feature Flag

  • New feature flag added
  • Existing feature flag updated
  • No feature flag changes

Feature flag name(s):

Checklist

  • I ran npm run re:build
  • I reviewed submitted code
  • I added unit tests for my changes where possible

The 'Switch to update' button on the team management user detail page
silently switched the entire dashboard context (org/merchant/profile)
without explaining what was happening, which confused merchants.

- Rename the button to 'Manage user' to match the in-context action
- Show a confirmation popup (Warning, WithIcon) before switching,
  naming the target merchant and profile so the user knows their
  dashboard context will change
@muditbhutani muditbhutani requested a review from a team as a code owner June 11, 2026 21:27
@semanticdiff-com

Copy link
Copy Markdown

Review changes with  SemanticDiff

After confirming the context switch from the team management user
detail page, the user previously had to locate and click the row's
'Manage user' button again in the new context.

The switch action now records the target role row in a Recoil atom
(the dashboard tree can remount mid-switch, so component state cannot
carry the intent) and the row's action consumes it once the new
context renders, opening the manage user modal directly.

@XyneSpaces XyneSpaces left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Automated Review Summary

PR: fix: add confirmation popup for cross-context user management action
Reviewer: XyneSpaces


Findings Overview

Severity Count Issues
⚠️ Warning 2 Recoil atom naming; useEffect dependencies
💡 Suggestion 2 Empty ID fallback; inline function

✅ Positive Notes

  • Confirmation popup pattern follows the established codebase pattern using PopUpState.useShowPopUp with (Warning, WithIcon) — consistent with existing implementations
  • Button redesign from "Switch to update" to "Manage user" with Secondary/Medium styling improves UX clarity
  • Auto-open modal logic correctly consumes the intent to prevent reopening on subsequent visits
  • Home page copy update from "20+" to "180+ gateways" accurately reflects current processor count

Detailed Findings

See inline comments for specific locations and fixes.

Verdict: Clean implementation with minor suggestions. The confirmation flow significantly improves the user experience by making the context switch explicit.

},
~isRequired=true,
)
// The dashboard can remount while the OMP context switch is in flight, so the

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

⚠️ Recoil atom naming should be namespaced

The atom name "autoOpenManageUserModal" uses camelCase without file/feature prefix. Other atoms in this codebase typically follow a namespaced pattern for clarity.

Suggestion: Use a more specific name like "userManagement_autoOpenManageUserModal" or "manageUserModal_autoOpenIntent" to match the convention used in other Recoil atoms.

// than component state. The stored key identifies the exact role row the
// switch was triggered for.
let autoOpenManageUserModalAtom: Recoil.recoilAtom<option<string>> = Recoil.atom(
"autoOpenManageUserModal",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

⚠️ Empty string fallback could cause key collisions

Using Option.getOr("") for orgId, merchantId, and profileId means rows with missing IDs will generate identical keys if they share the same userEmail and roleId.

let orgId = userInfoValue.org.id->Option.getOr("")

Suggestion: Consider using a sentinel value like "_" or a UUID placeholder, or ensure keys remain unique even when IDs are None:

let orgId = userInfoValue.org.id->Option.getOr("_none_")

if openModalByDefault {
setAutoOpenRowKey(_ => None)
}
None

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

💡 useEffect dependency array

The useEffect uses [openModalByDefault] but references setAutoOpenRowKey inside. While Recoil setters are stable, for correctness consider either:

  1. Adding setAutoOpenRowKey to dependencies (it's stable from Recoil)
  2. Or using the functional update form to avoid the dependency

Current code is functionally correct — this is a minor code hygiene note.

customButtonStyle="!p-2"
buttonType={PrimaryOutline}
onClick={_ => onSwitchForUserAction()->ignore}
text="Manage user"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

💡 Unnecessary wrapper function

The onClick handler wraps switchConfirmation() in an arrow function:

onClick={_ => switchConfirmation()}

Suggestion: Since switchConfirmation takes unit and returns unit, you can pass it directly:

onClick={_ => switchConfirmation()}

This is consistent with the existing pattern in this file (see line 289 where onSwitchForUserAction()->ignore is used).

Comment on lines +263 to +265
let profileId = userInfoValue.profile.id->Option.getOr("")
`${userEmail}-${orgId}-${merchantId}-${profileId}-${userInfoValue.roleId}`
}

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.

can we check if we can avoid using the concatenated string and maybe use a record ?

// auto-open intent for the manage user modal is kept in a Recoil atom rather
// than component state. The stored key identifies the exact role row the
// switch was triggered for.
let autoOpenManageUserModalAtom: Recoil.recoilAtom<option<string>> = Recoil.atom(

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.

instead of keeping this atom in helper file, can we keep it in a new file like UserManagementAtoms.res

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.

3 participants