Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow searching new user in chat finder page #40962

Merged
merged 10 commits into from
May 3, 2024
142 changes: 99 additions & 43 deletions src/libs/OptionsListUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,16 @@ type GetOptionsConfig = {
transactionViolations?: OnyxCollection<TransactionViolation[]>;
};

type GetUserToInviteConfig = {
searchValue: string;
excludeUnknownUsers?: boolean;
optionsToExclude?: Array<Partial<ReportUtils.OptionData>>;
selectedOptions?: Array<Partial<ReportUtils.OptionData>>;
betas: OnyxEntry<Beta[]>;
reportActions?: ReportActions;
showChatPreviewLine?: boolean;
};

type MemberForList = {
text: string;
alternateText: string;
Expand Down Expand Up @@ -1536,6 +1546,74 @@ function orderOptions(options: ReportUtils.OptionData[], searchValue: string | u
);
}

/**
* We create a new user option if the following conditions are satisfied:
* - There's no matching recent report and personal detail option
* - The searchValue is a valid email or phone number
* - The searchValue isn't the current personal detail login
* - We can use chronos or the search value is not the chronos email
*/
function getUserToInviteOption({
searchValue,
excludeUnknownUsers = false,
optionsToExclude = [],
selectedOptions = [],
betas,
reportActions = {},
showChatPreviewLine = false,
}: GetUserToInviteConfig): ReportUtils.OptionData | null {
const parsedPhoneNumber = PhoneNumber.parsePhoneNumber(LoginUtils.appendCountryCode(Str.removeSMSDomain(searchValue)));
const isCurrentUserLogin = isCurrentUser({login: searchValue} as PersonalDetails);
const isInSelectedOption = selectedOptions.some((option) => 'login' in option && option.login === searchValue);
const isValidEmail = Str.isValidEmail(searchValue) && !Str.isDomainEmail(searchValue) && !Str.endsWith(searchValue, CONST.SMS.DOMAIN);
const isValidPhoneNumber = parsedPhoneNumber.possible && Str.isValidE164Phone(LoginUtils.getPhoneNumberWithoutSpecialChars(parsedPhoneNumber.number?.input ?? ''));
const isInOptionToExclude =
optionsToExclude.findIndex((optionToExclude) => 'login' in optionToExclude && optionToExclude.login === PhoneNumber.addSMSDomainIfPhoneNumber(searchValue).toLowerCase()) !== -1;
const isChronosEmail = searchValue === CONST.EMAIL.CHRONOS;
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks looks soooooo much better ❤️


if (
!searchValue ||
isCurrentUserLogin ||
isInSelectedOption ||
(!isValidEmail && !isValidPhoneNumber) ||
isInOptionToExclude ||
(isChronosEmail && !Permissions.canUseChronos(betas)) ||
excludeUnknownUsers
) {
return null;
}

// Generates an optimistic account ID for new users not yet saved in Onyx
const optimisticAccountID = UserUtils.generateAccountID(searchValue);
const personalDetailsExtended = {
...allPersonalDetails,
[optimisticAccountID]: {
accountID: optimisticAccountID,
login: searchValue,
},
};
const userToInvite = createOption([optimisticAccountID], personalDetailsExtended, null, reportActions, {
showChatPreviewLine,
});
userToInvite.isOptimisticAccount = true;
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
userToInvite.alternateText = userToInvite.alternateText || searchValue;

// If user doesn't exist, use a fallback avatar
userToInvite.icons = [
{
source: UserUtils.getAvatar('', optimisticAccountID),
name: searchValue,
type: CONST.ICON_TYPE_AVATAR,
},
];

return userToInvite;
}

/**
* filter options based on specific conditions
*/
Expand Down Expand Up @@ -1844,52 +1922,23 @@ function getOptions(
currentUserOption = undefined;
}

let userToInvite: ReportUtils.OptionData | null = null;
const noOptions = recentReportOptions.length + personalDetailsOptions.length === 0 && !currentUserOption;
const noOptionsMatchExactly = !personalDetailsOptions
.concat(recentReportOptions)
.find((option) => option.login === PhoneNumber.addSMSDomainIfPhoneNumber(searchValue ?? '').toLowerCase() || option.login === searchValue?.toLowerCase());

if (
searchValue &&
(noOptions || noOptionsMatchExactly) &&
!isCurrentUser({login: searchValue} as PersonalDetails) &&
selectedOptions.every((option) => 'login' in option && option.login !== searchValue) &&
((Str.isValidEmail(searchValue) && !Str.isDomainEmail(searchValue) && !Str.endsWith(searchValue, CONST.SMS.DOMAIN)) ||
(parsedPhoneNumber.possible && Str.isValidE164Phone(LoginUtils.getPhoneNumberWithoutSpecialChars(parsedPhoneNumber.number?.input ?? '')))) &&
!optionsToExclude.find((optionToExclude) => 'login' in optionToExclude && optionToExclude.login === PhoneNumber.addSMSDomainIfPhoneNumber(searchValue).toLowerCase()) &&
(searchValue !== CONST.EMAIL.CHRONOS || Permissions.canUseChronos(betas)) &&
!excludeUnknownUsers
) {
// Generates an optimistic account ID for new users not yet saved in Onyx
const optimisticAccountID = UserUtils.generateAccountID(searchValue);
const personalDetailsExtended = {
...allPersonalDetails,
[optimisticAccountID]: {
accountID: optimisticAccountID,
login: searchValue,
avatar: UserUtils.getDefaultAvatar(optimisticAccountID),
},
};
userToInvite = createOption([optimisticAccountID], personalDetailsExtended, null, reportActions, {
showChatPreviewLine,
});
userToInvite.isOptimisticAccount = true;
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
userToInvite.alternateText = userToInvite.alternateText || searchValue;

// If user doesn't exist, use a default avatar
userToInvite.icons = [
{
source: UserUtils.getAvatar('', optimisticAccountID),
name: searchValue,
type: CONST.ICON_TYPE_AVATAR,
},
];
}
const userToInvite =
noOptions || noOptionsMatchExactly
? getUserToInviteOption({
searchValue,
excludeUnknownUsers,
optionsToExclude,
selectedOptions,
betas,
reportActions,
showChatPreviewLine,
})
: null;

// If we are prioritizing 1:1 chats in search, do it only once we started searching
if (sortByReportTypeInSearch && searchValue !== '') {
Expand Down Expand Up @@ -2249,7 +2298,7 @@ function getFirstKeyForList(data?: Option[] | null) {
/**
* Filters options based on the search input value
*/
function filterOptions(options: Options, searchInputValue: string): Options {
function filterOptions(options: Options, searchInputValue: string, betas: OnyxEntry<Beta[]> = []): Options {
const searchValue = getSearchValueForPhoneOrEmail(searchInputValue);
const searchTerms = searchValue ? searchValue.split(' ') : [];

Expand Down Expand Up @@ -2318,11 +2367,18 @@ function filterOptions(options: Options, searchInputValue: string): Options {
}, options);

const recentReports = matchResults.recentReports.concat(matchResults.personalDetails);
const userToInvite =
recentReports.length === 0
? getUserToInviteOption({
searchValue,
betas,
})
: null;

return {
personalDetails: [],
recentReports: orderOptions(recentReports, searchValue),
userToInvite: null,
userToInvite,
currentUserOption: null,
categoryOptions: [],
tagOptions: [],
Expand Down
8 changes: 4 additions & 4 deletions src/pages/ChatFinderPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,15 +101,15 @@ function ChatFinderPage({betas, isSearchingForReports, navigation}: ChatFinderPa
};
}

const newOptions = OptionsListUtils.filterOptions(searchOptions, debouncedSearchValue);
const header = OptionsListUtils.getHeaderMessage(newOptions.recentReports.length > 0, false, debouncedSearchValue);
const newOptions = OptionsListUtils.filterOptions(searchOptions, debouncedSearchValue, betas);
const header = OptionsListUtils.getHeaderMessage(newOptions.recentReports.length + Number(!!newOptions.userToInvite) > 0, false, debouncedSearchValue);
return {
recentReports: newOptions.recentReports,
personalDetails: newOptions.personalDetails,
userToInvite: null,
userToInvite: newOptions.userToInvite,
headerMessage: header,
};
}, [debouncedSearchValue, searchOptions]);
}, [debouncedSearchValue, searchOptions, betas]);

const {recentReports, personalDetails: localPersonalDetails, userToInvite, headerMessage} = debouncedSearchValue.trim() !== '' ? filteredOptions : searchOptions;

Expand Down
Loading