diff --git a/android/app/build.gradle b/android/app/build.gradle
index f7a83db89d9a..04d711009c10 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -91,8 +91,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1001040802
- versionName "1.4.8-2"
+ versionCode 1001040902
+ versionName "1.4.9-2"
}
flavorDimensions "default"
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index 9465f768afe1..07afc5a85593 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -19,7 +19,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.4.8
+ 1.4.9
CFBundleSignature
????
CFBundleURLTypes
@@ -40,7 +40,7 @@
CFBundleVersion
- 1.4.8.2
+ 1.4.9.2
ITSAppUsesNonExemptEncryption
LSApplicationQueriesSchemes
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index b4ae3184e624..a434ffdc5757 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -15,10 +15,10 @@
CFBundlePackageType
BNDL
CFBundleShortVersionString
- 1.4.8
+ 1.4.9
CFBundleSignature
????
CFBundleVersion
- 1.4.8.2
+ 1.4.9.2
diff --git a/package-lock.json b/package-lock.json
index 5e459506dccd..6e5b51fa4526 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "1.4.8-2",
+ "version": "1.4.9-2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "1.4.8-2",
+ "version": "1.4.9-2",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index 6c3eb5a589b4..8191454ef138 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.4.8-2",
+ "version": "1.4.9-2",
"author": "Expensify, Inc.",
"homepage": "https://new.expensify.com",
"description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js
index 8f1406439be9..49642308a357 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js
@@ -62,7 +62,7 @@ function AnchorRenderer(props) {
key={props.key}
displayName={displayName}
// Only pass the press handler for internal links. For public links or whitelisted internal links fallback to default link handling
- onPress={internalNewExpensifyPath || internalExpensifyPath ? Link.openLink : undefined}
+ onPress={internalNewExpensifyPath || internalExpensifyPath ? () => Link.openLink(attrHref, environmentURL, isAttachment) : undefined}
>
diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js
index 0249a9f5bb11..80725a1e2531 100644
--- a/src/components/ReportActionItem/MoneyRequestView.js
+++ b/src/components/ReportActionItem/MoneyRequestView.js
@@ -29,6 +29,7 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import * as ReportUtils from '@libs/ReportUtils';
import * as TransactionUtils from '@libs/TransactionUtils';
import AnimatedEmptyStateBackground from '@pages/home/report/AnimatedEmptyStateBackground';
+import reportActionPropTypes from '@pages/home/report/reportActionPropTypes';
import iouReportPropTypes from '@pages/iouReportPropTypes';
import reportPropTypes from '@pages/reportPropTypes';
import * as StyleUtils from '@styles/StyleUtils';
@@ -51,6 +52,9 @@ const propTypes = {
/** The expense report or iou report (only will have a value if this is a transaction thread) */
parentReport: iouReportPropTypes,
+ /** The actions from the parent report */
+ parentReportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)),
+
/** Collection of categories attached to a policy */
policyCategories: PropTypes.objectOf(categoryPropTypes),
@@ -65,6 +69,7 @@ const propTypes = {
const defaultProps = {
parentReport: {},
+ parentReportActions: {},
policyCategories: {},
transaction: {
amount: 0,
@@ -74,13 +79,13 @@ const defaultProps = {
policyTags: {},
};
-function MoneyRequestView({report, parentReport, policyCategories, shouldShowHorizontalRule, transaction, policyTags, policy}) {
+function MoneyRequestView({report, parentReport, parentReportActions, policyCategories, shouldShowHorizontalRule, transaction, policyTags, policy}) {
const theme = useTheme();
const styles = useThemeStyles();
const {isSmallScreenWidth} = useWindowDimensions();
const {translate} = useLocalize();
const {canUseViolations} = usePermissions();
- const parentReportAction = ReportActionsUtils.getParentReportAction(report);
+ const parentReportAction = parentReportActions[report.parentReportActionID] || {};
const moneyRequestReport = parentReport;
const {
created: transactionDate,
@@ -313,8 +318,8 @@ MoneyRequestView.displayName = 'MoneyRequestView';
export default compose(
withCurrentUserPersonalDetails,
withOnyx({
- parentReport: {
- key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`,
+ session: {
+ key: ONYXKEYS.SESSION,
},
policy: {
key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`,
@@ -322,18 +327,24 @@ export default compose(
policyCategories: {
key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${report.policyID}`,
},
- session: {
- key: ONYXKEYS.SESSION,
+ policyTags: {
+ key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report.policyID}`,
},
+ parentReport: {
+ key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`,
+ },
+ parentReportActions: {
+ key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID : '0'}`,
+ canEvict: false,
+ },
+ }),
+ withOnyx({
transaction: {
- key: ({report}) => {
- const parentReportAction = ReportActionsUtils.getParentReportAction(report);
+ key: ({report, parentReportActions}) => {
+ const parentReportAction = parentReportActions[report.parentReportActionID];
const transactionID = lodashGet(parentReportAction, ['originalMessage', 'IOUTransactionID'], 0);
return `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`;
},
},
- policyTags: {
- key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report.policyID}`,
- },
}),
)(MoneyRequestView);
diff --git a/src/languages/en.ts b/src/languages/en.ts
index a276de4e0f7c..817f06f6b344 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -1547,12 +1547,6 @@ export default {
invitePeople: 'Invite new members',
genericFailureMessage: 'An error occurred inviting the user to the workspace, please try again.',
pleaseEnterValidLogin: `Please ensure the email or phone number is valid (e.g. ${CONST.EXAMPLE_PHONE_NUMBER}).`,
- user: 'user',
- users: 'users',
- invited: 'invited',
- removed: 'removed',
- to: 'to',
- from: 'from',
},
inviteMessage: {
inviteMessageTitle: 'Add message',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 290d80a6f65d..b219021daa0f 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -1569,12 +1569,6 @@ export default {
invitePeople: 'Invitar nuevos miembros',
genericFailureMessage: 'Se produjo un error al invitar al usuario al espacio de trabajo. Vuelva a intentarlo..',
pleaseEnterValidLogin: `Asegúrese de que el correo electrónico o el número de teléfono sean válidos (p. ej. ${CONST.EXAMPLE_PHONE_NUMBER}).`,
- user: 'usuario',
- users: 'usuarios',
- invited: 'invitó',
- removed: 'eliminó',
- to: 'a',
- from: 'de',
},
inviteMessage: {
inviteMessageTitle: 'Añadir un mensaje',
diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts
index 77c34ebdc576..488ff0d9b98a 100644
--- a/src/libs/Localize/index.ts
+++ b/src/libs/Localize/index.ts
@@ -1,7 +1,6 @@
import * as RNLocalize from 'react-native-localize';
import Onyx from 'react-native-onyx';
import Log from '@libs/Log';
-import {MessageElementBase, MessageTextElement} from '@libs/MessageElement';
import Config from '@src/CONFIG';
import CONST from '@src/CONST';
import translations from '@src/languages/translations';
@@ -122,48 +121,15 @@ function translateIfPhraseKey(message: MaybePhraseKey): string {
}
}
-function getPreferredListFormat(): Intl.ListFormat {
- if (!CONJUNCTION_LIST_FORMATS_FOR_LOCALES) {
- init();
- }
-
- return CONJUNCTION_LIST_FORMATS_FOR_LOCALES[BaseLocaleListener.getPreferredLocale()];
-}
-
/**
* Format an array into a string with comma and "and" ("a dog, a cat and a chicken")
*/
-function formatList(components: string[]) {
- const listFormat = getPreferredListFormat();
- return listFormat.format(components);
-}
-
-function formatMessageElementList(elements: readonly E[]): ReadonlyArray {
- const listFormat = getPreferredListFormat();
- const parts = listFormat.formatToParts(elements.map((e) => e.content));
- const resultElements: Array = [];
-
- let nextElementIndex = 0;
- for (const part of parts) {
- if (part.type === 'element') {
- /**
- * The standard guarantees that all input elements will be present in the constructed parts, each exactly
- * once, and without any modifications: https://tc39.es/ecma402/#sec-createpartsfromlist
- */
- const element = elements[nextElementIndex++];
-
- resultElements.push(element);
- } else {
- const literalElement: MessageTextElement = {
- kind: 'text',
- content: part.value,
- };
-
- resultElements.push(literalElement);
- }
+function arrayToString(anArray: string[]) {
+ if (!CONJUNCTION_LIST_FORMATS_FOR_LOCALES) {
+ init();
}
-
- return resultElements;
+ const listFormat = CONJUNCTION_LIST_FORMATS_FOR_LOCALES[BaseLocaleListener.getPreferredLocale()];
+ return listFormat.format(anArray);
}
/**
@@ -173,5 +139,5 @@ function getDevicePreferredLocale(): string {
return RNLocalize.findBestAvailableLanguage([CONST.LOCALES.EN, CONST.LOCALES.ES])?.languageTag ?? CONST.LOCALES.DEFAULT;
}
-export {translate, translateLocal, translateIfPhraseKey, formatList, formatMessageElementList, getDevicePreferredLocale};
+export {translate, translateLocal, translateIfPhraseKey, arrayToString, getDevicePreferredLocale};
export type {PhraseParameters, Phrase, MaybePhraseKey};
diff --git a/src/libs/MessageElement.ts b/src/libs/MessageElement.ts
deleted file mode 100644
index 584d7e1e289a..000000000000
--- a/src/libs/MessageElement.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-type MessageElementBase = {
- readonly kind: string;
- readonly content: string;
-};
-
-type MessageTextElement = {
- readonly kind: 'text';
- readonly content: string;
-} & MessageElementBase;
-
-export type {MessageElementBase, MessageTextElement};
diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx
index a78b38728136..5e8a9f502dc5 100644
--- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx
+++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx
@@ -13,6 +13,7 @@ import type {AuthScreensParamList} from '@navigation/types';
import DemoSetupPage from '@pages/DemoSetupPage';
import NotFoundPage from '@pages/ErrorPage/NotFoundPage';
import DesktopSignInRedirectPage from '@pages/signin/DesktopSignInRedirectPage';
+import SearchInputManager from '@pages/workspace/SearchInputManager';
import useThemeStyles from '@styles/useThemeStyles';
import * as App from '@userActions/App';
import * as Download from '@userActions/Download';
@@ -123,6 +124,8 @@ const modalScreenListeners = {
Modal.setModalVisibility(true);
},
beforeRemove: () => {
+ // Clear search input (WorkspaceInvitePage) when modal is closed
+ SearchInputManager.searchInput = '';
Modal.setModalVisibility(false);
},
};
diff --git a/src/libs/PersonalDetailsUtils.js b/src/libs/PersonalDetailsUtils.js
index 8a4151391453..560480dcec9d 100644
--- a/src/libs/PersonalDetailsUtils.js
+++ b/src/libs/PersonalDetailsUtils.js
@@ -197,18 +197,6 @@ function getFormattedAddress(privatePersonalDetails) {
return formattedAddress.trim().replace(/,$/, '');
}
-/**
- * @param {Object} personalDetail - details object
- * @returns {String | undefined} - The effective display name
- */
-function getEffectiveDisplayName(personalDetail) {
- if (personalDetail) {
- return LocalePhoneNumber.formatPhoneNumber(personalDetail.login) || personalDetail.displayName;
- }
-
- return undefined;
-}
-
export {
getDisplayNameOrDefault,
getPersonalDetailsByIDs,
@@ -218,5 +206,4 @@ export {
getFormattedAddress,
getFormattedStreet,
getStreetLines,
- getEffectiveDisplayName,
};
diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts
index 2bc24c1706e6..6dc735ebd8b7 100644
--- a/src/libs/ReportActionsUtils.ts
+++ b/src/libs/ReportActionsUtils.ts
@@ -5,17 +5,14 @@ import OnyxUtils from 'react-native-onyx/lib/utils';
import {ValueOf} from 'type-fest';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
-import {ActionName, ChangeLog} from '@src/types/onyx/OriginalMessage';
+import {ActionName} from '@src/types/onyx/OriginalMessage';
import Report from '@src/types/onyx/Report';
-import ReportAction, {Message, ReportActions} from '@src/types/onyx/ReportAction';
+import ReportAction, {ReportActions} from '@src/types/onyx/ReportAction';
import {EmptyObject, isEmptyObject} from '@src/types/utils/EmptyObject';
import * as CollectionUtils from './CollectionUtils';
import * as Environment from './Environment/Environment';
import isReportMessageAttachment from './isReportMessageAttachment';
-import * as Localize from './Localize';
import Log from './Log';
-import {MessageElementBase, MessageTextElement} from './MessageElement';
-import * as PersonalDetailsUtils from './PersonalDetailsUtils';
type LastVisibleMessage = {
lastMessageTranslationKey?: string;
@@ -23,19 +20,6 @@ type LastVisibleMessage = {
lastMessageHtml?: string;
};
-type MemberChangeMessageUserMentionElement = {
- readonly kind: 'userMention';
- readonly accountID: number;
-} & MessageElementBase;
-
-type MemberChangeMessageRoomReferenceElement = {
- readonly kind: 'roomReference';
- readonly roomName: string;
- readonly roomID: number;
-} & MessageElementBase;
-
-type MemberChangeMessageElement = MessageTextElement | MemberChangeMessageUserMentionElement | MemberChangeMessageRoomReferenceElement;
-
const allReports: OnyxCollection = {};
Onyx.connect({
key: ONYXKEYS.COLLECTION.REPORT,
@@ -116,7 +100,7 @@ function isReimbursementQueuedAction(reportAction: OnyxEntry) {
return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTQUEUED;
}
-function isMemberChangeAction(reportAction: OnyxEntry) {
+function isChannelLogMemberAction(reportAction: OnyxEntry) {
return (
reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM ||
reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.REMOVE_FROM_ROOM ||
@@ -125,10 +109,6 @@ function isMemberChangeAction(reportAction: OnyxEntry) {
);
}
-function isInviteMemberAction(reportAction: OnyxEntry) {
- return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM;
-}
-
function isReimbursementDeQueuedAction(reportAction: OnyxEntry): boolean {
return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTDEQUEUED;
}
@@ -669,89 +649,6 @@ function isNotifiableReportAction(reportAction: OnyxEntry): boolea
return actions.includes(reportAction.actionName);
}
-function getMemberChangeMessageElements(reportAction: OnyxEntry): readonly MemberChangeMessageElement[] {
- const isInviteAction = isInviteMemberAction(reportAction);
-
- // Currently, we only render messages when members are invited
- const verb = isInviteAction ? Localize.translateLocal('workspace.invite.invited') : Localize.translateLocal('workspace.invite.removed');
-
- const originalMessage = reportAction?.originalMessage as ChangeLog;
- const targetAccountIDs: number[] = originalMessage?.targetAccountIDs ?? [];
- const personalDetails = PersonalDetailsUtils.getPersonalDetailsByIDs(targetAccountIDs, 0);
-
- const mentionElements = targetAccountIDs.map((accountID): MemberChangeMessageUserMentionElement => {
- const personalDetail = personalDetails.find((personal) => personal.accountID === accountID);
- const handleText = PersonalDetailsUtils.getEffectiveDisplayName(personalDetail) ?? Localize.translateLocal('common.hidden');
-
- return {
- kind: 'userMention',
- content: `@${handleText}`,
- accountID,
- };
- });
-
- const buildRoomElements = (): readonly MemberChangeMessageElement[] => {
- const roomName = originalMessage?.roomName;
-
- if (roomName) {
- const preposition = isInviteAction ? ` ${Localize.translateLocal('workspace.invite.to')} ` : ` ${Localize.translateLocal('workspace.invite.from')} `;
-
- if (originalMessage.reportID) {
- return [
- {
- kind: 'text',
- content: preposition,
- },
- {
- kind: 'roomReference',
- roomName,
- roomID: originalMessage.reportID,
- content: roomName,
- },
- ];
- }
- }
-
- return [];
- };
-
- return [
- {
- kind: 'text',
- content: `${verb} `,
- },
- ...Localize.formatMessageElementList(mentionElements),
- ...buildRoomElements(),
- ];
-}
-
-function getMemberChangeMessageFragment(reportAction: OnyxEntry): Message {
- const messageElements: readonly MemberChangeMessageElement[] = getMemberChangeMessageElements(reportAction);
- const html = messageElements
- .map((messageElement) => {
- switch (messageElement.kind) {
- case 'userMention':
- return ``;
- case 'roomReference':
- return `${messageElement.roomName}`;
- default:
- return messageElement.content;
- }
- })
- .join('');
-
- return {
- html: `${html}`,
- text: reportAction?.message ? reportAction?.message[0].text : '',
- type: CONST.REPORT.MESSAGE.TYPE.COMMENT,
- };
-}
-
-function getMemberChangeMessagePlainText(reportAction: OnyxEntry): string {
- const messageElements = getMemberChangeMessageElements(reportAction);
- return messageElements.map((element) => element.content).join('');
-}
-
/**
* Helper method to determine if the provided accountID has made a request on the specified report.
*
@@ -814,9 +711,7 @@ export {
shouldReportActionBeVisibleAsLastAction,
hasRequestFromCurrentAccount,
getFirstVisibleReportActionID,
- isMemberChangeAction,
- getMemberChangeMessageFragment,
- getMemberChangeMessagePlainText,
+ isChannelLogMemberAction,
isReimbursementDeQueuedAction,
};
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index b50b4611a249..1266f145de30 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -18,7 +18,7 @@ import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import {Beta, Login, PersonalDetails, Policy, PolicyTags, Report, ReportAction, Session, Transaction} from '@src/types/onyx';
import {Errors, Icon, PendingAction} from '@src/types/onyx/OnyxCommon';
-import {IOUMessage, OriginalMessageActionName} from '@src/types/onyx/OriginalMessage';
+import {ChangeLog, IOUMessage, OriginalMessageActionName} from '@src/types/onyx/OriginalMessage';
import {Message, ReportActions} from '@src/types/onyx/ReportAction';
import {Receipt, WaypointCollection} from '@src/types/onyx/Transaction';
import DeepValueOf from '@src/types/utils/DeepValueOf';
@@ -3858,7 +3858,7 @@ function getWhisperDisplayNames(participantAccountIDs?: number[]): string | unde
* Show subscript on workspace chats / threads and expense requests
*/
function shouldReportShowSubscript(report: OnyxEntry): boolean {
- if (isArchivedRoom(report)) {
+ if (isArchivedRoom(report) && !isWorkspaceThread(report)) {
return false;
}
@@ -4174,6 +4174,44 @@ function getIOUReportActionDisplayMessage(reportAction: OnyxEntry)
});
}
+/**
+ * Return room channel log display message
+ */
+function getChannelLogMemberMessage(reportAction: OnyxEntry): string {
+ const verb =
+ reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM
+ ? 'invited'
+ : 'removed';
+
+ const mentions = (reportAction?.originalMessage as ChangeLog)?.targetAccountIDs?.map(() => {
+ const personalDetail = allPersonalDetails?.accountID;
+ const displayNameOrLogin = LocalePhoneNumber.formatPhoneNumber(personalDetail?.login ?? '') || (personalDetail?.displayName ?? '') || Localize.translateLocal('common.hidden');
+ return `@${displayNameOrLogin}`;
+ });
+
+ const lastMention = mentions?.pop();
+ let message = '';
+
+ if (mentions?.length === 0) {
+ message = `${verb} ${lastMention}`;
+ } else if (mentions?.length === 1) {
+ message = `${verb} ${mentions?.[0]} and ${lastMention}`;
+ } else {
+ message = `${verb} ${mentions?.join(', ')}, and ${lastMention}`;
+ }
+
+ const roomName = (reportAction?.originalMessage as ChangeLog)?.roomName ?? '';
+ if (roomName) {
+ const preposition =
+ reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM
+ ? ' to'
+ : ' from';
+ message += `${preposition} ${roomName}`;
+ }
+
+ return message;
+}
+
/**
* Checks if a report is a group chat.
*
@@ -4408,6 +4446,7 @@ export {
getReimbursementQueuedActionMessage,
getReimbursementDeQueuedActionMessage,
getPersonalDetailsForAccountID,
+ getChannelLogMemberMessage,
getRoom,
shouldDisableWelcomeMessage,
navigateToPrivateNotes,
diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts
index 6e382e11b49b..bace29e06d28 100644
--- a/src/libs/SidebarUtils.ts
+++ b/src/libs/SidebarUtils.ts
@@ -375,17 +375,17 @@ function getOptionData(
const targetAccountIDs = lastAction?.originalMessage?.targetAccountIDs ?? [];
const verb =
lastAction.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || lastAction.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM
- ? Localize.translate(preferredLocale, 'workspace.invite.invited')
- : Localize.translate(preferredLocale, 'workspace.invite.removed');
- const users = Localize.translate(preferredLocale, targetAccountIDs.length > 1 ? 'workspace.invite.users' : 'workspace.invite.user');
+ ? 'invited'
+ : 'removed';
+ const users = targetAccountIDs.length > 1 ? 'users' : 'user';
result.alternateText = `${verb} ${targetAccountIDs.length} ${users}`;
const roomName = lastAction?.originalMessage?.roomName ?? '';
if (roomName) {
const preposition =
lastAction.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || lastAction.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM
- ? ` ${Localize.translate(preferredLocale, 'workspace.invite.to')}`
- : ` ${Localize.translate(preferredLocale, 'workspace.invite.from')}`;
+ ? ' to'
+ : ' from';
result.alternateText += `${preposition} ${roomName}`;
}
} else if (lastAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && lastActorDisplayName && lastMessageTextFromReport) {
diff --git a/src/libs/ValidationUtils.ts b/src/libs/ValidationUtils.ts
index 0adacac4035a..388020bc0d6d 100644
--- a/src/libs/ValidationUtils.ts
+++ b/src/libs/ValidationUtils.ts
@@ -30,6 +30,17 @@ function validateCardNumber(value: string): boolean {
return sum % 10 === 0;
}
+/**
+ * Validating that this is a valid address (PO boxes are not allowed)
+ */
+function isValidAddress(value: string): boolean {
+ if (!CONST.REGEX.ANY_VALUE.test(value)) {
+ return false;
+ }
+
+ return !CONST.REGEX.PO_BOX.test(value);
+}
+
/**
* Validate date fields
*/
@@ -193,6 +204,40 @@ function isValidWebsite(url: string): boolean {
return new RegExp(`^${URL_REGEX_WITH_REQUIRED_PROTOCOL}$`, 'i').test(url) && isLowerCase;
}
+function validateIdentity(identity: Record): Record {
+ const requiredFields = ['firstName', 'lastName', 'street', 'city', 'zipCode', 'state', 'ssnLast4', 'dob'];
+ const errors: Record = {};
+
+ // Check that all required fields are filled
+ requiredFields.forEach((fieldName) => {
+ if (isRequiredFulfilled(identity[fieldName])) {
+ return;
+ }
+ errors[fieldName] = true;
+ });
+
+ if (!isValidAddress(identity.street)) {
+ errors.street = true;
+ }
+
+ if (!isValidZipCode(identity.zipCode)) {
+ errors.zipCode = true;
+ }
+
+ // dob field has multiple validations/errors, we are handling it temporarily like this.
+ if (!isValidDate(identity.dob) || !meetsMaximumAgeRequirement(identity.dob)) {
+ errors.dob = true;
+ } else if (!meetsMinimumAgeRequirement(identity.dob)) {
+ errors.dobAge = true;
+ }
+
+ if (!isValidSSNLastFour(identity.ssnLast4)) {
+ errors.ssnLast4 = true;
+ }
+
+ return errors;
+}
+
function isValidUSPhone(phoneNumber = '', isCountryCodeOptional?: boolean): boolean {
const phone = phoneNumber || '';
const regionCode = isCountryCodeOptional ? CONST.COUNTRY.US : undefined;
@@ -259,51 +304,6 @@ function isValidPersonName(value: string) {
return /^[^\d^!#$%*=<>;{}"]+$/.test(value);
}
-/**
- * Validating that this is a valid address (PO boxes are not allowed)
- */
-function isValidAddress(value: string): boolean {
- if (!isValidLegalName(value)) {
- return false;
- }
-
- return !CONST.REGEX.PO_BOX.test(value);
-}
-
-function validateIdentity(identity: Record): Record {
- const requiredFields = ['firstName', 'lastName', 'street', 'city', 'zipCode', 'state', 'ssnLast4', 'dob'];
- const errors: Record = {};
-
- // Check that all required fields are filled
- requiredFields.forEach((fieldName) => {
- if (isRequiredFulfilled(identity[fieldName])) {
- return;
- }
- errors[fieldName] = true;
- });
-
- if (!isValidAddress(identity.street)) {
- errors.street = true;
- }
-
- if (!isValidZipCode(identity.zipCode)) {
- errors.zipCode = true;
- }
-
- // dob field has multiple validations/errors, we are handling it temporarily like this.
- if (!isValidDate(identity.dob) || !meetsMaximumAgeRequirement(identity.dob)) {
- errors.dob = true;
- } else if (!meetsMinimumAgeRequirement(identity.dob)) {
- errors.dobAge = true;
- }
-
- if (!isValidSSNLastFour(identity.ssnLast4)) {
- errors.ssnLast4 = true;
- }
-
- return errors;
-}
-
/**
* Checks if the provided string includes any of the provided reserved words
*/
@@ -384,6 +384,7 @@ export {
meetsMinimumAgeRequirement,
meetsMaximumAgeRequirement,
getAgeRequirementError,
+ isValidAddress,
isValidDate,
isValidPastDate,
isValidSecurityCode,
@@ -395,6 +396,7 @@ export {
getFieldRequiredErrors,
isValidUSPhone,
isValidWebsite,
+ validateIdentity,
isValidTwoFactorCode,
isNumericWithSpecialChars,
isValidRoutingNumber,
@@ -407,8 +409,6 @@ export {
isValidValidateCode,
isValidDisplayName,
isValidLegalName,
- isValidAddress,
- validateIdentity,
doesContainReservedWord,
isNumeric,
isValidAccountRoute,
diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js
index 6161fd2066ff..e5bac3182b0d 100644
--- a/src/libs/actions/Report.js
+++ b/src/libs/actions/Report.js
@@ -2044,6 +2044,11 @@ function leaveRoom(reportID, isWorkspaceMemberLeavingWorkspaceRoom = false) {
value: {
stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
statusNum: CONST.REPORT.STATUS.CLOSED,
+ chatType: report.chatType,
+ parentReportID: report.parentReportID,
+ parentReportActionID: report.parentReportActionID,
+ policyID: report.policyID,
+ type: report.type,
},
},
];
diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js
index d7d622d309d6..f1d62eef89ae 100644
--- a/src/pages/ReimbursementAccount/CompanyStep.js
+++ b/src/pages/ReimbursementAccount/CompanyStep.js
@@ -88,10 +88,6 @@ function CompanyStep({reimbursementAccount, reimbursementAccountDraft, getDefaul
];
const errors = ValidationUtils.getFieldRequiredErrors(values, requiredFields);
- if (values.companyName && !ValidationUtils.isValidLegalName(values.companyName)) {
- errors.companyName = 'bankAccount.error.companyName';
- }
-
if (values.addressStreet && !ValidationUtils.isValidAddress(values.addressStreet)) {
errors.addressStreet = 'bankAccount.error.addressStreet';
}
@@ -100,10 +96,6 @@ function CompanyStep({reimbursementAccount, reimbursementAccountDraft, getDefaul
errors.addressZipCode = 'bankAccount.error.zipCode';
}
- if (values.addressCity && !ValidationUtils.isValidLegalName(values.addressCity)) {
- errors.addressCity = 'bankAccount.error.addressCity';
- }
-
if (values.companyPhone && !ValidationUtils.isValidUSPhone(values.companyPhone, true)) {
errors.companyPhone = 'bankAccount.error.phoneNumber';
}
diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js
index 6c645bc87486..4f35926c5957 100644
--- a/src/pages/home/report/ContextMenu/ContextMenuActions.js
+++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js
@@ -281,8 +281,8 @@ export default [
} else if (ReportActionsUtils.isMoneyRequestAction(reportAction)) {
const displayMessage = ReportUtils.getIOUReportActionDisplayMessage(reportAction);
Clipboard.setString(displayMessage);
- } else if (ReportActionsUtils.isMemberChangeAction(reportAction)) {
- const logMessage = ReportActionsUtils.getMemberChangeMessagePlainText(reportAction);
+ } else if (ReportActionsUtils.isChannelLogMemberAction(reportAction)) {
+ const logMessage = ReportUtils.getChannelLogMemberMessage(reportAction);
Clipboard.setString(logMessage);
} else if (content) {
const parser = new ExpensiMark();
diff --git a/src/pages/home/report/ReportActionItemDraft.js b/src/pages/home/report/ReportActionItemDraft.js
deleted file mode 100644
index 9b3839aa78f2..000000000000
--- a/src/pages/home/report/ReportActionItemDraft.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import {View} from 'react-native';
-import useThemeStyles from '@styles/useThemeStyles';
-
-const propTypes = {
- /** Children view component for this action item */
- children: PropTypes.node.isRequired,
-};
-
-function ReportActionItemDraft(props) {
- const styles = useThemeStyles();
- return (
-
- {props.children}
-
- );
-}
-
-ReportActionItemDraft.propTypes = propTypes;
-ReportActionItemDraft.displayName = 'ReportActionItemDraft';
-export default ReportActionItemDraft;
diff --git a/src/pages/home/report/ReportActionItemDraft.tsx b/src/pages/home/report/ReportActionItemDraft.tsx
new file mode 100644
index 000000000000..b46af5401ee4
--- /dev/null
+++ b/src/pages/home/report/ReportActionItemDraft.tsx
@@ -0,0 +1,17 @@
+import React from 'react';
+import {View} from 'react-native';
+import useThemeStyles from '@styles/useThemeStyles';
+import ChildrenProps from '@src/types/utils/ChildrenProps';
+
+function ReportActionItemDraft({children}: ChildrenProps) {
+ const styles = useThemeStyles();
+
+ return (
+
+ {children}
+
+ );
+}
+
+ReportActionItemDraft.displayName = 'ReportActionItemDraft';
+export default ReportActionItemDraft;
diff --git a/src/pages/home/report/ReportActionItemMessage.js b/src/pages/home/report/ReportActionItemMessage.js
index 46e0438f250a..2265530f29a1 100644
--- a/src/pages/home/report/ReportActionItemMessage.js
+++ b/src/pages/home/report/ReportActionItemMessage.js
@@ -8,7 +8,6 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import * as ReportUtils from '@libs/ReportUtils';
import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
-import TextCommentFragment from './comment/TextCommentFragment';
import ReportActionItemFragment from './ReportActionItemFragment';
import reportActionPropTypes from './reportActionPropTypes';
@@ -41,20 +40,6 @@ function ReportActionItemMessage(props) {
const styles = useThemeStyles();
const fragments = _.compact(props.action.previousMessage || props.action.message);
const isIOUReport = ReportActionsUtils.isMoneyRequestAction(props.action);
- if (ReportActionsUtils.isMemberChangeAction(props.action)) {
- const fragment = ReportActionsUtils.getMemberChangeMessageFragment(props.action);
-
- return (
-
- );
- }
-
let iouMessage;
if (isIOUReport) {
const iouReportID = lodashGet(props.action, 'originalMessage.IOUReportID');
diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js
index 317fa86f23f3..183665891929 100644
--- a/src/pages/home/report/ReportActionsList.js
+++ b/src/pages/home/report/ReportActionsList.js
@@ -156,7 +156,7 @@ function ReportActionsList({
const readActionSkipped = useRef(false);
const hasHeaderRendered = useRef(false);
const hasFooterRendered = useRef(false);
- const reportActionSize = useRef(sortedReportActions.length);
+ const lastVisibleActionCreatedRef = useRef(report.lastVisibleActionCreated);
const lastReadTimeRef = useRef(report.lastReadTime);
const linkedReportActionID = lodashGet(route, 'params.reportActionID', '');
@@ -198,15 +198,15 @@ function ReportActionsList({
}
}
- if (currentUnreadMarker || reportActionSize.current === sortedReportActions.length) {
+ if (currentUnreadMarker || lastVisibleActionCreatedRef.current === report.lastVisibleActionCreated) {
return;
}
cacheUnreadMarkers.delete(report.reportID);
- reportActionSize.current = sortedReportActions.length;
+ lastVisibleActionCreatedRef.current = report.lastVisibleActionCreated;
setCurrentUnreadMarker(null);
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [sortedReportActions.length, report.reportID]);
+ }, [report.lastVisibleActionCreated, report.reportID]);
useEffect(() => {
if (!userActiveSince.current || report.reportID !== prevReportID) {
@@ -339,7 +339,10 @@ function ReportActionsList({
shouldDisplay = isCurrentMessageUnread && (!nextMessage || !isMessageUnread(nextMessage, lastReadTimeRef.current));
if (shouldDisplay && !messageManuallyMarkedUnread) {
const isWithinVisibleThreshold = scrollingVerticalOffset.current < MSG_VISIBLE_THRESHOLD ? reportAction.created < userActiveSince.current : true;
- shouldDisplay = reportAction.actorAccountID !== Report.getCurrentUserAccountID() && isWithinVisibleThreshold;
+ // Prevent displaying a new marker line when report action is of type "REPORTPREVIEW" and last actor is the current user
+ shouldDisplay =
+ (ReportActionsUtils.isReportPreviewAction(reportAction) ? !reportAction.childLastActorAccountID : reportAction.actorAccountID) !== Report.getCurrentUserAccountID() &&
+ isWithinVisibleThreshold;
}
if (shouldDisplay) {
cacheUnreadMarkers.set(report.reportID, reportAction.reportActionID);
diff --git a/src/pages/workspace/SearchInputManager.js b/src/pages/workspace/SearchInputManager.js
new file mode 100644
index 000000000000..599f7cca6cf9
--- /dev/null
+++ b/src/pages/workspace/SearchInputManager.js
@@ -0,0 +1,5 @@
+// eslint-disable-next-line prefer-const
+let searchInput = '';
+export default {
+ searchInput,
+};
diff --git a/src/pages/workspace/WorkspaceInvitePage.js b/src/pages/workspace/WorkspaceInvitePage.js
index 4bef69c82414..b18c234ea44d 100644
--- a/src/pages/workspace/WorkspaceInvitePage.js
+++ b/src/pages/workspace/WorkspaceInvitePage.js
@@ -21,6 +21,7 @@ import * as Policy from '@userActions/Policy';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
+import SearchInputManager from './SearchInputManager';
import {policyDefaultProps, policyPropTypes} from './withPolicy';
import withPolicyAndFullscreenLoading from './withPolicyAndFullscreenLoading';
@@ -75,6 +76,13 @@ function WorkspaceInvitePage(props) {
Policy.openWorkspaceInvitePage(props.route.params.policyID, _.keys(policyMemberEmailsToAccountIDs));
};
+ useEffect(() => {
+ if (!SearchInputManager.searchInput) {
+ return;
+ }
+ setSearchTerm(SearchInputManager.searchInput);
+ }, []);
+
useEffect(() => {
Policy.clearErrors(props.route.params.policyID);
openWorkspaceInvitePage();
@@ -255,7 +263,10 @@ function WorkspaceInvitePage(props) {
sections={sections}
textInputLabel={translate('optionsSelector.nameEmailOrPhoneNumber')}
textInputValue={searchTerm}
- onChangeText={setSearchTerm}
+ onChangeText={(value) => {
+ SearchInputManager.searchInput = value;
+ setSearchTerm(value);
+ }}
headerMessage={headerMessage}
onSelectRow={toggleOption}
onConfirm={inviteUser}
diff --git a/src/pages/workspace/WorkspaceMembersPage.js b/src/pages/workspace/WorkspaceMembersPage.js
index 09b613350705..d5cdbcfc69d8 100644
--- a/src/pages/workspace/WorkspaceMembersPage.js
+++ b/src/pages/workspace/WorkspaceMembersPage.js
@@ -1,3 +1,4 @@
+import {useIsFocused} from '@react-navigation/native';
import lodashGet from 'lodash/get';
import PropTypes from 'prop-types';
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
@@ -32,6 +33,7 @@ import * as Policy from '@userActions/Policy';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
+import SearchInputManager from './SearchInputManager';
import {policyDefaultProps, policyPropTypes} from './withPolicy';
import withPolicyAndFullscreenLoading from './withPolicyAndFullscreenLoading';
@@ -85,6 +87,17 @@ function WorkspaceMembersPage(props) {
const isOfflineAndNoMemberDataAvailable = _.isEmpty(props.policyMembers) && props.network.isOffline;
const prevPersonalDetails = usePrevious(props.personalDetails);
+ const isFocusedScreen = useIsFocused();
+
+ useEffect(() => {
+ if (!SearchInputManager.searchInput) {
+ return;
+ }
+ setSearchValue(SearchInputManager.searchInput);
+ }, [isFocusedScreen]);
+
+ useEffect(() => () => (SearchInputManager.searchInput = ''), []);
+
/**
* Get filtered personalDetails list with current policyMembers
* @param {Object} policyMembers
@@ -466,7 +479,10 @@ function WorkspaceMembersPage(props) {
sections={[{data, indexOffset: 0, isDisabled: false}]}
textInputLabel={props.translate('optionsSelector.findMember')}
textInputValue={searchValue}
- onChangeText={setSearchValue}
+ onChangeText={(value) => {
+ SearchInputManager.searchInput = value;
+ setSearchValue(value);
+ }}
headerMessage={getHeaderMessage()}
headerContent={getHeaderContent()}
onSelectRow={(item) => toggleUser(item.accountID)}
diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts
index 72ea275e3ba3..f76fbd5ffd7d 100644
--- a/src/types/onyx/OriginalMessage.ts
+++ b/src/types/onyx/OriginalMessage.ts
@@ -140,7 +140,6 @@ type ChronosOOOTimestamp = {
type ChangeLog = {
targetAccountIDs?: number[];
roomName?: string;
- reportID?: number;
};
type ChronosOOOEvent = {
diff --git a/src/types/onyx/ReportAction.ts b/src/types/onyx/ReportAction.ts
index a9ff44ad09e5..a0e90f4e9c34 100644
--- a/src/types/onyx/ReportAction.ts
+++ b/src/types/onyx/ReportAction.ts
@@ -115,12 +115,13 @@ type ReportActionBase = {
childStateNum?: ValueOf;
childLastReceiptTransactionIDs?: string;
childLastMoneyRequestComment?: string;
+ childLastActorAccountID?: number;
timestamp?: number;
reportActionTimestamp?: number;
childMoneyRequestCount?: number;
isFirstItem?: boolean;
- /** Informations about attachments of report action */
+ /** Information about attachments of report action */
attachmentInfo?: (File & {source: string; uri: string}) | Record;
/** Receipt tied to report action */
diff --git a/tests/unit/LocalizeTests.js b/tests/unit/LocalizeTests.js
index 7693a0a4a88d..4c89d587fc06 100644
--- a/tests/unit/LocalizeTests.js
+++ b/tests/unit/LocalizeTests.js
@@ -15,7 +15,7 @@ describe('localize', () => {
afterEach(() => Onyx.clear());
- describe('formatList', () => {
+ describe('arrayToString', () => {
test.each([
[
[],
@@ -52,9 +52,9 @@ describe('localize', () => {
[CONST.LOCALES.ES]: 'rory, vit e ionatan',
},
],
- ])('formatList(%s)', (input, {[CONST.LOCALES.DEFAULT]: expectedOutput, [CONST.LOCALES.ES]: expectedOutputES}) => {
- expect(Localize.formatList(input)).toBe(expectedOutput);
- return Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.ES).then(() => expect(Localize.formatList(input)).toBe(expectedOutputES));
+ ])('arrayToSpokenList(%s)', (input, {[CONST.LOCALES.DEFAULT]: expectedOutput, [CONST.LOCALES.ES]: expectedOutputES}) => {
+ expect(Localize.arrayToString(input)).toBe(expectedOutput);
+ return Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.ES).then(() => expect(Localize.arrayToString(input)).toBe(expectedOutputES));
});
});
});