From 83ef9d4bd799a65cf1a1a79e7a98ed37dd1bad76 Mon Sep 17 00:00:00 2001 From: Jameson Date: Tue, 26 May 2026 11:12:38 -0600 Subject: [PATCH 01/10] feat: implement ReturnUserExperience component and integrate with Redux state --- .../ReturnUserExperience-test.tsx | 146 +++++++++++++++++ .../ReturnUserExperience.test.tsx | 152 ++++++++++++++++++ 2 files changed, 298 insertions(+) create mode 100644 src/ReturnUserExperience/ReturnUserExperience-test.tsx create mode 100644 src/ReturnUserExperience/ReturnUserExperience.test.tsx diff --git a/src/ReturnUserExperience/ReturnUserExperience-test.tsx b/src/ReturnUserExperience/ReturnUserExperience-test.tsx new file mode 100644 index 0000000000..32565f23ea --- /dev/null +++ b/src/ReturnUserExperience/ReturnUserExperience-test.tsx @@ -0,0 +1,146 @@ +import React from 'react' +import { describe, it, expect } from 'vitest' +import { render, screen } from 'src/utilities/testingLibrary' +import { ReturnUserExperience } from './ReturnUserExperience' + +describe('ReturnUserExperience', () => { + const mockAppName = 'Test Financial App' + + const preloadedState = { + profiles: { + client: { + oauth_app_name: mockAppName, + }, + }, + } + + describe('rendering', () => { + it('should render the component without crashing', () => { + render(, { preloadedState }) + expect(screen.getByText('Connect your accounts')).toBeInTheDocument() + }) + + it('should render the development warning alert', () => { + render(, { preloadedState }) + const alert = screen.getByText('This feature is currently in development.') + expect(alert).toBeInTheDocument() + }) + + it('should render the main heading', () => { + render(, { preloadedState }) + const heading = screen.getByRole('heading', { level: 2 }) + expect(heading).toHaveTextContent('Connect your accounts') + }) + + it('should render the subtitle with app name interpolation', () => { + render(, { preloadedState }) + const subtitle = screen.getByText(new RegExp(mockAppName)) + expect(subtitle).toBeInTheDocument() + expect(subtitle).toHaveTextContent(`${mockAppName} uses MX to connect your accounts.`) + }) + + it('should render the learn more link', () => { + render(, { preloadedState }) + const link = screen.getByRole('link', { name: /learn more about mx/i }) + expect(link).toBeInTheDocument() + expect(link).toHaveAttribute('href', 'https://mx.com/learn-more') + expect(link).toHaveAttribute('target', '_blank') + expect(link).toHaveAttribute('rel', 'noopener noreferrer') + }) + + it('should render the MX sign in button', () => { + render(, { preloadedState }) + const button = screen.getByRole('button', { name: /connect faster by signing into mx/i }) + expect(button).toBeInTheDocument() + expect(button).toHaveClass('MuiButton-contained') + }) + + it('should render the guest sign in button', () => { + render(, { preloadedState }) + const button = screen.getByRole('button', { name: /continue as guest/i }) + expect(button).toBeInTheDocument() + expect(button).toHaveClass('MuiButton-outlined') + }) + + it('should render both buttons as full width', () => { + render(, { preloadedState }) + const buttons = screen.getAllByRole('button') + buttons.forEach((button) => { + if ( + button.textContent?.includes('Connect faster') || + button.textContent?.includes('Continue as guest') + ) { + expect(button).toHaveClass('MuiButton-fullWidth') + } + }) + }) + }) + + describe('Redux state integration', () => { + it('should use the oauth_app_name from Redux state', () => { + render(, { preloadedState }) + expect(screen.getByText(new RegExp(mockAppName))).toBeInTheDocument() + }) + + it('should display default app name when oauth_app_name is not provided', () => { + const emptyState = { + profiles: { + client: {}, + }, + } + render(, { preloadedState: emptyState }) + expect(screen.getByText(/This app uses MX to connect your accounts/)).toBeInTheDocument() + }) + + it('should display default app name when oauth_app_name is null', () => { + const nullState = { + profiles: { + client: { + oauth_app_name: null, + }, + }, + } + render(, { preloadedState: nullState }) + expect(screen.getByText(/This app uses MX to connect your accounts/)).toBeInTheDocument() + }) + }) + + describe('button interactions', () => { + it('should render the MX sign in button as clickable', async () => { + const { user } = render(, { preloadedState }) + const button = screen.getByRole('button', { name: /connect faster by signing into mx/i }) + expect(button).not.toBeDisabled() + await user.click(button) + }) + + it('should render the guest continue button as clickable', async () => { + const { user } = render(, { preloadedState }) + const button = screen.getByRole('button', { name: /continue as guest/i }) + expect(button).not.toBeDisabled() + await user.click(button) + }) + }) + + describe('accessibility', () => { + it('should have proper heading hierarchy', () => { + render(, { preloadedState }) + const heading = screen.getByRole('heading', { level: 2 }) + expect(heading).toHaveTextContent('Connect your accounts') + }) + + it('should have accessible buttons', () => { + render(, { preloadedState }) + const buttons = screen.getAllByRole('button') + expect(buttons.length).toBeGreaterThanOrEqual(2) + buttons.forEach((button) => { + expect(button).toHaveAccessibleName() + }) + }) + + it('should have an accessible learn more link', () => { + render(, { preloadedState }) + const link = screen.getByRole('link', { name: /learn more about mx/i }) + expect(link).toHaveAccessibleName() + }) + }) +}) diff --git a/src/ReturnUserExperience/ReturnUserExperience.test.tsx b/src/ReturnUserExperience/ReturnUserExperience.test.tsx new file mode 100644 index 0000000000..c3030aa8cf --- /dev/null +++ b/src/ReturnUserExperience/ReturnUserExperience.test.tsx @@ -0,0 +1,152 @@ +import React from 'react' +import { render, screen } from 'src/utilities/testingLibrary' +import { ReturnUserExperience } from './ReturnUserExperience' +import { initialState } from 'src/services/mockedData' + +describe('ReturnUserExperience', () => { + const mockAppName = 'Test Financial App' + + const preloadedState = { + ...initialState, + profiles: { + ...initialState.profiles, + client: { + ...initialState.profiles.client, + oauth_app_name: mockAppName, + }, + }, + } + + describe('rendering', () => { + it('should render the component without crashing', () => { + render(, { preloadedState }) + expect(screen.getByText('Connect your accounts')).toBeInTheDocument() + }) + + it('should render the main heading', () => { + render(, { preloadedState }) + const heading = screen.getByRole('heading', { level: 2 }) + expect(heading).toHaveTextContent('Connect your accounts') + }) + + it('should render the subtitle with app name interpolation', () => { + render(, { preloadedState }) + const subtitle = screen.getByText(new RegExp(mockAppName)) + expect(subtitle).toBeInTheDocument() + expect(subtitle).toHaveTextContent(`${mockAppName} uses MX to connect your accounts.`) + }) + + it('should render the learn more link', () => { + render(, { preloadedState }) + const link = screen.getByRole('link', { name: /learn more about mx/i }) + expect(link).toBeInTheDocument() + expect(link).toHaveAttribute('href', 'https://mx.com/learn-more') + expect(link).toHaveAttribute('target', '_blank') + expect(link).toHaveAttribute('rel', 'noopener noreferrer') + }) + + it('should render the MX sign in button', () => { + render(, { preloadedState }) + const button = screen.getByRole('button', { name: /connect faster by signing into mx/i }) + expect(button).toBeInTheDocument() + expect(button).toHaveClass('MuiButton-contained') + }) + + it('should render the guest sign in button', () => { + render(, { preloadedState }) + const button = screen.getByRole('button', { name: /continue as guest/i }) + expect(button).toBeInTheDocument() + expect(button).toHaveClass('MuiButton-outlined') + }) + + it('should render both buttons as full width', () => { + render(, { preloadedState }) + const buttons = screen.getAllByRole('button') + buttons.forEach((button) => { + if ( + button.textContent?.includes('Connect faster') || + button.textContent?.includes('Continue as guest') + ) { + expect(button).toHaveClass('MuiButton-fullWidth') + } + }) + }) + }) + + describe('Redux state integration', () => { + const undefinedState = { + ...initialState, + profiles: { + ...initialState.profiles, + client: { + ...initialState.profiles.client, + oauth_app_name: undefined, + }, + }, + } + const nullState = { + ...initialState, + profiles: { + ...initialState.profiles, + client: { + ...initialState.profiles.client, + oauth_app_name: null, + }, + }, + } + + it('should use the oauth_app_name from Redux state', () => { + render(, { preloadedState }) + expect(screen.getByText(new RegExp(mockAppName))).toBeInTheDocument() + }) + + it('should display default app name when oauth_app_name is not provided', () => { + render(, { preloadedState: undefinedState }) + expect(screen.getByText(/This app uses MX to connect your accounts/)).toBeInTheDocument() + }) + + it('should display default app name when oauth_app_name is null', () => { + render(, { preloadedState: nullState }) + expect(screen.getByText(/This app uses MX to connect your accounts/)).toBeInTheDocument() + }) + }) + + describe('button interactions', () => { + it('should render the MX sign in button as clickable', async () => { + const { user } = render(, { preloadedState }) + const button = screen.getByRole('button', { name: /connect faster by signing into mx/i }) + expect(button).not.toBeDisabled() + await user.click(button) + }) + + it('should render the guest continue button as clickable', async () => { + const { user } = render(, { preloadedState }) + const button = screen.getByRole('button', { name: /continue as guest/i }) + expect(button).not.toBeDisabled() + await user.click(button) + }) + }) + + describe('accessibility', () => { + it('should have proper heading hierarchy', () => { + render(, { preloadedState }) + const heading = screen.getByRole('heading', { level: 2 }) + expect(heading).toHaveTextContent('Connect your accounts') + }) + + it('should have accessible buttons', () => { + render(, { preloadedState }) + const buttons = screen.getAllByRole('button') + expect(buttons.length).toBeGreaterThanOrEqual(2) + buttons.forEach((button) => { + expect(button).toHaveAccessibleName() + }) + }) + + it('should have an accessible learn more link', () => { + render(, { preloadedState }) + const link = screen.getByRole('link', { name: /learn more about mx/i }) + expect(link).toHaveAccessibleName() + }) + }) +}) From c288dd11d4427620ad598d73da1855a9ebffc6fa Mon Sep 17 00:00:00 2001 From: Jameson Date: Tue, 26 May 2026 11:28:49 -0600 Subject: [PATCH 02/10] feat: add RuxPhoneNumber component for phone number input in ReturnUserExperience --- .../ReturnUserExperience.tsx | 9 +++ src/ReturnUserExperience/RuxPhoneNumber.tsx | 55 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 src/ReturnUserExperience/RuxPhoneNumber.tsx diff --git a/src/ReturnUserExperience/ReturnUserExperience.tsx b/src/ReturnUserExperience/ReturnUserExperience.tsx index 7e594ef3d7..4e7a701ec6 100644 --- a/src/ReturnUserExperience/ReturnUserExperience.tsx +++ b/src/ReturnUserExperience/ReturnUserExperience.tsx @@ -3,6 +3,7 @@ import { useSelector } from 'react-redux' import styles from './returnUserExperience.module.css' import RuxInfo from 'src/ReturnUserExperience/RuxInfo' +import { RuxPhoneNumber } from 'src/ReturnUserExperience/RuxPhoneNumber' import { Stack } from '@mui/material' import { Icon } from '@mxenabled/mxui' @@ -23,6 +24,7 @@ export const RUXViews = { export const ReturnUserExperience = React.forwardRef(() => { const [view, setView] = React.useState<(typeof RUXViews)[keyof typeof RUXViews]>(RUXViews.INFO) + const [userEnteredPhone, setUserEnteredPhone] = React.useState('') const clientGuid = useSelector((state: RootState) => state.profiles.client.guid) const sendAnalyticsEvent = useAnalyticsEvent() @@ -47,6 +49,13 @@ export const ReturnUserExperience = React.forwardRef(() => { )} {view === RUXViews.INFO && } + + {view === RUXViews.PHONE_NUMBER && ( + + )} ) }) diff --git a/src/ReturnUserExperience/RuxPhoneNumber.tsx b/src/ReturnUserExperience/RuxPhoneNumber.tsx new file mode 100644 index 0000000000..f017cc1117 --- /dev/null +++ b/src/ReturnUserExperience/RuxPhoneNumber.tsx @@ -0,0 +1,55 @@ +import React from 'react' +import InputAdornment from '@mui/material/InputAdornment' +import Stack from '@mui/material/Stack' +import Button from '@mui/material/Button' +import { Text } from '@mxenabled/mxui' +import { Link } from '@mui/material' + +import { TextField } from 'src/privacy/input' +import { __ } from 'src/utilities/Intl' +import styles from './returnUserExperience.module.css' + +export const RuxPhoneNumber = ({ + userEnteredPhone, + setUserEnteredPhone, +}: { + userEnteredPhone: string + setUserEnteredPhone: (phone: string) => void +}) => { + return ( + <> + +
+ Phone + + +1 + +
+ + ), + }} + onChange={(e: React.ChangeEvent) => setUserEnteredPhone(e.target.value)} + required={true} + value={userEnteredPhone} + /> + + + + {/* --TR: Full string 'By selecting "Get code", you agree to MX's Terms & Conditions' */} + {__('By selecting "Get code", you agree to')} + + {/* TODO: Do we translate this below? */} + {__("MX's Terms & Conditions")} + + . + + + + + ) +} + +export default RuxPhoneNumber From 30f7680d1d1825d61e4e4b3b3f35b26ab110cbdc Mon Sep 17 00:00:00 2001 From: Jameson Date: Tue, 26 May 2026 12:46:37 -0600 Subject: [PATCH 03/10] feat: update button text in RuxPhoneNumber component for improved clarity --- src/ReturnUserExperience/RuxPhoneNumber.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ReturnUserExperience/RuxPhoneNumber.tsx b/src/ReturnUserExperience/RuxPhoneNumber.tsx index f017cc1117..2197b4be22 100644 --- a/src/ReturnUserExperience/RuxPhoneNumber.tsx +++ b/src/ReturnUserExperience/RuxPhoneNumber.tsx @@ -46,7 +46,10 @@ export const RuxPhoneNumber = ({ . - + + ) From 7bfcc74d15248253e10a77064af9a763a8c807c8 Mon Sep 17 00:00:00 2001 From: Jameson Date: Thu, 28 May 2026 13:07:01 -0600 Subject: [PATCH 04/10] feat: update button in RuxPhoneNumber for full width and improve description in RuxTitle for clarity --- src/ReturnUserExperience/RuxPhoneNumber.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ReturnUserExperience/RuxPhoneNumber.tsx b/src/ReturnUserExperience/RuxPhoneNumber.tsx index 2197b4be22..a97ebea70c 100644 --- a/src/ReturnUserExperience/RuxPhoneNumber.tsx +++ b/src/ReturnUserExperience/RuxPhoneNumber.tsx @@ -46,7 +46,9 @@ export const RuxPhoneNumber = ({ . - + From bddd813af8ff2d3b59c8d478b7644f7478c59ef0 Mon Sep 17 00:00:00 2001 From: Jameson Date: Thu, 28 May 2026 13:22:18 -0600 Subject: [PATCH 05/10] feat: enhance RuxPhoneNumber and RuxInfo components with improved layout and additional app information --- src/ReturnUserExperience/RuxPhoneNumber.tsx | 26 ++++++++++++++++++- .../returnUserExperience.module.css | 2 +- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/ReturnUserExperience/RuxPhoneNumber.tsx b/src/ReturnUserExperience/RuxPhoneNumber.tsx index a97ebea70c..e218ae0b81 100644 --- a/src/ReturnUserExperience/RuxPhoneNumber.tsx +++ b/src/ReturnUserExperience/RuxPhoneNumber.tsx @@ -1,12 +1,15 @@ import React from 'react' +import { useSelector } from 'react-redux' + import InputAdornment from '@mui/material/InputAdornment' import Stack from '@mui/material/Stack' import Button from '@mui/material/Button' import { Text } from '@mxenabled/mxui' import { Link } from '@mui/material' -import { TextField } from 'src/privacy/input' +import { RootState } from 'src/redux/Store' import { __ } from 'src/utilities/Intl' +import { TextField } from 'src/privacy/input' import styles from './returnUserExperience.module.css' export const RuxPhoneNumber = ({ @@ -16,8 +19,29 @@ export const RuxPhoneNumber = ({ userEnteredPhone: string setUserEnteredPhone: (phone: string) => void }) => { + const appName = useSelector( + (state: RootState) => state.profiles.client.oauth_app_name || 'This app', + ) + return ( <> + + + {__('Connect your accounts')} + + + {__('%1 uses MX to connect your accounts.', appName)} + + {__('Learn more about MX.')} + + + + Date: Mon, 1 Jun 2026 11:11:21 -0600 Subject: [PATCH 06/10] feat: update RuxPhoneNumber and ReturnUserExperience components for improved layout and analytics tracking --- .../ReturnUserExperience.tsx | 12 ++- src/ReturnUserExperience/RuxPhoneNumber.tsx | 80 ++++++++++++++----- .../returnUserExperience.module.css | 4 +- src/const/Analytics.js | 1 + 4 files changed, 71 insertions(+), 26 deletions(-) diff --git a/src/ReturnUserExperience/ReturnUserExperience.tsx b/src/ReturnUserExperience/ReturnUserExperience.tsx index 4e7a701ec6..8bd26f1a4f 100644 --- a/src/ReturnUserExperience/ReturnUserExperience.tsx +++ b/src/ReturnUserExperience/ReturnUserExperience.tsx @@ -38,10 +38,14 @@ export const ReturnUserExperience = React.forwardRef(() => {
{view !== RUXViews.LIST && ( -
- -
- + {view === RUXViews.INFO && ( + <> +
+ +
+ + + )}
diff --git a/src/ReturnUserExperience/RuxPhoneNumber.tsx b/src/ReturnUserExperience/RuxPhoneNumber.tsx index e218ae0b81..41d3a4f056 100644 --- a/src/ReturnUserExperience/RuxPhoneNumber.tsx +++ b/src/ReturnUserExperience/RuxPhoneNumber.tsx @@ -1,15 +1,16 @@ import React from 'react' -import { useSelector } from 'react-redux' +import { useTheme } from '@mui/material' import InputAdornment from '@mui/material/InputAdornment' import Stack from '@mui/material/Stack' import Button from '@mui/material/Button' import { Text } from '@mxenabled/mxui' import { Link } from '@mui/material' -import { RootState } from 'src/redux/Store' import { __ } from 'src/utilities/Intl' +import useAnalyticsPath from 'src/hooks/useAnalyticsPath' import { TextField } from 'src/privacy/input' +import { PageviewInfo } from 'src/const/Analytics' import styles from './returnUserExperience.module.css' export const RuxPhoneNumber = ({ @@ -19,21 +20,27 @@ export const RuxPhoneNumber = ({ userEnteredPhone: string setUserEnteredPhone: (phone: string) => void }) => { - const appName = useSelector( - (state: RootState) => state.profiles.client.oauth_app_name || 'This app', - ) + useAnalyticsPath(...PageviewInfo.CONNECT_RUX_PHONE_NUMBER) + const { palette } = useTheme() return ( <> - {__('Connect your accounts')} + {__('Connect faster with your phone number')} - {__('%1 uses MX to connect your accounts.', appName)} + {__('Login or sign up with MX to securely access your saved accounts. ')} @@ -46,34 +53,58 @@ export const RuxPhoneNumber = ({ InputProps={{ startAdornment: ( -
- Phone - +
+ + Phone + + +1
), + style: { + paddingRight: '14px', + margin: '40px 0', + fontSize: '23px', + fontWeight: '400', + height: 'auto', + maxHeight: '60px', + }, }} - onChange={(e: React.ChangeEvent) => setUserEnteredPhone(e.target.value)} + fullWidth={true} + name="phoneNumber" + onChange={(e: React.ChangeEvent) => + setUserEnteredPhone(e.target.value.replace(/\D/g, '').slice(0, 10)) + } required={true} - value={userEnteredPhone} + value={formatPhone(userEnteredPhone)} /> - - + + {/* --TR: Full string 'By selecting "Get code", you agree to MX's Terms & Conditions' */} - {__('By selecting "Get code", you agree to')} - + {__('By selecting "Continue", you agree to ')} + {/* TODO: Do we translate this below? */} {__("MX's Terms & Conditions")} - . - - @@ -82,3 +113,12 @@ export const RuxPhoneNumber = ({ } export default RuxPhoneNumber + +const formatPhone = (value: string) => { + const digits = value.replace(/\D/g, '').slice(0, 10) + + if (digits.length === 0) return digits + if (digits.length <= 3) return `(${digits}` + if (digits.length <= 6) return `(${digits.slice(0, 3)}) ${digits.slice(3)}` + return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)} - ${digits.slice(6)}` +} diff --git a/src/ReturnUserExperience/returnUserExperience.module.css b/src/ReturnUserExperience/returnUserExperience.module.css index 0c86e5169f..0197cd7336 100644 --- a/src/ReturnUserExperience/returnUserExperience.module.css +++ b/src/ReturnUserExperience/returnUserExperience.module.css @@ -71,6 +71,6 @@ .titleContainer { padding-top: 16px; - padding-right: 16px; - padding-left: 16px; + margin-right: -8px; + margin-left: -8px; } \ No newline at end of file diff --git a/src/const/Analytics.js b/src/const/Analytics.js index 03405a3d45..f73a55b8ce 100644 --- a/src/const/Analytics.js +++ b/src/const/Analytics.js @@ -117,6 +117,7 @@ export const PageviewInfo = { CONNECT_OAUTH_ERROR: ['Connect Oauth Error', '/oauth_error'], CONNECT_NO_ELIGIBLE_ACCOUNTS: ['Connect No Eligible Accounts', '/no_eligible_accounts'], CONNECT_RUX_INFO: ['Connect RUX Info', '/rux_info'], + CONNECT_RUX_PHONE_NUMBER: ['Connect RUX Phone Number', '/rux_phone_number'], CONNECT_SEARCH: ['Connect Search', '/search'], CONNECT_SEARCH_FAILED: ['Connect Search Failed', '/search_failed'], CONNECT_SEARCH_NO_RESULTS: ['Connect Search No Results', '/no_results'], From f6eacfbbef7af375c946135f598ecf0e89a05587 Mon Sep 17 00:00:00 2001 From: Jameson Date: Mon, 1 Jun 2026 13:21:06 -0600 Subject: [PATCH 07/10] feat: integrate phone number handling in RuxPhoneNumber component --- src/ReturnUserExperience/ReturnUserExperience.tsx | 13 ++++++++++++- src/ReturnUserExperience/RuxPhoneNumber.tsx | 8 ++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/ReturnUserExperience/ReturnUserExperience.tsx b/src/ReturnUserExperience/ReturnUserExperience.tsx index 8bd26f1a4f..ae23b12137 100644 --- a/src/ReturnUserExperience/ReturnUserExperience.tsx +++ b/src/ReturnUserExperience/ReturnUserExperience.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { useSelector } from 'react-redux' +import { useDispatch, useSelector } from 'react-redux' import styles from './returnUserExperience.module.css' import RuxInfo from 'src/ReturnUserExperience/RuxInfo' @@ -13,6 +13,8 @@ import useAnalyticsEvent from 'src/hooks/useAnalyticsEvent' import { __ } from 'src/utilities/Intl' import { AnalyticEvents } from 'src/const/Analytics' import { RootState } from 'src/redux/Store' +import { ActionTypes } from 'src/redux/actions/Connect' +import { selectInitialConfig } from 'src/redux/reducers/configSlice' import { ClientLogo } from 'src/components/ClientLogo' export const RUXViews = { @@ -26,6 +28,8 @@ export const ReturnUserExperience = React.forwardRef(() => { const [view, setView] = React.useState<(typeof RUXViews)[keyof typeof RUXViews]>(RUXViews.INFO) const [userEnteredPhone, setUserEnteredPhone] = React.useState('') const clientGuid = useSelector((state: RootState) => state.profiles.client.guid) + const connectConfig = useSelector(selectInitialConfig) + const dispatch = useDispatch() const sendAnalyticsEvent = useAnalyticsEvent() const handleRuxInfoContinue = () => { @@ -33,6 +37,8 @@ export const ReturnUserExperience = React.forwardRef(() => { sendAnalyticsEvent(AnalyticEvents.RUX_INFO_CONTINUE_CLICKED) setView(RUXViews.PHONE_NUMBER) } + const handleContinueWithoutPhone = () => + dispatch({ type: ActionTypes.RESET_WIDGET_MFA_STEP, payload: connectConfig }) return (
@@ -56,6 +62,11 @@ export const ReturnUserExperience = React.forwardRef(() => { {view === RUXViews.PHONE_NUMBER && ( { + // sendAnalyticsEvent(AnalyticEvents.RUX_PHONE_NUMBER_CONTINUE_CLICKED) + setView(RUXViews.OTP) + }} setUserEnteredPhone={setUserEnteredPhone} userEnteredPhone={userEnteredPhone} /> diff --git a/src/ReturnUserExperience/RuxPhoneNumber.tsx b/src/ReturnUserExperience/RuxPhoneNumber.tsx index 41d3a4f056..f81428a75d 100644 --- a/src/ReturnUserExperience/RuxPhoneNumber.tsx +++ b/src/ReturnUserExperience/RuxPhoneNumber.tsx @@ -14,9 +14,13 @@ import { PageviewInfo } from 'src/const/Analytics' import styles from './returnUserExperience.module.css' export const RuxPhoneNumber = ({ + handleContinueWithoutPhone, + handleRuxContinue, userEnteredPhone, setUserEnteredPhone, }: { + handleContinueWithoutPhone: () => void + handleRuxContinue: () => void userEnteredPhone: string setUserEnteredPhone: (phone: string) => void }) => { @@ -101,10 +105,10 @@ export const RuxPhoneNumber = ({ {__("MX's Terms & Conditions")} - - From 6f0e72200af539c450b574a7804a974b3cfb8c04 Mon Sep 17 00:00:00 2001 From: Jameson Date: Wed, 3 Jun 2026 13:32:53 -0600 Subject: [PATCH 08/10] feat: remove outdated tests for ReturnUserExperience and add new tests for RuxPhoneNumber component --- .../ReturnUserExperience-test.tsx | 146 ----------------- .../ReturnUserExperience.test.tsx | 152 ------------------ src/ReturnUserExperience/RuxPhoneNumber.tsx | 6 +- .../__tests__/RuxPhoneNumber-test.tsx | 99 ++++++++++++ 4 files changed, 103 insertions(+), 300 deletions(-) delete mode 100644 src/ReturnUserExperience/ReturnUserExperience-test.tsx delete mode 100644 src/ReturnUserExperience/ReturnUserExperience.test.tsx create mode 100644 src/ReturnUserExperience/__tests__/RuxPhoneNumber-test.tsx diff --git a/src/ReturnUserExperience/ReturnUserExperience-test.tsx b/src/ReturnUserExperience/ReturnUserExperience-test.tsx deleted file mode 100644 index 32565f23ea..0000000000 --- a/src/ReturnUserExperience/ReturnUserExperience-test.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import React from 'react' -import { describe, it, expect } from 'vitest' -import { render, screen } from 'src/utilities/testingLibrary' -import { ReturnUserExperience } from './ReturnUserExperience' - -describe('ReturnUserExperience', () => { - const mockAppName = 'Test Financial App' - - const preloadedState = { - profiles: { - client: { - oauth_app_name: mockAppName, - }, - }, - } - - describe('rendering', () => { - it('should render the component without crashing', () => { - render(, { preloadedState }) - expect(screen.getByText('Connect your accounts')).toBeInTheDocument() - }) - - it('should render the development warning alert', () => { - render(, { preloadedState }) - const alert = screen.getByText('This feature is currently in development.') - expect(alert).toBeInTheDocument() - }) - - it('should render the main heading', () => { - render(, { preloadedState }) - const heading = screen.getByRole('heading', { level: 2 }) - expect(heading).toHaveTextContent('Connect your accounts') - }) - - it('should render the subtitle with app name interpolation', () => { - render(, { preloadedState }) - const subtitle = screen.getByText(new RegExp(mockAppName)) - expect(subtitle).toBeInTheDocument() - expect(subtitle).toHaveTextContent(`${mockAppName} uses MX to connect your accounts.`) - }) - - it('should render the learn more link', () => { - render(, { preloadedState }) - const link = screen.getByRole('link', { name: /learn more about mx/i }) - expect(link).toBeInTheDocument() - expect(link).toHaveAttribute('href', 'https://mx.com/learn-more') - expect(link).toHaveAttribute('target', '_blank') - expect(link).toHaveAttribute('rel', 'noopener noreferrer') - }) - - it('should render the MX sign in button', () => { - render(, { preloadedState }) - const button = screen.getByRole('button', { name: /connect faster by signing into mx/i }) - expect(button).toBeInTheDocument() - expect(button).toHaveClass('MuiButton-contained') - }) - - it('should render the guest sign in button', () => { - render(, { preloadedState }) - const button = screen.getByRole('button', { name: /continue as guest/i }) - expect(button).toBeInTheDocument() - expect(button).toHaveClass('MuiButton-outlined') - }) - - it('should render both buttons as full width', () => { - render(, { preloadedState }) - const buttons = screen.getAllByRole('button') - buttons.forEach((button) => { - if ( - button.textContent?.includes('Connect faster') || - button.textContent?.includes('Continue as guest') - ) { - expect(button).toHaveClass('MuiButton-fullWidth') - } - }) - }) - }) - - describe('Redux state integration', () => { - it('should use the oauth_app_name from Redux state', () => { - render(, { preloadedState }) - expect(screen.getByText(new RegExp(mockAppName))).toBeInTheDocument() - }) - - it('should display default app name when oauth_app_name is not provided', () => { - const emptyState = { - profiles: { - client: {}, - }, - } - render(, { preloadedState: emptyState }) - expect(screen.getByText(/This app uses MX to connect your accounts/)).toBeInTheDocument() - }) - - it('should display default app name when oauth_app_name is null', () => { - const nullState = { - profiles: { - client: { - oauth_app_name: null, - }, - }, - } - render(, { preloadedState: nullState }) - expect(screen.getByText(/This app uses MX to connect your accounts/)).toBeInTheDocument() - }) - }) - - describe('button interactions', () => { - it('should render the MX sign in button as clickable', async () => { - const { user } = render(, { preloadedState }) - const button = screen.getByRole('button', { name: /connect faster by signing into mx/i }) - expect(button).not.toBeDisabled() - await user.click(button) - }) - - it('should render the guest continue button as clickable', async () => { - const { user } = render(, { preloadedState }) - const button = screen.getByRole('button', { name: /continue as guest/i }) - expect(button).not.toBeDisabled() - await user.click(button) - }) - }) - - describe('accessibility', () => { - it('should have proper heading hierarchy', () => { - render(, { preloadedState }) - const heading = screen.getByRole('heading', { level: 2 }) - expect(heading).toHaveTextContent('Connect your accounts') - }) - - it('should have accessible buttons', () => { - render(, { preloadedState }) - const buttons = screen.getAllByRole('button') - expect(buttons.length).toBeGreaterThanOrEqual(2) - buttons.forEach((button) => { - expect(button).toHaveAccessibleName() - }) - }) - - it('should have an accessible learn more link', () => { - render(, { preloadedState }) - const link = screen.getByRole('link', { name: /learn more about mx/i }) - expect(link).toHaveAccessibleName() - }) - }) -}) diff --git a/src/ReturnUserExperience/ReturnUserExperience.test.tsx b/src/ReturnUserExperience/ReturnUserExperience.test.tsx deleted file mode 100644 index c3030aa8cf..0000000000 --- a/src/ReturnUserExperience/ReturnUserExperience.test.tsx +++ /dev/null @@ -1,152 +0,0 @@ -import React from 'react' -import { render, screen } from 'src/utilities/testingLibrary' -import { ReturnUserExperience } from './ReturnUserExperience' -import { initialState } from 'src/services/mockedData' - -describe('ReturnUserExperience', () => { - const mockAppName = 'Test Financial App' - - const preloadedState = { - ...initialState, - profiles: { - ...initialState.profiles, - client: { - ...initialState.profiles.client, - oauth_app_name: mockAppName, - }, - }, - } - - describe('rendering', () => { - it('should render the component without crashing', () => { - render(, { preloadedState }) - expect(screen.getByText('Connect your accounts')).toBeInTheDocument() - }) - - it('should render the main heading', () => { - render(, { preloadedState }) - const heading = screen.getByRole('heading', { level: 2 }) - expect(heading).toHaveTextContent('Connect your accounts') - }) - - it('should render the subtitle with app name interpolation', () => { - render(, { preloadedState }) - const subtitle = screen.getByText(new RegExp(mockAppName)) - expect(subtitle).toBeInTheDocument() - expect(subtitle).toHaveTextContent(`${mockAppName} uses MX to connect your accounts.`) - }) - - it('should render the learn more link', () => { - render(, { preloadedState }) - const link = screen.getByRole('link', { name: /learn more about mx/i }) - expect(link).toBeInTheDocument() - expect(link).toHaveAttribute('href', 'https://mx.com/learn-more') - expect(link).toHaveAttribute('target', '_blank') - expect(link).toHaveAttribute('rel', 'noopener noreferrer') - }) - - it('should render the MX sign in button', () => { - render(, { preloadedState }) - const button = screen.getByRole('button', { name: /connect faster by signing into mx/i }) - expect(button).toBeInTheDocument() - expect(button).toHaveClass('MuiButton-contained') - }) - - it('should render the guest sign in button', () => { - render(, { preloadedState }) - const button = screen.getByRole('button', { name: /continue as guest/i }) - expect(button).toBeInTheDocument() - expect(button).toHaveClass('MuiButton-outlined') - }) - - it('should render both buttons as full width', () => { - render(, { preloadedState }) - const buttons = screen.getAllByRole('button') - buttons.forEach((button) => { - if ( - button.textContent?.includes('Connect faster') || - button.textContent?.includes('Continue as guest') - ) { - expect(button).toHaveClass('MuiButton-fullWidth') - } - }) - }) - }) - - describe('Redux state integration', () => { - const undefinedState = { - ...initialState, - profiles: { - ...initialState.profiles, - client: { - ...initialState.profiles.client, - oauth_app_name: undefined, - }, - }, - } - const nullState = { - ...initialState, - profiles: { - ...initialState.profiles, - client: { - ...initialState.profiles.client, - oauth_app_name: null, - }, - }, - } - - it('should use the oauth_app_name from Redux state', () => { - render(, { preloadedState }) - expect(screen.getByText(new RegExp(mockAppName))).toBeInTheDocument() - }) - - it('should display default app name when oauth_app_name is not provided', () => { - render(, { preloadedState: undefinedState }) - expect(screen.getByText(/This app uses MX to connect your accounts/)).toBeInTheDocument() - }) - - it('should display default app name when oauth_app_name is null', () => { - render(, { preloadedState: nullState }) - expect(screen.getByText(/This app uses MX to connect your accounts/)).toBeInTheDocument() - }) - }) - - describe('button interactions', () => { - it('should render the MX sign in button as clickable', async () => { - const { user } = render(, { preloadedState }) - const button = screen.getByRole('button', { name: /connect faster by signing into mx/i }) - expect(button).not.toBeDisabled() - await user.click(button) - }) - - it('should render the guest continue button as clickable', async () => { - const { user } = render(, { preloadedState }) - const button = screen.getByRole('button', { name: /continue as guest/i }) - expect(button).not.toBeDisabled() - await user.click(button) - }) - }) - - describe('accessibility', () => { - it('should have proper heading hierarchy', () => { - render(, { preloadedState }) - const heading = screen.getByRole('heading', { level: 2 }) - expect(heading).toHaveTextContent('Connect your accounts') - }) - - it('should have accessible buttons', () => { - render(, { preloadedState }) - const buttons = screen.getAllByRole('button') - expect(buttons.length).toBeGreaterThanOrEqual(2) - buttons.forEach((button) => { - expect(button).toHaveAccessibleName() - }) - }) - - it('should have an accessible learn more link', () => { - render(, { preloadedState }) - const link = screen.getByRole('link', { name: /learn more about mx/i }) - expect(link).toHaveAccessibleName() - }) - }) -}) diff --git a/src/ReturnUserExperience/RuxPhoneNumber.tsx b/src/ReturnUserExperience/RuxPhoneNumber.tsx index f81428a75d..b3023846f3 100644 --- a/src/ReturnUserExperience/RuxPhoneNumber.tsx +++ b/src/ReturnUserExperience/RuxPhoneNumber.tsx @@ -38,6 +38,7 @@ export const RuxPhoneNumber = ({ - - diff --git a/src/ReturnUserExperience/__tests__/RuxPhoneNumber-test.tsx b/src/ReturnUserExperience/__tests__/RuxPhoneNumber-test.tsx new file mode 100644 index 0000000000..3b03246f0e --- /dev/null +++ b/src/ReturnUserExperience/__tests__/RuxPhoneNumber-test.tsx @@ -0,0 +1,99 @@ +import React from 'react' +import { RuxPhoneNumber } from 'src/ReturnUserExperience/RuxPhoneNumber' +import { render, screen } from 'src/utilities/testingLibrary' + +describe('RuxPhoneNumber', () => { + it('renders the main heading', () => { + render( + {}} + handleRuxContinue={() => {}} + setUserEnteredPhone={() => {}} + userEnteredPhone="" + />, + ) + const heading = screen.getByText('Connect faster with your phone number') + expect(heading).toBeInTheDocument() + }) + + it('renders the subtitle with a learn more link', () => { + render( + {}} + handleRuxContinue={() => {}} + setUserEnteredPhone={() => {}} + userEnteredPhone="" + />, + ) + const subtitle = screen.getByText( + /Login or sign up with MX to securely access your saved accounts./i, + ) + expect(subtitle).toBeInTheDocument() + + const link = screen.getByRole('link', { name: /learn more about mx/i }) + expect(link).toBeInTheDocument() + expect(link).toHaveAttribute('href', 'https://mx.com/learn-more') + expect(link).toHaveAttribute('target', '_blank') + expect(link).toHaveAttribute('rel', 'noopener noreferrer') + }) + + it('renders the phone number input with correct label', () => { + render( + {}} + handleRuxContinue={() => {}} + setUserEnteredPhone={() => {}} + userEnteredPhone="" + />, + ) + const phoneInput = screen.getByRole('textbox') + expect(phoneInput).toBeInTheDocument() + }) + + it('renders the continue without phone number button', () => { + render( + {}} + handleRuxContinue={() => {}} + setUserEnteredPhone={() => {}} + userEnteredPhone="" + />, + ) + const continueWithoutPhoneButton = screen.getByRole('button', { + name: 'Continue without phone number', + }) + expect(continueWithoutPhoneButton).toBeInTheDocument() + }) + + it('calls handleContinueWithoutPhone when the continue without phone number button is clicked', () => { + const handleContinueWithoutPhoneMock = vi.fn() + render( + {}} + setUserEnteredPhone={() => {}} + userEnteredPhone="" + />, + ) + const continueWithoutPhoneButton = screen.getByRole('button', { + name: 'Continue without phone number', + }) + continueWithoutPhoneButton.click() + expect(handleContinueWithoutPhoneMock).toHaveBeenCalledTimes(1) + }) + + it('calls handleRuxContinue when the continue button is clicked', () => { + const handleRuxContinueMock = vi.fn() + render( + {}} + handleRuxContinue={handleRuxContinueMock} + setUserEnteredPhone={() => {}} + userEnteredPhone="" + />, + ) + const continueButton = screen.getByRole('button', { name: 'Continue' }) + continueButton.click() + expect(handleRuxContinueMock).toHaveBeenCalledTimes(1) + }) +}) From 1dd4470627f9e5aaf0d783638f5dc741686947ba Mon Sep 17 00:00:00 2001 From: Jameson Date: Wed, 3 Jun 2026 13:45:03 -0600 Subject: [PATCH 09/10] feat: add tests for ReturnUserExperience and RuxInfo components to ensure proper rendering and functionality --- src/ReturnUserExperience/returnUserExperience.module.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ReturnUserExperience/returnUserExperience.module.css b/src/ReturnUserExperience/returnUserExperience.module.css index 0197cd7336..0e462743a7 100644 --- a/src/ReturnUserExperience/returnUserExperience.module.css +++ b/src/ReturnUserExperience/returnUserExperience.module.css @@ -73,4 +73,4 @@ padding-top: 16px; margin-right: -8px; margin-left: -8px; -} \ No newline at end of file +} From 37b17498d4038a16fba1f00515ebde7065066dd9 Mon Sep 17 00:00:00 2001 From: Jameson Date: Fri, 5 Jun 2026 13:44:32 -0600 Subject: [PATCH 10/10] feat: add Spanish and French translations for phone number features and related messages --- src/const/language/es.json | 5 +++++ src/const/language/es.po | 35 +++++++++++++++++++++++++++++++---- src/const/language/frCa.json | 5 +++++ src/const/language/frCa.po | 36 ++++++++++++++++++++++++++++++++---- 4 files changed, 73 insertions(+), 8 deletions(-) diff --git a/src/const/language/es.json b/src/const/language/es.json index e047c29e00..657366ed0e 100644 --- a/src/const/language/es.json +++ b/src/const/language/es.json @@ -433,6 +433,11 @@ "Connect your accounts": "Conecta tus cuentas", "%1 uses MX to connect your accounts. ": "%1 usa MX para conectar tus cuentas. ", "Learn more about MX.": "Obtén más información sobre MX.", + "Connect faster with your phone number": "Conéctate más rápido con tu número de teléfono", + "Login or sign up with MX to securely access your saved accounts. ": "Inicia sesión o regístrate con MX para acceder de forma segura a tus cuentas guardadas. ", + "By selecting \"Continue\", you agree to ": "Al seleccionar \"Continuar\", aceptas ", + "MX's Terms & Conditions": "los Términos y Condiciones de MX", + "Continue without phone number": "Continuar sin número de teléfono", "connect/disclosure/button\u0004Continue": "Continuar", "connect/disclosure/policy/text\u0004By clicking Continue, you agree to the ": "Al hacer clic en Continuar, tu aceptas la ", "connect/disclosure/policy/link\u0004MX Privacy Policy.": "Política de privacidad de Money Experience.", diff --git a/src/const/language/es.po b/src/const/language/es.po index 351207f983..dfc041ec9d 100644 --- a/src/const/language/es.po +++ b/src/const/language/es.po @@ -169,9 +169,11 @@ msgstr "Continuar" #: src/components/support/GeneralSupport.js #: src/components/support/SupportSuccess.js #: src/components/ConnectSuccessSurvey.tsx src/components/FindAccountInfo.js -#: src/components/LeavingNoticeFlat.js src/ReturnUserExperience/RuxInfo.tsx -#: src/views/mfa/DefaultMFA.js src/views/mfa/MFAImages.js -#: src/views/mfa/MFAOptions.js src/views/microdeposits/VerifyDeposits.js +#: src/components/LeavingNoticeFlat.js +#: src/ReturnUserExperience/RuxPhoneNumber.tsx +#: src/ReturnUserExperience/RuxInfo.tsx src/views/mfa/DefaultMFA.js +#: src/views/mfa/MFAImages.js src/views/mfa/MFAOptions.js +#: src/views/microdeposits/VerifyDeposits.js #: src/views/microdeposits/HowItWorks.js #: src/views/microdeposits/MicrodepositErrors.js #: src/views/microdeposits/PersonalInfoForm.js @@ -2165,7 +2167,8 @@ msgstr "Privado" #: src/ReturnUserExperience/RuxInfo.tsx msgid "We never sell your phone number or use it for marketing." -msgstr "Nunca vendemos tu número de teléfono ni lo usamos con fines de mercadotecnia." +msgstr "" +"Nunca vendemos tu número de teléfono ni lo usamos con fines de mercadotecnia." #: src/ReturnUserExperience/RuxInfo.tsx msgid "Connect your accounts" @@ -2175,6 +2178,30 @@ msgstr "Conecta tus cuentas" msgid "%1 uses MX to connect your accounts. " msgstr "%1 usa MX para conectar tus cuentas. " +#: src/ReturnUserExperience/RuxPhoneNumber.tsx #: src/ReturnUserExperience/RuxInfo.tsx msgid "Learn more about MX." msgstr "Obtén más información sobre MX." + +#: src/ReturnUserExperience/RuxPhoneNumber.tsx +msgid "Connect faster with your phone number" +msgstr "Conéctate más rápido con tu número de teléfono" + +#: src/ReturnUserExperience/RuxPhoneNumber.tsx +msgid "Login or sign up with MX to securely access your saved accounts. " +msgstr "" +"Inicia sesión o regístrate con MX para acceder de forma segura a tus cuentas " +"guardadas. " + +#. TR: Full string 'By selecting "Get code", you agree to MX's Terms & Conditions' */} +#: src/ReturnUserExperience/RuxPhoneNumber.tsx +msgid "By selecting \"Continue\", you agree to " +msgstr "Al seleccionar \"Continuar\", aceptas " + +#: src/ReturnUserExperience/RuxPhoneNumber.tsx +msgid "MX's Terms & Conditions" +msgstr "los Términos y Condiciones de MX" + +#: src/ReturnUserExperience/RuxPhoneNumber.tsx +msgid "Continue without phone number" +msgstr "Continuar sin número de teléfono" diff --git a/src/const/language/frCa.json b/src/const/language/frCa.json index cff752cd1c..cb4129e26b 100644 --- a/src/const/language/frCa.json +++ b/src/const/language/frCa.json @@ -436,6 +436,11 @@ "Connect your accounts": "Connectez vos comptes", "%1 uses MX to connect your accounts. ": "%1 utilise MX pour connecter vos comptes. ", "Learn more about MX.": "En savoir plus sur MX.", + "Connect faster with your phone number": "Connectez-vous plus rapidement avec votre numéro de téléphone", + "Login or sign up with MX to securely access your saved accounts. ": "Connectez-vous ou inscrivez-vous avec MX pour accéder en toute sécurité à vos comptes enregistrés. ", + "By selecting \"Continue\", you agree to ": "En sélectionnant \"Continuer\", vous acceptez ", + "MX's Terms & Conditions": "les conditions générales de MX", + "Continue without phone number": "Continuer sans numéro de téléphone", "connect/disclosure/policy/text\u0004By clicking Continue, you agree to the ": "En cliquant sur Continuer, vous acceptez la ", "connect/disclosure/policy/link\u0004MX Privacy Policy.": "Politique de confidentialité de MX.", "connect/disclosure/policy/link\u0004MX Privacy Policy": "Politique de confidentialité de MX.", diff --git a/src/const/language/frCa.po b/src/const/language/frCa.po index 7fac8eb59d..a3cc46c401 100644 --- a/src/const/language/frCa.po +++ b/src/const/language/frCa.po @@ -69,9 +69,11 @@ msgstr "Continuer" #: src/components/support/GeneralSupport.js #: src/components/support/SupportSuccess.js #: src/components/ConnectSuccessSurvey.tsx src/components/FindAccountInfo.js -#: src/components/LeavingNoticeFlat.js src/ReturnUserExperience/RuxInfo.tsx -#: src/views/mfa/DefaultMFA.js src/views/mfa/MFAImages.js -#: src/views/mfa/MFAOptions.js src/views/microdeposits/VerifyDeposits.js +#: src/components/LeavingNoticeFlat.js +#: src/ReturnUserExperience/RuxPhoneNumber.tsx +#: src/ReturnUserExperience/RuxInfo.tsx src/views/mfa/DefaultMFA.js +#: src/views/mfa/MFAImages.js src/views/mfa/MFAOptions.js +#: src/views/microdeposits/VerifyDeposits.js #: src/views/microdeposits/HowItWorks.js #: src/views/microdeposits/MicrodepositErrors.js #: src/views/microdeposits/PersonalInfoForm.js @@ -2258,7 +2260,9 @@ msgstr "Privé" #: src/ReturnUserExperience/RuxInfo.tsx msgid "We never sell your phone number or use it for marketing." -msgstr "Nous ne vendons jamais votre numéro de téléphone ni ne l’utilisons pour le marketing." +msgstr "" +"Nous ne vendons jamais votre numéro de téléphone ni ne l’utilisons pour le " +"marketing." #: src/ReturnUserExperience/RuxInfo.tsx msgid "Connect your accounts" @@ -2268,6 +2272,30 @@ msgstr "Connectez vos comptes" msgid "%1 uses MX to connect your accounts. " msgstr "%1 utilise MX pour connecter vos comptes. " +#: src/ReturnUserExperience/RuxPhoneNumber.tsx #: src/ReturnUserExperience/RuxInfo.tsx msgid "Learn more about MX." msgstr "En savoir plus sur MX." + +#: src/ReturnUserExperience/RuxPhoneNumber.tsx +msgid "Connect faster with your phone number" +msgstr "Connectez-vous plus rapidement avec votre numéro de téléphone" + +#: src/ReturnUserExperience/RuxPhoneNumber.tsx +msgid "Login or sign up with MX to securely access your saved accounts. " +msgstr "" +"Connectez-vous ou inscrivez-vous avec MX pour accéder en toute sécurité à " +"vos comptes enregistrés. " + +#. TR: Full string 'By selecting "Get code", you agree to MX's Terms & Conditions' */} +#: src/ReturnUserExperience/RuxPhoneNumber.tsx +msgid "By selecting \"Continue\", you agree to " +msgstr "En sélectionnant \"Continuer\", vous acceptez " + +#: src/ReturnUserExperience/RuxPhoneNumber.tsx +msgid "MX's Terms & Conditions" +msgstr "les conditions générales de MX" + +#: src/ReturnUserExperience/RuxPhoneNumber.tsx +msgid "Continue without phone number" +msgstr "Continuer sans numéro de téléphone"