, policy: OnyxEntry): boolean {
+ return reportField?.type === 'formula' && reportField?.fieldID === CONST.REPORT_FIELD_TITLE_FIELD_ID;
+}
+
+/**
+ * Given a report field, check if the field can be edited or not.
+ * For title fields, its considered disabled if `deletable` prop is `true` (https://github.com/Expensify/App/issues/35043#issuecomment-1911275433)
+ * For non title fields, its considered disabled if:
+ * 1. The user is not admin of the report
+ * 2. Report is settled or it is closed
+ */
+function isReportFieldDisabled(report: OnyxEntry, reportField: OnyxEntry, policy: OnyxEntry): boolean {
+ const isReportSettled = isSettled(report?.reportID);
+ const isReportClosed = report?.statusNum === CONST.REPORT.STATUS_NUM.CLOSED;
+ const isTitleField = isReportFieldOfTypeTitle(reportField);
+ const isAdmin = isPolicyAdmin(report?.policyID ?? '', {[`${ONYXKEYS.COLLECTION.POLICY}${policy?.id ?? ''}`]: policy});
+ return isTitleField ? !reportField?.deletable : !isAdmin && (isReportSettled || isReportClosed);
+}
+
+/**
+ * Given a set of report fields, return the field of type formula
+ */
+function getFormulaTypeReportField(reportFields: PolicyReportFields) {
+ return Object.values(reportFields).find((field) => field.type === 'formula');
+}
+
+/**
+ * Get the report fields attached to the policy given policyID
+ */
+function getReportFieldsByPolicyID(policyID: string) {
+ return Object.entries(allPolicyReportFields ?? {}).find(([key]) => key.replace(ONYXKEYS.COLLECTION.POLICY_REPORT_FIELDS, '') === policyID)?.[1];
+}
+
+/**
+ * Get the report fields that we should display a MoneyReportView gets opened
+ */
+
+function getAvailableReportFields(report: Report, policyReportFields: PolicyReportField[]): PolicyReportField[] {
+ // Get the report fields that are attached to a report. These will persist even if a field is deleted from the policy.
+ const reportFields = Object.values(report.reportFields ?? {});
+ const reportIsSettled = isSettled(report.reportID);
+
+ // If the report is settled, we don't want to show any new field that gets added to the policy.
+ if (reportIsSettled) {
+ return reportFields;
+ }
+
+ // If the report is unsettled, we want to merge the new fields that get added to the policy with the fields that
+ // are attached to the report.
+ const mergedFieldIds = Array.from(new Set([...policyReportFields.map(({fieldID}) => fieldID), ...reportFields.map(({fieldID}) => fieldID)]));
+ return mergedFieldIds.map((id) => report?.reportFields?.[id] ?? policyReportFields.find(({fieldID}) => fieldID === id)) as PolicyReportField[];
+}
+
/**
* Get the title for an IOU or expense chat which will be showing the payer and the amount
*/
function getMoneyRequestReportName(report: OnyxEntry, policy: OnyxEntry | undefined = undefined): string {
+ const isReportSettled = isSettled(report?.reportID ?? '');
+ const reportFields = isReportSettled ? report?.reportFields : getReportFieldsByPolicyID(report?.policyID ?? '');
+ const titleReportField = getFormulaTypeReportField(reportFields ?? {});
+
+ if (titleReportField && report?.reportName && Permissions.canUseReportFields(allBetas ?? [])) {
+ return report.reportName;
+ }
+
const moneyRequestTotal = getMoneyRequestSpendBreakdown(report).totalDisplaySpend;
const formattedAmount = CurrencyUtils.convertToDisplayString(moneyRequestTotal, report?.currency, hasOnlyDistanceRequestTransactions(report?.reportID));
const payerOrApproverName = isExpenseReport(report) ? getPolicyName(report, false, policy) : getDisplayNameForParticipant(report?.managerID) ?? '';
@@ -3604,6 +3694,8 @@ function shouldReportBeInOptionList({
// Exclude reports that have no data because there wouldn't be anything to show in the option item.
// This can happen if data is currently loading from the server or a report is in various stages of being created.
// This can also happen for anyone accessing a public room or archived room for which they don't have access to the underlying policy.
+ // Optionally exclude reports that do not belong to currently active workspace
+
if (
!report?.reportID ||
!report?.type ||
@@ -4538,32 +4630,6 @@ function navigateToPrivateNotes(report: Report, session: Session) {
Navigation.navigate(ROUTES.PRIVATE_NOTES_LIST.getRoute(report.reportID));
}
-/**
- * Given a report field and a report, get the title of the field.
- * This is specially useful when we have a report field of type formula.
- */
-function getReportFieldTitle(report: OnyxEntry, reportField: PolicyReportField): string {
- const value = report?.reportFields?.[reportField.fieldID] ?? reportField.defaultValue;
-
- if (reportField.type !== 'formula') {
- return value;
- }
-
- return value.replaceAll(CONST.REGEX.REPORT_FIELD_TITLE, (match, property) => {
- if (report && property in report) {
- return report[property as keyof Report]?.toString() ?? match;
- }
- return match;
- });
-}
-
-/**
- * Given a report field, check if the field is for the report title.
- */
-function isReportFieldOfTypeTitle(reportField: PolicyReportField): boolean {
- return reportField.type === 'formula' && reportField.fieldID === CONST.REPORT_FIELD_TITLE_FIELD_ID;
-}
-
/**
* Checks if thread replies should be displayed
*/
@@ -4790,7 +4856,6 @@ export {
canEditWriteCapability,
hasSmartscanError,
shouldAutoFocusOnKeyPress,
- getReportFieldTitle,
shouldDisplayThreadReplies,
shouldDisableThread,
doesReportBelongToWorkspace,
@@ -4798,6 +4863,8 @@ export {
isReportParticipant,
isValidReport,
isReportFieldOfTypeTitle,
+ isReportFieldDisabled,
+ getAvailableReportFields,
};
export type {
diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts
index 9119907e9393..02c32e089016 100644
--- a/src/libs/SidebarUtils.ts
+++ b/src/libs/SidebarUtils.ts
@@ -114,7 +114,7 @@ function getOrderedReportIDs(
// Generate a unique cache key based on the function arguments
const cachedReportsKey = JSON.stringify(
- [currentReportId, allReports, betas, policies, priorityMode, reportActionCount],
+ [currentReportId, allReports, betas, policies, priorityMode, reportActionCount, currentPolicyID, policyMemberAccountIDs],
// Exclude some properties not to overwhelm a cached key value with huge data, which we don't need to store in a cacheKey
(key, value: unknown) => (['participantAccountIDs', 'participants', 'lastMessageText', 'visibleChatMemberAccountIDs'].includes(key) ? undefined : value),
);
@@ -173,7 +173,7 @@ function getOrderedReportIDs(
const archivedReports: Report[] = [];
if (currentPolicyID || policyMemberAccountIDs.length > 0) {
- reportsToDisplay = reportsToDisplay.filter((report) => ReportUtils.doesReportBelongToWorkspace(report, currentPolicyID, policyMemberAccountIDs));
+ reportsToDisplay = reportsToDisplay.filter((report) => ReportUtils.doesReportBelongToWorkspace(report, policyMemberAccountIDs, currentPolicyID));
}
// There are a few properties that need to be calculated for the report which are used when sorting reports.
reportsToDisplay.forEach((report) => {
@@ -373,7 +373,7 @@ function getOptionData({
? 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');
- result.alternateText = `${verb} ${targetAccountIDs.length} ${users}`;
+ result.alternateText = `${lastActorDisplayName} ${verb} ${targetAccountIDs.length} ${users}`.trim();
const roomName = lastAction?.originalMessage?.roomName ?? '';
if (roomName) {
diff --git a/src/libs/ValidationUtils.ts b/src/libs/ValidationUtils.ts
index 7eff51c354df..9b2b1d01b80b 100644
--- a/src/libs/ValidationUtils.ts
+++ b/src/libs/ValidationUtils.ts
@@ -1,4 +1,5 @@
import {addYears, endOfMonth, format, isAfter, isBefore, isSameDay, isValid, isWithinInterval, parse, parseISO, startOfDay, subYears} from 'date-fns';
+import Str from 'expensify-common/lib/str';
import {URL_REGEX_WITH_REQUIRED_PROTOCOL} from 'expensify-common/lib/Url';
import isDate from 'lodash/isDate';
import isEmpty from 'lodash/isEmpty';
@@ -265,6 +266,12 @@ function isValidUSPhone(phoneNumber = '', isCountryCodeOptional?: boolean): bool
const phone = phoneNumber || '';
const regionCode = isCountryCodeOptional ? CONST.COUNTRY.US : undefined;
+ // When we pass regionCode as an option to parsePhoneNumber it wrongly assumes inputs like '=15123456789' as valid
+ // so we need to check if it is a valid phone.
+ if (regionCode && !Str.isValidPhone(phone)) {
+ return false;
+ }
+
const parsedPhoneNumber = parsePhoneNumber(phone, {regionCode});
return parsedPhoneNumber.possible && parsedPhoneNumber.regionCode === CONST.COUNTRY.US;
}
diff --git a/src/libs/WorkspacesSettingsUtils.ts b/src/libs/WorkspacesSettingsUtils.ts
new file mode 100644
index 000000000000..c41393cb75f7
--- /dev/null
+++ b/src/libs/WorkspacesSettingsUtils.ts
@@ -0,0 +1,205 @@
+import Onyx from 'react-native-onyx';
+import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
+import type {ValueOf} from 'type-fest';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import type {Policy, PolicyMembers, ReimbursementAccount, Report} from '@src/types/onyx';
+import * as OptionsListUtils from './OptionsListUtils';
+import {hasCustomUnitsError, hasPolicyError, hasPolicyMemberError} from './PolicyUtils';
+import * as ReportActionsUtils from './ReportActionsUtils';
+import * as ReportUtils from './ReportUtils';
+
+type CheckingMethod = () => boolean;
+
+let allReports: OnyxCollection;
+
+type BrickRoad = ValueOf | undefined;
+
+Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (value) => (allReports = value),
+});
+
+let allPolicies: OnyxCollection;
+
+Onyx.connect({
+ key: ONYXKEYS.COLLECTION.POLICY,
+ waitForCollectionCallback: true,
+ callback: (value) => (allPolicies = value),
+});
+
+let allPolicyMembers: OnyxCollection;
+
+Onyx.connect({
+ key: ONYXKEYS.COLLECTION.POLICY_MEMBERS,
+ waitForCollectionCallback: true,
+ callback: (val) => {
+ allPolicyMembers = val;
+ },
+});
+
+let reimbursementAccount: OnyxEntry;
+
+Onyx.connect({
+ key: ONYXKEYS.REIMBURSEMENT_ACCOUNT,
+ callback: (val) => {
+ reimbursementAccount = val;
+ },
+});
+
+/**
+ * @param report
+ * @returns BrickRoad for the policy passed as a param
+ */
+const getBrickRoadForPolicy = (report: Report): BrickRoad => {
+ const reportActions = ReportActionsUtils.getAllReportActions(report.reportID);
+ const reportErrors = OptionsListUtils.getAllReportErrors(report, reportActions);
+ const doesReportContainErrors = Object.keys(reportErrors ?? {}).length !== 0 ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined;
+ if (doesReportContainErrors) {
+ return CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR;
+ }
+
+ // To determine if the report requires attention from the current user, we need to load the parent report action
+ let itemParentReportAction = {};
+ if (report.parentReportID) {
+ const itemParentReportActions = ReportActionsUtils.getAllReportActions(report.parentReportID);
+ itemParentReportAction = report.parentReportActionID ? itemParentReportActions[report.parentReportActionID] : {};
+ }
+ const reportOption = {...report, isUnread: ReportUtils.isUnread(report), isUnreadWithMention: ReportUtils.isUnreadWithMention(report)};
+ const shouldShowGreenDotIndicator = ReportUtils.requiresAttentionFromCurrentUser(reportOption, itemParentReportAction);
+ return shouldShowGreenDotIndicator ? CONST.BRICK_ROAD_INDICATOR_STATUS.INFO : undefined;
+};
+
+function hasGlobalWorkspaceSettingsRBR(policies: OnyxCollection, policyMembers: OnyxCollection) {
+ const cleanPolicies = Object.fromEntries(Object.entries(policies ?? {}).filter(([, policy]) => !!policy));
+
+ const cleanAllPolicyMembers = Object.fromEntries(Object.entries(policyMembers ?? {}).filter(([, policyMemberValues]) => !!policyMemberValues));
+ const errorCheckingMethods: CheckingMethod[] = [
+ () => Object.values(cleanPolicies).some(hasPolicyError),
+ () => Object.values(cleanPolicies).some(hasCustomUnitsError),
+ () => Object.values(cleanAllPolicyMembers).some(hasPolicyMemberError),
+ () => Object.keys(reimbursementAccount?.errors ?? {}).length > 0,
+ ];
+
+ return errorCheckingMethods.some((errorCheckingMethod) => errorCheckingMethod());
+}
+
+function hasWorkspaceSettingsRBR(policy: Policy) {
+ const policyMemberError = allPolicyMembers ? hasPolicyMemberError(allPolicyMembers[`${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policy.id}`]) : false;
+
+ return Object.keys(reimbursementAccount?.errors ?? {}).length > 0 || hasPolicyError(policy) || hasCustomUnitsError(policy) || policyMemberError;
+}
+
+function getChatTabBrickRoad(policyID?: string): BrickRoad | undefined {
+ if (!allReports) {
+ return undefined;
+ }
+
+ // If policyID is undefined, then all reports are checked whether they contain any brick road
+ const policyReports = policyID ? Object.values(allReports).filter((report) => report?.policyID === policyID) : Object.values(allReports);
+
+ let hasChatTabGBR = false;
+
+ const hasChatTabRBR = policyReports.some((report) => {
+ const brickRoad = report ? getBrickRoadForPolicy(report) : undefined;
+ if (!hasChatTabGBR && brickRoad === CONST.BRICK_ROAD_INDICATOR_STATUS.INFO) {
+ hasChatTabGBR = true;
+ }
+ return brickRoad === CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR;
+ });
+
+ if (hasChatTabRBR) {
+ return CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR;
+ }
+
+ if (hasChatTabGBR) {
+ return CONST.BRICK_ROAD_INDICATOR_STATUS.INFO;
+ }
+
+ return undefined;
+}
+
+function checkIfWorkspaceSettingsTabHasRBR(policyID?: string) {
+ if (!policyID) {
+ return hasGlobalWorkspaceSettingsRBR(allPolicies, allPolicyMembers);
+ }
+ const policy = allPolicies ? allPolicies[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`] : null;
+
+ if (!policy) {
+ return false;
+ }
+
+ return hasWorkspaceSettingsRBR(policy);
+}
+
+/**
+ * @returns a map where the keys are policyIDs and the values are BrickRoads for each policy
+ */
+function getWorkspacesBrickRoads(): Record {
+ if (!allReports) {
+ return {};
+ }
+
+ // The key in this map is the workspace id
+ const workspacesBrickRoadsMap: Record = {};
+
+ Object.values(allPolicies ?? {}).forEach((policy) => {
+ if (!policy) {
+ return;
+ }
+
+ if (hasWorkspaceSettingsRBR(policy)) {
+ workspacesBrickRoadsMap[policy.id] = CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR;
+ }
+ });
+
+ Object.values(allReports).forEach((report) => {
+ const policyID = report?.policyID ?? CONST.POLICY.EMPTY;
+ if (!report || workspacesBrickRoadsMap[policyID] === CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR) {
+ return;
+ }
+ const workspaceBrickRoad = getBrickRoadForPolicy(report);
+
+ if (!workspaceBrickRoad && !!workspacesBrickRoadsMap[policyID]) {
+ return;
+ }
+
+ workspacesBrickRoadsMap[policyID] = workspaceBrickRoad;
+ });
+
+ return workspacesBrickRoadsMap;
+}
+
+/**
+ * @returns a map where the keys are policyIDs and the values are truthy booleans if policy has unread content
+ */
+function getWorkspacesUnreadStatuses(): Record {
+ if (!allReports) {
+ return {};
+ }
+
+ const workspacesUnreadStatuses: Record = {};
+
+ Object.values(allReports).forEach((report) => {
+ const policyID = report?.policyID;
+ if (!policyID || workspacesUnreadStatuses[policyID]) {
+ return;
+ }
+
+ workspacesUnreadStatuses[policyID] = ReportUtils.isUnread(report);
+ });
+
+ return workspacesUnreadStatuses;
+}
+
+export {
+ getBrickRoadForPolicy,
+ getWorkspacesBrickRoads,
+ getWorkspacesUnreadStatuses,
+ hasGlobalWorkspaceSettingsRBR,
+ checkIfWorkspaceSettingsTabHasRBR,
+ hasWorkspaceSettingsRBR,
+ getChatTabBrickRoad,
+};
+export type {BrickRoad};
diff --git a/src/libs/actions/App.ts b/src/libs/actions/App.ts
index 930c31fde287..a03eccfe477a 100644
--- a/src/libs/actions/App.ts
+++ b/src/libs/actions/App.ts
@@ -122,7 +122,7 @@ function setLocale(locale: Locale) {
function setLocaleAndNavigate(locale: Locale) {
setLocale(locale);
- Navigation.goBack(ROUTES.SETTINGS_PREFERENCES);
+ Navigation.goBack();
}
function setSidebarLoaded() {
@@ -504,6 +504,10 @@ function handleRestrictedEvent(eventName: string) {
API.write(WRITE_COMMANDS.HANDLE_RESTRICTED_EVENT, parameters);
}
+function updateLastVisitedPath(path: string) {
+ Onyx.merge(ONYXKEYS.LAST_VISITED_PATH, path);
+}
+
export {
setLocale,
setLocaleAndNavigate,
@@ -521,4 +525,5 @@ export {
finalReconnectAppAfterActivatingReliableUpdates,
savePolicyDraftByNewWorkspace,
createWorkspaceWithPolicyDraftAndNavigateToIt,
+ updateLastVisitedPath,
};
diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js
index a9e1b09ed984..f2bdb097497e 100644
--- a/src/libs/actions/IOU.js
+++ b/src/libs/actions/IOU.js
@@ -349,7 +349,7 @@ function getOutstandingChildRequest(policy, needsToBeManuallySubmitted) {
* @param {Array} optimisticPolicyRecentlyUsedCategories
* @param {Array} optimisticPolicyRecentlyUsedTags
* @param {boolean} isNewChatReport
- * @param {boolean} isNewIOUReport
+ * @param {boolean} shouldCreateNewMoneyRequestReport
* @param {Object} policy - May be undefined, an empty object, or an object matching the Policy type (src/types/onyx/Policy.ts)
* @param {Array} policyTags
* @param {Array} policyCategories
@@ -368,7 +368,7 @@ function buildOnyxDataForMoneyRequest(
optimisticPolicyRecentlyUsedCategories,
optimisticPolicyRecentlyUsedTags,
isNewChatReport,
- isNewIOUReport,
+ shouldCreateNewMoneyRequestReport,
policy,
policyTags,
policyCategories,
@@ -391,14 +391,14 @@ function buildOnyxDataForMoneyRequest(
},
},
{
- onyxMethod: isNewIOUReport ? Onyx.METHOD.SET : Onyx.METHOD.MERGE,
+ onyxMethod: shouldCreateNewMoneyRequestReport ? Onyx.METHOD.SET : Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`,
value: {
...iouReport,
lastMessageText: iouAction.message[0].text,
lastMessageHtml: iouAction.message[0].html,
pendingFields: {
- ...(isNewIOUReport ? {createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD} : {preview: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}),
+ ...(shouldCreateNewMoneyRequestReport ? {createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD} : {preview: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}),
},
},
},
@@ -416,10 +416,10 @@ function buildOnyxDataForMoneyRequest(
},
},
{
- onyxMethod: isNewIOUReport ? Onyx.METHOD.SET : Onyx.METHOD.MERGE,
+ onyxMethod: shouldCreateNewMoneyRequestReport ? Onyx.METHOD.SET : Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`,
value: {
- ...(isNewIOUReport ? {[iouCreatedAction.reportActionID]: iouCreatedAction} : {}),
+ ...(shouldCreateNewMoneyRequestReport ? {[iouCreatedAction.reportActionID]: iouCreatedAction} : {}),
[iouAction.reportActionID]: iouAction,
},
},
@@ -507,7 +507,7 @@ function buildOnyxDataForMoneyRequest(
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`,
value: {
- ...(isNewIOUReport
+ ...(shouldCreateNewMoneyRequestReport
? {
[iouCreatedAction.reportActionID]: {
pendingAction: null,
@@ -547,7 +547,7 @@ function buildOnyxDataForMoneyRequest(
value: {
pendingFields: null,
errorFields: {
- ...(isNewIOUReport ? {createChat: ErrorUtils.getMicroSecondOnyxError('report.genericCreateReportFailureMessage')} : {}),
+ ...(shouldCreateNewMoneyRequestReport ? {createChat: ErrorUtils.getMicroSecondOnyxError('report.genericCreateReportFailureMessage')} : {}),
},
},
},
@@ -593,7 +593,7 @@ function buildOnyxDataForMoneyRequest(
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`,
value: {
- ...(isNewIOUReport
+ ...(shouldCreateNewMoneyRequestReport
? {
[iouCreatedAction.reportActionID]: {
errors: getReceiptError(transaction.receipt, transaction.filename || transaction.receipt.filename, isScanRequest),
@@ -634,7 +634,7 @@ function buildOnyxDataForMoneyRequest(
* Gathers all the data needed to make a money request. It attempts to find existing reports, iouReports, and receipts. If it doesn't find them, then
* it creates optimistic versions of them and uses those instead
*
- * @param {Object} report
+ * @param {Object} parentChatReport
* @param {Object} participant
* @param {String} comment
* @param {Number} amount
@@ -651,6 +651,7 @@ function buildOnyxDataForMoneyRequest(
* @param {Object} [policy]
* @param {Object} [policyTags]
* @param {Object} [policyCategories]
+ * @param {Number} [moneyRequestReportID] - If user requests money via the report composer on some money request report, we always add a request to that specific report.
* @returns {Object} data
* @returns {String} data.payerEmail
* @returns {Object} data.iouReport
@@ -666,7 +667,7 @@ function buildOnyxDataForMoneyRequest(
* @returns {Object} data.onyxData.failureData
*/
function getMoneyRequestInformation(
- report,
+ parentChatReport,
participant,
comment,
amount,
@@ -683,6 +684,7 @@ function getMoneyRequestInformation(
policy = undefined,
policyTags = undefined,
policyCategories = undefined,
+ moneyRequestReportID = 0,
) {
const payerEmail = OptionsListUtils.addSMSDomainIfPhoneNumber(participant.login);
const payerAccountID = Number(participant.accountID);
@@ -690,7 +692,7 @@ function getMoneyRequestInformation(
// STEP 1: Get existing chat report OR build a new optimistic one
let isNewChatReport = false;
- let chatReport = lodashGet(report, 'reportID', null) ? report : null;
+ let chatReport = lodashGet(parentChatReport, 'reportID', null) ? parentChatReport : null;
// If this is a policyExpenseChat, the chatReport must exist and we can get it from Onyx.
// report is null if the flow is initiated from the global create menu. However, participant always stores the reportID if it exists, which is the case for policyExpenseChats
@@ -708,9 +710,15 @@ function getMoneyRequestInformation(
chatReport = ReportUtils.buildOptimisticChatReport([payerAccountID]);
}
- // STEP 2: Get existing IOU report and update its total OR build a new optimistic one
- const isNewIOUReport = !chatReport.iouReportID || ReportUtils.hasIOUWaitingOnCurrentUserBankAccount(chatReport);
- let iouReport = isNewIOUReport ? null : allReports[`${ONYXKEYS.COLLECTION.REPORT}${chatReport.iouReportID}`];
+ // STEP 2: Get the money request report. If the moneyRequestReportID has been provided, we want to add the transaction to this specific report.
+ // If no such reportID has been provided, let's use the chatReport.iouReportID property. In case that is not present, build a new optimistic money request report.
+ let iouReport = null;
+ const shouldCreateNewMoneyRequestReport = !moneyRequestReportID && (!chatReport.iouReportID || ReportUtils.hasIOUWaitingOnCurrentUserBankAccount(chatReport));
+ if (moneyRequestReportID > 0) {
+ iouReport = allReports[`${ONYXKEYS.COLLECTION.REPORT}${moneyRequestReportID}`];
+ } else if (!shouldCreateNewMoneyRequestReport) {
+ iouReport = allReports[`${ONYXKEYS.COLLECTION.REPORT}${chatReport.iouReportID}`];
+ }
// Check if the Scheduled Submit is enabled in case of expense report
let needsToBeManuallySubmitted = true;
@@ -719,7 +727,7 @@ function getMoneyRequestInformation(
isFromPaidPolicy = PolicyUtils.isPaidGroupPolicy(policy);
// If the scheduled submit is turned off on the policy, user needs to manually submit the report which is indicated by GBR in LHN
- needsToBeManuallySubmitted = isFromPaidPolicy && !(policy.isHarvestingEnabled || false);
+ needsToBeManuallySubmitted = isFromPaidPolicy && !(lodashGet(policy, 'harvesting.enabled', policy.isHarvestingEnabled) || false);
// If the linked expense report on paid policy is not draft, we need to create a new draft expense report
if (iouReport && isFromPaidPolicy && !ReportUtils.isDraftExpenseReport(iouReport)) {
@@ -807,7 +815,7 @@ function getMoneyRequestInformation(
currentTime,
);
- let reportPreviewAction = isNewIOUReport ? null : ReportActionsUtils.getReportPreviewAction(chatReport.reportID, iouReport.reportID);
+ let reportPreviewAction = shouldCreateNewMoneyRequestReport ? null : ReportActionsUtils.getReportPreviewAction(chatReport.reportID, iouReport.reportID);
if (reportPreviewAction) {
reportPreviewAction = ReportUtils.updateReportPreview(iouReport, reportPreviewAction, false, comment, optimisticTransaction);
} else {
@@ -845,7 +853,7 @@ function getMoneyRequestInformation(
optimisticPolicyRecentlyUsedCategories,
optimisticPolicyRecentlyUsedTags,
isNewChatReport,
- isNewIOUReport,
+ shouldCreateNewMoneyRequestReport,
policy,
policyTags,
policyCategories,
@@ -860,7 +868,7 @@ function getMoneyRequestInformation(
transaction: optimisticTransaction,
iouAction,
createdChatReportActionID: isNewChatReport ? optimisticCreatedActionForChat.reportActionID : 0,
- createdIOUReportActionID: isNewIOUReport ? optimisticCreatedActionForIOU.reportActionID : 0,
+ createdIOUReportActionID: shouldCreateNewMoneyRequestReport ? optimisticCreatedActionForIOU.reportActionID : 0,
reportPreviewAction,
onyxData: {
optimisticData,
@@ -892,6 +900,7 @@ function createDistanceRequest(report, participant, comment, created, category,
// If the report is an iou or expense report, we should get the linked chat report to be passed to the getMoneyRequestInformation function
const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report);
const currentChatReport = isMoneyRequestReport ? ReportUtils.getReport(report.chatReportID) : report;
+ const moneyRequestReportID = isMoneyRequestReport ? report.reportID : 0;
const currentCreated = DateUtils.enrichMoneyRequestTimestamp(created);
const optimisticReceipt = {
@@ -916,6 +925,7 @@ function createDistanceRequest(report, participant, comment, created, category,
policy,
policyTags,
policyCategories,
+ moneyRequestReportID,
);
API.write(
'CreateDistanceRequest',
@@ -1313,6 +1323,7 @@ function requestMoney(
// If the report is iou or expense report, we should get the linked chat report to be passed to the getMoneyRequestInformation function
const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report);
const currentChatReport = isMoneyRequestReport ? ReportUtils.getReport(report.chatReportID) : report;
+ const moneyRequestReportID = isMoneyRequestReport ? report.reportID : 0;
const currentCreated = DateUtils.enrichMoneyRequestTimestamp(created);
const {payerAccountID, payerEmail, iouReport, chatReport, transaction, iouAction, createdChatReportActionID, createdIOUReportActionID, reportPreviewAction, onyxData} =
getMoneyRequestInformation(
@@ -1333,6 +1344,7 @@ function requestMoney(
policy,
policyTags,
policyCategories,
+ moneyRequestReportID,
);
const activeReportID = isMoneyRequestReport ? report.reportID : chatReport.reportID;
@@ -1389,10 +1401,11 @@ function requestMoney(
* @param {String} category
* @param {String} tag
* @param {String} existingSplitChatReportID - the report ID where the split bill happens, could be a group chat or a workspace chat
+ * @param {Boolean} billable
*
* @return {Object}
*/
-function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAccountID, amount, comment, currency, merchant, category, tag, existingSplitChatReportID = '') {
+function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAccountID, amount, comment, currency, merchant, category, tag, existingSplitChatReportID = '', billable = false) {
const currentUserEmailForIOUSplit = OptionsListUtils.addSMSDomainIfPhoneNumber(currentUserLogin);
const participantAccountIDs = _.map(participants, (participant) => Number(participant.accountID));
const existingSplitChatReport =
@@ -1416,6 +1429,7 @@ function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAcco
undefined,
category,
tag,
+ billable,
);
// Note: The created action must be optimistically generated before the IOU action so there's no chance that the created action appears after the IOU action in the chat
@@ -1617,6 +1631,7 @@ function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAcco
undefined,
category,
tag,
+ billable,
);
// STEP 4: Build optimistic reportActions. We need:
@@ -1734,8 +1749,9 @@ function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAcco
* @param {String} category
* @param {String} tag
* @param {String} existingSplitChatReportID - Either a group DM or a workspace chat
+ * @param {Boolean} billable
*/
-function splitBill(participants, currentUserLogin, currentUserAccountID, amount, comment, currency, merchant, category, tag, existingSplitChatReportID = '') {
+function splitBill(participants, currentUserLogin, currentUserAccountID, amount, comment, currency, merchant, category, tag, existingSplitChatReportID = '', billable = false) {
const {splitData, splits, onyxData} = createSplitsAndOnyxData(
participants,
currentUserLogin,
@@ -1747,6 +1763,7 @@ function splitBill(participants, currentUserLogin, currentUserAccountID, amount,
category,
tag,
existingSplitChatReportID,
+ billable,
);
API.write(
'SplitBill',
@@ -1759,6 +1776,7 @@ function splitBill(participants, currentUserLogin, currentUserAccountID, amount,
category,
merchant,
tag,
+ billable,
transactionID: splitData.transactionID,
reportActionID: splitData.reportActionID,
createdReportActionID: splitData.createdReportActionID,
@@ -1782,9 +1800,10 @@ function splitBill(participants, currentUserLogin, currentUserAccountID, amount,
* @param {String} merchant
* @param {String} category
* @param {String} tag
+ * @param {Boolean} billable
*/
-function splitBillAndOpenReport(participants, currentUserLogin, currentUserAccountID, amount, comment, currency, merchant, category, tag) {
- const {splitData, splits, onyxData} = createSplitsAndOnyxData(participants, currentUserLogin, currentUserAccountID, amount, comment, currency, merchant, category, tag);
+function splitBillAndOpenReport(participants, currentUserLogin, currentUserAccountID, amount, comment, currency, merchant, category, tag, billable) {
+ const {splitData, splits, onyxData} = createSplitsAndOnyxData(participants, currentUserLogin, currentUserAccountID, amount, comment, currency, merchant, category, tag, billable);
API.write(
'SplitBillAndOpenReport',
@@ -1797,6 +1816,7 @@ function splitBillAndOpenReport(participants, currentUserLogin, currentUserAccou
comment,
category,
tag,
+ billable,
transactionID: splitData.transactionID,
reportActionID: splitData.reportActionID,
createdReportActionID: splitData.createdReportActionID,
@@ -1821,8 +1841,9 @@ function splitBillAndOpenReport(participants, currentUserLogin, currentUserAccou
* @param {String} tag
* @param {Object} receipt
* @param {String} existingSplitChatReportID - Either a group DM or a workspace chat
+ * @param {Boolean} billable
*/
-function startSplitBill(participants, currentUserLogin, currentUserAccountID, comment, category, tag, receipt, existingSplitChatReportID = '') {
+function startSplitBill(participants, currentUserLogin, currentUserAccountID, comment, category, tag, receipt, existingSplitChatReportID = '', billable = false) {
const currentUserEmailForIOUSplit = OptionsListUtils.addSMSDomainIfPhoneNumber(currentUserLogin);
const participantAccountIDs = _.map(participants, (participant) => Number(participant.accountID));
const existingSplitChatReport =
@@ -1850,6 +1871,7 @@ function startSplitBill(participants, currentUserLogin, currentUserAccountID, co
undefined,
category,
tag,
+ billable,
);
// Note: The created action must be optimistically generated before the IOU action so there's no chance that the created action appears after the IOU action in the chat
@@ -2063,13 +2085,14 @@ function startSplitBill(participants, currentUserLogin, currentUserAccountID, co
category,
tag,
isFromGroupDM: !existingSplitChatReport,
+ billable,
...(existingSplitChatReport ? {} : {createdReportActionID: splitChatCreatedReportAction.reportActionID}),
},
{optimisticData, successData, failureData},
);
resetMoneyRequestInfo();
- Navigation.dismissModal(splitChatReport.reportID);
+ Navigation.dismissModalWithReport(splitChatReport);
Report.notifyNewAction(splitChatReport.chatReportID, currentUserAccountID);
}
@@ -3467,7 +3490,7 @@ function payMoneyRequest(paymentType, chatReport, iouReport) {
const apiCommand = paymentType === CONST.IOU.PAYMENT_TYPE.EXPENSIFY ? 'PayMoneyRequestWithWallet' : 'PayMoneyRequest';
API.write(apiCommand, params, {optimisticData, successData, failureData});
- Navigation.dismissModal(chatReport.reportID);
+ Navigation.dismissModalWithReport(chatReport);
}
function detachReceipt(transactionID) {
@@ -3752,6 +3775,15 @@ function navigateToStartStepIfScanFileCannotBeRead(receiptFilename, receiptPath,
FileUtils.readFileAsync(receiptPath, receiptFilename, onSuccess, onFailure);
}
+/**
+ * Save the preferred payment method for a policy
+ * @param {String} policyID
+ * @param {String} paymentMethod
+ */
+function savePreferredPaymentMethod(policyID, paymentMethod) {
+ Onyx.merge(`${ONYXKEYS.NVP_LAST_PAYMENT_METHOD}`, {[policyID]: paymentMethod});
+}
+
export {
setMoneyRequestParticipants,
createDistanceRequest,
@@ -3812,4 +3844,5 @@ export {
getIOUReportID,
editMoneyRequest,
navigateToStartStepIfScanFileCannotBeRead,
+ savePreferredPaymentMethod,
};
diff --git a/src/libs/actions/PersonalDetails.ts b/src/libs/actions/PersonalDetails.ts
index e7d9b48c46e9..ac044b0bc25b 100644
--- a/src/libs/actions/PersonalDetails.ts
+++ b/src/libs/actions/PersonalDetails.ts
@@ -136,7 +136,7 @@ function updatePronouns(pronouns: string) {
});
}
- Navigation.goBack(ROUTES.SETTINGS_PROFILE);
+ Navigation.goBack();
}
function updateDisplayName(firstName: string, lastName: string) {
@@ -163,7 +163,7 @@ function updateDisplayName(firstName: string, lastName: string) {
});
}
- Navigation.goBack(ROUTES.SETTINGS_PROFILE);
+ Navigation.goBack();
}
function updateLegalName(legalFirstName: string, legalLastName: string) {
diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts
index fbe92aeb378d..0c3a8afc1576 100644
--- a/src/libs/actions/Policy.ts
+++ b/src/libs/actions/Policy.ts
@@ -1157,6 +1157,7 @@ function createDraftInitialWorkspace(policyOwnerEmail = '', policyName = '', pol
name: workspaceName,
role: CONST.POLICY.ROLE.ADMIN,
owner: sessionEmail,
+ ownerAccountID: sessionAccountID,
isPolicyExpenseChatEnabled: true,
outputCurrency,
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
@@ -1218,6 +1219,7 @@ function createWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policyName
name: workspaceName,
role: CONST.POLICY.ROLE.ADMIN,
owner: sessionEmail,
+ ownerAccountID: sessionAccountID,
isPolicyExpenseChatEnabled: true,
outputCurrency,
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
@@ -1595,6 +1597,7 @@ function createWorkspaceFromIOUPayment(iouReport: Report): string | undefined {
name: workspaceName,
role: CONST.POLICY.ROLE.ADMIN,
owner: sessionEmail,
+ ownerAccountID: sessionAccountID,
isPolicyExpenseChatEnabled: true,
// Setting the currency to USD as we can only add the VBBA for this policy currency right now
diff --git a/src/libs/actions/ReimbursementAccount/navigation.js b/src/libs/actions/ReimbursementAccount/navigation.js
index ebc1862e9c74..0ea09465d795 100644
--- a/src/libs/actions/ReimbursementAccount/navigation.js
+++ b/src/libs/actions/ReimbursementAccount/navigation.js
@@ -16,11 +16,11 @@ function goToWithdrawalAccountSetupStep(stepID, newAchData) {
/**
* Navigate to the correct bank account route based on the bank account state and type
*
- * @param {string} policyId - The policy ID associated with the bank account.
+ * @param {string} policyID - The policy ID associated with the bank account.
* @param {string} [backTo=''] - An optional return path. If provided, it will be URL-encoded and appended to the resulting URL.
*/
-function navigateToBankAccountRoute(policyId, backTo) {
- Navigation.navigate(ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute('', policyId, backTo));
+function navigateToBankAccountRoute(policyID, backTo) {
+ Navigation.navigate(ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute('', policyID, backTo));
}
export {goToWithdrawalAccountSetupStep, navigateToBankAccountRoute};
diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts
index 1f6905cfb8e0..782cf2b174c2 100644
--- a/src/libs/actions/Report.ts
+++ b/src/libs/actions/Report.ts
@@ -51,9 +51,12 @@ import Navigation from '@libs/Navigation/Navigation';
import LocalNotification from '@libs/Notification/LocalNotification';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
+import getPolicyMemberAccountIDs from '@libs/PolicyMembersUtils';
+import {extractPolicyIDFromPath} from '@libs/PolicyUtils';
import * as Pusher from '@libs/Pusher/pusher';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import * as ReportUtils from '@libs/ReportUtils';
+import {doesReportBelongToWorkspace} from '@libs/ReportUtils';
import type {OptimisticAddCommentReportAction} from '@libs/ReportUtils';
import shouldSkipDeepLinkNavigation from '@libs/shouldSkipDeepLinkNavigation';
import * as UserUtils from '@libs/UserUtils';
@@ -63,7 +66,7 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Route} from '@src/ROUTES';
import ROUTES from '@src/ROUTES';
-import type {PersonalDetails, PersonalDetailsList, ReportActionReactions, ReportMetadata, ReportUserIsTyping} from '@src/types/onyx';
+import type {PersonalDetails, PersonalDetailsList, PolicyReportField, RecentlyUsedReportFields, ReportActionReactions, ReportMetadata, ReportUserIsTyping} from '@src/types/onyx';
import type {Decision, OriginalMessageIOU} from '@src/types/onyx/OriginalMessage';
import type {NotificationPreference, WriteCapability} from '@src/types/onyx/Report';
import type Report from '@src/types/onyx/Report';
@@ -175,6 +178,23 @@ Linking.getInitialURL().then((url) => {
reportIDDeeplinkedFromOldDot = reportID;
});
+let lastVisitedPath: string | undefined;
+Onyx.connect({
+ key: ONYXKEYS.LAST_VISITED_PATH,
+ callback: (value) => {
+ if (!value) {
+ return;
+ }
+ lastVisitedPath = value;
+ },
+});
+
+let allRecentlyUsedReportFields: OnyxEntry = {};
+Onyx.connect({
+ key: ONYXKEYS.RECENTLY_USED_REPORT_FIELDS,
+ callback: (val) => (allRecentlyUsedReportFields = val),
+});
+
/** Get the private pusher channel name for a Report. */
function getReportChannelName(reportID: string): string {
return `${CONST.PUSHER.PRIVATE_REPORT_CHANNEL_PREFIX}${reportID}${CONFIG.PUSHER.SUFFIX}`;
@@ -717,14 +737,15 @@ function navigateToAndOpenReport(userLogins: string[], shouldDismissModal = true
if (!chat) {
newChat = ReportUtils.buildOptimisticChatReport(participantAccountIDs);
}
- const reportID = chat ? chat.reportID : newChat.reportID;
+ const report = chat ?? newChat;
// We want to pass newChat here because if anything is passed in that param (even an existing chat), we will try to create a chat on the server
- openReport(reportID, userLogins, newChat);
+ openReport(report.reportID, userLogins, newChat);
if (shouldDismissModal) {
- Navigation.dismissModal(reportID);
+ Navigation.dismissModalWithReport(report);
} else {
- Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportID));
+ Navigation.navigateWithSwitchPolicyID({route: ROUTES.HOME});
+ Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(report.reportID));
}
}
@@ -739,11 +760,11 @@ function navigateToAndOpenReportWithAccountIDs(participantAccountIDs: number[])
if (!chat) {
newChat = ReportUtils.buildOptimisticChatReport(participantAccountIDs);
}
- const reportID = chat ? chat.reportID : newChat.reportID;
+ const report = chat ?? newChat;
// We want to pass newChat here because if anything is passed in that param (even an existing chat), we will try to create a chat on the server
- openReport(reportID, [], newChat, '0', false, participantAccountIDs);
- Navigation.dismissModal(reportID);
+ openReport(report.reportID, [], newChat, '0', false, participantAccountIDs);
+ Navigation.dismissModalWithReport(report);
}
/**
@@ -1462,6 +1483,137 @@ function toggleSubscribeToChildReport(childReportID = '0', parentReportAction: P
}
}
+function updateReportName(reportID: string, value: string, previousValue: string) {
+ const optimisticData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
+ value: {
+ reportName: value,
+ pendingFields: {
+ reportName: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+ },
+ },
+ },
+ ];
+ const failureData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
+ value: {
+ reportName: previousValue,
+ pendingFields: {
+ reportName: null,
+ },
+ errorFields: {
+ reportName: ErrorUtils.getMicroSecondOnyxError('report.genericUpdateReporNameEditFailureMessage'),
+ },
+ },
+ },
+ ];
+
+ const successData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
+ value: {
+ pendingFields: {
+ reportName: null,
+ },
+ errorFields: {
+ reportName: null,
+ },
+ },
+ },
+ ];
+
+ const parameters = {
+ reportID,
+ reportName: value,
+ };
+
+ API.write(WRITE_COMMANDS.SET_REPORT_NAME, parameters, {optimisticData, failureData, successData});
+}
+
+function updateReportField(reportID: string, reportField: PolicyReportField, previousReportField: PolicyReportField) {
+ const recentlyUsedValues = allRecentlyUsedReportFields?.[reportField.fieldID] ?? [];
+
+ const optimisticData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
+ value: {
+ reportFields: {
+ [reportField.fieldID]: reportField,
+ },
+ pendingFields: {
+ [reportField.fieldID]: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+ },
+ },
+ },
+ ];
+
+ if (reportField.type === 'dropdown') {
+ optimisticData.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: ONYXKEYS.RECENTLY_USED_REPORT_FIELDS,
+ value: {
+ [reportField.fieldID]: [...new Set([...recentlyUsedValues, reportField.value])],
+ },
+ });
+ }
+
+ const failureData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
+ value: {
+ reportFields: {
+ [reportField.fieldID]: previousReportField,
+ },
+ pendingFields: {
+ [reportField.fieldID]: null,
+ },
+ errorFields: {
+ [reportField.fieldID]: ErrorUtils.getMicroSecondOnyxError('report.genericUpdateReportFieldFailureMessage'),
+ },
+ },
+ },
+ ];
+
+ if (reportField.type === 'dropdown') {
+ failureData.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: ONYXKEYS.RECENTLY_USED_REPORT_FIELDS,
+ value: {
+ [reportField.fieldID]: recentlyUsedValues,
+ },
+ });
+ }
+
+ const successData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
+ value: {
+ pendingFields: {
+ [reportField.fieldID]: null,
+ },
+ errorFields: {
+ [reportField.fieldID]: null,
+ },
+ },
+ },
+ ];
+
+ const parameters = {
+ reportID,
+ reportFields: JSON.stringify({[reportField.fieldID]: reportField}),
+ };
+
+ API.write(WRITE_COMMANDS.SET_REPORT_FIELD, parameters, {optimisticData, failureData, successData});
+}
+
function updateWelcomeMessage(reportID: string, previousValue: string, newValue: string) {
// No change needed, navigate back
if (previousValue === newValue) {
@@ -1811,7 +1963,16 @@ function showReportActionNotification(reportID: string, reportAction: ReportActi
return;
}
- const onClick = () => Modal.close(() => Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportID)));
+ const onClick = () =>
+ Modal.close(() => {
+ const policyID = lastVisitedPath && extractPolicyIDFromPath(lastVisitedPath);
+ const policyMembersAccountIDs = policyID ? getPolicyMemberAccountIDs(policyID) : [];
+ const reportBelongsToWorkspace = policyID ? doesReportBelongToWorkspace(report, policyMembersAccountIDs, policyID) : false;
+ if (!reportBelongsToWorkspace) {
+ Navigation.navigateWithSwitchPolicyID({route: ROUTES.HOME});
+ }
+ Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportID));
+ });
if (reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.MODIFIEDEXPENSE) {
LocalNotification.showModifiedExpenseNotification(report, reportAction, onClick);
@@ -2721,5 +2882,7 @@ export {
getDraftPrivateNote,
updateLastVisitTime,
clearNewRoomFormError,
+ updateReportField,
+ updateReportName,
resolveActionableMentionWhisper,
};
diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts
index a7aab98f02c6..60c05d0cb677 100644
--- a/src/libs/actions/Task.ts
+++ b/src/libs/actions/Task.ts
@@ -729,7 +729,6 @@ function deleteTask(taskReportID: string, taskTitle: string, originalStateNum: n
],
errors: undefined,
linkMetadata: [],
- reportActionID: '',
};
const optimisticReportActions = {
[parentReportAction.reportActionID]: optimisticReportAction,
@@ -751,8 +750,7 @@ function deleteTask(taskReportID: string, taskTitle: string, originalStateNum: n
key: `${ONYXKEYS.COLLECTION.REPORT}${parentReport?.reportID}`,
value: {
lastMessageText: ReportActionsUtils.getLastVisibleMessage(parentReport?.reportID ?? '', optimisticReportActions as OnyxTypes.ReportActions).lastMessageText ?? '',
- lastVisibleActionCreated:
- ReportActionsUtils.getLastVisibleAction(parentReport?.reportID ?? '', optimisticReportActions as OnyxTypes.ReportActions)?.childLastVisibleActionCreated ?? 'created',
+ lastVisibleActionCreated: ReportActionsUtils.getLastVisibleAction(parentReport?.reportID ?? '', optimisticReportActions as OnyxTypes.ReportActions)?.created,
},
},
{
diff --git a/src/libs/actions/TwoFactorAuthActions.ts b/src/libs/actions/TwoFactorAuthActions.ts
index c4b74836f9db..7c875b886e0b 100644
--- a/src/libs/actions/TwoFactorAuthActions.ts
+++ b/src/libs/actions/TwoFactorAuthActions.ts
@@ -2,7 +2,6 @@ import Onyx from 'react-native-onyx';
import Navigation from '@libs/Navigation/Navigation';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Route} from '@src/ROUTES';
-import ROUTES from '@src/ROUTES';
import type {TwoFactorAuthStep} from '@src/types/onyx/Account';
/**
@@ -21,8 +20,7 @@ function setCodesAreCopied() {
function quitAndNavigateBack(backTo?: Route) {
clearTwoFactorAuthData();
- // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
- Navigation.goBack(backTo || ROUTES.SETTINGS_SECURITY);
+ Navigation.goBack(backTo);
}
export {clearTwoFactorAuthData, setTwoFactorAuthStep, quitAndNavigateBack, setCodesAreCopied};
diff --git a/src/libs/actions/User.ts b/src/libs/actions/User.ts
index d6ed882be54a..a8ef33a92e38 100644
--- a/src/libs/actions/User.ts
+++ b/src/libs/actions/User.ts
@@ -597,7 +597,7 @@ function updateChatPriorityMode(mode: ValueOf, autom
API.write(WRITE_COMMANDS.UPDATE_CHAT_PRIORITY_MODE, parameters, {optimisticData});
if (!autoSwitchedToFocusMode) {
- Navigation.goBack(ROUTES.SETTINGS_PREFERENCES);
+ Navigation.goBack();
}
}
@@ -781,7 +781,7 @@ function updateTheme(theme: ValueOf) {
API.write(WRITE_COMMANDS.UPDATE_THEME, parameters, {optimisticData});
- Navigation.navigate(ROUTES.SETTINGS_PREFERENCES);
+ Navigation.goBack();
}
/**
diff --git a/src/libs/getIsSmallScreenWidth.ts b/src/libs/getIsSmallScreenWidth.ts
new file mode 100644
index 000000000000..6fba45ea1319
--- /dev/null
+++ b/src/libs/getIsSmallScreenWidth.ts
@@ -0,0 +1,6 @@
+import {Dimensions} from 'react-native';
+import variables from '@styles/variables';
+
+export default function getIsSmallScreenWidth(windowWidth = Dimensions.get('window').width) {
+ return windowWidth <= variables.mobileResponsiveWidthBreakpoint;
+}
diff --git a/src/libs/interceptAnonymousUser.ts b/src/libs/interceptAnonymousUser.ts
new file mode 100644
index 000000000000..d4e40cf44779
--- /dev/null
+++ b/src/libs/interceptAnonymousUser.ts
@@ -0,0 +1,16 @@
+import * as Session from './actions/Session';
+
+/**
+ * Checks if user is anonymous. If true, shows the sign in modal, else,
+ * executes the callback.
+ */
+const interceptAnonymousUser = (callback: () => void) => {
+ const isAnonymousUser = Session.isAnonymousUser();
+ if (isAnonymousUser) {
+ Session.signOutAndRedirectToSignIn();
+ } else {
+ callback();
+ }
+};
+
+export default interceptAnonymousUser;
diff --git a/src/pages/DetailsPage.js b/src/pages/DetailsPage.js
index 47ade872d25a..a4cafd59cb73 100755
--- a/src/pages/DetailsPage.js
+++ b/src/pages/DetailsPage.js
@@ -136,7 +136,9 @@ function DetailsPage(props) {
{({show}) => (
diff --git a/src/pages/EditReportFieldDatePage.tsx b/src/pages/EditReportFieldDatePage.tsx
index 6faa84ef8b43..82659eca62c2 100644
--- a/src/pages/EditReportFieldDatePage.tsx
+++ b/src/pages/EditReportFieldDatePage.tsx
@@ -23,38 +23,42 @@ type EditReportFieldDatePageProps = {
/** ID of the policy report field */
fieldID: string;
+ /** Flag to indicate if the field can be left blank */
+ isRequired: boolean;
+
/** Callback to fire when the Save button is pressed */
- onSubmit: () => void;
+ onSubmit: (form: OnyxFormValuesFields) => void;
};
-function EditReportFieldDatePage({fieldName, onSubmit, fieldValue, fieldID}: EditReportFieldDatePageProps) {
+function EditReportFieldDatePage({fieldName, isRequired, onSubmit, fieldValue, fieldID}: EditReportFieldDatePageProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const inputRef = useRef(null);
const validate = useCallback(
- (values: OnyxFormValuesFields) => {
+ (value: OnyxFormValuesFields