From 0623467434afeb3ab88f8224f24e2b3759718cc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Fri, 24 Oct 2025 15:51:24 +0100 Subject: [PATCH 01/23] Extract FS test email logic to separate function --- src/libs/Fullstory/index.native.ts | 13 +++++++++++-- src/libs/Fullstory/index.ts | 20 ++++++++++++++------ src/libs/Fullstory/types.ts | 5 +++++ 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/libs/Fullstory/index.native.ts b/src/libs/Fullstory/index.native.ts index 21fa6bb44aac..efb05f50fc36 100644 --- a/src/libs/Fullstory/index.native.ts +++ b/src/libs/Fullstory/index.native.ts @@ -14,6 +14,15 @@ const FS: Fullstory = { onReady: () => Promise.resolve(), + shouldInitialize: (userMetadata, envName) => { + const isTestEmail = userMetadata.email !== undefined && userMetadata.email.startsWith('fullstory') && userMetadata.email.endsWith(CONST.EMAIL.QA_DOMAIN); + if ((CONST.ENVIRONMENT.PRODUCTION !== envName && !isTestEmail) || Str.extractEmailDomain(userMetadata.email ?? '') === CONST.EXPENSIFY_PARTNER_NAME) { + return false; + } + + return true; + }, + consent: (shouldConsent) => FullStory.consent(shouldConsent), identify: (userMetadata, envName) => { @@ -34,10 +43,10 @@ const FS: Fullstory = { // after the init function since this function is also called on updates for // UserMetadata onyx key. getEnvironment().then((envName: string) => { - const isTestEmail = userMetadata.email !== undefined && userMetadata.email.startsWith('fullstory') && userMetadata.email.endsWith(CONST.EMAIL.QA_DOMAIN); - if ((CONST.ENVIRONMENT.PRODUCTION !== envName && !isTestEmail) || Str.extractEmailDomain(userMetadata.email ?? '') === CONST.EXPENSIFY_PARTNER_NAME) { + if (!FS.shouldInitialize(userMetadata, envName)) { return; } + FullStory.restart(); FullStory.consent(true); FS.identify(userMetadata, envName); diff --git a/src/libs/Fullstory/index.ts b/src/libs/Fullstory/index.ts index ac2d941fb610..4c853ca5c7f7 100644 --- a/src/libs/Fullstory/index.ts +++ b/src/libs/Fullstory/index.ts @@ -33,6 +33,15 @@ const FS: Fullstory = { } }), + shouldInitialize: (userMetadata, envName) => { + const isTestEmail = userMetadata.email !== undefined && userMetadata.email.startsWith('fullstory') && userMetadata.email.endsWith(CONST.EMAIL.QA_DOMAIN); + if ((CONST.ENVIRONMENT.PRODUCTION !== envName && !isTestEmail) || Str.extractEmailDomain(userMetadata.email ?? '') === CONST.EXPENSIFY_PARTNER_NAME || Session.isSupportAuthToken()) { + return false; + } + + return true; + }, + consent: (shouldConsent) => FullStory(CONST.FULLSTORY.OPERATION.SET_IDENTITY, {consent: shouldConsent}), identify: (userMetadata) => { @@ -53,14 +62,13 @@ const FS: Fullstory = { if (!userMetadata?.accountID) { return; } + try { + // We only use FullStory in production environment. We need to check this here + // after the init function since this function is also called on updates for + // UserMetadata onyx key. getEnvironment().then((envName: string) => { - const isTestEmail = userMetadata.email !== undefined && userMetadata.email.startsWith('fullstory') && userMetadata.email.endsWith(CONST.EMAIL.QA_DOMAIN); - if ( - (CONST.ENVIRONMENT.PRODUCTION !== envName && !isTestEmail) || - Str.extractEmailDomain(userMetadata.email ?? '') === CONST.EXPENSIFY_PARTNER_NAME || - Session.isSupportAuthToken() - ) { + if (!FS.shouldInitialize(userMetadata, envName)) { // On web, if we started FS at some point in a browser, it will run forever. So let's shut it down if we don't want it to run. if (isInitialized()) { FullStory(CONST.FULLSTORY.OPERATION.SHUTDOWN); diff --git a/src/libs/Fullstory/types.ts b/src/libs/Fullstory/types.ts index 14d06b270f46..f0674d645159 100644 --- a/src/libs/Fullstory/types.ts +++ b/src/libs/Fullstory/types.ts @@ -47,6 +47,11 @@ type Fullstory = { */ onReady: () => Promise; + /** + * Whether Fullstory should be initialized. + */ + shouldInitialize: (userMetadata: UserMetadata, envName: string) => boolean; + /** * Sets the identity consent status using the Fullstory library. */ From bb1be71a751202fc5216beffa3abada897f299da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Fri, 24 Oct 2025 16:19:48 +0100 Subject: [PATCH 02/23] Extract common FS shouldInitialize logic --- src/libs/Fullstory/common.ts | 14 ++++++++++++-- src/libs/Fullstory/index.native.ts | 13 ++----------- src/libs/Fullstory/index.ts | 12 ++---------- src/libs/Fullstory/types.ts | 4 +++- 4 files changed, 19 insertions(+), 24 deletions(-) diff --git a/src/libs/Fullstory/common.ts b/src/libs/Fullstory/common.ts index ad3b7ad86fdd..c502d6ae0a81 100644 --- a/src/libs/Fullstory/common.ts +++ b/src/libs/Fullstory/common.ts @@ -1,6 +1,7 @@ +import {Str} from 'expensify-common'; import {isConciergeChatReport, shouldUnmaskChat} from '@libs/ReportUtils'; import CONST from '@src/CONST'; -import type {GetChatFSClass} from './types'; +import type {GetChatFSClass, ShouldInitializeFullstory} from './types'; const getChatFSClass: GetChatFSClass = (context, report) => { if (isConciergeChatReport(report)) { @@ -14,4 +15,13 @@ const getChatFSClass: GetChatFSClass = (context, report) => { return CONST.FULLSTORY.CLASS.MASK; }; -export default getChatFSClass; +const shouldInitializeFullstory: ShouldInitializeFullstory = (userMetadata, envName) => { + const isTestEmail = userMetadata.email !== undefined && userMetadata.email.startsWith('fullstory') && userMetadata.email.endsWith(CONST.EMAIL.QA_DOMAIN); + if ((CONST.ENVIRONMENT.PRODUCTION !== envName && !isTestEmail) || Str.extractEmailDomain(userMetadata.email ?? '') === CONST.EXPENSIFY_PARTNER_NAME) { + return false; + } + + return true; +}; + +export {getChatFSClass, shouldInitializeFullstory}; diff --git a/src/libs/Fullstory/index.native.ts b/src/libs/Fullstory/index.native.ts index efb05f50fc36..3a6575e78001 100644 --- a/src/libs/Fullstory/index.native.ts +++ b/src/libs/Fullstory/index.native.ts @@ -1,8 +1,6 @@ import FullStory, {FSPage} from '@fullstory/react-native'; -import {Str} from 'expensify-common'; -import CONST from '@src/CONST'; import getEnvironment from '@src/libs/Environment/getEnvironment'; -import getChatFSClass from './common'; +import {getChatFSClass, shouldInitializeFullstory} from './common'; import type {Fullstory} from './types'; const FS: Fullstory = { @@ -14,14 +12,7 @@ const FS: Fullstory = { onReady: () => Promise.resolve(), - shouldInitialize: (userMetadata, envName) => { - const isTestEmail = userMetadata.email !== undefined && userMetadata.email.startsWith('fullstory') && userMetadata.email.endsWith(CONST.EMAIL.QA_DOMAIN); - if ((CONST.ENVIRONMENT.PRODUCTION !== envName && !isTestEmail) || Str.extractEmailDomain(userMetadata.email ?? '') === CONST.EXPENSIFY_PARTNER_NAME) { - return false; - } - - return true; - }, + shouldInitialize: shouldInitializeFullstory, consent: (shouldConsent) => FullStory.consent(shouldConsent), diff --git a/src/libs/Fullstory/index.ts b/src/libs/Fullstory/index.ts index 4c853ca5c7f7..f3990de5806c 100644 --- a/src/libs/Fullstory/index.ts +++ b/src/libs/Fullstory/index.ts @@ -1,9 +1,8 @@ import {FullStory, init, isInitialized} from '@fullstory/browser'; -import {Str} from 'expensify-common'; import * as Session from '@userActions/Session'; import CONST from '@src/CONST'; import getEnvironment from '@src/libs/Environment/getEnvironment'; -import getChatFSClass from './common'; +import {getChatFSClass, shouldInitializeFullstory} from './common'; import type {FSPageLike, Fullstory} from './types'; // Placeholder Browser API does not support Manual Page definition @@ -33,14 +32,7 @@ const FS: Fullstory = { } }), - shouldInitialize: (userMetadata, envName) => { - const isTestEmail = userMetadata.email !== undefined && userMetadata.email.startsWith('fullstory') && userMetadata.email.endsWith(CONST.EMAIL.QA_DOMAIN); - if ((CONST.ENVIRONMENT.PRODUCTION !== envName && !isTestEmail) || Str.extractEmailDomain(userMetadata.email ?? '') === CONST.EXPENSIFY_PARTNER_NAME || Session.isSupportAuthToken()) { - return false; - } - - return true; - }, + shouldInitialize: (userMetadata, envName) => shouldInitializeFullstory(userMetadata, envName) && !Session.isSupportAuthToken(), consent: (shouldConsent) => FullStory(CONST.FULLSTORY.OPERATION.SET_IDENTITY, {consent: shouldConsent}), diff --git a/src/libs/Fullstory/types.ts b/src/libs/Fullstory/types.ts index f0674d645159..7d0f0ae35768 100644 --- a/src/libs/Fullstory/types.ts +++ b/src/libs/Fullstory/types.ts @@ -26,6 +26,8 @@ interface FSPageLikeConstructor { type GetChatFSClass = (context: OnyxEntry, report: OnyxInputOrEntry) => FSClass; +type ShouldInitializeFullstory = (userMetadata: UserMetadata, envName: string) => boolean; + type Fullstory = { /** * Fullstory class used for page tracking. @@ -164,4 +166,4 @@ type MultipleFSClassProps = */ Partial>; -export type {FSPageLike, FSPageLikeConstructor, Fullstory, GetChatFSClass, PropertiesWithoutPageName, ForwardedFSClassProps, MultipleFSClassProps}; +export type {FSPageLike, FSPageLikeConstructor, Fullstory, GetChatFSClass, PropertiesWithoutPageName, ForwardedFSClassProps, MultipleFSClassProps, ShouldInitializeFullstory}; From ffbbc390cf0fa0683ec8628e20d9c1fe93b4d8d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Fri, 24 Oct 2025 16:26:45 +0100 Subject: [PATCH 03/23] Fix type --- src/libs/Fullstory/common.ts | 4 ++-- src/libs/Fullstory/types.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libs/Fullstory/common.ts b/src/libs/Fullstory/common.ts index c502d6ae0a81..5c877fac5f80 100644 --- a/src/libs/Fullstory/common.ts +++ b/src/libs/Fullstory/common.ts @@ -1,7 +1,7 @@ import {Str} from 'expensify-common'; import {isConciergeChatReport, shouldUnmaskChat} from '@libs/ReportUtils'; import CONST from '@src/CONST'; -import type {GetChatFSClass, ShouldInitializeFullstory} from './types'; +import type {GetChatFSClass, ShouldInitialize} from './types'; const getChatFSClass: GetChatFSClass = (context, report) => { if (isConciergeChatReport(report)) { @@ -15,7 +15,7 @@ const getChatFSClass: GetChatFSClass = (context, report) => { return CONST.FULLSTORY.CLASS.MASK; }; -const shouldInitializeFullstory: ShouldInitializeFullstory = (userMetadata, envName) => { +const shouldInitializeFullstory: ShouldInitialize = (userMetadata, envName) => { const isTestEmail = userMetadata.email !== undefined && userMetadata.email.startsWith('fullstory') && userMetadata.email.endsWith(CONST.EMAIL.QA_DOMAIN); if ((CONST.ENVIRONMENT.PRODUCTION !== envName && !isTestEmail) || Str.extractEmailDomain(userMetadata.email ?? '') === CONST.EXPENSIFY_PARTNER_NAME) { return false; diff --git a/src/libs/Fullstory/types.ts b/src/libs/Fullstory/types.ts index 7d0f0ae35768..0ea72512e251 100644 --- a/src/libs/Fullstory/types.ts +++ b/src/libs/Fullstory/types.ts @@ -26,7 +26,7 @@ interface FSPageLikeConstructor { type GetChatFSClass = (context: OnyxEntry, report: OnyxInputOrEntry) => FSClass; -type ShouldInitializeFullstory = (userMetadata: UserMetadata, envName: string) => boolean; +type ShouldInitialize = (userMetadata: UserMetadata, envName: string) => boolean; type Fullstory = { /** @@ -52,7 +52,7 @@ type Fullstory = { /** * Whether Fullstory should be initialized. */ - shouldInitialize: (userMetadata: UserMetadata, envName: string) => boolean; + shouldInitialize: ShouldInitialize; /** * Sets the identity consent status using the Fullstory library. @@ -166,4 +166,4 @@ type MultipleFSClassProps = */ Partial>; -export type {FSPageLike, FSPageLikeConstructor, Fullstory, GetChatFSClass, PropertiesWithoutPageName, ForwardedFSClassProps, MultipleFSClassProps, ShouldInitializeFullstory}; +export type {FSPageLike, FSPageLikeConstructor, Fullstory, GetChatFSClass, PropertiesWithoutPageName, ForwardedFSClassProps, MultipleFSClassProps, ShouldInitialize}; From bbe41ce952023979ff06497ed3b5b9f773dfb6b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Fri, 24 Oct 2025 20:43:07 +0100 Subject: [PATCH 04/23] Draft of new logic to decide if report should be masked or not --- .../MoneyRequestReportTransactionItem.tsx | 5 +-- .../MoneyRequestReportTransactionList.tsx | 6 --- src/libs/Fullstory/common.ts | 43 +++++++++++++++++-- 3 files changed, 40 insertions(+), 14 deletions(-) diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionItem.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionItem.tsx index eed8ffd54f0c..4612481039f8 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionItem.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionItem.tsx @@ -13,14 +13,13 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import ControlSelection from '@libs/ControlSelection'; import canUseTouchScreen from '@libs/DeviceCapabilities/canUseTouchScreen'; -import type {ForwardedFSClassProps} from '@libs/Fullstory/types'; import {getTransactionPendingAction, isTransactionPendingDelete} from '@libs/TransactionUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import type {Report, TransactionViolation} from '@src/types/onyx'; import type {TransactionWithOptionalHighlight} from './MoneyRequestReportTransactionList'; -type MoneyRequestReportTransactionItemProps = ForwardedFSClassProps & { +type MoneyRequestReportTransactionItemProps = { /** The transaction that is being displayed */ transaction: TransactionWithOptionalHighlight; @@ -77,7 +76,6 @@ function MoneyRequestReportTransactionItem({ amountColumnSize, taxAmountColumnSize, scrollToNewTransaction, - forwardedFSClass, }: MoneyRequestReportTransactionItemProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -133,7 +131,6 @@ function MoneyRequestReportTransactionItem({ disabled={isTransactionPendingDelete(transaction)} ref={viewRef} wrapperStyle={[animatedHighlightStyle, styles.userSelectNone]} - forwardedFSClass={forwardedFSClass} > { @@ -314,8 +311,6 @@ function MoneyRequestReportTransactionList({ const listHorizontalPadding = styles.ph5; - const transactionItemFSClass = FS.getChatFSClass(personalDetailsList, report); - if (isEmptyTransactions) { return ( <> @@ -391,7 +386,6 @@ function MoneyRequestReportTransactionList({ taxAmountColumnSize={taxAmountColumnSize} // if we add few new transactions, then we need to scroll to the first one scrollToNewTransaction={transaction.transactionID === newTransactions?.at(0)?.transactionID ? scrollToNewTransaction : undefined} - forwardedFSClass={transactionItemFSClass} /> ); })} diff --git a/src/libs/Fullstory/common.ts b/src/libs/Fullstory/common.ts index 5c877fac5f80..f8bbdd29d2b6 100644 --- a/src/libs/Fullstory/common.ts +++ b/src/libs/Fullstory/common.ts @@ -1,18 +1,53 @@ import {Str} from 'expensify-common'; -import {isConciergeChatReport, shouldUnmaskChat} from '@libs/ReportUtils'; +import type {OnyxCollection} from 'react-native-onyx'; +import Onyx from 'react-native-onyx'; +import {chatIncludesConcierge, isConciergeChatReport, shouldUnmaskChat} from '@libs/ReportUtils'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {Report} from '@src/types/onyx'; import type {GetChatFSClass, ShouldInitialize} from './types'; +let allReports: OnyxCollection; +Onyx.connectWithoutView({ + key: ONYXKEYS.COLLECTION.REPORT, + waitForCollectionCallback: true, + callback: (value) => { + allReports = value; + }, +}); + const getChatFSClass: GetChatFSClass = (context, report) => { - if (isConciergeChatReport(report)) { + if (!report?.participants) { + return CONST.FULLSTORY.CLASS.UNMASK; + } + + const participantAccountIDs = Object.keys(report.participants); + + if (report.type === CONST.REPORT.TYPE.IOU || report.type === CONST.REPORT.TYPE.EXPENSE || report.type === CONST.REPORT.TYPE.INVOICE) { return CONST.FULLSTORY.CLASS.UNMASK; } - if (shouldUnmaskChat(context, report)) { + const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`]; + if (parentReport && (parentReport.type === CONST.REPORT.TYPE.IOU || parentReport.type === CONST.REPORT.TYPE.EXPENSE || parentReport.type === CONST.REPORT.TYPE.INVOICE)) { return CONST.FULLSTORY.CLASS.UNMASK; } - return CONST.FULLSTORY.CLASS.MASK; + // DMs / Groups / Rooms + if (participantAccountIDs.length >= 2) { + return chatIncludesConcierge(report) ? CONST.FULLSTORY.CLASS.UNMASK : CONST.FULLSTORY.CLASS.MASK; + } + + return CONST.FULLSTORY.CLASS.UNMASK; + + // if (isConciergeChatReport(report)) { + // return CONST.FULLSTORY.CLASS.UNMASK; + // } + + // if (shouldUnmaskChat(context, report)) { + // return CONST.FULLSTORY.CLASS.UNMASK; + // } + + // return CONST.FULLSTORY.CLASS.MASK; }; const shouldInitializeFullstory: ShouldInitialize = (userMetadata, envName) => { From 87e44dcf11399d5af815aaccff5311f5f126a6cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Fri, 24 Oct 2025 20:43:34 +0100 Subject: [PATCH 05/23] Unmask money request previews --- .../ReportActionItem/MoneyRequestReportPreview/index.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestReportPreview/index.tsx b/src/components/ReportActionItem/MoneyRequestReportPreview/index.tsx index d9cceea897a7..262736a5683f 100644 --- a/src/components/ReportActionItem/MoneyRequestReportPreview/index.tsx +++ b/src/components/ReportActionItem/MoneyRequestReportPreview/index.tsx @@ -8,7 +8,6 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import useTransactionViolations from '@hooks/useTransactionViolations'; -import FS from '@libs/Fullstory'; import Performance from '@libs/Performance'; import {getIOUActionForReportID, isSplitBillAction as isSplitBillActionReportActionsUtils, isTrackExpenseAction as isTrackExpenseActionReportActionsUtils} from '@libs/ReportActionsUtils'; import {isIOUReport} from '@libs/ReportUtils'; @@ -132,8 +131,6 @@ function MoneyRequestReportPreview({ /> ); - const fsClass = FS.getChatFSClass(personalDetailsList, iouReport); - return ( ); } From e6119a840c6f46f9718ca551eab73b6979f68706 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Thu, 30 Oct 2025 17:31:26 +0000 Subject: [PATCH 06/23] Fix input FS masking --- src/components/RNMarkdownTextInput.tsx | 43 ++++++++++++------- src/components/RNMaskedTextInput.tsx | 2 +- src/components/RNTextInput.tsx | 2 +- .../Search/SearchAutocompleteInput.tsx | 1 - 4 files changed, 29 insertions(+), 19 deletions(-) diff --git a/src/components/RNMarkdownTextInput.tsx b/src/components/RNMarkdownTextInput.tsx index 5b7b259090d6..dd17fb577073 100644 --- a/src/components/RNMarkdownTextInput.tsx +++ b/src/components/RNMarkdownTextInput.tsx @@ -2,9 +2,11 @@ import type {MarkdownTextInputProps} from '@expensify/react-native-live-markdown import {MarkdownTextInput} from '@expensify/react-native-live-markdown'; import type {ForwardedRef} from 'react'; import React, {useCallback, useEffect, useRef} from 'react'; +import {View} from 'react-native'; import Animated, {useSharedValue} from 'react-native-reanimated'; import useShortMentionsList from '@hooks/useShortMentionsList'; import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; import toggleSelectionFormat from '@libs/FormatSelectionUtils'; import type {ForwardedFSClassProps} from '@libs/Fullstory/types'; import {parseExpensiMarkWithShortMentions} from '@libs/ParsingUtils'; @@ -23,8 +25,9 @@ type RNMarkdownTextInputWithRefProps = Omit & ref?: ForwardedRef; }; -function RNMarkdownTextInputWithRef({maxLength, parser, ref, forwardedFSClass = CONST.FULLSTORY.CLASS.MASK, ...props}: RNMarkdownTextInputWithRefProps) { +function RNMarkdownTextInputWithRef({maxLength, parser, ref, forwardedFSClass = CONST.FULLSTORY.CLASS.UNMASK, ...props}: RNMarkdownTextInputWithRefProps) { const theme = useTheme(); + const styles = useThemeStyles(); const {availableLoginsList, currentUserMentions} = useShortMentionsList(); const mentionsSharedVal = useSharedValue(availableLoginsList); @@ -71,22 +74,30 @@ function RNMarkdownTextInputWithRef({maxLength, parser, ref, forwardedFSClass = }, [availableLoginsList, mentionsSharedVal]); return ( - + > + + ); } diff --git a/src/components/RNMaskedTextInput.tsx b/src/components/RNMaskedTextInput.tsx index 9891b3fa4830..0d8136e604a5 100644 --- a/src/components/RNMaskedTextInput.tsx +++ b/src/components/RNMaskedTextInput.tsx @@ -18,7 +18,7 @@ type RNMaskedTextInputWithRefProps = MaskedTextInputProps & ref?: ForwardedRef; }; -function RNMaskedTextInputWithRef({ref, forwardedFSClass = CONST.FULLSTORY.CLASS.MASK, ...props}: RNMaskedTextInputWithRefProps) { +function RNMaskedTextInputWithRef({ref, forwardedFSClass = CONST.FULLSTORY.CLASS.UNMASK, ...props}: RNMaskedTextInputWithRefProps) { const theme = useTheme(); return ( diff --git a/src/components/RNTextInput.tsx b/src/components/RNTextInput.tsx index bffdce8aa466..0ca4c1a3f509 100644 --- a/src/components/RNTextInput.tsx +++ b/src/components/RNTextInput.tsx @@ -17,7 +17,7 @@ type RNTextInputWithRefProps = TextInputProps & ref?: ForwardedRef; }; -function RNTextInputWithRef({ref, forwardedFSClass = CONST.FULLSTORY.CLASS.MASK, ...props}: RNTextInputWithRefProps) { +function RNTextInputWithRef({ref, forwardedFSClass = CONST.FULLSTORY.CLASS.UNMASK, ...props}: RNTextInputWithRefProps) { const theme = useTheme(); return ( diff --git a/src/components/Search/SearchAutocompleteInput.tsx b/src/components/Search/SearchAutocompleteInput.tsx index 09afd73908e2..56489dc3cd46 100644 --- a/src/components/Search/SearchAutocompleteInput.tsx +++ b/src/components/Search/SearchAutocompleteInput.tsx @@ -240,7 +240,6 @@ function SearchAutocompleteInput({ shouldShowClearButton={!!value && !isSearchingForReports} shouldHideClearButton={false} onClearInput={clearFilters} - forwardedFSClass={CONST.FULLSTORY.CLASS.UNMASK} /> From 261a87148b4c993c77420ad9dd7ec5aefb29be4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Thu, 30 Oct 2025 19:50:41 +0000 Subject: [PATCH 07/23] Mask the composer input if the report is masked too --- src/components/Composer/types.ts | 82 +++++++------ .../ComposerWithSuggestions.tsx | 114 +++++++++--------- .../ReportActionCompose.tsx | 4 + 3 files changed, 106 insertions(+), 94 deletions(-) diff --git a/src/components/Composer/types.ts b/src/components/Composer/types.ts index 6eb833d9a356..19e855ade69f 100644 --- a/src/components/Composer/types.ts +++ b/src/components/Composer/types.ts @@ -1,4 +1,5 @@ import type {StyleProp, TextInputProps, TextInputSelectionChangeEvent, TextStyle} from 'react-native'; +import type {ForwardedFSClassProps} from '@libs/Fullstory/types'; import type {FileObject} from '@src/types/utils/Attachment'; type TextSelection = { @@ -12,62 +13,63 @@ type CustomSelectionChangeEvent = TextInputSelectionChangeEvent & { positionY?: number; }; -type ComposerProps = Omit & { - /** Indicate whether input is multiline */ - multiline?: boolean; +type ComposerProps = Omit & + ForwardedFSClassProps & { + /** Indicate whether input is multiline */ + multiline?: boolean; - /** Maximum number of lines in the text input */ - maxLines?: number; + /** Maximum number of lines in the text input */ + maxLines?: number; - /** The default value of the comment box */ - defaultValue?: string; + /** The default value of the comment box */ + defaultValue?: string; - /** The value of the comment box */ - value?: string; + /** The value of the comment box */ + value?: string; - /** - * Callback when the input was cleared using the .clear ref method. - * The text parameter will be the value of the text that was cleared. - */ - onClear?: (text: string) => void; + /** + * Callback when the input was cleared using the .clear ref method. + * The text parameter will be the value of the text that was cleared. + */ + onClear?: (text: string) => void; - /** Callback method handle when the input is changed */ - onChangeText?: (numberOfLines: string) => void; + /** Callback method handle when the input is changed */ + onChangeText?: (numberOfLines: string) => void; - /** Callback method to handle pasting a file */ - onPasteFile?: (files: FileObject | FileObject[]) => void; + /** Callback method to handle pasting a file */ + onPasteFile?: (files: FileObject | FileObject[]) => void; - /** General styles to apply to the text input */ - // eslint-disable-next-line react/forbid-prop-types - style?: StyleProp; + /** General styles to apply to the text input */ + // eslint-disable-next-line react/forbid-prop-types + style?: StyleProp; - /** Whether or not this TextInput is disabled. */ - isDisabled?: boolean; + /** Whether or not this TextInput is disabled. */ + isDisabled?: boolean; - /** Set focus to this component the first time it renders. + /** Set focus to this component the first time it renders. Override this in case you need to set focus on one field out of many, or when you want to disable autoFocus */ - autoFocus?: boolean; + autoFocus?: boolean; - /** Update selection position on change */ - onSelectionChange?: (event: CustomSelectionChangeEvent) => void; + /** Update selection position on change */ + onSelectionChange?: (event: CustomSelectionChangeEvent) => void; - /** Selection Object */ - selection?: TextSelection; + /** Selection Object */ + selection?: TextSelection; - /** Should we calculate the caret position */ - shouldCalculateCaretPosition?: boolean; + /** Should we calculate the caret position */ + shouldCalculateCaretPosition?: boolean; - /** Function to check whether composer is covered up or not */ - checkComposerVisibility?: () => boolean; + /** Function to check whether composer is covered up or not */ + checkComposerVisibility?: () => boolean; - /** Whether the full composer is open */ - isComposerFullSize?: boolean; + /** Whether the full composer is open */ + isComposerFullSize?: boolean; - /** Should make the input only scroll inside the element avoid scroll out to parent */ - shouldContainScroll?: boolean; + /** Should make the input only scroll inside the element avoid scroll out to parent */ + shouldContainScroll?: boolean; - /** Indicates whether the composer is in a group policy report. Used for disabling report mentioning style in markdown input */ - isGroupPolicyReport?: boolean; -}; + /** Indicates whether the composer is in a group policy report. Used for disabling report mentioning style in markdown input */ + isGroupPolicyReport?: boolean; + }; export type {TextSelection, ComposerProps, CustomSelectionChangeEvent}; diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index b7b8193ef34e..e85703cbb13b 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -27,6 +27,7 @@ import {canSkipTriggerHotkeys, findCommonSuffixLength, insertText, insertWhiteSp import convertToLTRForComposer from '@libs/convertToLTRForComposer'; import {containsOnlyEmojis, extractEmojis, getAddedEmojis, replaceAndExtractEmojis} from '@libs/EmojiUtils'; import focusComposerWithDelay from '@libs/focusComposerWithDelay'; +import type {ForwardedFSClassProps} from '@libs/Fullstory/types'; import getPlatform from '@libs/getPlatform'; import {addKeyDownPressListener, removeKeyDownPressListener} from '@libs/KeyboardShortcut/KeyDownPressListener'; import {detectAndRewritePaste} from '@libs/MarkdownLinkHelpers'; @@ -60,85 +61,86 @@ type SyncSelection = { type NewlyAddedChars = {startIndex: number; endIndex: number; diff: string}; -type ComposerWithSuggestionsProps = Partial & { - /** Report ID */ - reportID: string; +type ComposerWithSuggestionsProps = Partial & + ForwardedFSClassProps & { + /** Report ID */ + reportID: string; - /** Callback to focus composer */ - onFocus: () => void; + /** Callback to focus composer */ + onFocus: () => void; - /** Callback to blur composer */ - onBlur: (event: BlurEvent) => void; + /** Callback to blur composer */ + onBlur: (event: BlurEvent) => void; - /** Callback when layout of composer changes */ - onLayout?: (event: LayoutChangeEvent) => void; + /** Callback when layout of composer changes */ + onLayout?: (event: LayoutChangeEvent) => void; - /** Callback to update the value of the composer */ - onValueChange: (value: string) => void; + /** Callback to update the value of the composer */ + onValueChange: (value: string) => void; - /** Callback when the composer got cleared on the UI thread */ - onCleared?: (text: string) => void; + /** Callback when the composer got cleared on the UI thread */ + onCleared?: (text: string) => void; - /** Whether the composer is full size */ - isComposerFullSize: boolean; + /** Whether the composer is full size */ + isComposerFullSize: boolean; - /** Function to set whether the full composer is available */ - setIsFullComposerAvailable: (isFullComposerAvailable: boolean) => void; + /** Function to set whether the full composer is available */ + setIsFullComposerAvailable: (isFullComposerAvailable: boolean) => void; - /** Whether the menu is visible */ - isMenuVisible: boolean; + /** Whether the menu is visible */ + isMenuVisible: boolean; - /** The placeholder for the input */ - inputPlaceholder: string; + /** The placeholder for the input */ + inputPlaceholder: string; - /** Callback when a file is pasted */ - onPasteFile: (file: FileObject | FileObject[]) => void; + /** Callback when a file is pasted */ + onPasteFile: (file: FileObject | FileObject[]) => void; - /** Whether the input is disabled, defaults to false */ - disabled?: boolean; + /** Whether the input is disabled, defaults to false */ + disabled?: boolean; - /** Function to set whether the comment is empty */ - setIsCommentEmpty: (isCommentEmpty: boolean) => void; + /** Function to set whether the comment is empty */ + setIsCommentEmpty: (isCommentEmpty: boolean) => void; - /** Function to handle sending a message */ - handleSendMessage: () => void; + /** Function to handle sending a message */ + handleSendMessage: () => void; - /** Whether the compose input should show */ - shouldShowComposeInput: OnyxEntry; + /** Whether the compose input should show */ + shouldShowComposeInput: OnyxEntry; - /** Function to measure the parent container */ - measureParentContainer: (callback: MeasureInWindowOnSuccessCallback) => void; + /** Function to measure the parent container */ + measureParentContainer: (callback: MeasureInWindowOnSuccessCallback) => void; - /** Whether the scroll is likely to trigger a layout */ - isScrollLikelyLayoutTriggered: RefObject; + /** Whether the scroll is likely to trigger a layout */ + isScrollLikelyLayoutTriggered: RefObject; - /** Function to raise the scroll is likely layout triggered */ - raiseIsScrollLikelyLayoutTriggered: () => void; + /** Function to raise the scroll is likely layout triggered */ + raiseIsScrollLikelyLayoutTriggered: () => void; - /** The ref to the suggestions */ - suggestionsRef: React.RefObject; + /** The ref to the suggestions */ + suggestionsRef: React.RefObject; - /** The ref to the next modal will open */ - isNextModalWillOpenRef: RefObject; + /** The ref to the next modal will open */ + isNextModalWillOpenRef: RefObject; - /** The last report action */ - lastReportAction?: OnyxEntry; + /** The last report action */ + lastReportAction?: OnyxEntry; - /** Whether to include chronos */ - includeChronos?: boolean; + /** Whether to include chronos */ + includeChronos?: boolean; - /** Whether report is from group policy */ - isGroupPolicyReport: boolean; + /** Whether report is from group policy */ + isGroupPolicyReport: boolean; - /** policy ID of the report */ - policyID?: string; + /** policy ID of the report */ + policyID?: string; - /** Whether the main composer was hidden */ - didHideComposerInput?: boolean; + /** Whether the main composer was hidden */ + didHideComposerInput?: boolean; - /** Reference to the outer element */ - ref?: ForwardedRef; -}; + /** Reference to the outer element */ + ref?: ForwardedRef; + }; type SwitchToCurrentReportProps = { preexistingReportID: string; @@ -225,6 +227,9 @@ function ComposerWithSuggestions({ // For testing children, didHideComposerInput, + + // Fullstory + forwardedFSClass, }: ComposerWithSuggestionsProps) { const {isKeyboardShown} = useKeyboardState(); const theme = useTheme(); @@ -852,6 +857,7 @@ function ComposerWithSuggestions({ onScroll={hideSuggestionMenu} shouldContainScroll={isMobileSafari()} isGroupPolicyReport={isGroupPolicyReport} + forwardedFSClass={forwardedFSClass} /> diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index aaaf759eaf50..f1a900d68113 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -33,6 +33,7 @@ import canFocusInputOnScreenFocus from '@libs/canFocusInputOnScreenFocus'; import ComposerFocusManager from '@libs/ComposerFocusManager'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import DomUtils from '@libs/DomUtils'; +import FS from '@libs/Fullstory'; import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; import Performance from '@libs/Performance'; import {getLinkedTransactionID, getReportAction, isMoneyRequestAction} from '@libs/ReportActionsUtils'; @@ -469,6 +470,8 @@ function ReportActionCompose({ setIsAttachmentPreviewActive, }); + const fsClass = FS.getChatFSClass(undefined, report); + return ( @@ -547,6 +550,7 @@ function ReportActionCompose({ measureParentContainer={measureContainer} onValueChange={onValueChange} didHideComposerInput={didHideComposerInput} + forwardedFSClass={fsClass} /> {shouldDisplayDualDropZone && ( Date: Fri, 31 Oct 2025 18:31:52 +0000 Subject: [PATCH 08/23] Mask 2FA codes --- .../settings/Security/TwoFactorAuth/CopyCodesPage.tsx | 5 ++++- src/pages/settings/Security/TwoFactorAuth/VerifyPage.tsx | 7 +++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/pages/settings/Security/TwoFactorAuth/CopyCodesPage.tsx b/src/pages/settings/Security/TwoFactorAuth/CopyCodesPage.tsx index 346d03404b76..1d6fa84393ca 100644 --- a/src/pages/settings/Security/TwoFactorAuth/CopyCodesPage.tsx +++ b/src/pages/settings/Security/TwoFactorAuth/CopyCodesPage.tsx @@ -84,7 +84,10 @@ function CopyCodesPage({route}: TwoFactorAuthPageProps) { ) : ( <> - + {!!account?.recoveryCodes && account?.recoveryCodes?.split(', ').map((code) => ( {translate('twoFactorAuth.authenticatorApp')}. - + {translate('twoFactorAuth.addKey')} - {!!account?.twoFactorAuthSecretKey && {splitSecretInChunks(account?.twoFactorAuthSecretKey ?? '')}} + {!!account?.twoFactorAuthSecretKey && {splitSecretInChunks(account?.twoFactorAuthSecretKey ?? '')}} Date: Fri, 31 Oct 2025 18:40:44 +0000 Subject: [PATCH 09/23] Only mask inputs in payment card form --- src/components/AddPaymentCard/PaymentCardForm.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/AddPaymentCard/PaymentCardForm.tsx b/src/components/AddPaymentCard/PaymentCardForm.tsx index fddbcd2f0838..919e77e53a48 100644 --- a/src/components/AddPaymentCard/PaymentCardForm.tsx +++ b/src/components/AddPaymentCard/PaymentCardForm.tsx @@ -269,7 +269,6 @@ function PaymentCardForm({ submitButtonText={submitButtonText} scrollContextEnabled style={[styles.mh5, styles.flexGrow1]} - forwardedFSClass={CONST.FULLSTORY.CLASS.MASK} > @@ -308,6 +309,7 @@ function PaymentCardForm({ placeholder={translate(label.defaults.expirationDate)} inputMode={CONST.INPUT_MODE.NUMERIC} maxLength={5} + forwardedFSClass={CONST.FULLSTORY.CLASS.MASK} /> @@ -320,6 +322,7 @@ function PaymentCardForm({ role={CONST.ROLE.PRESENTATION} maxLength={4} inputMode={CONST.INPUT_MODE.NUMERIC} + forwardedFSClass={CONST.FULLSTORY.CLASS.MASK} /> @@ -334,6 +337,7 @@ function PaymentCardForm({ maxInputLength={CONST.FORM_CHARACTER_LIMIT} // Limit the address search only to the USA until we fully can support international debit cards limitSearchesToCountry={CONST.COUNTRY.US} + forwardedFSClass={CONST.FULLSTORY.CLASS.MASK} /> )} From 263f3103813fd75f560710c1af6142a890670be2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Fri, 31 Oct 2025 18:55:05 +0000 Subject: [PATCH 10/23] Fix LHN chat preview to only mask for the masked reports --- src/components/LHNOptionsList/OptionRowLHN.tsx | 7 +++++-- src/pages/home/HeaderView.tsx | 3 --- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 10ae16d623ef..b453e4028060 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -21,6 +21,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import DateUtils from '@libs/DateUtils'; import DomUtils from '@libs/DomUtils'; import {containsCustomEmoji as containsCustomEmojiUtils, containsOnlyCustomEmoji} from '@libs/EmojiUtils'; +import FS from '@libs/Fullstory'; import {shouldOptionShowTooltip, shouldUseBoldText} from '@libs/OptionsListUtils'; import Performance from '@libs/Performance'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; @@ -163,6 +164,8 @@ function OptionRowLHN({ const subscriptAvatarBorderColor = isOptionFocused ? focusedBackgroundColor : theme.sidebar; const firstIcon = optionItem.icons?.at(0); + const alternateTextFSClass = FS.getChatFSClass(undefined, report); + const onOptionPress = (event: GestureResponderEvent | KeyboardEvent | undefined) => { Performance.markStart(CONST.TIMING.OPEN_REPORT); Timing.start(CONST.TIMING.OPEN_REPORT); @@ -291,7 +294,7 @@ function OptionRowLHN({ style={alternateTextStyle} numberOfLines={1} accessibilityLabel={translate('accessibilityHints.lastChatMessagePreview')} - fsClass={CONST.FULLSTORY.CLASS.MASK} + fsClass={alternateTextFSClass} > {alternateTextContainsCustomEmojiWithText ? ( {optionItem.descriptiveText} diff --git a/src/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx index f8f65c4831f5..93753c7e1cb0 100644 --- a/src/pages/home/HeaderView.tsx +++ b/src/pages/home/HeaderView.tsx @@ -32,7 +32,6 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useSubscriptionPlan from '@hooks/useSubscriptionPlan'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import FS from '@libs/Fullstory'; import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; import Navigation from '@libs/Navigation/Navigation'; import {getPersonalDetailsForAccountIDs} from '@libs/OptionsListUtils'; @@ -146,7 +145,6 @@ function HeaderView({report, parentReportAction, onNavigationMenuButtonClicked, const isPersonalExpenseChat = isPolicyExpenseChat && isCurrentUserSubmitter(report); const hasTeam2025Pricing = useHasTeam2025Pricing(); const subscriptionPlan = useSubscriptionPlan(); - const displayNamesFSClass = FS.getChatFSClass(personalDetails, report); const shouldShowSubtitle = () => { if (!subtitle) { @@ -297,7 +295,6 @@ function HeaderView({report, parentReportAction, onNavigationMenuButtonClicked, shouldUseFullTitle={isChatRoom || isPolicyExpenseChat || isChatThread || isTaskReport || shouldUseGroupTitle || isReportArchived} renderAdditionalText={renderAdditionalText} shouldAddEllipsis={shouldAddEllipsis} - forwardedFSClass={displayNamesFSClass} /> {!isEmptyObject(parentNavigationSubtitleData) && ( From d9cd30fe67b66572e8ea04dbddcdc04e4573a136 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Wed, 5 Nov 2025 18:06:22 +0000 Subject: [PATCH 11/23] Fix and improve getChatFSClass implementation --- jest/setupMockFullstoryLib.ts | 1 + .../LHNOptionsList/OptionRowLHN.tsx | 2 +- .../ChatListItem.tsx | 2 +- .../Search/TaskListItem.tsx | 3 +- src/libs/Fullstory/common.ts | 47 ++++++++++------- src/libs/Fullstory/types.ts | 4 +- src/libs/ReportUtils.ts | 52 ------------------- .../ReportActionCompose.tsx | 2 +- src/pages/home/report/ReportActionsList.tsx | 7 ++- 9 files changed, 39 insertions(+), 81 deletions(-) diff --git a/jest/setupMockFullstoryLib.ts b/jest/setupMockFullstoryLib.ts index 343703ee2f08..51a5f85b3af5 100644 --- a/jest/setupMockFullstoryLib.ts +++ b/jest/setupMockFullstoryLib.ts @@ -18,6 +18,7 @@ export default function mockFSLibrary() { getChatFSClass: jest.fn(), init: jest.fn(), onReady: jest.fn(), + shouldInitialize: jest.fn().mockReturnValue(false), consent: jest.fn(), identify: jest.fn(), consentAndIdentify: jest.fn(), diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 518c877e84b7..c804b17dd6ec 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -167,7 +167,7 @@ function OptionRowLHN({ const subscriptAvatarBorderColor = isOptionFocused ? focusedBackgroundColor : theme.sidebar; const firstIcon = optionItem.icons?.at(0); - const alternateTextFSClass = FS.getChatFSClass(undefined, report); + const alternateTextFSClass = FS.getChatFSClass(report); const onOptionPress = (event: GestureResponderEvent | KeyboardEvent | undefined) => { Performance.markStart(CONST.TIMING.OPEN_REPORT); diff --git a/src/components/SelectionListWithSections/ChatListItem.tsx b/src/components/SelectionListWithSections/ChatListItem.tsx index a337558d57af..233a7c44cf0c 100644 --- a/src/components/SelectionListWithSections/ChatListItem.tsx +++ b/src/components/SelectionListWithSections/ChatListItem.tsx @@ -49,7 +49,7 @@ function ChatListItem({ item.cursorStyle, ]; - const fsClass = FS.getChatFSClass(personalDetails, report); + const fsClass = FS.getChatFSClass(report); return ( ({ onLongPressRow, shouldSyncFocus, allReports, - personalDetails, }: TaskListItemProps) { const taskItem = item as unknown as TaskListItemType; const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${taskItem?.parentReportID}`]; @@ -53,7 +52,7 @@ function TaskListItem({ backgroundColor: theme.highlightBG, }); - const fsClass = FS.getChatFSClass(personalDetails, parentReport); + const fsClass = FS.getChatFSClass(parentReport); return ( { - if (!report?.participants) { +const allowedReportChatTypes: Array> = [ + CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, + CONST.REPORT.CHAT_TYPE.POLICY_ADMINS, + CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE, + CONST.REPORT.CHAT_TYPE.INVOICE, +]; + +const allowedReportTypes: Array> = [CONST.REPORT.TYPE.IOU, CONST.REPORT.TYPE.EXPENSE, CONST.REPORT.TYPE.INVOICE]; + +const getChatFSClass: GetChatFSClass = (report) => { + if (!report) { return CONST.FULLSTORY.CLASS.UNMASK; } - const participantAccountIDs = Object.keys(report.participants); + // Self DMs should be masked. + if (report.chatType === CONST.REPORT.CHAT_TYPE.SELF_DM) { + return CONST.FULLSTORY.CLASS.MASK; + } - if (report.type === CONST.REPORT.TYPE.IOU || report.type === CONST.REPORT.TYPE.EXPENSE || report.type === CONST.REPORT.TYPE.INVOICE) { + // Workspace expense chat, #admins, #announce rooms and invoices should be unmasked. + if (report.chatType && allowedReportChatTypes.includes(report.chatType)) { return CONST.FULLSTORY.CLASS.UNMASK; } + // IOUs, expenses and invoices should be unmasked. + if (report.type && (allowedReportTypes as string[]).includes(report.type)) { + return CONST.FULLSTORY.CLASS.UNMASK; + } + + // If the report doesn't meet the condition above we check if the parent report is an IOU, expense or invoice. const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`]; - if (parentReport && (parentReport.type === CONST.REPORT.TYPE.IOU || parentReport.type === CONST.REPORT.TYPE.EXPENSE || parentReport.type === CONST.REPORT.TYPE.INVOICE)) { + if (parentReport?.type && (allowedReportTypes as string[]).includes(parentReport.type)) { return CONST.FULLSTORY.CLASS.UNMASK; } - // DMs / Groups / Rooms - if (participantAccountIDs.length >= 2) { + // DMs / Groups / Rooms should be unmasked only if Concierge is in the chat. + const participantAccountIDs = Object.keys(report.participants ?? {}); + if (participantAccountIDs.length > 0) { return chatIncludesConcierge(report) ? CONST.FULLSTORY.CLASS.UNMASK : CONST.FULLSTORY.CLASS.MASK; } return CONST.FULLSTORY.CLASS.UNMASK; - - // if (isConciergeChatReport(report)) { - // return CONST.FULLSTORY.CLASS.UNMASK; - // } - - // if (shouldUnmaskChat(context, report)) { - // return CONST.FULLSTORY.CLASS.UNMASK; - // } - - // return CONST.FULLSTORY.CLASS.MASK; }; const shouldInitializeFullstory: ShouldInitialize = (userMetadata, envName) => { diff --git a/src/libs/Fullstory/types.ts b/src/libs/Fullstory/types.ts index f56577b0f0ea..536d92588d00 100644 --- a/src/libs/Fullstory/types.ts +++ b/src/libs/Fullstory/types.ts @@ -1,7 +1,7 @@ import type {OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; -import type {OnyxInputOrEntry, PersonalDetailsList, Report, UserMetadata} from '@src/types/onyx'; +import type {OnyxInputOrEntry, Report, UserMetadata} from '@src/types/onyx'; type FSClass = ValueOf; @@ -24,7 +24,7 @@ interface FSPageLikeConstructor { new (name: string, properties: PropertiesWithoutPageName): FSPageLike; } -type GetChatFSClass = (context: OnyxEntry, report: OnyxInputOrEntry) => FSClass; +type GetChatFSClass = (report: OnyxInputOrEntry) => FSClass; type ShouldInitialize = (userMetadata: UserMetadata, envName: string) => boolean; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index fb01bc5d9610..39570745f6b6 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -11987,57 +11987,6 @@ function hasInvoiceReports() { return reports.some((report) => isInvoiceReport(report)); } -function shouldUnmaskChat(participantsContext: OnyxEntry, report: OnyxInputOrEntry): boolean { - if (!report?.participants) { - return true; - } - - if (isThread(report) && report?.chatType && report?.chatType === CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT) { - return true; - } - - if (isThread(report) && report?.type === CONST.REPORT.TYPE.EXPENSE) { - return true; - } - - if (isAdminRoom(report)) { - return true; - } - - const participantAccountIDs = Object.keys(report.participants); - - if (participantAccountIDs.length > 2) { - return false; - } - - if (participantsContext) { - let teamInChat = false; - let userInChat = false; - - for (const participantAccountID of participantAccountIDs) { - const id = Number(participantAccountID); - const contextAccountData = participantsContext[id]; - - if (contextAccountData) { - const login = contextAccountData.login ?? ''; - - if (login.endsWith(CONST.EMAIL.EXPENSIFY_EMAIL_DOMAIN) || login.endsWith(CONST.EMAIL.EXPENSIFY_TEAM_EMAIL_DOMAIN)) { - teamInChat = true; - } else { - userInChat = true; - } - } - } - - // exclude teamOnly chat - if (teamInChat && userInChat) { - return true; - } - } - - return false; -} - function getReportMetadata(reportID: string | undefined) { return reportID ? allReportMetadataKeyValue[reportID] : undefined; } @@ -12816,7 +12765,6 @@ export { getAllReportErrors, getAllReportActionsErrorsAndReportActionThatRequiresAttention, hasInvoiceReports, - shouldUnmaskChat, shouldExcludeAncestorReportAction, getReportMetadata, buildOptimisticSelfDMReport, diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index f33e0fcd2355..0d5e02f78bd7 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -472,7 +472,7 @@ function ReportActionCompose({ setIsAttachmentPreviewActive, }); - const fsClass = FS.getChatFSClass(undefined, report); + const fsClass = FS.getChatFSClass(report); return ( diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index 114337042278..6467d802ac84 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -3,14 +3,14 @@ import {useIsFocused, useRoute} from '@react-navigation/native'; import {isUserValidatedSelector} from '@selectors/Account'; import {accountIDSelector} from '@selectors/Session'; import {tierNameSelector} from '@selectors/UserWallet'; -import React, {memo, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState} from 'react'; +import React, {memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState} from 'react'; import type {LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent} from 'react-native'; import {DeviceEventEmitter, InteractionManager, View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {renderScrollComponent as renderActionSheetAwareScrollView} from '@components/ActionSheetAwareScrollView'; import InvertedFlatList from '@components/InvertedFlatList'; import {AUTOSCROLL_TO_TOP_THRESHOLD} from '@components/InvertedFlatList/BaseInvertedFlatList'; -import {PersonalDetailsContext, usePersonalDetails} from '@components/OnyxListItemProvider'; +import {usePersonalDetails} from '@components/OnyxListItemProvider'; import ReportActionsSkeletonView from '@components/ReportActionsSkeletonView'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useIsAnonymousUser from '@hooks/useIsAnonymousUser'; @@ -182,7 +182,6 @@ function ReportActionsList({ const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true}); const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {canBeMissing: true}); const [accountID] = useOnyx(ONYXKEYS.SESSION, {selector: accountIDSelector, canBeMissing: true}); - const participantsContext = useContext(PersonalDetailsContext); const isReportArchived = useReportIsArchived(report?.reportID); const [userWalletTierName] = useOnyx(ONYXKEYS.USER_WALLET, {selector: tierNameSelector, canBeMissing: false}); const [isUserValidated] = useOnyx(ONYXKEYS.ACCOUNT, {selector: isUserValidatedSelector, canBeMissing: true}); @@ -601,7 +600,7 @@ function ReportActionsList({ // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [report.reportID]); - const reportActionsListFSClass = FS.getChatFSClass(participantsContext, report); + const reportActionsListFSClass = FS.getChatFSClass(report); const lastIOUActionWithError = sortedVisibleReportActions.find((action) => action.errors); const prevLastIOUActionWithError = usePrevious(lastIOUActionWithError); From b037369659fceec6d401cf70ffaa0be8475a2548 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Wed, 5 Nov 2025 18:46:25 +0000 Subject: [PATCH 12/23] Fix masking for company card feeds --- src/pages/workspace/companyCards/addNew/AddNewCardPage.tsx | 7 +------ src/pages/workspace/companyCards/addNew/DetailsStep.tsx | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/pages/workspace/companyCards/addNew/AddNewCardPage.tsx b/src/pages/workspace/companyCards/addNew/AddNewCardPage.tsx index 928b8438466b..e653e2d9f407 100644 --- a/src/pages/workspace/companyCards/addNew/AddNewCardPage.tsx +++ b/src/pages/workspace/companyCards/addNew/AddNewCardPage.tsx @@ -131,12 +131,7 @@ function AddNewCardPage({policy}: WithPolicyAndFullscreenLoadingProps) { return ( <> - - {CurrentStep} - + {CurrentStep} {!!feedProvider && !isStripeFeedProvider ? translate(`workspace.companyCards.addNewCard.feedDetails.${feedProvider}.title`) : ''} - {renderInputs()} + {renderInputs()} {!!feedProvider && !isStripeFeedProvider && ( Date: Fri, 7 Nov 2025 15:31:09 +0000 Subject: [PATCH 13/23] Fix masking on USD bank account flow --- src/components/AddressSearch/index.tsx | 2 + src/components/AddressSearch/types.ts | 3 +- src/components/DatePicker/DatePickerModal.tsx | 2 + src/components/DatePicker/index.tsx | 3 + src/components/DatePicker/types.ts | 3 +- src/components/MenuItem.tsx | 9 ++- src/components/SubStepForms/AddressStep.tsx | 80 ++++++++++--------- .../SubStepForms/ConfirmationStep.tsx | 47 +++++++---- .../SubStepForms/DateOfBirthStep.tsx | 40 +++++----- src/components/SubStepForms/FullNameStep.tsx | 59 +++++++------- .../SubStepForms/SingleFieldStep.tsx | 72 +++++++++-------- .../AddressFormFields.tsx | 9 ++- .../PersonalInfo/subSteps/Address.tsx | 2 + .../PersonalInfo/subSteps/Confirmation.tsx | 1 + .../PersonalInfo/subSteps/DateOfBirth.tsx | 2 + .../PersonalInfo/subSteps/FullName.tsx | 2 + .../subSteps/SocialSecurityNumber.tsx | 1 + .../USD/USDVerifiedBankAccountFlow.tsx | 9 +-- 18 files changed, 199 insertions(+), 147 deletions(-) diff --git a/src/components/AddressSearch/index.tsx b/src/components/AddressSearch/index.tsx index 4e0fe3d646f7..40f3b453ebf3 100644 --- a/src/components/AddressSearch/index.tsx +++ b/src/components/AddressSearch/index.tsx @@ -80,6 +80,7 @@ function AddressSearch( value, locationBias, caretHidden, + forwardedFSClass, }: AddressSearchProps, ref: ForwardedRef, ) { @@ -366,6 +367,7 @@ function AddressSearch( (null); @@ -74,6 +75,7 @@ function DatePickerModal({ shouldEnableNewFocusManagement shouldMeasureAnchorPositionFromTop={shouldPositionFromTop} shouldSkipRemeasurement + forwardedFSClass={forwardedFSClass} > , ) { @@ -145,6 +146,7 @@ function DatePicker( textInputContainerStyles={isModalVisible ? styles.borderColorFocus : {}} shouldHideClearButton={shouldHideClearButton} onClearInput={handleClear} + forwardedFSClass={forwardedFSClass} /> @@ -158,6 +160,7 @@ function DatePicker( onClose={closeDatePicker} anchorPosition={popoverPosition} shouldPositionFromTop={!isInverted} + forwardedFSClass={forwardedFSClass} /> ); diff --git a/src/components/DatePicker/types.ts b/src/components/DatePicker/types.ts index 1e4bafa5f1d9..dceffd7f6c1e 100644 --- a/src/components/DatePicker/types.ts +++ b/src/components/DatePicker/types.ts @@ -1,8 +1,9 @@ import type PopoverWithMeasuredContentProps from '@components/PopoverWithMeasuredContent/types'; import type {BaseTextInputProps} from '@components/TextInput/BaseTextInput/types'; +import type {ForwardedFSClassProps} from '@libs/Fullstory/types'; import type {OnyxFormValuesMapping} from '@src/ONYXKEYS'; -type DatePickerBaseProps = { +type DatePickerBaseProps = ForwardedFSClassProps & { /** * The datepicker supports any value that `new Date()` can parse. * `onInputChange` would always be called with a Date (or null) diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index b9c4f4bfd7ab..59be1c00fa5a 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -12,6 +12,7 @@ import ControlSelection from '@libs/ControlSelection'; import convertToLTR from '@libs/convertToLTR'; import {canUseTouchScreen, hasHoverSupport} from '@libs/DeviceCapabilities'; import {containsCustomEmoji, containsOnlyCustomEmoji} from '@libs/EmojiUtils'; +import type {ForwardedFSClassProps} from '@libs/Fullstory/types'; import getButtonState from '@libs/getButtonState'; import mergeRefs from '@libs/mergeRefs'; import Parser from '@libs/Parser'; @@ -64,7 +65,7 @@ type NoIcon = { icon?: undefined; }; -type MenuItemBaseProps = { +type MenuItemBaseProps = ForwardedFSClassProps & { /** Reference to the outer element */ ref?: PressableRef | Ref; @@ -506,6 +507,7 @@ function MenuItem({ copyValue = title, copyable = false, hasSubMenuItems = false, + forwardedFSClass, ref, }: MenuItemProps) { const theme = useTheme(); @@ -835,7 +837,10 @@ function MenuItem({ )} {(!!title || !!shouldShowTitleIcon) && ( - + {!!title && (shouldRenderAsHTML || (shouldParseTitle && !!html.length)) && ( diff --git a/src/components/SubStepForms/AddressStep.tsx b/src/components/SubStepForms/AddressStep.tsx index 322517d69358..6bcfc2eade86 100644 --- a/src/components/SubStepForms/AddressStep.tsx +++ b/src/components/SubStepForms/AddressStep.tsx @@ -6,6 +6,7 @@ import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import type {SubStepProps} from '@hooks/useSubStep/types'; import useThemeStyles from '@hooks/useThemeStyles'; +import type {ForwardedFSClassProps} from '@libs/Fullstory/types'; import {getFieldRequiredErrors, isValidAddress, isValidZipCode, isValidZipCodeInternational} from '@libs/ValidationUtils'; import AddressFormFields from '@pages/ReimbursementAccount/AddressFormFields'; import HelpLinks from '@pages/ReimbursementAccount/USD/Requestor/PersonalInfo/HelpLinks'; @@ -19,61 +20,62 @@ type AddressValues = { zipCode: string; }; -type AddressStepProps = SubStepProps & { - /** The ID of the form */ - formID: TFormID; +type AddressStepProps = SubStepProps & + ForwardedFSClassProps & { + /** The ID of the form */ + formID: TFormID; - /** The title of the form */ - formTitle: string; + /** The title of the form */ + formTitle: string; - /** The disclaimer informing that PO box is not allowed */ - formPOBoxDisclaimer?: string; + /** The disclaimer informing that PO box is not allowed */ + formPOBoxDisclaimer?: string; - /** The validation function to call when the form is submitted */ - customValidate?: (values: FormOnyxValues) => FormInputErrors; + /** The validation function to call when the form is submitted */ + customValidate?: (values: FormOnyxValues) => FormInputErrors; - /** A function to call when the form is submitted */ - onSubmit: (values: FormOnyxValues) => void; + /** A function to call when the form is submitted */ + onSubmit: (values: FormOnyxValues) => void; - /** Fields list of the form */ - stepFields: Array>; + /** Fields list of the form */ + stepFields: Array>; - /** The IDs of the input fields */ - inputFieldsIDs: AddressValues; + /** The IDs of the input fields */ + inputFieldsIDs: AddressValues; - /** The default values for the form */ - defaultValues: AddressValues; + /** The default values for the form */ + defaultValues: AddressValues; - /** Should show help links */ - shouldShowHelpLinks?: boolean; + /** Should show help links */ + shouldShowHelpLinks?: boolean; - /** Indicates if country selector should be displayed */ - shouldDisplayCountrySelector?: boolean; + /** Indicates if country selector should be displayed */ + shouldDisplayCountrySelector?: boolean; - /** Indicates if state selector should be displayed */ - shouldDisplayStateSelector?: boolean; + /** Indicates if state selector should be displayed */ + shouldDisplayStateSelector?: boolean; - /** Label for the state selector */ - stateSelectorLabel?: string; + /** Label for the state selector */ + stateSelectorLabel?: string; - /** The title of the state selector modal */ - stateSelectorModalHeaderTitle?: string; + /** The title of the state selector modal */ + stateSelectorModalHeaderTitle?: string; - /** The title of the state selector search input */ - stateSelectorSearchInputTitle?: string; + /** The title of the state selector search input */ + stateSelectorSearchInputTitle?: string; - /** Callback to be called when the country is changed */ - onCountryChange?: (country: unknown) => void; + /** Callback to be called when the country is changed */ + onCountryChange?: (country: unknown) => void; - /** Translation key of street field */ - streetTranslationKey?: TranslationPaths; + /** Translation key of street field */ + streetTranslationKey?: TranslationPaths; - /** Indicates if country can be changed by user */ - shouldAllowCountryChange?: boolean; + /** Indicates if country can be changed by user */ + shouldAllowCountryChange?: boolean; - /** Indicates if zip code format should be validated */ - shouldValidateZipCodeFormat?: boolean; -}; + /** Indicates if zip code format should be validated */ + shouldValidateZipCodeFormat?: boolean; + }; function AddressStep({ formID, @@ -95,6 +97,7 @@ function AddressStep({ streetTranslationKey = 'common.streetAddress', shouldAllowCountryChange = true, shouldValidateZipCodeFormat = true, + forwardedFSClass, }: AddressStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -153,6 +156,7 @@ function AddressStep({ onCountryChange={onCountryChange} shouldAllowCountryChange={shouldAllowCountryChange} shouldValidateZipCodeFormat={shouldValidateZipCodeFormat} + forwardedFSClass={forwardedFSClass} /> {!!shouldShowHelpLinks && } diff --git a/src/components/SubStepForms/ConfirmationStep.tsx b/src/components/SubStepForms/ConfirmationStep.tsx index c7e963dc13db..641433b78242 100644 --- a/src/components/SubStepForms/ConfirmationStep.tsx +++ b/src/components/SubStepForms/ConfirmationStep.tsx @@ -11,6 +11,7 @@ import useNetwork from '@hooks/useNetwork'; import useSafeAreaPaddings from '@hooks/useSafeAreaPaddings'; import type {SubStepProps} from '@hooks/useSubStep/types'; import useThemeStyles from '@hooks/useThemeStyles'; +import type {ForwardedFSClassProps} from '@libs/Fullstory/types'; import CONST from '@src/CONST'; type SummaryItem = { @@ -20,30 +21,41 @@ type SummaryItem = { onPress: () => void; }; -type ConfirmationStepProps = SubStepProps & { - /** The title of the step */ - pageTitle: string; +type ConfirmationStepProps = SubStepProps & + ForwardedFSClassProps & { + /** The title of the step */ + pageTitle: string; - /** The summary items to display */ - summaryItems: SummaryItem[]; + /** The summary items to display */ + summaryItems: SummaryItem[]; - /** Whether show additional section with Onfido terms etc. */ - showOnfidoLinks: boolean; + /** Whether show additional section with Onfido terms etc. */ + showOnfidoLinks: boolean; - /** The title of the Onfido section */ - onfidoLinksTitle?: string; + /** The title of the Onfido section */ + onfidoLinksTitle?: string; - /** Whether the data is loading */ - isLoading?: boolean; + /** Whether the data is loading */ + isLoading?: boolean; - /** The error message to display */ - error?: string; + /** The error message to display */ + error?: string; - /** Whether to apply safe area padding bottom */ - shouldApplySafeAreaPaddingBottom?: boolean; -}; + /** Whether to apply safe area padding bottom */ + shouldApplySafeAreaPaddingBottom?: boolean; + }; -function ConfirmationStep({pageTitle, summaryItems, showOnfidoLinks, onfidoLinksTitle, isLoading, error, onNext, shouldApplySafeAreaPaddingBottom = true}: ConfirmationStepProps) { +function ConfirmationStep({ + pageTitle, + summaryItems, + showOnfidoLinks, + onfidoLinksTitle, + isLoading, + error, + onNext, + shouldApplySafeAreaPaddingBottom = true, + forwardedFSClass, +}: ConfirmationStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const {isOffline} = useNetwork(); @@ -63,6 +75,7 @@ function ConfirmationStep({pageTitle, summaryItems, showOnfidoLinks, onfidoLinks title={title} shouldShowRightIcon={shouldShowRightIcon} onPress={onPress} + forwardedFSClass={forwardedFSClass} /> ))} diff --git a/src/components/SubStepForms/DateOfBirthStep.tsx b/src/components/SubStepForms/DateOfBirthStep.tsx index 1c4bf7b42c61..1c1517e86f32 100644 --- a/src/components/SubStepForms/DateOfBirthStep.tsx +++ b/src/components/SubStepForms/DateOfBirthStep.tsx @@ -8,35 +8,37 @@ import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import type {SubStepProps} from '@hooks/useSubStep/types'; import useThemeStyles from '@hooks/useThemeStyles'; +import type {ForwardedFSClassProps} from '@libs/Fullstory/types'; import {getFieldRequiredErrors, isValidPastDate, meetsMaximumAgeRequirement, meetsMinimumAgeRequirement} from '@libs/ValidationUtils'; import CONST from '@src/CONST'; import type {OnyxFormValuesMapping} from '@src/ONYXKEYS'; -type DateOfBirthStepProps = SubStepProps & { - /** The ID of the form */ - formID: TFormID; +type DateOfBirthStepProps = SubStepProps & + ForwardedFSClassProps & { + /** The ID of the form */ + formID: TFormID; - /** The title of the form */ - formTitle: string; + /** The title of the form */ + formTitle: string; - /** The validation function to call when the form is submitted */ - customValidate?: (values: FormOnyxValues) => FormInputErrors; + /** The validation function to call when the form is submitted */ + customValidate?: (values: FormOnyxValues) => FormInputErrors; - /** A function to call when the form is submitted */ - onSubmit: (values: FormOnyxValues) => void; + /** A function to call when the form is submitted */ + onSubmit: (values: FormOnyxValues) => void; - /** Fields list of the form */ - stepFields: Array>; + /** Fields list of the form */ + stepFields: Array>; - /** The ID of the date of birth input */ - dobInputID: string; + /** The ID of the date of birth input */ + dobInputID: string; - /** The default value for the date of birth input */ - dobDefaultValue: string; + /** The default value for the date of birth input */ + dobDefaultValue: string; - /** Optional footer component */ - footerComponent?: React.ReactNode; -}; + /** Optional footer component */ + footerComponent?: React.ReactNode; + }; function DateOfBirthStep({ formID, @@ -48,6 +50,7 @@ function DateOfBirthStep({ dobDefaultValue, isEditing, footerComponent, + forwardedFSClass, }: DateOfBirthStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -97,6 +100,7 @@ function DateOfBirthStep({ maxDate={maxDate} shouldSaveDraft={!isEditing} autoFocus + forwardedFSClass={forwardedFSClass} /> {footerComponent} diff --git a/src/components/SubStepForms/FullNameStep.tsx b/src/components/SubStepForms/FullNameStep.tsx index 8f98f284d47d..18fb4caa8a39 100644 --- a/src/components/SubStepForms/FullNameStep.tsx +++ b/src/components/SubStepForms/FullNameStep.tsx @@ -8,48 +8,50 @@ import TextInput from '@components/TextInput'; import useLocalize from '@hooks/useLocalize'; import type {SubStepProps} from '@hooks/useSubStep/types'; import useThemeStyles from '@hooks/useThemeStyles'; +import type {ForwardedFSClassProps} from '@libs/Fullstory/types'; import {doesContainReservedWord, getFieldRequiredErrors, isRequiredFulfilled, isValidLegalName} from '@libs/ValidationUtils'; import HelpLinks from '@pages/ReimbursementAccount/USD/Requestor/PersonalInfo/HelpLinks'; import CONST from '@src/CONST'; import type {OnyxFormValuesMapping} from '@src/ONYXKEYS'; -type FullNameStepProps = SubStepProps & { - /** The ID of the form */ - formID: TFormID; +type FullNameStepProps = SubStepProps & + ForwardedFSClassProps & { + /** The ID of the form */ + formID: TFormID; - /** The title of the form */ - formTitle: string; + /** The title of the form */ + formTitle: string; - /** The validation function to call when the form is submitted */ - customValidate?: (values: FormOnyxValues) => FormInputErrors; + /** The validation function to call when the form is submitted */ + customValidate?: (values: FormOnyxValues) => FormInputErrors; - /** A function to call when the form is submitted */ - onSubmit: (values: FormOnyxValues) => void; + /** A function to call when the form is submitted */ + onSubmit: (values: FormOnyxValues) => void; - /** Fields list of the form */ - stepFields: Array>; + /** Fields list of the form */ + stepFields: Array>; - /** The ID of the first name input */ - firstNameInputID: string; + /** The ID of the first name input */ + firstNameInputID: string; - /** The ID of the last name input */ - lastNameInputID: string; + /** The ID of the last name input */ + lastNameInputID: string; - /** The default values for the form */ - defaultValues: { - firstName: string; - lastName: string; - }; + /** The default values for the form */ + defaultValues: { + firstName: string; + lastName: string; + }; - /** Should show the help link or not */ - shouldShowHelpLinks?: boolean; + /** Should show the help link or not */ + shouldShowHelpLinks?: boolean; - /** Custom label of the first name input */ - customFirstNameLabel?: string; + /** Custom label of the first name input */ + customFirstNameLabel?: string; - /** Custom label of the last name input */ - customLastNameLabel?: string; -}; + /** Custom label of the last name input */ + customLastNameLabel?: string; + }; function FullNameStep({ formID, @@ -64,6 +66,7 @@ function FullNameStep({ shouldShowHelpLinks = true, customFirstNameLabel, customLastNameLabel, + forwardedFSClass, }: FullNameStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -136,6 +139,7 @@ function FullNameStep({ defaultValue={defaultValues.firstName} shouldSaveDraft={!isEditing} containerStyles={[styles.mb6]} + forwardedFSClass={forwardedFSClass} /> ({ defaultValue={defaultValues.lastName} shouldSaveDraft={!isEditing} containerStyles={[styles.mb6]} + forwardedFSClass={forwardedFSClass} /> {shouldShowHelpLinks && } diff --git a/src/components/SubStepForms/SingleFieldStep.tsx b/src/components/SubStepForms/SingleFieldStep.tsx index 7c3a79d067b1..f84d2191354c 100644 --- a/src/components/SubStepForms/SingleFieldStep.tsx +++ b/src/components/SubStepForms/SingleFieldStep.tsx @@ -11,59 +11,61 @@ import useDelayedAutoFocus from '@hooks/useDelayedAutoFocus'; import useLocalize from '@hooks/useLocalize'; import type {SubStepProps} from '@hooks/useSubStep/types'; import useThemeStyles from '@hooks/useThemeStyles'; +import type {ForwardedFSClassProps} from '@libs/Fullstory/types'; import HelpLinks from '@pages/ReimbursementAccount/USD/Requestor/PersonalInfo/HelpLinks'; import CONST from '@src/CONST'; import type {OnyxFormValuesMapping} from '@src/ONYXKEYS'; -type SingleFieldStepProps = SubStepProps & { - /** The ID of the form */ - formID: TFormID; +type SingleFieldStepProps = SubStepProps & + ForwardedFSClassProps & { + /** The ID of the form */ + formID: TFormID; - /** The title of the form */ - formTitle: string; + /** The title of the form */ + formTitle: string; - /** The disclaimer to show below the form title */ - formDisclaimer?: string; + /** The disclaimer to show below the form title */ + formDisclaimer?: string; - /** The validation function to call when the form is submitted */ - validate: (values: FormOnyxValues) => FormInputErrors; + /** The validation function to call when the form is submitted */ + validate: (values: FormOnyxValues) => FormInputErrors; - /** A function to call when the form is submitted */ - onSubmit: (values: FormOnyxValues) => void; + /** A function to call when the form is submitted */ + onSubmit: (values: FormOnyxValues) => void; - /** The ID of the form input */ - inputId: string; + /** The ID of the form input */ + inputId: string; - /** The label of the input */ - inputLabel: string; + /** The label of the input */ + inputLabel: string; - /** The mode of the input */ - inputMode?: InputModeOptions; + /** The mode of the input */ + inputMode?: InputModeOptions; - /** The default values for the form */ - defaultValue: string; + /** The default values for the form */ + defaultValue: string; - /** Whether to show help links */ - shouldShowHelpLinks?: boolean; + /** Whether to show help links */ + shouldShowHelpLinks?: boolean; - /** Max length of the field */ - maxLength?: number; + /** Max length of the field */ + maxLength?: number; - /** Should the submit button be enabled when offline */ - enabledWhenOffline?: boolean; + /** Should the submit button be enabled when offline */ + enabledWhenOffline?: boolean; - /** Set the default value to the input if there is a valid saved value */ - shouldUseDefaultValue?: boolean; + /** Set the default value to the input if there is a valid saved value */ + shouldUseDefaultValue?: boolean; - /** Should the input be disabled */ - disabled?: boolean; + /** Should the input be disabled */ + disabled?: boolean; - /** Placeholder displayed inside input */ - placeholder?: string; + /** Placeholder displayed inside input */ + placeholder?: string; - /** Whether to delay autoFocus to avoid conflicts with navigation animations */ - shouldDelayAutoFocus?: boolean; -}; + /** Whether to delay autoFocus to avoid conflicts with navigation animations */ + shouldDelayAutoFocus?: boolean; + }; function SingleFieldStep({ formID, @@ -83,6 +85,7 @@ function SingleFieldStep({ disabled = false, placeholder, shouldDelayAutoFocus = false, + forwardedFSClass, }: SingleFieldStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -120,6 +123,7 @@ function SingleFieldStep({ placeholder={placeholder} autoFocus={!shouldDelayAutoFocus} ref={internalInputRef} + forwardedFSClass={forwardedFSClass} /> {shouldShowHelpLinks && } diff --git a/src/pages/ReimbursementAccount/AddressFormFields.tsx b/src/pages/ReimbursementAccount/AddressFormFields.tsx index c102d5df8a44..d82d9b65ce85 100644 --- a/src/pages/ReimbursementAccount/AddressFormFields.tsx +++ b/src/pages/ReimbursementAccount/AddressFormFields.tsx @@ -8,13 +8,14 @@ import PushRowWithModal from '@components/PushRowWithModal'; import TextInput from '@components/TextInput'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import type {ForwardedFSClassProps} from '@libs/Fullstory/types'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import type {Address} from '@src/types/onyx/PrivatePersonalDetails'; type AddressErrors = Record; -type AddressFormProps = { +type AddressFormProps = ForwardedFSClassProps & { /** Translate key for Street name */ streetTranslationKey: TranslationPaths; @@ -93,6 +94,7 @@ function AddressFormFields({ onCountryChange, shouldAllowCountryChange = true, shouldValidateZipCodeFormat = true, + forwardedFSClass, }: AddressFormProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -124,6 +126,7 @@ function AddressFormFields({ maxInputLength={CONST.FORM_CHARACTER_LIMIT} limitSearchesToCountry={shouldAllowCountryChange ? undefined : defaultValues?.country} onCountryChange={handleCountryChange} + forwardedFSClass={forwardedFSClass} /> {shouldDisplayStateSelector && ( @@ -152,6 +156,7 @@ function AddressFormFields({ defaultValue={defaultValues?.state} inputID={inputKeys.state ?? 'stateInput'} errorText={errors?.state ? translate('bankAccount.error.addressState') : ''} + forwardedFSClass={forwardedFSClass} /> )} @@ -168,6 +173,7 @@ function AddressFormFields({ errorText={errors?.zipCode ? translate('bankAccount.error.zipCode') : ''} hint={translate('common.zipCodeExampleFormat', {zipSampleFormat: CONST.COUNTRY_ZIP_REGEX_DATA.US.samples})} containerStyles={styles.mt3} + forwardedFSClass={forwardedFSClass} /> {shouldDisplayCountrySelector && ( @@ -184,6 +190,7 @@ function AddressFormFields({ onValueChange={handleCountryChange} stateInputIDToReset={inputKeys.state ?? 'stateInput'} shouldAllowChange={shouldAllowCountryChange} + forwardedFSClass={forwardedFSClass} /> )} diff --git a/src/pages/ReimbursementAccount/USD/Requestor/PersonalInfo/subSteps/Address.tsx b/src/pages/ReimbursementAccount/USD/Requestor/PersonalInfo/subSteps/Address.tsx index 3c96c7244545..1ed63f46e2ab 100644 --- a/src/pages/ReimbursementAccount/USD/Requestor/PersonalInfo/subSteps/Address.tsx +++ b/src/pages/ReimbursementAccount/USD/Requestor/PersonalInfo/subSteps/Address.tsx @@ -5,6 +5,7 @@ import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccountStepFormSubmit'; import type {SubStepProps} from '@hooks/useSubStep/types'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/ReimbursementAccountForm'; import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; @@ -57,6 +58,7 @@ function Address({onNext, onMove, isEditing}: SubStepProps) { inputFieldsIDs={INPUT_KEYS} defaultValues={defaultValues} shouldAllowCountryChange={false} + forwardedFSClass={CONST.FULLSTORY.CLASS.MASK} /> ); } diff --git a/src/pages/ReimbursementAccount/USD/Requestor/PersonalInfo/subSteps/Confirmation.tsx b/src/pages/ReimbursementAccount/USD/Requestor/PersonalInfo/subSteps/Confirmation.tsx index 16ed4384ad3c..676649481200 100644 --- a/src/pages/ReimbursementAccount/USD/Requestor/PersonalInfo/subSteps/Confirmation.tsx +++ b/src/pages/ReimbursementAccount/USD/Requestor/PersonalInfo/subSteps/Confirmation.tsx @@ -68,6 +68,7 @@ function Confirmation({onNext, onMove, isEditing}: SubStepProps) { onfidoLinksTitle={`${translate('personalInfoStep.byAddingThisBankAccount')} `} isLoading={isLoading} error={error} + forwardedFSClass={CONST.FULLSTORY.CLASS.MASK} /> ); } diff --git a/src/pages/ReimbursementAccount/USD/Requestor/PersonalInfo/subSteps/DateOfBirth.tsx b/src/pages/ReimbursementAccount/USD/Requestor/PersonalInfo/subSteps/DateOfBirth.tsx index a43ee49d960c..e4318d51885b 100644 --- a/src/pages/ReimbursementAccount/USD/Requestor/PersonalInfo/subSteps/DateOfBirth.tsx +++ b/src/pages/ReimbursementAccount/USD/Requestor/PersonalInfo/subSteps/DateOfBirth.tsx @@ -7,6 +7,7 @@ import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccoun import type {SubStepProps} from '@hooks/useSubStep/types'; import useThemeStyles from '@hooks/useThemeStyles'; import HelpLinks from '@pages/ReimbursementAccount/USD/Requestor/PersonalInfo/HelpLinks'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/ReimbursementAccountForm'; import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; @@ -46,6 +47,7 @@ function DateOfBirth({onNext, onMove, isEditing}: SubStepProps) { dobInputID={PERSONAL_INFO_DOB_KEY} dobDefaultValue={dobDefaultValue} footerComponent={} + forwardedFSClass={CONST.FULLSTORY.CLASS.MASK} /> ); } diff --git a/src/pages/ReimbursementAccount/USD/Requestor/PersonalInfo/subSteps/FullName.tsx b/src/pages/ReimbursementAccount/USD/Requestor/PersonalInfo/subSteps/FullName.tsx index 4d34f7b393a3..2d11f94f7a60 100644 --- a/src/pages/ReimbursementAccount/USD/Requestor/PersonalInfo/subSteps/FullName.tsx +++ b/src/pages/ReimbursementAccount/USD/Requestor/PersonalInfo/subSteps/FullName.tsx @@ -4,6 +4,7 @@ import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccountStepFormSubmit'; import type {SubStepProps} from '@hooks/useSubStep/types'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/ReimbursementAccountForm'; @@ -38,6 +39,7 @@ function FullName({onNext, onMove, isEditing}: SubStepProps) { firstNameInputID={PERSONAL_INFO_STEP_KEY.FIRST_NAME} lastNameInputID={PERSONAL_INFO_STEP_KEY.LAST_NAME} defaultValues={defaultValues} + forwardedFSClass={CONST.FULLSTORY.CLASS.MASK} /> ); } diff --git a/src/pages/ReimbursementAccount/USD/Requestor/PersonalInfo/subSteps/SocialSecurityNumber.tsx b/src/pages/ReimbursementAccount/USD/Requestor/PersonalInfo/subSteps/SocialSecurityNumber.tsx index 17933aed18b9..6c49a1892d8f 100644 --- a/src/pages/ReimbursementAccount/USD/Requestor/PersonalInfo/subSteps/SocialSecurityNumber.tsx +++ b/src/pages/ReimbursementAccount/USD/Requestor/PersonalInfo/subSteps/SocialSecurityNumber.tsx @@ -62,6 +62,7 @@ function SocialSecurityNumber({onNext, onMove, isEditing}: SubStepProps) { defaultValue={defaultSsnLast4} maxLength={CONST.BANK_ACCOUNT.MAX_LENGTH.SSN} enabledWhenOffline + forwardedFSClass={CONST.FULLSTORY.CLASS.MASK} /> ); } diff --git a/src/pages/ReimbursementAccount/USD/USDVerifiedBankAccountFlow.tsx b/src/pages/ReimbursementAccount/USD/USDVerifiedBankAccountFlow.tsx index f3ca3b9c65d9..b364bfa4a092 100644 --- a/src/pages/ReimbursementAccount/USD/USDVerifiedBankAccountFlow.tsx +++ b/src/pages/ReimbursementAccount/USD/USDVerifiedBankAccountFlow.tsx @@ -88,14 +88,7 @@ function USDVerifiedBankAccountFlow({ } if (CurrentStep) { - return ( - - {CurrentStep} - - ); + return {CurrentStep}; } return null; From 1514137f2e6ce8b1c3a85072d070fab4a28e661a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Fri, 7 Nov 2025 16:55:48 +0000 Subject: [PATCH 14/23] Fix masking on international bank account flow --- src/components/ValuePicker/index.tsx | 3 ++- src/components/ValuePicker/types.ts | 3 ++- .../InternationalDepositAccountContent.tsx | 1 - .../substeps/AccountHolderInformation.tsx | 1 + .../substeps/BankAccountDetails.tsx | 1 + .../InternationalDepositAccount/substeps/BankInformation.tsx | 1 + 6 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/ValuePicker/index.tsx b/src/components/ValuePicker/index.tsx index feaf4d13b712..6db68b3c1c5c 100644 --- a/src/components/ValuePicker/index.tsx +++ b/src/components/ValuePicker/index.tsx @@ -9,7 +9,7 @@ import ValueSelectionList from './ValueSelectionList'; import ValueSelectorModal from './ValueSelectorModal'; function ValuePicker( - {value, label, items, placeholder = '', errorText = '', onInputChange, furtherDetails, shouldShowTooltips = true, shouldShowModal = true}: ValuePickerProps, + {value, label, items, placeholder = '', errorText = '', onInputChange, furtherDetails, shouldShowTooltips = true, shouldShowModal = true, forwardedFSClass}: ValuePickerProps, forwardedRef: ForwardedRef, ) { const [isPickerVisible, setIsPickerVisible] = useState(false); @@ -45,6 +45,7 @@ function ValuePicker( furtherDetails={furtherDetails} brickRoadIndicator={errorText ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} errorText={errorText} + forwardedFSClass={forwardedFSClass} /> ; -type ValuePickerProps = { +type ValuePickerProps = ForwardedFSClassProps & { /** Item to display */ value?: string; diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/InternationalDepositAccountContent.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/InternationalDepositAccountContent.tsx index 403d6980abb3..57ab6659a3bc 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/InternationalDepositAccountContent.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/InternationalDepositAccountContent.tsx @@ -141,7 +141,6 @@ function InternationalDepositAccountContent({privatePersonalDetails, corpayField ))} diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx index 5a2aa4a9230b..5e4e5ef4dc1d 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx @@ -92,6 +92,7 @@ function BankAccountDetails({isEditing, onNext, resetScreenIndex, formValues, fi label={field.label + (field.isRequired ? '' : ` (${translate('common.optional')})`)} items={(field.valueSet ?? []).map(({id, text}) => ({value: id, label: text}))} shouldSaveDraft={!isEditing} + forwardedFSClass={CONST.FULLSTORY.CLASS.MASK} /> ))} diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx index 097d9cb199ca..097cf95a2269 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx @@ -106,6 +106,7 @@ function BankInformation({isEditing, onNext, formValues, fieldsMap}: CustomSubSt lat: '', lng: '', }} + forwardedFSClass={CONST.FULLSTORY.CLASS.MASK} /> ))} From 46b4a220b5e651e4a0437872b1840cba0320bcf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Fri, 7 Nov 2025 17:36:34 +0000 Subject: [PATCH 15/23] Fix masking of enable reimbursement flow --- src/components/SubStepForms/RegistrationNumberStep.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/SubStepForms/RegistrationNumberStep.tsx b/src/components/SubStepForms/RegistrationNumberStep.tsx index 97c339a82ceb..c5955b69682f 100644 --- a/src/components/SubStepForms/RegistrationNumberStep.tsx +++ b/src/components/SubStepForms/RegistrationNumberStep.tsx @@ -92,6 +92,7 @@ function RegistrationNumberStep({ shouldSaveDraft={!isEditing} autoFocus={!shouldDelayAutoFocus} ref={internalInputRef} + forwardedFSClass={CONST.FULLSTORY.CLASS.MASK} /> Date: Fri, 7 Nov 2025 18:05:43 +0000 Subject: [PATCH 16/23] Fix masking on Wallet flow --- src/components/RadioButtonWithLabel.tsx | 25 ++++++++++++++++--- src/components/RadioButtons.tsx | 9 +++++-- src/components/SingleChoiceQuestion.tsx | 6 +++-- .../EnablePayments/AdditionalDetailsStep.tsx | 6 +++++ src/pages/EnablePayments/EnablePayments.tsx | 9 +------ .../EnablePayments/EnablePaymentsPage.tsx | 1 - src/pages/EnablePayments/IdologyQuestions.tsx | 1 + .../PersonalInfo/substeps/AddressStep.tsx | 2 ++ .../substeps/ConfirmationStep.tsx | 1 + .../PersonalInfo/substeps/DateOfBirthStep.tsx | 2 ++ .../PersonalInfo/substeps/LegalNameStep.tsx | 2 ++ .../PersonalInfo/substeps/PhoneNumberStep.tsx | 1 + .../substeps/SocialSecurityNumberStep.tsx | 1 + 13 files changed, 50 insertions(+), 16 deletions(-) diff --git a/src/components/RadioButtonWithLabel.tsx b/src/components/RadioButtonWithLabel.tsx index 824660d631af..a077782041d8 100644 --- a/src/components/RadioButtonWithLabel.tsx +++ b/src/components/RadioButtonWithLabel.tsx @@ -3,12 +3,13 @@ import React from 'react'; import type {StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; +import type {ForwardedFSClassProps} from '@libs/Fullstory/types'; import FormHelpMessage from './FormHelpMessage'; import * as Pressables from './Pressable'; import RadioButton from './RadioButton'; import Text from './Text'; -type RadioButtonWithLabelProps = { +type RadioButtonWithLabelProps = ForwardedFSClassProps & { /** Whether the radioButton is checked */ isChecked: boolean; @@ -42,7 +43,18 @@ type RadioButtonWithLabelProps = { const PressableWithFeedback = Pressables.PressableWithFeedback; -function RadioButtonWithLabel({labelElement, style, label = '', hasError = false, errorText = '', isChecked, onPress, wrapperStyle, shouldBlendOpacity}: RadioButtonWithLabelProps) { +function RadioButtonWithLabel({ + labelElement, + style, + label = '', + hasError = false, + errorText = '', + isChecked, + onPress, + wrapperStyle, + shouldBlendOpacity, + forwardedFSClass, +}: RadioButtonWithLabelProps) { const styles = useThemeStyles(); const defaultStyles = [styles.flexRow, styles.alignItemsCenter]; @@ -69,7 +81,14 @@ function RadioButtonWithLabel({labelElement, style, label = '', hasError = false pressDimmingValue={0.5} shouldBlendOpacity={shouldBlendOpacity} > - {!!label && {label}} + {!!label && ( + + {label} + + )} {!!labelElement && labelElement} diff --git a/src/components/RadioButtons.tsx b/src/components/RadioButtons.tsx index 07e8fe38f772..5b67a1bae0fd 100644 --- a/src/components/RadioButtons.tsx +++ b/src/components/RadioButtons.tsx @@ -3,6 +3,7 @@ import type {ForwardedRef} from 'react'; import {View} from 'react-native'; import type {StyleProp, ViewStyle} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; +import type {ForwardedFSClassProps} from '@libs/Fullstory/types'; import FormHelpMessage from './FormHelpMessage'; import RadioButtonWithLabel from './RadioButtonWithLabel'; @@ -12,7 +13,7 @@ type Choice = { style?: StyleProp; }; -type RadioButtonsProps = { +type RadioButtonsProps = ForwardedFSClassProps & { /** List of choices to display via radio buttons */ items: Choice[]; @@ -35,7 +36,10 @@ type RadioButtonsProps = { value?: string; }; -function RadioButtons({items, onPress, defaultCheckedValue = '', radioButtonStyle, errorText, onInputChange = () => {}, value}: RadioButtonsProps, ref: ForwardedRef) { +function RadioButtons( + {items, onPress, defaultCheckedValue = '', radioButtonStyle, errorText, onInputChange = () => {}, value, forwardedFSClass}: RadioButtonsProps, + ref: ForwardedRef, +) { const styles = useThemeStyles(); const [checkedValue, setCheckedValue] = useState(defaultCheckedValue); @@ -63,6 +67,7 @@ function RadioButtons({items, onPress, defaultCheckedValue = '', radioButtonStyl return onPress(item.value); }} label={item.label} + forwardedFSClass={forwardedFSClass} /> ))} diff --git a/src/components/SingleChoiceQuestion.tsx b/src/components/SingleChoiceQuestion.tsx index e52007850475..3047260afec9 100644 --- a/src/components/SingleChoiceQuestion.tsx +++ b/src/components/SingleChoiceQuestion.tsx @@ -3,11 +3,12 @@ import React, {forwardRef} from 'react'; // eslint-disable-next-line no-restricted-imports import type {Text as RNText} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; +import type {ForwardedFSClassProps} from '@libs/Fullstory/types'; import type {Choice} from './RadioButtons'; import RadioButtons from './RadioButtons'; import Text from './Text'; -type SingleChoiceQuestionProps = { +type SingleChoiceQuestionProps = ForwardedFSClassProps & { prompt: string; errorText?: string; possibleAnswers: Choice[]; @@ -15,7 +16,7 @@ type SingleChoiceQuestionProps = { onInputChange: (value: string) => void; }; -function SingleChoiceQuestion({prompt, errorText, possibleAnswers, currentQuestionIndex, onInputChange}: SingleChoiceQuestionProps, ref: ForwardedRef) { +function SingleChoiceQuestion({prompt, errorText, possibleAnswers, currentQuestionIndex, onInputChange, forwardedFSClass}: SingleChoiceQuestionProps, ref: ForwardedRef) { const styles = useThemeStyles(); return ( @@ -31,6 +32,7 @@ function SingleChoiceQuestion({prompt, errorText, possibleAnswers, currentQuesti key={currentQuestionIndex} onPress={onInputChange} errorText={errorText} + forwardedFSClass={forwardedFSClass} /> ); diff --git a/src/pages/EnablePayments/AdditionalDetailsStep.tsx b/src/pages/EnablePayments/AdditionalDetailsStep.tsx index 3792e570711e..af9e70111c68 100644 --- a/src/pages/EnablePayments/AdditionalDetailsStep.tsx +++ b/src/pages/EnablePayments/AdditionalDetailsStep.tsx @@ -177,6 +177,7 @@ function AdditionalDetailsStep({currentUserPersonalDetails}: AdditionalDetailsSt role={CONST.ROLE.PRESENTATION} defaultValue={extractFirstAndLastNameFromAvailableDetails(currentUserPersonalDetails).firstName} shouldSaveDraft + forwardedFSClass={CONST.FULLSTORY.CLASS.MASK} /> diff --git a/src/pages/EnablePayments/EnablePayments.tsx b/src/pages/EnablePayments/EnablePayments.tsx index 98b33d46765f..c530e46511ba 100644 --- a/src/pages/EnablePayments/EnablePayments.tsx +++ b/src/pages/EnablePayments/EnablePayments.tsx @@ -84,14 +84,7 @@ function EnablePaymentsPage() { } if (CurrentStep) { - return ( - - {CurrentStep} - - ); + return {CurrentStep}; } return null; diff --git a/src/pages/EnablePayments/EnablePaymentsPage.tsx b/src/pages/EnablePayments/EnablePaymentsPage.tsx index 249524e58e4e..adc7fdcdab84 100644 --- a/src/pages/EnablePayments/EnablePaymentsPage.tsx +++ b/src/pages/EnablePayments/EnablePaymentsPage.tsx @@ -53,7 +53,6 @@ function EnablePaymentsPage() { shouldShowOfflineIndicator={userWallet?.currentStep !== CONST.WALLET.STEP.ONFIDO} includeSafeAreaPaddingBottom testID={EnablePaymentsPage.displayName} - forwardedFSClass={CONST.FULLSTORY.CLASS.MASK} > {() => { if (userWallet?.errorCode === CONST.WALLET.ERROR.KYC) { diff --git a/src/pages/EnablePayments/IdologyQuestions.tsx b/src/pages/EnablePayments/IdologyQuestions.tsx index 330fa3ec6aab..95e6d74010fd 100644 --- a/src/pages/EnablePayments/IdologyQuestions.tsx +++ b/src/pages/EnablePayments/IdologyQuestions.tsx @@ -131,6 +131,7 @@ function IdologyQuestions({questions, idNumber}: IdologyQuestionsProps) { chooseAnswer(SafeString(value)); }} onInputChange={() => {}} + forwardedFSClass={CONST.FULLSTORY.CLASS.MASK} /> ); } diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/ConfirmationStep.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/ConfirmationStep.tsx index c3757f7015df..0e7f67f794eb 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/ConfirmationStep.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/ConfirmationStep.tsx @@ -77,6 +77,7 @@ function ConfirmationStep({onNext, onMove, isEditing}: SubStepProps) { onfidoLinksTitle={`${translate('personalInfoStep.byAddingThisBankAccount')} `} isLoading={isLoading} error={error} + forwardedFSClass={CONST.FULLSTORY.CLASS.MASK} /> ); } diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirthStep.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirthStep.tsx index aea46fefcd3d..247829562540 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirthStep.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirthStep.tsx @@ -5,6 +5,7 @@ import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import type {SubStepProps} from '@hooks/useSubStep/types'; import useWalletAdditionalDetailsStepFormSubmit from '@hooks/useWalletAdditionalDetailsStepFormSubmit'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {OnyxFormValuesMapping} from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/WalletAdditionalDetailsForm'; @@ -35,6 +36,7 @@ function DateOfBirthStep({onNext, onMove, isEditing}: SubStepProps) { stepFields={STEP_FIELDS} dobInputID={PERSONAL_INFO_DOB_KEY as keyof FormOnyxValues} dobDefaultValue={dobDefaultValue} + forwardedFSClass={CONST.FULLSTORY.CLASS.MASK} /> ); } diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/LegalNameStep.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/LegalNameStep.tsx index 3b3ee4815d66..aa653d35af0f 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/LegalNameStep.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/LegalNameStep.tsx @@ -4,6 +4,7 @@ import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import type {SubStepProps} from '@hooks/useSubStep/types'; import useWalletAdditionalDetailsStepFormSubmit from '@hooks/useWalletAdditionalDetailsStepFormSubmit'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/WalletAdditionalDetailsForm'; @@ -37,6 +38,7 @@ function LegalNameStep({onNext, onMove, isEditing}: SubStepProps) { firstNameInputID={PERSONAL_INFO_STEP_KEY.FIRST_NAME} lastNameInputID={PERSONAL_INFO_STEP_KEY.LAST_NAME} defaultValues={defaultValues} + forwardedFSClass={CONST.FULLSTORY.CLASS.MASK} /> ); } diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/PhoneNumberStep.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/PhoneNumberStep.tsx index f72c97bf2f58..fd17c74e426f 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/PhoneNumberStep.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/PhoneNumberStep.tsx @@ -62,6 +62,7 @@ function PhoneNumberStep({onNext, onMove, isEditing}: SubStepProps) { inputMode={CONST.INPUT_MODE.TEL} defaultValue={defaultPhoneNumber} enabledWhenOffline + forwardedFSClass={CONST.FULLSTORY.CLASS.MASK} /> ); } diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumberStep.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumberStep.tsx index b30ed80fab01..cef42f494d6e 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumberStep.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumberStep.tsx @@ -58,6 +58,7 @@ function SocialSecurityNumberStep({onNext, onMove, isEditing}: SubStepProps) { inputMode={CONST.INPUT_MODE.NUMERIC} defaultValue={defaultSsnLast4} maxLength={shouldAskForFullSSN ? CONST.BANK_ACCOUNT.MAX_LENGTH.FULL_SSN : CONST.BANK_ACCOUNT.MAX_LENGTH.SSN} + forwardedFSClass={CONST.FULLSTORY.CLASS.MASK} /> ); } From f7a33da7b7e8cb0254ff14d448a2ef30558f3912 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Fri, 7 Nov 2025 18:09:27 +0000 Subject: [PATCH 17/23] Fix masking on Wallet card details page --- src/pages/settings/Wallet/WalletPage/CardDetails.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pages/settings/Wallet/WalletPage/CardDetails.tsx b/src/pages/settings/Wallet/WalletPage/CardDetails.tsx index 2c882ed2d19f..b4fb68179b53 100644 --- a/src/pages/settings/Wallet/WalletPage/CardDetails.tsx +++ b/src/pages/settings/Wallet/WalletPage/CardDetails.tsx @@ -42,7 +42,7 @@ function CardDetails({pan = '', expiration = '', cvv = '', onUpdateAddressPress} const [privatePersonalDetails] = useOnyx(ONYXKEYS.PRIVATE_PERSONAL_DETAILS, {canBeMissing: true}); return ( - + {pan?.length > 0 && ( )} {expiration?.length > 0 && ( @@ -58,6 +59,7 @@ function CardDetails({pan = '', expiration = '', cvv = '', onUpdateAddressPress} title={expiration} interactive={false} copyable + forwardedFSClass={CONST.FULLSTORY.CLASS.MASK} /> )} {cvv?.length > 0 && ( @@ -66,6 +68,7 @@ function CardDetails({pan = '', expiration = '', cvv = '', onUpdateAddressPress} title={cvv} interactive={false} copyable + forwardedFSClass={CONST.FULLSTORY.CLASS.MASK} /> )} {pan?.length > 0 && ( @@ -76,6 +79,7 @@ function CardDetails({pan = '', expiration = '', cvv = '', onUpdateAddressPress} title={getFormattedAddress(privatePersonalDetails || defaultPrivatePersonalDetails)} interactive={false} copyable + forwardedFSClass={CONST.FULLSTORY.CLASS.MASK} /> Date: Fri, 7 Nov 2025 19:20:32 +0000 Subject: [PATCH 18/23] Fix masking on Non-USD bank flow --- .../ConnectedVerifiedBankAccount.tsx | 2 -- .../NonUSD/BankInfo/subSteps/AccountHolderDetails.tsx | 1 + .../NonUSD/BankInfo/subSteps/BankAccountDetails.tsx | 1 + .../NonUSD/BankInfo/subSteps/Confirmation.tsx | 1 + .../NonUSD/NonUSDVerifiedBankAccountFlow.tsx | 9 +-------- 5 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/pages/ReimbursementAccount/ConnectedVerifiedBankAccount.tsx b/src/pages/ReimbursementAccount/ConnectedVerifiedBankAccount.tsx index 2283603d8f46..d011ad04e5f0 100644 --- a/src/pages/ReimbursementAccount/ConnectedVerifiedBankAccount.tsx +++ b/src/pages/ReimbursementAccount/ConnectedVerifiedBankAccount.tsx @@ -15,7 +15,6 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import WorkspaceResetBankAccountModal from '@pages/workspace/WorkspaceResetBankAccountModal'; import {requestResetBankAccount, resetReimbursementAccount} from '@userActions/ReimbursementAccount'; -import CONST from '@src/CONST'; import type {ReimbursementAccount} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -68,7 +67,6 @@ function ConnectedVerifiedBankAccount({ shouldEnablePickerAvoiding={false} shouldEnableMaxHeight style={[styles.flex1, styles.justifyContentBetween, styles.mh2]} - forwardedFSClass={CONST.FULLSTORY.CLASS.MASK} > ); diff --git a/src/pages/ReimbursementAccount/NonUSD/BankInfo/subSteps/BankAccountDetails.tsx b/src/pages/ReimbursementAccount/NonUSD/BankInfo/subSteps/BankAccountDetails.tsx index d7888b51cb2d..fb4f4fd8cf0a 100644 --- a/src/pages/ReimbursementAccount/NonUSD/BankInfo/subSteps/BankAccountDetails.tsx +++ b/src/pages/ReimbursementAccount/NonUSD/BankInfo/subSteps/BankAccountDetails.tsx @@ -112,6 +112,7 @@ function BankAccountDetails({onNext, isEditing, corpayFields}: BankInfoSubStepPr city: 'bankCity', country: '', }} + forwardedFSClass={CONST.FULLSTORY.CLASS.MASK} /> ); diff --git a/src/pages/ReimbursementAccount/NonUSD/BankInfo/subSteps/Confirmation.tsx b/src/pages/ReimbursementAccount/NonUSD/BankInfo/subSteps/Confirmation.tsx index 9eba1ffc0ad9..c9369c902a82 100644 --- a/src/pages/ReimbursementAccount/NonUSD/BankInfo/subSteps/Confirmation.tsx +++ b/src/pages/ReimbursementAccount/NonUSD/BankInfo/subSteps/Confirmation.tsx @@ -47,6 +47,7 @@ function Confirmation({onNext, onMove, corpayFields}: BankInfoSubStepProps) { } }} key={field.id} + forwardedFSClass={CONST.FULLSTORY.CLASS.MASK} /> ); }), diff --git a/src/pages/ReimbursementAccount/NonUSD/NonUSDVerifiedBankAccountFlow.tsx b/src/pages/ReimbursementAccount/NonUSD/NonUSDVerifiedBankAccountFlow.tsx index 3782514a1b07..a6cc82695cb6 100644 --- a/src/pages/ReimbursementAccount/NonUSD/NonUSDVerifiedBankAccountFlow.tsx +++ b/src/pages/ReimbursementAccount/NonUSD/NonUSDVerifiedBankAccountFlow.tsx @@ -178,14 +178,7 @@ function NonUSDVerifiedBankAccountFlow({ } if (CurrentStep) { - return ( - - {CurrentStep} - - ); + return {CurrentStep}; } return null; From 92e735230a79d2e882d7fdb9a7098fe09910f6fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Wed, 12 Nov 2025 16:00:57 +0000 Subject: [PATCH 19/23] Fix lint --- src/libs/Fullstory/common.ts | 12 ++++++------ .../PersonalInfo/substeps/ConfirmationStep.tsx | 4 ++-- .../substeps/SocialSecurityNumberStep.tsx | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/libs/Fullstory/common.ts b/src/libs/Fullstory/common.ts index d5954de3409b..957f05ff2c2d 100644 --- a/src/libs/Fullstory/common.ts +++ b/src/libs/Fullstory/common.ts @@ -17,14 +17,14 @@ Onyx.connectWithoutView({ }, }); -const allowedReportChatTypes: Array> = [ +const allowedReportChatTypes = new Set>([ CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, CONST.REPORT.CHAT_TYPE.POLICY_ADMINS, CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE, CONST.REPORT.CHAT_TYPE.INVOICE, -]; +]); -const allowedReportTypes: Array> = [CONST.REPORT.TYPE.IOU, CONST.REPORT.TYPE.EXPENSE, CONST.REPORT.TYPE.INVOICE]; +const allowedReportTypes = new Set>([CONST.REPORT.TYPE.IOU, CONST.REPORT.TYPE.EXPENSE, CONST.REPORT.TYPE.INVOICE]); const getChatFSClass: GetChatFSClass = (report) => { if (!report) { @@ -37,18 +37,18 @@ const getChatFSClass: GetChatFSClass = (report) => { } // Workspace expense chat, #admins, #announce rooms and invoices should be unmasked. - if (report.chatType && allowedReportChatTypes.includes(report.chatType)) { + if (report.chatType && allowedReportChatTypes.has(report.chatType)) { return CONST.FULLSTORY.CLASS.UNMASK; } // IOUs, expenses and invoices should be unmasked. - if (report.type && (allowedReportTypes as string[]).includes(report.type)) { + if (report.type && allowedReportTypes.has(report.type as ValueOf)) { return CONST.FULLSTORY.CLASS.UNMASK; } // If the report doesn't meet the condition above we check if the parent report is an IOU, expense or invoice. const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`]; - if (parentReport?.type && (allowedReportTypes as string[]).includes(parentReport.type)) { + if (parentReport?.type && allowedReportTypes.has(parentReport.type as ValueOf)) { return CONST.FULLSTORY.CLASS.UNMASK; } diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/ConfirmationStep.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/ConfirmationStep.tsx index 0e7f67f794eb..e97ff13093e5 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/ConfirmationStep.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/ConfirmationStep.tsx @@ -3,7 +3,7 @@ import CommonConfirmationStep from '@components/SubStepForms/ConfirmationStep'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import type {SubStepProps} from '@hooks/useSubStep/types'; -import * as ErrorUtils from '@libs/ErrorUtils'; +import {getLatestErrorMessage} from '@libs/ErrorUtils'; import getSubstepValues from '@pages/EnablePayments/utils/getSubstepValues'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -19,7 +19,7 @@ function ConfirmationStep({onNext, onMove, isEditing}: SubStepProps) { const [walletAdditionalDetailsDraft] = useOnyx(ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS_DRAFT); const isLoading = walletAdditionalDetails?.isLoading ?? false; - const error = ErrorUtils.getLatestErrorMessage(walletAdditionalDetails ?? {}); + const error = getLatestErrorMessage(walletAdditionalDetails ?? {}); const values = useMemo(() => getSubstepValues(PERSONAL_INFO_STEP_KEYS, walletAdditionalDetailsDraft, walletAdditionalDetails), [walletAdditionalDetails, walletAdditionalDetailsDraft]); const shouldAskForFullSSN = walletAdditionalDetails?.errorCode === CONST.WALLET.ERROR.SSN; diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumberStep.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumberStep.tsx index cef42f494d6e..0b0eed36cb3d 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumberStep.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumberStep.tsx @@ -5,7 +5,7 @@ import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import type {SubStepProps} from '@hooks/useSubStep/types'; import useWalletAdditionalDetailsStepFormSubmit from '@hooks/useWalletAdditionalDetailsStepFormSubmit'; -import * as ValidationUtils from '@libs/ValidationUtils'; +import {getFieldRequiredErrors, isValidSSNFullNine, isValidSSNLastFour} from '@libs/ValidationUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/WalletAdditionalDetailsForm'; @@ -22,13 +22,13 @@ function SocialSecurityNumberStep({onNext, onMove, isEditing}: SubStepProps) { const validate = useCallback( (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + const errors = getFieldRequiredErrors(values, STEP_FIELDS); if (shouldAskForFullSSN) { - if (values.ssn && !ValidationUtils.isValidSSNFullNine(values.ssn)) { + if (values.ssn && !isValidSSNFullNine(values.ssn)) { errors.ssn = translate('additionalDetailsStep.ssnFull9Error'); } - } else if (values.ssn && !ValidationUtils.isValidSSNLastFour(values.ssn)) { + } else if (values.ssn && !isValidSSNLastFour(values.ssn)) { errors.ssn = translate('bankAccount.error.ssnLast4'); } From bd4dd19625b450a051f7e2b57a597ff26f3a9660 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Wed, 12 Nov 2025 16:19:54 +0000 Subject: [PATCH 20/23] Add comment about Onyx.connectWithoutView --- package.json | 2 +- src/libs/Fullstory/common.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index ed30dc86379e..f8d0756d7687 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "test:debug": "TZ=utc NODE_OPTIONS='--inspect-brk --experimental-vm-modules' jest --runInBand", "perf-test": "NODE_OPTIONS=--experimental-vm-modules npx reassure", "typecheck": "NODE_OPTIONS=--max_old_space_size=8192 tsc", - "lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --max-warnings=128 --cache --cache-location=node_modules/.cache/eslint", + "lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --max-warnings=129 --cache --cache-location=node_modules/.cache/eslint", "lint-changed": "NODE_OPTIONS=--max_old_space_size=8192 ./scripts/lintChanged.sh", "lint-watch": "npx eslint-watch --watch --changed", "shellcheck": "./scripts/shellCheck.sh", diff --git a/src/libs/Fullstory/common.ts b/src/libs/Fullstory/common.ts index 957f05ff2c2d..461d49897ab2 100644 --- a/src/libs/Fullstory/common.ts +++ b/src/libs/Fullstory/common.ts @@ -8,6 +8,9 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type {Report} from '@src/types/onyx'; import type {GetChatFSClass, ShouldInitialize} from './types'; +// This data is only used for Fullstory to determine if a chat-related element should be +// masked or not, so it's acceptable to use `Onyx.connectWithoutView` and avoid many UI elements +// having to subscribe to this whole collection. let allReports: OnyxCollection; Onyx.connectWithoutView({ key: ONYXKEYS.COLLECTION.REPORT, From 15040a5876c9dc8d82ebfaad93db5d1d25197166 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Wed, 12 Nov 2025 16:21:59 +0000 Subject: [PATCH 21/23] TEMPORARY: Always initialize FS --- src/libs/Fullstory/index.native.ts | 6 ++++-- src/libs/Fullstory/index.ts | 8 +++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/libs/Fullstory/index.native.ts b/src/libs/Fullstory/index.native.ts index 04f99dedf1e3..b84eaa476e44 100644 --- a/src/libs/Fullstory/index.native.ts +++ b/src/libs/Fullstory/index.native.ts @@ -1,6 +1,7 @@ import FullStory, {FSPage} from '@fullstory/react-native'; import getEnvironment from '@src/libs/Environment/getEnvironment'; -import {getChatFSClass, shouldInitializeFullstory} from './common'; +// import {getChatFSClass, shouldInitializeFullstory} from './common'; +import {getChatFSClass} from './common'; import type {Fullstory} from './types'; const FS: Fullstory = { @@ -12,7 +13,8 @@ const FS: Fullstory = { onReady: () => Promise.resolve(), - shouldInitialize: shouldInitializeFullstory, + // shouldInitialize: shouldInitializeFullstory, + shouldInitialize: () => true, consent: (shouldConsent) => FullStory.consent(shouldConsent), diff --git a/src/libs/Fullstory/index.ts b/src/libs/Fullstory/index.ts index 43273a942dbc..5df55eb82ee4 100644 --- a/src/libs/Fullstory/index.ts +++ b/src/libs/Fullstory/index.ts @@ -1,8 +1,9 @@ import {FullStory, init, isInitialized} from '@fullstory/browser'; -import * as Session from '@userActions/Session'; +// import * as Session from '@userActions/Session'; import CONST from '@src/CONST'; import getEnvironment from '@src/libs/Environment/getEnvironment'; -import {getChatFSClass, shouldInitializeFullstory} from './common'; +// import {getChatFSClass, shouldInitializeFullstory} from './common'; +import {getChatFSClass} from './common'; import type {FSPageLike, Fullstory} from './types'; // Placeholder Browser API does not support Manual Page definition @@ -32,7 +33,8 @@ const FS: Fullstory = { } }), - shouldInitialize: (userMetadata, envName) => shouldInitializeFullstory(userMetadata, envName) && !Session.isSupportAuthToken(), + // shouldInitialize: (userMetadata, envName) => shouldInitializeFullstory(userMetadata, envName) && !Session.isSupportAuthToken(), + shouldInitialize: () => true, consent: (shouldConsent) => FullStory(CONST.FULLSTORY.OPERATION.SET_IDENTITY, {consent: shouldConsent}), From 163a078b8aea8a31e8fd40626642be68887fcc29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Mon, 24 Nov 2025 16:35:41 +0000 Subject: [PATCH 22/23] Revert "TEMPORARY: Always initialize FS" This reverts commit 15040a5876c9dc8d82ebfaad93db5d1d25197166. --- src/libs/Fullstory/index.native.ts | 6 ++---- src/libs/Fullstory/index.ts | 8 +++----- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/libs/Fullstory/index.native.ts b/src/libs/Fullstory/index.native.ts index b84eaa476e44..04f99dedf1e3 100644 --- a/src/libs/Fullstory/index.native.ts +++ b/src/libs/Fullstory/index.native.ts @@ -1,7 +1,6 @@ import FullStory, {FSPage} from '@fullstory/react-native'; import getEnvironment from '@src/libs/Environment/getEnvironment'; -// import {getChatFSClass, shouldInitializeFullstory} from './common'; -import {getChatFSClass} from './common'; +import {getChatFSClass, shouldInitializeFullstory} from './common'; import type {Fullstory} from './types'; const FS: Fullstory = { @@ -13,8 +12,7 @@ const FS: Fullstory = { onReady: () => Promise.resolve(), - // shouldInitialize: shouldInitializeFullstory, - shouldInitialize: () => true, + shouldInitialize: shouldInitializeFullstory, consent: (shouldConsent) => FullStory.consent(shouldConsent), diff --git a/src/libs/Fullstory/index.ts b/src/libs/Fullstory/index.ts index 5df55eb82ee4..43273a942dbc 100644 --- a/src/libs/Fullstory/index.ts +++ b/src/libs/Fullstory/index.ts @@ -1,9 +1,8 @@ import {FullStory, init, isInitialized} from '@fullstory/browser'; -// import * as Session from '@userActions/Session'; +import * as Session from '@userActions/Session'; import CONST from '@src/CONST'; import getEnvironment from '@src/libs/Environment/getEnvironment'; -// import {getChatFSClass, shouldInitializeFullstory} from './common'; -import {getChatFSClass} from './common'; +import {getChatFSClass, shouldInitializeFullstory} from './common'; import type {FSPageLike, Fullstory} from './types'; // Placeholder Browser API does not support Manual Page definition @@ -33,8 +32,7 @@ const FS: Fullstory = { } }), - // shouldInitialize: (userMetadata, envName) => shouldInitializeFullstory(userMetadata, envName) && !Session.isSupportAuthToken(), - shouldInitialize: () => true, + shouldInitialize: (userMetadata, envName) => shouldInitializeFullstory(userMetadata, envName) && !Session.isSupportAuthToken(), consent: (shouldConsent) => FullStory(CONST.FULLSTORY.OPERATION.SET_IDENTITY, {consent: shouldConsent}), From 981bc6cf4bbdb7a1d20f1f80f6259b75488cf85a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Wed, 26 Nov 2025 19:48:37 +0000 Subject: [PATCH 23/23] Mask chat results on Search autocomplete list --- src/components/Search/SearchAutocompleteList.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/Search/SearchAutocompleteList.tsx b/src/components/Search/SearchAutocompleteList.tsx index 6eae736f98bb..52253dd636ad 100644 --- a/src/components/Search/SearchAutocompleteList.tsx +++ b/src/components/Search/SearchAutocompleteList.tsx @@ -18,8 +18,9 @@ import useThemeStyles from '@hooks/useThemeStyles'; import {getCardFeedsForDisplay} from '@libs/CardFeedUtils'; import {getCardDescription, isCard, isCardHiddenFromSearch} from '@libs/CardUtils'; import {getDecodedCategoryName} from '@libs/CategoryUtils'; +import FS from '@libs/Fullstory'; import Log from '@libs/Log'; -import type {Options} from '@libs/OptionsListUtils'; +import type {Options, SearchOption} from '@libs/OptionsListUtils'; import {combineOrderingOfReportsAndPersonalDetails, getSearchOptions} from '@libs/OptionsListUtils'; import Performance from '@libs/Performance'; import {getAllTaxRates, getCleanedTagName, shouldShowPolicy} from '@libs/PolicyUtils'; @@ -150,10 +151,13 @@ function SearchRouterItem(props: UserListItemProps | SearchQueryList /> ); } + + const fsClass = FS.getChatFSClass((props.item as SearchOption | undefined)?.item); + return (