Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
f1b7e3b
Revert "Revert "Trigger whisper when user invites a member to the cha…
parasharrajat Sep 25, 2025
1143398
Merge branch 'main' of github.com:Expensify/App into revert-70755-rev…
parasharrajat Oct 7, 2025
cefa9ce
Merge branch 'main' of github.com:Expensify/App into revert-70755-rev…
parasharrajat Oct 20, 2025
083e106
Add lib to find the topmost active report in history to navigate back to
parasharrajat Oct 20, 2025
af40dae
navigate to the correct report
parasharrajat Oct 21, 2025
027ed95
Fix action method param order
parasharrajat Oct 21, 2025
0e4cea9
Merge branch 'main' of github.com:Expensify/App into revert-70755-rev…
parasharrajat Oct 21, 2025
e6bad2c
lint fixes
parasharrajat Oct 21, 2025
beb0856
Lint issues
parasharrajat Oct 21, 2025
7a59a60
Merge branch 'main' of github.com:Expensify/App into revert-70755-rev…
parasharrajat Oct 21, 2025
a3df059
Use backto For correcr navigation
parasharrajat Oct 23, 2025
a4c1fe9
Merge branch 'main' of github.com:Expensify/App into revert-70755-rev…
parasharrajat Oct 29, 2025
28d94ba
Hide invite to submit expenses for system users
parasharrajat Nov 1, 2025
0d101bc
Merge branch 'main' of github.com:Expensify/App into revert-70755-rev…
parasharrajat Nov 7, 2025
2a54e40
Show the invite to submit expense button even if one of the mentione…
parasharrajat Nov 13, 2025
ce9e237
fix lint
parasharrajat Nov 13, 2025
51f207d
Fix mention checks
parasharrajat Nov 13, 2025
fab9eeb
Apply suggestion from @parasharrajat
parasharrajat Nov 13, 2025
56cc5a1
Merge branch 'main' of github.com:Expensify/App into revert-70755-rev…
parasharrajat Nov 19, 2025
4d20c5e
Pass ancestors
parasharrajat Nov 19, 2025
3747ae5
Merge branch 'Expensify:main' into revert-70755-revert-69844-parashar…
parasharrajat Nov 20, 2025
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
7 changes: 7 additions & 0 deletions src/libs/ReportActionsUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
type MemberChangeMessageElement = MessageTextElement | MemberChangeMessageUserMentionElement | MemberChangeMessageRoomReferenceElement;

let allReportActions: OnyxCollection<ReportActions>;
Onyx.connect({

Check warning on line 60 in src/libs/ReportActionsUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
waitForCollectionCallback: true,
callback: (actions) => {
Expand All @@ -69,7 +69,7 @@
});

let allReports: OnyxCollection<Report>;
Onyx.connect({

Check warning on line 72 in src/libs/ReportActionsUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT,
waitForCollectionCallback: true,
callback: (value) => {
Expand All @@ -78,14 +78,14 @@
});

let isNetworkOffline = false;
Onyx.connect({

Check warning on line 81 in src/libs/ReportActionsUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.NETWORK,
callback: (val) => (isNetworkOffline = val?.isOffline ?? false),
});

let currentUserAccountID: number | undefined;
let currentEmail = '';
Onyx.connect({

Check warning on line 88 in src/libs/ReportActionsUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.SESSION,
callback: (value) => {
// When signed out, value is undefined
Expand All @@ -99,7 +99,7 @@
});

let privatePersonalDetails: PrivatePersonalDetails | undefined;
Onyx.connect({

Check warning on line 102 in src/libs/ReportActionsUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.PRIVATE_PERSONAL_DETAILS,
callback: (personalDetails) => {
privatePersonalDetails = personalDetails;
Expand Down Expand Up @@ -3331,6 +3331,12 @@
return getOriginalMessage(action)?.to;
}

function isSystemUserMentioned(action: OnyxInputOrEntry<ReportAction<typeof CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_MENTION_WHISPER>>): boolean {
const mentionedUsers = getOriginalMessage(action)?.inviteeAccountIDs;
const systemAccountIDs = new Set(Object.values(CONST.ACCOUNT_ID));
return mentionedUsers?.some((accountID) => systemAccountIDs.has(accountID)) ?? false;
}

export {
doesReportHaveVisibleActions,
extractLinksFromMessageHtml,
Expand Down Expand Up @@ -3511,6 +3517,7 @@
isPendingHide,
filterOutDeprecatedReportActions,
getActionableCardFraudAlertMessage,
isSystemUserMentioned,
};

export type {LastVisibleMessage};
8 changes: 8 additions & 0 deletions src/libs/actions/Report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@
let currentUserAccountID = -1;
let currentUserEmail: string | undefined;

Onyx.connect({

Check warning on line 270 in src/libs/actions/Report.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.SESSION,
callback: (value) => {
// When signed out, val is undefined
Expand All @@ -280,13 +280,13 @@
},
});

Onyx.connect({

Check warning on line 283 in src/libs/actions/Report.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.CONCIERGE_REPORT_ID,
callback: (value) => (conciergeReportID = value),
});

let preferredSkinTone: number = CONST.EMOJI_DEFAULT_SKIN_TONE;
Onyx.connect({

Check warning on line 289 in src/libs/actions/Report.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE,
callback: (value) => {
preferredSkinTone = EmojiUtils.getPreferredSkinToneIndex(value);
Expand All @@ -296,7 +296,7 @@
// map of reportID to all reportActions for that report
const allReportActions: OnyxCollection<ReportActions> = {};

Onyx.connect({

Check warning on line 299 in src/libs/actions/Report.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
callback: (actions, key) => {
if (!key || !actions) {
Expand All @@ -308,7 +308,7 @@
});

let allTransactionViolations: OnyxCollection<TransactionViolations> = {};
Onyx.connect({

Check warning on line 311 in src/libs/actions/Report.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS,
waitForCollectionCallback: true,
callback: (value) => (allTransactionViolations = value),
Expand Down Expand Up @@ -3853,6 +3853,13 @@
API.write(WRITE_COMMANDS.INVITE_TO_ROOM, parameters, {optimisticData, successData, failureData});
}

/** Invites people to a room via concierge whisper */
function inviteToRoomAction(reportID: string, ancestors: Ancestor[], inviteeEmailsToAccountIDs: InvitedEmailsToAccountIDs, timezoneParam: Timezone) {
const inviteeEmails = Object.keys(inviteeEmailsToAccountIDs);

addComment(reportID, reportID, ancestors, inviteeEmails.map((login) => `@${login}`).join(' '), timezoneParam, false);
}

function clearAddRoomMemberError(reportID: string, invitedAccountID: string) {
const reportMetadata = getReportMetadata(reportID);
Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {
Expand Down Expand Up @@ -6082,6 +6089,7 @@
inviteToGroupChat,
buildInviteToRoomOnyxData,
inviteToRoom,
inviteToRoomAction,
joinRoom,
leaveGroupChat,
leaveRoom,
Expand Down
22 changes: 16 additions & 6 deletions src/pages/RoomInvitePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ import InviteMemberListItem from '@components/SelectionListWithSections/InviteMe
import type {Section} from '@components/SelectionListWithSections/types';
import withNavigationTransitionEnd from '@components/withNavigationTransitionEnd';
import type {WithNavigationTransitionEndProps} from '@components/withNavigationTransitionEnd';
import useAncestors from '@hooks/useAncestors';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useDebouncedState from '@hooks/useDebouncedState';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import useReportIsArchived from '@hooks/useReportIsArchived';
import useThemeStyles from '@hooks/useThemeStyles';
import {inviteToRoom, searchInServer} from '@libs/actions/Report';
import {inviteToRoomAction, searchInServer} from '@libs/actions/Report';
import {clearUserSearchPhrase, updateUserSearchPhrase} from '@libs/actions/RoomMembersUserSearchPhrase';
import {READ_COMMANDS} from '@libs/API/types';
import {canUseTouchScreen} from '@libs/DeviceCapabilities';
Expand Down Expand Up @@ -55,13 +57,14 @@ function RoomInvitePage({
},
}: RoomInvitePageProps) {
const styles = useThemeStyles();
const {translate, formatPhoneNumber} = useLocalize();
const {translate} = useLocalize();
const [userSearchPhrase] = useOnyx(ONYXKEYS.ROOM_MEMBERS_USER_SEARCH_PHRASE, {canBeMissing: true});
const [countryCode = CONST.DEFAULT_COUNTRY_CODE] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false});
const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(userSearchPhrase ?? '');
const [selectedOptions, setSelectedOptions] = useState<OptionData[]>([]);
const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false, canBeMissing: true});
const isReportArchived = useReportIsArchived(report.reportID);
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
const [nvpDismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING, {canBeMissing: true});

const {options, areOptionsInitialized} = useOptionsList();
Expand Down Expand Up @@ -192,6 +195,9 @@ function RoomInvitePage({
return reportID && (!isPolicyEmployee || isReportArchived ? ROUTES.REPORT_WITH_ID_DETAILS.getRoute(reportID, backTo) : ROUTES.ROOM_MEMBERS.getRoute(reportID, backTo));
}, [isPolicyEmployee, reportID, backTo, isReportArchived]);
const reportName = useMemo(() => getReportName(report), [report]);

const ancestors = useAncestors(report);

const inviteUsers = useCallback(() => {
HttpUtils.cancelPendingRequests(READ_COMMANDS.SEARCH_FOR_REPORTS);

Expand All @@ -208,11 +214,15 @@ function RoomInvitePage({
invitedEmailsToAccountIDs[login] = Number(accountID);
}
if (reportID) {
inviteToRoom(reportID, invitedEmailsToAccountIDs, formatPhoneNumber);
inviteToRoomAction(reportID, ancestors, invitedEmailsToAccountIDs, currentUserPersonalDetails.timezone ?? CONST.DEFAULT_TIME_ZONE);
clearUserSearchPhrase();
if (backTo) {
Navigation.goBack(backTo);
} else {
Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(reportID));
}
}
clearUserSearchPhrase();
Navigation.goBack(backRoute);
}, [selectedOptions, backRoute, reportID, validate, formatPhoneNumber]);
}, [validate, selectedOptions, ancestors, reportID, currentUserPersonalDetails.timezone, backTo]);

const goBack = useCallback(() => {
Navigation.goBack(backRoute);
Expand Down
8 changes: 6 additions & 2 deletions src/pages/home/report/PureReportActionItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ import {isReportMessageAttachment} from '@libs/isReportMessageAttachment';
import Navigation from '@libs/Navigation/Navigation';
import Permissions from '@libs/Permissions';
import {getDisplayNameOrDefault} from '@libs/PersonalDetailsUtils';
import {getCleanedTagName, getPersonalPolicy, isPolicyAdmin, isPolicyOwner} from '@libs/PolicyUtils';
import {getCleanedTagName, getPersonalPolicy, isPolicyAdmin, isPolicyOwner, isPolicyUser} from '@libs/PolicyUtils';
import {
extractLinksFromMessageHtml,
getActionableCardFraudAlertMessage,
Expand Down Expand Up @@ -131,6 +131,7 @@ import {
isRenamedAction,
isResolvedConciergeCategoryOptions,
isSplitBillAction as isSplitBillActionReportActionsUtils,
isSystemUserMentioned,
isTagModificationAction,
isTaskAction,
isTrackExpenseAction as isTrackExpenseActionReportActionsUtils,
Expand Down Expand Up @@ -905,7 +906,10 @@ function PureReportActionItem({
const actionableMentionWhisperOptions = [];
const isReportInPolicy = !!report?.policyID && report.policyID !== CONST.POLICY.ID_FAKE && getPersonalPolicy()?.id !== report.policyID;

if (isReportInPolicy && (isPolicyAdmin(policy) || isPolicyOwner(policy, currentUserAccountID))) {
// Show the invite to submit expense button even if one of the mentioned users is a not a policy member
const hasMentionedPolicyMembers = getOriginalMessage(action)?.inviteeEmails?.every((login) => isPolicyUser(policy, login)) ?? false;

if ((isPolicyAdmin(policy) || isPolicyOwner(policy, currentUserAccountID)) && isReportInPolicy && !isSystemUserMentioned(action) && !hasMentionedPolicyMembers) {
actionableMentionWhisperOptions.push({
text: 'actionableMentionWhisperOptions.inviteToSubmitExpense',
key: `${action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE_TO_SUBMIT_EXPENSE}`,
Expand Down
Loading