diff --git a/android/app/build.gradle b/android/app/build.gradle
index 491c810cb350..0594d6afc211 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -110,8 +110,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1009003902
- versionName "9.0.39-2"
+ versionCode 1009003904
+ versionName "9.0.39-4"
// Supported language variants must be declared here to avoid from being removed during the compilation.
// This also helps us to not include unnecessary language variants in the APK.
resConfigs "en", "es"
diff --git a/android/build.gradle b/android/build.gradle
index 85e547439cc1..fd3f9997612e 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -29,7 +29,7 @@ buildscript {
classpath("com.google.firebase:firebase-crashlytics-gradle:2.7.1")
classpath("com.google.firebase:perf-plugin:1.4.1")
// Fullstory integration
- classpath ("com.fullstory:gradle-plugin-local:1.49.0")
+ classpath ("com.fullstory:gradle-plugin-local:1.52.0")
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
diff --git a/assets/images/expensify-card.svg b/assets/images/expensify-card.svg
index 52f55778b2bd..2989f5025ae4 100644
--- a/assets/images/expensify-card.svg
+++ b/assets/images/expensify-card.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/babel.config.js b/babel.config.js
index 3721edaa7afb..663eb29d5d2f 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -21,6 +21,7 @@ const defaultPlugins = [
'@babel/transform-runtime',
'@babel/plugin-proposal-class-properties',
+ ['@babel/plugin-transform-object-rest-spread', {useBuiltIns: true, loose: true}],
// We use `@babel/plugin-transform-class-properties` for transforming ReactNative libraries and do not use it for our own
// source code transformation as we do not use class property assignment.
diff --git a/docs/articles/new-expensify/expenses-&-payments/Connect-a-Personal-Bank-Account.md b/docs/articles/new-expensify/expenses-&-payments/Connect-a-Personal-Bank-Account.md
index b8e66c937a0a..2d33552f3e3a 100644
--- a/docs/articles/new-expensify/expenses-&-payments/Connect-a-Personal-Bank-Account.md
+++ b/docs/articles/new-expensify/expenses-&-payments/Connect-a-Personal-Bank-Account.md
@@ -8,7 +8,10 @@ Connecting a personal bank account to Expensify allows you to get reimbursed for
1. Click your profile image or icon in the bottom left menu.
2. Click **Wallet**.
3. Click **Add Bank Account**.
-4. Click **Continue** (this will open a new window and redirect you to Plaid).
+
+ ![Wallet Settings, showing where to connect a bank account](https://help.expensify.com/assets/images/addbankaccount_01.png){:width="100%"}
+
+5. Click **Continue** (this will open a new window and redirect you to Plaid).
{% include info.html %}
Plaid is an encrypted third-party financial data platform that Expensify uses to securely verify your banking information.
@@ -19,4 +22,6 @@ Plaid is an encrypted third-party financial data platform that Expensify uses to
7. Choose which account you want to connect to Expensify.
8. Click **Save & continue**.
+ ![Wallet Settings, showing bank account connected](https://help.expensify.com/assets/images/addbankaccount_03.png){:width="100%"}
+
Once connected, all payments and reimbursements will be deposited directly into that bank account.
diff --git a/docs/assets/images/invoices_01.png b/docs/assets/images/invoices_01.png
new file mode 100644
index 000000000000..fc6d5587bb03
Binary files /dev/null and b/docs/assets/images/invoices_01.png differ
diff --git a/docs/assets/images/invoices_02.png b/docs/assets/images/invoices_02.png
new file mode 100644
index 000000000000..29038987c18a
Binary files /dev/null and b/docs/assets/images/invoices_02.png differ
diff --git a/docs/assets/images/invoices_03.png b/docs/assets/images/invoices_03.png
new file mode 100644
index 000000000000..fd78aa731784
Binary files /dev/null and b/docs/assets/images/invoices_03.png differ
diff --git a/docs/assets/images/invoices_04.png b/docs/assets/images/invoices_04.png
new file mode 100644
index 000000000000..d2e301a9d1a5
Binary files /dev/null and b/docs/assets/images/invoices_04.png differ
diff --git a/docs/assets/images/invoices_05.png b/docs/assets/images/invoices_05.png
new file mode 100644
index 000000000000..8eae5efaa9df
Binary files /dev/null and b/docs/assets/images/invoices_05.png differ
diff --git a/docs/assets/images/invoices_06.png b/docs/assets/images/invoices_06.png
new file mode 100644
index 000000000000..2858227891eb
Binary files /dev/null and b/docs/assets/images/invoices_06.png differ
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index 7bed47c032af..5f6051c85745 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -40,7 +40,7 @@
CFBundleVersion
- 9.0.39.2
+ 9.0.39.4
FullStory
OrgId
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index 6e01c0d2f912..c29a15f26438 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -19,6 +19,6 @@
CFBundleSignature
????
CFBundleVersion
- 9.0.39.2
+ 9.0.39.4
diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist
index 74158e9d574a..2b43cf12b38a 100644
--- a/ios/NotificationServiceExtension/Info.plist
+++ b/ios/NotificationServiceExtension/Info.plist
@@ -13,7 +13,7 @@
CFBundleShortVersionString
9.0.39
CFBundleVersion
- 9.0.39.2
+ 9.0.39.4
NSExtension
NSExtensionPointIdentifier
diff --git a/ios/Podfile b/ios/Podfile
index 2ed1752abf4f..e807089c26b9 100644
--- a/ios/Podfile
+++ b/ios/Podfile
@@ -121,4 +121,4 @@ target 'NotificationServiceExtension' do
pod 'AirshipServiceExtension'
end
-pod 'FullStory', :http => 'https://ios-releases.fullstory.com/fullstory-1.49.0-xcframework.tar.gz'
\ No newline at end of file
+pod 'FullStory', :http => 'https://ios-releases.fullstory.com/fullstory-1.52.0-xcframework.tar.gz'
\ No newline at end of file
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 0f1a42791d1e..a801a7c4de1c 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -143,8 +143,8 @@ PODS:
- GoogleUtilities/Environment (~> 7.7)
- "GoogleUtilities/NSData+zlib (~> 7.7)"
- fmt (9.1.0)
- - FullStory (1.49.0)
- - fullstory_react-native (1.4.2):
+ - FullStory (1.52.0)
+ - fullstory_react-native (1.7.1):
- DoubleConversion
- FullStory (~> 1.14)
- glog
@@ -2700,7 +2700,7 @@ DEPENDENCIES:
- ExpoModulesCore (from `../node_modules/expo-modules-core`)
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`)
- fmt (from `../node_modules/react-native/third-party-podspecs/fmt.podspec`)
- - "FullStory (from `{:http=>\"https://ios-releases.fullstory.com/fullstory-1.49.0-xcframework.tar.gz\"}`)"
+ - "FullStory (from `{:http=>\"https://ios-releases.fullstory.com/fullstory-1.52.0-xcframework.tar.gz\"}`)"
- "fullstory_react-native (from `../node_modules/@fullstory/react-native`)"
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
- hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)
@@ -2874,7 +2874,7 @@ EXTERNAL SOURCES:
fmt:
:podspec: "../node_modules/react-native/third-party-podspecs/fmt.podspec"
FullStory:
- :http: https://ios-releases.fullstory.com/fullstory-1.49.0-xcframework.tar.gz
+ :http: https://ios-releases.fullstory.com/fullstory-1.52.0-xcframework.tar.gz
fullstory_react-native:
:path: "../node_modules/@fullstory/react-native"
glog:
@@ -3089,7 +3089,7 @@ EXTERNAL SOURCES:
CHECKOUT OPTIONS:
FullStory:
- :http: https://ios-releases.fullstory.com/fullstory-1.49.0-xcframework.tar.gz
+ :http: https://ios-releases.fullstory.com/fullstory-1.52.0-xcframework.tar.gz
SPEC CHECKSUMS:
Airship: bb32ff2c5a811352da074480357d9f02dbb8f327
@@ -3116,8 +3116,8 @@ SPEC CHECKSUMS:
FirebasePerformance: 0c01a7a496657d7cea86d40c0b1725259d164c6c
FirebaseRemoteConfig: 2d6e2cfdb49af79535c8af8a80a4a5009038ec2b
fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120
- FullStory: c95f74445f871bc344cdc4a4e4ece61b5554e55d
- fullstory_react-native: 1818ee93dc38801665f26869f7ad68abb698a89a
+ FullStory: c8a10b2358c0d33c57be84d16e4c440b0434b33d
+ fullstory_react-native: 44dc2c85a6316df2713e6cb0048ce5719c3b0bab
glog: 69ef571f3de08433d766d614c73a9838a06bf7eb
GoogleAppMeasurement: 5ba1164e3c844ba84272555e916d0a6d3d977e91
GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a
@@ -3248,6 +3248,6 @@ SPEC CHECKSUMS:
VisionCamera: c6c8aa4b028501fc87644550fbc35a537d4da3fb
Yoga: a1d7895431387402a674fd0d1c04ec85e87909b8
-PODFILE CHECKSUM: e479ec84cb53e5fd463486d71dfee91708d3fd9a
+PODFILE CHECKSUM: a07e55247056ec5d84d1af31d694506efff3cfe2
COCOAPODS: 1.15.2
diff --git a/package-lock.json b/package-lock.json
index 3c27c44a3bd7..c1896b581645 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "9.0.39-2",
+ "version": "9.0.39-4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "9.0.39-2",
+ "version": "9.0.39-4",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index 6f4980f04ee0..577eac08c6d5 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "9.0.39-2",
+ "version": "9.0.39-4",
"author": "Expensify, Inc.",
"homepage": "https://new.expensify.com",
"description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",
diff --git a/src/CONST.ts b/src/CONST.ts
index c8cbe72bc39e..9ee9ec4d9147 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -2452,6 +2452,7 @@ const CONST = {
},
},
EXPENSIFY_CARD: {
+ NAME: 'expensifyCard',
BANK: 'Expensify Card',
FRAUD_TYPES: {
DOMAIN: 'domain',
diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts
index 64ad1f660c12..7fcb675dc191 100755
--- a/src/ONYXKEYS.ts
+++ b/src/ONYXKEYS.ts
@@ -207,8 +207,8 @@ const ONYXKEYS = {
/** The NVP containing all information related to educational tooltip in workspace chat */
NVP_WORKSPACE_TOOLTIP: 'workspaceTooltip',
- /** Whether to hide save search rename tooltip */
- NVP_SHOULD_HIDE_SAVED_SEARCH_RENAME_TOOLTIP: 'nvp_should_hide_saved_search_rename_tooltip',
+ /** Whether to show save search rename tooltip */
+ SHOULD_SHOW_SAVED_SEARCH_RENAME_TOOLTIP: 'shouldShowSavedSearchRenameTooltip',
/** Whether to hide gbr tooltip */
NVP_SHOULD_HIDE_GBR_TOOLTIP: 'nvp_should_hide_gbr_tooltip',
@@ -983,7 +983,7 @@ type OnyxValuesMapping = {
[ONYXKEYS.APPROVAL_WORKFLOW]: OnyxTypes.ApprovalWorkflowOnyx;
[ONYXKEYS.IMPORTED_SPREADSHEET]: OnyxTypes.ImportedSpreadsheet;
[ONYXKEYS.LAST_ROUTE]: string;
- [ONYXKEYS.NVP_SHOULD_HIDE_SAVED_SEARCH_RENAME_TOOLTIP]: boolean;
+ [ONYXKEYS.SHOULD_SHOW_SAVED_SEARCH_RENAME_TOOLTIP]: boolean;
};
type OnyxValues = OnyxValuesMapping & OnyxCollectionValuesMapping & OnyxFormValuesMapping & OnyxFormDraftValuesMapping;
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index 9b351bd31899..c0ec944b71e1 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -878,6 +878,10 @@ const ROUTES = {
route: 'settings/workspaces/:policyID/members/:accountID',
getRoute: (policyID: string, accountID: number) => `settings/workspaces/${policyID}/members/${accountID}` as const,
},
+ WORKSPACE_MEMBER_NEW_CARD: {
+ route: 'settings/workspaces/:policyID/members/:accountID/new-card',
+ getRoute: (policyID: string, accountID: number) => `settings/workspaces/${policyID}/members/${accountID}/new-card` as const,
+ },
WORKSPACE_MEMBER_ROLE_SELECTION: {
route: 'settings/workspaces/:policyID/members/:accountID/role-selection',
getRoute: (policyID: string, accountID: number) => `settings/workspaces/${policyID}/members/${accountID}/role-selection` as const,
@@ -960,10 +964,6 @@ const ROUTES = {
route: 'settings/workspaces/:policyID/company-cards/select-feed',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/company-cards/select-feed` as const,
},
- WORKSPACE_EXPENSIFY_CARD: {
- route: 'settings/workspaces/:policyID/expensify-card',
- getRoute: (policyID: string) => `settings/workspaces/${policyID}/expensify-card` as const,
- },
WORKSPACE_COMPANY_CARDS_ASSIGN_CARD: {
route: 'settings/workspaces/:policyID/company-cards/:feed/assign-card',
getRoute: (policyID: string, feed: string) => `settings/workspaces/${policyID}/company-cards/${feed}/assign-card` as const,
@@ -980,6 +980,10 @@ const ROUTES = {
route: 'settings/workspaces/:policyID/company-cards/:bank/:cardID/edit/export',
getRoute: (policyID: string, cardID: string, bank: string) => `settings/workspaces/${policyID}/company-cards/${bank}/${cardID}/edit/export` as const,
},
+ WORKSPACE_EXPENSIFY_CARD: {
+ route: 'settings/workspaces/:policyID/expensify-card',
+ getRoute: (policyID: string) => `settings/workspaces/${policyID}/expensify-card` as const,
+ },
WORKSPACE_EXPENSIFY_CARD_DETAILS: {
route: 'settings/workspaces/:policyID/expensify-card/:cardID',
getRoute: (policyID: string, cardID: string, backTo?: string) => getUrlWithBackToParam(`settings/workspaces/${policyID}/expensify-card/${cardID}`, backTo),
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index da92d2b0940d..920bd48dd42e 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -463,6 +463,7 @@ const SCREENS = {
CATEGORIES_IMPORTED: 'Categories_Imported',
MORE_FEATURES: 'Workspace_More_Features',
MEMBER_DETAILS: 'Workspace_Member_Details',
+ MEMBER_NEW_CARD: 'Workspace_Member_NewCard',
OWNER_CHANGE_CHECK: 'Workspace_Owner_Change_Check',
OWNER_CHANGE_SUCCESS: 'Workspace_Owner_Change_Success',
OWNER_CHANGE_ERROR: 'Workspace_Owner_Change_Error',
diff --git a/src/components/FocusTrap/FocusTrapForScreen/index.web.tsx b/src/components/FocusTrap/FocusTrapForScreen/index.web.tsx
index a51a7d7456e1..14f14aee8c73 100644
--- a/src/components/FocusTrap/FocusTrapForScreen/index.web.tsx
+++ b/src/components/FocusTrap/FocusTrapForScreen/index.web.tsx
@@ -6,7 +6,6 @@ import sharedTrapStack from '@components/FocusTrap/sharedTrapStack';
import TOP_TAB_SCREENS from '@components/FocusTrap/TOP_TAB_SCREENS';
import WIDE_LAYOUT_INACTIVE_SCREENS from '@components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
-import canFocusInputOnScreenFocus from '@libs/canFocusInputOnScreenFocus';
import CONST from '@src/CONST';
import type FocusTrapProps from './FocusTrapProps';
@@ -42,30 +41,15 @@ function FocusTrapForScreen({children, focusTrapSettings}: FocusTrapProps) {
paused={!isFocused}
containerElements={focusTrapSettings?.containerElements?.length ? focusTrapSettings.containerElements : undefined}
focusTrapOptions={{
+ onActivate: () => {
+ (document?.activeElement as HTMLElement)?.blur();
+ },
trapStack: sharedTrapStack,
allowOutsideClick: true,
fallbackFocus: document.body,
delayInitialFocus: CONST.ANIMATED_TRANSITION,
- initialFocus: (focusTrapContainers) => {
- if (!canFocusInputOnScreenFocus()) {
- return false;
- }
-
- const isFocusedElementInsideContainer = !!focusTrapContainers?.some((container) => container.contains(document.activeElement));
- const hasButtonWithEnterListener = !!focusTrapContainers?.some(
- (container) => !!container.querySelector(`button[data-listener="${CONST.KEYBOARD_SHORTCUTS.ENTER.shortcutKey}"]`),
- );
- if (isFocusedElementInsideContainer || hasButtonWithEnterListener) {
- return false;
- }
- return undefined;
- },
- setReturnFocus: (element) => {
- if (document.activeElement && document.activeElement !== document.body) {
- return false;
- }
- return element;
- },
+ initialFocus: false,
+ setReturnFocus: false,
...(focusTrapSettings?.focusTrapOptions ?? {}),
}}
>
diff --git a/src/components/HeaderWithBackButton/index.tsx b/src/components/HeaderWithBackButton/index.tsx
index f1e715bface8..eb04ad5540eb 100755
--- a/src/components/HeaderWithBackButton/index.tsx
+++ b/src/components/HeaderWithBackButton/index.tsx
@@ -7,6 +7,7 @@ import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
import PinButton from '@components/PinButton';
import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback';
+import SearchButton from '@components/Search/SearchRouter/SearchButton';
import ThreeDotsMenu from '@components/ThreeDotsMenu';
import Tooltip from '@components/Tooltip';
import useKeyboardState from '@hooks/useKeyboardState';
@@ -60,6 +61,7 @@ function HeaderWithBackButton({
shouldOverlayDots = false,
shouldOverlay = false,
shouldNavigateToTopMostReport = false,
+ shouldDisplaySearchRouter = false,
progressBarPercentage,
style,
}: HeaderWithBackButtonProps) {
@@ -261,6 +263,7 @@ function HeaderWithBackButton({
)}
+ {shouldDisplaySearchRouter && }
diff --git a/src/components/HeaderWithBackButton/types.ts b/src/components/HeaderWithBackButton/types.ts
index c55a7bddc80c..22885b6ceac5 100644
--- a/src/components/HeaderWithBackButton/types.ts
+++ b/src/components/HeaderWithBackButton/types.ts
@@ -128,6 +128,9 @@ type HeaderWithBackButtonProps = Partial & {
/** Whether we should overlay the 3 dots menu */
shouldOverlayDots?: boolean;
+ /** Whether we should display the button that opens new SearchRouter */
+ shouldDisplaySearchRouter?: boolean;
+
/** 0 - 100 number indicating current progress of the progress bar */
progressBarPercentage?: number;
diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx
index 4fc92d619e68..d5570cb18872 100644
--- a/src/components/MoneyReportHeader.tsx
+++ b/src/components/MoneyReportHeader.tsx
@@ -41,7 +41,7 @@ import SettlementButton from './SettlementButton';
type MoneyReportHeaderProps = {
/** The report currently being looked at */
- report: OnyxTypes.Report;
+ report: OnyxEntry;
/** The policy tied to the expense report */
policy: OnyxEntry;
@@ -61,8 +61,8 @@ type MoneyReportHeaderProps = {
};
function MoneyReportHeader({policy, report: moneyRequestReport, transactionThreadReportID, reportActions, shouldUseNarrowLayout = false, onBackButtonPress}: MoneyReportHeaderProps) {
- const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${moneyRequestReport.chatReportID}`);
- const [nextStep] = useOnyx(`${ONYXKEYS.COLLECTION.NEXT_STEP}${moneyRequestReport.reportID}`);
+ const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${moneyRequestReport?.chatReportID ?? '-1'}`);
+ const [nextStep] = useOnyx(`${ONYXKEYS.COLLECTION.NEXT_STEP}${moneyRequestReport?.reportID ?? '-1'}`);
const [transactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`);
const [session] = useOnyx(ONYXKEYS.SESSION);
const requestParentReportAction = useMemo(() => {
@@ -109,10 +109,10 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
const hasOnlyPendingTransactions = allTransactions.length > 0 && allTransactions.every((t) => TransactionUtils.isExpensifyCardTransaction(t) && TransactionUtils.isPending(t));
const transactionIDs = allTransactions.map((t) => t.transactionID);
const hasAllPendingRTERViolations = TransactionUtils.allHavePendingRTERViolation([transaction?.transactionID ?? '-1']);
- const hasOnlyHeldExpenses = ReportUtils.hasOnlyHeldExpenses(moneyRequestReport.reportID);
+ const hasOnlyHeldExpenses = ReportUtils.hasOnlyHeldExpenses(moneyRequestReport?.reportID ?? '');
const isPayAtEndExpense = TransactionUtils.isPayAtEndExpense(transaction);
const isArchivedReport = ReportUtils.isArchivedRoomWithID(moneyRequestReport?.reportID);
- const [archiveReason] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${moneyRequestReport.reportID}`, {selector: ReportUtils.getArchiveReason});
+ const [archiveReason] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${moneyRequestReport?.reportID ?? '-1'}`, {selector: ReportUtils.getArchiveReason});
const shouldShowPayButton = useMemo(
() => IOU.canIOUBePaid(moneyRequestReport, chatReport, policy, transaction ? [transaction] : undefined),
@@ -123,7 +123,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
const shouldDisableApproveButton = shouldShowApproveButton && !ReportUtils.isAllowedToApproveExpenseReport(moneyRequestReport);
- const shouldShowSubmitButton = isDraft && reimbursableSpend !== 0 && !hasAllPendingRTERViolations;
+ const shouldShowSubmitButton = !!moneyRequestReport && isDraft && reimbursableSpend !== 0 && !hasAllPendingRTERViolations;
const isAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN;
const shouldShowExportIntegrationButton = !shouldShowPayButton && !shouldShowSubmitButton && connectedIntegration && isAdmin && ReportUtils.canBeExported(moneyRequestReport);
@@ -137,9 +137,9 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
const shouldShowAnyButton =
shouldShowSettlementButton || shouldShowApproveButton || shouldShowSubmitButton || shouldShowNextStep || hasAllPendingRTERViolations || shouldShowExportIntegrationButton;
const bankAccountRoute = ReportUtils.getBankAccountRoute(chatReport);
- const formattedAmount = CurrencyUtils.convertToDisplayString(reimbursableSpend, moneyRequestReport.currency);
+ const formattedAmount = CurrencyUtils.convertToDisplayString(reimbursableSpend, moneyRequestReport?.currency);
const [nonHeldAmount, fullAmount] = ReportUtils.getNonHeldAndFullAmount(moneyRequestReport, policy);
- const isAnyTransactionOnHold = ReportUtils.hasHeldExpenses(moneyRequestReport.reportID);
+ const isAnyTransactionOnHold = ReportUtils.hasHeldExpenses(moneyRequestReport?.reportID);
const displayedAmount = isAnyTransactionOnHold && canAllowSettlement ? nonHeldAmount : formattedAmount;
const isMoreContentShown = shouldShowNextStep || shouldShowStatusBar || (shouldShowAnyButton && shouldUseNarrowLayout);
const {isDelegateAccessRestricted, delegatorEmail} = useDelegateUserDetails();
@@ -285,6 +285,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
report={moneyRequestReport}
policy={policy}
shouldShowBackButton={shouldUseNarrowLayout}
+ shouldDisplaySearchRouter
onBackButtonPress={onBackButtonPress}
// Shows border if no buttons or banners are showing below the header
shouldShowBorderBottom={!isMoreContentShown}
@@ -292,9 +293,9 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
{shouldShowSettlementButton && !shouldUseNarrowLayout && (
{shouldShowSettlementButton && shouldUseNarrowLayout && (
({
+ anchor: null,
+ report: undefined,
+ reportNameValuePairs: undefined,
+ action: undefined,
+ checkIfContextMenuActive: () => {},
+ isDisabled: true,
+ }),
+ [],
+ );
+
const mentionReportContextValue = useMemo(() => ({currentReportID: reportID}), [reportID]);
// An intermediate structure that helps us classify the fields as "primary" and "supplementary".
@@ -300,29 +313,31 @@ function MoneyRequestConfirmationListFooter({
},
{
item: (
-
-
+ {
- Navigation.navigate(
- ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams(), reportActionID),
- );
- }}
- style={[styles.moneyRequestMenuItem]}
- titleStyle={styles.flex1}
- disabled={didConfirm}
- interactive={!isReadOnly}
- numberOfLinesTitle={2}
- />
-
+ value={mentionReportContextValue}
+ >
+ {
+ Navigation.navigate(
+ ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams(), reportActionID),
+ );
+ }}
+ style={[styles.moneyRequestMenuItem]}
+ titleStyle={styles.flex1}
+ disabled={didConfirm}
+ interactive={!isReadOnly}
+ numberOfLinesTitle={2}
+ />
+
+
),
shouldShow: true,
isSupplementary: false,
diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx
index 0e0633042a7d..ab7004ce4d17 100644
--- a/src/components/MoneyRequestHeader.tsx
+++ b/src/components/MoneyRequestHeader.tsx
@@ -27,7 +27,7 @@ import ProcessMoneyRequestHoldMenu from './ProcessMoneyRequestHoldMenu';
type MoneyRequestHeaderProps = {
/** The report currently being looked at */
- report: Report;
+ report: OnyxEntry;
/** The policy which the report is tied to */
policy: OnyxEntry;
@@ -43,7 +43,7 @@ type MoneyRequestHeaderProps = {
};
function MoneyRequestHeader({report, parentReportAction, policy, shouldUseNarrowLayout = false, onBackButtonPress}: MoneyRequestHeaderProps) {
- const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`);
+ const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID ?? '-1'}`);
const [transaction] = useOnyx(
`${ONYXKEYS.COLLECTION.TRANSACTION}${
ReportActionsUtils.isMoneyRequestAction(parentReportAction) ? ReportActionsUtils.getOriginalMessage(parentReportAction)?.IOUTransactionID ?? -1 : -1
@@ -58,12 +58,13 @@ function MoneyRequestHeader({report, parentReportAction, policy, shouldUseNarrow
const [shouldShowHoldMenu, setShouldShowHoldMenu] = useState(false);
const isOnHold = TransactionUtils.isOnHold(transaction);
const isDuplicate = TransactionUtils.isDuplicate(transaction?.transactionID ?? '');
+ const reportID = report?.reportID;
const hasAllPendingRTERViolations = TransactionUtils.allHavePendingRTERViolation([transaction?.transactionID ?? '-1']);
const markAsCash = useCallback(() => {
- TransactionActions.markAsCash(transaction?.transactionID ?? '-1', report.reportID);
- }, [report.reportID, transaction?.transactionID]);
+ TransactionActions.markAsCash(transaction?.transactionID ?? '-1', reportID ?? '');
+ }, [reportID, transaction?.transactionID]);
const isScanning = TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction);
@@ -129,10 +130,12 @@ function MoneyRequestHeader({report, parentReportAction, policy, shouldUseNarrow
shouldShowPinButton={false}
report={{
...report,
+ reportID: reportID ?? '',
ownerAccountID: parentReport?.ownerAccountID,
}}
policy={policy}
shouldShowBackButton={shouldUseNarrowLayout}
+ shouldDisplaySearchRouter
onBackButtonPress={onBackButtonPress}
>
{hasAllPendingRTERViolations && !shouldUseNarrowLayout && (
@@ -149,7 +152,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, shouldUseNarrow
text={translate('iou.reviewDuplicates')}
style={[styles.p0, styles.ml2]}
onPress={() => {
- Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_PAGE.getRoute(report.reportID));
+ Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_PAGE.getRoute(reportID ?? ''));
}}
/>
)}
@@ -171,7 +174,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, shouldUseNarrow
text={translate('iou.reviewDuplicates')}
style={[styles.w100, styles.pr0]}
onPress={() => {
- Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_PAGE.getRoute(report.reportID));
+ Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_PAGE.getRoute(reportID ?? ''));
}}
/>
diff --git a/src/components/PercentageForm.tsx b/src/components/PercentageForm.tsx
index 8d9ca950f49c..76e3b19891c4 100644
--- a/src/components/PercentageForm.tsx
+++ b/src/components/PercentageForm.tsx
@@ -1,6 +1,5 @@
import type {ForwardedRef} from 'react';
-import React, {forwardRef, useCallback, useMemo, useRef, useState} from 'react';
-import type {NativeSyntheticEvent, TextInputSelectionChangeEventData} from 'react-native';
+import React, {forwardRef, useCallback, useMemo, useRef} from 'react';
import useLocalize from '@hooks/useLocalize';
import * as MoneyRequestUtils from '@libs/MoneyRequestUtils';
import CONST from '@src/CONST';
@@ -21,14 +20,6 @@ type PercentageFormProps = {
label?: string;
};
-/**
- * Returns the new selection object based on the updated amount's length
- */
-const getNewSelection = (oldSelection: {start: number; end: number}, prevLength: number, newLength: number) => {
- const cursorPosition = oldSelection.end + (newLength - prevLength);
- return {start: cursorPosition, end: cursorPosition};
-};
-
function PercentageForm({value: amount, errorText, onInputChange, label, ...rest}: PercentageFormProps, forwardedRef: ForwardedRef) {
const {toLocaleDigit, numberFormat} = useLocalize();
@@ -36,13 +27,6 @@ function PercentageForm({value: amount, errorText, onInputChange, label, ...rest
const currentAmount = useMemo(() => (typeof amount === 'string' ? amount : ''), [amount]);
- const [selection, setSelection] = useState({
- start: currentAmount.length,
- end: currentAmount.length,
- });
-
- const forwardDeletePressedRef = useRef(false);
-
/**
* Sets the selection and the amount accordingly to the value passed to the input
* @param newAmount - Changed amount from user input
@@ -55,16 +39,13 @@ function PercentageForm({value: amount, errorText, onInputChange, label, ...rest
// Use a shallow copy of selection to trigger setSelection
// More info: https://github.com/Expensify/App/issues/16385
if (!MoneyRequestUtils.validatePercentage(newAmountWithoutSpaces)) {
- setSelection((prevSelection) => ({...prevSelection}));
return;
}
const strippedAmount = MoneyRequestUtils.stripCommaFromAmount(newAmountWithoutSpaces);
- const isForwardDelete = currentAmount.length > strippedAmount.length && forwardDeletePressedRef.current;
- setSelection(getNewSelection(selection, isForwardDelete ? strippedAmount.length : currentAmount.length, strippedAmount.length));
onInputChange?.(strippedAmount);
},
- [currentAmount, onInputChange, selection],
+ [onInputChange],
);
const formattedAmount = MoneyRequestUtils.replaceAllDigits(currentAmount, toLocaleDigit);
@@ -84,10 +65,6 @@ function PercentageForm({value: amount, errorText, onInputChange, label, ...rest
}
textInput.current = ref;
}}
- selection={selection}
- onSelectionChange={(e: NativeSyntheticEvent) => {
- setSelection(e.nativeEvent.selection);
- }}
suffixCharacter="%"
keyboardType={CONST.KEYBOARD_TYPE.DECIMAL_PAD}
// eslint-disable-next-line react/jsx-props-no-spreading
diff --git a/src/components/ProcessMoneyReportHoldMenu.tsx b/src/components/ProcessMoneyReportHoldMenu.tsx
index 4a63714b6157..8cbbd1199b33 100644
--- a/src/components/ProcessMoneyReportHoldMenu.tsx
+++ b/src/components/ProcessMoneyReportHoldMenu.tsx
@@ -25,7 +25,7 @@ type ProcessMoneyReportHoldMenuProps = {
isVisible: boolean;
/** The report currently being looked at */
- moneyRequestReport: OnyxTypes.Report;
+ moneyRequestReport: OnyxEntry;
/** Not held amount of expense report */
nonHeldAmount?: string;
@@ -62,8 +62,8 @@ function ProcessMoneyReportHoldMenu({
const onSubmit = (full: boolean) => {
if (isApprove) {
IOU.approveMoneyRequest(moneyRequestReport, full);
- if (!full && isLinkedTransactionHeld(Navigation.getTopmostReportActionId() ?? '-1', moneyRequestReport.reportID)) {
- Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(moneyRequestReport.reportID));
+ if (!full && isLinkedTransactionHeld(Navigation.getTopmostReportActionId() ?? '-1', moneyRequestReport?.reportID ?? '')) {
+ Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(moneyRequestReport?.reportID ?? ''));
}
} else if (chatReport && paymentType) {
IOU.payMoneyRequest(paymentType, chatReport, moneyRequestReport, full);
diff --git a/src/components/ReportActionItem/MoneyReportView.tsx b/src/components/ReportActionItem/MoneyReportView.tsx
index 6ab2db05c49f..8546aa8165c9 100644
--- a/src/components/ReportActionItem/MoneyReportView.tsx
+++ b/src/components/ReportActionItem/MoneyReportView.tsx
@@ -1,7 +1,7 @@
import {Str} from 'expensify-common';
import React, {useMemo} from 'react';
import type {StyleProp, TextStyle} from 'react-native';
-import {View} from 'react-native';
+import {ActivityIndicator, View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
import Icon from '@components/Icon';
@@ -12,6 +12,7 @@ import SpacerView from '@components/SpacerView';
import Text from '@components/Text';
import UnreadActionIndicator from '@components/UnreadActionIndicator';
import useLocalize from '@hooks/useLocalize';
+import useNetwork from '@hooks/useNetwork';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
@@ -47,6 +48,7 @@ function MoneyReportView({report, policy, isCombinedReport = false, shouldShowTo
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const {translate} = useLocalize();
+ const {isOffline} = useNetwork();
const isSettled = ReportUtils.isSettled(report.reportID);
const isTotalUpdated = ReportUtils.hasUpdatedTotal(report, policy);
@@ -160,12 +162,20 @@ function MoneyReportView({report, policy, isCombinedReport = false, shouldShowTo
/>
)}
-
- {formattedTotalAmount}
-
+ {!isTotalUpdated && !isOffline ? (
+
+ ) : (
+
+ {formattedTotalAmount}
+
+ )}
)}
diff --git a/src/components/ReportActionItem/TaskPreview.tsx b/src/components/ReportActionItem/TaskPreview.tsx
index 7a6e4942b178..053ad0c2c63e 100644
--- a/src/components/ReportActionItem/TaskPreview.tsx
+++ b/src/components/ReportActionItem/TaskPreview.tsx
@@ -1,12 +1,13 @@
import {Str} from 'expensify-common';
import React from 'react';
import {View} from 'react-native';
-import {useOnyx, withOnyx} from 'react-native-onyx';
+import {useOnyx} from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
import Avatar from '@components/Avatar';
import Checkbox from '@components/Checkbox';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
+import {usePersonalDetails} from '@components/OnyxProvider';
import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback';
import RenderHTML from '@components/RenderHTML';
import {showContextMenuForReport} from '@components/ShowContextMenuContext';
@@ -27,45 +28,37 @@ import * as Task from '@userActions/Task';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
-import type {Report, ReportAction} from '@src/types/onyx';
+import type {ReportAction} from '@src/types/onyx';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
-type TaskPreviewOnyxProps = {
- /* Onyx Props */
+type TaskPreviewProps = WithCurrentUserPersonalDetailsProps & {
+ /** The ID of the associated policy */
+ // eslint-disable-next-line react/no-unused-prop-types
+ policyID: string;
+ /** The ID of the associated taskReport */
+ taskReportID: string;
- /* current report of TaskPreview */
- taskReport: OnyxEntry;
-};
-
-type TaskPreviewProps = WithCurrentUserPersonalDetailsProps &
- TaskPreviewOnyxProps & {
- /** The ID of the associated policy */
- // eslint-disable-next-line react/no-unused-prop-types
- policyID: string;
- /** The ID of the associated taskReport */
- taskReportID: string;
-
- /** Whether the task preview is hovered so we can modify its style */
- isHovered: boolean;
+ /** Whether the task preview is hovered so we can modify its style */
+ isHovered: boolean;
- /** The linked reportAction */
- action: OnyxEntry;
+ /** The linked reportAction */
+ action: OnyxEntry;
- /** The chat report associated with taskReport */
- chatReportID: string;
+ /** The chat report associated with taskReport */
+ chatReportID: string;
- /** Popover context menu anchor, used for showing context menu */
- contextMenuAnchor: ContextMenuAnchor;
+ /** Popover context menu anchor, used for showing context menu */
+ contextMenuAnchor: ContextMenuAnchor;
- /** Callback for updating context menu active state, used for showing context menu */
- checkIfContextMenuActive: () => void;
- };
+ /** Callback for updating context menu active state, used for showing context menu */
+ checkIfContextMenuActive: () => void;
+};
-function TaskPreview({taskReport, taskReportID, action, contextMenuAnchor, chatReportID, checkIfContextMenuActive, currentUserPersonalDetails, isHovered = false}: TaskPreviewProps) {
+function TaskPreview({taskReportID, action, contextMenuAnchor, chatReportID, checkIfContextMenuActive, currentUserPersonalDetails, isHovered = false}: TaskPreviewProps) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const {translate} = useLocalize();
-
+ const [taskReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`);
// The reportAction might not contain details regarding the taskReport
// Only the direct parent reportAction will contain details about the taskReport
// Other linked reportActions will only contain the taskReportID and we will grab the details from there
@@ -74,7 +67,8 @@ function TaskPreview({taskReport, taskReportID, action, contextMenuAnchor, chatR
: action?.childStateNum === CONST.REPORT.STATE_NUM.APPROVED && action?.childStatusNum === CONST.REPORT.STATUS_NUM.APPROVED;
const taskTitle = Str.htmlEncode(TaskUtils.getTaskTitle(taskReportID, action?.childReportName ?? ''));
const taskAssigneeAccountID = Task.getTaskAssigneeAccountID(taskReport) ?? action?.childManagerAccountID ?? -1;
- const [avatar] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {selector: (personalDetails) => personalDetails?.[taskAssigneeAccountID]?.avatar});
+ const personalDetails = usePersonalDetails();
+ const avatar = personalDetails?.[taskAssigneeAccountID]?.avatar ?? Expensicons.FallbackAvatar;
const htmlForTaskPreview = `${taskTitle}`;
const isDeletedParentAction = ReportUtils.isCanceledTaskReport(taskReport, action);
@@ -134,10 +128,4 @@ function TaskPreview({taskReport, taskReportID, action, contextMenuAnchor, chatR
TaskPreview.displayName = 'TaskPreview';
-export default withCurrentUserPersonalDetails(
- withOnyx({
- taskReport: {
- key: ({taskReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`,
- },
- })(TaskPreview),
-);
+export default withCurrentUserPersonalDetails(TaskPreview);
diff --git a/src/components/Search/SearchPageHeader.tsx b/src/components/Search/SearchPageHeader.tsx
index 73829989409c..704f72055410 100644
--- a/src/components/Search/SearchPageHeader.tsx
+++ b/src/components/Search/SearchPageHeader.tsx
@@ -33,6 +33,7 @@ import type {SearchDataTypes, SearchReport} from '@src/types/onyx/SearchResults'
import type DeepValueOf from '@src/types/utils/DeepValueOf';
import type IconAsset from '@src/types/utils/IconAsset';
import {useSearchContext} from './SearchContext';
+import SearchButton from './SearchRouter/SearchButton';
import type {SearchQueryJSON} from './types';
type HeaderWrapperProps = Pick & {
@@ -295,11 +296,13 @@ function SearchPageHeader({queryJSON, hash, onSelectDeleteOption, setOfflineModa
}
const onPress = () => {
- const values = SearchUtils.getFiltersFormValues(queryJSON);
+ const values = SearchUtils.buildFilterFormValuesFromQuery(queryJSON);
SearchActions.updateAdvancedFilters(values);
Navigation.navigate(ROUTES.SEARCH_ADVANCED_FILTERS);
};
+ const displaySearchRouter = SearchUtils.isCannedSearchQuery(queryJSON);
+
return (
- {headerButtonsOptions.length > 0 ? (
- null}
- shouldAlwaysShowDropdownMenu
- pressOnEnter
- buttonSize={CONST.DROPDOWN_BUTTON_SIZE.MEDIUM}
- customText={translate('workspace.common.selected', {selectedNumber: selectedTransactionsKeys.length})}
- options={headerButtonsOptions}
- isSplitButton={false}
- shouldUseStyleUtilityForAnchorPosition
- />
- ) : (
-
- )}
+ <>
+ {headerButtonsOptions.length > 0 ? (
+ null}
+ shouldAlwaysShowDropdownMenu
+ pressOnEnter
+ buttonSize={CONST.DROPDOWN_BUTTON_SIZE.MEDIUM}
+ customText={translate('workspace.common.selected', {selectedNumber: selectedTransactionsKeys.length})}
+ options={headerButtonsOptions}
+ isSplitButton={false}
+ shouldUseStyleUtilityForAnchorPosition
+ />
+ ) : (
+
+ )}
+ {displaySearchRouter && }
+ >
);
}
diff --git a/src/components/Search/SearchRouter/SearchButton.tsx b/src/components/Search/SearchRouter/SearchButton.tsx
index 4948c90ce3d1..05693ad5ea22 100644
--- a/src/components/Search/SearchRouter/SearchButton.tsx
+++ b/src/components/Search/SearchRouter/SearchButton.tsx
@@ -2,6 +2,7 @@ import React from 'react';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
import {PressableWithoutFeedback} from '@components/Pressable';
+import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import Permissions from '@libs/Permissions';
@@ -10,6 +11,7 @@ import {useSearchRouterContext} from './SearchRouterContext';
function SearchButton() {
const styles = useThemeStyles();
const theme = useTheme();
+ const {translate} = useLocalize();
const {openSearchRouter} = useSearchRouterContext();
if (!Permissions.canUseNewSearchRouter()) {
@@ -18,8 +20,8 @@ function SearchButton() {
return (
{
openSearchRouter();
}}
diff --git a/src/components/Search/SearchRouter/SearchRouter.tsx b/src/components/Search/SearchRouter/SearchRouter.tsx
index 63699d34ce04..dfe2cbbe16c6 100644
--- a/src/components/Search/SearchRouter/SearchRouter.tsx
+++ b/src/components/Search/SearchRouter/SearchRouter.tsx
@@ -14,17 +14,18 @@ import ROUTES from '@src/ROUTES';
import {useSearchRouterContext} from './SearchRouterContext';
import SearchRouterInput from './SearchRouterInput';
-const SEARCH_DEBOUNCE_DELAY = 200;
+const SEARCH_DEBOUNCE_DELAY = 150;
function SearchRouter() {
const styles = useThemeStyles();
const {isSmallScreenWidth} = useResponsiveLayout();
const {isSearchRouterDisplayed, closeSearchRouter} = useSearchRouterContext();
- const [currentQuery, setCurrentQuery] = useState(undefined);
+
+ const [userSearchQuery, setUserSearchQuery] = useState(undefined);
const clearUserQuery = () => {
- setCurrentQuery(undefined);
+ setUserSearchQuery(undefined);
};
const onSearchChange = debounce((userQuery: string) => {
@@ -39,19 +40,24 @@ function SearchRouter() {
// eslint-disable-next-line
console.log('parsedQuery', queryJSON);
- setCurrentQuery(queryJSON);
+ setUserSearchQuery(queryJSON);
} else {
// Handle query parsing error
}
}, SEARCH_DEBOUNCE_DELAY);
const onSearchSubmit = useCallback(() => {
+ if (!userSearchQuery) {
+ return;
+ }
+
closeSearchRouter();
- const query = SearchUtils.buildSearchQueryString(currentQuery);
+ const query = SearchUtils.buildSearchQueryString(userSearchQuery);
Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query}));
+
clearUserQuery();
- }, [currentQuery, closeSearchRouter]);
+ }, [closeSearchRouter, userSearchQuery]);
useKeyboardShortcut(CONST.KEYBOARD_SHORTCUTS.ESCAPE, () => {
closeSearchRouter();
diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx
index 4e23c0883f84..d1080da19932 100644
--- a/src/components/Search/index.tsx
+++ b/src/components/Search/index.tsx
@@ -389,6 +389,7 @@ function Search({queryJSON}: SearchProps) {
getItemHeight={getItemHeightMemoized}
shouldSingleExecuteRowSelect
shouldPreventDefaultFocusOnSelectRow={!DeviceCapabilities.canUseTouchScreen()}
+ shouldPreventDefault={false}
listHeaderWrapperStyle={[styles.ph8, styles.pv3, styles.pb5]}
containerStyle={[styles.pv0]}
showScrollIndicator={false}
diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx
index 9ee264bc9bf9..197c64b99a26 100644
--- a/src/components/SelectionList/BaseSelectionList.tsx
+++ b/src/components/SelectionList/BaseSelectionList.tsx
@@ -71,6 +71,7 @@ function BaseSelectionList(
disableKeyboardShortcuts = false,
children,
shouldStopPropagation = false,
+ shouldPreventDefault = true,
shouldShowTooltips = true,
shouldUseDynamicMaxToRenderPerBatch = false,
rightHandSideComponent,
@@ -623,6 +624,7 @@ function BaseSelectionList(
captureOnInputs: true,
shouldBubble: !flattenedSections.allOptions[focusedIndex],
shouldStopPropagation,
+ shouldPreventDefault,
isActive: !disableKeyboardShortcuts && !disableEnterShortcut && isFocused,
});
diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts
index c1085c71e0f6..14c83ef25ed4 100644
--- a/src/components/SelectionList/types.ts
+++ b/src/components/SelectionList/types.ts
@@ -436,9 +436,12 @@ type BaseSelectionListProps = Partial & {
/** Whether tooltips should be shown */
shouldShowTooltips?: boolean;
- /** Whether to stop automatic form submission on pressing enter key or not */
+ /** Whether to stop automatic propagation on pressing enter key or not */
shouldStopPropagation?: boolean;
+ /** Whether to call preventDefault() on pressing enter key or not */
+ shouldPreventDefault?: boolean;
+
/** Whether to prevent default focusing of options and focus the textinput when selecting an option */
shouldPreventDefaultFocusOnSelectRow?: boolean;
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 4cfe7c59e6cc..7844d12e764a 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -2881,6 +2881,7 @@ export default {
startTransactionDate: 'Start transaction date',
cardName: 'Card name',
assignedYouCard: (assigner: string) => `${assigner} assigned you a company card! Imported transactions will appear in this chat.`,
+ chooseCardFeed: 'Choose card feed',
},
expensifyCard: {
issueAndManageCards: 'Issue and manage your Expensify Cards',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index d6b3c6b20758..362fe5575dd0 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -2926,6 +2926,7 @@ export default {
startTransactionDate: 'Fecha de inicio de transacciones',
cardName: 'Nombre de la tarjeta',
assignedYouCard: (assigner: string) => `¡${assigner} te ha asignado una tarjeta de empresa! Las transacciones importadas aparecerán en este chat.`,
+ chooseCardFeed: 'Elige feed de tarjetas',
},
expensifyCard: {
issueAndManageCards: 'Emitir y gestionar Tarjetas Expensify',
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
index 864d810a4469..4108addac0f3 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
@@ -242,6 +242,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/workspace/categories/ImportedCategoriesPage').default,
[SCREENS.WORKSPACE.UPGRADE]: () => require('../../../../pages/workspace/upgrade/WorkspaceUpgradePage').default,
[SCREENS.WORKSPACE.MEMBER_DETAILS]: () => require('../../../../pages/workspace/members/WorkspaceMemberDetailsPage').default,
+ [SCREENS.WORKSPACE.MEMBER_NEW_CARD]: () => require('../../../../pages/workspace/members/WorkspaceMemberNewCardPage').default,
[SCREENS.WORKSPACE.OWNER_CHANGE_CHECK]: () => require('@pages/workspace/members/WorkspaceOwnerChangeWrapperPage').default,
[SCREENS.WORKSPACE.OWNER_CHANGE_SUCCESS]: () => require('../../../../pages/workspace/members/WorkspaceOwnerChangeSuccessPage').default,
[SCREENS.WORKSPACE.OWNER_CHANGE_ERROR]: () => require('../../../../pages/workspace/members/WorkspaceOwnerChangeErrorPage').default,
diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx
index d06be872c70a..5befa446f6f9 100644
--- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx
+++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx
@@ -14,12 +14,13 @@ import useThemeStyles from '@hooks/useThemeStyles';
import * as Session from '@libs/actions/Session';
import interceptAnonymousUser from '@libs/interceptAnonymousUser';
import Navigation from '@libs/Navigation/Navigation';
-import type {RootStackParamList, State} from '@libs/Navigation/types';
+import type {AuthScreensParamList, RootStackParamList, State} from '@libs/Navigation/types';
import {isCentralPaneName} from '@libs/NavigationUtils';
import * as PolicyUtils from '@libs/PolicyUtils';
import * as SearchUtils from '@libs/SearchUtils';
import type {BrickRoad} from '@libs/WorkspacesSettingsUtils';
import {getChatTabBrickRoad} from '@libs/WorkspacesSettingsUtils';
+import navigationRef from '@navigation/navigationRef';
import BottomTabAvatar from '@pages/home/sidebar/BottomTabAvatar';
import BottomTabBarFloatingActionButton from '@pages/home/sidebar/BottomTabBarFloatingActionButton';
import variables from '@styles/variables';
@@ -113,9 +114,11 @@ function BottomTabBar({selectedTab}: BottomTabBarProps) {
return;
}
interceptAnonymousUser(() => {
- const currentSearchParams = SearchUtils.getCurrentSearchParams();
- if (currentSearchParams) {
- const {q, ...rest} = currentSearchParams;
+ const rootState = navigationRef.getRootState() as State;
+ const lastSearchRoute = rootState.routes.filter((route) => route.name === SCREENS.SEARCH.CENTRAL_PANE).at(-1);
+
+ if (lastSearchRoute) {
+ const {q, ...rest} = lastSearchRoute.params as AuthScreensParamList[typeof SCREENS.SEARCH.CENTRAL_PANE];
const cleanedQuery = handleQueryWithPolicyID(q, activeWorkspaceID);
Navigation.navigate(
diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar.tsx
index 985c16d50c22..4684eb9637be 100644
--- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar.tsx
+++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar.tsx
@@ -23,9 +23,9 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
-type TopBarProps = {breadcrumbLabel: string; activeWorkspaceID?: string; shouldDisplaySearch?: boolean; isCustomSearchQuery?: boolean};
+type TopBarProps = {breadcrumbLabel: string; activeWorkspaceID?: string; shouldDisplaySearch?: boolean; isCustomSearchQuery?: boolean; shouldDisplaySearchRouter?: boolean};
-function TopBar({breadcrumbLabel, activeWorkspaceID, shouldDisplaySearch = true, isCustomSearchQuery = false}: TopBarProps) {
+function TopBar({breadcrumbLabel, activeWorkspaceID, shouldDisplaySearch = true, isCustomSearchQuery = false, shouldDisplaySearchRouter = false}: TopBarProps) {
const styles = useThemeStyles();
const theme = useTheme();
const {translate} = useLocalize();
@@ -74,8 +74,7 @@ function TopBar({breadcrumbLabel, activeWorkspaceID, shouldDisplaySearch = true,
{translate('common.cancel')}
)}
- {/* This is only temporary for development and will be cleaned up in: https://github.com/Expensify/App/issues/49122 */}
-
+ {shouldDisplaySearchRouter && }
{displaySearch && (
> = {
SCREENS.WORKSPACE.INVITE,
SCREENS.WORKSPACE.INVITE_MESSAGE,
SCREENS.WORKSPACE.MEMBER_DETAILS,
+ SCREENS.WORKSPACE.MEMBER_NEW_CARD,
SCREENS.WORKSPACE.OWNER_CHANGE_CHECK,
SCREENS.WORKSPACE.OWNER_CHANGE_SUCCESS,
SCREENS.WORKSPACE.OWNER_CHANGE_ERROR,
diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts
index 69bbe12c857b..f90ddbe2f818 100644
--- a/src/libs/Navigation/linkingConfig/config.ts
+++ b/src/libs/Navigation/linkingConfig/config.ts
@@ -611,6 +611,9 @@ const config: LinkingOptions['config'] = {
[SCREENS.WORKSPACE.MEMBER_DETAILS]: {
path: ROUTES.WORKSPACE_MEMBER_DETAILS.route,
},
+ [SCREENS.WORKSPACE.MEMBER_NEW_CARD]: {
+ path: ROUTES.WORKSPACE_MEMBER_NEW_CARD.route,
+ },
[SCREENS.WORKSPACE.OWNER_CHANGE_SUCCESS]: {
path: ROUTES.WORKSPACE_OWNER_CHANGE_SUCCESS.route,
},
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index e3a4def63e8b..1326a0c86709 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -358,6 +358,10 @@ type SettingsNavigatorParamList = {
policyID: string;
accountID: string;
};
+ [SCREENS.WORKSPACE.MEMBER_NEW_CARD]: {
+ policyID: string;
+ accountID: string;
+ };
[SCREENS.WORKSPACE.OWNER_CHANGE_SUCCESS]: {
policyID: string;
accountID: number;
diff --git a/src/libs/Network/NetworkStore.ts b/src/libs/Network/NetworkStore.ts
index 89b40e63834c..aa9d8c59fb5b 100644
--- a/src/libs/Network/NetworkStore.ts
+++ b/src/libs/Network/NetworkStore.ts
@@ -98,7 +98,23 @@ function getAuthToken(): string | null | undefined {
}
function isSupportRequest(command: string): boolean {
- return [WRITE_COMMANDS.OPEN_APP, SIDE_EFFECT_REQUEST_COMMANDS.RECONNECT_APP, SIDE_EFFECT_REQUEST_COMMANDS.OPEN_REPORT, READ_COMMANDS.SEARCH].some((cmd) => cmd === command);
+ return [
+ WRITE_COMMANDS.OPEN_APP,
+ SIDE_EFFECT_REQUEST_COMMANDS.RECONNECT_APP,
+ SIDE_EFFECT_REQUEST_COMMANDS.OPEN_REPORT,
+ READ_COMMANDS.SEARCH,
+ READ_COMMANDS.OPEN_CARD_DETAILS_PAGE,
+ READ_COMMANDS.OPEN_POLICY_CATEGORIES_PAGE,
+ READ_COMMANDS.OPEN_POLICY_COMPANY_CARDS_PAGE,
+ READ_COMMANDS.OPEN_POLICY_DISTANCE_RATES_PAGE,
+ READ_COMMANDS.OPEN_POLICY_EXPENSIFY_CARDS_PAGE,
+ READ_COMMANDS.OPEN_POLICY_MORE_FEATURES_PAGE,
+ READ_COMMANDS.OPEN_POLICY_PROFILE_PAGE,
+ READ_COMMANDS.OPEN_POLICY_REPORT_FIELDS_PAGE,
+ READ_COMMANDS.OPEN_POLICY_TAGS_PAGE,
+ READ_COMMANDS.OPEN_POLICY_WORKFLOWS_PAGE,
+ READ_COMMANDS.OPEN_SUBSCRIPTION_PAGE,
+ ].some((cmd) => cmd === command);
}
function isSupportAuthToken(): boolean {
diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts
index afedd308371c..50242435ed36 100644
--- a/src/libs/OptionsListUtils.ts
+++ b/src/libs/OptionsListUtils.ts
@@ -496,9 +496,11 @@ function getAllReportErrors(report: OnyxEntry, reportActions: OnyxEntry<
reportActionErrors.smartscan = ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericSmartscanFailureMessage');
}
// All error objects related to the report. Each object in the sources contains error messages keyed by microtime
- // Use Object.assign to merge all error objects into one since it is faster and uses less memory than spread operator
- // eslint-disable-next-line prefer-object-spread
- const errorSources = Object.assign({}, reportErrors, reportErrorFields, reportActionErrors);
+ const errorSources = {
+ reportErrors,
+ ...reportErrorFields,
+ ...reportActionErrors,
+ };
// Combine all error messages keyed by microtime into one object
const errorSourcesArray = Object.values(errorSources ?? {});
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index d38d3f8b950e..5c0451f2ea01 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -4097,12 +4097,12 @@ function getUploadingAttachmentHtml(file?: FileObject): string {
return `${file.name}`;
}
-function getReportDescriptionText(report: Report): string {
- if (!report.description) {
+function getReportDescriptionText(report: OnyxEntry): string {
+ if (!report?.description) {
return '';
}
- return Parser.htmlToText(report.description);
+ return Parser.htmlToText(report?.description);
}
function getPolicyDescriptionText(policy: OnyxEntry): string {
diff --git a/src/libs/SearchUtils.ts b/src/libs/SearchUtils.ts
index 9760ff80ca19..e178c7dcb77b 100644
--- a/src/libs/SearchUtils.ts
+++ b/src/libs/SearchUtils.ts
@@ -10,7 +10,6 @@ import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
-import SCREENS from '@src/SCREENS';
import type {SearchAdvancedFiltersForm} from '@src/types/form';
import FILTER_KEYS from '@src/types/form/SearchAdvancedFiltersForm';
import type * as OnyxTypes from '@src/types/onyx';
@@ -20,8 +19,6 @@ import * as CurrencyUtils from './CurrencyUtils';
import DateUtils from './DateUtils';
import {translateLocal} from './Localize';
import Navigation from './Navigation/Navigation';
-import navigationRef from './Navigation/navigationRef';
-import type {AuthScreensParamList, RootStackParamList, State} from './Navigation/types';
import * as PersonalDetailsUtils from './PersonalDetailsUtils';
import * as ReportActionsUtils from './ReportActionsUtils';
import * as ReportUtils from './ReportUtils';
@@ -64,6 +61,7 @@ const emptyPersonalDetails = {
displayName: undefined,
login: undefined,
};
+/* Search list and results related */
/**
* @private
@@ -391,14 +389,6 @@ function getSortedReportActionData(data: ReportActionListItemType[]) {
});
}
-function getCurrentSearchParams() {
- const rootState = navigationRef.getRootState() as State;
-
- const lastSearchRoute = rootState.routes.filter((route) => route.name === SCREENS.SEARCH.CENTRAL_PANE).at(-1);
-
- return lastSearchRoute ? (lastSearchRoute.params as AuthScreensParamList[typeof SCREENS.SEARCH.CENTRAL_PANE]) : undefined;
-}
-
function isSearchResultsEmpty(searchResults: SearchResults) {
return !Object.keys(searchResults?.data).some((key) => key.startsWith(ONYXKEYS.COLLECTION.TRANSACTION));
}
@@ -407,6 +397,20 @@ function getQueryHashFromString(query: SearchQueryString): number {
return UserUtils.hashText(query, 2 ** 32);
}
+function getExpenseTypeTranslationKey(expenseType: ValueOf): TranslationPaths {
+ // eslint-disable-next-line default-case
+ switch (expenseType) {
+ case CONST.SEARCH.TRANSACTION_TYPE.DISTANCE:
+ return 'common.distance';
+ case CONST.SEARCH.TRANSACTION_TYPE.CARD:
+ return 'common.card';
+ case CONST.SEARCH.TRANSACTION_TYPE.CASH:
+ return 'iou.cash';
+ }
+}
+
+/* Search query related */
+
/**
* Update string query with all the default params that are set by parser
*/
@@ -467,16 +471,58 @@ function sanitizeString(str: string) {
return str;
}
-function getExpenseTypeTranslationKey(expenseType: ValueOf): TranslationPaths {
- // eslint-disable-next-line default-case
- switch (expenseType) {
- case CONST.SEARCH.TRANSACTION_TYPE.DISTANCE:
- return 'common.distance';
- case CONST.SEARCH.TRANSACTION_TYPE.CARD:
- return 'common.card';
- case CONST.SEARCH.TRANSACTION_TYPE.CASH:
- return 'iou.cash';
+/**
+ * @private
+ * traverses the AST and returns filters as a QueryFilters object
+ */
+function getFilters(queryJSON: SearchQueryJSON) {
+ const filters = {} as QueryFilters;
+ const filterKeys = Object.values(CONST.SEARCH.SYNTAX_FILTER_KEYS);
+
+ function traverse(node: ASTNode) {
+ if (!node.operator) {
+ return;
+ }
+
+ if (typeof node?.left === 'object' && node.left) {
+ traverse(node.left);
+ }
+
+ if (typeof node?.right === 'object' && node.right && !Array.isArray(node.right)) {
+ traverse(node.right);
+ }
+
+ const nodeKey = node.left as ValueOf;
+ if (!filterKeys.includes(nodeKey)) {
+ return;
+ }
+
+ if (!filters[nodeKey]) {
+ filters[nodeKey] = [];
+ }
+
+ // the "?? []" is added only for typescript because otherwise TS throws an error, in newer TS versions this should be fixed
+ const filterArray = filters[nodeKey] ?? [];
+ if (!Array.isArray(node.right)) {
+ filterArray.push({
+ operator: node.operator,
+ value: node.right as string | number,
+ });
+ } else {
+ node.right.forEach((element) => {
+ filterArray.push({
+ operator: node.operator,
+ value: element as string | number,
+ });
+ });
+ }
}
+
+ if (queryJSON.filters) {
+ traverse(queryJSON.filters);
+ }
+
+ return filters;
}
function buildSearchQueryJSON(query: SearchQueryString) {
@@ -528,7 +574,7 @@ function buildSearchQueryString(queryJSON?: SearchQueryJSON) {
/**
* Given object with chosen search filters builds correct query string from them
*/
-function buildQueryStringFromFilterValues(filterValues: Partial) {
+function buildQueryStringFromFilterFormValues(filterValues: Partial) {
// We separate type and status filters from other filters to maintain hashes consistency for saved searches
const {type, status, ...otherFilters} = filterValues;
const filtersString: string[] = [];
@@ -592,65 +638,10 @@ function buildQueryStringFromFilterValues(filterValues: Partial;
- if (!filterKeys.includes(nodeKey)) {
- return;
- }
-
- if (!filters[nodeKey]) {
- filters[nodeKey] = [];
- }
-
- // the "?? []" is added only for typescript because otherwise TS throws an error, in newer TS versions this should be fixed
- const filterArray = filters[nodeKey] ?? [];
- if (!Array.isArray(node.right)) {
- filterArray.push({
- operator: node.operator,
- value: node.right as string | number,
- });
- } else {
- node.right.forEach((element) => {
- filterArray.push({
- operator: node.operator,
- value: element as string | number,
- });
- });
- }
- }
-
- if (queryJSON.filters) {
- traverse(queryJSON.filters);
- }
-
- return filters;
-}
-
/**
* returns the values of the filters in a format that can be used in the SearchAdvancedFiltersForm as initial form values
*/
-function getFiltersFormValues(queryJSON: SearchQueryJSON) {
+function buildFilterFormValuesFromQuery(queryJSON: SearchQueryJSON) {
const filters = getFilters(queryJSON);
const filterKeys = Object.keys(filters);
const filtersForm = {} as Partial;
@@ -827,14 +818,12 @@ function isCorrectSearchUserName(displayName?: string) {
}
export {
- buildQueryStringFromFilterValues,
+ buildQueryStringFromFilterFormValues,
buildSearchQueryJSON,
buildSearchQueryString,
- getCurrentSearchParams,
- getFiltersFormValues,
+ buildFilterFormValuesFromQuery,
getPolicyIDFromSearchQuery,
getListItem,
- getSearchHeaderTitle,
getSections,
getShouldShowMerchant,
getSortedSections,
@@ -842,6 +831,7 @@ export {
isSearchResultsEmpty,
isTransactionListItemType,
isReportActionListItemType,
+ getSearchHeaderTitle,
normalizeQuery,
shouldShowYear,
buildCannedSearchQuery,
diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts
index daaa766145ed..4221c9db2897 100644
--- a/src/libs/actions/IOU.ts
+++ b/src/libs/actions/IOU.ts
@@ -6433,7 +6433,7 @@ function getHoldReportActionsAndTransactions(reportID: string) {
function getReportFromHoldRequestsOnyxData(
chatReport: OnyxTypes.Report,
- iouReport: OnyxTypes.Report,
+ iouReport: OnyxEntry,
recipient: Participant,
): {
optimisticHoldReportID: string;
@@ -6442,13 +6442,13 @@ function getReportFromHoldRequestsOnyxData(
optimisticData: OnyxUpdate[];
failureData: OnyxUpdate[];
} {
- const {holdReportActions, holdTransactions} = getHoldReportActionsAndTransactions(iouReport.reportID);
+ const {holdReportActions, holdTransactions} = getHoldReportActionsAndTransactions(iouReport?.reportID ?? '');
const firstHoldTransaction = holdTransactions[0];
const newParentReportActionID = rand64();
const optimisticExpenseReport = ReportUtils.buildOptimisticExpenseReport(
chatReport.reportID,
- chatReport.policyID ?? iouReport.policyID ?? '',
+ chatReport.policyID ?? iouReport?.policyID ?? '',
recipient.accountID ?? 1,
holdTransactions.reduce((acc, transaction) => acc + transaction.amount, 0) * (ReportUtils.isIOUReport(iouReport) ? 1 : -1),
getCurrency(firstHoldTransaction),
@@ -6537,7 +6537,7 @@ function getReportFromHoldRequestsOnyxData(
// remove hold report actions from old iou report
{
onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport?.reportID ?? ''}`,
value: deleteHoldReportActions,
},
// add hold report actions to new iou report
@@ -6588,7 +6588,7 @@ function getReportFromHoldRequestsOnyxData(
// add hold report actions back to old iou report
{
onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport?.reportID ?? ''}`,
value: bringReportActionsBack,
},
// remove hold report actions from the new iou report
@@ -6616,7 +6616,7 @@ function getReportFromHoldRequestsOnyxData(
function getPayMoneyRequestParams(
initialChatReport: OnyxTypes.Report,
- iouReport: OnyxTypes.Report,
+ iouReport: OnyxEntry,
recipient: Participant,
paymentMethodType: PaymentMethodType,
full: boolean,
@@ -6679,34 +6679,34 @@ function getPayMoneyRequestParams(
}
}
- let total = (iouReport.total ?? 0) - (iouReport.nonReimbursableTotal ?? 0);
- if (ReportUtils.hasHeldExpenses(iouReport.reportID) && !full && !!iouReport.unheldTotal) {
- total = iouReport.unheldTotal;
+ let total = (iouReport?.total ?? 0) - (iouReport?.nonReimbursableTotal ?? 0);
+ if (ReportUtils.hasHeldExpenses(iouReport?.reportID ?? '') && !full && !!iouReport?.unheldTotal) {
+ total = iouReport?.unheldTotal;
}
const optimisticIOUReportAction = ReportUtils.buildOptimisticIOUReportAction(
CONST.IOU.REPORT_ACTION_TYPE.PAY,
ReportUtils.isExpenseReport(iouReport) ? -total : total,
- iouReport.currency ?? '',
+ iouReport?.currency ?? '',
'',
[recipient],
'',
paymentMethodType,
- iouReport.reportID,
+ iouReport?.reportID,
true,
);
// In some instances, the report preview action might not be available to the payer (only whispered to the requestor)
// hence we need to make the updates to the action safely.
let optimisticReportPreviewAction = null;
- const reportPreviewAction = getReportPreviewAction(chatReport.reportID, iouReport.reportID);
+ const reportPreviewAction = getReportPreviewAction(chatReport.reportID, iouReport?.reportID ?? '');
if (reportPreviewAction) {
optimisticReportPreviewAction = ReportUtils.updateReportPreview(iouReport, reportPreviewAction, true);
}
let currentNextStep = null;
let optimisticNextStep = null;
if (!isInvoiceReport) {
- currentNextStep = allNextSteps[`${ONYXKEYS.COLLECTION.NEXT_STEP}${iouReport.reportID}`] ?? null;
+ currentNextStep = allNextSteps[`${ONYXKEYS.COLLECTION.NEXT_STEP}${iouReport?.reportID ?? ''}`] ?? null;
optimisticNextStep = NextStepUtils.buildNextStep(iouReport, CONST.REPORT.STATUS_NUM.REIMBURSED);
}
@@ -6734,7 +6734,7 @@ function getPayMoneyRequestParams(
},
{
onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport?.reportID ?? ''}`,
value: {
[optimisticIOUReportAction.reportActionID]: {
...(optimisticIOUReportAction as OnyxTypes.ReportAction),
@@ -6744,7 +6744,7 @@ function getPayMoneyRequestParams(
},
{
onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport?.reportID ?? ''}`,
value: {
...iouReport,
lastMessageText: ReportActionsUtils.getReportActionText(optimisticIOUReportAction),
@@ -6761,18 +6761,18 @@ function getPayMoneyRequestParams(
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.NVP_LAST_PAYMENT_METHOD,
- value: {[iouReport.policyID ?? '-1']: paymentMethodType},
+ value: {[iouReport?.policyID ?? '-1']: paymentMethodType},
},
{
onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${iouReport.reportID}`,
+ key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${iouReport?.reportID ?? ''}`,
value: optimisticNextStep,
},
);
successData.push({
onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport?.reportID ?? ''}`,
value: {
pendingFields: {
preview: null,
@@ -6785,7 +6785,7 @@ function getPayMoneyRequestParams(
failureData.push(
{
onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport?.reportID ?? ''}`,
value: {
[optimisticIOUReportAction.reportActionID]: {
errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.other'),
@@ -6794,7 +6794,7 @@ function getPayMoneyRequestParams(
},
{
onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport?.reportID ?? ''}`,
value: {
...iouReport,
},
@@ -6806,7 +6806,7 @@ function getPayMoneyRequestParams(
},
{
onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${iouReport.reportID}`,
+ key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${iouReport?.reportID ?? ''}`,
value: currentNextStep,
},
);
@@ -6833,7 +6833,7 @@ function getPayMoneyRequestParams(
// Optimistically unhold all transactions if we pay all requests
if (full) {
- const reportTransactions = TransactionUtils.getAllReportTransactions(iouReport.reportID);
+ const reportTransactions = TransactionUtils.getAllReportTransactions(iouReport?.reportID);
for (const transaction of reportTransactions) {
optimisticData.push({
onyxMethod: Onyx.METHOD.MERGE,
@@ -6871,7 +6871,7 @@ function getPayMoneyRequestParams(
return {
params: {
- iouReportID: iouReport.reportID,
+ iouReportID: iouReport?.reportID ?? '',
chatReportID: chatReport.reportID,
reportActionID: optimisticIOUReportAction.reportActionID,
paymentMethodType,
@@ -7530,7 +7530,7 @@ function completePaymentOnboarding(paymentSelected: ValueOf, full = true) {
if (chatReport.policyID && SubscriptionUtils.shouldRestrictUserBillableActions(chatReport.policyID)) {
Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(chatReport.policyID));
return;
@@ -7539,7 +7539,7 @@ function payMoneyRequest(paymentType: PaymentMethodType, chatReport: OnyxTypes.R
const paymentSelected = paymentType === CONST.IOU.PAYMENT_TYPE.VBBA ? CONST.IOU.PAYMENT_SELECTED.BBA : CONST.IOU.PAYMENT_SELECTED.PBA;
completePaymentOnboarding(paymentSelected);
- const recipient = {accountID: iouReport.ownerAccountID};
+ const recipient = {accountID: iouReport?.ownerAccountID ?? -1};
const {params, optimisticData, successData, failureData} = getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentType, full);
// For now, we need to call the PayMoneyRequestWithWallet API since PayMoneyRequest was not updated to work with
@@ -7549,8 +7549,8 @@ function payMoneyRequest(paymentType: PaymentMethodType, chatReport: OnyxTypes.R
API.write(apiCommand, params, {optimisticData, successData, failureData});
}
-function payInvoice(paymentMethodType: PaymentMethodType, chatReport: OnyxTypes.Report, invoiceReport: OnyxTypes.Report, payAsBusiness = false) {
- const recipient = {accountID: invoiceReport.ownerAccountID};
+function payInvoice(paymentMethodType: PaymentMethodType, chatReport: OnyxTypes.Report, invoiceReport: OnyxEntry, payAsBusiness = false) {
+ const recipient = {accountID: invoiceReport?.ownerAccountID ?? -1};
const {
optimisticData,
successData,
@@ -7575,7 +7575,7 @@ function payInvoice(paymentMethodType: PaymentMethodType, chatReport: OnyxTypes.
completePaymentOnboarding(paymentSelected);
let params: PayInvoiceParams = {
- reportID: invoiceReport.reportID,
+ reportID: invoiceReport?.reportID ?? '',
reportActionID,
paymentMethodType,
payAsBusiness,
diff --git a/src/libs/actions/Policy/Member.ts b/src/libs/actions/Policy/Member.ts
index eae625388f33..44ce9ea6f91c 100644
--- a/src/libs/actions/Policy/Member.ts
+++ b/src/libs/actions/Policy/Member.ts
@@ -887,7 +887,7 @@ function declineJoinRequest(reportID: string, reportAction: OnyxEntry void) {
const finalParameters = enhanceParameters(WRITE_COMMANDS.EXPORT_MEMBERS_CSV, {
policyID,
});
@@ -899,7 +899,7 @@ function downloadMembersCSV(policyID: string) {
formData.append(key, String(value));
});
- fileDownload(ApiUtils.getCommandURL({command: WRITE_COMMANDS.EXPORT_MEMBERS_CSV}), fileName, '', false, formData, CONST.NETWORK.METHOD.POST);
+ fileDownload(ApiUtils.getCommandURL({command: WRITE_COMMANDS.EXPORT_MEMBERS_CSV}), fileName, '', false, formData, CONST.NETWORK.METHOD.POST, onDownloadFailed);
}
export {
diff --git a/src/libs/actions/Policy/Tag.ts b/src/libs/actions/Policy/Tag.ts
index d6f67e496b92..f2cd818fd6c1 100644
--- a/src/libs/actions/Policy/Tag.ts
+++ b/src/libs/actions/Policy/Tag.ts
@@ -990,7 +990,7 @@ function setPolicyTagApprover(policyID: string, tag: string, approver: string) {
API.write(WRITE_COMMANDS.SET_POLICY_TAG_APPROVER, parameters, onyxData);
}
-function downloadTagsCSV(policyID: string) {
+function downloadTagsCSV(policyID: string, onDownloadFailed: () => void) {
const finalParameters = enhanceParameters(WRITE_COMMANDS.EXPORT_TAGS_CSV, {
policyID,
});
@@ -1001,7 +1001,7 @@ function downloadTagsCSV(policyID: string) {
formData.append(key, String(value));
});
- fileDownload(ApiUtils.getCommandURL({command: WRITE_COMMANDS.EXPORT_TAGS_CSV}), fileName, '', false, formData, CONST.NETWORK.METHOD.POST);
+ fileDownload(ApiUtils.getCommandURL({command: WRITE_COMMANDS.EXPORT_TAGS_CSV}), fileName, '', false, formData, CONST.NETWORK.METHOD.POST, onDownloadFailed);
}
export {
diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts
index a4f0e59ef976..873603b68739 100644
--- a/src/libs/actions/Search.ts
+++ b/src/libs/actions/Search.ts
@@ -156,8 +156,12 @@ function clearAdvancedFilters() {
Onyx.merge(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM, values);
}
+function showSavedSearchRenameTooltip() {
+ Onyx.set(ONYXKEYS.SHOULD_SHOW_SAVED_SEARCH_RENAME_TOOLTIP, true);
+}
+
function dismissSavedSearchRenameTooltip() {
- Onyx.merge(ONYXKEYS.NVP_SHOULD_HIDE_SAVED_SEARCH_RENAME_TOOLTIP, true);
+ Onyx.set(ONYXKEYS.SHOULD_SHOW_SAVED_SEARCH_RENAME_TOOLTIP, false);
}
export {
@@ -173,4 +177,5 @@ export {
clearAdvancedFilters,
deleteSavedSearch,
dismissSavedSearchRenameTooltip,
+ showSavedSearchRenameTooltip,
};
diff --git a/src/libs/shouldFetchReport.ts b/src/libs/shouldFetchReport.ts
index 5259e88ca6d8..3d364d68deca 100644
--- a/src/libs/shouldFetchReport.ts
+++ b/src/libs/shouldFetchReport.ts
@@ -1,6 +1,7 @@
+import type {OnyxEntry} from 'react-native-onyx';
import type Report from '@src/types/onyx/Report';
-export default function shouldFetchReport(report: Report) {
+export default function shouldFetchReport(report: OnyxEntry) {
// If the report is optimistic, there's no need to fetch it. The original action should create it.
// If there is an error for creating the chat, there's no need to fetch it since it doesn't exist
return !report?.isOptimisticReport && !report?.errorFields?.createChat;
diff --git a/src/pages/AddressPage.tsx b/src/pages/AddressPage.tsx
index 88e52409751b..fc9ae171ccf5 100644
--- a/src/pages/AddressPage.tsx
+++ b/src/pages/AddressPage.tsx
@@ -85,6 +85,7 @@ function AddressPage({title, address, updateAddress, isLoadingApp = true, backTo
title={title}
shouldShowBackButton
onBackButtonPress={() => Navigation.goBack(backTo)}
+ shouldDisplaySearchRouter
/>
{isLoadingApp ? (
diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx
index 8f08b128619a..87ee65a7e2df 100644
--- a/src/pages/Search/AdvancedSearchFilters.tsx
+++ b/src/pages/Search/AdvancedSearchFilters.tsx
@@ -27,6 +27,7 @@ import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {SearchAdvancedFiltersForm} from '@src/types/form';
import type {CardList, PersonalDetailsList, Report} from '@src/types/onyx';
+import {isEmptyObject} from '@src/types/utils/EmptyObject';
const baseFilterConfig = {
date: {
@@ -229,7 +230,7 @@ function AdvancedSearchFilters() {
const personalDetails = usePersonalDetails();
const currentType = searchAdvancedFilters?.type ?? CONST.SEARCH.DATA_TYPES.EXPENSE;
- const queryString = useMemo(() => SearchUtils.buildQueryStringFromFilterValues(searchAdvancedFilters) || '', [searchAdvancedFilters]);
+ const queryString = useMemo(() => SearchUtils.buildQueryStringFromFilterFormValues(searchAdvancedFilters) || '', [searchAdvancedFilters]);
const queryJSON = useMemo(() => SearchUtils.buildSearchQueryJSON(queryString || SearchUtils.buildCannedSearchQuery()) ?? ({} as SearchQueryJSON), [queryString]);
const applyFiltersAndNavigate = () => {
@@ -250,6 +251,10 @@ function AdvancedSearchFilters() {
return;
}
+ if (isEmptyObject(savedSearches)) {
+ SearchActions.showSavedSearchRenameTooltip();
+ }
+
SearchActions.saveSearch({
queryJSON,
});
diff --git a/src/pages/Search/SearchPageBottomTab.tsx b/src/pages/Search/SearchPageBottomTab.tsx
index 9226093154ae..7095413398b8 100644
--- a/src/pages/Search/SearchPageBottomTab.tsx
+++ b/src/pages/Search/SearchPageBottomTab.tsx
@@ -52,6 +52,7 @@ function SearchPageBottomTab() {
activeWorkspaceID={policyID}
breadcrumbLabel={translate('common.search')}
shouldDisplaySearch={false}
+ shouldDisplaySearchRouter={shouldUseNarrowLayout}
isCustomSearchQuery={shouldUseNarrowLayout && !SearchUtils.isCannedSearchQuery(queryJSON)}
/>
diff --git a/src/pages/Search/SearchTypeMenu.tsx b/src/pages/Search/SearchTypeMenu.tsx
index 7c8af2388f52..311168dc5d61 100644
--- a/src/pages/Search/SearchTypeMenu.tsx
+++ b/src/pages/Search/SearchTypeMenu.tsx
@@ -59,7 +59,7 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) {
const {singleExecution} = useSingleExecution();
const {translate} = useLocalize();
const [savedSearches] = useOnyx(ONYXKEYS.SAVED_SEARCHES);
- const [shouldHideSavedSearchRenameTooltip] = useOnyx(ONYXKEYS.NVP_SHOULD_HIDE_SAVED_SEARCH_RENAME_TOOLTIP, {initialValue: true});
+ const [shouldShowSavedSearchRenameTooltip] = useOnyx(ONYXKEYS.SHOULD_SHOW_SAVED_SEARCH_RENAME_TOOLTIP);
const {showDeleteModal, DeleteConfirmModal} = useDeleteSavedSearch();
const personalDetails = usePersonalDetails();
@@ -99,7 +99,7 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) {
[showDeleteModal],
);
- const createSavedSearchMenuItem = (item: SaveSearchItem, key: string, isNarrow: boolean) => {
+ const createSavedSearchMenuItem = (item: SaveSearchItem, key: string, isNarrow: boolean, index: number) => {
let title = item.name;
if (title === item.query) {
const jsonQuery = SearchUtils.buildSearchQueryJSON(item.query) ?? ({} as SearchQueryJSON);
@@ -124,7 +124,7 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) {
if (!isNarrow) {
return {
...baseMenuItem,
- shouldRenderTooltip: !shouldHideSavedSearchRenameTooltip,
+ shouldRenderTooltip: index === 0 && shouldShowSavedSearchRenameTooltip === true,
tooltipAnchorAlignment: {
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT,
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM,
@@ -178,7 +178,7 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) {
if (!savedSearches) {
return [];
}
- return Object.entries(savedSearches).map(([key, item]) => createSavedSearchMenuItem(item as SaveSearchItem, key, shouldUseNarrowLayout));
+ return Object.entries(savedSearches).map(([key, item], index) => createSavedSearchMenuItem(item as SaveSearchItem, key, shouldUseNarrowLayout, index));
};
const renderSavedSearchesSection = useCallback(
diff --git a/src/pages/Search/SearchTypeMenuNarrow.tsx b/src/pages/Search/SearchTypeMenuNarrow.tsx
index 0158a15bfc41..b5d390e2fc53 100644
--- a/src/pages/Search/SearchTypeMenuNarrow.tsx
+++ b/src/pages/Search/SearchTypeMenuNarrow.tsx
@@ -57,7 +57,7 @@ function SearchTypeMenuNarrow({typeMenuItems, activeItemIndex, queryJSON, title,
const openMenu = useCallback(() => setIsPopoverVisible(true), []);
const closeMenu = useCallback(() => setIsPopoverVisible(false), []);
const onPress = () => {
- const values = SearchUtils.getFiltersFormValues(queryJSON);
+ const values = SearchUtils.buildFilterFormValuesFromQuery(queryJSON);
SearchActions.updateAdvancedFilters(values);
Navigation.navigate(ROUTES.SEARCH_ADVANCED_FILTERS);
};
diff --git a/src/pages/TeachersUnite/SaveTheWorldPage.tsx b/src/pages/TeachersUnite/SaveTheWorldPage.tsx
index b4f850981c92..b30fedab530e 100644
--- a/src/pages/TeachersUnite/SaveTheWorldPage.tsx
+++ b/src/pages/TeachersUnite/SaveTheWorldPage.tsx
@@ -56,6 +56,7 @@ function SaveTheWorldPage() {
Navigation.goBack()}
icon={Illustrations.TeachersUnite}
/>
diff --git a/src/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx
index 14ed4b583baf..0f4da0a0e302 100644
--- a/src/pages/home/HeaderView.tsx
+++ b/src/pages/home/HeaderView.tsx
@@ -13,6 +13,7 @@ import OfflineWithFeedback from '@components/OfflineWithFeedback';
import ParentNavigationSubtitle from '@components/ParentNavigationSubtitle';
import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback';
import ReportHeaderSkeletonView from '@components/ReportHeaderSkeletonView';
+import SearchButton from '@components/Search/SearchRouter/SearchButton';
import SubscriptAvatar from '@components/SubscriptAvatar';
import TaskHeaderActionButton from '@components/TaskHeaderActionButton';
import Text from '@components/Text';
@@ -39,7 +40,7 @@ type HeaderViewProps = {
onNavigationMenuButtonClicked: () => void;
/** The report currently being looked at */
- report: OnyxTypes.Report;
+ report: OnyxEntry;
/** The report action the transaction is tied to from the parent report */
parentReportAction: OnyxEntry | null;
@@ -53,9 +54,9 @@ type HeaderViewProps = {
function HeaderView({report, parentReportAction, reportID, onNavigationMenuButtonClicked, shouldUseNarrowLayout = false}: HeaderViewProps) {
const [isDeleteTaskConfirmModalVisible, setIsDeleteTaskConfirmModalVisible] = React.useState(false);
- const [invoiceReceiverPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${report.invoiceReceiver && 'policyID' in report.invoiceReceiver ? report.invoiceReceiver.policyID : -1}`);
+ const [invoiceReceiverPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${report?.invoiceReceiver && 'policyID' in report.invoiceReceiver ? report.invoiceReceiver.policyID : -1}`);
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
- const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID || report?.reportID || '-1'}`);
+ const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID || report?.reportID || '-1'}`);
const policy = usePolicy(report?.policyID);
const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST);
@@ -75,7 +76,7 @@ function HeaderView({report, parentReportAction, reportID, onNavigationMenuButto
const isChatRoom = ReportUtils.isChatRoom(report);
const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(report);
const isTaskReport = ReportUtils.isTaskReport(report);
- const reportHeaderData = !isTaskReport && !isChatThread && report.parentReportID ? parentReport : report;
+ const reportHeaderData = !isTaskReport && !isChatThread && report?.parentReportID ? parentReport : report;
// Use sorted display names for the title for group chats on native small screen widths
const title = ReportUtils.getReportName(reportHeaderData, undefined, parentReportAction, personalDetails, invoiceReceiverPolicy);
const subtitle = ReportUtils.getChatRoomSubtitle(reportHeaderData);
@@ -83,7 +84,7 @@ function HeaderView({report, parentReportAction, reportID, onNavigationMenuButto
const reportDescription = ReportUtils.getReportDescriptionText(report);
const policyName = ReportUtils.getPolicyName(report, true);
const policyDescription = ReportUtils.getPolicyDescriptionText(policy);
- const isPersonalExpenseChat = isPolicyExpenseChat && ReportUtils.isCurrentUserSubmitter(report.reportID);
+ const isPersonalExpenseChat = isPolicyExpenseChat && ReportUtils.isCurrentUserSubmitter(report?.reportID ?? '');
const shouldShowSubtitle = () => {
if (!subtitle) {
return false;
@@ -128,7 +129,7 @@ function HeaderView({report, parentReportAction, reportID, onNavigationMenuButto
const shouldShowBorderBottom = !isTaskReport || !shouldUseNarrowLayout;
const shouldDisableDetailPage = ReportUtils.shouldDisableDetailPage(report);
const shouldUseGroupTitle = isGroupChat && (!!report?.reportName || !isMultipleParticipant);
- const isLoading = !report.reportID || !title;
+ const isLoading = !report?.reportID || !title;
return (
) : (
-
+
)}
@@ -269,6 +270,7 @@ function HeaderView({report, parentReportAction, reportID, onNavigationMenuButto
{isTaskReport && !shouldUseNarrowLayout && ReportUtils.isOpenTaskReport(report, parentReportAction) && }
{canJoin && !shouldUseNarrowLayout && joinButton}
+
);
- if (isSingleTransactionView && report) {
+ if (isSingleTransactionView) {
headerView = (
- {shouldShowMostRecentReportAction && report && (
+ {shouldShowMostRecentReportAction && (
;
-
- emojiReactions: OnyxEntry;
-
- /** The user's wallet account */
- userWallet: OnyxEntry;
-
- /** The transaction (linked with the report action) route error */
- linkedTransactionRouteError: NonNullable> | null;
-};
-
type ReportActionItemProps = {
/** Report for this action */
- report: OnyxTypes.Report;
+ report: OnyxEntry;
/** The transaction thread report associated with the report for this action, if any */
transactionThreadReport?: OnyxEntry;
@@ -150,7 +136,7 @@ type ReportActionItemProps = {
/** Whether context menu should be displayed */
shouldDisplayContextMenu?: boolean;
-} & ReportActionItemOnyxProps;
+};
function ReportActionItem({
action,
@@ -158,19 +144,15 @@ function ReportActionItem({
transactionThreadReport,
linkedReportActionID,
displayAsGroup,
- emojiReactions,
index,
- iouReport,
isMostRecentIOUReportAction,
parentReportAction,
shouldDisplayNewMarker,
- userWallet,
shouldHideThreadDividerLine = false,
shouldShowSubscriptAvatar = false,
onPress = undefined,
isFirstVisibleReportAction = false,
shouldUseThreadDividerLine = false,
- linkedTransactionRouteError,
hideThreadReplies = false,
shouldDisplayContextMenu = true,
parentReportActionForTransactionThread,
@@ -178,17 +160,25 @@ function ReportActionItem({
const {translate} = useLocalize();
const {shouldUseNarrowLayout} = useResponsiveLayout();
const blockedFromConcierge = useBlockedFromConcierge();
+ const reportID = report?.reportID ?? '';
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
- const originalReportID = useMemo(() => ReportUtils.getOriginalReportID(report.reportID, action) || '-1', [report.reportID, action]);
+ const originalReportID = useMemo(() => ReportUtils.getOriginalReportID(reportID, action) || '-1', [reportID, action]);
const [draftMessage] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${originalReportID}`, {
selector: (draftMessagesForReport) => {
const matchingDraftMessage = draftMessagesForReport?.[action.reportActionID];
return typeof matchingDraftMessage === 'string' ? matchingDraftMessage : matchingDraftMessage?.message;
},
});
+ const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${ReportActionsUtils.getIOUReportIDFromReportActionPreview(action) ?? -1}`);
+ const [emojiReactions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${action.reportActionID}`);
+ const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET);
+ const [linkedTransactionRouteError] = useOnyx(
+ `${ONYXKEYS.COLLECTION.TRANSACTION}${ReportActionsUtils.isMoneyRequestAction(action) ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID ?? -1 : -1}`,
+ {selector: (transaction) => transaction?.errorFields?.route ?? null},
+ );
const theme = useTheme();
const styles = useThemeStyles();
- const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID || -1}`);
+ const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID ?? -1}`);
const StyleUtils = useStyleUtils();
const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT;
const [isContextMenuActive, setIsContextMenuActive] = useState(() => ReportActionContextMenu.isActiveReportAction(action.reportActionID));
@@ -206,7 +196,7 @@ function ReportActionItem({
// The app would crash due to subscribing to the entire report collection if parentReportID is an empty string. So we should have a fallback ID here.
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
- const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID || -1}`);
+ const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID || -1}`);
const isReportActionLinked = linkedReportActionID && action.reportActionID && linkedReportActionID === action.reportActionID;
const reportScrollManager = useReportScrollManager();
const isActionableWhisper =
@@ -288,15 +278,15 @@ function ReportActionItem({
}
downloadedPreviews.current = urls;
- Report.expandURLPreview(report.reportID, action.reportActionID);
- }, [action, report.reportID]);
+ Report.expandURLPreview(reportID, action.reportActionID);
+ }, [action, reportID]);
useEffect(() => {
if (draftMessage === undefined || !ReportActionsUtils.isDeletedAction(action)) {
return;
}
- Report.deleteReportActionDraft(report.reportID, action);
- }, [draftMessage, action, report.reportID]);
+ Report.deleteReportActionDraft(reportID, action);
+ }, [draftMessage, action, reportID]);
// Hide the message if it is being moderated for a higher offense, or is hidden by a moderator
// Removed messages should not be shown anyway and should not need this flow
@@ -350,7 +340,7 @@ function ReportActionItem({
event,
selection,
popoverAnchorRef.current,
- report.reportID,
+ reportID,
action.reportActionID,
originalReportID,
draftMessage ?? '',
@@ -365,7 +355,7 @@ function ReportActionItem({
setIsEmojiPickerActive as () => void,
);
},
- [draftMessage, action, report.reportID, toggleContextMenuFromActiveReportAction, originalReportID, shouldDisplayContextMenu, disabledActions, isArchivedRoom, isChronosReport],
+ [draftMessage, action, reportID, toggleContextMenuFromActiveReportAction, originalReportID, shouldDisplayContextMenu, disabledActions, isArchivedRoom, isChronosReport],
);
// Handles manual scrolling to the bottom of the chat when the last message is an actionable whisper and it's resolved.
@@ -382,15 +372,15 @@ function ReportActionItem({
const toggleReaction = useCallback(
(emoji: Emoji, ignoreSkinToneOnCompare?: boolean) => {
- Report.toggleEmojiReaction(report.reportID, action, emoji, emojiReactions, undefined, ignoreSkinToneOnCompare);
+ Report.toggleEmojiReaction(reportID, action, emoji, emojiReactions, undefined, ignoreSkinToneOnCompare);
},
- [report, action, emojiReactions],
+ [reportID, action, emojiReactions],
);
const contextValue = useMemo(
() => ({
anchor: popoverAnchorRef.current,
- report,
+ report: {...report, reportID: report?.reportID ?? ''},
reportNameValuePairs,
action,
transactionThreadReport,
@@ -400,7 +390,7 @@ function ReportActionItem({
[report, action, toggleContextMenuFromActiveReportAction, transactionThreadReport, reportNameValuePairs],
);
- const attachmentContextValue = useMemo(() => ({reportID: report.reportID, type: CONST.ATTACHMENT_TYPE.REPORT}), [report.reportID]);
+ const attachmentContextValue = useMemo(() => ({reportID, type: CONST.ATTACHMENT_TYPE.REPORT}), [reportID]);
const actionableItemButtons: ActionableItem[] = useMemo(() => {
if (ReportActionsUtils.isActionableAddPaymentCard(action) && shouldRenderAddPaymentCard()) {
@@ -428,7 +418,7 @@ function ReportActionItem({
text: 'actionableMentionTrackExpense.submit',
key: `${action.reportActionID}-actionableMentionTrackExpense-submit`,
onPress: () => {
- ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', report.reportID, CONST.IOU.ACTION.SUBMIT, action.reportActionID);
+ ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.SUBMIT, action.reportActionID);
},
isMediumSized: true,
},
@@ -436,7 +426,7 @@ function ReportActionItem({
text: 'actionableMentionTrackExpense.categorize',
key: `${action.reportActionID}-actionableMentionTrackExpense-categorize`,
onPress: () => {
- ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', report.reportID, CONST.IOU.ACTION.CATEGORIZE, action.reportActionID);
+ ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.CATEGORIZE, action.reportActionID);
},
isMediumSized: true,
},
@@ -444,7 +434,7 @@ function ReportActionItem({
text: 'actionableMentionTrackExpense.share',
key: `${action.reportActionID}-actionableMentionTrackExpense-share`,
onPress: () => {
- ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', report.reportID, CONST.IOU.ACTION.SHARE, action.reportActionID);
+ ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.SHARE, action.reportActionID);
},
isMediumSized: true,
},
@@ -452,7 +442,7 @@ function ReportActionItem({
text: 'actionableMentionTrackExpense.nothing',
key: `${action.reportActionID}-actionableMentionTrackExpense-nothing`,
onPress: () => {
- Report.dismissTrackExpenseActionableWhisper(report.reportID, action);
+ Report.dismissTrackExpenseActionableWhisper(reportID, action);
},
isMediumSized: true,
},
@@ -464,13 +454,13 @@ function ReportActionItem({
{
text: 'actionableMentionJoinWorkspaceOptions.accept',
key: `${action.reportActionID}-actionableMentionJoinWorkspace-${CONST.REPORT.ACTIONABLE_MENTION_JOIN_WORKSPACE_RESOLUTION.ACCEPT}`,
- onPress: () => Member.acceptJoinRequest(report.reportID, action),
+ onPress: () => Member.acceptJoinRequest(reportID, action),
isPrimary: true,
},
{
text: 'actionableMentionJoinWorkspaceOptions.decline',
key: `${action.reportActionID}-actionableMentionJoinWorkspace-${CONST.REPORT.ACTIONABLE_MENTION_JOIN_WORKSPACE_RESOLUTION.DECLINE}`,
- onPress: () => Member.declineJoinRequest(report.reportID, action),
+ onPress: () => Member.declineJoinRequest(reportID, action),
},
];
}
@@ -480,13 +470,13 @@ function ReportActionItem({
{
text: 'common.yes',
key: `${action.reportActionID}-actionableReportMentionWhisper-${CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.CREATE}`,
- onPress: () => Report.resolveActionableReportMentionWhisper(report.reportID, action, CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.CREATE),
+ onPress: () => Report.resolveActionableReportMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.CREATE),
isPrimary: true,
},
{
text: 'common.no',
key: `${action.reportActionID}-actionableReportMentionWhisper-${CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.NOTHING}`,
- onPress: () => Report.resolveActionableReportMentionWhisper(report.reportID, action, CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.NOTHING),
+ onPress: () => Report.resolveActionableReportMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.NOTHING),
},
];
}
@@ -495,16 +485,16 @@ function ReportActionItem({
{
text: 'actionableMentionWhisperOptions.invite',
key: `${action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE}`,
- onPress: () => Report.resolveActionableMentionWhisper(report.reportID, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE),
+ onPress: () => Report.resolveActionableMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE),
isPrimary: true,
},
{
text: 'actionableMentionWhisperOptions.nothing',
key: `${action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING}`,
- onPress: () => Report.resolveActionableMentionWhisper(report.reportID, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING),
+ onPress: () => Report.resolveActionableMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING),
},
];
- }, [action, isActionableWhisper, report.reportID]);
+ }, [action, isActionableWhisper, reportID]);
/**
* Get the content of ReportActionItem
@@ -530,9 +520,9 @@ function ReportActionItem({
children = (
);
@@ -636,7 +626,7 @@ function ReportActionItem({
} else if (ReportActionsUtils.isReimbursementDeQueuedAction(action)) {
children = ;
} else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.MODIFIED_EXPENSE) {
- children = ;
+ children = ;
} else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.SUBMITTED) {
children = ;
} else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.APPROVED) {
@@ -673,7 +663,7 @@ function ReportActionItem({
children = (
);
} else if (ReportActionsUtils.isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_INTEGRATION)) {
@@ -694,7 +684,7 @@ function ReportActionItem({
{draftMessage === undefined ? (
);
}
@@ -923,7 +913,7 @@ function ReportActionItem({
{shouldDisplayNewMarker && (!shouldUseThreadDividerLine || !isFirstVisibleReportAction) && }
{shouldDisplayContextMenu && (
({
- iouReport: {
- key: ({action}) => {
- const iouReportID = ReportActionsUtils.getIOUReportIDFromReportActionPreview(action);
- return `${ONYXKEYS.COLLECTION.REPORT}${iouReportID ?? -1}`;
- },
- initialValue: {} as OnyxTypes.Report,
- },
- emojiReactions: {
- key: ({action}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${action.reportActionID}`,
- initialValue: {},
- },
- userWallet: {
- key: ONYXKEYS.USER_WALLET,
- },
- linkedTransactionRouteError: {
- key: ({action}) =>
- `${ONYXKEYS.COLLECTION.TRANSACTION}${ReportActionsUtils.isMoneyRequestAction(action) ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID ?? -1 : -1}`,
- selector: (transaction: OnyxEntry) => transaction?.errorFields?.route ?? null,
- },
-})(
- memo(ReportActionItem, (prevProps, nextProps) => {
- const prevParentReportAction = prevProps.parentReportAction;
- const nextParentReportAction = nextProps.parentReportAction;
- return (
- prevProps.displayAsGroup === nextProps.displayAsGroup &&
- prevProps.isMostRecentIOUReportAction === nextProps.isMostRecentIOUReportAction &&
- prevProps.shouldDisplayNewMarker === nextProps.shouldDisplayNewMarker &&
- lodashIsEqual(prevProps.emojiReactions, nextProps.emojiReactions) &&
- lodashIsEqual(prevProps.action, nextProps.action) &&
- lodashIsEqual(prevProps.iouReport, nextProps.iouReport) &&
- lodashIsEqual(prevProps.report.pendingFields, nextProps.report.pendingFields) &&
- lodashIsEqual(prevProps.report.isDeletedParentAction, nextProps.report.isDeletedParentAction) &&
- lodashIsEqual(prevProps.report.errorFields, nextProps.report.errorFields) &&
- prevProps.report?.statusNum === nextProps.report?.statusNum &&
- prevProps.report?.stateNum === nextProps.report?.stateNum &&
- prevProps.report?.parentReportID === nextProps.report?.parentReportID &&
- prevProps.report?.parentReportActionID === nextProps.report?.parentReportActionID &&
- // TaskReport's created actions render the TaskView, which updates depending on certain fields in the TaskReport
- ReportUtils.isTaskReport(prevProps.report) === ReportUtils.isTaskReport(nextProps.report) &&
- prevProps.action.actionName === nextProps.action.actionName &&
- prevProps.report.reportName === nextProps.report.reportName &&
- prevProps.report.description === nextProps.report.description &&
- ReportUtils.isCompletedTaskReport(prevProps.report) === ReportUtils.isCompletedTaskReport(nextProps.report) &&
- prevProps.report.managerID === nextProps.report.managerID &&
- prevProps.shouldHideThreadDividerLine === nextProps.shouldHideThreadDividerLine &&
- prevProps.report?.total === nextProps.report?.total &&
- prevProps.report?.nonReimbursableTotal === nextProps.report?.nonReimbursableTotal &&
- prevProps.report?.policyAvatar === nextProps.report?.policyAvatar &&
- prevProps.linkedReportActionID === nextProps.linkedReportActionID &&
- lodashIsEqual(prevProps.report.fieldList, nextProps.report.fieldList) &&
- lodashIsEqual(prevProps.transactionThreadReport, nextProps.transactionThreadReport) &&
- lodashIsEqual(prevProps.reportActions, nextProps.reportActions) &&
- lodashIsEqual(prevProps.linkedTransactionRouteError, nextProps.linkedTransactionRouteError) &&
- lodashIsEqual(prevParentReportAction, nextParentReportAction)
- );
- }),
-);
+export default memo(ReportActionItem, (prevProps, nextProps) => {
+ const prevParentReportAction = prevProps.parentReportAction;
+ const nextParentReportAction = nextProps.parentReportAction;
+ return (
+ prevProps.displayAsGroup === nextProps.displayAsGroup &&
+ prevProps.isMostRecentIOUReportAction === nextProps.isMostRecentIOUReportAction &&
+ prevProps.shouldDisplayNewMarker === nextProps.shouldDisplayNewMarker &&
+ lodashIsEqual(prevProps.action, nextProps.action) &&
+ lodashIsEqual(prevProps.report?.pendingFields, nextProps.report?.pendingFields) &&
+ lodashIsEqual(prevProps.report?.isDeletedParentAction, nextProps.report?.isDeletedParentAction) &&
+ lodashIsEqual(prevProps.report?.errorFields, nextProps.report?.errorFields) &&
+ prevProps.report?.statusNum === nextProps.report?.statusNum &&
+ prevProps.report?.stateNum === nextProps.report?.stateNum &&
+ prevProps.report?.parentReportID === nextProps.report?.parentReportID &&
+ prevProps.report?.parentReportActionID === nextProps.report?.parentReportActionID &&
+ // TaskReport's created actions render the TaskView, which updates depending on certain fields in the TaskReport
+ ReportUtils.isTaskReport(prevProps.report) === ReportUtils.isTaskReport(nextProps.report) &&
+ prevProps.action.actionName === nextProps.action.actionName &&
+ prevProps.report?.reportName === nextProps.report?.reportName &&
+ prevProps.report?.description === nextProps.report?.description &&
+ ReportUtils.isCompletedTaskReport(prevProps.report) === ReportUtils.isCompletedTaskReport(nextProps.report) &&
+ prevProps.report?.managerID === nextProps.report?.managerID &&
+ prevProps.shouldHideThreadDividerLine === nextProps.shouldHideThreadDividerLine &&
+ prevProps.report?.total === nextProps.report?.total &&
+ prevProps.report?.nonReimbursableTotal === nextProps.report?.nonReimbursableTotal &&
+ prevProps.report?.policyAvatar === nextProps.report?.policyAvatar &&
+ prevProps.linkedReportActionID === nextProps.linkedReportActionID &&
+ lodashIsEqual(prevProps.report?.fieldList, nextProps.report?.fieldList) &&
+ lodashIsEqual(prevProps.transactionThreadReport, nextProps.transactionThreadReport) &&
+ lodashIsEqual(prevProps.reportActions, nextProps.reportActions) &&
+ lodashIsEqual(prevParentReportAction, nextParentReportAction)
+ );
+});
diff --git a/src/pages/home/report/ReportActionItemContentCreated.tsx b/src/pages/home/report/ReportActionItemContentCreated.tsx
index 21bb6471f5b1..ad40df3d5213 100644
--- a/src/pages/home/report/ReportActionItemContentCreated.tsx
+++ b/src/pages/home/report/ReportActionItemContentCreated.tsx
@@ -74,6 +74,8 @@ function ReportActionItemContentCreated({contextValue, parentReportAction, trans
[shouldHideThreadDividerLine, report.reportID, styles.reportHorizontalRule],
);
+ const contextMenuValue = useMemo(() => ({...contextValue, isDisabled: true}), [contextValue]);
+
if (ReportActionsUtils.isTransactionThread(parentReportAction)) {
const isReversedTransaction = ReportActionsUtils.isReversedTransaction(parentReportAction);
@@ -104,7 +106,7 @@ function ReportActionItemContentCreated({contextValue, parentReportAction, trans
}
return (
-
+
-
+
& {
wrapperStyle?: StyleProp;
/** Report for this action */
- report: Report;
+ report: OnyxEntry;
/** IOU Report for this action, if any */
iouReport?: OnyxEntry;
@@ -82,7 +82,7 @@ function ReportActionItemSingle({
const {translate} = useLocalize();
const personalDetails = usePersonalDetails() ?? CONST.EMPTY_OBJECT;
const actorAccountID = ReportUtils.getReportActionActorAccountID(action, iouReport);
- const [invoiceReceiverPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${report.invoiceReceiver && 'policyID' in report.invoiceReceiver ? report.invoiceReceiver.policyID : -1}`);
+ const [invoiceReceiverPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${report?.invoiceReceiver && 'policyID' in report.invoiceReceiver ? report.invoiceReceiver.policyID : -1}`);
let displayName = ReportUtils.getDisplayNameForParticipant(actorAccountID);
const {avatar, login, pendingFields, status, fallbackIcon} = personalDetails[actorAccountID ?? -1] ?? {};
@@ -101,7 +101,7 @@ function ReportActionItemSingle({
displayName = ReportUtils.getPolicyName(report);
actorHint = displayName;
avatarSource = ReportUtils.getWorkspaceIcon(report).source;
- avatarId = report.policyID;
+ avatarId = report?.policyID;
} else if (action?.delegateAccountID && personalDetails[action?.delegateAccountID]) {
// We replace the actor's email, name, and avatar with the Copilot manually for now. And only if we have their
// details. This will be improved upon when the Copilot feature is implemented.
@@ -143,7 +143,7 @@ function ReportActionItemSingle({
}
} else if (!isWorkspaceActor) {
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
- const avatarIconIndex = report.isOwnPolicyExpenseChat || ReportUtils.isPolicyExpenseChat(report) ? 0 : 1;
+ const avatarIconIndex = report?.isOwnPolicyExpenseChat || ReportUtils.isPolicyExpenseChat(report) ? 0 : 1;
const reportIcons = ReportUtils.getIcons(report, {});
secondaryAvatar = reportIcons[avatarIconIndex];
@@ -174,7 +174,7 @@ function ReportActionItemSingle({
const showActorDetails = useCallback(() => {
if (isWorkspaceActor) {
- showWorkspaceDetails(reportID);
+ showWorkspaceDetails(reportID ?? '');
} else {
// Show participants page IOU report preview
if (iouReportID && displayAllActors) {
diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx
index d01efdf28e9c..9ff8762956d7 100644
--- a/src/pages/home/report/ReportActionsList.tsx
+++ b/src/pages/home/report/ReportActionsList.tsx
@@ -168,11 +168,9 @@ function ReportActionsList({
const userActiveSince = useRef(DateUtils.getDBTime());
const lastMessageTime = useRef(null);
const [isVisible, setIsVisible] = useState(Visibility.isVisible());
- const [messageManuallyMarkedUnread, setMessageManuallyMarkedUnread] = useState(0);
const isFocused = useIsFocused();
const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID ?? -1}`);
- const [accountID] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => session?.accountID ?? 0});
useEffect(() => {
const unsubscriber = Visibility.onVisibilityChange(() => {
@@ -212,7 +210,6 @@ function ReportActionsList({
const [unreadMarkerTime, setUnreadMarkerTime] = useState(report.lastReadTime ?? '');
useEffect(() => {
setUnreadMarkerTime(report.lastReadTime ?? '');
- setMessageManuallyMarkedUnread(0);
// eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
}, [report.reportID]);
@@ -225,16 +222,9 @@ function ReportActionsList({
const nextMessage = sortedVisibleReportActions[index + 1];
const isCurrentMessageUnread = isMessageUnread(reportAction, unreadMarkerTime);
const isNextMessageRead = !nextMessage || !isMessageUnread(nextMessage, unreadMarkerTime);
- let shouldDisplay = isCurrentMessageUnread && isNextMessageRead && !ReportActionsUtils.shouldHideNewMarker(reportAction);
-
- if (shouldDisplay && !messageManuallyMarkedUnread) {
- // Prevent displaying a new marker line when report action is of type "REPORT_PREVIEW" and last actor is the current user
- const isFromCurrentUser = accountID === (ReportActionsUtils.isReportPreviewAction(reportAction) ? !reportAction.childLastActorAccountID : reportAction.actorAccountID);
- const isWithinVisibleThreshold = scrollingVerticalOffset.current < MSG_VISIBLE_THRESHOLD ? reportAction.created < (userActiveSince.current ?? '') : true;
- shouldDisplay = !isFromCurrentUser && isWithinVisibleThreshold;
- }
-
- return shouldDisplay;
+ const shouldDisplay = isCurrentMessageUnread && isNextMessageRead && !ReportActionsUtils.shouldHideNewMarker(reportAction);
+ const isWithinVisibleThreshold = scrollingVerticalOffset.current < MSG_VISIBLE_THRESHOLD ? reportAction.created < (userActiveSince.current ?? '') : true;
+ return shouldDisplay && isWithinVisibleThreshold;
};
// Scan through each visible report action until we find the appropriate action to show the unread marker
@@ -246,7 +236,7 @@ function ReportActionsList({
}
return null;
- }, [accountID, sortedVisibleReportActions, unreadMarkerTime, messageManuallyMarkedUnread]);
+ }, [sortedVisibleReportActions, unreadMarkerTime]);
/**
* Subscribe to read/unread events and update our unreadMarkerTime
@@ -254,11 +244,10 @@ function ReportActionsList({
useEffect(() => {
const unreadActionSubscription = DeviceEventEmitter.addListener(`unreadAction_${report.reportID}`, (newLastReadTime: string) => {
setUnreadMarkerTime(newLastReadTime);
- setMessageManuallyMarkedUnread(new Date().getTime());
+ userActiveSince.current = DateUtils.getDBTime();
});
const readNewestActionSubscription = DeviceEventEmitter.addListener(`readNewestAction_${report.reportID}`, (newLastReadTime: string) => {
setUnreadMarkerTime(newLastReadTime);
- setMessageManuallyMarkedUnread(0);
});
return () => {
@@ -284,7 +273,6 @@ function ReportActionsList({
}
setUnreadMarkerTime(mostRecentReportActionCreated);
- setMessageManuallyMarkedUnread(0);
// eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
}, [sortedVisibleReportActions]);
diff --git a/src/pages/home/report/ReportActionsListItemRenderer.tsx b/src/pages/home/report/ReportActionsListItemRenderer.tsx
index 48c578fd743a..ff1c2431ca8b 100644
--- a/src/pages/home/report/ReportActionsListItemRenderer.tsx
+++ b/src/pages/home/report/ReportActionsListItemRenderer.tsx
@@ -24,7 +24,7 @@ type ReportActionsListItemRendererProps = {
index: number;
/** Report for this action */
- report: Report;
+ report: OnyxEntry;
/** The transaction thread report associated with the report for this action, if any */
transactionThreadReport: OnyxEntry;
@@ -76,6 +76,7 @@ function ReportActionsListItemRenderer({
ReportUtils.isChatThread(report) &&
(!ReportActionsUtils.isTransactionThread(parentReportAction) || ReportActionsUtils.isSentMoneyReportAction(parentReportAction));
+ const originalMessage = useMemo(() => ReportActionsUtils.getOriginalMessage(reportAction), [reportAction]);
/**
* Create a lightweight ReportAction so as to keep the re-rendering as light as possible by
* passing in only the required props.
@@ -88,7 +89,7 @@ function ReportActionsListItemRenderer({
pendingAction: reportAction.pendingAction,
actionName: reportAction.actionName,
errors: reportAction.errors,
- originalMessage: reportAction?.originalMessage,
+ originalMessage,
childCommenterCount: reportAction.childCommenterCount,
linkMetadata: reportAction.linkMetadata,
childReportID: reportAction.childReportID,
@@ -118,7 +119,6 @@ function ReportActionsListItemRenderer({
reportAction.pendingAction,
reportAction.actionName,
reportAction.errors,
- reportAction?.originalMessage,
reportAction.childCommenterCount,
reportAction.linkMetadata,
reportAction.childReportID,
@@ -141,6 +141,7 @@ function ReportActionsListItemRenderer({
reportAction.childManagerAccountID,
reportAction.childMoneyRequestCount,
reportAction.childOwnerAccountID,
+ originalMessage,
],
);
diff --git a/src/pages/settings/AboutPage/AboutPage.tsx b/src/pages/settings/AboutPage/AboutPage.tsx
index 1f3f235a9c3f..4232e08f70f0 100644
--- a/src/pages/settings/AboutPage/AboutPage.tsx
+++ b/src/pages/settings/AboutPage/AboutPage.tsx
@@ -135,6 +135,7 @@ function AboutPage() {
Navigation.goBack(ROUTES.SETTINGS)}
icon={Illustrations.PalmTree}
/>
diff --git a/src/pages/settings/Preferences/PreferencesPage.tsx b/src/pages/settings/Preferences/PreferencesPage.tsx
index e51250f3538a..f2c5f0366640 100755
--- a/src/pages/settings/Preferences/PreferencesPage.tsx
+++ b/src/pages/settings/Preferences/PreferencesPage.tsx
@@ -40,6 +40,7 @@ function PreferencesPage() {
title={translate('common.preferences')}
icon={Illustrations.Gears}
shouldShowBackButton={shouldUseNarrowLayout}
+ shouldDisplaySearchRouter
onBackButtonPress={() => Navigation.goBack()}
/>
diff --git a/src/pages/settings/Profile/ProfilePage.tsx b/src/pages/settings/Profile/ProfilePage.tsx
index 38dec5fe0647..46f280abf191 100755
--- a/src/pages/settings/Profile/ProfilePage.tsx
+++ b/src/pages/settings/Profile/ProfilePage.tsx
@@ -113,6 +113,7 @@ function ProfilePage() {
title={translate('common.profile')}
onBackButtonPress={() => Navigation.goBack()}
shouldShowBackButton={shouldUseNarrowLayout}
+ shouldDisplaySearchRouter
icon={Illustrations.Profile}
/>
diff --git a/src/pages/settings/Security/SecuritySettingsPage.tsx b/src/pages/settings/Security/SecuritySettingsPage.tsx
index 6403b0ac64e2..e54e7d873096 100644
--- a/src/pages/settings/Security/SecuritySettingsPage.tsx
+++ b/src/pages/settings/Security/SecuritySettingsPage.tsx
@@ -141,6 +141,7 @@ function SecuritySettingsPage() {
shouldShowBackButton={shouldUseNarrowLayout}
onBackButtonPress={() => Navigation.goBack()}
icon={Illustrations.LockClosed}
+ shouldDisplaySearchRouter
/>
diff --git a/src/pages/settings/Subscription/CardSection/CardSection.tsx b/src/pages/settings/Subscription/CardSection/CardSection.tsx
index ecc6a6cdc27a..2cb99207f8e0 100644
--- a/src/pages/settings/Subscription/CardSection/CardSection.tsx
+++ b/src/pages/settings/Subscription/CardSection/CardSection.tsx
@@ -61,7 +61,7 @@ function CardSection() {
}, []);
const viewPurchases = useCallback(() => {
- const query = SearchUtils.buildQueryStringFromFilterValues({merchant: CONST.EXPENSIFY_MERCHANT});
+ const query = SearchUtils.buildQueryStringFromFilterFormValues({merchant: CONST.EXPENSIFY_MERCHANT});
Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query}));
}, []);
diff --git a/src/pages/settings/Subscription/SubscriptionSettingsPage.tsx b/src/pages/settings/Subscription/SubscriptionSettingsPage.tsx
index 16fa481b16a6..c34db3fa77a8 100644
--- a/src/pages/settings/Subscription/SubscriptionSettingsPage.tsx
+++ b/src/pages/settings/Subscription/SubscriptionSettingsPage.tsx
@@ -47,6 +47,7 @@ function SubscriptionSettingsPage() {
title={translate('workspace.common.subscription')}
onBackButtonPress={() => Navigation.goBack()}
shouldShowBackButton={shouldUseNarrowLayout}
+ shouldDisplaySearchRouter
icon={Illustrations.CreditCardsNew}
/>
diff --git a/src/pages/settings/Troubleshoot/TroubleshootPage.tsx b/src/pages/settings/Troubleshoot/TroubleshootPage.tsx
index 79f7e45d8cda..d6d0489f7676 100644
--- a/src/pages/settings/Troubleshoot/TroubleshootPage.tsx
+++ b/src/pages/settings/Troubleshoot/TroubleshootPage.tsx
@@ -1,7 +1,6 @@
import React, {useCallback, useMemo, useState} from 'react';
import {View} from 'react-native';
-import Onyx, {withOnyx} from 'react-native-onyx';
-import type {OnyxEntry} from 'react-native-onyx';
+import Onyx, {useOnyx} from 'react-native-onyx';
import type {SvgProps} from 'react-native-svg';
import ClientSideLoggingToolMenu from '@components/ClientSideLoggingToolMenu';
import ConfirmModal from '@components/ConfirmModal';
@@ -39,14 +38,7 @@ type BaseMenuItem = {
action: () => void | Promise;
};
-type TroubleshootPageOnyxProps = {
- shouldStoreLogs: OnyxEntry;
- shouldMaskOnyxState: boolean;
-};
-
-type TroubleshootPageProps = TroubleshootPageOnyxProps;
-
-function TroubleshootPage({shouldStoreLogs, shouldMaskOnyxState}: TroubleshootPageProps) {
+function TroubleshootPage() {
const {translate} = useLocalize();
const styles = useThemeStyles();
const {isProduction} = useEnvironment();
@@ -55,6 +47,9 @@ function TroubleshootPage({shouldStoreLogs, shouldMaskOnyxState}: TroubleshootPa
const {shouldUseNarrowLayout} = useResponsiveLayout();
const illustrationStyle = getLightbulbIllustrationStyle();
+ const [shouldStoreLogs] = useOnyx(ONYXKEYS.SHOULD_STORE_LOGS);
+ const [shouldMaskOnyxState = true] = useOnyx(ONYXKEYS.SHOULD_MASK_ONYX_STATE);
+
const exportOnyxState = useCallback(() => {
ExportOnyxState.readFromOnyxDatabase().then((value: Record) => {
const dataToShare = ExportOnyxState.maskOnyxState(value, shouldMaskOnyxState);
@@ -106,6 +101,7 @@ function TroubleshootPage({shouldStoreLogs, shouldMaskOnyxState}: TroubleshootPa
Navigation.goBack(ROUTES.SETTINGS)}
icon={Illustrations.Lightbulb}
/>
@@ -176,12 +172,4 @@ function TroubleshootPage({shouldStoreLogs, shouldMaskOnyxState}: TroubleshootPa
TroubleshootPage.displayName = 'TroubleshootPage';
-export default withOnyx({
- shouldStoreLogs: {
- key: ONYXKEYS.SHOULD_STORE_LOGS,
- },
- shouldMaskOnyxState: {
- key: ONYXKEYS.SHOULD_MASK_ONYX_STATE,
- selector: (shouldMaskOnyxState) => shouldMaskOnyxState ?? true,
- },
-})(TroubleshootPage);
+export default TroubleshootPage;
diff --git a/src/pages/settings/Wallet/WalletPage/WalletPage.tsx b/src/pages/settings/Wallet/WalletPage/WalletPage.tsx
index cf13c29ffb20..3ef77458de11 100644
--- a/src/pages/settings/Wallet/WalletPage/WalletPage.tsx
+++ b/src/pages/settings/Wallet/WalletPage/WalletPage.tsx
@@ -367,6 +367,7 @@ function WalletPage({shouldListenForResize = false}: WalletPageProps) {
onBackButtonPress={() => Navigation.goBack()}
icon={Illustrations.MoneyIntoWallet}
shouldShowBackButton={shouldUseNarrowLayout}
+ shouldDisplaySearchRouter
/>
diff --git a/src/pages/workspace/WorkspaceMembersPage.tsx b/src/pages/workspace/WorkspaceMembersPage.tsx
index 5cee6f58d1de..a3fc5efc1346 100644
--- a/src/pages/workspace/WorkspaceMembersPage.tsx
+++ b/src/pages/workspace/WorkspaceMembersPage.tsx
@@ -12,6 +12,7 @@ import Button from '@components/Button';
import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu';
import type {DropdownOption, WorkspaceMemberBulkActionType} from '@components/ButtonWithDropdownMenu/types';
import ConfirmModal from '@components/ConfirmModal';
+import DecisionModal from '@components/DecisionModal';
import * as Expensicons from '@components/Icon/Expensicons';
import * as Illustrations from '@components/Icon/Illustrations';
import MessagesRow from '@components/MessagesRow';
@@ -78,10 +79,11 @@ function WorkspaceMembersPage({personalDetails, route, policy, currentUserPerson
const prevAccountIDs = usePrevious(accountIDs);
const textInputRef = useRef(null);
const [isOfflineModalVisible, setIsOfflineModalVisible] = useState(false);
+ const [isDownloadFailureModalVisible, setIsDownloadFailureModalVisible] = useState(false);
const isOfflineAndNoMemberDataAvailable = isEmptyObject(policy?.employeeList) && isOffline;
const prevPersonalDetails = usePrevious(personalDetails);
const {translate, formatPhoneNumber, preferredLocale} = useLocalize();
- const {shouldUseNarrowLayout} = useResponsiveLayout();
+ const {shouldUseNarrowLayout, isSmallScreenWidth} = useResponsiveLayout();
const isPolicyAdmin = PolicyUtils.isPolicyAdmin(policy);
const isLoading = useMemo(
() => !isOfflineAndNoMemberDataAvailable && (!OptionsListUtils.isPersonalDetailsReady(personalDetails) || isEmptyObject(policy?.employeeList)),
@@ -583,7 +585,12 @@ function WorkspaceMembersPage({personalDetails, route, policy, currentUserPerson
Modal.close(() => setIsOfflineModalVisible(true));
return;
}
- Member.downloadMembersCSV(policyID);
+
+ Modal.close(() => {
+ Member.downloadMembersCSV(policyID, () => {
+ setIsDownloadFailureModalVisible(true);
+ });
+ });
},
},
];
@@ -646,6 +653,15 @@ function WorkspaceMembersPage({personalDetails, route, policy, currentUserPerson
});
}}
/>
+ setIsDownloadFailureModalVisible(false)}
+ secondOptionText={translate('common.buttonConfirm')}
+ isVisible={isDownloadFailureModalVisible}
+ onClose={() => setIsDownloadFailureModalVisible(false)}
+ />
;
-type WorkspaceListPageOnyxProps = {
- /** The list of this user's policies */
- policies: OnyxCollection;
-
- /** Bank account attached to free plan */
- reimbursementAccount: OnyxEntry;
-
- /** All reports shared with the user (coming from Onyx) */
- reports: OnyxCollection;
-
- /** Session info for the currently logged in user. */
- session: OnyxEntry;
-};
-
-type WorkspaceListPageProps = WorkspaceListPageOnyxProps;
-
const workspaceFeatures: FeatureListItem[] = [
{
icon: Illustrations.MoneyReceipts,
@@ -117,13 +100,17 @@ function dismissWorkspaceError(policyID: string, pendingAction: OnyxCommon.Pendi
const stickyHeaderIndices = [0];
-function WorkspacesListPage({policies, reimbursementAccount, reports, session}: WorkspaceListPageProps) {
+function WorkspacesListPage() {
const theme = useTheme();
const styles = useThemeStyles();
const {translate} = useLocalize();
const {isOffline} = useNetwork();
const {shouldUseNarrowLayout, isMediumScreenWidth} = useResponsiveLayout();
const [allConnectionSyncProgresses] = useOnyx(ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS);
+ const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY);
+ const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT);
+ const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT);
+ const [session] = useOnyx(ONYXKEYS.SESSION);
const {activeWorkspaceID, setActiveWorkspaceID} = useActiveWorkspace();
@@ -313,7 +300,7 @@ function WorkspacesListPage({policies, reimbursementAccount, reports, session}:
}
return Object.values(policies)
- .filter((policy): policy is PolicyType => PolicyUtils.shouldShowPolicy(policy, !!isOffline, session?.email))
+ .filter((policy): policy is PolicyType => PolicyUtils.shouldShowPolicy(policy, isOffline, session?.email))
.map((policy): WorkspaceItem => {
if (policy?.isJoinRequestPending && policy?.policyDetailsForNonMembers) {
const policyInfo = Object.values(policy.policyDetailsForNonMembers)[0];
@@ -387,6 +374,7 @@ function WorkspacesListPage({policies, reimbursementAccount, reports, session}:
Navigation.goBack()}
icon={Illustrations.BigRocket}
>
@@ -423,6 +411,7 @@ function WorkspacesListPage({policies, reimbursementAccount, reports, session}:
Navigation.goBack()}
icon={Illustrations.BigRocket}
>
@@ -452,18 +441,4 @@ function WorkspacesListPage({policies, reimbursementAccount, reports, session}:
WorkspacesListPage.displayName = 'WorkspacesListPage';
-export default withOnyx({
- policies: {
- key: ONYXKEYS.COLLECTION.POLICY,
- },
- // @ts-expect-error: ONYXKEYS.REIMBURSEMENT_ACCOUNT is conflicting with ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM
- reimbursementAccount: {
- key: ONYXKEYS.REIMBURSEMENT_ACCOUNT,
- },
- reports: {
- key: ONYXKEYS.COLLECTION.REPORT,
- },
- session: {
- key: ONYXKEYS.SESSION,
- },
-})(WorkspacesListPage);
+export default WorkspacesListPage;
diff --git a/src/pages/workspace/companyCards/assignCard/AssigneeStep.tsx b/src/pages/workspace/companyCards/assignCard/AssigneeStep.tsx
index 5be837840390..a57395d69cec 100644
--- a/src/pages/workspace/companyCards/assignCard/AssigneeStep.tsx
+++ b/src/pages/workspace/companyCards/assignCard/AssigneeStep.tsx
@@ -21,7 +21,6 @@ import Navigation from '@navigation/Navigation';
import * as CompanyCards from '@userActions/CompanyCards';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
-import ROUTES from '@src/ROUTES';
import type * as OnyxTypes from '@src/types/onyx';
const MINIMUM_MEMBER_TO_SHOW_SEARCH = 8;
@@ -68,7 +67,7 @@ function AssigneeStep({policy}: AssigneeStepProps) {
CompanyCards.setAssignCardStepAndData({currentStep: CONST.COMPANY_CARD.STEP.CONFIRMATION, isEditing: false});
return;
}
- Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(policy?.id ?? '-1'));
+ Navigation.goBack();
};
const shouldShowSearchInput = policy?.employeeList && Object.keys(policy.employeeList).length >= MINIMUM_MEMBER_TO_SHOW_SEARCH;
diff --git a/src/pages/workspace/companyCards/assignCard/TransactionStartDateStep.tsx b/src/pages/workspace/companyCards/assignCard/TransactionStartDateStep.tsx
index cfdb09e3df83..9c53889d6519 100644
--- a/src/pages/workspace/companyCards/assignCard/TransactionStartDateStep.tsx
+++ b/src/pages/workspace/companyCards/assignCard/TransactionStartDateStep.tsx
@@ -99,25 +99,25 @@ function TransactionStartDateStep() {
shouldSingleExecuteRowSelect
initiallyFocusedOptionKey={dateOptionSelected}
shouldUpdateFocusedIndex
- containerStyle={[styles.flex0, styles.flexShrink0, styles.flexBasisAuto, styles.pb0]}
- // containerStyle={[styles.flexReset, styles.pb0]}
+ listFooterContent={
+ dateOptionSelected === CONST.COMPANY_CARD.TRANSACTION_START_DATE_OPTIONS.CUSTOM ? (
+ <>
+ setIsModalOpened(true)}
+ />
+ setIsModalOpened(false)}
+ />
+ >
+ ) : null
+ }
/>
- {dateOptionSelected === CONST.COMPANY_CARD.TRANSACTION_START_DATE_OPTIONS.CUSTOM && (
- <>
- setIsModalOpened(true)}
- />
- setIsModalOpened(false)}
- />
- >
- )}
diff --git a/src/pages/workspace/tags/WorkspaceTagsPage.tsx b/src/pages/workspace/tags/WorkspaceTagsPage.tsx
index 7bc4f9d9e603..8ccf6e61eaae 100644
--- a/src/pages/workspace/tags/WorkspaceTagsPage.tsx
+++ b/src/pages/workspace/tags/WorkspaceTagsPage.tsx
@@ -8,6 +8,7 @@ import Button from '@components/Button';
import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu';
import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types';
import ConfirmModal from '@components/ConfirmModal';
+import DecisionModal from '@components/DecisionModal';
import EmptyStateComponent from '@components/EmptyStateComponent';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import * as Expensicons from '@components/Icon/Expensicons';
@@ -50,12 +51,13 @@ import type {PolicyTag, PolicyTagList, TagListItem} from './types';
type WorkspaceTagsPageProps = StackScreenProps;
function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) {
- const {shouldUseNarrowLayout} = useResponsiveLayout();
+ const {shouldUseNarrowLayout, isSmallScreenWidth} = useResponsiveLayout();
const styles = useThemeStyles();
const theme = useTheme();
const {translate} = useLocalize();
const {windowWidth} = useWindowDimensions();
const [selectedTags, setSelectedTags] = useState>({});
+ const [isDownloadFailureModalVisible, setIsDownloadFailureModalVisible] = useState(false);
const [isDeleteTagsConfirmModalVisible, setIsDeleteTagsConfirmModalVisible] = useState(false);
const [isOfflineModalVisible, setIsOfflineModalVisible] = useState(false);
const isFocused = useIsFocused();
@@ -316,7 +318,11 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) {
Modal.close(() => setIsOfflineModalVisible(true));
return;
}
- Tag.downloadTagsCSV(policyID);
+ Modal.close(() => {
+ Tag.downloadTagsCSV(policyID, () => {
+ setIsDownloadFailureModalVisible(true);
+ });
+ });
},
});
}
@@ -435,6 +441,15 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) {
confirmText={translate('common.buttonConfirm')}
shouldShowCancelButton={false}
/>
+ setIsDownloadFailureModalVisible(false)}
+ secondOptionText={translate('common.buttonConfirm')}
+ isVisible={isDownloadFailureModalVisible}
+ onClose={() => setIsDownloadFailureModalVisible(false)}
+ />
);
diff --git a/src/pages/workspace/withPolicy.tsx b/src/pages/workspace/withPolicy.tsx
index 4eef95267a40..e1fdd2b29687 100644
--- a/src/pages/workspace/withPolicy.tsx
+++ b/src/pages/workspace/withPolicy.tsx
@@ -32,6 +32,7 @@ type PolicyRoute = RouteProp<
| typeof SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET
| typeof SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_FREQUENCY
| typeof SCREENS.WORKSPACE.MEMBER_DETAILS
+ | typeof SCREENS.WORKSPACE.MEMBER_NEW_CARD
| typeof SCREENS.WORKSPACE.INVOICES
| typeof SCREENS.WORKSPACE.OWNER_CHANGE_CHECK
| typeof SCREENS.WORKSPACE.TAX_EDIT
diff --git a/src/styles/index.ts b/src/styles/index.ts
index 05bedf7cf3af..f94a047fd459 100644
--- a/src/styles/index.ts
+++ b/src/styles/index.ts
@@ -3220,6 +3220,10 @@ const styles = (theme: ThemeColors) =>
color: theme.heading,
},
+ moneyRequestLoadingHeight: {
+ height: 27,
+ },
+
defaultCheckmarkWrapper: {
marginLeft: 8,
alignSelf: 'center',
diff --git a/tests/perf-test/ReportScreen.perf-test.tsx b/tests/perf-test/ReportScreen.perf-test.tsx
index 8b1d2d5de405..95ac9729e606 100644
--- a/tests/perf-test/ReportScreen.perf-test.tsx
+++ b/tests/perf-test/ReportScreen.perf-test.tsx
@@ -103,6 +103,7 @@ jest.mock('@src/hooks/useEnvironment', () =>
jest.mock('@src/libs/Permissions', () => ({
canUseLinkPreviews: jest.fn(() => true),
canUseDefaultRooms: jest.fn(() => true),
+ canUseNewSearchRouter: jest.fn(() => true),
}));
jest.mock('@src/libs/Navigation/Navigation', () => ({