From d8c267c53dd089ddc34322c11190d26e00352a90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Thu, 8 Jan 2026 15:18:41 +0100 Subject: [PATCH 1/8] add `shouldAcceptName` to `GetOptionsConfig` type --- src/libs/OptionsListUtils/types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/OptionsListUtils/types.ts b/src/libs/OptionsListUtils/types.ts index e56b75130757..fb2b73e3f30e 100644 --- a/src/libs/OptionsListUtils/types.ts +++ b/src/libs/OptionsListUtils/types.ts @@ -193,6 +193,7 @@ type GetOptionsConfig = { maxElements?: number; maxRecentReportElements?: number; includeUserToInvite?: boolean; + shouldAcceptName?: boolean; } & GetValidReportsConfig; type GetUserToInviteConfig = { From c35b43d7ff42f896115b2aec6791bd54ccb68c44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Thu, 8 Jan 2026 15:18:51 +0100 Subject: [PATCH 2/8] fix `getUserToInviteOption` to set login for names --- src/libs/OptionsListUtils/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libs/OptionsListUtils/index.ts b/src/libs/OptionsListUtils/index.ts index 9686f4de3917..675a6b722c15 100644 --- a/src/libs/OptionsListUtils/index.ts +++ b/src/libs/OptionsListUtils/index.ts @@ -1703,7 +1703,7 @@ function getUserToInviteOption({ showChatPreviewLine, }); userToInvite.isOptimisticAccount = true; - userToInvite.login = isValidEmail || isValidPhoneNumber ? searchValue : ''; + userToInvite.login = searchValue; // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing userToInvite.text = userToInvite.text || searchValue; // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing @@ -2125,6 +2125,7 @@ function getValidOptions( maxElements, includeUserToInvite = false, maxRecentReportElements = undefined, + shouldAcceptName = false, ...config }: GetOptionsConfig = {}, countryCode: number = CONST.DEFAULT_COUNTRY_CODE, @@ -2311,6 +2312,7 @@ function getValidOptions( countryCode, { excludeLogins: loginsToExclude, + shouldAcceptName, }, ); } From f79073234f56df08d7d85b049f9e92bc7acc95f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Thu, 8 Jan 2026 15:18:59 +0100 Subject: [PATCH 3/8] add `shouldAcceptName: true` for attendees context --- src/hooks/useSearchSelector.base.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hooks/useSearchSelector.base.ts b/src/hooks/useSearchSelector.base.ts index 7196fa0a9ea5..cd87176342b2 100644 --- a/src/hooks/useSearchSelector.base.ts +++ b/src/hooks/useSearchSelector.base.ts @@ -271,6 +271,7 @@ function useSearchSelectorBase({ searchString: computedSearchTerm, includeUserToInvite, includeCurrentUser, + shouldAcceptName: true, }); default: return getEmptyOptions(); From dc04048aef1a74123cdd55ecb63e84d936f89961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Thu, 8 Jan 2026 15:19:09 +0100 Subject: [PATCH 4/8] add tests for `getUserToInviteOption` with names --- tests/unit/OptionsListUtilsTest.tsx | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/unit/OptionsListUtilsTest.tsx b/tests/unit/OptionsListUtilsTest.tsx index 283a8a23b589..418fda95bcb1 100644 --- a/tests/unit/OptionsListUtilsTest.tsx +++ b/tests/unit/OptionsListUtilsTest.tsx @@ -27,6 +27,7 @@ import { getPersonalDetailSearchTerms, getSearchOptions, getSearchValueForPhoneOrEmail, + getUserToInviteOption, getValidOptions, optionsOrderBy, orderOptions, @@ -2962,4 +2963,24 @@ describe('OptionsListUtils', () => { expect(searchTerms2.includes(displayName)).toBe(true); }); }); + + describe('getUserToInviteOption', () => { + it('should not return userToInvite for plain text name when shouldAcceptName is false', () => { + const result = getUserToInviteOption({ + searchValue: 'Jeff Amazon', + loginList: {}, + }); + expect(result).toBeNull(); + }); + + it('should return userToInvite for plain text name when shouldAcceptName is true', () => { + const result = getUserToInviteOption({ + searchValue: 'Jeff Amazon', + shouldAcceptName: true, + loginList: {}, + }); + expect(result).not.toBeNull(); + expect(result?.login).toBe('Jeff Amazon'); + }); + }); }); From a8f5491b3f63cbec0b26e04b12c96017281be857 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Thu, 8 Jan 2026 17:13:42 +0100 Subject: [PATCH 5/8] reject attendee names with angle brackets --- src/libs/OptionsListUtils/index.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/libs/OptionsListUtils/index.ts b/src/libs/OptionsListUtils/index.ts index 48e13f6105a8..85bd59c195c3 100644 --- a/src/libs/OptionsListUtils/index.ts +++ b/src/libs/OptionsListUtils/index.ts @@ -2740,11 +2740,16 @@ function filterUserToInvite( countryCode: number = CONST.DEFAULT_COUNTRY_CODE, config?: FilterUserToInviteConfig, ): SearchOptionData | null { - const {canInviteUser = true, excludeLogins = {}} = config ?? {}; + const {canInviteUser = true, excludeLogins = {}, shouldAcceptName = false} = config ?? {}; if (!canInviteUser) { return null; } + // Angle brackets are not valid characters for users names + if (shouldAcceptName && (searchValue.includes('<') || searchValue.includes('>'))) { + return null; + } + const canCreateOptimisticDetail = canCreateOptimisticPersonalDetailOption({ recentReportOptions: options.recentReports, personalDetailsOptions: options.personalDetails, From dd0bea5b7a38e7435aacfc09e8d7a52d160a899c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Thu, 8 Jan 2026 17:13:48 +0100 Subject: [PATCH 6/8] fallback to `displayName` for attendee login --- src/pages/iou/request/MoneyRequestAttendeeSelector.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx b/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx index 36c8b99b1f8a..5643fc78b4ba 100644 --- a/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx +++ b/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx @@ -79,7 +79,7 @@ function MoneyRequestAttendeeSelector({attendees = [], onFinish, onAttendeesAdde ...attendee, reportID: CONST.DEFAULT_NUMBER_ID.toString(), selected: true, - login: attendee.email, + login: attendee.email || attendee.displayName, ...getPersonalDetailByEmail(attendee.email), })), [attendees], @@ -208,7 +208,7 @@ function MoneyRequestAttendeeSelector({attendees = [], onFinish, onAttendeesAdde ...attendee, reportID: CONST.DEFAULT_NUMBER_ID.toString(), selected: true, - login: attendee.email, + login: attendee.email || attendee.displayName, ...getPersonalDetailByEmail(attendee.email), })), orderedAvailableOptions.recentReports, From b93dbdb361189d243a274e430fe190ed2e25332c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Thu, 8 Jan 2026 17:41:28 +0100 Subject: [PATCH 7/8] centralize angle bracket validation in `getUserToInviteOption` --- src/libs/OptionsListUtils/index.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/libs/OptionsListUtils/index.ts b/src/libs/OptionsListUtils/index.ts index 85bd59c195c3..a161671fead7 100644 --- a/src/libs/OptionsListUtils/index.ts +++ b/src/libs/OptionsListUtils/index.ts @@ -1687,7 +1687,10 @@ function getUserToInviteOption({ const isValidPhoneNumber = parsedPhoneNumber.possible && Str.isValidE164Phone(getPhoneNumberWithoutSpecialChars(parsedPhoneNumber.number?.input ?? '')); const isInOptionToExclude = loginsToExclude[addSMSDomainIfPhoneNumber(searchValue).toLowerCase()]; - if (isCurrentUserLogin || isInSelectedOption || (!isValidEmail && !isValidPhoneNumber && !shouldAcceptName) || isInOptionToExclude) { + // Angle brackets are not valid characters for user names + const hasInvalidCharacters = shouldAcceptName && (searchValue.includes('<') || searchValue.includes('>')); + + if (isCurrentUserLogin || isInSelectedOption || (!isValidEmail && !isValidPhoneNumber && !shouldAcceptName) || isInOptionToExclude || hasInvalidCharacters) { return null; } @@ -2446,7 +2449,7 @@ function getFilteredRecentAttendees(personalDetails: OnyxEntry !attendees.find(({email, displayName}) => (attendee.email ? email === attendee.email : displayName === attendee.displayName))) .map((attendee) => ({ ...attendee, - login: attendee.email ?? attendee.displayName, + login: attendee.email ? attendee.email : attendee.displayName, ...getPersonalDetailByEmail(attendee.email), })) .map((attendee) => getParticipantsOption(attendee, personalDetails)); @@ -2740,16 +2743,11 @@ function filterUserToInvite( countryCode: number = CONST.DEFAULT_COUNTRY_CODE, config?: FilterUserToInviteConfig, ): SearchOptionData | null { - const {canInviteUser = true, excludeLogins = {}, shouldAcceptName = false} = config ?? {}; + const {canInviteUser = true, excludeLogins = {}} = config ?? {}; if (!canInviteUser) { return null; } - // Angle brackets are not valid characters for users names - if (shouldAcceptName && (searchValue.includes('<') || searchValue.includes('>'))) { - return null; - } - const canCreateOptimisticDetail = canCreateOptimisticPersonalDetailOption({ recentReportOptions: options.recentReports, personalDetailsOptions: options.personalDetails, From da7e709601314d2a22c32f28c554fc30b480e50e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Thu, 8 Jan 2026 17:41:37 +0100 Subject: [PATCH 8/8] reuse `initialSelectedOptions` in sections --- .../iou/request/MoneyRequestAttendeeSelector.tsx | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx b/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx index 5643fc78b4ba..8b626bb99e5c 100644 --- a/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx +++ b/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx @@ -79,7 +79,7 @@ function MoneyRequestAttendeeSelector({attendees = [], onFinish, onAttendeesAdde ...attendee, reportID: CONST.DEFAULT_NUMBER_ID.toString(), selected: true, - login: attendee.email || attendee.displayName, + login: attendee.email ? attendee.email : attendee.displayName, ...getPersonalDetailByEmail(attendee.email), })), [attendees], @@ -204,13 +204,7 @@ function MoneyRequestAttendeeSelector({attendees = [], onFinish, onAttendeesAdde const cleanSearchTerm = searchTerm.trim().toLowerCase(); const formatResults = formatSectionsFromSearchTerm( cleanSearchTerm, - attendees.map((attendee) => ({ - ...attendee, - reportID: CONST.DEFAULT_NUMBER_ID.toString(), - selected: true, - login: attendee.email || attendee.displayName, - ...getPersonalDetailByEmail(attendee.email), - })), + initialSelectedOptions, orderedAvailableOptions.recentReports, orderedAvailableOptions.personalDetails, personalDetails, @@ -272,6 +266,7 @@ function MoneyRequestAttendeeSelector({attendees = [], onFinish, onAttendeesAdde areOptionsInitialized, didScreenTransitionEnd, searchTerm, + initialSelectedOptions, attendees, orderedAvailableOptions.recentReports, orderedAvailableOptions.personalDetails,