Skip to content

Improved /account page blocking#2255

Merged
sjd210 merged 9 commits into
mainfrom
improvement/account-page-blocking
Jul 27, 2026
Merged

Improved /account page blocking#2255
sjd210 merged 9 commits into
mainfrom
improvement/account-page-blocking

Conversation

@jacbn

@jacbn jacbn commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

We have several issues in the past regarding the account page preventing saving owing to a field being invalid in a different tab.

For example, you could:

  • Reset the first name to blank (invalid)
  • Switch to a different tab and attempt to make an account change
  • This will not work.

Admittedly, an option becoming invalid is unlikely, but accidental changes are not – it wouldn't be obvious that e.g. a name change has occurred if you weren't on the tab.

As a solution to these, we would instead prefer that there be a warning if you attempt to switch tabs with unsaved changes. If you proceed regardless, the tab states should all reset, so that if you make adjustments to another tab, the first set of changes cannot block the changes.

@codecov

codecov Bot commented Jul 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 32.39437% with 48 lines in your changes missing coverage. Please review.
✅ Project coverage is 43.22%. Comparing base (2e39f84) to head (e8b8dd6).
⚠️ Report is 24 commits behind head on main.

Files with missing lines Patch % Lines
src/app/components/pages/MyAccount.tsx 47.05% 18 Missing ⚠️
...pp/components/elements/sidebar/SidebarElements.tsx 0.00% 5 Missing ⚠️
...p/components/elements/sidebar/MyAccountSidebar.tsx 0.00% 4 Missing ⚠️
.../components/elements/sidebar/PolicyPageSidebar.tsx 0.00% 4 Missing ⚠️
...mponents/elements/sidebar/MyAssignmentsSidebar.tsx 33.33% 2 Missing ⚠️
...p/components/elements/sidebar/MyQuizzesSidebar.tsx 50.00% 2 Missing ⚠️
...mponents/elements/sidebar/QuestionDecksSidebar.tsx 0.00% 2 Missing ⚠️
.../app/components/elements/sidebar/SignupSidebar.tsx 0.00% 2 Missing ⚠️
...app/components/elements/inputs/StyledTabPicker.tsx 66.66% 1 Missing ⚠️
...nents/elements/sidebar/AnvilAppsListingSidebar.tsx 0.00% 1 Missing ⚠️
... and 7 more
Additional details and impacted files
@@           Coverage Diff            @@
##             main    #2255    +/-   ##
========================================
  Coverage   43.22%   43.22%            
========================================
  Files         603      603            
  Lines       25775    25793    +18     
  Branches     7662     8577   +915     
========================================
+ Hits        11141    11149     +8     
- Misses      14585    14587     +2     
- Partials       49       57     +8     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@sjd210 sjd210 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.

Also, an interesting quirk that I don't think matters: Changing data in one tab and then navigating away to another tab with a hash anchor will still cause the "You have unsaved changes on this page" dialog on trying to click away, despite the user now being on a different page without that data.
We don't surface the hash anchors on the page, so it would have to be done intentionally, and we still fail safely - so if a user puts themself in that position, so be it.

}
sidebar={siteSpecific(
<MyAccountSidebar editingOtherUser={editingOtherUser} activeTab={activeTab} setActiveTab={setActiveTab}/>,
<MyAccountSidebar editingOtherUser={editingOtherUser} activeTab={activeTab} setActiveTab={safelyChangeTab}/>,

@sjd210 sjd210 Jul 17, 2026

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.

This safelyChangeTabs is firing twice, so whenever the confirm dialog is cancelled - it immediately comes straight back up.

It turns out that this is kinda a pre-existing issue with StyledTabPicker (although one that doesn't matter for most instances, since duplicating a navigate or setState makes no difference).

return <Label {...rest} id={props.id ?? id} tabIndex={-1} className={classNames("d-flex align-items-center py-2 my-1 w-100 tab-picker", rest.className, {"checked": checked})}>
<Input type={type} checked={checked ?? false} onChange={onInputChange} readOnly={onInputChange === undefined} disabled={disabled} aria-labelledby={props.id ?? id} />
<PickerContents checkboxTitle={checkboxTitle} count={count} suffix={suffix} disabled={disabled} />
</Label>;

When we use an onClick/onKeyboardDown system for StyledTabPicker like MyAccountSidebar does, the onClick gets set to the above Label AND activates the Input within so sends two events.

Some StyledTabPicker instances use onInputChange directly, which avoids this - so we should probably either change all instances to that site-wide OR we can protect against the issue within StyledTabPicker by passing onClick as a prop directly to the Input and not to the Label (as is done via {...rest}). Either works 🤷‍♀️

@jacbn jacbn Jul 20, 2026

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.

I think this is almost right – but the input activating isn't doing anything as it doesn't have an event handler. The Label element is, however, repeating the onClick method because of the input; this post describes how onClick is hitting both the label and the input, but the input's one is getting propagated up to the label so that one fires twice. Your fix works because onInputChange is explicitly not applied to the Label but is to the Input.

To prove this, a third alternative solution (not suggesting we use this, just as proof of concept) is to stop the propagation of the onClick and onChange events inside the input:

<Input ... onClick={(e) => e.stopPropagation()} onChange={(e) => {e.stopPropagation(); onInputChange?.(e);}}... />

I don't really know if we should be running event handlers in the input or the label – as the post describes, events that hit the label are also sent to the input if they are tied, but if they are nested events will also propagate from the input to the label, so it seems arbitrary which we choose. Perhaps the input is the more logical place for them; they shouldn't fire twice if existing there, I think.

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.

I've made a cleanup branch targeting this one here; it's quite a large set of changes so figured it would be best reviewed separately? Do let me know your thoughts.

@sjd210 sjd210 Jul 20, 2026

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.

That Stack Overflow post is also how I found out what the issue was. I kinda just... didn't fully explain it while reviewing, just giving the gist (although I certainly could've linked it too) 🙃 My bad

Reviewing the cleanup branch now

jacbn and others added 4 commits July 24, 2026 13:45
An issue with `onChange`, as highlighted by a failing VRT, is that selecting an item that was previously checked does not do anything, unlike `onClick`. This set of changes therefore re-allows `onClick` and `onKeyDown` as props on the picker, but requires `onChange` such that mixing usages of both must be carefully considered.
…hange-cleanup

StyledTabPicker onChange cleanup
@sjd210
sjd210 merged commit 621c7cf into main Jul 27, 2026
10 checks passed
@sjd210
sjd210 deleted the improvement/account-page-blocking branch July 27, 2026 14:50
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.

2 participants