, policy: OnyxEntry field.type === 'formula');
}
+/**
+ * Given a set of report fields, return the field that refers to title
+ */
+function getTitleReportField(reportFields: PolicyReportFields) {
+ return Object.values(reportFields).find((field) => isReportFieldOfTypeTitle(field));
+}
+
/**
* Get the report fields attached to the policy given policyID
*/
@@ -2235,8 +2245,7 @@ function areAllRequestsBeingSmartScanned(iouReportID: string, reportPreviewActio
*
*/
function hasMissingSmartscanFields(iouReportID: string): boolean {
- const transactionsWithReceipts = getTransactionsWithReceipts(iouReportID);
- return transactionsWithReceipts.some((transaction) => TransactionUtils.hasMissingSmartscanFields(transaction));
+ return TransactionUtils.getAllReportTransactions(iouReportID).some((transaction) => TransactionUtils.hasMissingSmartscanFields(transaction));
}
/**
@@ -2560,39 +2569,6 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu
return participantsWithoutCurrentUser.map((accountID) => getDisplayNameForParticipant(accountID, isMultipleParticipantReport)).join(', ');
}
-/**
- * Recursively navigates through thread parents to get the root report and workspace name.
- * The recursion stops when we find a non thread or money request report, whichever comes first.
- */
-function getRootReportAndWorkspaceName(report: OnyxEntry): ReportAndWorkspaceName {
- if (!report) {
- return {
- rootReportName: '',
- };
- }
- if (isChildReport(report) && !isMoneyRequestReport(report) && !isTaskReport(report)) {
- const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`] ?? null;
- return getRootReportAndWorkspaceName(parentReport);
- }
-
- if (isIOURequest(report)) {
- return {
- rootReportName: getReportName(report),
- };
- }
- if (isExpenseRequest(report)) {
- return {
- rootReportName: getReportName(report),
- workspaceName: isIOUReport(report) ? CONST.POLICY.OWNER_EMAIL_FAKE : getPolicyName(report, true),
- };
- }
-
- return {
- rootReportName: getReportName(report),
- workspaceName: getPolicyName(report, true),
- };
-}
-
/**
* Get either the policyName or domainName the chat is tied to
*/
@@ -2620,16 +2596,15 @@ function getChatRoomSubtitle(report: OnyxEntry): string | undefined {
* Gets the parent navigation subtitle for the report
*/
function getParentNavigationSubtitle(report: OnyxEntry): ParentNavigationSummaryParams {
- if (isThread(report)) {
- const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`] ?? null;
- const {rootReportName, workspaceName} = getRootReportAndWorkspaceName(parentReport);
- if (!rootReportName) {
- return {};
- }
-
- return {rootReportName, workspaceName};
+ const parentReport = getParentReport(report);
+ if (isEmptyObject(parentReport)) {
+ return {};
}
- return {};
+
+ return {
+ reportName: getReportName(parentReport),
+ workspaceName: getPolicyName(parentReport, true),
+ };
}
/**
@@ -2701,7 +2676,7 @@ function getPolicyDescriptionText(policy: Policy): string {
return parser.htmlToText(policy.description);
}
-function buildOptimisticAddCommentReportAction(text?: string, file?: File, actorAccountID?: number): OptimisticReportAction {
+function buildOptimisticAddCommentReportAction(text?: string, file?: FileObject, actorAccountID?: number): OptimisticReportAction {
const parser = new ExpensiMark();
const commentText = getParsedComment(text ?? '');
const isAttachment = !text && file !== undefined;
@@ -2887,6 +2862,38 @@ function buildOptimisticIOUReport(payeeAccountID: number, payerAccountID: number
};
}
+function getHumanReadableStatus(statusNum: number): string {
+ const status = Object.keys(CONST.REPORT.STATUS_NUM).find((key) => CONST.REPORT.STATUS_NUM[key as keyof typeof CONST.REPORT.STATUS_NUM] === statusNum);
+ return status ? `${status.charAt(0)}${status.slice(1).toLowerCase()}` : '';
+}
+
+/**
+ * Populates the report field formula with the values from the report and policy.
+ * Currently, this only supports optimistic expense reports.
+ * Each formula field is either replaced with a value, or removed.
+ * If after all replacements the formula is empty, the original formula is returned.
+ * See {@link https://help.expensify.com/articles/expensify-classic/insights-and-custom-reporting/Custom-Templates}
+ */
+function populateOptimisticReportFormula(formula: string, report: OptimisticExpenseReport, policy: Policy | EmptyObject): string {
+ const createdDate = report.lastVisibleActionCreated ? new Date(report.lastVisibleActionCreated) : undefined;
+ const result = formula
+ .replaceAll('{report:id}', report.reportID)
+ // We don't translate because the server response is always in English
+ .replaceAll('{report:type}', 'Expense Report')
+ .replaceAll('{report:startdate}', createdDate ? format(createdDate, CONST.DATE.FNS_FORMAT_STRING) : '')
+ .replaceAll('{report:total}', report.total?.toString() ?? '')
+ .replaceAll('{report:currency}', report.currency ?? '')
+ .replaceAll('{report:policyname}', policy.name ?? '')
+ .replaceAll('{report:created}', createdDate ? format(createdDate, CONST.DATE.FNS_DATE_TIME_FORMAT_STRING) : '')
+ .replaceAll('{report:created:yyyy-MM-dd}', createdDate ? format(createdDate, CONST.DATE.FNS_FORMAT_STRING) : '')
+ .replaceAll('{report:status}', report.statusNum !== undefined ? getHumanReadableStatus(report.statusNum) : '')
+ .replaceAll('{user:email}', currentUserEmail ?? '')
+ .replaceAll('{user:email|frontPart}', currentUserEmail ? currentUserEmail.split('@')[0] : '')
+ .replaceAll(/\{report:(.+)}/g, '');
+
+ return result.trim().length ? result : formula;
+}
+
/**
* Builds an optimistic Expense report with a randomly generated reportID
*
@@ -2896,7 +2903,6 @@ function buildOptimisticIOUReport(payeeAccountID: number, payerAccountID: number
* @param total - Amount in cents
* @param currency
*/
-
function buildOptimisticExpenseReport(chatReportID: string, policyID: string, payeeAccountID: number, total: number, currency: string): OptimisticExpenseReport {
// The amount for Expense reports are stored as negative value in the database
const storedTotal = total * -1;
@@ -2917,7 +2923,6 @@ function buildOptimisticExpenseReport(chatReportID: string, policyID: string, pa
type: CONST.REPORT.TYPE.EXPENSE,
ownerAccountID: payeeAccountID,
currency,
-
// We don't translate reportName because the server response is always in English
reportName: `${policyName} owes ${formattedTotal}`,
stateNum,
@@ -2933,6 +2938,11 @@ function buildOptimisticExpenseReport(chatReportID: string, policyID: string, pa
expenseReport.managerID = policy.submitsTo;
}
+ const titleReportField = getTitleReportField(getReportFieldsByPolicyID(policyID) ?? {});
+ if (!!titleReportField && reportFieldsEnabled(expenseReport)) {
+ expenseReport.reportName = populateOptimisticReportFormula(titleReportField.defaultValue, expenseReport, policy);
+ }
+
return expenseReport;
}
@@ -3307,7 +3317,6 @@ function updateReportPreview(iouReport: OnyxEntry, reportPreviewAction:
const message = getReportPreviewMessage(iouReport, reportPreviewAction);
return {
...reportPreviewAction,
- created: DateUtils.getDBTime(),
message: [
{
html: message,
@@ -3989,10 +3998,10 @@ function shouldReportBeInOptionList({
/**
* Attempts to find a report in onyx with the provided list of participants. Does not include threads, task, money request, room, and policy expense chat.
*/
-function getChatByParticipants(newParticipantList: number[]): OnyxEntry {
+function getChatByParticipants(newParticipantList: number[], reports: OnyxCollection = allReports): OnyxEntry {
const sortedNewParticipantList = newParticipantList.sort();
return (
- Object.values(allReports ?? {}).find((report) => {
+ Object.values(reports ?? {}).find((report) => {
// If the report has been deleted, or there are no participants (like an empty #admins room) then skip it
if (
!report ||
@@ -4240,7 +4249,7 @@ function canRequestMoney(report: OnyxEntry, policy: OnyxEntry, o
if (isMoneyRequestReport(report)) {
const isOwnExpenseReport = isExpenseReport(report) && isOwnPolicyExpenseChat;
if (isOwnExpenseReport && PolicyUtils.isPaidGroupPolicy(policy)) {
- return isDraftExpenseReport(report);
+ return isDraftExpenseReport(report) || isExpenseReportWithInstantSubmittedState(report);
}
return (isOwnExpenseReport || isIOUReport(report)) && !isReportApproved(report) && !isSettled(report?.reportID);
@@ -5042,6 +5051,7 @@ export {
isPublicAnnounceRoom,
isConciergeChatReport,
isProcessingReport,
+ isExpenseReportWithInstantSubmittedState,
isCurrentUserTheOnlyParticipant,
hasAutomatedExpensifyAccountIDs,
hasExpensifyGuidesEmails,
@@ -5195,6 +5205,7 @@ export {
buildOptimisticUnHoldReportAction,
shouldDisplayThreadReplies,
shouldDisableThread,
+ getUserDetailTooltipText,
doesReportBelongToWorkspace,
getChildReportNotificationPreference,
getAllAncestorReportActions,
diff --git a/src/libs/Request.ts b/src/libs/Request.ts
index aa94eccbca2e..fc31160bbc1c 100644
--- a/src/libs/Request.ts
+++ b/src/libs/Request.ts
@@ -13,7 +13,9 @@ function makeXHR(request: Request): Promise {
// If we're using the Supportal token and this is not a Supportal request
// let's just return a promise that will resolve itself.
if (NetworkStore.getSupportAuthToken() && !NetworkStore.isSupportRequest(request.command)) {
- return new Promise((resolve) => resolve());
+ return new Promise((resolve) => {
+ resolve();
+ });
}
return HttpUtils.xhr(request.command, finalParameters, request.type, request.shouldUseSecure);
diff --git a/src/libs/RequestThrottle.ts b/src/libs/RequestThrottle.ts
index 36935982afbb..4c524394cb2c 100644
--- a/src/libs/RequestThrottle.ts
+++ b/src/libs/RequestThrottle.ts
@@ -26,9 +26,10 @@ function sleep(): Promise {
requestRetryCount++;
return new Promise((resolve, reject) => {
if (requestRetryCount <= CONST.NETWORK.MAX_REQUEST_RETRIES) {
- return setTimeout(resolve, getRequestWaitTime());
+ setTimeout(resolve, getRequestWaitTime());
+ return;
}
- return reject();
+ reject();
});
}
diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts
index 3489053951b6..67e31c610369 100644
--- a/src/libs/TransactionUtils.ts
+++ b/src/libs/TransactionUtils.ts
@@ -12,6 +12,7 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject';
import {isCorporateCard, isExpensifyCard} from './CardUtils';
import DateUtils from './DateUtils';
import * as NumberUtils from './NumberUtils';
+import {getCleanedTagName} from './PolicyUtils';
import type {OptimisticIOUReportAction} from './ReportUtils';
let allTransactions: OnyxCollection = {};
@@ -409,7 +410,7 @@ function getTag(transaction: OnyxEntry, tagIndex?: number): string
}
function getTagForDisplay(transaction: OnyxEntry, tagIndex?: number): string {
- return getTag(transaction, tagIndex).replace(/[\\\\]:/g, ':');
+ return getCleanedTagName(getTag(transaction, tagIndex));
}
/**
@@ -480,7 +481,7 @@ function isReceiptBeingScanned(transaction: OnyxEntry): boolean {
* Check if the transaction has a non-smartscanning receipt and is missing required fields
*/
function hasMissingSmartscanFields(transaction: OnyxEntry): boolean {
- return Boolean(transaction && hasReceipt(transaction) && !isDistanceRequest(transaction) && !isReceiptBeingScanned(transaction) && areRequiredFieldsEmpty(transaction));
+ return Boolean(transaction && !isDistanceRequest(transaction) && !isReceiptBeingScanned(transaction) && areRequiredFieldsEmpty(transaction));
}
/**
diff --git a/src/libs/Trie/index.ts b/src/libs/Trie/index.ts
index c23c7b814a22..a82c90c15772 100644
--- a/src/libs/Trie/index.ts
+++ b/src/libs/Trie/index.ts
@@ -31,7 +31,6 @@ class Trie {
}
if (!newNode.children[newWord[0]]) {
newNode.children[newWord[0]] = new TrieNode();
- this.add(newWord.substring(1), metaData, newNode.children[newWord[0]], true);
}
this.add(newWord.substring(1), metaData, newNode.children[newWord[0]], true);
}
diff --git a/src/libs/UnreadIndicatorUpdater/updateUnread/index.desktop.ts b/src/libs/UnreadIndicatorUpdater/updateUnread/index.desktop.ts
index 5cbba61542b1..75013ebe621f 100644
--- a/src/libs/UnreadIndicatorUpdater/updateUnread/index.desktop.ts
+++ b/src/libs/UnreadIndicatorUpdater/updateUnread/index.desktop.ts
@@ -1,4 +1,4 @@
-import ELECTRON_EVENTS from '../../../../desktop/ELECTRON_EVENTS';
+import ELECTRON_EVENTS from '@desktop/ELECTRON_EVENTS';
import type UpdateUnread from './types';
/**
diff --git a/src/libs/ValidationUtils.ts b/src/libs/ValidationUtils.ts
index 0a46acbea102..669d10c4a1b8 100644
--- a/src/libs/ValidationUtils.ts
+++ b/src/libs/ValidationUtils.ts
@@ -11,6 +11,7 @@ import type {OnyxFormKey} from '@src/ONYXKEYS';
import type {Report} from '@src/types/onyx';
import * as CardUtils from './CardUtils';
import DateUtils from './DateUtils';
+import type {MaybePhraseKey} from './Localize';
import * as LoginUtils from './LoginUtils';
import {parsePhoneNumber} from './PhoneNumber';
import StringUtils from './StringUtils';
@@ -190,7 +191,7 @@ function meetsMaximumAgeRequirement(date: string): boolean {
/**
* Validate that given date is in a specified range of years before now.
*/
-function getAgeRequirementError(date: string, minimumAge: number, maximumAge: number): string | Array> {
+function getAgeRequirementError(date: string, minimumAge: number, maximumAge: number): MaybePhraseKey {
const currentDate = startOfDay(new Date());
const testDate = parse(date, CONST.DATE.FNS_FORMAT_STRING, currentDate);
@@ -360,7 +361,7 @@ function isValidPersonName(value: string) {
/**
* Checks if the provided string includes any of the provided reserved words
*/
-function doesContainReservedWord(value: string, reservedWords: string[]): boolean {
+function doesContainReservedWord(value: string, reservedWords: typeof CONST.DISPLAY_NAME.RESERVED_NAMES): boolean {
const valueToCheck = value.trim().toLowerCase();
return reservedWords.some((reservedWord) => valueToCheck.includes(reservedWord.toLowerCase()));
}
diff --git a/src/libs/Visibility/index.desktop.ts b/src/libs/Visibility/index.desktop.ts
index c01b6001f456..e3cab6ec44a6 100644
--- a/src/libs/Visibility/index.desktop.ts
+++ b/src/libs/Visibility/index.desktop.ts
@@ -1,4 +1,4 @@
-import ELECTRON_EVENTS from '../../../desktop/ELECTRON_EVENTS';
+import ELECTRON_EVENTS from '@desktop/ELECTRON_EVENTS';
import type {HasFocus, IsVisible, OnVisibilityChange} from './types';
/**
diff --git a/src/libs/actions/App.ts b/src/libs/actions/App.ts
index d2c650d6f1c0..6442f2ec0eef 100644
--- a/src/libs/actions/App.ts
+++ b/src/libs/actions/App.ts
@@ -15,7 +15,7 @@ import type {
ReconnectAppParams,
UpdatePreferredLocaleParams,
} from '@libs/API/parameters';
-import {READ_COMMANDS, SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
+import {SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
import * as Browser from '@libs/Browser';
import DateUtils from '@libs/DateUtils';
import Log from '@libs/Log';
@@ -211,7 +211,7 @@ function openApp() {
getPolicyParamsForOpenOrReconnect().then((policyParams: PolicyParamsForOpenOrReconnect) => {
const params: OpenAppParams = {enablePriorityModeFilter: true, ...policyParams};
- API.read(READ_COMMANDS.OPEN_APP, params, getOnyxDataForOpenOrReconnect(true));
+ API.write(WRITE_COMMANDS.OPEN_APP, params, getOnyxDataForOpenOrReconnect(true));
});
}
diff --git a/src/libs/actions/CachedPDFPaths/index.native.ts b/src/libs/actions/CachedPDFPaths/index.native.ts
new file mode 100644
index 000000000000..09203995e9a1
--- /dev/null
+++ b/src/libs/actions/CachedPDFPaths/index.native.ts
@@ -0,0 +1,47 @@
+import {exists, unlink} from 'react-native-fs';
+import Onyx from 'react-native-onyx';
+import ONYXKEYS from '@src/ONYXKEYS';
+import type {Add, Clear, ClearAll, ClearByKey} from './types';
+
+/*
+ * We need to save the paths of PDF files so we can delete them later.
+ * This is to remove the cached PDFs when an attachment is deleted or the user logs out.
+ */
+let pdfPaths: Record = {};
+Onyx.connect({
+ key: ONYXKEYS.CACHED_PDF_PATHS,
+ callback: (val) => {
+ pdfPaths = val ?? {};
+ },
+});
+
+const add: Add = (id: string, path: string) => {
+ if (pdfPaths[id]) {
+ return Promise.resolve();
+ }
+ return Onyx.merge(ONYXKEYS.CACHED_PDF_PATHS, {[id]: path});
+};
+
+const clear: Clear = (path: string) => {
+ if (!path) {
+ return Promise.resolve();
+ }
+ return new Promise((resolve) => {
+ exists(path).then((exist) => {
+ if (!exist) {
+ resolve();
+ }
+ return unlink(path);
+ });
+ });
+};
+
+const clearByKey: ClearByKey = (id: string) => {
+ clear(pdfPaths[id] ?? '').then(() => Onyx.merge(ONYXKEYS.CACHED_PDF_PATHS, {[id]: null}));
+};
+
+const clearAll: ClearAll = () => {
+ Promise.all(Object.values(pdfPaths).map(clear)).then(() => Onyx.merge(ONYXKEYS.CACHED_PDF_PATHS, {}));
+};
+
+export {add, clearByKey, clearAll};
diff --git a/src/libs/actions/CachedPDFPaths/index.ts b/src/libs/actions/CachedPDFPaths/index.ts
new file mode 100644
index 000000000000..3cac21bf3c25
--- /dev/null
+++ b/src/libs/actions/CachedPDFPaths/index.ts
@@ -0,0 +1,9 @@
+import type {Add, ClearAll, ClearByKey} from './types';
+
+const add: Add = () => Promise.resolve();
+
+const clearByKey: ClearByKey = () => {};
+
+const clearAll: ClearAll = () => {};
+
+export {add, clearByKey, clearAll};
diff --git a/src/libs/actions/CachedPDFPaths/types.ts b/src/libs/actions/CachedPDFPaths/types.ts
new file mode 100644
index 000000000000..98b768c4645e
--- /dev/null
+++ b/src/libs/actions/CachedPDFPaths/types.ts
@@ -0,0 +1,6 @@
+type Add = (id: string, path: string) => Promise;
+type Clear = (path: string) => Promise;
+type ClearAll = () => void;
+type ClearByKey = (id: string) => void;
+
+export type {Add, Clear, ClearAll, ClearByKey};
diff --git a/src/libs/actions/Device/generateDeviceID/index.desktop.ts b/src/libs/actions/Device/generateDeviceID/index.desktop.ts
index f8af0a7faa81..45daf7062a5a 100644
--- a/src/libs/actions/Device/generateDeviceID/index.desktop.ts
+++ b/src/libs/actions/Device/generateDeviceID/index.desktop.ts
@@ -1,4 +1,4 @@
-import ELECTRON_EVENTS from '../../../../../desktop/ELECTRON_EVENTS';
+import ELECTRON_EVENTS from '@desktop/ELECTRON_EVENTS';
import type GenerateDeviceID from './types';
/**
diff --git a/src/libs/actions/Device/index.ts b/src/libs/actions/Device/index.ts
index 761e27d95a78..e7c19d20e4fe 100644
--- a/src/libs/actions/Device/index.ts
+++ b/src/libs/actions/Device/index.ts
@@ -12,7 +12,8 @@ let deviceID: string | null = null;
function getDeviceID(): Promise {
return new Promise((resolve) => {
if (deviceID) {
- return resolve(deviceID);
+ resolve(deviceID);
+ return;
}
const connectionID = Onyx.connect({
diff --git a/src/libs/actions/EmojiPickerAction.ts b/src/libs/actions/EmojiPickerAction.ts
index 0fc21713c608..c63e1c13d29a 100644
--- a/src/libs/actions/EmojiPickerAction.ts
+++ b/src/libs/actions/EmojiPickerAction.ts
@@ -68,7 +68,7 @@ function showEmojiPicker(
/**
* Hide the Emoji Picker modal.
*/
-function hideEmojiPicker(isNavigating: boolean) {
+function hideEmojiPicker(isNavigating?: boolean) {
if (!emojiPickerRef.current) {
return;
}
diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts
index ca43d92bde83..dbab05dcfcd1 100644
--- a/src/libs/actions/IOU.ts
+++ b/src/libs/actions/IOU.ts
@@ -1,3 +1,4 @@
+import type {ParamListBase, StackNavigationState} from '@react-navigation/native';
import type {StackScreenProps} from '@react-navigation/stack';
import {format} from 'date-fns';
import fastMerge from 'expensify-common/lib/fastMerge';
@@ -43,7 +44,7 @@ import type {OptimisticChatReport, OptimisticCreatedReportAction, OptimisticIOUR
import * as TransactionUtils from '@libs/TransactionUtils';
import * as UserUtils from '@libs/UserUtils';
import ViolationsUtils from '@libs/Violations/ViolationsUtils';
-import type {MoneyRequestNavigatorParamList} from '@navigation/types';
+import type {MoneyRequestNavigatorParamList, NavigationPartialRoute} from '@navigation/types';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
@@ -57,6 +58,7 @@ import type {OnyxData} from '@src/types/onyx/Request';
import type {Comment, Receipt, ReceiptSource, TaxRate, TransactionChanges, WaypointCollection} from '@src/types/onyx/Transaction';
import type {EmptyObject} from '@src/types/utils/EmptyObject';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
+import * as CachedPDFPaths from './CachedPDFPaths';
import * as Policy from './Policy';
import * as Report from './Report';
@@ -260,6 +262,28 @@ function clearMoneyRequest(transactionID: string) {
Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, null);
}
+/**
+ * Update money request-related pages IOU type params
+ */
+function updateMoneyRequestTypeParams(routes: StackNavigationState['routes'] | NavigationPartialRoute[], newIouType: string, tab: string) {
+ routes.forEach((route) => {
+ const tabList = [CONST.TAB_REQUEST.DISTANCE, CONST.TAB_REQUEST.MANUAL, CONST.TAB_REQUEST.SCAN] as string[];
+ if (!route.name.startsWith('Money_Request_') && !tabList.includes(route.name)) {
+ return;
+ }
+ const newParams: Record = {iouType: newIouType};
+ if (route.name === 'Money_Request_Create') {
+ // Both screen and nested params are needed to properly update the nested tab navigator
+ newParams.params = {...newParams};
+ newParams.screen = tab;
+ }
+ Navigation.setParams(newParams, route.key ?? '');
+
+ // Recursively update nested money request tab params
+ updateMoneyRequestTypeParams(route.state?.routes ?? [], newIouType, tab);
+ });
+}
+
// eslint-disable-next-line @typescript-eslint/naming-convention
function startMoneyRequest_temporaryForRefactor(iouType: ValueOf, reportID: string) {
clearMoneyRequest(CONST.IOU.OPTIMISTIC_TRANSACTION_ID);
@@ -324,9 +348,9 @@ function setMoneyRequestParticipants_temporaryForRefactor(transactionID: string,
Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {participants});
}
-function setMoneyRequestReceipt(transactionID: string, source: string, filename: string, isDraft: boolean) {
+function setMoneyRequestReceipt(transactionID: string, source: string, filename: string, isDraft: boolean, type?: string) {
Onyx.merge(`${isDraft ? ONYXKEYS.COLLECTION.TRANSACTION_DRAFT : ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {
- receipt: {source},
+ receipt: {source, type: type ?? ''},
filename,
});
}
@@ -361,9 +385,25 @@ function getReceiptError(receipt?: Receipt, filename?: string, isScanRequest = t
: ErrorUtils.getMicroSecondOnyxErrorObject({error: CONST.IOU.RECEIPT_ERROR, source: receipt.source?.toString() ?? '', filename: filename ?? ''});
}
-/** Return the object to update hasOutstandingChildRequest */
-function getOutstandingChildRequest(needsToBeManuallySubmitted: boolean, policy: OnyxEntry | EmptyObject = null): OutstandingChildRequest {
- if (!needsToBeManuallySubmitted) {
+function needsToBeManuallySubmitted(iouReport: OnyxTypes.Report) {
+ const isPolicyExpenseChat = ReportUtils.isExpenseReport(iouReport);
+
+ if (isPolicyExpenseChat) {
+ const policy = ReportUtils.getPolicy(iouReport.policyID);
+ const 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
+ return isFromPaidPolicy && !policy.harvesting?.enabled;
+ }
+
+ return true;
+}
+
+/**
+ * Return the object to update hasOutstandingChildRequest
+ */
+function getOutstandingChildRequest(policy: OnyxEntry | EmptyObject, iouReport: OnyxTypes.Report): OutstandingChildRequest {
+ if (!needsToBeManuallySubmitted(iouReport)) {
return {
hasOutstandingChildRequest: false,
};
@@ -375,8 +415,9 @@ function getOutstandingChildRequest(needsToBeManuallySubmitted: boolean, policy:
};
}
- // We don't need to update hasOutstandingChildRequest in this case
- return {};
+ return {
+ hasOutstandingChildRequest: iouReport.managerID === userAccountID && iouReport.total !== 0,
+ };
}
/** Builds the Onyx data for a money request */
@@ -399,10 +440,9 @@ function buildOnyxDataForMoneyRequest(
policyTagList?: OnyxEntry,
policyCategories?: OnyxEntry,
optimisticNextStep?: OnyxTypes.ReportNextStep | null,
- needsToBeManuallySubmitted = true,
): [OnyxUpdate[], OnyxUpdate[], OnyxUpdate[]] {
const isScanRequest = TransactionUtils.isScanRequest(transaction);
- const outstandingChildRequest = getOutstandingChildRequest(needsToBeManuallySubmitted, policy);
+ const outstandingChildRequest = getOutstandingChildRequest(policy ?? {}, iouReport);
const clearedPendingFields = Object.fromEntries(Object.keys(transaction.pendingFields ?? {}).map((key) => [key, null]));
const optimisticData: OnyxUpdate[] = [];
@@ -795,17 +835,12 @@ function getMoneyRequestInformation(
iouReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${chatReport.iouReportID}`] ?? null;
}
- // Check if the Scheduled Submit is enabled in case of expense report
- let needsToBeManuallySubmitted = true;
let isFromPaidPolicy = false;
if (isPolicyExpenseChat) {
isFromPaidPolicy = PolicyUtils.isPaidGroupPolicy(policy ?? null);
- // 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?.harvesting?.enabled;
-
- // 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)) {
+ // If the linked expense report on paid policy is not draft and not instantly submitted, we need to create a new draft expense report
+ if (iouReport && isFromPaidPolicy && !ReportUtils.isDraftExpenseReport(iouReport) && !ReportUtils.isExpenseReportWithInstantSubmittedState(iouReport)) {
iouReport = null;
}
}
@@ -814,7 +849,7 @@ function getMoneyRequestInformation(
if (isPolicyExpenseChat) {
iouReport = {...iouReport};
if (iouReport?.currency === currency && typeof iouReport.total === 'number') {
- // Because of the Expense reports are stored as negative values, we substract the total from the amount
+ // Because of the Expense reports are stored as negative values, we subtract the total from the amount
iouReport.total -= amount;
}
} else {
@@ -943,7 +978,6 @@ function getMoneyRequestInformation(
policyTagList,
policyCategories,
optimisticNextStep,
- needsToBeManuallySubmitted,
);
return {
@@ -1210,11 +1244,21 @@ function getUpdateMoneyRequestParams(
}
updatedMoneyRequestReport.cachedTotal = CurrencyUtils.convertToDisplayString(updatedMoneyRequestReport.total, transactionDetails?.currency);
- optimisticData.push({
- onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport?.reportID}`,
- value: updatedMoneyRequestReport,
- });
+ optimisticData.push(
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport?.reportID}`,
+ value: updatedMoneyRequestReport,
+ },
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport?.parentReportID}`,
+ value: {
+ hasOutstandingChildRequest:
+ iouReport && needsToBeManuallySubmitted(iouReport) && updatedMoneyRequestReport.managerID === userAccountID && updatedMoneyRequestReport.total !== 0,
+ },
+ },
+ );
successData.push({
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport?.reportID}`,
@@ -2995,6 +3039,13 @@ function deleteMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repor
[reportPreviewAction?.reportActionID ?? '']: updatedReportPreviewAction,
},
},
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport?.reportID}`,
+ value: {
+ hasOutstandingChildRequest: iouReport && needsToBeManuallySubmitted(iouReport) && updatedIOUReport?.managerID === userAccountID && updatedIOUReport.total !== 0,
+ },
+ },
);
if (!shouldDeleteIOUReport && updatedReportPreviewAction.childMoneyRequestCount === 0) {
@@ -3139,6 +3190,7 @@ function deleteMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repor
// STEP 6: Make the API request
API.write(WRITE_COMMANDS.DELETE_MONEY_REQUEST, parameters, {optimisticData, successData, failureData});
+ CachedPDFPaths.clearByKey(transactionID);
// STEP 7: Navigate the user depending on which page they are on and which resources were deleted
if (iouReport && isSingleTransactionView && shouldDeleteTransactionThread && !shouldDeleteIOUReport) {
@@ -4193,6 +4245,7 @@ function navigateToStartStepIfScanFileCannotBeRead(
iouType: ValueOf,
transactionID: string,
reportID: string,
+ receiptType: string,
) {
if (!receiptFilename || !receiptPath) {
return;
@@ -4206,7 +4259,7 @@ function navigateToStartStepIfScanFileCannotBeRead(
}
IOUUtils.navigateToStartMoneyRequestStep(requestType, iouType, transactionID, reportID);
};
- FileUtils.readFileAsync(receiptPath, receiptFilename, onSuccess, onFailure);
+ FileUtils.readFileAsync(receiptPath, receiptFilename, onSuccess, onFailure, receiptType);
}
/** Save the preferred payment method for a policy */
@@ -4233,6 +4286,8 @@ export {
initMoneyRequest,
startMoneyRequest_temporaryForRefactor,
resetMoneyRequestInfo,
+ clearMoneyRequest,
+ updateMoneyRequestTypeParams,
setMoneyRequestAmount_temporaryForRefactor,
setMoneyRequestBillable_temporaryForRefactor,
setMoneyRequestCreated,
diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts
index 57cd4a6fc071..41288ce5eecd 100644
--- a/src/libs/actions/Policy.ts
+++ b/src/libs/actions/Policy.ts
@@ -54,6 +54,7 @@ import type {
} from '@src/types/onyx';
import type {Errors} from '@src/types/onyx/OnyxCommon';
import type {Attributes, CustomUnit, Rate, Unit} from '@src/types/onyx/Policy';
+import type {OnyxData} from '@src/types/onyx/Request';
import type {EmptyObject} from '@src/types/utils/EmptyObject';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
@@ -391,6 +392,9 @@ function setWorkspaceAutoReporting(policyID: string, enabled: boolean) {
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
autoReporting: enabled,
+ harvesting: {
+ enabled: true,
+ },
pendingFields: {isAutoApprovalEnabled: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE},
},
},
@@ -402,7 +406,7 @@ function setWorkspaceAutoReporting(policyID: string, enabled: boolean) {
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
autoReporting: !enabled,
- pendingFields: {isAutoApprovalEnabled: null},
+ pendingFields: {isAutoApprovalEnabled: null, harvesting: null},
},
},
];
@@ -412,7 +416,7 @@ function setWorkspaceAutoReporting(policyID: string, enabled: boolean) {
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
- pendingFields: {isAutoApprovalEnabled: null},
+ pendingFields: {isAutoApprovalEnabled: null, harvesting: null},
},
},
];
@@ -1398,6 +1402,12 @@ function createWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policyName
outputCurrency,
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
customUnits,
+ areCategoriesEnabled: true,
+ areTagsEnabled: false,
+ areDistanceRatesEnabled: false,
+ areWorkflowsEnabled: false,
+ areReportFieldsEnabled: false,
+ areConnectionsEnabled: false,
},
},
{
@@ -1799,6 +1809,12 @@ function createWorkspaceFromIOUPayment(iouReport: Report | EmptyObject): string
outputCurrency: CONST.CURRENCY.USD,
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
customUnits,
+ areCategoriesEnabled: true,
+ areTagsEnabled: false,
+ areDistanceRatesEnabled: false,
+ areWorkflowsEnabled: false,
+ areReportFieldsEnabled: false,
+ areConnectionsEnabled: false,
};
const optimisticData: OnyxUpdate[] = [
@@ -2178,6 +2194,60 @@ function createWorkspaceFromIOUPayment(iouReport: Report | EmptyObject): string
return policyID;
}
+const setWorkspaceRequiresCategory = (policyID: string, requiresCategory: boolean) => {
+ const onyxData: OnyxData = {
+ optimisticData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ requiresCategory,
+ errors: {
+ requiresCategory: null,
+ },
+ pendingFields: {
+ requiresCategory: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+ },
+ },
+ },
+ ],
+ successData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ errors: {
+ requiresCategory: null,
+ },
+ pendingFields: {
+ requiresCategory: null,
+ },
+ },
+ },
+ ],
+ failureData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ requiresCategory: !requiresCategory,
+ errors: ErrorUtils.getMicroSecondOnyxError('workspace.categories.genericFailureMessage'),
+ pendingFields: {
+ requiresCategory: null,
+ },
+ },
+ },
+ ],
+ };
+
+ const parameters = {
+ policyID,
+ requiresCategory,
+ };
+
+ API.write('SetWorkspaceRequiresCategory', parameters, onyxData);
+};
+
export {
removeMembers,
addMembersToWorkspace,
@@ -2221,4 +2291,5 @@ export {
setWorkspaceAutoReporting,
setWorkspaceApprovalMode,
updateWorkspaceDescription,
+ setWorkspaceRequiresCategory,
};
diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts
index f29f8a4fbaab..c363f49e7e3d 100644
--- a/src/libs/actions/Report.ts
+++ b/src/libs/actions/Report.ts
@@ -7,6 +7,7 @@ import type {NullishDeep, OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-nat
import Onyx from 'react-native-onyx';
import type {PartialDeep, ValueOf} from 'type-fest';
import type {Emoji} from '@assets/emojis/types';
+import type {FileObject} from '@components/AttachmentModal';
import * as ActiveClientManager from '@libs/ActiveClientManager';
import * as API from '@libs/API';
import type {
@@ -47,6 +48,7 @@ import DateUtils from '@libs/DateUtils';
import * as EmojiUtils from '@libs/EmojiUtils';
import * as Environment from '@libs/Environment/Environment';
import * as ErrorUtils from '@libs/ErrorUtils';
+import getPlatform from '@libs/getPlatform';
import Log from '@libs/Log';
import Navigation from '@libs/Navigation/Navigation';
import LocalNotification from '@libs/Notification/LocalNotification';
@@ -76,6 +78,7 @@ import type {Message, ReportActionBase, ReportActions} from '@src/types/onyx/Rep
import type ReportAction from '@src/types/onyx/ReportAction';
import type {EmptyObject} from '@src/types/utils/EmptyObject';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
+import * as CachedPDFPaths from './CachedPDFPaths';
import * as Modal from './Modal';
import * as Session from './Session';
import * as Welcome from './Welcome';
@@ -175,10 +178,27 @@ const typingWatchTimers: Record = {};
let reportIDDeeplinkedFromOldDot: string | undefined;
Linking.getInitialURL().then((url) => {
- const params = new URLSearchParams(url ?? '');
- const exitToRoute = params.get('exitTo') ?? '';
- const {reportID} = ReportUtils.parseReportRouteParams(exitToRoute);
- reportIDDeeplinkedFromOldDot = reportID;
+ const isWeb = ([CONST.PLATFORM.WEB] as unknown as string).includes(getPlatform());
+ const currentParams = new URLSearchParams(url ?? '');
+ const currentExitToRoute = currentParams.get('exitTo') ?? '';
+ const {reportID: currentReportID} = ReportUtils.parseReportRouteParams(currentExitToRoute);
+
+ if (!isWeb) {
+ reportIDDeeplinkedFromOldDot = currentReportID;
+
+ return;
+ }
+
+ const prevUrl = sessionStorage.getItem(CONST.SESSION_STORAGE_KEYS.INITIAL_URL);
+ const prevParams = new URLSearchParams(prevUrl ?? '');
+ const prevExitToRoute = prevParams.get('exitTo') ?? '';
+ const {reportID: prevReportID} = ReportUtils.parseReportRouteParams(prevExitToRoute);
+
+ reportIDDeeplinkedFromOldDot = currentReportID || prevReportID;
+
+ if (currentReportID && url) {
+ sessionStorage.setItem(CONST.SESSION_STORAGE_KEYS.INITIAL_URL, url);
+ }
});
let lastVisitedPath: string | undefined;
@@ -355,7 +375,7 @@ function notifyNewAction(reportID: string, accountID?: number, reportActionID?:
* - Adding one attachment
* - Add both a comment and attachment simultaneously
*/
-function addActions(reportID: string, text = '', file?: File) {
+function addActions(reportID: string, text = '', file?: FileObject) {
let reportCommentText = '';
let reportCommentAction: OptimisticAddCommentReportAction | undefined;
let attachmentAction: OptimisticAddCommentReportAction | undefined;
@@ -514,7 +534,7 @@ function addActions(reportID: string, text = '', file?: File) {
}
/** Add an attachment and optional comment. */
-function addAttachment(reportID: string, file: File, text = '') {
+function addAttachment(reportID: string, file: FileObject, text = '') {
addActions(reportID, text, file);
}
@@ -1223,6 +1243,7 @@ function deleteReportComment(reportID: string, reportAction: ReportAction) {
reportActionID,
};
+ CachedPDFPaths.clearByKey(reportActionID);
API.write(WRITE_COMMANDS.DELETE_COMMENT, parameters, {optimisticData, successData, failureData});
}
@@ -1719,7 +1740,7 @@ function updateWriteCapabilityAndNavigate(report: Report, newValue: WriteCapabil
/**
* Navigates to the 1:1 report with Concierge
*/
-function navigateToConciergeChat(shouldDismissModal = false, shouldPopCurrentScreen = false, checkIfCurrentPageActive = () => true) {
+function navigateToConciergeChat(shouldDismissModal = false, checkIfCurrentPageActive = () => true) {
// If conciergeChatReportID contains a concierge report ID, we navigate to the concierge chat using the stored report ID.
// Otherwise, we would find the concierge chat and navigate to it.
if (!conciergeChatReportID) {
@@ -1730,17 +1751,11 @@ function navigateToConciergeChat(shouldDismissModal = false, shouldPopCurrentScr
if (!checkIfCurrentPageActive()) {
return;
}
- if (shouldPopCurrentScreen && !shouldDismissModal) {
- Navigation.goBack();
- }
navigateToAndOpenReport([CONST.EMAIL.CONCIERGE], shouldDismissModal);
});
} else if (shouldDismissModal) {
Navigation.dismissModal(conciergeChatReportID);
} else {
- if (shouldPopCurrentScreen) {
- Navigation.goBack();
- }
Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(conciergeChatReportID));
}
}
@@ -2254,6 +2269,7 @@ function leaveRoom(reportID: string, isWorkspaceMemberLeavingWorkspaceRoom = fal
if (!report) {
return;
}
+ const isChatThread = ReportUtils.isChatThread(report);
// Pusher's leavingStatus should be sent earlier.
// Place the broadcast before calling the LeaveRoom API to prevent a race condition
@@ -2262,20 +2278,22 @@ function leaveRoom(reportID: string, isWorkspaceMemberLeavingWorkspaceRoom = fal
// If a workspace member is leaving a workspace room, they don't actually lose the room from Onyx.
// Instead, their notification preference just gets set to "hidden".
+ // Same applies for chat threads too
const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
- value: isWorkspaceMemberLeavingWorkspaceRoom
- ? {
- notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN,
- }
- : {
- reportID: null,
- stateNum: CONST.REPORT.STATE_NUM.APPROVED,
- statusNum: CONST.REPORT.STATUS_NUM.CLOSED,
- notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN,
- },
+ value:
+ isWorkspaceMemberLeavingWorkspaceRoom || isChatThread
+ ? {
+ notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN,
+ }
+ : {
+ reportID: null,
+ stateNum: CONST.REPORT.STATE_NUM.APPROVED,
+ statusNum: CONST.REPORT.STATUS_NUM.CLOSED,
+ notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN,
+ },
},
];
@@ -2283,12 +2301,13 @@ function leaveRoom(reportID: string, isWorkspaceMemberLeavingWorkspaceRoom = fal
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
- value: isWorkspaceMemberLeavingWorkspaceRoom
- ? {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN}
- : Object.keys(report).reduce>((acc, key) => {
- acc[key] = null;
- return acc;
- }, {}),
+ value:
+ isWorkspaceMemberLeavingWorkspaceRoom || isChatThread
+ ? {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN}
+ : Object.keys(report).reduce>((acc, key) => {
+ acc[key] = null;
+ return acc;
+ }, {}),
},
];
@@ -2344,15 +2363,19 @@ function leaveRoom(reportID: string, isWorkspaceMemberLeavingWorkspaceRoom = fal
const lastAccessedReportID = filteredReportsByLastRead.at(-1)?.reportID;
if (lastAccessedReportID) {
- // We should call Navigation.goBack to pop the current route first before navigating to Concierge.
- Navigation.goBack();
+ // If it is not a chat thread we should call Navigation.goBack to pop the current route first before navigating to last accessed report.
+ if (!isChatThread) {
+ Navigation.goBack();
+ }
Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(lastAccessedReportID));
} else {
const participantAccountIDs = PersonalDetailsUtils.getAccountIDsByLogins([CONST.EMAIL.CONCIERGE]);
const chat = ReportUtils.getChatByParticipants(participantAccountIDs);
if (chat?.reportID) {
- // We should call Navigation.goBack to pop the current route first before navigating to Concierge.
- Navigation.goBack();
+ // If it is not a chat thread we should call Navigation.goBack to pop the current route first before navigating to Concierge.
+ if (!isChatThread) {
+ Navigation.goBack();
+ }
Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(chat.reportID));
}
}
diff --git a/src/libs/actions/Session/clearCache/index.ts b/src/libs/actions/Session/clearCache/index.ts
index 6d288c6cbd3b..3daa8ec2d7d7 100644
--- a/src/libs/actions/Session/clearCache/index.ts
+++ b/src/libs/actions/Session/clearCache/index.ts
@@ -1,5 +1,8 @@
import type ClearCache from './types';
-const clearStorage: ClearCache = () => new Promise((res) => res());
+const clearStorage: ClearCache = () =>
+ new Promise((resolve) => {
+ resolve();
+ });
export default clearStorage;
diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts
index 74e6a6c78eeb..9c9384912251 100644
--- a/src/libs/actions/Task.ts
+++ b/src/libs/actions/Task.ts
@@ -386,7 +386,7 @@ function editTask(report: OnyxTypes.Report, {title, description}: OnyxTypes.Task
const editTaskReportAction = ReportUtils.buildOptimisticEditedTaskReportAction(currentUserEmail);
// Sometimes title or description is undefined, so we need to check for that, and we provide it to multiple functions
- const reportName = (title ?? report?.reportName).trim();
+ const reportName = (title ?? report?.reportName)?.trim();
// Description can be unset, so we default to an empty string if so
const reportDescription = (description ?? report.description ?? '').trim();
diff --git a/src/libs/actions/User.ts b/src/libs/actions/User.ts
index d7cef2aca546..5d089ed6e393 100644
--- a/src/libs/actions/User.ts
+++ b/src/libs/actions/User.ts
@@ -33,7 +33,7 @@ import playSoundExcludingMobile from '@libs/Sound/playSoundExcludingMobile';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
-import type {FrequentlyUsedEmoji} from '@src/types/onyx';
+import type {BlockedFromConcierge, FrequentlyUsedEmoji} from '@src/types/onyx';
import type Login from '@src/types/onyx/Login';
import type {OnyxServerUpdate} from '@src/types/onyx/OnyxUpdatesFromServer';
import type OnyxPersonalDetails from '@src/types/onyx/PersonalDetails';
@@ -47,8 +47,6 @@ import * as OnyxUpdates from './OnyxUpdates';
import * as Report from './Report';
import * as Session from './Session';
-type BlockedFromConciergeNVP = {expiresAt: number};
-
let currentUserAccountID = -1;
let currentEmail = '';
Onyx.connect({
@@ -447,7 +445,7 @@ function validateSecondaryLogin(contactMethod: string, validateCode: string) {
* and if so whether the expiresAt date of a user's ban is before right now
*
*/
-function isBlockedFromConcierge(blockedFromConciergeNVP: OnyxEntry): boolean {
+function isBlockedFromConcierge(blockedFromConciergeNVP: OnyxEntry): boolean {
if (isEmptyObject(blockedFromConciergeNVP)) {
return false;
}
diff --git a/src/libs/calculateAnchorPosition.ts b/src/libs/calculateAnchorPosition.ts
index 0f1e383522eb..3dc5924d023a 100644
--- a/src/libs/calculateAnchorPosition.ts
+++ b/src/libs/calculateAnchorPosition.ts
@@ -16,13 +16,15 @@ type AnchorOrigin = {
export default function calculateAnchorPosition(anchorComponent: View | RNText, anchorOrigin?: AnchorOrigin): Promise {
return new Promise((resolve) => {
if (!anchorComponent) {
- return resolve({horizontal: 0, vertical: 0});
+ resolve({horizontal: 0, vertical: 0});
+ return;
}
anchorComponent.measureInWindow((x, y, width, height) => {
if (anchorOrigin?.vertical === CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP && anchorOrigin?.horizontal === CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT) {
- return resolve({horizontal: x, vertical: y + height + (anchorOrigin?.shiftVertical ?? 0)});
+ resolve({horizontal: x, vertical: y + height + (anchorOrigin?.shiftVertical ?? 0)});
+ return;
}
- return resolve({horizontal: x + width, vertical: y + (anchorOrigin?.shiftVertical ?? 0)});
+ resolve({horizontal: x + width, vertical: y + (anchorOrigin?.shiftVertical ?? 0)});
});
});
}
diff --git a/src/libs/fileDownload/FileUtils.ts b/src/libs/fileDownload/FileUtils.ts
index 70ab01f62466..06bd47f3b39b 100644
--- a/src/libs/fileDownload/FileUtils.ts
+++ b/src/libs/fileDownload/FileUtils.ts
@@ -159,7 +159,7 @@ function appendTimeToFileName(fileName: string): string {
* @param path - the blob url of the locally uploaded file
* @param fileName - name of the file to read
*/
-const readFileAsync: ReadFileAsync = (path, fileName, onSuccess, onFailure = () => {}) =>
+const readFileAsync: ReadFileAsync = (path, fileName, onSuccess, onFailure = () => {}, fileType = '') =>
new Promise((resolve) => {
if (!path) {
resolve();
@@ -176,7 +176,9 @@ const readFileAsync: ReadFileAsync = (path, fileName, onSuccess, onFailure = ()
}
res.blob()
.then((blob) => {
- const file = new File([blob], cleanFileName(fileName), {type: blob.type});
+ // On Android devices, fetching blob for a file with name containing spaces fails to retrieve the type of file.
+ // In this case, let us fallback on fileType provided by the caller of this function.
+ const file = new File([blob], cleanFileName(fileName), {type: blob.type || fileType});
file.source = path;
// For some reason, the File object on iOS does not have a uri property
// so images aren't uploaded correctly to the backend
diff --git a/src/libs/fileDownload/types.ts b/src/libs/fileDownload/types.ts
index 6d92bddd5816..f09ce495795b 100644
--- a/src/libs/fileDownload/types.ts
+++ b/src/libs/fileDownload/types.ts
@@ -8,7 +8,7 @@ type GetImageResolution = (url: File | Asset) => Promise;
type ExtensionAndFileName = {fileName: string; fileExtension: string};
type SplitExtensionFromFileName = (fileName: string) => ExtensionAndFileName;
-type ReadFileAsync = (path: string, fileName: string, onSuccess: (file: File) => void, onFailure: (error?: unknown) => void) => Promise;
+type ReadFileAsync = (path: string, fileName: string, onSuccess: (file: File) => void, onFailure: (error?: unknown) => void, fileType?: string) => Promise;
type AttachmentDetails = {
previewSourceURL: null | string;
diff --git a/src/pages/ConciergePage.tsx b/src/pages/ConciergePage.tsx
index 4abf8f0d2033..10040a6c3146 100644
--- a/src/pages/ConciergePage.tsx
+++ b/src/pages/ConciergePage.tsx
@@ -40,7 +40,7 @@ function ConciergePage({session}: ConciergePageProps) {
if (isUnmounted.current) {
return;
}
- Report.navigateToConciergeChat(undefined, true, () => !isUnmounted.current);
+ Report.navigateToConciergeChat(true, () => !isUnmounted.current);
});
} else {
Navigation.navigate();
diff --git a/src/pages/EnablePayments/TermsStep.js b/src/pages/EnablePayments/TermsStep.js
index a09e1801c3b0..9fa3a4becea3 100644
--- a/src/pages/EnablePayments/TermsStep.js
+++ b/src/pages/EnablePayments/TermsStep.js
@@ -7,6 +7,7 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton';
import Text from '@components/Text';
import TextLink from '@components/TextLink';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
+import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import compose from '@libs/compose';
import * as ErrorUtils from '@libs/ErrorUtils';
@@ -32,6 +33,30 @@ const defaultProps = {
walletTerms: {},
};
+function HaveReadAndAgreeLabel() {
+ const {translate} = useLocalize();
+
+ return (
+
+ {`${translate('termsStep.haveReadAndAgree')}`}
+ {`${translate('termsStep.electronicDisclosures')}.`}
+
+ );
+}
+
+function AgreeToTheLabel() {
+ const {translate} = useLocalize();
+
+ return (
+
+ {`${translate('termsStep.agreeToThe')} `}
+ {`${translate('common.privacy')} `}
+ {`${translate('common.and')} `}
+ {`${translate('termsStep.walletAgreement')}.`}
+
+ );
+}
+
function TermsStep(props) {
const styles = useThemeStyles();
const [hasAcceptedDisclosure, setHasAcceptedDisclosure] = useState(false);
@@ -71,27 +96,12 @@ function TermsStep(props) {
accessibilityLabel={props.translate('termsStep.haveReadAndAgree')}
style={[styles.mb4, styles.mt4]}
onInputChange={toggleDisclosure}
- LabelComponent={() => (
-
- {`${props.translate('termsStep.haveReadAndAgree')}`}
- {`${props.translate('termsStep.electronicDisclosures')}.`}
-
- )}
+ LabelComponent={HaveReadAndAgreeLabel}
/>
(
-
- {`${props.translate('termsStep.agreeToThe')} `}
-
- {`${props.translate('common.privacy')} `}
-
- {`${props.translate('common.and')} `}
-
- {`${props.translate('termsStep.walletAgreement')}.`}
-
- )}
+ LabelComponent={AgreeToTheLabel}
/>
void;
-const defaultProps = {
- onBackButtonPress: undefined,
+ title: string;
};
-function LoadingPage(props) {
+function LoadingPage({onBackButtonPress, title}: LoadingPageProps) {
const styles = useThemeStyles();
return (
@@ -30,7 +26,5 @@ function LoadingPage(props) {
}
LoadingPage.displayName = 'LoadingPage';
-LoadingPage.propTypes = propTypes;
-LoadingPage.defaultProps = defaultProps;
export default LoadingPage;
diff --git a/src/pages/NewChatPage.tsx b/src/pages/NewChatPage.tsx
index 054f229ac16a..72393e89ae1a 100755
--- a/src/pages/NewChatPage.tsx
+++ b/src/pages/NewChatPage.tsx
@@ -273,6 +273,7 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, isSearchingF
textInputLabel={translate('optionsSelector.nameEmailOrPhoneNumber')}
safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle}
isLoadingNewOptions={isSearchingForReports}
+ autoFocus={false}
/>