Skip to content

fix(ux): improve delete account modal with inline errors#36293

Open
sandranymark wants to merge 8 commits intoRocketChat:developfrom
sandranymark:fix/delete-profile-inline-error
Open

fix(ux): improve delete account modal with inline errors#36293
sandranymark wants to merge 8 commits intoRocketChat:developfrom
sandranymark:fix/delete-profile-inline-error

Conversation

@sandranymark
Copy link
Contributor

@sandranymark sandranymark commented Jun 24, 2025

Proposed changes (including videos or screenshots)

This PR improves the Delete My Account modal (ActionConfirmModal) by adding inline error handling using react-hook-form:

  • Replaces useState logic with react-hook-form
  • Displays inline validation message when the input field is empty
  • Shows error message ("Invalid password") under the input field.
  • Ensures the modal closes correctly after successful account deletion
  • Improves accessibility via aria-* attributes

Issue(s)

image

Steps to test or reproduce

Navigate to My AccountProfile

  1. Click "Delete my account"
  2. Test the following scenarios:
  3. Leave the input empty
    Inline validation error should appear below the field
  4. Enter an incorrect password
    "Invalid password" message should display under the field
  5. Enter the correct password
    Account is deleted, modal closes, user is logged out, and a success toast is shown

Further comments

WA-58

Summary by CodeRabbit

  • Bug Fixes

    • Shows a clear “Invalid password” message on the field instead of a generic alert.
    • Focuses the input when there’s an error for quicker correction.
    • Closes the delete-account modal immediately after successful deletion.
  • Refactor

    • Migrated the confirmation input to a form-based flow with async submission for smoother handling.
    • Improved validation and error display within the modal.
    • Enhanced accessibility with proper labels, error descriptions, and live announcements.

@sandranymark sandranymark requested a review from a team as a code owner June 24, 2025 13:30
@dionisio-bot
Copy link
Contributor

dionisio-bot bot commented Jun 24, 2025

Looks like this PR is not ready to merge, because of the following issues:

  • This PR is targeting the wrong base branch. It should target 8.3.0, but it targets 8.2.0

Please fix the issues and try again

If you have any trouble, please check the PR guidelines

@changeset-bot
Copy link

changeset-bot bot commented Jun 24, 2025

⚠️ No Changeset found

Latest commit: ab15ae8

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Member

@gabriellsh gabriellsh left a comment

Choose a reason for hiding this comment

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

There's some linting errors, would it be possible to fix those?

About the overall solution: It's a nice approach, but I think we could make it a little more declarative/generic if instead of using this "dependency inversion" pattern (accepting a callback to set the error in the confirmation fn), we could throw a custom error with the correct message, and wrap the the fn invocation in a try catch.

export class InputError extends Error {
	constructor(message: string) {
		super(message);
	}
}
if (error.errorType === 'error-invalid-password') {
	throw new InputError(t('Invalid_password'));
}
try {
	await onConfirm(inputText);
	onCancel();
} catch (error) {
	if (error instanceof InputError) {
		setError('credential', { message: error.message });
	}
}

This way we decouple the modal use from it's action, and we can also emit other types of errors in the future if we wish to.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 16, 2025

Walkthrough

Introduces a new InputError class, updates AccountProfilePage to throw InputError on invalid password and close the modal before logout, and refactors ActionConfirmModal to use react-hook-form with an async onConfirm that surfaces field-specific errors and manages focus/accessibility.

Changes

Cohort / File(s) Summary
Account deletion flow: UI and control
apps/meteor/client/views/account/profile/AccountProfilePage.tsx, apps/meteor/client/views/account/profile/ActionConfirmModal.tsx
Profile page now throws InputError for invalid password and closes modal before logout. Modal refactored to react-hook-form; onConfirm is async, awaits server, maps InputError to field error, manages focus and ARIA attributes.
Error type addition
apps/meteor/client/views/account/profile/DeleteAccountError.ts
Added exported InputError class extending Error for surfacing input-specific validation errors.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant M as ActionConfirmModal
  participant P as AccountProfilePage
  participant S as Server (Delete Account API)

  U->>M: Submit credential
  M->>P: onConfirm(credential) [async]
  P->>S: deleteOwnAccount(credential)
  alt Success
    S-->>P: OK
    P-->>M: resolve()
    M->>M: close via onCancel()
    M-->>U: Modal closed
    P->>P: logout()
  else Invalid password
    S-->>P: error-invalid-password
    P-->>M: reject(InputError("Invalid_password"))
    M->>M: set field error + focus input
    M-->>U: Inline error shown
  else Other error
    S-->>P: other error
    P-->>M: reject(Error)
    M-->>U: Generic failure (no field error)
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

A bunny taps the keys with cheer,
“Invalid password? Crystal-clear!”
New errors hop to fields in form,
Async winds now match the norm.
Modal shuts, then logs you out—
Thump-thump feet, no toast to shout.
Carrots up—clean flows throughout! 🥕🐇

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
Title check ✅ Passed The title clearly summarizes the main change: improving the delete account modal with inline errors using react-hook-form, which aligns with all the changes across the three files.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (5)
apps/meteor/client/views/account/profile/DeleteAccountError.ts (1)

1-4: Name and prototype fix for Error subclass

Set the error name and prototype to ensure correct instanceof behavior across transpiled targets and cleaner diagnostics.

-export class InputError extends Error { constructor(message: string) {
-super(message);
-  }
-}
+export class InputError extends Error {
+  constructor(message: string) {
+    super(message);
+    this.name = 'InputError';
+    // Ensure instanceof works when transpiled
+    Object.setPrototypeOf(this, new.target.prototype);
+  }
+}
apps/meteor/client/views/account/profile/AccountProfilePage.tsx (2)

108-110: Harden error-shape check for invalid password

Backends sometimes emit either errorType or error. Consider accepting both to avoid missing the inline error path.

-                if (error.errorType === 'error-invalid-password') {
+                if (error?.errorType === 'error-invalid-password' || error?.error === 'error-invalid-password') {
                   throw new InputError(t('Invalid_password'));
                 }

95-116: SSO/no-password flow — verified: client hashing matches server contract

The API /v1/users.deleteOwnAccount requires a password string; server deleteUserOwnAccount() checks local-password users via Accounts._checkPasswordAsync (digest lowercased) and otherwise compares SHA256(user.username) === password.trim() (apps/meteor/app/lib/server/methods/deleteUserOwnAccount.ts / apps/meteor/app/api/server/v1/users.ts). The client (apps/meteor/client/views/account/profile/AccountProfilePage.tsx) sends { password: SHA256(passwordOrUsername) } for both flows — correct.

  • Optional: handle server 'error-invalid-username' the same way as 'error-invalid-password' (InputError) to surface a clear validation error for SSO/no-password users.
apps/meteor/client/views/account/profile/ActionConfirmModal.tsx (2)

62-88: A11y polish: native required + autocomplete hints

Add required to inputs and set autocomplete to improve UX with screen readers and password managers.

         <Controller
           name='credential'
           control={control}
           rules={{ required: t('Invalid_field') }}
           render={({ field }) =>
             isPassword ? (
               <PasswordInput
                 {...field}
                 id={inputId}
+                required
+                autoComplete='current-password'
                 aria-labelledby={actionTextId}
                 aria-describedby={errors.credential ? errorId : undefined}
                 aria-invalid={Boolean(errors.credential)}
                 aria-required='true'
               />
             ) : (
               <TextInput
                 {...field}
                 id={inputId}
                 placeholder={t('Username')}
+                required
+                autoComplete='username'
                 aria-labelledby={actionTextId}
                 aria-describedby={errors.credential ? errorId : undefined}
                 aria-invalid={Boolean(errors.credential)}
                 aria-required='true'
               />
             )
           }
         />

15-16: Remove stale TODO

The component now uses react-hook-form; the TODO is obsolete.

-// TODO: Use react-hook-form
 const ActionConfirmModal = ({ isPassword, onConfirm, onCancel }: ActionConfirmModalProps) => {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4c2a437 and af6baea.

📒 Files selected for processing (3)
  • apps/meteor/client/views/account/profile/AccountProfilePage.tsx (2 hunks)
  • apps/meteor/client/views/account/profile/ActionConfirmModal.tsx (2 hunks)
  • apps/meteor/client/views/account/profile/DeleteAccountError.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
apps/meteor/client/views/account/profile/AccountProfilePage.tsx (1)
apps/meteor/client/views/account/profile/DeleteAccountError.ts (1)
  • InputError (1-4)
apps/meteor/client/views/account/profile/ActionConfirmModal.tsx (1)
apps/meteor/client/views/account/profile/DeleteAccountError.ts (1)
  • InputError (1-4)
🔇 Additional comments (3)
apps/meteor/client/views/account/profile/AccountProfilePage.tsx (2)

24-24: Typed input error import looks good

Using a dedicated InputError for field-level feedback is appropriate here.


100-100: Avoid double-close races with downstream modals

This closes the modal on success (good). However, ActionConfirmModal currently also calls onCancel after onConfirm resolves, which can double-close or accidentally close a replacement modal (e.g., ConfirmOwnerChangeModal). I’m proposing removal of that auto-close in ActionConfirmModal to avoid races; keeping this line is fine once that change is applied.

apps/meteor/client/views/account/profile/ActionConfirmModal.tsx (1)

11-11: API change: onConfirm is now async — update/verify all call sites

No direct JSX usages were found in your search output; check wrappers/re-exports and any external consumers and ensure passed onConfirm handlers return Promise (mark handlers async or return a Promise and handle rejections).

Copy link
Contributor

@aleksandernsilva aleksandernsilva left a comment

Choose a reason for hiding this comment

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

Hey, changes are looking good. I left comments on a couple of small improvements. I also noticed the code still has a few lint errors, could you take a look?

@dougfabris dougfabris added this to the 8.3.0 milestone Feb 20, 2026
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 2 files

@dougfabris dougfabris changed the title fix: improve delete account modal with inline errors fix(ux): improve delete account modal with inline errors Feb 20, 2026
@codecov
Copy link

codecov bot commented Feb 20, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 70.64%. Comparing base (f661fbc) to head (ab15ae8).
⚠️ Report is 60 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop   #36293      +/-   ##
===========================================
+ Coverage    70.55%   70.64%   +0.09%     
===========================================
  Files         3188     3186       -2     
  Lines       112651   112655       +4     
  Branches     20400    20429      +29     
===========================================
+ Hits         79476    79583     +107     
+ Misses       31114    31028      -86     
+ Partials      2061     2044      -17     
Flag Coverage Δ
unit 71.27% <ø> (-0.28%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Member

@gabriellsh gabriellsh left a comment

Choose a reason for hiding this comment

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

Failing tests related to changes

@dougfabris dougfabris added the stat: QA assured Means it has been tested and approved by a company insider label Feb 25, 2026
@dionisio-bot dionisio-bot bot enabled auto-merge February 25, 2026 22:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stat: QA assured Means it has been tested and approved by a company insider

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants