From 9d557ff0bf2e30e3c252c86d6a5b1af6678157aa Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Fri, 23 Oct 2026 08:47:51 -0400 Subject: [PATCH 1/2] fix(mosaic): make only Resend a button and tighten Back spacing in reverification --- .changeset/chubby-donkeys-write.md | 2 ++ .../reverification/reverification.messages.ts | 9 +++++--- .../reverification/reverification.view.tsx | 7 ++++-- .../steps/reverification-method-picker.tsx | 21 +++++++++--------- .../steps/reverification-otp.tsx | 22 +++++++++++-------- .../src/stories/reverification.stories.tsx | 3 ++- 6 files changed, 38 insertions(+), 26 deletions(-) create mode 100644 .changeset/chubby-donkeys-write.md diff --git a/.changeset/chubby-donkeys-write.md b/.changeset/chubby-donkeys-write.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/chubby-donkeys-write.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/packages/mosaic/src/features/reverification/reverification.messages.ts b/packages/mosaic/src/features/reverification/reverification.messages.ts index 4c6213dde2f..064d1062011 100644 --- a/packages/mosaic/src/features/reverification/reverification.messages.ts +++ b/packages/mosaic/src/features/reverification/reverification.messages.ts @@ -27,7 +27,8 @@ export const reverificationMessages = { emailCode: { description: 'Enter the code sent to your email to continue', formTitle: 'Verification code', - resendButton: 'Didn’t receive a code? Resend', + resendButton: 'Resend', + resendText: 'Didn’t receive a code?', title: 'Verification required', }, passkey: { @@ -44,13 +45,15 @@ export const reverificationMessages = { phoneCode: { description: 'Enter the code sent to your phone to continue', formTitle: 'Verification code', - resendButton: 'Didn’t receive a code? Resend', + resendButton: 'Resend', + resendText: 'Didn’t receive a code?', title: 'Verification required', }, phoneCodeMfa: { description: 'Enter the code sent to your phone to continue', formTitle: 'Verification code', - resendButton: 'Didn’t receive a code? Resend', + resendButton: 'Resend', + resendText: 'Didn’t receive a code?', title: 'Verification required', }, error: { diff --git a/packages/mosaic/src/features/reverification/reverification.view.tsx b/packages/mosaic/src/features/reverification/reverification.view.tsx index 823052e6f59..e5c3a51bcf9 100644 --- a/packages/mosaic/src/features/reverification/reverification.view.tsx +++ b/packages/mosaic/src/features/reverification/reverification.view.tsx @@ -150,11 +150,14 @@ export function ReverificationView({ pendingLabel: m.verifying, }; const hasAlternatives = methods.length > 0; - const resendLabel = otpChannel === 'phone' ? m.phoneCode.resendButton : m.emailCode.resendButton; + const resendCopy = otpChannel === 'phone' ? m.phoneCode : m.emailCode; const resend = otpChannel === 'email' || otpChannel === 'phone' ? { - label: resendRemainingSeconds ? `${resendLabel} (${resendRemainingSeconds})` : resendLabel, + text: resendCopy.resendText, + label: resendRemainingSeconds + ? `${resendCopy.resendButton} (${resendRemainingSeconds})` + : resendCopy.resendButton, disabled: !canResend || isPending, onClick: onResend, } diff --git a/packages/mosaic/src/features/reverification/steps/reverification-method-picker.tsx b/packages/mosaic/src/features/reverification/steps/reverification-method-picker.tsx index a4ff345ebc4..e8625e5446e 100644 --- a/packages/mosaic/src/features/reverification/steps/reverification-method-picker.tsx +++ b/packages/mosaic/src/features/reverification/steps/reverification-method-picker.tsx @@ -100,18 +100,17 @@ export function ReverificationMethodPicker({ ); })} + {onBack ? ( + + ) : null} - {onBack ? ( - - ) : null} void; @@ -93,15 +94,18 @@ export function ReverificationOTP({ {errorMessage} {resend ? ( - + + {resend.text}{' '} + + ) : null} diff --git a/packages/swingset/src/stories/reverification.stories.tsx b/packages/swingset/src/stories/reverification.stories.tsx index d33912d229d..98558a9f838 100644 --- a/packages/swingset/src/stories/reverification.stories.tsx +++ b/packages/swingset/src/stories/reverification.stories.tsx @@ -417,7 +417,8 @@ function OTPPanel({ resend={ renderResend ? { - label: isResending ? 'Sending a new code…' : 'Didn’t receive a code? Resend', + text: 'Didn’t receive a code?', + label: isResending ? 'Sending a new code…' : 'Resend', disabled: isResending, onClick: () => setValue(''), } From 0ae6ad22f82db3b0e4abb27764dd6e6fd816b43f Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Fri, 23 Oct 2026 09:59:22 -0400 Subject: [PATCH 2/2] fix(mosaic): move resend and add-phone links into field descriptions and use tabular countdowns --- .../reverification/reverification.messages.ts | 3 ++ .../reverification/reverification.view.tsx | 18 +++++-- .../steps/reverification-otp.tsx | 4 +- .../user-profile-add-email.dialog.test.tsx | 10 ++-- .../user-profile-add-email.dialog.tsx | 21 +++++---- .../user-profile-add-email.messages.ts | 5 +- .../user-profile-mfa.feature.test.tsx | 2 +- .../user-profile-add-sms.view.tsx | 22 +++++---- .../user-profile-add-phone.dialog.test.tsx | 10 ++-- ...ser-profile-phone-section.feature.test.tsx | 2 +- .../user-profile-add-phone.messages.ts | 5 +- .../user-profile/user-profile-phone.steps.tsx | 47 ++++++++++--------- .../user-profile-profile-panel.styles.ts | 3 -- 13 files changed, 89 insertions(+), 63 deletions(-) diff --git a/packages/mosaic/src/features/reverification/reverification.messages.ts b/packages/mosaic/src/features/reverification/reverification.messages.ts index 064d1062011..1df87ac6755 100644 --- a/packages/mosaic/src/features/reverification/reverification.messages.ts +++ b/packages/mosaic/src/features/reverification/reverification.messages.ts @@ -28,6 +28,7 @@ export const reverificationMessages = { description: 'Enter the code sent to your email to continue', formTitle: 'Verification code', resendButton: 'Resend', + resendCountdown: 'Resend ({seconds})', resendText: 'Didn’t receive a code?', title: 'Verification required', }, @@ -46,6 +47,7 @@ export const reverificationMessages = { description: 'Enter the code sent to your phone to continue', formTitle: 'Verification code', resendButton: 'Resend', + resendCountdown: 'Resend ({seconds})', resendText: 'Didn’t receive a code?', title: 'Verification required', }, @@ -53,6 +55,7 @@ export const reverificationMessages = { description: 'Enter the code sent to your phone to continue', formTitle: 'Verification code', resendButton: 'Resend', + resendCountdown: 'Resend ({seconds})', resendText: 'Didn’t receive a code?', title: 'Verification required', }, diff --git a/packages/mosaic/src/features/reverification/reverification.view.tsx b/packages/mosaic/src/features/reverification/reverification.view.tsx index e5c3a51bcf9..863a17448b6 100644 --- a/packages/mosaic/src/features/reverification/reverification.view.tsx +++ b/packages/mosaic/src/features/reverification/reverification.view.tsx @@ -1,3 +1,4 @@ +import * as stylex from '@stylexjs/stylex'; import type { Ref } from 'react'; import { Button } from '../../components/button'; @@ -10,7 +11,8 @@ import { SecurityLockSquare } from '../../icons/glyphs/security-lock-square'; import { SecurityPasskey } from '../../icons/glyphs/security-passkey'; import { SecurityPhone } from '../../icons/glyphs/security-phone'; import type { MosaicMessages } from '../../localization'; -import { fill, useMessages } from '../../localization'; +import { fill, rich, useMessages } from '../../localization'; +import { tabularNumbersStyle } from '../../styles/typography.styles'; import { styles } from './reverification.styles'; import type { ReverificationErrorReason, @@ -155,9 +157,17 @@ export function ReverificationView({ otpChannel === 'email' || otpChannel === 'phone' ? { text: resendCopy.resendText, - label: resendRemainingSeconds - ? `${resendCopy.resendButton} (${resendRemainingSeconds})` - : resendCopy.resendButton, + label: resendRemainingSeconds ? ( + + {rich(resendCopy.resendCountdown, { + values: { + seconds: {resendRemainingSeconds}, + }, + })} + + ) : ( + resendCopy.resendButton + ), disabled: !canResend || isPending, onClick: onResend, } diff --git a/packages/mosaic/src/features/reverification/steps/reverification-otp.tsx b/packages/mosaic/src/features/reverification/steps/reverification-otp.tsx index 3f80d9c0b27..122b393f759 100644 --- a/packages/mosaic/src/features/reverification/steps/reverification-otp.tsx +++ b/packages/mosaic/src/features/reverification/steps/reverification-otp.tsx @@ -1,5 +1,5 @@ import { useMergeRefs } from '@floating-ui/react'; -import type { FormEvent, Ref } from 'react'; +import type { FormEvent, ReactNode, Ref } from 'react'; import { useId } from 'react'; import { Button, SubmitButton } from '../../../components/button'; @@ -19,7 +19,7 @@ export interface ReverificationOtpMessages { export interface ReverificationOtpResend { text: string; - label: string; + label: ReactNode; disabled?: boolean; onClick: () => void; } diff --git a/packages/mosaic/src/features/user-profile/user-profile-email-section/__tests__/user-profile-add-email.dialog.test.tsx b/packages/mosaic/src/features/user-profile/user-profile-email-section/__tests__/user-profile-add-email.dialog.test.tsx index d7ab0aff862..32e7e381e30 100644 --- a/packages/mosaic/src/features/user-profile/user-profile-email-section/__tests__/user-profile-add-email.dialog.test.tsx +++ b/packages/mosaic/src/features/user-profile/user-profile-email-section/__tests__/user-profile-add-email.dialog.test.tsx @@ -147,7 +147,7 @@ describe('UserProfileAddEmailDialog', () => { const verify = screen.getByRole('button', { name: 'Verify', exact: true }); expect(verify).toHaveAttribute('aria-busy', 'true'); await user.click(verify); - await user.click(screen.getByRole('button', { name: 'Didn’t receive a code? Resend' })); + await user.click(screen.getByRole('button', { name: 'Resend' })); expect(props.onSubmit).not.toHaveBeenCalled(); expect(props.onResend).not.toHaveBeenCalled(); @@ -159,13 +159,15 @@ describe('UserProfileAddEmailDialog', () => { const field = screen.getByRole('textbox', { name: step === 'email' ? 'Email' : 'Verification code' }); expect(field).toHaveAttribute('aria-invalid', 'true'); const describedControl = step === 'verify' ? screen.getByRole('group', { name: 'Verification code' }) : field; - expect(describedControl).toHaveAccessibleDescription('Please try again.'); + expect(describedControl).toHaveAccessibleDescription( + step === 'verify' ? 'Please try again. Didn’t receive a code? Resend' : 'Please try again.', + ); }); it('allows resending only after the countdown and the current request finish', async () => { const user = userEvent.setup(); const { props, rerender } = renderView({ step: 'verify', resendSeconds: 12 }); - await user.click(screen.getByRole('button', { name: 'Didn’t receive a code? Resend (12)' })); + await user.click(screen.getByRole('button', { name: 'Resend (12)' })); expect(props.onResend).not.toHaveBeenCalled(); rerender( @@ -176,7 +178,7 @@ describe('UserProfileAddEmailDialog', () => { /> , ); - await user.click(screen.getByRole('button', { name: 'Didn’t receive a code? Resend' })); + await user.click(screen.getByRole('button', { name: 'Resend' })); expect(props.onResend).toHaveBeenCalledOnce(); rerender( diff --git a/packages/mosaic/src/features/user-profile/user-profile-email-section/user-profile-add-email.dialog.tsx b/packages/mosaic/src/features/user-profile/user-profile-email-section/user-profile-add-email.dialog.tsx index 718e6cd987c..095c9e72684 100644 --- a/packages/mosaic/src/features/user-profile/user-profile-email-section/user-profile-add-email.dialog.tsx +++ b/packages/mosaic/src/features/user-profile/user-profile-email-section/user-profile-add-email.dialog.tsx @@ -16,9 +16,9 @@ import { Otp } from '../../../components/otp'; import { Text } from '../../../components/text'; import { ArrowUpRight } from '../../../icons/glyphs/arrow-up-right'; import { fill, rich, useMessages } from '../../../localization'; +import { tabularNumbersStyle } from '../../../styles/typography.styles'; import type { ReverificationController } from '../../reverification'; import { Reverification } from '../../reverification'; -import { styles as panelStyles } from '../user-profile-profile-panel.styles'; const styles = stylex.create({ ssoConnection: { @@ -253,13 +253,16 @@ function VerifyEmailStep(props: VerifyEmailStepProps) { {props.errorMessage} - + + {m.verify.resendText}{' '} + + @@ -435,7 +438,7 @@ function ResendButton(props: ResendButtonProps) { ) : resendSeconds > 0 ? ( {rich(props.labels.resendCountdown, { - values: { seconds: {resendSeconds} }, + values: { seconds: {resendSeconds} }, })} ) : ( diff --git a/packages/mosaic/src/features/user-profile/user-profile-email-section/user-profile-add-email.messages.ts b/packages/mosaic/src/features/user-profile/user-profile-email-section/user-profile-add-email.messages.ts index 7cd6329823a..ac0bcf3bd32 100644 --- a/packages/mosaic/src/features/user-profile/user-profile-email-section/user-profile-add-email.messages.ts +++ b/packages/mosaic/src/features/user-profile/user-profile-email-section/user-profile-add-email.messages.ts @@ -13,9 +13,10 @@ export const userProfileAddEmailMessages = { submit: 'Verify', pending: 'Verifying', cancel: 'Cancel', - resend: 'Didn’t receive a code? Resend', + resend: 'Resend', + resendText: 'Didn’t receive a code?', resending: 'Sending code…', - resendCountdown: 'Didn’t receive a code? Resend ({seconds})', + resendCountdown: 'Resend ({seconds})', }, link: { title: 'Verify your email', diff --git a/packages/mosaic/src/features/user-profile/user-profile-mfa-section/__tests__/user-profile-mfa.feature.test.tsx b/packages/mosaic/src/features/user-profile/user-profile-mfa-section/__tests__/user-profile-mfa.feature.test.tsx index 44f54d227b6..d7504997b29 100644 --- a/packages/mosaic/src/features/user-profile/user-profile-mfa-section/__tests__/user-profile-mfa.feature.test.tsx +++ b/packages/mosaic/src/features/user-profile/user-profile-mfa-section/__tests__/user-profile-mfa.feature.test.tsx @@ -141,7 +141,7 @@ describe('User profile MFA', () => { expect(screen.getByText('You cannot set your last identification as second factor.')).toBeVisible(), ); expect(screen.getByRole('combobox', { name: /Phone number/ })).toHaveAccessibleDescription( - 'You cannot set your last identification as second factor.', + 'Add a new phone number You cannot set your last identification as second factor.', ); expect(fapi.mfa.phoneUpdates).toHaveLength(0); expect(screen.getByRole('button', { name: 'Back' })).toBeVisible(); diff --git a/packages/mosaic/src/features/user-profile/user-profile-mfa-section/user-profile-add-sms.view.tsx b/packages/mosaic/src/features/user-profile/user-profile-mfa-section/user-profile-add-sms.view.tsx index d3653871c4b..8380cc82e8d 100644 --- a/packages/mosaic/src/features/user-profile/user-profile-mfa-section/user-profile-add-sms.view.tsx +++ b/packages/mosaic/src/features/user-profile/user-profile-mfa-section/user-profile-add-sms.view.tsx @@ -141,17 +141,19 @@ function SelectPhoneStep(props: UserProfileAddSmsViewProps & { inputRef?: Ref {props.errorMessage} + + + - + + {m.verify.resendText}{' '} + + diff --git a/packages/mosaic/src/features/user-profile/user-profile-profile-panel.styles.ts b/packages/mosaic/src/features/user-profile/user-profile-profile-panel.styles.ts index 9de819edbf7..1fcba8de2c0 100644 --- a/packages/mosaic/src/features/user-profile/user-profile-profile-panel.styles.ts +++ b/packages/mosaic/src/features/user-profile/user-profile-profile-panel.styles.ts @@ -3,9 +3,6 @@ import * as stylex from '@stylexjs/stylex'; import { space } from '../../tokens.stylex'; export const styles = stylex.create({ - countdown: { - fontVariantNumeric: 'tabular-nums', - }, contactValue: { gap: space['2'], alignItems: 'center',