Skip to content

feat(mfa): create new ModalMfaProtected component#19362

Merged
MagentaManifold merged 1 commit intomainfrom
FXA-12302
Sep 2, 2025
Merged

feat(mfa): create new ModalMfaProtected component#19362
MagentaManifold merged 1 commit intomainfrom
FXA-12302

Conversation

@MagentaManifold
Copy link
Copy Markdown
Contributor

@MagentaManifold MagentaManifold commented Aug 27, 2025

Closes #

Because

  • we need a modal for MFA confirmation code input for MfaProtectedActionGuard

This pull request

  • creates a new ModalMfaProtected component, with stories and unit tests.

Issue that this pull request solves

Closes: FXA-12302

Checklist

Put an x in the boxes that apply

  • My commit is GPG signed.
  • If applicable, I have modified or added tests which pass locally.
  • I have added necessary documentation (if appropriate).
  • I have verified that my changes render correctly in RTL (if appropriate).

Screenshots (Optional)

image

Other information (Optional)

Any other information that is important to this pull request.

@MagentaManifold MagentaManifold marked this pull request as ready for review August 27, 2025 17:46
@MagentaManifold MagentaManifold requested review from a team as code owners August 27, 2025 17:46
Comment on lines +3 to +4
modal-mfa-protected-title = Enter confirmation code
modal-mfa-protected-subtitle = to change your { -product-mozilla-account } info
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.

These two strings need to either be a single string (similar to confirm-signup-code-heading-2) or we should adjust the strings to make sure the title and subtitle stand alone (e.g. something like Enter confirmation code Enter code to change your { -product-mozilla-account } info).

While we've been doing the former, I think we should consider avoiding this design going forward.

  1. Some languages end up inverting the two sections with the span element coming first (following their language word order) so you don't have consistency across languages.
  2. This structure no longer works for strings in the CMS since they can't be combined (and thus become very difficult to translate naturally), so we've had to adjust those already.

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.

Hi @bcolsson! I have updated the strings, now both the main title and subtitle stand alone. Our content designer Jeff wonder if it's necessary to update existing designs to avoid splitting sentences as well.

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.

@MagentaManifold - Missed this question: I wouldn't call it a high priority for the strings currently localized but I'd consider updating them at some point since we're already changing them elsewhere (such as in the CMS strings).

@MagentaManifold
Copy link
Copy Markdown
Contributor Author

The Figma design does not include error states, and my current assumptions on error messages are:

  • When clicking on an MFA protected button, if we fail to request a new email OTP code due to rate limiting or some server error, then the modal does not show up, and an alert bar describing the error is shown at the top of the page (will be handled by MFAProtectedActionGuard, not the modal UI component).
  • An error banner shows up inside the modal if we fail to resend code or fail to verify the code due to rate limiting or some server error.
  • An error tooltip shows up on the input if the code submitted is invalid or expired.
  • A success banner shows up inside the modal if the confirmation code is successfully resent.

It might also make sense to close the modal and show an alert bar when encountering a server error, and the success banner can also be an alert bar, so I want the reviewer's opinion on this!

const onClick = useCallback(
(ev: React.MouseEvent) => {
ev.preventDefault();
ev.stopPropagation();
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.

Did you mean to change this? If so let's put in a a separate 'polish' PR.

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.

Yeah because preventDefault doesn't actually work. stopPropagation is a hack (used in our actual code as well) needed to prevent the modal from disappearing due to losing focus. And yeah I can put it in a separate PR

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.

Thanks for fixing this. That makes sense.

>
<p id="modal-mfa-protected-desc" className="my-6">
Enter the code that was sent to{' '}
<span className="font-bold">{email}</span> within 5 minutes. This
Copy link
Copy Markdown
Contributor

@dschom dschom Aug 28, 2025

Choose a reason for hiding this comment

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

We should make this configurable. Please file a follow up for this since it involves messing with the app's configuration. For this PR however, pass the time limit as a prop.

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.

Yeah I should have thought about it, cuz I literally just worked that email template

@dschom
Copy link
Copy Markdown
Contributor

dschom commented Aug 28, 2025

The Figma design does not include error states, and my current assumptions on error messages are:

* When clicking on an MFA protected button, if we fail to request a new email OTP code due to rate limiting or some server error, then the modal does not show up, and an alert bar describing the error is shown at the top of the page (will be handled by `MFAProtectedActionGuard`, not the modal UI component).

I'd say this is correct assumption.

* An error **banner** shows up inside the modal if we fail to resend code or fail to verify the code due to rate limiting or some server error.

I think closing the modal, and just using the aforementioned error banner is fine. Basically if the error is unexpected / atypical then using the generic error banner seems fine to me.

* An error **tooltip** shows up on the input if the code submitted is invalid or expired.

Yep seems consistent with other code entry user experiences. This is an expected / typical error state, so I think handling it like this makes sense.

* A success banner shows up inside the modal if the confirmation code is successfully resent.

It might also make sense to close the modal and show an alert bar when encountering a server error, and the success banner can also be an alert bar, so I want the reviewer's opinion on this!

I think in the case of an unexpected server error, closing the modal and showing the error banner makes sense. In the case of the success state, it seems a bit trickier. Like you are saying, most of the flows in settings just use the success alert banner like this:
image

However, I have a feeling this would feel off since we basically are just continuing the flow... I couldn't find an existing example of what to do here. The verified session guard appears to just send the user on to the next step without any success message, so for now let's leave out the success message. I think it'll be obvious enough that user has provided the code when they are directed to the next step. At least that's my opinion. Once this is available on stage, it might be good to ping UX about it. These sorts of things are easier to decide upon when seeing them in action.


await userEvent.click(screen.getByTestId('modal-dismiss'));

expect(onDismiss).toHaveBeenCalled();
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.

Nothing to do here, but just a call out. I think the component that invokes this model should display a banner alerting the user that in order to proceed they must provide verification. Again, maybe ping UX on what message to display on dismissal if unsure.

Copy link
Copy Markdown
Contributor

@dschom dschom left a comment

Choose a reason for hiding this comment

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

Thanks for the updates! R+WC

  • Make the time frame a prop and address the l10n feedback.
  • Address the l10n feedback

@MagentaManifold
Copy link
Copy Markdown
Contributor Author

@dschom Thanks for the review! By "success banner" I mean for successfully resending the code, not for successful verification (I agree that we don't need a success message for verification since continuing is enough). I am using a ready-made resend success banner component (ResendCodeSuccessBanner, used in several other pages as well). I guess it makes sense to keep the consistency

@MagentaManifold MagentaManifold force-pushed the FXA-12302 branch 2 times, most recently from 27a50ce to dfc9720 Compare August 29, 2025 18:41
@dschom dschom mentioned this pull request Sep 2, 2025
4 tasks
Comment on lines +11 to +15
modal-mfa-protected-instruction =
Enter the code that was sent to <email>{ $email }</email> within { $expirationTime ->
[one] 1 minute
*[other] { $expirationTime } minutes
}.
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.

Suggested change
modal-mfa-protected-instruction =
Enter the code that was sent to <email>{ $email }</email> within { $expirationTime ->
[one] 1 minute
*[other] { $expirationTime } minutes
}.
modal-mfa-protected-instruction =
{ $expirationTime ->
[one] Enter the code that was sent to <email>{ $email }</email> within { $expirationTime } minute.
*[other] Enter the code that was sent to <email>{ $email }</email> within { $expirationTime } minutes.
}

While technically valid syntax, it's best practice to have the whole string for each variant of the selector. (This is better supported by Pontoon and easier to localize.)

# email (String) - the user's email
# expirationTime (Number) - the expiration time in minutes
modal-mfa-protected-instruction = { $expirationTime ->
[one] Enter the code that was sent to <email>{ $email }</email> within 1 minute.
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.

Suggested change
[one] Enter the code that was sent to <email>{ $email }</email> within 1 minute.
[one] Enter the code that was sent to <email>{ $email }</email> within { $expirationTime } minute.

Minor nit. Feel free to merge after fixing.

Because:

* we need a modal for MFA confirmation code input for `MfaProtectedActionGuard`

This commit:

* creates a new ModalMfaProtected, with stories and unit tests.

Closes #FXA-12302
@MagentaManifold MagentaManifold merged commit 3356b39 into main Sep 2, 2025
19 checks passed
@MagentaManifold MagentaManifold deleted the FXA-12302 branch September 2, 2025 20:25
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