Skip to content

Fix after invite user, going back arrived at the invite page again #61585

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

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 54 additions & 49 deletions src/pages/InviteReportParticipantsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,27 @@ import type {WithNavigationTransitionEndProps} from '@components/withNavigationT
import useDebouncedState from '@hooks/useDebouncedState';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import * as UserSearchPhraseActions from '@libs/actions/RoomMembersUserSearchPhrase';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
import * as LoginUtils from '@libs/LoginUtils';
import {inviteToGroupChat, searchInServer} from '@libs/actions/Report';
import {clearUserSearchPhrase, updateUserSearchPhrase} from '@libs/actions/RoomMembersUserSearchPhrase';
import {canUseTouchScreen} from '@libs/DeviceCapabilities';
import {appendCountryCode} from '@libs/LoginUtils';
import Navigation from '@libs/Navigation/Navigation';
import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types';
import type {ParticipantsNavigatorParamList} from '@libs/Navigation/types';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import * as PhoneNumber from '@libs/PhoneNumber';
import * as ReportUtils from '@libs/ReportUtils';
import * as Report from '@userActions/Report';
import {
filterAndOrderOptions,
formatMemberForList,
getEmptyOptions,
getHeaderMessage,
getMemberInviteOptions,
getSearchValueForPhoneOrEmail,
isPersonalDetailsReady,
} from '@libs/OptionsListUtils';
import type {MemberForList} from '@libs/OptionsListUtils';
import {getLoginsByAccountIDs} from '@libs/PersonalDetailsUtils';
import {addSMSDomainIfPhoneNumber, parsePhoneNumber} from '@libs/PhoneNumber';
import {getGroupChatName, getParticipantsAccountIDsForDisplay} from '@libs/ReportUtils';
import type {OptionData} from '@libs/ReportUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
Expand All @@ -35,7 +45,7 @@ import withReportOrNotFound from './home/report/withReportOrNotFound';

type InviteReportParticipantsPageProps = WithReportOrNotFoundProps & WithNavigationTransitionEndProps;

type Sections = Array<SectionListData<OptionsListUtils.MemberForList, Section<OptionsListUtils.MemberForList>>>;
type Sections = Array<SectionListData<MemberForList, Section<MemberForList>>>;

function InviteReportParticipantsPage({betas, report, didScreenTransitionEnd}: InviteReportParticipantsPageProps) {
const route = useRoute<PlatformStackRouteProp<ParticipantsNavigatorParamList, typeof SCREENS.REPORT_PARTICIPANTS.INVITE>>();
Expand All @@ -45,23 +55,23 @@ function InviteReportParticipantsPage({betas, report, didScreenTransitionEnd}: I

const styles = useThemeStyles();
const {translate} = useLocalize();
const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST);
const [userSearchPhrase] = useOnyx(ONYXKEYS.ROOM_MEMBERS_USER_SEARCH_PHRASE);
const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {canBeMissing: false});
const [userSearchPhrase] = useOnyx(ONYXKEYS.ROOM_MEMBERS_USER_SEARCH_PHRASE, {canBeMissing: true});
const [searchValue, debouncedSearchTerm, setSearchValue] = useDebouncedState(userSearchPhrase ?? '');
const [selectedOptions, setSelectedOptions] = useState<ReportUtils.OptionData[]>([]);
const [selectedOptions, setSelectedOptions] = useState<OptionData[]>([]);

useEffect(() => {
UserSearchPhraseActions.updateUserSearchPhrase(debouncedSearchTerm);
Report.searchInServer(debouncedSearchTerm);
updateUserSearchPhrase(debouncedSearchTerm);
searchInServer(debouncedSearchTerm);
}, [debouncedSearchTerm]);

// Any existing participants and Expensify emails should not be eligible for invitation
const excludedUsers = useMemo(() => {
const res = {
...CONST.EXPENSIFY_EMAILS_OBJECT,
};
const participantsAccountIDs = ReportUtils.getParticipantsAccountIDsForDisplay(report, false, true);
const loginsByAccountIDs = PersonalDetailsUtils.getLoginsByAccountIDs(participantsAccountIDs);
const participantsAccountIDs = getParticipantsAccountIDsForDisplay(report, false, true);
const loginsByAccountIDs = getLoginsByAccountIDs(participantsAccountIDs);
for (const login of loginsByAccountIDs) {
res[login] = true;
}
Expand All @@ -70,27 +80,24 @@ function InviteReportParticipantsPage({betas, report, didScreenTransitionEnd}: I

const defaultOptions = useMemo(() => {
if (!areOptionsInitialized) {
return OptionsListUtils.getEmptyOptions();
return getEmptyOptions();
}

return OptionsListUtils.getMemberInviteOptions(options.personalDetails, betas ?? [], excludedUsers, false, options.reports, true);
return getMemberInviteOptions(options.personalDetails, betas ?? [], excludedUsers, false, options.reports, true);
}, [areOptionsInitialized, betas, excludedUsers, options.personalDetails, options.reports]);

const inviteOptions = useMemo(
() => OptionsListUtils.filterAndOrderOptions(defaultOptions, debouncedSearchTerm, {excludeLogins: excludedUsers}),
[debouncedSearchTerm, defaultOptions, excludedUsers],
);
const inviteOptions = useMemo(() => filterAndOrderOptions(defaultOptions, debouncedSearchTerm, {excludeLogins: excludedUsers}), [debouncedSearchTerm, defaultOptions, excludedUsers]);

useEffect(() => {
// Update selectedOptions with the latest personalDetails information
const detailsMap: Record<string, OptionsListUtils.MemberForList> = {};
const detailsMap: Record<string, MemberForList> = {};
inviteOptions.personalDetails.forEach((detail) => {
if (!detail.login) {
return;
}
detailsMap[detail.login] = OptionsListUtils.formatMemberForList(detail);
detailsMap[detail.login] = formatMemberForList(detail);
});
const newSelectedOptions: ReportUtils.OptionData[] = [];
const newSelectedOptions: OptionData[] = [];
selectedOptions.forEach((option) => {
newSelectedOptions.push(option.login && option.login in detailsMap ? {...detailsMap[option.login], isSelected: true} : option);
});
Expand All @@ -112,12 +119,12 @@ function InviteReportParticipantsPage({betas, report, didScreenTransitionEnd}: I
filterSelectedOptions = selectedOptions.filter((option) => {
const accountID = option?.accountID;
const isOptionInPersonalDetails = inviteOptions.personalDetails.some((personalDetail) => accountID && personalDetail?.accountID === accountID);
const processedSearchValue = OptionsListUtils.getSearchValueForPhoneOrEmail(debouncedSearchTerm);
const processedSearchValue = getSearchValueForPhoneOrEmail(debouncedSearchTerm);
const isPartOfSearchTerm = !!option.text?.toLowerCase().includes(processedSearchValue) || !!option.login?.toLowerCase().includes(processedSearchValue);
return isPartOfSearchTerm || isOptionInPersonalDetails;
});
}
const filterSelectedOptionsFormatted = filterSelectedOptions.map((selectedOption) => OptionsListUtils.formatMemberForList(selectedOption));
const filterSelectedOptionsFormatted = filterSelectedOptions.map((selectedOption) => formatMemberForList(selectedOption));

sectionsArr.push({
title: undefined,
Expand All @@ -127,9 +134,9 @@ function InviteReportParticipantsPage({betas, report, didScreenTransitionEnd}: I
// Filtering out selected users from the search results
const selectedLogins = selectedOptions.map(({login}) => login);
const recentReportsWithoutSelected = inviteOptions.recentReports.filter(({login}) => !selectedLogins.includes(login));
const recentReportsFormatted = recentReportsWithoutSelected.map((reportOption) => OptionsListUtils.formatMemberForList(reportOption));
const recentReportsFormatted = recentReportsWithoutSelected.map((reportOption) => formatMemberForList(reportOption));
const personalDetailsWithoutSelected = inviteOptions.personalDetails.filter(({login}) => !selectedLogins.includes(login));
const personalDetailsFormatted = personalDetailsWithoutSelected.map((personalDetail) => OptionsListUtils.formatMemberForList(personalDetail));
const personalDetailsFormatted = personalDetailsWithoutSelected.map((personalDetail) => formatMemberForList(personalDetail));
const hasUnselectedUserToInvite = inviteOptions.userToInvite && !selectedLogins.includes(inviteOptions.userToInvite.login);

sectionsArr.push({
Expand All @@ -145,18 +152,18 @@ function InviteReportParticipantsPage({betas, report, didScreenTransitionEnd}: I
if (hasUnselectedUserToInvite) {
sectionsArr.push({
title: undefined,
data: inviteOptions.userToInvite ? [OptionsListUtils.formatMemberForList(inviteOptions.userToInvite)] : [],
data: inviteOptions.userToInvite ? [formatMemberForList(inviteOptions.userToInvite)] : [],
});
}

return sectionsArr;
}, [areOptionsInitialized, selectedOptions, debouncedSearchTerm, inviteOptions.recentReports, inviteOptions.personalDetails, inviteOptions.userToInvite, translate]);

const toggleOption = useCallback(
(option: OptionsListUtils.MemberForList) => {
(option: MemberForList) => {
const isOptionInList = selectedOptions.some((selectedOption) => selectedOption.login === option.login);

let newSelectedOptions: ReportUtils.OptionData[];
let newSelectedOptions: OptionData[];
if (isOptionInList) {
newSelectedOptions = selectedOptions.filter((selectedOption) => selectedOption.login !== option.login);
} else {
Expand All @@ -171,8 +178,12 @@ function InviteReportParticipantsPage({betas, report, didScreenTransitionEnd}: I
const validate = useCallback(() => selectedOptions.length > 0, [selectedOptions]);

const reportID = report.reportID;
const backRoute = useMemo(() => ROUTES.REPORT_PARTICIPANTS.getRoute(reportID, route.params.backTo), [reportID, route.params.backTo]);
const reportName = useMemo(() => ReportUtils.getGroupChatName(undefined, true, report), [report]);
const reportName = useMemo(() => getGroupChatName(undefined, true, report), [report]);

const goBack = useCallback(() => {
Navigation.goBack(ROUTES.REPORT_PARTICIPANTS.getRoute(reportID, route.params.backTo));
}, [reportID, route.params.backTo]);

const inviteUsers = useCallback(() => {
if (!validate()) {
return;
Expand All @@ -186,9 +197,9 @@ function InviteReportParticipantsPage({betas, report, didScreenTransitionEnd}: I
}
invitedEmailsToAccountIDs[login] = accountID;
});
Report.inviteToGroupChat(reportID, invitedEmailsToAccountIDs);
Navigation.navigate(backRoute);
}, [selectedOptions, backRoute, reportID, validate]);
inviteToGroupChat(reportID, invitedEmailsToAccountIDs);
goBack();
}, [selectedOptions, goBack, reportID, validate]);

const headerMessage = useMemo(() => {
const processedLogin = debouncedSearchTerm.trim().toLowerCase();
Expand All @@ -198,15 +209,11 @@ function InviteReportParticipantsPage({betas, report, didScreenTransitionEnd}: I
}
if (
!inviteOptions.userToInvite &&
excludedUsers[
PhoneNumber.parsePhoneNumber(LoginUtils.appendCountryCode(processedLogin)).possible
? PhoneNumber.addSMSDomainIfPhoneNumber(LoginUtils.appendCountryCode(processedLogin))
: processedLogin
]
excludedUsers[parsePhoneNumber(appendCountryCode(processedLogin)).possible ? addSMSDomainIfPhoneNumber(appendCountryCode(processedLogin)) : processedLogin]
) {
return translate('messages.userIsAlreadyMember', {login: processedLogin, name: reportName ?? ''});
}
return OptionsListUtils.getHeaderMessage(inviteOptions.recentReports.length + inviteOptions.personalDetails.length !== 0, !!inviteOptions.userToInvite, processedLogin);
return getHeaderMessage(inviteOptions.recentReports.length + inviteOptions.personalDetails.length !== 0, !!inviteOptions.userToInvite, processedLogin);
}, [debouncedSearchTerm, inviteOptions.userToInvite, inviteOptions.recentReports.length, inviteOptions.personalDetails.length, excludedUsers, translate, reportName]);

const footerContent = useMemo(
Expand All @@ -215,7 +222,7 @@ function InviteReportParticipantsPage({betas, report, didScreenTransitionEnd}: I
isDisabled={!selectedOptions.length}
buttonText={translate('common.invite')}
onSubmit={() => {
UserSearchPhraseActions.clearUserSearchPhrase();
clearUserSearchPhrase();
inviteUsers();
}}
containerStyles={[styles.flexReset, styles.flexGrow0, styles.flexShrink0, styles.flexBasisAuto]}
Expand All @@ -233,9 +240,7 @@ function InviteReportParticipantsPage({betas, report, didScreenTransitionEnd}: I
<HeaderWithBackButton
title={translate('workspace.invite.members')}
subtitle={reportName}
onBackButtonPress={() => {
Navigation.goBack(backRoute);
}}
onBackButtonPress={goBack}
/>

<SelectionList
Expand All @@ -251,8 +256,8 @@ function InviteReportParticipantsPage({betas, report, didScreenTransitionEnd}: I
onSelectRow={toggleOption}
onConfirm={inviteUsers}
showScrollIndicator
shouldPreventDefaultFocusOnSelectRow={!DeviceCapabilities.canUseTouchScreen()}
showLoadingPlaceholder={!didScreenTransitionEnd || !OptionsListUtils.isPersonalDetailsReady(personalDetails)}
shouldPreventDefaultFocusOnSelectRow={!canUseTouchScreen()}
showLoadingPlaceholder={!didScreenTransitionEnd || !isPersonalDetailsReady(personalDetails)}
footerContent={footerContent}
/>
</ScreenWrapper>
Expand Down