diff --git a/.github/actions/javascript/markPullRequestsAsDeployed/action.yml b/.github/actions/javascript/markPullRequestsAsDeployed/action.yml
index f0ca77bdbf0..40dfc05e544 100644
--- a/.github/actions/javascript/markPullRequestsAsDeployed/action.yml
+++ b/.github/actions/javascript/markPullRequestsAsDeployed/action.yml
@@ -14,7 +14,6 @@ inputs:
GITHUB_TOKEN:
description: "Github token for authentication"
required: true
- default: "${{ github.token }}"
ANDROID:
description: "Android job result ('success', 'failure', 'cancelled', or 'skipped')"
required: true
@@ -27,6 +26,12 @@ inputs:
WEB:
description: "Web job result ('success', 'failure', 'cancelled', or 'skipped')"
required: true
+ DATE:
+ description: "The date of deployment"
+ required: false
+ NOTE:
+ description: "Additional note from the deployer"
+ required: false
runs:
using: "node20"
main: "./index.js"
diff --git a/.github/actions/javascript/markPullRequestsAsDeployed/index.js b/.github/actions/javascript/markPullRequestsAsDeployed/index.js
index 9f97e4a72d2..b38b0414139 100644
--- a/.github/actions/javascript/markPullRequestsAsDeployed/index.js
+++ b/.github/actions/javascript/markPullRequestsAsDeployed/index.js
@@ -12713,9 +12713,15 @@ async function run() {
const desktopResult = getDeployTableMessage(core.getInput('DESKTOP', { required: true }));
const iOSResult = getDeployTableMessage(core.getInput('IOS', { required: true }));
const webResult = getDeployTableMessage(core.getInput('WEB', { required: true }));
+ const date = core.getInput('DATE');
+ const note = core.getInput('NOTE');
function getDeployMessage(deployer, deployVerb, prTitle) {
let message = `š [${deployVerb}](${workflowURL}) to ${isProd ? 'production' : 'staging'}`;
- message += ` by https://github.com/${deployer} in version: ${version} š`;
+ message += ` by https://github.com/${deployer} in version: ${version} `;
+ if (date) {
+ message += `on ${date}`;
+ }
+ message += `š`;
message += `\n\nplatform | result\n---|---\nš¤ android š¤|${androidResult}\nš„ desktop š„|${desktopResult}`;
message += `\nš iOS š|${iOSResult}\nšø web šø|${webResult}`;
if (deployVerb === 'Cherry-picked' && !/no ?qa/gi.test(prTitle ?? '')) {
@@ -12723,6 +12729,9 @@ async function run() {
message +=
'\n\n@Expensify/applauseleads please QA this PR and check it off on the [deploy checklist](https://github.com/Expensify/App/issues?q=is%3Aopen+is%3Aissue+label%3AStagingDeployCash) if it passes.';
}
+ if (note) {
+ message += `\n\n_Note:_ ${note}`;
+ }
return message;
}
if (isProd) {
diff --git a/.github/actions/javascript/markPullRequestsAsDeployed/markPullRequestsAsDeployed.ts b/.github/actions/javascript/markPullRequestsAsDeployed/markPullRequestsAsDeployed.ts
index 71a5c7d5c6e..e6424c89833 100644
--- a/.github/actions/javascript/markPullRequestsAsDeployed/markPullRequestsAsDeployed.ts
+++ b/.github/actions/javascript/markPullRequestsAsDeployed/markPullRequestsAsDeployed.ts
@@ -55,9 +55,16 @@ async function run() {
const iOSResult = getDeployTableMessage(core.getInput('IOS', {required: true}) as PlatformResult);
const webResult = getDeployTableMessage(core.getInput('WEB', {required: true}) as PlatformResult);
+ const date = core.getInput('DATE');
+ const note = core.getInput('NOTE');
+
function getDeployMessage(deployer: string, deployVerb: string, prTitle?: string): string {
let message = `š [${deployVerb}](${workflowURL}) to ${isProd ? 'production' : 'staging'}`;
- message += ` by https://github.com/${deployer} in version: ${version} š`;
+ message += ` by https://github.com/${deployer} in version: ${version} `;
+ if (date) {
+ message += `on ${date}`;
+ }
+ message += `š`;
message += `\n\nplatform | result\n---|---\nš¤ android š¤|${androidResult}\nš„ desktop š„|${desktopResult}`;
message += `\nš iOS š|${iOSResult}\nšø web šø|${webResult}`;
@@ -67,6 +74,10 @@ async function run() {
'\n\n@Expensify/applauseleads please QA this PR and check it off on the [deploy checklist](https://github.com/Expensify/App/issues?q=is%3Aopen+is%3Aissue+label%3AStagingDeployCash) if it passes.';
}
+ if (note) {
+ message += `\n\n_Note:_ ${note}`;
+ }
+
return message;
}
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 2ab19d13183..53afe03720f 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -649,34 +649,14 @@ jobs:
GITHUB_TOKEN: ${{ github.token }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
- postGithubComment:
- name: Post a GitHub comments on all deployed PRs when platforms are done building and deploying
- runs-on: ubuntu-latest
+ postGithubComments:
+ uses: ./.github/workflows/postDeployComments.yml
if: ${{ always() && fromJSON(needs.checkDeploymentSuccess.outputs.IS_AT_LEAST_ONE_PLATFORM_DEPLOYED) }}
needs: [prep, android, desktop, iOS, web, checkDeploymentSuccess, createPrerelease, finalizeRelease]
- steps:
- - name: Checkout
- uses: actions/checkout@v4
-
- - name: Setup Node
- uses: ./.github/actions/composite/setupNode
-
- - name: Get Release Pull Request List
- id: getReleasePRList
- uses: ./.github/actions/javascript/getDeployPullRequestList
- with:
- TAG: ${{ needs.prep.outputs.APP_VERSION }}
- GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }}
- IS_PRODUCTION_DEPLOY: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }}
-
- - name: Comment on issues
- uses: ./.github/actions/javascript/markPullRequestsAsDeployed
- with:
- PR_LIST: ${{ steps.getReleasePRList.outputs.PR_LIST }}
- IS_PRODUCTION_DEPLOY: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }}
- DEPLOY_VERSION: ${{ needs.prep.outputs.APP_VERSION }}
- GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }}
- ANDROID: ${{ needs.android.result }}
- DESKTOP: ${{ needs.desktop.result }}
- IOS: ${{ needs.iOS.result }}
- WEB: ${{ needs.web.result }}
+ with:
+ version: ${{ needs.prep.outputs.APP_VERSION }}
+ env: ${{ github.ref == 'refs/heads/production' && 'production' || 'staging' }}
+ android: ${{ needs.android.result }}
+ ios: ${{ needs.iOS.result }}
+ web: ${{ needs.web.result }}
+ desktop: ${{ needs.desktop.result }}
diff --git a/.github/workflows/postDeployComments.yml b/.github/workflows/postDeployComments.yml
new file mode 100644
index 00000000000..3893d3cf3f7
--- /dev/null
+++ b/.github/workflows/postDeployComments.yml
@@ -0,0 +1,118 @@
+name: Post Deploy Comments
+
+on:
+ workflow_call:
+ inputs:
+ version:
+ description: The version that was deployed
+ required: true
+ type: string
+ env:
+ description: The environment that was deployed (staging or prod)
+ required: true
+ type: string
+ android:
+ description: Android deploy status
+ required: true
+ type: string
+ ios:
+ description: iOS deploy status
+ required: true
+ type: string
+ web:
+ description: Web deploy status
+ required: true
+ type: string
+ desktop:
+ description: Desktop deploy status
+ required: true
+ type: string
+ workflow_dispatch:
+ inputs:
+ version:
+ description: The version that was deployed
+ required: true
+ type: string
+ env:
+ description: The environment that was deployed (staging or prod)
+ required: true
+ type: choice
+ options:
+ - staging
+ - production
+ android:
+ description: Android deploy status
+ required: true
+ type: choice
+ options:
+ - success
+ - failure
+ - cancelled
+ - skipped
+ ios:
+ description: iOS deploy status
+ required: true
+ type: choice
+ options:
+ - success
+ - failure
+ - cancelled
+ - skipped
+ web:
+ description: Web deploy status
+ required: true
+ type: choice
+ options:
+ - success
+ - failure
+ - cancelled
+ - skipped
+ desktop:
+ description: Desktop deploy status
+ required: true
+ type: choice
+ options:
+ - success
+ - failure
+ - cancelled
+ - skipped
+ date:
+ description: The date when this deploy occurred
+ required: false
+ type: string
+ note:
+ description: Any additional note you want to include with the deploy comment
+ required: false
+ type: string
+
+jobs:
+ postDeployComments:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup Node
+ uses: ./.github/actions/composite/setupNode
+
+ - name: Get pull request list
+ id: getPullRequestList
+ uses: ./.github/actions/javascript/getDeployPullRequestList
+ with:
+ TAG: ${{ inputs.version }}
+ GITHUB_TOKEN: ${{ github.token }}
+ IS_PRODUCTION_DEPLOY: ${{ inputs.env == 'production' }}
+
+ - name: Comment on issues
+ uses: ./.github/actions/javascript/markPullRequestsAsDeployed
+ with:
+ PR_LIST: ${{ steps.getPullRequestList.outputs.PR_LIST }}
+ IS_PRODUCTION_DEPLOY: ${{ inputs.env == 'production' }}
+ DEPLOY_VERSION: ${{ inputs.version }}
+ GITHUB_TOKEN: ${{ github.token }}
+ ANDROID: ${{ inputs.android }}
+ DESKTOP: ${{ inputs.desktop }}
+ IOS: ${{ inputs.ios }}
+ WEB: ${{ inputs.web }}
+ DATE: ${{ inputs.date }}
+ NOTE: ${{ inputs.note }}
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 491c810cb35..fffbd66aefa 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 1009004000
+ versionName "9.0.40-0"
// 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 85e547439cc..fd3f9997612 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 52f55778b2b..2989f5025ae 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 3721edaa7af..663eb29d5d2 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 b8e66c937a0..2d33552f3e3 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 00000000000..fc6d5587bb0
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 00000000000..29038987c18
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 00000000000..fd78aa73178
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 00000000000..d2e301a9d1a
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 00000000000..8eae5efaa9d
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 00000000000..2858227891e
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 7bed47c032a..7d3f7bba95f 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -19,7 +19,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 9.0.39
+ 9.0.40
CFBundleSignature
????
CFBundleURLTypes
@@ -40,7 +40,7 @@
CFBundleVersion
- 9.0.39.2
+ 9.0.40.0
FullStory
OrgId
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index 6e01c0d2f91..aa39f6fba8a 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -15,10 +15,10 @@
CFBundlePackageType
BNDL
CFBundleShortVersionString
- 9.0.39
+ 9.0.40
CFBundleSignature
????
CFBundleVersion
- 9.0.39.2
+ 9.0.40.0
diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist
index 74158e9d574..f6a27648973 100644
--- a/ios/NotificationServiceExtension/Info.plist
+++ b/ios/NotificationServiceExtension/Info.plist
@@ -11,9 +11,9 @@
CFBundleName
$(PRODUCT_NAME)
CFBundleShortVersionString
- 9.0.39
+ 9.0.40
CFBundleVersion
- 9.0.39.2
+ 9.0.40.0
NSExtension
NSExtensionPointIdentifier
diff --git a/ios/Podfile b/ios/Podfile
index 2ed1752abf4..e807089c26b 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 0f1a42791d1..a801a7c4de1 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 3c27c44a3bd..d5672be4b48 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "9.0.39-2",
+ "version": "9.0.40-0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "9.0.39-2",
+ "version": "9.0.40-0",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index 6f4980f04ee..4965fb8f60e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "9.0.39-2",
+ "version": "9.0.40-0",
"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 17d49215b20..d770d2bda75 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -2101,6 +2101,9 @@ const CONST = {
ACCESS_VARIANTS: {
CREATE: 'create',
},
+ PAGE_INDEX: {
+ CONFIRM: 'confirm',
+ },
PAYMENT_SELECTED: {
BBA: 'BBA',
PBA: 'PBA',
@@ -2456,6 +2459,7 @@ const CONST = {
},
},
EXPENSIFY_CARD: {
+ NAME: 'expensifyCard',
BANK: 'Expensify Card',
FRAUD_TYPES: {
DOMAIN: 'domain',
@@ -4559,7 +4563,7 @@ const CONST = {
{
type: 'setupTags',
autoCompleted: false,
- title: 'Set up tags (optional)',
+ title: 'Set up tags',
description: ({workspaceMoreFeaturesLink}) =>
'Tags can be used if you want more details with every expense. Use tags for projects, clients, locations, departments, and more. If you need multiple levels of tags you can upgrade to a control plan.\n' +
'\n' +
diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts
index 64ad1f660c1..7fcb675dc19 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 9b351bd3189..6ed67c0d312 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -74,7 +74,7 @@ const ROUTES = {
SUBMIT_EXPENSE: 'submit-expense',
FLAG_COMMENT: {
route: 'flag/:reportID/:reportActionID',
- getRoute: (reportID: string, reportActionID: string) => `flag/${reportID}/${reportActionID}` as const,
+ getRoute: (reportID: string, reportActionID: string, backTo?: string) => getUrlWithBackToParam(`flag/${reportID}/${reportActionID}` as const, backTo),
},
CHAT_FINDER: 'chat-finder',
PROFILE: {
@@ -287,11 +287,11 @@ const ROUTES = {
},
EDIT_REPORT_FIELD_REQUEST: {
route: 'r/:reportID/edit/policyField/:policyID/:fieldID',
- getRoute: (reportID: string, policyID: string, fieldID: string) => `r/${reportID}/edit/policyField/${policyID}/${fieldID}` as const,
+ getRoute: (reportID: string, policyID: string, fieldID: string, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/edit/policyField/${policyID}/${fieldID}` as const, backTo),
},
REPORT_WITH_ID_DETAILS_SHARE_CODE: {
route: 'r/:reportID/details/shareCode',
- getRoute: (reportID: string) => `r/${reportID}/details/shareCode` as const,
+ getRoute: (reportID: string, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/details/shareCode` as const, backTo),
},
ATTACHMENTS: {
route: 'attachment',
@@ -300,19 +300,19 @@ const ROUTES = {
},
REPORT_PARTICIPANTS: {
route: 'r/:reportID/participants',
- getRoute: (reportID: string) => `r/${reportID}/participants` as const,
+ getRoute: (reportID: string, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/participants` as const, backTo),
},
REPORT_PARTICIPANTS_INVITE: {
route: 'r/:reportID/participants/invite',
- getRoute: (reportID: string) => `r/${reportID}/participants/invite` as const,
+ getRoute: (reportID: string, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/participants/invite` as const, backTo),
},
REPORT_PARTICIPANTS_DETAILS: {
route: 'r/:reportID/participants/:accountID',
- getRoute: (reportID: string, accountID: number) => `r/${reportID}/participants/${accountID}` as const,
+ getRoute: (reportID: string, accountID: number, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/participants/${accountID}` as const, backTo),
},
REPORT_PARTICIPANTS_ROLE_SELECTION: {
route: 'r/:reportID/participants/:accountID/role',
- getRoute: (reportID: string, accountID: number) => `r/${reportID}/participants/${accountID}/role` as const,
+ getRoute: (reportID: string, accountID: number, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/participants/${accountID}/role` as const, backTo),
},
REPORT_WITH_ID_DETAILS: {
route: 'r/:reportID/details',
@@ -320,65 +320,65 @@ const ROUTES = {
},
REPORT_WITH_ID_DETAILS_EXPORT: {
route: 'r/:reportID/details/export/:connectionName',
- getRoute: (reportID: string, connectionName: ConnectionName) => `r/${reportID}/details/export/${connectionName}` as const,
+ getRoute: (reportID: string, connectionName: ConnectionName, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/details/export/${connectionName}` as const, backTo),
},
REPORT_SETTINGS: {
route: 'r/:reportID/settings',
- getRoute: (reportID: string) => `r/${reportID}/settings` as const,
+ getRoute: (reportID: string, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/settings` as const, backTo),
},
REPORT_SETTINGS_NAME: {
route: 'r/:reportID/settings/name',
- getRoute: (reportID: string) => `r/${reportID}/settings/name` as const,
+ getRoute: (reportID: string, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/settings/name` as const, backTo),
},
REPORT_SETTINGS_NOTIFICATION_PREFERENCES: {
route: 'r/:reportID/settings/notification-preferences',
- getRoute: (reportID: string) => `r/${reportID}/settings/notification-preferences` as const,
+ getRoute: (reportID: string, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/settings/notification-preferences` as const, backTo),
},
REPORT_SETTINGS_WRITE_CAPABILITY: {
route: 'r/:reportID/settings/who-can-post',
- getRoute: (reportID: string) => `r/${reportID}/settings/who-can-post` as const,
+ getRoute: (reportID: string, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/settings/who-can-post` as const, backTo),
},
REPORT_SETTINGS_VISIBILITY: {
route: 'r/:reportID/settings/visibility',
- getRoute: (reportID: string) => `r/${reportID}/settings/visibility` as const,
+ getRoute: (reportID: string, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/settings/visibility` as const, backTo),
},
SPLIT_BILL_DETAILS: {
route: 'r/:reportID/split/:reportActionID',
- getRoute: (reportID: string, reportActionID: string) => `r/${reportID}/split/${reportActionID}` as const,
+ getRoute: (reportID: string, reportActionID: string, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/split/${reportActionID}` as const, backTo),
},
TASK_TITLE: {
route: 'r/:reportID/title',
- getRoute: (reportID: string) => `r/${reportID}/title` as const,
+ getRoute: (reportID: string, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/title` as const, backTo),
},
REPORT_DESCRIPTION: {
route: 'r/:reportID/description',
- getRoute: (reportID: string) => `r/${reportID}/description` as const,
+ getRoute: (reportID: string, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/description` as const, backTo),
},
TASK_ASSIGNEE: {
route: 'r/:reportID/assignee',
- getRoute: (reportID: string) => `r/${reportID}/assignee` as const,
+ getRoute: (reportID: string, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/assignee` as const, backTo),
},
PRIVATE_NOTES_LIST: {
route: 'r/:reportID/notes',
- getRoute: (reportID: string) => `r/${reportID}/notes` as const,
+ getRoute: (reportID: string, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/notes` as const, backTo),
},
PRIVATE_NOTES_EDIT: {
route: 'r/:reportID/notes/:accountID/edit',
- getRoute: (reportID: string, accountID: string | number) => `r/${reportID}/notes/${accountID}/edit` as const,
+ getRoute: (reportID: string, accountID: string | number, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/notes/${accountID}/edit` as const, backTo),
},
ROOM_MEMBERS: {
route: 'r/:reportID/members',
- getRoute: (reportID: string) => `r/${reportID}/members` as const,
+ getRoute: (reportID: string, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/members` as const, backTo),
},
ROOM_MEMBER_DETAILS: {
route: 'r/:reportID/members/:accountID',
- getRoute: (reportID: string, accountID: string | number) => `r/${reportID}/members/${accountID}` as const,
+ getRoute: (reportID: string, accountID: string | number, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/members/${accountID}` as const, backTo),
},
ROOM_INVITE: {
route: 'r/:reportID/invite/:role?',
- getRoute: (reportID: string, role?: string) => {
+ getRoute: (reportID: string, role?: string, backTo?: string) => {
const route = role ? (`r/${reportID}/invite/${role}` as const) : (`r/${reportID}/invite` as const);
- return route;
+ return getUrlWithBackToParam(route, backTo);
},
},
MONEY_REQUEST_HOLD_REASON: {
@@ -406,9 +406,9 @@ const ROUTES = {
`${action as string}/${iouType as string}/confirmation/${transactionID}/${reportID}${participantsAutoAssigned ? '?participantsAutoAssigned=true' : ''}` as const,
},
MONEY_REQUEST_STEP_AMOUNT: {
- route: ':action/:iouType/amount/:transactionID/:reportID',
- getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '') =>
- getUrlWithBackToParam(`${action as string}/${iouType as string}/amount/${transactionID}/${reportID}`, backTo),
+ route: ':action/:iouType/amount/:transactionID/:reportID/:pageIndex?',
+ getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, pageIndex: string, backTo = '') =>
+ getUrlWithBackToParam(`${action as string}/${iouType as string}/amount/${transactionID}/${reportID}/${pageIndex}`, backTo),
},
MONEY_REQUEST_STEP_TAX_RATE: {
route: ':action/:iouType/taxRate/:transactionID/:reportID?',
@@ -536,12 +536,27 @@ const ROUTES = {
IOU_SEND_ADD_DEBIT_CARD: 'pay/new/add-debit-card',
IOU_SEND_ENABLE_PAYMENTS: 'pay/new/enable-payments',
- NEW_TASK: 'new/task',
- NEW_TASK_ASSIGNEE: 'new/task/assignee',
+ NEW_TASK: {
+ route: 'new/task',
+ getRoute: (backTo?: string) => getUrlWithBackToParam('new/task', backTo),
+ },
+ NEW_TASK_ASSIGNEE: {
+ route: 'new/task/assignee',
+ getRoute: (backTo?: string) => getUrlWithBackToParam('new/task/assignee', backTo),
+ },
NEW_TASK_SHARE_DESTINATION: 'new/task/share-destination',
- NEW_TASK_DETAILS: 'new/task/details',
- NEW_TASK_TITLE: 'new/task/title',
- NEW_TASK_DESCRIPTION: 'new/task/description',
+ NEW_TASK_DETAILS: {
+ route: 'new/task/details',
+ getRoute: (backTo?: string) => getUrlWithBackToParam('new/task/details', backTo),
+ },
+ NEW_TASK_TITLE: {
+ route: 'new/task/title',
+ getRoute: (backTo?: string) => getUrlWithBackToParam('new/task/title', backTo),
+ },
+ NEW_TASK_DESCRIPTION: {
+ route: 'new/task/description',
+ getRoute: (backTo?: string) => getUrlWithBackToParam('new/task/description', backTo),
+ },
TEACHERS_UNITE: 'settings/teachersunite',
I_KNOW_A_TEACHER: 'settings/teachersunite/i-know-a-teacher',
@@ -878,6 +893,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 +979,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 +995,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),
@@ -1093,7 +1112,10 @@ const ROUTES = {
route: 'referral/:contentType',
getRoute: (contentType: string, backTo?: string) => getUrlWithBackToParam(`referral/${contentType}`, backTo),
},
- PROCESS_MONEY_REQUEST_HOLD: 'hold-expense-educational',
+ PROCESS_MONEY_REQUEST_HOLD: {
+ route: 'hold-expense-educational',
+ getRoute: (backTo?: string) => getUrlWithBackToParam('hold-expense-educational', backTo),
+ },
TRAVEL_MY_TRIPS: 'travel',
TRAVEL_TCS: 'travel/terms',
TRACK_TRAINING_MODAL: 'track-training',
@@ -1122,39 +1144,39 @@ const ROUTES = {
},
TRANSACTION_DUPLICATE_REVIEW_PAGE: {
route: 'r/:threadReportID/duplicates/review',
- getRoute: (threadReportID: string) => `r/${threadReportID}/duplicates/review` as const,
+ getRoute: (threadReportID: string, backTo?: string) => getUrlWithBackToParam(`r/${threadReportID}/duplicates/review` as const, backTo),
},
TRANSACTION_DUPLICATE_REVIEW_MERCHANT_PAGE: {
route: 'r/:threadReportID/duplicates/review/merchant',
- getRoute: (threadReportID: string) => `r/${threadReportID}/duplicates/review/merchant` as const,
+ getRoute: (threadReportID: string, backTo?: string) => getUrlWithBackToParam(`r/${threadReportID}/duplicates/review/merchant` as const, backTo),
},
TRANSACTION_DUPLICATE_REVIEW_CATEGORY_PAGE: {
route: 'r/:threadReportID/duplicates/review/category',
- getRoute: (threadReportID: string) => `r/${threadReportID}/duplicates/review/category` as const,
+ getRoute: (threadReportID: string, backTo?: string) => getUrlWithBackToParam(`r/${threadReportID}/duplicates/review/category` as const, backTo),
},
TRANSACTION_DUPLICATE_REVIEW_TAG_PAGE: {
route: 'r/:threadReportID/duplicates/review/tag',
- getRoute: (threadReportID: string) => `r/${threadReportID}/duplicates/review/tag` as const,
+ getRoute: (threadReportID: string, backTo?: string) => getUrlWithBackToParam(`r/${threadReportID}/duplicates/review/tag` as const, backTo),
},
TRANSACTION_DUPLICATE_REVIEW_TAX_CODE_PAGE: {
route: 'r/:threadReportID/duplicates/review/tax-code',
- getRoute: (threadReportID: string) => `r/${threadReportID}/duplicates/review/tax-code` as const,
+ getRoute: (threadReportID: string, backTo?: string) => getUrlWithBackToParam(`r/${threadReportID}/duplicates/review/tax-code` as const, backTo),
},
TRANSACTION_DUPLICATE_REVIEW_DESCRIPTION_PAGE: {
route: 'r/:threadReportID/duplicates/review/description',
- getRoute: (threadReportID: string) => `r/${threadReportID}/duplicates/review/description` as const,
+ getRoute: (threadReportID: string, backTo?: string) => getUrlWithBackToParam(`r/${threadReportID}/duplicates/review/description` as const, backTo),
},
TRANSACTION_DUPLICATE_REVIEW_REIMBURSABLE_PAGE: {
route: 'r/:threadReportID/duplicates/review/reimbursable',
- getRoute: (threadReportID: string) => `r/${threadReportID}/duplicates/review/reimbursable` as const,
+ getRoute: (threadReportID: string, backTo?: string) => getUrlWithBackToParam(`r/${threadReportID}/duplicates/review/reimbursable` as const, backTo),
},
TRANSACTION_DUPLICATE_REVIEW_BILLABLE_PAGE: {
route: 'r/:threadReportID/duplicates/review/billable',
- getRoute: (threadReportID: string) => `r/${threadReportID}/duplicates/review/billable` as const,
+ getRoute: (threadReportID: string, backTo?: string) => getUrlWithBackToParam(`r/${threadReportID}/duplicates/review/billable` as const, backTo),
},
TRANSACTION_DUPLICATE_CONFIRMATION_PAGE: {
route: 'r/:threadReportID/duplicates/confirm',
- getRoute: (threadReportID: string) => `r/${threadReportID}/duplicates/confirm` as const,
+ getRoute: (threadReportID: string, backTo?: string) => getUrlWithBackToParam(`r/${threadReportID}/duplicates/confirm` as const, backTo),
},
POLICY_ACCOUNTING_XERO_IMPORT: {
route: 'settings/workspaces/:policyID/accounting/xero/import',
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index da92d2b0940..920bd48dd42 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/AvatarWithDisplayName.tsx b/src/components/AvatarWithDisplayName.tsx
index 1cd1bfb36d8..07845eca37b 100644
--- a/src/components/AvatarWithDisplayName.tsx
+++ b/src/components/AvatarWithDisplayName.tsx
@@ -79,10 +79,15 @@ function AvatarWithDisplayName({
actorAccountID.current = parentReportAction?.actorAccountID ?? -1;
}, [parentReportActions, report]);
+ const goToDetailsPage = useCallback(() => {
+ ReportUtils.navigateToDetailsPage(report, Navigation.getReportRHPActiveRoute());
+ }, [report]);
+
const showActorDetails = useCallback(() => {
// We should navigate to the details page if the report is a IOU/expense report
if (shouldEnableDetailPageNavigation) {
- return ReportUtils.navigateToDetailsPage(report);
+ goToDetailsPage();
+ return;
}
if (ReportUtils.isExpenseReport(report) && report?.ownerAccountID) {
@@ -107,7 +112,7 @@ function AvatarWithDisplayName({
// Report detail route is added as fallback but based on the current implementation this route won't be executed
Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report.reportID));
}
- }, [report, shouldEnableDetailPageNavigation]);
+ }, [report, shouldEnableDetailPageNavigation, goToDetailsPage]);
const headerView = (
@@ -172,7 +177,7 @@ function AvatarWithDisplayName({
return (
ReportUtils.navigateToDetailsPage(report)}
+ onPress={goToDetailsPage}
style={[styles.flexRow, styles.alignItemsCenter, styles.flex1]}
accessibilityLabel={title}
role={CONST.ROLE.BUTTON}
diff --git a/src/components/EmptyStateComponent/index.tsx b/src/components/EmptyStateComponent/index.tsx
index 876f1a74540..8eb991b17b6 100644
--- a/src/components/EmptyStateComponent/index.tsx
+++ b/src/components/EmptyStateComponent/index.tsx
@@ -22,6 +22,7 @@ function EmptyStateComponent({
buttonAction,
containerStyles,
title,
+ titleStyles,
subtitle,
headerStyles,
headerContentStyles,
@@ -30,7 +31,7 @@ function EmptyStateComponent({
}: EmptyStateComponentProps) {
const styles = useThemeStyles();
const [videoAspectRatio, setVideoAspectRatio] = useState(VIDEO_ASPECT_RATIO);
- const {isSmallScreenWidth} = useResponsiveLayout();
+ const {shouldUseNarrowLayout} = useResponsiveLayout();
const setAspectRatio = (event: VideoReadyForDisplayEvent | VideoLoadedEventType | undefined) => {
if (!event) {
@@ -82,7 +83,10 @@ function EmptyStateComponent({
}, [headerMedia, headerMediaType, headerContentStyles, videoAspectRatio, styles.emptyStateVideo, lottieWebViewStyles]);
return (
-
+
{HeaderComponent}
-
- {title}
- {subtitle}
+
+ {title}
+ {typeof subtitle === 'string' ? {subtitle} : subtitle}
{!!buttonText && !!buttonAction && (
)}
+ {shouldDisplaySearchRouter && }
diff --git a/src/components/HeaderWithBackButton/types.ts b/src/components/HeaderWithBackButton/types.ts
index c55a7bddc80..22885b6ceac 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 4fc92d619e6..b1340531c7f 100644
--- a/src/components/MoneyReportHeader.tsx
+++ b/src/components/MoneyReportHeader.tsx
@@ -4,6 +4,7 @@ import type {OnyxEntry} from 'react-native-onyx';
import {useOnyx} from 'react-native-onyx';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
+import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import * as CurrencyUtils from '@libs/CurrencyUtils';
@@ -41,7 +42,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;
@@ -53,16 +54,14 @@ type MoneyReportHeaderProps = {
// eslint-disable-next-line react/no-unused-prop-types
transactionThreadReportID?: string | null;
- /** Whether we should display the header as in narrow layout */
- shouldUseNarrowLayout?: boolean;
-
/** Method to trigger when pressing close button of the header */
onBackButtonPress: () => void;
};
-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}`);
+function MoneyReportHeader({policy, report: moneyRequestReport, transactionThreadReportID, reportActions, onBackButtonPress}: MoneyReportHeaderProps) {
+ const {shouldUseNarrowLayout, isSmallScreenWidth} = useResponsiveLayout();
+ 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 +108,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 +122,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 +136,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();
@@ -255,14 +254,14 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
return;
}
- if (shouldUseNarrowLayout) {
- if (Navigation.getActiveRoute().slice(1) === ROUTES.PROCESS_MONEY_REQUEST_HOLD) {
+ if (isSmallScreenWidth) {
+ if (Navigation.getActiveRoute().slice(1) === ROUTES.PROCESS_MONEY_REQUEST_HOLD.route) {
Navigation.goBack();
}
} else {
- Navigation.navigate(ROUTES.PROCESS_MONEY_REQUEST_HOLD);
+ Navigation.navigate(ROUTES.PROCESS_MONEY_REQUEST_HOLD.getRoute(Navigation.getReportRHPActiveRoute()));
}
- }, [shouldUseNarrowLayout, shouldShowHoldMenu]);
+ }, [isSmallScreenWidth, shouldShowHoldMenu]);
const handleHoldRequestClose = () => {
IOU.dismissHoldUseExplanation();
@@ -285,6 +284,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 +292,9 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
{shouldShowSettlementButton && !shouldUseNarrowLayout && (
{shouldShowSettlementButton && shouldUseNarrowLayout && (
- {shouldUseNarrowLayout && shouldShowHoldMenu && (
+ {isSmallScreenWidth && shouldShowHoldMenu && (
{
- const activeRoute = Navigation.getActiveRouteWithoutParams();
+ const activeRoute = Navigation.getActiveRoute();
if (option.isSelfDM) {
Navigation.navigate(ROUTES.PROFILE.getRoute(currentUserPersonalDetails.accountID, activeRoute));
diff --git a/src/components/MoneyRequestConfirmationListFooter.tsx b/src/components/MoneyRequestConfirmationListFooter.tsx
index b77e3b993b8..e6b957f16b6 100644
--- a/src/components/MoneyRequestConfirmationListFooter.tsx
+++ b/src/components/MoneyRequestConfirmationListFooter.tsx
@@ -35,6 +35,7 @@ import PDFThumbnail from './PDFThumbnail';
import PressableWithoutFocus from './Pressable/PressableWithoutFocus';
import ReceiptEmptyState from './ReceiptEmptyState';
import ReceiptImage from './ReceiptImage';
+import {ShowContextMenuContext} from './ShowContextMenuContext';
import ShowMoreButton from './ShowMoreButton';
type MoneyRequestConfirmationListFooterProps = {
@@ -268,6 +269,18 @@ function MoneyRequestConfirmationListFooter({
const resolvedThumbnail = isLocalFile ? receiptThumbnail : tryResolveUrlFromApiRoot(receiptThumbnail ?? '');
const resolvedReceiptImage = isLocalFile ? receiptImage : tryResolveUrlFromApiRoot(receiptImage ?? '');
+ const contextMenuContextValue = useMemo(
+ () => ({
+ 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".
@@ -286,7 +299,7 @@ function MoneyRequestConfirmationListFooter({
return;
}
- Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_AMOUNT.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()));
+ Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_AMOUNT.getRoute(action, iouType, transactionID, reportID, CONST.IOU.PAGE_INDEX.CONFIRM, Navigation.getActiveRoute()));
}}
style={[styles.moneyRequestMenuItem, styles.mt2]}
titleStyle={styles.moneyRequestConfirmationAmount}
@@ -300,29 +313,29 @@ 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.getActiveRoute(), reportActionID));
+ }}
+ style={[styles.moneyRequestMenuItem]}
+ titleStyle={styles.flex1}
+ disabled={didConfirm}
+ interactive={!isReadOnly}
+ numberOfLinesTitle={2}
+ />
+
+
),
shouldShow: true,
isSupplementary: false,
@@ -389,7 +402,7 @@ function MoneyRequestConfirmationListFooter({
style={[styles.moneyRequestMenuItem]}
titleStyle={styles.flex1}
onPress={() => {
- Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_MERCHANT.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()));
+ Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_MERCHANT.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRoute()));
}}
disabled={didConfirm}
interactive={!isReadOnly}
@@ -413,7 +426,7 @@ function MoneyRequestConfirmationListFooter({
style={[styles.moneyRequestMenuItem]}
titleStyle={styles.flex1}
onPress={() => {
- Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DATE.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams(), reportActionID));
+ Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DATE.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRoute(), reportActionID));
}}
disabled={didConfirm}
interactive={!isReadOnly}
@@ -432,9 +445,7 @@ function MoneyRequestConfirmationListFooter({
title={iouCategory}
description={translate('common.category')}
numberOfLinesTitle={2}
- onPress={() =>
- Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CATEGORY.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams(), reportActionID))
- }
+ onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CATEGORY.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRoute(), reportActionID))}
style={[styles.moneyRequestMenuItem]}
titleStyle={styles.flex1}
disabled={didConfirm}
@@ -457,9 +468,7 @@ function MoneyRequestConfirmationListFooter({
description={name}
numberOfLinesTitle={2}
onPress={() =>
- Navigation.navigate(
- ROUTES.MONEY_REQUEST_STEP_TAG.getRoute(action, iouType, index, transactionID, reportID, Navigation.getActiveRouteWithoutParams(), reportActionID),
- )
+ Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAG.getRoute(action, iouType, index, transactionID, reportID, Navigation.getActiveRoute(), reportActionID))
}
style={[styles.moneyRequestMenuItem]}
disabled={didConfirm}
@@ -480,7 +489,7 @@ function MoneyRequestConfirmationListFooter({
description={taxRates?.name}
style={[styles.moneyRequestMenuItem]}
titleStyle={styles.flex1}
- onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAX_RATE.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()))}
+ onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAX_RATE.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRoute()))}
disabled={didConfirm}
interactive={canModifyTaxFields}
/>
@@ -497,7 +506,7 @@ function MoneyRequestConfirmationListFooter({
description={translate('iou.taxAmount')}
style={[styles.moneyRequestMenuItem]}
titleStyle={styles.flex1}
- onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()))}
+ onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRoute()))}
disabled={didConfirm}
interactive={canModifyTaxFields}
/>
diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx
index 0e0633042a7..d2db257dc77 100644
--- a/src/components/MoneyRequestHeader.tsx
+++ b/src/components/MoneyRequestHeader.tsx
@@ -4,6 +4,7 @@ import {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import {useOnyx} from 'react-native-onyx';
import useLocalize from '@hooks/useLocalize';
+import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
@@ -27,7 +28,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;
@@ -35,15 +36,13 @@ type MoneyRequestHeaderProps = {
/** The report action the transaction is tied to from the parent report */
parentReportAction: OnyxEntry;
- /** Whether we should display the header as in narrow layout */
- shouldUseNarrowLayout?: boolean;
-
/** Method to trigger when pressing close button of the header */
onBackButtonPress: () => void;
};
-function MoneyRequestHeader({report, parentReportAction, policy, shouldUseNarrowLayout = false, onBackButtonPress}: MoneyRequestHeaderProps) {
- const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`);
+function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPress}: MoneyRequestHeaderProps) {
+ const {shouldUseNarrowLayout, isSmallScreenWidth} = useResponsiveLayout();
+ 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 +57,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);
@@ -106,14 +106,14 @@ function MoneyRequestHeader({report, parentReportAction, policy, shouldUseNarrow
return;
}
- if (shouldUseNarrowLayout) {
- if (Navigation.getActiveRoute().slice(1) === ROUTES.PROCESS_MONEY_REQUEST_HOLD) {
+ if (isSmallScreenWidth) {
+ if (Navigation.getActiveRoute().slice(1) === ROUTES.PROCESS_MONEY_REQUEST_HOLD.route) {
Navigation.goBack();
}
} else {
- Navigation.navigate(ROUTES.PROCESS_MONEY_REQUEST_HOLD);
+ Navigation.navigate(ROUTES.PROCESS_MONEY_REQUEST_HOLD.getRoute(Navigation.getReportRHPActiveRoute()));
}
- }, [shouldUseNarrowLayout, shouldShowHoldMenu]);
+ }, [isSmallScreenWidth, shouldShowHoldMenu]);
const handleHoldRequestClose = () => {
IOU.dismissHoldUseExplanation();
@@ -129,10 +129,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 +151,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 ?? '', Navigation.getReportRHPActiveRoute()));
}}
/>
)}
@@ -171,7 +173,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 ?? '', Navigation.getReportRHPActiveRoute()));
}}
/>
@@ -185,7 +187,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, shouldUseNarrow
)}
- {shouldUseNarrowLayout && shouldShowHoldMenu && (
+ {isSmallScreenWidth && shouldShowHoldMenu && (
{
- 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 4a63714b615..8cbbd1199b3 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/PromotedActionsBar.tsx b/src/components/PromotedActionsBar.tsx
index c374259f044..ee940fe2cf1 100644
--- a/src/components/PromotedActionsBar.tsx
+++ b/src/components/PromotedActionsBar.tsx
@@ -24,12 +24,14 @@ type PromotedAction = {
key: string;
} & ThreeDotsMenuItem;
-type BasePromotedActions = typeof CONST.PROMOTED_ACTIONS.PIN | typeof CONST.PROMOTED_ACTIONS.SHARE | typeof CONST.PROMOTED_ACTIONS.JOIN;
+type BasePromotedActions = typeof CONST.PROMOTED_ACTIONS.PIN | typeof CONST.PROMOTED_ACTIONS.JOIN;
type PromotedActionsType = Record PromotedAction> & {
- message: (params: {reportID?: string; accountID?: number; login?: string}) => PromotedAction;
+ [CONST.PROMOTED_ACTIONS.SHARE]: (report: OnyxReport, backTo?: string) => PromotedAction;
} & {
- hold: (params: {
+ [CONST.PROMOTED_ACTIONS.MESSAGE]: (params: {reportID?: string; accountID?: number; login?: string}) => PromotedAction;
+} & {
+ [CONST.PROMOTED_ACTIONS.HOLD]: (params: {
isTextHold: boolean;
reportAction: ReportAction | undefined;
reportID?: string;
@@ -43,9 +45,9 @@ const PromotedActions = {
key: CONST.PROMOTED_ACTIONS.PIN,
...HeaderUtils.getPinMenuItem(report),
}),
- share: (report) => ({
+ share: (report, backTo) => ({
key: CONST.PROMOTED_ACTIONS.SHARE,
- ...HeaderUtils.getShareMenuItem(report),
+ ...HeaderUtils.getShareMenuItem(report, backTo),
}),
join: (report) => ({
key: CONST.PROMOTED_ACTIONS.JOIN,
diff --git a/src/components/ReportActionItem/MoneyReportView.tsx b/src/components/ReportActionItem/MoneyReportView.tsx
index 8546aa8165c..19ee17bba3b 100644
--- a/src/components/ReportActionItem/MoneyReportView.tsx
+++ b/src/components/ReportActionItem/MoneyReportView.tsx
@@ -127,7 +127,16 @@ function MoneyReportView({report, policy, isCombinedReport = false, shouldShowTo
Navigation.navigate(ROUTES.EDIT_REPORT_FIELD_REQUEST.getRoute(report.reportID, report.policyID ?? '-1', reportField.fieldID))}
+ onPress={() =>
+ Navigation.navigate(
+ ROUTES.EDIT_REPORT_FIELD_REQUEST.getRoute(
+ report.reportID,
+ report.policyID ?? '-1',
+ reportField.fieldID,
+ Navigation.getReportRHPActiveRoute(),
+ ),
+ )
+ }
shouldShowRightIcon
disabled={isFieldDisabled}
wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]}
diff --git a/src/components/ReportActionItem/MoneyRequestAction.tsx b/src/components/ReportActionItem/MoneyRequestAction.tsx
index 15f9cee3705..af54e2940d3 100644
--- a/src/components/ReportActionItem/MoneyRequestAction.tsx
+++ b/src/components/ReportActionItem/MoneyRequestAction.tsx
@@ -89,7 +89,7 @@ function MoneyRequestAction({
const onMoneyRequestPreviewPressed = () => {
if (isSplitBillAction) {
const reportActionID = action.reportActionID ?? '-1';
- Navigation.navigate(ROUTES.SPLIT_BILL_DETAILS.getRoute(chatReportID, reportActionID));
+ Navigation.navigate(ROUTES.SPLIT_BILL_DETAILS.getRoute(chatReportID, reportActionID, Navigation.getReportRHPActiveRoute()));
return;
}
diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx
index f2460cae71e..6aa047052e1 100644
--- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx
+++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx
@@ -283,24 +283,25 @@ function MoneyRequestPreviewContent({
);
const navigateToReviewFields = () => {
+ const backTo = route.params.backTo;
const comparisonResult = TransactionUtils.compareDuplicateTransactionFields(reviewingTransactionID);
Transaction.setReviewDuplicatesKey({...comparisonResult.keep, duplicates, transactionID: transaction?.transactionID ?? ''});
if ('merchant' in comparisonResult.change) {
- Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_MERCHANT_PAGE.getRoute(route.params?.threadReportID));
+ Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_MERCHANT_PAGE.getRoute(route.params?.threadReportID, backTo));
} else if ('category' in comparisonResult.change) {
- Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_CATEGORY_PAGE.getRoute(route.params?.threadReportID));
+ Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_CATEGORY_PAGE.getRoute(route.params?.threadReportID, backTo));
} else if ('tag' in comparisonResult.change) {
- Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_TAG_PAGE.getRoute(route.params?.threadReportID));
+ Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_TAG_PAGE.getRoute(route.params?.threadReportID, backTo));
} else if ('description' in comparisonResult.change) {
- Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_DESCRIPTION_PAGE.getRoute(route.params?.threadReportID));
+ Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_DESCRIPTION_PAGE.getRoute(route.params?.threadReportID, backTo));
} else if ('taxCode' in comparisonResult.change) {
- Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_TAX_CODE_PAGE.getRoute(route.params?.threadReportID));
+ Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_TAX_CODE_PAGE.getRoute(route.params?.threadReportID, backTo));
} else if ('billable' in comparisonResult.change) {
- Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_BILLABLE_PAGE.getRoute(route.params?.threadReportID));
+ Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_BILLABLE_PAGE.getRoute(route.params?.threadReportID, backTo));
} else if ('reimbursable' in comparisonResult.change) {
- Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_REIMBURSABLE_PAGE.getRoute(route.params?.threadReportID));
+ Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_REIMBURSABLE_PAGE.getRoute(route.params?.threadReportID, backTo));
} else {
- Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_CONFIRMATION_PAGE.getRoute(route.params?.threadReportID));
+ Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_CONFIRMATION_PAGE.getRoute(route.params?.threadReportID, backTo));
}
};
diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx
index 9559a19d08a..549fd55ac1b 100644
--- a/src/components/ReportActionItem/MoneyRequestView.tsx
+++ b/src/components/ReportActionItem/MoneyRequestView.tsx
@@ -328,7 +328,15 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals
shouldShowRightIcon={canEditDistance}
titleStyle={styles.flex1}
onPress={() =>
- Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '-1', report?.reportID ?? '-1'))
+ Navigation.navigate(
+ ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(
+ CONST.IOU.ACTION.EDIT,
+ iouType,
+ transaction?.transactionID ?? '-1',
+ report?.reportID ?? '-1',
+ Navigation.getReportRHPActiveRoute(),
+ ),
+ )
}
/>
@@ -340,7 +348,15 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals
shouldShowRightIcon={canEditDistanceRate}
titleStyle={styles.flex1}
onPress={() =>
- Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE_RATE.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '-1', report?.reportID ?? '-1'))
+ Navigation.navigate(
+ ROUTES.MONEY_REQUEST_STEP_DISTANCE_RATE.getRoute(
+ CONST.IOU.ACTION.EDIT,
+ iouType,
+ transaction?.transactionID ?? '-1',
+ report?.reportID ?? '-1',
+ Navigation.getReportRHPActiveRoute(),
+ ),
+ )
}
brickRoadIndicator={getErrorForField('customUnitRateID') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
errorText={getErrorForField('customUnitRateID')}
@@ -355,7 +371,17 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals
interactive={canEditDistance}
shouldShowRightIcon={canEditDistance}
titleStyle={styles.flex1}
- onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '-1', report?.reportID ?? '-1'))}
+ onPress={() =>
+ Navigation.navigate(
+ ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(
+ CONST.IOU.ACTION.EDIT,
+ iouType,
+ transaction?.transactionID ?? '-1',
+ report?.reportID ?? '-1',
+ Navigation.getReportRHPActiveRoute(),
+ ),
+ )
+ }
/>
);
@@ -423,7 +449,16 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals
shouldShowRightIcon={canEdit}
titleStyle={styles.flex1}
onPress={() =>
- Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAG.getRoute(CONST.IOU.ACTION.EDIT, iouType, orderWeight, transaction?.transactionID ?? '', report?.reportID ?? '-1'))
+ Navigation.navigate(
+ ROUTES.MONEY_REQUEST_STEP_TAG.getRoute(
+ CONST.IOU.ACTION.EDIT,
+ iouType,
+ orderWeight,
+ transaction?.transactionID ?? '',
+ report?.reportID ?? '-1',
+ Navigation.getReportRHPActiveRoute(),
+ ),
+ )
}
brickRoadIndicator={tagError ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
errorText={tagError}
@@ -500,7 +535,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals
iouType,
transaction?.transactionID ?? '-1',
report?.reportID ?? '-1',
- Navigation.getActiveRouteWithoutParams(),
+ Navigation.getReportRHPActiveRoute(),
),
)
}
@@ -518,7 +553,16 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals
interactive={canEditAmount}
shouldShowRightIcon={canEditAmount}
onPress={() =>
- Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_AMOUNT.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '-1', report?.reportID ?? '-1'))
+ Navigation.navigate(
+ ROUTES.MONEY_REQUEST_STEP_AMOUNT.getRoute(
+ CONST.IOU.ACTION.EDIT,
+ iouType,
+ transaction?.transactionID ?? '-1',
+ report?.reportID ?? '-1',
+ '',
+ Navigation.getReportRHPActiveRoute(),
+ ),
+ )
}
brickRoadIndicator={getErrorForField('amount') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
errorText={getErrorForField('amount')}
@@ -533,7 +577,15 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals
shouldShowRightIcon={canEdit}
titleStyle={styles.flex1}
onPress={() =>
- Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '-1', report?.reportID ?? '-1'))
+ Navigation.navigate(
+ ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.getRoute(
+ CONST.IOU.ACTION.EDIT,
+ iouType,
+ transaction?.transactionID ?? '-1',
+ report?.reportID ?? '-1',
+ Navigation.getReportRHPActiveRoute(),
+ ),
+ )
}
wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]}
brickRoadIndicator={getErrorForField('comment') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
@@ -552,7 +604,15 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals
shouldShowRightIcon={canEditMerchant}
titleStyle={styles.flex1}
onPress={() =>
- Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_MERCHANT.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '-1', report?.reportID ?? '-1'))
+ Navigation.navigate(
+ ROUTES.MONEY_REQUEST_STEP_MERCHANT.getRoute(
+ CONST.IOU.ACTION.EDIT,
+ iouType,
+ transaction?.transactionID ?? '-1',
+ report?.reportID ?? '-1',
+ Navigation.getReportRHPActiveRoute(),
+ ),
+ )
}
wrapperStyle={[styles.taskDescriptionMenuItem]}
brickRoadIndicator={getErrorForField('merchant') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
@@ -569,7 +629,15 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals
shouldShowRightIcon={canEditDate}
titleStyle={styles.flex1}
onPress={() =>
- Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DATE.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '-1', report?.reportID ?? '-1' ?? '-1'))
+ Navigation.navigate(
+ ROUTES.MONEY_REQUEST_STEP_DATE.getRoute(
+ CONST.IOU.ACTION.EDIT,
+ iouType,
+ transaction?.transactionID ?? '-1',
+ report?.reportID ?? '-1' ?? '-1',
+ Navigation.getReportRHPActiveRoute(),
+ ),
+ )
}
brickRoadIndicator={getErrorForField('date') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
errorText={getErrorForField('date')}
@@ -584,7 +652,15 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals
shouldShowRightIcon={canEdit}
titleStyle={styles.flex1}
onPress={() =>
- Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CATEGORY.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '-1', report?.reportID ?? '-1'))
+ Navigation.navigate(
+ ROUTES.MONEY_REQUEST_STEP_CATEGORY.getRoute(
+ CONST.IOU.ACTION.EDIT,
+ iouType,
+ transaction?.transactionID ?? '-1',
+ report?.reportID ?? '-1',
+ Navigation.getReportRHPActiveRoute(),
+ ),
+ )
}
brickRoadIndicator={getErrorForField('category') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
errorText={getErrorForField('category')}
@@ -611,7 +687,15 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals
shouldShowRightIcon={canEditTaxFields}
titleStyle={styles.flex1}
onPress={() =>
- Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAX_RATE.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '-1', report?.reportID ?? '-1'))
+ Navigation.navigate(
+ ROUTES.MONEY_REQUEST_STEP_TAX_RATE.getRoute(
+ CONST.IOU.ACTION.EDIT,
+ iouType,
+ transaction?.transactionID ?? '-1',
+ report?.reportID ?? '-1',
+ Navigation.getReportRHPActiveRoute(),
+ ),
+ )
}
brickRoadIndicator={getErrorForField('tax') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
errorText={getErrorForField('tax')}
@@ -628,7 +712,13 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals
titleStyle={styles.flex1}
onPress={() =>
Navigation.navigate(
- ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '-1', report?.reportID ?? '-1'),
+ ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.getRoute(
+ CONST.IOU.ACTION.EDIT,
+ iouType,
+ transaction?.transactionID ?? '-1',
+ report?.reportID ?? '-1',
+ Navigation.getReportRHPActiveRoute(),
+ ),
)
}
/>
diff --git a/src/components/ReportActionItem/TaskPreview.tsx b/src/components/ReportActionItem/TaskPreview.tsx
index 7a6e4942b17..053ad0c2c63 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/ReportActionItem/TaskView.tsx b/src/components/ReportActionItem/TaskView.tsx
index 2822e34a3d0..9a2906aa7d6 100644
--- a/src/components/ReportActionItem/TaskView.tsx
+++ b/src/components/ReportActionItem/TaskView.tsx
@@ -81,7 +81,7 @@ function TaskView({report, ...props}: TaskViewProps) {
(e.currentTarget as HTMLElement).blur();
}
- Navigation.navigate(ROUTES.TASK_TITLE.getRoute(report.reportID));
+ Navigation.navigate(ROUTES.TASK_TITLE.getRoute(report.reportID, Navigation.getReportRHPActiveRoute()));
})}
style={({pressed}) => [
styles.ph5,
@@ -144,7 +144,7 @@ function TaskView({report, ...props}: TaskViewProps) {
shouldRenderAsHTML
description={translate('task.description')}
title={report.description ?? ''}
- onPress={() => Navigation.navigate(ROUTES.REPORT_DESCRIPTION.getRoute(report.reportID))}
+ onPress={() => Navigation.navigate(ROUTES.REPORT_DESCRIPTION.getRoute(report.reportID, Navigation.getReportRHPActiveRoute()))}
shouldShowRightIcon={isOpen}
disabled={disableState}
wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]}
@@ -162,7 +162,7 @@ function TaskView({report, ...props}: TaskViewProps) {
iconType={CONST.ICON_TYPE_AVATAR}
avatarSize={CONST.AVATAR_SIZE.SMALLER}
titleStyle={styles.assigneeTextStyle}
- onPress={() => Navigation.navigate(ROUTES.TASK_ASSIGNEE.getRoute(report.reportID))}
+ onPress={() => Navigation.navigate(ROUTES.TASK_ASSIGNEE.getRoute(report.reportID, Navigation.getReportRHPActiveRoute()))}
shouldShowRightIcon={isOpen}
disabled={disableState}
wrapperStyle={[styles.pv2]}
@@ -174,7 +174,7 @@ function TaskView({report, ...props}: TaskViewProps) {
) : (
Navigation.navigate(ROUTES.TASK_ASSIGNEE.getRoute(report.reportID))}
+ onPress={() => Navigation.navigate(ROUTES.TASK_ASSIGNEE.getRoute(report.reportID, Navigation.getReportRHPActiveRoute()))}
shouldShowRightIcon={isOpen}
disabled={disableState}
wrapperStyle={[styles.pv2]}
diff --git a/src/components/ReportWelcomeText.tsx b/src/components/ReportWelcomeText.tsx
index 96f705ea2d5..68f060d22e6 100644
--- a/src/components/ReportWelcomeText.tsx
+++ b/src/components/ReportWelcomeText.tsx
@@ -57,7 +57,7 @@ function ReportWelcomeText({report, policy, personalDetails}: ReportWelcomeTextP
return;
}
- Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report.reportID));
+ Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report.reportID, Navigation.getReportRHPActiveRoute()));
};
const welcomeHeroText = useMemo(() => {
@@ -113,11 +113,12 @@ function ReportWelcomeText({report, policy, personalDetails}: ReportWelcomeTextP
(welcomeMessage?.messageHtml ? (
{
+ const activeRoute = Navigation.getReportRHPActiveRoute();
if (ReportUtils.canEditReportDescription(report, policy)) {
- Navigation.navigate(ROUTES.REPORT_DESCRIPTION.getRoute(report?.reportID ?? '-1'));
+ Navigation.navigate(ROUTES.REPORT_DESCRIPTION.getRoute(report?.reportID ?? '-1', activeRoute));
return;
}
- Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report?.reportID ?? '-1'));
+ Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report?.reportID ?? '-1', activeRoute));
}}
style={styles.renderHTML}
accessibilityLabel={translate('reportDescriptionPage.roomDescription')}
@@ -161,7 +162,7 @@ function ReportWelcomeText({report, policy, personalDetails}: ReportWelcomeTextP
) : (
Navigation.navigate(ROUTES.PROFILE.getRoute(accountID))}
+ onPress={() => Navigation.navigate(ROUTES.PROFILE.getRoute(accountID, Navigation.getReportRHPActiveRoute()))}
suppressHighlighting
>
{displayName}
diff --git a/src/components/Search/SearchPageHeader.tsx b/src/components/Search/SearchPageHeader.tsx
index 73829989409..023e7315e11 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 & {
@@ -58,7 +59,7 @@ function HeaderWrapper({icon, title, subtitle, children, subtitleStyles = {}}: H
}
subtitle={
{subtitle}
@@ -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 4948c90ce3d..05693ad5ea2 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 63699d34ce0..dfe2cbbe16c 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 4e23c0883f8..d1080da1993 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 9ee264bc9bf..197c64b99a2 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 c1085c71e0f..14c83ef25ed 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/hooks/useReviewDuplicatesNavigation.tsx b/src/hooks/useReviewDuplicatesNavigation.tsx
index e14731024c1..de905647e44 100644
--- a/src/hooks/useReviewDuplicatesNavigation.tsx
+++ b/src/hooks/useReviewDuplicatesNavigation.tsx
@@ -3,50 +3,86 @@ import Navigation from '@libs/Navigation/Navigation';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
-type StepName = 'description' | 'merchant' | 'category' | 'billable' | 'tag' | 'taxCode' | 'reimbursable';
+type StepName = 'description' | 'merchant' | 'category' | 'billable' | 'tag' | 'taxCode' | 'reimbursable' | 'confirmation';
-function useReviewDuplicatesNavigation(stepNames: string[], currentScreenName: StepName, threadReportID: string) {
- const [nextScreen, setNextScreen] = useState(currentScreenName);
+function useReviewDuplicatesNavigation(stepNames: string[], currentScreenName: StepName, threadReportID: string, backTo?: string) {
+ const [nextScreen, setNextScreen] = useState();
+ const [prevScreen, setPrevScreen] = useState();
const [currentScreenIndex, setCurrentScreenIndex] = useState(0);
const intersection = useMemo(() => CONST.REVIEW_DUPLICATES_ORDER.filter((element) => stepNames.includes(element)), [stepNames]);
useEffect(() => {
+ if (currentScreenName === 'confirmation') {
+ setPrevScreen(intersection[intersection.length - 1] ?? '');
+ return;
+ }
const currentIndex = intersection.indexOf(currentScreenName);
const nextScreenIndex = currentIndex + 1;
+ const prevScreenIndex = currentIndex - 1;
setCurrentScreenIndex(currentIndex);
setNextScreen(intersection[nextScreenIndex] ?? '');
+ setPrevScreen(intersection[prevScreenIndex] ?? '');
}, [currentScreenName, intersection]);
+ const goBack = () => {
+ switch (prevScreen) {
+ case 'merchant':
+ Navigation.goBack(ROUTES.TRANSACTION_DUPLICATE_REVIEW_MERCHANT_PAGE.getRoute(threadReportID, backTo));
+ break;
+ case 'category':
+ Navigation.goBack(ROUTES.TRANSACTION_DUPLICATE_REVIEW_CATEGORY_PAGE.getRoute(threadReportID, backTo));
+ break;
+ case 'tag':
+ Navigation.goBack(ROUTES.TRANSACTION_DUPLICATE_REVIEW_TAG_PAGE.getRoute(threadReportID, backTo));
+ break;
+ case 'description':
+ Navigation.goBack(ROUTES.TRANSACTION_DUPLICATE_REVIEW_DESCRIPTION_PAGE.getRoute(threadReportID, backTo));
+ break;
+ case 'taxCode':
+ Navigation.goBack(ROUTES.TRANSACTION_DUPLICATE_REVIEW_TAX_CODE_PAGE.getRoute(threadReportID, backTo));
+ break;
+ case 'reimbursable':
+ Navigation.goBack(ROUTES.TRANSACTION_DUPLICATE_REVIEW_REIMBURSABLE_PAGE.getRoute(threadReportID, backTo));
+ break;
+ case 'billable':
+ Navigation.goBack(ROUTES.TRANSACTION_DUPLICATE_REVIEW_BILLABLE_PAGE.getRoute(threadReportID, backTo));
+ break;
+ default:
+ Navigation.goBack(ROUTES.TRANSACTION_DUPLICATE_REVIEW_PAGE.getRoute(threadReportID, backTo));
+ break;
+ }
+ };
+
const navigateToNextScreen = () => {
switch (nextScreen) {
case 'merchant':
- Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_MERCHANT_PAGE.getRoute(threadReportID));
+ Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_MERCHANT_PAGE.getRoute(threadReportID, backTo));
break;
case 'category':
- Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_CATEGORY_PAGE.getRoute(threadReportID));
+ Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_CATEGORY_PAGE.getRoute(threadReportID, backTo));
break;
case 'tag':
- Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_TAG_PAGE.getRoute(threadReportID));
+ Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_TAG_PAGE.getRoute(threadReportID, backTo));
break;
case 'description':
- Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_DESCRIPTION_PAGE.getRoute(threadReportID));
+ Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_DESCRIPTION_PAGE.getRoute(threadReportID, backTo));
break;
case 'taxCode':
- Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_TAX_CODE_PAGE.getRoute(threadReportID));
+ Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_TAX_CODE_PAGE.getRoute(threadReportID, backTo));
break;
case 'reimbursable':
- Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_REIMBURSABLE_PAGE.getRoute(threadReportID));
+ Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_REIMBURSABLE_PAGE.getRoute(threadReportID, backTo));
break;
case 'billable':
- Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_BILLABLE_PAGE.getRoute(threadReportID));
+ Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_BILLABLE_PAGE.getRoute(threadReportID, backTo));
break;
default:
- Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_CONFIRMATION_PAGE.getRoute(threadReportID));
+ Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_CONFIRMATION_PAGE.getRoute(threadReportID, backTo));
break;
}
};
- return {navigateToNextScreen, currentScreenIndex};
+ return {navigateToNextScreen, goBack, currentScreenIndex};
}
export default useReviewDuplicatesNavigation;
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 4cfe7c59e6c..5883b3f4ce8 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -2140,6 +2140,8 @@ export default {
},
bookTravel: 'Book travel',
bookDemo: 'Book demo',
+ bookADemo: 'Book a demo',
+ toLearnMore: ' to learn more.',
termsAndConditions: {
header: 'Before we continue...',
title: 'Please read the Terms & Conditions for travel',
@@ -2881,6 +2883,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 d6b3c6b2075..756ec884514 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -2170,6 +2170,8 @@ export default {
},
bookTravel: 'Reservar viajes',
bookDemo: 'Pedir demostraciĆ³n',
+ bookADemo: 'Reserva una demo',
+ toLearnMore: ' para obtener mĆ”s informaciĆ³n.',
termsAndConditions: {
header: 'Antes de continuar...',
title: 'Por favor, lee los TĆ©rminos y condiciones para reservar viajes',
@@ -2926,6 +2928,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/API/index.ts b/src/libs/API/index.ts
index 65fd2b6ad01..be1706886b1 100644
--- a/src/libs/API/index.ts
+++ b/src/libs/API/index.ts
@@ -116,10 +116,10 @@ function processRequest(request: OnyxRequest, type: ApiRequestType): Promise(command: TCommand, apiCommandParameters: ApiRequestCommandParameters[TCommand], onyxData: OnyxData = {}): void {
+function write(command: TCommand, apiCommandParameters: ApiRequestCommandParameters[TCommand], onyxData: OnyxData = {}): Promise {
Log.info('[API] Called API write', false, {command, ...apiCommandParameters});
const request = prepareRequest(command, CONST.API_REQUEST_TYPE.WRITE, apiCommandParameters, onyxData);
- processRequest(request, CONST.API_REQUEST_TYPE.WRITE);
+ return processRequest(request, CONST.API_REQUEST_TYPE.WRITE);
}
/**
diff --git a/src/libs/API/parameters/SaveSearch.ts b/src/libs/API/parameters/SaveSearch.ts
index e0ad38dd836..9dd3416320c 100644
--- a/src/libs/API/parameters/SaveSearch.ts
+++ b/src/libs/API/parameters/SaveSearch.ts
@@ -2,7 +2,7 @@ import type {SearchQueryString} from '@components/Search/types';
type SaveSearchParams = {
jsonQuery: SearchQueryString;
- name?: string;
+ newName?: string;
};
export default SaveSearchParams;
diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts
index 88372382d3c..8e35a0cb198 100644
--- a/src/libs/API/types.ts
+++ b/src/libs/API/types.ts
@@ -90,6 +90,7 @@ const WRITE_COMMANDS = {
SIGN_IN_WITH_GOOGLE: 'SignInWithGoogle',
SIGN_IN_USER: 'SigninUser',
SIGN_IN_USER_WITH_LINK: 'SigninUserWithLink',
+ SEARCH: 'Search',
REQUEST_UNLINK_VALIDATION_LINK: 'RequestUnlinkValidationLink',
UNLINK_LOGIN: 'UnlinkLogin',
ENABLE_TWO_FACTOR_AUTH: 'EnableTwoFactorAuth',
@@ -633,6 +634,7 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.REMOVE_POLICY_CATEGORY_RECEIPTS_REQUIRED]: Parameters.RemovePolicyCategoryReceiptsRequiredParams;
[WRITE_COMMANDS.SET_POLICY_CATEGORY_MAX_AMOUNT]: Parameters.SetPolicyCategoryMaxAmountParams;
[WRITE_COMMANDS.SET_POLICY_CATEGORY_APPROVER]: Parameters.SetPolicyCategoryApproverParams;
+ [WRITE_COMMANDS.SEARCH]: Parameters.SearchParams;
[WRITE_COMMANDS.SET_POLICY_CATEGORY_TAX]: Parameters.SetPolicyCategoryTaxParams;
[WRITE_COMMANDS.JOIN_POLICY_VIA_INVITE_LINK]: Parameters.JoinPolicyInviteLinkParams;
[WRITE_COMMANDS.ACCEPT_JOIN_REQUEST]: Parameters.AcceptJoinRequestParams;
@@ -872,7 +874,6 @@ const READ_COMMANDS = {
OPEN_POLICY_ACCOUNTING_PAGE: 'OpenPolicyAccountingPage',
OPEN_POLICY_PROFILE_PAGE: 'OpenPolicyProfilePage',
OPEN_POLICY_INITIAL_PAGE: 'OpenPolicyInitialPage',
- SEARCH: 'Search',
OPEN_SUBSCRIPTION_PAGE: 'OpenSubscriptionPage',
OPEN_DRAFT_DISTANCE_EXPENSE: 'OpenDraftDistanceExpense',
START_ISSUE_NEW_CARD_FLOW: 'StartIssueNewCardFlow',
@@ -930,7 +931,6 @@ type ReadCommandParameters = {
[READ_COMMANDS.OPEN_POLICY_EDIT_CARD_LIMIT_TYPE_PAGE]: Parameters.OpenPolicyEditCardLimitTypePageParams;
[READ_COMMANDS.OPEN_POLICY_PROFILE_PAGE]: Parameters.OpenPolicyProfilePageParams;
[READ_COMMANDS.OPEN_POLICY_INITIAL_PAGE]: Parameters.OpenPolicyInitialPageParams;
- [READ_COMMANDS.SEARCH]: Parameters.SearchParams;
[READ_COMMANDS.OPEN_SUBSCRIPTION_PAGE]: null;
[READ_COMMANDS.OPEN_DRAFT_DISTANCE_EXPENSE]: null;
[READ_COMMANDS.START_ISSUE_NEW_CARD_FLOW]: Parameters.StartIssueNewCardFlowParams;
diff --git a/src/libs/DateUtils.ts b/src/libs/DateUtils.ts
index 6b43a549256..2de905ff604 100644
--- a/src/libs/DateUtils.ts
+++ b/src/libs/DateUtils.ts
@@ -47,6 +47,8 @@ type CustomStatusTypes = ValueOf;
type Locale = ValueOf;
type WeekDay = 0 | 1 | 2 | 3 | 4 | 5 | 6;
+const TIMEZONE_UPDATE_THROTTLE_MINUTES = 5;
+
let currentUserAccountID: number | undefined;
Onyx.connect({
key: ONYXKEYS.SESSION,
@@ -352,12 +354,12 @@ function getDaysOfWeek(preferredLocale: Locale): string[] {
return daysOfWeek.map((date) => format(date, 'eeee'));
}
-// Used to throttle updates to the timezone when necessary
-let lastUpdatedTimezoneTime = new Date();
+// Used to throttle updates to the timezone when necessary. Initialize outside the throttle window so it's updated the first time.
+let lastUpdatedTimezoneTime = subMinutes(new Date(), TIMEZONE_UPDATE_THROTTLE_MINUTES + 1);
function canUpdateTimezone(): boolean {
const currentTime = new Date();
- const fiveMinutesAgo = subMinutes(currentTime, 5);
+ const fiveMinutesAgo = subMinutes(currentTime, TIMEZONE_UPDATE_THROTTLE_MINUTES);
// Compare the last updated time with five minutes ago
return isBefore(lastUpdatedTimezoneTime, fiveMinutesAgo);
}
diff --git a/src/libs/HeaderUtils.ts b/src/libs/HeaderUtils.ts
index 03c582d6b16..b31d59804c5 100644
--- a/src/libs/HeaderUtils.ts
+++ b/src/libs/HeaderUtils.ts
@@ -17,11 +17,11 @@ function getPinMenuItem(report: OnyxReport): ThreeDotsMenuItem {
};
}
-function getShareMenuItem(report: OnyxReport): ThreeDotsMenuItem {
+function getShareMenuItem(report: OnyxReport, backTo?: string): ThreeDotsMenuItem {
return {
icon: Expensicons.QrCode,
text: Localize.translateLocal('common.share'),
- onSelected: () => Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS_SHARE_CODE.getRoute(report?.reportID ?? '')),
+ onSelected: () => Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS_SHARE_CODE.getRoute(report?.reportID ?? '', backTo)),
};
}
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
index 864d810a446..4108addac0f 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 d06be872c70..5befa446f6f 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 985c16d50c2..4684eb9637b 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 69bbe12c857..92698876949 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,
},
@@ -952,12 +955,12 @@ const config: LinkingOptions['config'] = {
},
[SCREENS.RIGHT_MODAL.NEW_TASK]: {
screens: {
- [SCREENS.NEW_TASK.ROOT]: ROUTES.NEW_TASK,
- [SCREENS.NEW_TASK.TASK_ASSIGNEE_SELECTOR]: ROUTES.NEW_TASK_ASSIGNEE,
+ [SCREENS.NEW_TASK.ROOT]: ROUTES.NEW_TASK.route,
+ [SCREENS.NEW_TASK.TASK_ASSIGNEE_SELECTOR]: ROUTES.NEW_TASK_ASSIGNEE.route,
[SCREENS.NEW_TASK.TASK_SHARE_DESTINATION_SELECTOR]: ROUTES.NEW_TASK_SHARE_DESTINATION,
- [SCREENS.NEW_TASK.DETAILS]: ROUTES.NEW_TASK_DETAILS,
- [SCREENS.NEW_TASK.TITLE]: ROUTES.NEW_TASK_TITLE,
- [SCREENS.NEW_TASK.DESCRIPTION]: ROUTES.NEW_TASK_DESCRIPTION,
+ [SCREENS.NEW_TASK.DETAILS]: ROUTES.NEW_TASK_DETAILS.route,
+ [SCREENS.NEW_TASK.TITLE]: ROUTES.NEW_TASK_TITLE.route,
+ [SCREENS.NEW_TASK.DESCRIPTION]: ROUTES.NEW_TASK_DESCRIPTION.route,
},
},
[SCREENS.RIGHT_MODAL.TEACHERS_UNITE]: {
@@ -1122,7 +1125,7 @@ const config: LinkingOptions['config'] = {
},
[SCREENS.RIGHT_MODAL.PROCESS_MONEY_REQUEST_HOLD]: {
screens: {
- [SCREENS.PROCESS_MONEY_REQUEST_HOLD_ROOT]: ROUTES.PROCESS_MONEY_REQUEST_HOLD,
+ [SCREENS.PROCESS_MONEY_REQUEST_HOLD_ROOT]: ROUTES.PROCESS_MONEY_REQUEST_HOLD.route,
},
},
[SCREENS.RIGHT_MODAL.TRAVEL]: {
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index e3a4def63e8..39cc50affaa 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;
@@ -825,53 +829,87 @@ type ProfileNavigatorParamList = {
};
type ReportDetailsNavigatorParamList = {
- [SCREENS.REPORT_DETAILS.ROOT]: undefined;
+ [SCREENS.REPORT_DETAILS.ROOT]: {
+ reportID: string;
+ backTo?: Routes;
+ };
[SCREENS.REPORT_DETAILS.SHARE_CODE]: {
reportID: string;
+ backTo?: Routes;
};
[SCREENS.REPORT_DETAILS.EXPORT]: {
reportID: string;
policyID: string;
connectionName: ConnectionName;
+ backTo?: Routes;
};
};
type ReportSettingsNavigatorParamList = {
- [SCREENS.REPORT_SETTINGS.ROOT]: {reportID: string};
- [SCREENS.REPORT_SETTINGS.NAME]: {reportID: string};
- [SCREENS.REPORT_SETTINGS.NOTIFICATION_PREFERENCES]: {reportID: string};
- [SCREENS.REPORT_SETTINGS.WRITE_CAPABILITY]: {reportID: string};
+ [SCREENS.REPORT_SETTINGS.ROOT]: {
+ reportID: string;
+ backTo?: Routes;
+ };
+ [SCREENS.REPORT_SETTINGS.NAME]: {
+ reportID: string;
+ backTo?: Routes;
+ };
+ [SCREENS.REPORT_SETTINGS.NOTIFICATION_PREFERENCES]: {
+ reportID: string;
+ backTo?: Routes;
+ };
+ [SCREENS.REPORT_SETTINGS.WRITE_CAPABILITY]: {
+ reportID: string;
+ backTo?: Routes;
+ };
[SCREENS.REPORT_SETTINGS.VISIBILITY]: {
reportID: string;
+ backTo?: Routes;
};
};
type ReportDescriptionNavigatorParamList = {
- [SCREENS.REPORT_DESCRIPTION_ROOT]: {reportID: string};
+ [SCREENS.REPORT_DESCRIPTION_ROOT]: {
+ reportID: string;
+ backTo?: Routes;
+ };
};
type ParticipantsNavigatorParamList = {
- [SCREENS.REPORT_PARTICIPANTS.ROOT]: {reportID: string};
- [SCREENS.REPORT_PARTICIPANTS.INVITE]: {reportID: string};
+ [SCREENS.REPORT_PARTICIPANTS.ROOT]: {
+ reportID: string;
+ backTo?: Routes;
+ };
+ [SCREENS.REPORT_PARTICIPANTS.INVITE]: {
+ reportID: string;
+ backTo?: Routes;
+ };
[SCREENS.REPORT_PARTICIPANTS.DETAILS]: {
reportID: string;
accountID: string;
+ backTo?: Routes;
};
[SCREENS.REPORT_PARTICIPANTS.ROLE]: {
reportID: string;
accountID: string;
+ backTo?: Routes;
};
};
type RoomMembersNavigatorParamList = {
- [SCREENS.ROOM_MEMBERS.ROOT]: {reportID: string};
+ [SCREENS.ROOM_MEMBERS.ROOT]: {
+ reportID: string;
+ backTo?: Routes;
+ };
[SCREENS.ROOM_MEMBERS.INVITE]: {
reportID: string;
role?: 'accountant';
+ backTo?: Routes;
};
[SCREENS.ROOM_MEMBERS.DETAILS]: {
reportID: string;
accountID: string;
+ backTo?: Routes;
};
};
@@ -985,6 +1023,7 @@ type MoneyRequestNavigatorParamList = {
backTo: never;
action: never;
currency: never;
+ pageIndex?: string;
};
[SCREENS.MONEY_REQUEST.START]: {
iouType: IOUType;
@@ -998,6 +1037,7 @@ type MoneyRequestNavigatorParamList = {
transactionID: string;
backTo: Routes;
action: IOUAction;
+ pageIndex?: string;
currency?: string;
};
[SCREENS.MONEY_REQUEST.STEP_DISTANCE_RATE]: {
@@ -1036,12 +1076,22 @@ type MoneyRequestNavigatorParamList = {
};
type NewTaskNavigatorParamList = {
- [SCREENS.NEW_TASK.ROOT]: undefined;
- [SCREENS.NEW_TASK.TASK_ASSIGNEE_SELECTOR]: undefined;
+ [SCREENS.NEW_TASK.ROOT]: {
+ backTo?: Routes;
+ };
+ [SCREENS.NEW_TASK.TASK_ASSIGNEE_SELECTOR]: {
+ backTo?: Routes;
+ };
[SCREENS.NEW_TASK.TASK_SHARE_DESTINATION_SELECTOR]: undefined;
- [SCREENS.NEW_TASK.DETAILS]: undefined;
- [SCREENS.NEW_TASK.TITLE]: undefined;
- [SCREENS.NEW_TASK.DESCRIPTION]: undefined;
+ [SCREENS.NEW_TASK.DETAILS]: {
+ backTo?: Routes;
+ };
+ [SCREENS.NEW_TASK.TITLE]: {
+ backTo?: Routes;
+ };
+ [SCREENS.NEW_TASK.DESCRIPTION]: {
+ backTo?: Routes;
+ };
};
type TeachersUniteNavigatorParamList = {
@@ -1052,9 +1102,12 @@ type TeachersUniteNavigatorParamList = {
};
type TaskDetailsNavigatorParamList = {
- [SCREENS.TASK.TITLE]: undefined;
+ [SCREENS.TASK.TITLE]: {
+ backTo?: Routes;
+ };
[SCREENS.TASK.ASSIGNEE]: {
reportID: string;
+ backTo?: Routes;
};
};
@@ -1066,6 +1119,7 @@ type SplitDetailsNavigatorParamList = {
[SCREENS.SPLIT_DETAILS.ROOT]: {
reportID: string;
reportActionID: string;
+ backTo?: Routes;
};
[SCREENS.SPLIT_DETAILS.EDIT_REQUEST]: {
field: string;
@@ -1099,11 +1153,17 @@ type FlagCommentNavigatorParamList = {
[SCREENS.FLAG_COMMENT_ROOT]: {
reportID: string;
reportActionID: string;
+ backTo?: Routes;
};
};
type EditRequestNavigatorParamList = {
- [SCREENS.EDIT_REQUEST.REPORT_FIELD]: undefined;
+ [SCREENS.EDIT_REQUEST.REPORT_FIELD]: {
+ fieldID: string;
+ reportID: string;
+ policyID: string;
+ backTo?: Routes;
+ };
};
type SignInNavigatorParamList = {
@@ -1126,37 +1186,48 @@ type ProcessMoneyRequestHoldNavigatorParamList = {
};
type PrivateNotesNavigatorParamList = {
- [SCREENS.PRIVATE_NOTES.LIST]: undefined;
+ [SCREENS.PRIVATE_NOTES.LIST]: {
+ backTo?: Routes;
+ };
[SCREENS.PRIVATE_NOTES.EDIT]: {
reportID: string;
accountID: string;
+ backTo?: Routes;
};
};
type TransactionDuplicateNavigatorParamList = {
[SCREENS.TRANSACTION_DUPLICATE.REVIEW]: {
threadReportID: string;
+ backTo?: Routes;
};
[SCREENS.TRANSACTION_DUPLICATE.MERCHANT]: {
threadReportID: string;
+ backTo?: Routes;
};
[SCREENS.TRANSACTION_DUPLICATE.CATEGORY]: {
threadReportID: string;
+ backTo?: Routes;
};
[SCREENS.TRANSACTION_DUPLICATE.TAG]: {
threadReportID: string;
+ backTo?: Routes;
};
[SCREENS.TRANSACTION_DUPLICATE.DESCRIPTION]: {
threadReportID: string;
+ backTo?: Routes;
};
[SCREENS.TRANSACTION_DUPLICATE.TAX_CODE]: {
threadReportID: string;
+ backTo?: Routes;
};
[SCREENS.TRANSACTION_DUPLICATE.BILLABLE]: {
threadReportID: string;
+ backTo?: Routes;
};
[SCREENS.TRANSACTION_DUPLICATE.REIMBURSABLE]: {
threadReportID: string;
+ backTo?: Routes;
};
};
diff --git a/src/libs/Network/NetworkStore.ts b/src/libs/Network/NetworkStore.ts
index 89b40e63834..fe90aa87495 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,
+ WRITE_COMMANDS.SEARCH,
+ SIDE_EFFECT_REQUEST_COMMANDS.RECONNECT_APP,
+ SIDE_EFFECT_REQUEST_COMMANDS.OPEN_REPORT,
+ 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 afedd308371..50242435ed3 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 d38d3f8b950..f4391a1171a 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -3755,9 +3755,9 @@ function getReportName(
return `${reportActionMessage} (${Localize.translateLocal('common.archived')})`;
}
if (!isEmptyObject(parentReportAction) && ReportActionsUtils.isModifiedExpenseAction(parentReportAction)) {
- return ModifiedExpenseMessage.getForReportAction(report?.reportID, parentReportAction);
+ const modifiedMessage = ModifiedExpenseMessage.getForReportAction(report?.reportID, parentReportAction);
+ return formatReportLastMessageText(modifiedMessage);
}
-
if (isTripRoom(report)) {
return report?.reportName ?? '';
}
@@ -3920,34 +3920,34 @@ function getParentNavigationSubtitle(report: OnyxEntry, invoiceReceiverP
/**
* Navigate to the details page of a given report
*/
-function navigateToDetailsPage(report: OnyxEntry) {
+function navigateToDetailsPage(report: OnyxEntry, backTo?: string) {
const isSelfDMReport = isSelfDM(report);
const isOneOnOneChatReport = isOneOnOneChat(report);
const participantAccountID = getParticipantsAccountIDsForDisplay(report);
if (isSelfDMReport || isOneOnOneChatReport) {
- Navigation.navigate(ROUTES.PROFILE.getRoute(participantAccountID[0]));
+ Navigation.navigate(ROUTES.PROFILE.getRoute(participantAccountID[0], backTo));
return;
}
if (report?.reportID) {
- Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report?.reportID));
+ Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report?.reportID, backTo));
}
}
/**
* Go back to the details page of a given report
*/
-function goBackToDetailsPage(report: OnyxEntry) {
+function goBackToDetailsPage(report: OnyxEntry, backTo?: string) {
const isOneOnOneChatReport = isOneOnOneChat(report);
const participantAccountID = getParticipantsAccountIDsForDisplay(report);
if (isOneOnOneChatReport) {
- Navigation.navigate(ROUTES.PROFILE.getRoute(participantAccountID[0]));
+ Navigation.goBack(ROUTES.PROFILE.getRoute(participantAccountID[0], backTo));
return;
}
- Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(report?.reportID ?? '-1'));
+ Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(report?.reportID ?? '-1', backTo));
}
function navigateBackAfterDeleteTransaction(backRoute: Route | undefined, isFromRHP?: boolean) {
@@ -3970,7 +3970,7 @@ function navigateBackAfterDeleteTransaction(backRoute: Route | undefined, isFrom
/**
* Go back to the previous page from the edit private page of a given report
*/
-function goBackFromPrivateNotes(report: OnyxEntry, session: OnyxEntry) {
+function goBackFromPrivateNotes(report: OnyxEntry, session: OnyxEntry, backTo?: string) {
if (isEmpty(report) || isEmpty(session) || !session.accountID) {
return;
}
@@ -3979,16 +3979,16 @@ function goBackFromPrivateNotes(report: OnyxEntry, session: OnyxEntry${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 {
@@ -7138,16 +7138,16 @@ function shouldAutoFocusOnKeyPress(event: KeyboardEvent): boolean {
/**
* Navigates to the appropriate screen based on the presence of a private note for the current user.
*/
-function navigateToPrivateNotes(report: OnyxEntry, session: OnyxEntry) {
+function navigateToPrivateNotes(report: OnyxEntry, session: OnyxEntry, backTo?: string) {
if (isEmpty(report) || isEmpty(session) || !session.accountID) {
return;
}
const currentUserPrivateNote = report.privateNotes?.[session.accountID]?.note ?? '';
if (isEmpty(currentUserPrivateNote)) {
- Navigation.navigate(ROUTES.PRIVATE_NOTES_EDIT.getRoute(report.reportID, session.accountID));
+ Navigation.navigate(ROUTES.PRIVATE_NOTES_EDIT.getRoute(report.reportID, session.accountID, backTo));
return;
}
- Navigation.navigate(ROUTES.PRIVATE_NOTES_LIST.getRoute(report.reportID));
+ Navigation.navigate(ROUTES.PRIVATE_NOTES_LIST.getRoute(report.reportID, backTo));
}
/**
diff --git a/src/libs/SearchUtils.ts b/src/libs/SearchUtils.ts
index 9760ff80ca1..e178c7dcb77 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/TripReservationUtils.ts b/src/libs/TripReservationUtils.ts
index 13bc3293cd9..b5777479931 100644
--- a/src/libs/TripReservationUtils.ts
+++ b/src/libs/TripReservationUtils.ts
@@ -1,8 +1,43 @@
+import {Str} from 'expensify-common';
+import type {Dispatch, SetStateAction} from 'react';
+import type {OnyxEntry} from 'react-native-onyx';
+import Onyx from 'react-native-onyx';
+import type {LocaleContextProps} from '@components/LocaleContextProvider';
import * as Expensicons from '@src/components/Icon/Expensicons';
import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
+import type {TravelSettings} from '@src/types/onyx';
import type {Reservation, ReservationType} from '@src/types/onyx/Transaction';
import type Transaction from '@src/types/onyx/Transaction';
+import {isEmptyObject} from '@src/types/utils/EmptyObject';
import type IconAsset from '@src/types/utils/IconAsset';
+import * as Link from './actions/Link';
+import Navigation from './Navigation/Navigation';
+
+let travelSettings: OnyxEntry;
+Onyx.connect({
+ key: ONYXKEYS.NVP_TRAVEL_SETTINGS,
+ callback: (val) => {
+ travelSettings = val;
+ },
+});
+
+let activePolicyID: OnyxEntry;
+Onyx.connect({
+ key: ONYXKEYS.NVP_ACTIVE_POLICY_ID,
+ callback: (val) => {
+ activePolicyID = val;
+ },
+});
+
+let primaryLogin: string;
+Onyx.connect({
+ key: ONYXKEYS.ACCOUNT,
+ callback: (val) => {
+ primaryLogin = val?.primaryLogin ?? '';
+ },
+});
function getTripReservationIcon(reservationType: ReservationType): IconAsset {
switch (reservationType) {
@@ -38,4 +73,24 @@ function getTripEReceiptIcon(transaction?: Transaction): IconAsset | undefined {
}
}
-export {getTripReservationIcon, getReservationsFromTripTransactions, getTripEReceiptIcon};
+function bookATrip(translate: LocaleContextProps['translate'], setCtaErrorMessage: Dispatch>, ctaErrorMessage = ''): void {
+ if (Str.isSMSLogin(primaryLogin)) {
+ setCtaErrorMessage(translate('travel.phoneError'));
+ return;
+ }
+ if (isEmptyObject(travelSettings)) {
+ Navigation.navigate(ROUTES.WORKSPACE_PROFILE_ADDRESS.getRoute(activePolicyID ?? '-1', Navigation.getActiveRoute()));
+ return;
+ }
+ if (!travelSettings?.hasAcceptedTerms) {
+ Navigation.navigate(ROUTES.TRAVEL_TCS);
+ return;
+ }
+ if (ctaErrorMessage) {
+ setCtaErrorMessage('');
+ }
+ Link.openTravelDotLink(activePolicyID)?.catch(() => {
+ setCtaErrorMessage(translate('travel.errorMessage'));
+ });
+}
+export {getTripReservationIcon, getReservationsFromTripTransactions, getTripEReceiptIcon, bookATrip};
diff --git a/src/libs/actions/App.ts b/src/libs/actions/App.ts
index a0f60752913..6b6f1a5f6dc 100644
--- a/src/libs/actions/App.ts
+++ b/src/libs/actions/App.ts
@@ -243,10 +243,9 @@ function getOnyxDataForOpenOrReconnect(isOpenApp = false): OnyxData {
* Fetches data needed for app initialization
*/
function openApp() {
- getPolicyParamsForOpenOrReconnect().then((policyParams: PolicyParamsForOpenOrReconnect) => {
+ return getPolicyParamsForOpenOrReconnect().then((policyParams: PolicyParamsForOpenOrReconnect) => {
const params: OpenAppParams = {enablePriorityModeFilter: true, ...policyParams};
-
- API.write(WRITE_COMMANDS.OPEN_APP, params, getOnyxDataForOpenOrReconnect(true));
+ return API.write(WRITE_COMMANDS.OPEN_APP, params, getOnyxDataForOpenOrReconnect(true));
});
}
diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts
index daaa766145e..4221c9db289 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 eae625388f3..44ce9ea6f91 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 d6f67e496b9..f2cd818fd6c 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/Report.ts b/src/libs/actions/Report.ts
index b038f16d003..e53cac804b9 100644
--- a/src/libs/actions/Report.ts
+++ b/src/libs/actions/Report.ts
@@ -187,12 +187,12 @@ const allReportActions: OnyxCollection = {};
Onyx.connect({
key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
- callback: (action, key) => {
- if (!key || !action) {
+ callback: (actions, key) => {
+ if (!key || !actions) {
return;
}
const reportID = CollectionUtils.extractCollectionItemID(key);
- allReportActions[reportID] = action;
+ allReportActions[reportID] = actions;
},
});
@@ -1357,6 +1357,15 @@ function handleReportChanged(report: OnyxEntry) {
return;
}
+ // Handle cleanup of stale optimistic IOU report and its report preview separately
+ if (report?.reportID && report.preexistingReportID && ReportUtils.isMoneyRequestReport(report) && report?.parentReportActionID) {
+ Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`, {
+ [report.parentReportActionID]: null,
+ });
+ Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, null);
+ return;
+ }
+
// It is possible that we optimistically created a DM/group-DM for a set of users for which a report already exists.
// In this case, the API will let us know by returning a preexistingReportID.
// We should clear out the optimistically created report and re-route the user to the preexisting report.
@@ -1705,15 +1714,11 @@ function updateNotificationPreference(
reportID: string,
previousValue: NotificationPreference | undefined,
newValue: NotificationPreference,
- navigate: boolean,
parentReportID?: string,
parentReportActionID?: string,
- report?: OnyxEntry,
) {
+ // No change needed
if (previousValue === newValue) {
- if (navigate && !isEmptyObject(report) && report.reportID) {
- ReportUtils.goBackToDetailsPage(report);
- }
return;
}
@@ -1761,9 +1766,6 @@ function updateNotificationPreference(
const parameters: UpdateReportNotificationPreferenceParams = {reportID, notificationPreference: newValue};
API.write(WRITE_COMMANDS.UPDATE_REPORT_NOTIFICATION_PREFERENCE, parameters, {optimisticData, failureData});
- if (navigate && !isEmptyObject(report)) {
- ReportUtils.goBackToDetailsPage(report);
- }
}
function updateRoomVisibility(reportID: string, previousValue: RoomVisibility | undefined, newValue: RoomVisibility) {
@@ -1805,9 +1807,9 @@ function toggleSubscribeToChildReport(childReportID = '-1', parentReportAction:
openReport(childReportID);
const parentReportActionID = parentReportAction?.reportActionID ?? '-1';
if (!prevNotificationPreference || prevNotificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN) {
- updateNotificationPreference(childReportID, prevNotificationPreference, CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, false, parentReportID, parentReportActionID);
+ updateNotificationPreference(childReportID, prevNotificationPreference, CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, parentReportID, parentReportActionID);
} else {
- updateNotificationPreference(childReportID, prevNotificationPreference, CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, false, parentReportID, parentReportActionID);
+ updateNotificationPreference(childReportID, prevNotificationPreference, CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, parentReportID, parentReportActionID);
}
} else {
const participantAccountIDs = [...new Set([currentUserAccountID, Number(parentReportAction?.actorAccountID)])];
@@ -1831,7 +1833,7 @@ function toggleSubscribeToChildReport(childReportID = '-1', parentReportAction:
openReport(newChat.reportID, '', participantLogins, newChat, parentReportAction.reportActionID);
const notificationPreference =
prevNotificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN ? CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS : CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN;
- updateNotificationPreference(newChat.reportID, prevNotificationPreference, notificationPreference, false, parentReportID, parentReportAction?.reportActionID);
+ updateNotificationPreference(newChat.reportID, prevNotificationPreference, notificationPreference, parentReportID, parentReportAction?.reportActionID);
}
}
@@ -2052,9 +2054,8 @@ function deleteReportField(reportID: string, reportField: PolicyReportField) {
}
function updateDescription(reportID: string, previousValue: string, newValue: string) {
- // No change needed, navigate back
+ // No change needed
if (previousValue === newValue) {
- Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(reportID));
return;
}
@@ -2120,12 +2121,11 @@ function updateDescription(reportID: string, previousValue: string, newValue: st
const parameters: UpdateRoomDescriptionParams = {reportID, description: parsedDescription, reportActionID: optimisticDescriptionUpdatedReportAction.reportActionID};
API.write(WRITE_COMMANDS.UPDATE_ROOM_DESCRIPTION, parameters, {optimisticData, failureData, successData});
- Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(reportID));
}
-function updateWriteCapabilityAndNavigate(report: Report, newValue: WriteCapability) {
+function updateWriteCapability(report: Report, newValue: WriteCapability) {
+ // No change needed
if (report.writeCapability === newValue) {
- Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(report.reportID));
return;
}
@@ -2147,8 +2147,6 @@ function updateWriteCapabilityAndNavigate(report: Report, newValue: WriteCapabil
const parameters: UpdateReportWriteCapabilityParams = {reportID: report.reportID, writeCapability: newValue};
API.write(WRITE_COMMANDS.UPDATE_REPORT_WRITE_CAPABILITY, parameters, {optimisticData, failureData});
- // Return to the report settings page since this field utilizes push-to-page
- Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(report.reportID));
}
/**
@@ -2314,13 +2312,12 @@ function navigateToConciergeChatAndDeleteReport(reportID: string, shouldPopToTop
* @param policyRoomReport The policy room report
* @param policyRoomName The updated name for the policy room
*/
-function updatePolicyRoomNameAndNavigate(policyRoomReport: Report, policyRoomName: string) {
+function updatePolicyRoomName(policyRoomReport: Report, policyRoomName: string) {
const reportID = policyRoomReport.reportID;
const previousName = policyRoomReport.reportName;
- // No change needed, navigate back
+ // No change needed
if (previousName === policyRoomName) {
- Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(reportID));
return;
}
@@ -2386,7 +2383,6 @@ function updatePolicyRoomNameAndNavigate(policyRoomReport: Report, policyRoomNam
};
API.write(WRITE_COMMANDS.UPDATE_POLICY_ROOM_NAME, parameters, {optimisticData, successData, failureData});
- Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(reportID));
}
/**
@@ -2758,10 +2754,8 @@ function joinRoom(report: OnyxEntry) {
report.reportID,
ReportUtils.getReportNotificationPreference(report),
CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS,
- false,
report.parentReportID,
report.parentReportActionID,
- report,
);
}
@@ -4100,7 +4094,7 @@ export {
addComment,
addAttachment,
updateDescription,
- updateWriteCapabilityAndNavigate,
+ updateWriteCapability,
updateNotificationPreference,
subscribeToReportTypingEvents,
subscribeToReportLeavingEvents,
@@ -4129,7 +4123,7 @@ export {
navigateToAndOpenReportWithAccountIDs,
navigateToAndOpenChildReport,
toggleSubscribeToChildReport,
- updatePolicyRoomNameAndNavigate,
+ updatePolicyRoomName,
clearPolicyRoomNameErrors,
clearIOUError,
subscribeToNewActionEvent,
diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts
index a4f0e59ef97..722e8880803 100644
--- a/src/libs/actions/Search.ts
+++ b/src/libs/actions/Search.ts
@@ -5,7 +5,7 @@ import type {FormOnyxValues} from '@components/Form/types';
import type {SearchQueryJSON} from '@components/Search/types';
import * as API from '@libs/API';
import type {ExportSearchItemsToCSVParams} from '@libs/API/parameters';
-import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
+import {WRITE_COMMANDS} from '@libs/API/types';
import * as ApiUtils from '@libs/ApiUtils';
import fileDownload from '@libs/fileDownload';
import enhanceParameters from '@libs/Network/enhanceParameters';
@@ -51,11 +51,11 @@ function getOnyxLoadingData(hash: number): {optimisticData: OnyxUpdate[]; finall
return {optimisticData, finallyData};
}
-function saveSearch({queryJSON, name}: {queryJSON: SearchQueryJSON; name?: string}) {
- const saveSearchName = name ?? queryJSON?.inputQuery ?? '';
+function saveSearch({queryJSON, newName}: {queryJSON: SearchQueryJSON; newName?: string}) {
+ const saveSearchName = newName ?? queryJSON?.inputQuery ?? '';
const jsonQuery = JSON.stringify(queryJSON);
- API.write(WRITE_COMMANDS.SAVE_SEARCH, {jsonQuery, name: saveSearchName});
+ API.write(WRITE_COMMANDS.SAVE_SEARCH, {jsonQuery, newName: saveSearchName});
}
function deleteSavedSearch(hash: number) {
@@ -71,7 +71,7 @@ function search({queryJSON, offset}: {queryJSON: SearchQueryJSON; offset?: numbe
};
const jsonQuery = JSON.stringify(queryWithOffset);
- API.read(READ_COMMANDS.SEARCH, {hash: queryJSON.hash, jsonQuery}, {optimisticData, finallyData});
+ API.write(WRITE_COMMANDS.SEARCH, {hash: queryJSON.hash, jsonQuery}, {optimisticData, finallyData});
}
/**
@@ -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/actions/Task.ts b/src/libs/actions/Task.ts
index 41ccfee1786..08568b6d5a0 100644
--- a/src/libs/actions/Task.ts
+++ b/src/libs/actions/Task.ts
@@ -18,6 +18,7 @@ import playSound, {SOUNDS} from '@libs/Sound';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
+import type {Route} from '@src/ROUTES';
import type * as OnyxTypes from '@src/types/onyx';
import type {Icon} from '@src/types/onyx/OnyxCommon';
import type {ReportActions} from '@src/types/onyx/ReportAction';
@@ -846,7 +847,7 @@ function clearOutTaskInfoAndNavigate(reportID?: string, chatReport?: OnyxEntry) {
/**
* Closes the current open task modal and clears out the task info from the store.
*/
-function dismissModalAndClearOutTaskInfo() {
- Navigation.closeRHPFlow();
+function dismissModalAndClearOutTaskInfo(backTo?: Route) {
+ if (backTo) {
+ Navigation.goBack(backTo);
+ } else {
+ Navigation.closeRHPFlow();
+ }
clearOutTaskInfo();
}
diff --git a/src/libs/shouldFetchReport.ts b/src/libs/shouldFetchReport.ts
index 5259e88ca6d..3d364d68dec 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 88e52409751..fc9ae171ccf 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/EditReportFieldPage.tsx b/src/pages/EditReportFieldPage.tsx
index 5690d320213..ec4ce6568f7 100644
--- a/src/pages/EditReportFieldPage.tsx
+++ b/src/pages/EditReportFieldPage.tsx
@@ -1,3 +1,4 @@
+import type {StackScreenProps} from '@react-navigation/stack';
import {Str} from 'expensify-common';
import React, {useState} from 'react';
import {withOnyx} from 'react-native-onyx';
@@ -14,9 +15,12 @@ import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import isSearchTopmostCentralPane from '@libs/Navigation/isSearchTopmostCentralPane';
import Navigation from '@libs/Navigation/Navigation';
+import type {EditRequestNavigatorParamList} from '@libs/Navigation/types';
import * as ReportUtils from '@libs/ReportUtils';
import * as ReportActions from '@src/libs/actions/Report';
import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
+import type SCREENS from '@src/SCREENS';
import type {Policy, Report} from '@src/types/onyx';
import EditReportFieldDate from './EditReportFieldDate';
import EditReportFieldDropdown from './EditReportFieldDropdown';
@@ -30,26 +34,12 @@ type EditReportFieldPageOnyxProps = {
policy: OnyxEntry;
};
-type EditReportFieldPageProps = EditReportFieldPageOnyxProps & {
- /** Route from navigation */
- route: {
- /** Params from the route */
- params: {
- /** Which field we are editing */
- fieldID: string;
-
- /** reportID for the expense report */
- reportID: string;
-
- /** policyID for the expense report */
- policyID: string;
- };
- };
-};
+type EditReportFieldPageProps = EditReportFieldPageOnyxProps & StackScreenProps;
function EditReportFieldPage({route, policy, report}: EditReportFieldPageProps) {
const {windowWidth} = useWindowDimensions();
const styles = useThemeStyles();
+ const backTo = route.params.backTo;
const fieldKey = ReportUtils.getReportFieldKey(route.params.fieldID);
const reportField = report?.fieldList?.[fieldKey] ?? policy?.fieldList?.[fieldKey];
const policyField = policy?.fieldList?.[fieldKey] ?? reportField;
@@ -71,11 +61,19 @@ function EditReportFieldPage({route, policy, report}: EditReportFieldPageProps)
);
}
+ const goBack = () => {
+ if (isReportFieldTitle) {
+ Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report.reportID, backTo));
+ return;
+ }
+ Navigation.goBack(backTo);
+ };
+
const handleReportFieldChange = (form: FormOnyxValues) => {
const value = form[fieldKey];
if (isReportFieldTitle) {
ReportActions.updateReportName(report.reportID, value, report.reportName ?? '');
- Navigation.goBack();
+ goBack();
} else {
ReportActions.updateReportField(report.reportID, {...reportField, value: value === '' ? null : value}, reportField);
Navigation.dismissModal(isSearchTopmostCentralPane() ? undefined : report?.reportID);
@@ -111,6 +109,7 @@ function EditReportFieldPage({route, policy, report}: EditReportFieldPageProps)
threeDotsMenuItems={menuItems}
shouldShowThreeDotsButton={!!menuItems?.length}
threeDotsAnchorPosition={styles.threeDotsPopoverOffsetNoCloseButton(windowWidth)}
+ onBackButtonPress={goBack}
/>
{({safeAreaPaddingBottomStyle}) => (
-
+ Navigation.goBack(route.params.backTo)}
+ />
>>;
function InviteReportParticipantsPage({betas, personalDetails, report, didScreenTransitionEnd}: InviteReportParticipantsPageProps) {
+ const route = useRoute>();
const {options, areOptionsInitialized} = useOptionsList({
shouldInitialize: didScreenTransitionEnd,
});
@@ -163,7 +168,7 @@ function InviteReportParticipantsPage({betas, personalDetails, report, didScreen
const validate = useCallback(() => selectedOptions.length > 0, [selectedOptions]);
const reportID = report.reportID;
- const backRoute = useMemo(() => ROUTES.REPORT_PARTICIPANTS.getRoute(reportID), [reportID]);
+ const backRoute = useMemo(() => ROUTES.REPORT_PARTICIPANTS.getRoute(reportID, route.params.backTo), [reportID, route.params.backTo]);
const reportName = useMemo(() => ReportUtils.getGroupChatName(undefined, true, report), [report]);
const inviteUsers = useCallback(() => {
if (!validate()) {
diff --git a/src/pages/PrivateNotes/PrivateNotesEditPage.tsx b/src/pages/PrivateNotes/PrivateNotesEditPage.tsx
index 1779fe8e085..ac8eb7f862b 100644
--- a/src/pages/PrivateNotes/PrivateNotesEditPage.tsx
+++ b/src/pages/PrivateNotes/PrivateNotesEditPage.tsx
@@ -47,6 +47,7 @@ type PrivateNotesEditPageProps = WithReportAndPrivateNotesOrNotFoundProps &
};
function PrivateNotesEditPage({route, personalDetailsList, report, session}: PrivateNotesEditPageProps) {
+ const backTo = route.params.backTo;
const styles = useThemeStyles();
const {translate} = useLocalize();
@@ -102,9 +103,9 @@ function PrivateNotesEditPage({route, personalDetailsList, report, session}: Pri
Keyboard.dismiss();
if (!Object.values({...report.privateNotes, [route.params.accountID]: {note: editedNote}}).some((item) => item.note)) {
- ReportUtils.navigateToDetailsPage(report);
+ ReportUtils.navigateToDetailsPage(report, backTo);
} else {
- Navigation.goBack(ROUTES.PRIVATE_NOTES_LIST.getRoute(report.reportID));
+ Navigation.goBack(ROUTES.PRIVATE_NOTES_LIST.getRoute(report.reportID, backTo));
}
};
@@ -116,7 +117,7 @@ function PrivateNotesEditPage({route, personalDetailsList, report, session}: Pri
>
ReportUtils.goBackFromPrivateNotes(report, session)}
+ onBackButtonPress={() => ReportUtils.goBackFromPrivateNotes(report, session, backTo)}
shouldShowBackButton
onCloseButtonPress={() => Navigation.dismissModal()}
/>
diff --git a/src/pages/PrivateNotes/PrivateNotesListPage.tsx b/src/pages/PrivateNotes/PrivateNotesListPage.tsx
index 055bba42c55..cc7ee9f54da 100644
--- a/src/pages/PrivateNotes/PrivateNotesListPage.tsx
+++ b/src/pages/PrivateNotes/PrivateNotesListPage.tsx
@@ -1,3 +1,5 @@
+import type {RouteProp} from '@react-navigation/native';
+import {useRoute} from '@react-navigation/native';
import React, {useCallback, useMemo} from 'react';
import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
@@ -11,11 +13,13 @@ import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
+import type {PrivateNotesNavigatorParamList} from '@libs/Navigation/types';
import type {WithReportAndPrivateNotesOrNotFoundProps} from '@pages/home/report/withReportAndPrivateNotesOrNotFound';
import withReportAndPrivateNotesOrNotFound from '@pages/home/report/withReportAndPrivateNotesOrNotFound';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
+import type SCREENS from '@src/SCREENS';
import type {PersonalDetailsList, Report} from '@src/types/onyx';
type PrivateNotesListPageOnyxProps = {
@@ -40,6 +44,8 @@ type NoteListItem = {
};
function PrivateNotesListPage({report, personalDetailsList, session}: PrivateNotesListPageProps) {
+ const route = useRoute>();
+ const backTo = route.params.backTo;
const styles = useThemeStyles();
const {translate} = useLocalize();
const getAttachmentValue = useCallback((item: NoteListItem) => ({reportID: item.reportID, accountID: Number(item.accountID), type: CONST.ATTACHMENT_TYPE.NOTE}), []);
@@ -77,13 +83,13 @@ function PrivateNotesListPage({report, personalDetailsList, session}: PrivateNot
reportID: report.reportID,
accountID,
title: Number(session?.accountID) === Number(accountID) ? translate('privateNotes.myNote') : personalDetailsList?.[accountID]?.login ?? '',
- action: () => Navigation.navigate(ROUTES.PRIVATE_NOTES_EDIT.getRoute(report.reportID, accountID)),
+ action: () => Navigation.navigate(ROUTES.PRIVATE_NOTES_EDIT.getRoute(report.reportID, accountID, backTo)),
brickRoadIndicator: privateNoteBrickRoadIndicator(Number(accountID)),
note: privateNote?.note ?? '',
disabled: Number(session?.accountID) !== Number(accountID),
};
});
- }, [report, personalDetailsList, session, translate]);
+ }, [report, personalDetailsList, session, translate, backTo]);
return (
Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report.reportID, backTo))}
onCloseButtonPress={() => Navigation.dismissModal()}
/>
{translate('privateNotes.personalNoteMessage')}
diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx
index 2b732bb5506..0d47e3fd8f3 100755
--- a/src/pages/ProfilePage.tsx
+++ b/src/pages/ProfilePage.tsx
@@ -277,7 +277,7 @@ function ProfilePage({route}: ProfilePageProps) {
shouldShowRightIcon
title={notificationPreference}
description={translate('notificationPreferencesPage.label')}
- onPress={() => Navigation.navigate(ROUTES.REPORT_SETTINGS_NOTIFICATION_PREFERENCES.getRoute(report.reportID))}
+ onPress={() => Navigation.navigate(ROUTES.REPORT_SETTINGS_NOTIFICATION_PREFERENCES.getRoute(report.reportID, navigateBackTo))}
/>
)}
{!isEmptyObject(report) && report.reportID && !isCurrentUser && (
@@ -285,7 +285,7 @@ function ProfilePage({route}: ProfilePageProps) {
title={`${translate('privateNotes.title')}`}
titleStyle={styles.flex1}
icon={Expensicons.Pencil}
- onPress={() => ReportUtils.navigateToPrivateNotes(report, session)}
+ onPress={() => ReportUtils.navigateToPrivateNotes(report, session, navigateBackTo)}
wrapperStyle={styles.breakAll}
shouldShowRightIcon
brickRoadIndicator={ReportActions.hasErrorInPrivateNotes(report) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx
index d0049dfe586..66c61b134ae 100644
--- a/src/pages/ReportDetailsPage.tsx
+++ b/src/pages/ReportDetailsPage.tsx
@@ -74,10 +74,11 @@ const CASES = {
type CaseID = ValueOf;
-function ReportDetailsPage({policies, report}: ReportDetailsPageProps) {
+function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) {
const {translate} = useLocalize();
const {isOffline} = useNetwork();
const styles = useThemeStyles();
+ const backTo = route.params.backTo;
// 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 @typescript-eslint/prefer-nullish-coalescing */
@@ -315,9 +316,9 @@ function ReportDetailsPage({policies, report}: ReportDetailsPageProps) {
shouldShowRightIcon: true,
action: () => {
if (shouldOpenRoomMembersPage) {
- Navigation.navigate(ROUTES.ROOM_MEMBERS.getRoute(report?.reportID ?? '-1'));
+ Navigation.navigate(ROUTES.ROOM_MEMBERS.getRoute(report?.reportID ?? '-1', backTo));
} else {
- Navigation.navigate(ROUTES.REPORT_PARTICIPANTS.getRoute(report?.reportID ?? '-1'));
+ Navigation.navigate(ROUTES.REPORT_PARTICIPANTS.getRoute(report?.reportID ?? '-1', backTo));
}
},
});
@@ -342,7 +343,7 @@ function ReportDetailsPage({policies, report}: ReportDetailsPageProps) {
isAnonymousAction: false,
shouldShowRightIcon: true,
action: () => {
- Navigation.navigate(ROUTES.REPORT_SETTINGS.getRoute(report?.reportID ?? '-1'));
+ Navigation.navigate(ROUTES.REPORT_SETTINGS.getRoute(report?.reportID ?? '-1', backTo));
},
});
}
@@ -355,7 +356,7 @@ function ReportDetailsPage({policies, report}: ReportDetailsPageProps) {
icon: Expensicons.Pencil,
isAnonymousAction: false,
shouldShowRightIcon: true,
- action: () => ReportUtils.navigateToPrivateNotes(report, session),
+ action: () => ReportUtils.navigateToPrivateNotes(report, session, backTo),
brickRoadIndicator: Report.hasErrorInPrivateNotes(report) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined,
});
}
@@ -410,7 +411,7 @@ function ReportDetailsPage({policies, report}: ReportDetailsPageProps) {
icon: Expensicons.Upload,
isAnonymousAction: false,
action: () => {
- Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS_EXPORT.getRoute(report?.reportID ?? '', connectedIntegration));
+ Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS_EXPORT.getRoute(report?.reportID ?? '', connectedIntegration, backTo));
},
});
}
@@ -470,6 +471,7 @@ function ReportDetailsPage({policies, report}: ReportDetailsPageProps) {
isDebugModeEnabled,
unapproveExpenseReportOrShowModal,
isExpenseReport,
+ backTo,
]);
const displayNamesWithTooltips = useMemo(() => {
@@ -577,10 +579,10 @@ function ReportDetailsPage({policies, report}: ReportDetailsPageProps) {
result.push(PromotedActions.pin(report));
}
- result.push(PromotedActions.share(report));
+ result.push(PromotedActions.share(report, backTo));
return result;
- }, [report, moneyRequestAction, canJoin, isExpenseReport, shouldShowHoldAction, canHoldUnholdReportAction.canHoldRequest, transactionThreadReportID, isDelegateAccessRestricted]);
+ }, [report, moneyRequestAction, canJoin, isExpenseReport, shouldShowHoldAction, canHoldUnholdReportAction.canHoldRequest, transactionThreadReportID, isDelegateAccessRestricted, backTo]);
const nameSectionExpenseIOU = (
@@ -642,7 +644,7 @@ function ReportDetailsPage({policies, report}: ReportDetailsPageProps) {
shouldCheckActionAllowedOnPress={false}
description={!shouldDisableRename ? roomDescription : ''}
furtherDetails={chatRoomSubtitle && !isGroupChat ? additionalRoomDetails : ''}
- onPress={() => Navigation.navigate(ROUTES.REPORT_SETTINGS_NAME.getRoute(report.reportID))}
+ onPress={() => Navigation.navigate(ROUTES.REPORT_SETTINGS_NAME.getRoute(report.reportID, backTo))}
/>
@@ -687,7 +689,7 @@ function ReportDetailsPage({policies, report}: ReportDetailsPageProps) {
titleStyle={styles.newKansasLarge}
shouldCheckActionAllowedOnPress={false}
description={Str.UCFirst(titleField.name)}
- onPress={() => Navigation.navigate(ROUTES.EDIT_REPORT_FIELD_REQUEST.getRoute(report.reportID, report.policyID ?? '-1', titleField.fieldID ?? '-1'))}
+ onPress={() => Navigation.navigate(ROUTES.EDIT_REPORT_FIELD_REQUEST.getRoute(report.reportID, report.policyID ?? '-1', titleField.fieldID ?? '-1', backTo))}
furtherDetailsComponent={nameSectionFurtherDetailsContent}
/>
@@ -722,7 +724,10 @@ function ReportDetailsPage({policies, report}: ReportDetailsPageProps) {
return (
-
+ Navigation.goBack(backTo)}
+ />
{renderedAvatar}
@@ -744,7 +749,7 @@ function ReportDetailsPage({policies, report}: ReportDetailsPageProps) {
characterLimit={100}
shouldCheckActionAllowedOnPress={false}
description={translate('reportDescriptionPage.roomDescription')}
- onPress={() => Navigation.navigate(ROUTES.REPORT_DESCRIPTION.getRoute(report.reportID))}
+ onPress={() => Navigation.navigate(ROUTES.REPORT_DESCRIPTION.getRoute(report.reportID, Navigation.getActiveRoute()))}
/>
)}
diff --git a/src/pages/ReportParticipantDetailsPage.tsx b/src/pages/ReportParticipantDetailsPage.tsx
index 6ff710cf23b..db978b70cad 100644
--- a/src/pages/ReportParticipantDetailsPage.tsx
+++ b/src/pages/ReportParticipantDetailsPage.tsx
@@ -49,7 +49,7 @@ function ReportParticipantDetails({personalDetails, report, route}: ReportPartic
const [isRemoveMemberConfirmModalVisible, setIsRemoveMemberConfirmModalVisible] = React.useState(false);
const accountID = Number(route.params.accountID);
- const backTo = ROUTES.REPORT_PARTICIPANTS.getRoute(report?.reportID ?? '-1');
+ const backTo = ROUTES.REPORT_PARTICIPANTS.getRoute(report?.reportID ?? '-1', route.params.backTo);
const member = report?.participants?.[accountID];
const details = personalDetails?.[accountID] ?? ({} as PersonalDetails);
@@ -68,8 +68,8 @@ function ReportParticipantDetails({personalDetails, report, route}: ReportPartic
}, [accountID]);
const openRoleSelectionModal = useCallback(() => {
- Navigation.navigate(ROUTES.REPORT_PARTICIPANTS_ROLE_SELECTION.getRoute(report.reportID, accountID));
- }, [accountID, report.reportID]);
+ Navigation.navigate(ROUTES.REPORT_PARTICIPANTS_ROLE_SELECTION.getRoute(report.reportID, accountID, route.params.backTo));
+ }, [accountID, report.reportID, route.params.backTo]);
if (!member) {
return ;
diff --git a/src/pages/ReportParticipantRoleSelectionPage.tsx b/src/pages/ReportParticipantRoleSelectionPage.tsx
index d2fb5ca365a..17b84e8903e 100644
--- a/src/pages/ReportParticipantRoleSelectionPage.tsx
+++ b/src/pages/ReportParticipantRoleSelectionPage.tsx
@@ -30,7 +30,7 @@ function ReportParticipantRoleSelectionPage({report, route}: ReportParticipantRo
const styles = useThemeStyles();
const accountID = Number(route?.params?.accountID) ?? -1;
- const backTo = ROUTES.REPORT_PARTICIPANTS_DETAILS.getRoute(report?.reportID ?? '-1', accountID);
+ const backTo = ROUTES.REPORT_PARTICIPANTS_DETAILS.getRoute(report?.reportID ?? '-1', accountID, route.params.backTo);
const member = report.participants?.[accountID];
if (!member) {
diff --git a/src/pages/ReportParticipantsPage.tsx b/src/pages/ReportParticipantsPage.tsx
index 8ce9ec4d09a..579e6f7ec10 100755
--- a/src/pages/ReportParticipantsPage.tsx
+++ b/src/pages/ReportParticipantsPage.tsx
@@ -1,4 +1,5 @@
import {useIsFocused} from '@react-navigation/native';
+import type {StackScreenProps} from '@react-navigation/stack';
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {InteractionManager, View} from 'react-native';
import type {TextInput} from 'react-native';
@@ -28,18 +29,22 @@ import {turnOffMobileSelectionMode} from '@libs/actions/MobileSelectionMode';
import * as Report from '@libs/actions/Report';
import * as UserSearchPhraseActions from '@libs/actions/RoomMembersUserSearchPhrase';
import Navigation from '@libs/Navigation/Navigation';
+import type {ParticipantsNavigatorParamList} from '@libs/Navigation/types';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import * as ReportUtils from '@libs/ReportUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
+import type SCREENS from '@src/SCREENS';
import type {WithReportOrNotFoundProps} from './home/report/withReportOrNotFound';
import withReportOrNotFound from './home/report/withReportOrNotFound';
type MemberOption = Omit & {accountID: number};
-function ReportParticipantsPage({report}: WithReportOrNotFoundProps) {
+type ReportParticipantsPageProps = WithReportOrNotFoundProps & StackScreenProps;
+function ReportParticipantsPage({report, route}: ReportParticipantsPageProps) {
+ const backTo = route.params.backTo;
const [selectedMembers, setSelectedMembers] = useState([]);
const [removeMembersConfirmModalVisible, setRemoveMembersConfirmModalVisible] = useState(false);
const {translate, formatPhoneNumber} = useLocalize();
@@ -192,8 +197,8 @@ function ReportParticipantsPage({report}: WithReportOrNotFoundProps) {
* Open the modal to invite a user
*/
const inviteUser = useCallback(() => {
- Navigation.navigate(ROUTES.REPORT_PARTICIPANTS_INVITE.getRoute(report.reportID));
- }, [report]);
+ Navigation.navigate(ROUTES.REPORT_PARTICIPANTS_INVITE.getRoute(report.reportID, backTo));
+ }, [report, backTo]);
/**
* Remove selected users from the workspace
@@ -330,12 +335,12 @@ function ReportParticipantsPage({report}: WithReportOrNotFoundProps) {
const openMemberDetails = useCallback(
(item: MemberOption) => {
if (isGroupChat && isCurrentUserAdmin) {
- Navigation.navigate(ROUTES.REPORT_PARTICIPANTS_DETAILS.getRoute(report.reportID, item.accountID));
+ Navigation.navigate(ROUTES.REPORT_PARTICIPANTS_DETAILS.getRoute(report.reportID, item.accountID, backTo));
return;
}
- Navigation.navigate(ROUTES.PROFILE.getRoute(item.accountID));
+ Navigation.navigate(ROUTES.PROFILE.getRoute(item.accountID, Navigation.getActiveRoute()));
},
- [report, isCurrentUserAdmin, isGroupChat],
+ [report, isCurrentUserAdmin, isGroupChat, backTo],
);
const headerTitle = useMemo(() => {
if (
@@ -377,7 +382,7 @@ function ReportParticipantsPage({report}: WithReportOrNotFoundProps) {
if (report) {
setSearchValue('');
- Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report.reportID));
+ Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report.reportID, backTo));
}
}}
guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_MEMBERS}
diff --git a/src/pages/RoomDescriptionPage.tsx b/src/pages/RoomDescriptionPage.tsx
index 6589a73c88e..a70f17d1468 100644
--- a/src/pages/RoomDescriptionPage.tsx
+++ b/src/pages/RoomDescriptionPage.tsx
@@ -1,4 +1,5 @@
-import {useFocusEffect} from '@react-navigation/native';
+import type {RouteProp} from '@react-navigation/native';
+import {useFocusEffect, useRoute} from '@react-navigation/native';
import React, {useCallback, useRef, useState} from 'react';
import {View} from 'react-native';
import type {OnyxCollection} from 'react-native-onyx';
@@ -13,6 +14,8 @@ import TextInput from '@components/TextInput';
import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
+import Navigation from '@libs/Navigation/Navigation';
+import type {ReportDescriptionNavigatorParamList} from '@libs/Navigation/types';
import Parser from '@libs/Parser';
import * as ReportUtils from '@libs/ReportUtils';
import updateMultilineInputRange from '@libs/updateMultilineInputRange';
@@ -20,6 +23,8 @@ import variables from '@styles/variables';
import * as Report from '@userActions/Report';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
+import type SCREENS from '@src/SCREENS';
import INPUT_IDS from '@src/types/form/ReportDescriptionForm';
import type * as OnyxTypes from '@src/types/onyx';
@@ -32,6 +37,8 @@ type RoomDescriptionPageProps = {
};
function RoomDescriptionPage({report, policies}: RoomDescriptionPageProps) {
+ const route = useRoute>();
+ const backTo = route.params.backTo;
const styles = useThemeStyles();
const [description, setDescription] = useState(() => Parser.htmlToMarkdown(report?.description ?? ''));
const reportDescriptionInputRef = useRef(null);
@@ -43,9 +50,17 @@ function RoomDescriptionPage({report, policies}: RoomDescriptionPageProps) {
setDescription(value);
}, []);
+ const goBack = useCallback(() => {
+ Navigation.goBack(backTo ?? ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report.reportID));
+ }, [report.reportID, backTo]);
+
const submitForm = useCallback(() => {
- Report.updateDescription(report.reportID, report?.description ?? '', description.trim());
- }, [report.reportID, report.description, description]);
+ const previousValue = report?.description ?? '';
+ const newValue = description.trim();
+
+ Report.updateDescription(report.reportID, previousValue, newValue);
+ goBack();
+ }, [report.reportID, report.description, description, goBack]);
useFocusEffect(
useCallback(() => {
@@ -68,7 +83,10 @@ function RoomDescriptionPage({report, policies}: RoomDescriptionPageProps) {
includeSafeAreaPaddingBottom={false}
testID={RoomDescriptionPage.displayName}
>
-
+
{canEdit && (
ReportUtils.getReportName(report), [report]);
const inviteUsers = useCallback(() => {
HttpUtils.cancelPendingRequests(READ_COMMANDS.SEARCH_FOR_REPORTS);
diff --git a/src/pages/RoomMemberDetailsPage.tsx b/src/pages/RoomMemberDetailsPage.tsx
index 8b2e89024d8..475cf37a884 100644
--- a/src/pages/RoomMemberDetailsPage.tsx
+++ b/src/pages/RoomMemberDetailsPage.tsx
@@ -38,7 +38,7 @@ function RoomMemberDetailsPage({report, route}: RoomMemberDetailsPagePageProps)
const [isRemoveMemberConfirmModalVisible, setIsRemoveMemberConfirmModalVisible] = React.useState(false);
const accountID = Number(route.params.accountID);
- const backTo = ROUTES.ROOM_MEMBERS.getRoute(report?.reportID ?? '-1');
+ const backTo = ROUTES.ROOM_MEMBERS.getRoute(report?.reportID ?? '-1', route.params.backTo);
const member = report?.participants?.[accountID];
const details = personalDetails?.[accountID] ?? ({} as PersonalDetails);
diff --git a/src/pages/RoomMembersPage.tsx b/src/pages/RoomMembersPage.tsx
index 19e631098e4..765c738bc57 100644
--- a/src/pages/RoomMembersPage.tsx
+++ b/src/pages/RoomMembersPage.tsx
@@ -1,4 +1,5 @@
-import {useIsFocused} from '@react-navigation/native';
+import type {RouteProp} from '@react-navigation/native';
+import {useIsFocused, useRoute} from '@react-navigation/native';
import type {StackScreenProps} from '@react-navigation/stack';
import React, {useCallback, useEffect, useMemo, useState} from 'react';
import {View} from 'react-native';
@@ -55,6 +56,7 @@ type RoomMembersPageProps = WithReportOrNotFoundProps &
StackScreenProps;
function RoomMembersPage({report, session, policies}: RoomMembersPageProps) {
+ const route = useRoute>();
const styles = useThemeStyles();
const {formatPhoneNumber, translate} = useLocalize();
const [selectedMembers, setSelectedMembers] = useState([]);
@@ -65,6 +67,7 @@ function RoomMembersPage({report, session, policies}: RoomMembersPageProps) {
const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT;
const policy = useMemo(() => policies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID ?? ''}`], [policies, report?.policyID]);
const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(report), [report]);
+ const backTo = route.params.backTo;
const isFocusedScreen = useIsFocused();
const {isOffline} = useNetwork();
@@ -113,8 +116,8 @@ function RoomMembersPage({report, session, policies}: RoomMembersPageProps) {
return;
}
setSearchValue('');
- Navigation.navigate(ROUTES.ROOM_INVITE.getRoute(report.reportID));
- }, [report, setSearchValue]);
+ Navigation.navigate(ROUTES.ROOM_INVITE.getRoute(report.reportID, undefined, backTo));
+ }, [report, setSearchValue, backTo]);
/**
* Remove selected users from the room
@@ -311,9 +314,9 @@ function RoomMembersPage({report, session, policies}: RoomMembersPageProps) {
/** Opens the room member details page */
const openRoomMemberDetails = useCallback(
(item: ListItem) => {
- Navigation.navigate(ROUTES.ROOM_MEMBER_DETAILS.getRoute(report.reportID, item?.accountID ?? -1));
+ Navigation.navigate(ROUTES.ROOM_MEMBER_DETAILS.getRoute(report.reportID, item?.accountID ?? -1, backTo));
},
- [report],
+ [report, backTo],
);
const selectionModeHeader = selectionMode?.isEnabled && isSmallScreenWidth;
@@ -345,7 +348,7 @@ function RoomMembersPage({report, session, policies}: RoomMembersPageProps) {
}
subtitleKey={isEmptyObject(report) ? undefined : 'roomMembersPage.notAuthorized'}
onBackButtonPress={() => {
- Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report.reportID));
+ Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report.reportID, backTo));
}}
>
{headerButtons}
diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx
index 8f08b128619..87ee65a7e2d 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/EmptySearchView.tsx b/src/pages/Search/EmptySearchView.tsx
index edcf6ab2321..c1ea305c5b6 100644
--- a/src/pages/Search/EmptySearchView.tsx
+++ b/src/pages/Search/EmptySearchView.tsx
@@ -1,36 +1,103 @@
-import React, {useMemo} from 'react';
+import React, {useMemo, useState} from 'react';
+import {Linking, View} from 'react-native';
+import DotIndicatorMessage from '@components/DotIndicatorMessage';
import EmptyStateComponent from '@components/EmptyStateComponent';
+import type {FeatureListItem} from '@components/FeatureList';
+import * as Illustrations from '@components/Icon/Illustrations';
import LottieAnimations from '@components/LottieAnimations';
+import MenuItem from '@components/MenuItem';
import SearchRowSkeleton from '@components/Skeletons/SearchRowSkeleton';
+import Text from '@components/Text';
+import TextLink from '@components/TextLink';
import useLocalize from '@hooks/useLocalize';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
-import Navigation from '@libs/Navigation/Navigation';
+import * as TripsResevationUtils from '@libs/TripReservationUtils';
+import variables from '@styles/variables';
import CONST from '@src/CONST';
-import ROUTES from '@src/ROUTES';
import type {SearchDataTypes} from '@src/types/onyx/SearchResults';
type EmptySearchViewProps = {
type: SearchDataTypes;
};
+const tripsFeatures: FeatureListItem[] = [
+ {
+ icon: Illustrations.PiggyBank,
+ translationKey: 'travel.features.saveMoney',
+ },
+ {
+ icon: Illustrations.Alert,
+ translationKey: 'travel.features.alerts',
+ },
+];
+
function EmptySearchView({type}: EmptySearchViewProps) {
const theme = useTheme();
const StyleUtils = useStyleUtils();
const {translate} = useLocalize();
const styles = useThemeStyles();
+ const [ctaErrorMessage, setCtaErrorMessage] = useState('');
+
+ const subtitleComponent = useMemo(() => {
+ return (
+ <>
+
+ {translate('travel.subtitle')}{' '}
+ {
+ Linking.openURL(CONST.BOOK_TRAVEL_DEMO_URL);
+ }}
+ >
+ {translate('travel.bookADemo')}
+
+ {translate('travel.toLearnMore')}
+
+
+ {tripsFeatures.map((tripsFeature) => (
+
+
+
+ ))}
+
+ {ctaErrorMessage && (
+
+ )}
+ >
+ );
+ }, [styles, translate, ctaErrorMessage]);
+
const content = useMemo(() => {
switch (type) {
case CONST.SEARCH.DATA_TYPES.TRIP:
return {
headerMedia: LottieAnimations.TripsEmptyState,
- headerStyles: [StyleUtils.getBackgroundColorStyle(theme.travelBG), styles.w100],
- title: translate('search.searchResults.emptyTripResults.title'),
- subtitle: translate('search.searchResults.emptyTripResults.subtitle'),
+ headerStyles: StyleUtils.getBackgroundColorStyle(theme.travelBG),
+ headerContentStyles: StyleUtils.getWidthAndHeightStyle(375, 240),
+ title: translate('travel.title'),
+ titleStyles: {...styles.textAlignLeft},
+ subtitle: subtitleComponent,
buttonText: translate('search.searchResults.emptyTripResults.buttonText'),
- buttonAction: () => Navigation.navigate(ROUTES.TRAVEL_MY_TRIPS),
+ buttonAction: () => TripsResevationUtils.bookATrip(translate, setCtaErrorMessage, ctaErrorMessage),
};
case CONST.SEARCH.DATA_TYPES.CHAT:
case CONST.SEARCH.DATA_TYPES.EXPENSE:
@@ -46,7 +113,7 @@ function EmptySearchView({type}: EmptySearchViewProps) {
headerContentStyles: styles.emptyStateFolderWebStyles,
};
}
- }, [type, StyleUtils, translate, theme, styles.w100, styles.emptyStateFolderWebStyles]);
+ }, [type, StyleUtils, translate, theme, styles, subtitleComponent, ctaErrorMessage]);
return (
diff --git a/src/pages/Search/SearchTypeMenu.tsx b/src/pages/Search/SearchTypeMenu.tsx
index 7c8af2388f5..311168dc5d6 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 83a5c3189a1..8bbbfde2e8f 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/ShareCodePage.tsx b/src/pages/ShareCodePage.tsx
index e11e99a6e85..18f6c5960b5 100644
--- a/src/pages/ShareCodePage.tsx
+++ b/src/pages/ShareCodePage.tsx
@@ -19,6 +19,7 @@ import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import Clipboard from '@libs/Clipboard';
import Navigation from '@libs/Navigation/Navigation';
+import type {BackToParams} from '@libs/Navigation/types';
import * as ReportUtils from '@libs/ReportUtils';
import * as Url from '@libs/Url';
import * as UserUtils from '@libs/UserUtils';
@@ -34,7 +35,7 @@ type ShareCodePageOnyxProps = {
policy?: OnyxEntry;
};
-type ShareCodePageProps = ShareCodePageOnyxProps;
+type ShareCodePageProps = ShareCodePageOnyxProps & BackToParams;
/**
* When sharing a policy (workspace) only return user avatar that is user defined. Default ws avatars have separate logic.
@@ -53,7 +54,7 @@ function getLogoForWorkspace(report: OnyxEntry, policy?: OnyxEntry
Navigation.goBack(isReport ? ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report?.reportID) : undefined)}
+ onBackButtonPress={() => Navigation.goBack(isReport ? ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report?.reportID, backTo) : undefined)}
shouldShowBackButton
/>
@@ -146,7 +147,7 @@ function ShareCodePage({report, policy}: ShareCodePageProps) {
diff --git a/src/pages/TeachersUnite/SaveTheWorldPage.tsx b/src/pages/TeachersUnite/SaveTheWorldPage.tsx
index b4f850981c9..b30fedab530 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/TransactionDuplicate/Confirmation.tsx b/src/pages/TransactionDuplicate/Confirmation.tsx
index 9830ea2a874..15217e215ad 100644
--- a/src/pages/TransactionDuplicate/Confirmation.tsx
+++ b/src/pages/TransactionDuplicate/Confirmation.tsx
@@ -15,6 +15,7 @@ import ScrollView from '@components/ScrollView';
import {ShowContextMenuContext} from '@components/ShowContextMenuContext';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
+import useReviewDuplicatesNavigation from '@hooks/useReviewDuplicatesNavigation';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
import type {TransactionDuplicateNavigatorParamList} from '@libs/Navigation/types';
@@ -36,6 +37,9 @@ function Confirmation() {
const route = useRoute>();
const [reviewDuplicates, reviewDuplicatesResult] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES);
const transaction = useMemo(() => TransactionUtils.buildNewTransactionAfterReviewingDuplicates(reviewDuplicates), [reviewDuplicates]);
+ const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? '');
+ const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID);
+ const {goBack} = useReviewDuplicatesNavigation(Object.keys(compareResult.change ?? {}), 'confirmation', route.params.threadReportID, route.params.backTo);
const [report, reportResult] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`);
const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transaction?.reportID}`);
const reportAction = Object.values(reportActions ?? {}).find(
@@ -83,7 +87,10 @@ function Confirmation() {
{({safeAreaPaddingBottomStyle}) => (
-
+
-
+ Navigation.goBack(route.params.backTo)}
+ />
);
- if (isSingleTransactionView && report) {
+ if (isSingleTransactionView) {
headerView = (
);
@@ -311,14 +310,13 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro
Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(route?.params?.reportID));
}, [transactionThreadReportID, route?.params?.reportActionID, route?.params?.reportID, linkedAction, reportID]);
- if (report && (ReportUtils.isMoneyRequestReport(report) || ReportUtils.isInvoiceReport(report))) {
+ if (ReportUtils.isMoneyRequestReport(report) || ReportUtils.isInvoiceReport(report)) {
headerView = (
);
@@ -440,7 +438,7 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro
return;
}
- if (report && !shouldFetchReport(report) && (isInitialPageReady || isLinkedMessagePageReady)) {
+ if (!shouldFetchReport(report) && (isInitialPageReady || isLinkedMessagePageReady)) {
return;
}
@@ -773,7 +771,7 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro
{shouldShowSkeleton && (
<>
- {shouldShowMostRecentReportAction && report && (
+ {shouldShowMostRecentReportAction && (
{
+ const activeRoute = Navigation.getActiveRoute();
if (closePopover) {
- hideContextMenu(false, () => Navigation.navigate(ROUTES.FLAG_COMMENT.getRoute(reportID, reportAction?.reportActionID)));
+ hideContextMenu(false, () => Navigation.navigate(ROUTES.FLAG_COMMENT.getRoute(reportID, reportAction?.reportActionID, activeRoute)));
return;
}
- Navigation.navigate(ROUTES.FLAG_COMMENT.getRoute(reportID, reportAction?.reportActionID));
+ Navigation.navigate(ROUTES.FLAG_COMMENT.getRoute(reportID, reportAction?.reportActionID, activeRoute));
},
getDescription: () => {},
},
diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx
index dd2853983ec..99d25e2b402 100644
--- a/src/pages/home/report/ReportActionItem.tsx
+++ b/src/pages/home/report/ReportActionItem.tsx
@@ -3,7 +3,7 @@ import React, {memo, useCallback, useContext, useEffect, useMemo, useRef, useSta
import type {GestureResponderEvent, TextInput} from 'react-native';
import {InteractionManager, View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
-import {useOnyx, withOnyx} from 'react-native-onyx';
+import {useOnyx} from 'react-native-onyx';
import type {Emoji} from '@assets/emojis/types';
import {AttachmentContext} from '@components/AttachmentContext';
import Button from '@components/Button';
@@ -64,7 +64,6 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type * as OnyxTypes from '@src/types/onyx';
-import type {Errors} from '@src/types/onyx/OnyxCommon';
import type {JoinWorkspaceResolution} from '@src/types/onyx/OriginalMessage';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import {RestrictedReadOnlyContextMenuActions} from './ContextMenu/ContextMenuActions';
@@ -82,22 +81,9 @@ import ReportActionItemSingle from './ReportActionItemSingle';
import ReportActionItemThread from './ReportActionItemThread';
import ReportAttachmentsContext from './ReportAttachmentsContext';
-type ReportActionItemOnyxProps = {
- /** IOU report for this action, if any */
- iouReport: OnyxEntry;
-
- 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 21bb6471f5b..ad40df3d521 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 (
-
+
-
+
ReportUtils.navigateToDetailsPage(report)}
+ onPress={() => ReportUtils.navigateToDetailsPage(report, Navigation.getReportRHPActiveRoute())}
style={[styles.mh5, styles.mb3, styles.alignSelfStart, shouldDisableDetailPage && styles.cursorDefault]}
accessibilityLabel={translate('common.details')}
role={CONST.ROLE.BUTTON}
diff --git a/src/pages/home/report/ReportActionItemSingle.tsx b/src/pages/home/report/ReportActionItemSingle.tsx
index 0e58296d39f..57ac3a0c0bf 100644
--- a/src/pages/home/report/ReportActionItemSingle.tsx
+++ b/src/pages/home/report/ReportActionItemSingle.tsx
@@ -39,7 +39,7 @@ type ReportActionItemSingleProps = Partial & {
wrapperStyle?: StyleProp;
/** Report for this action */
- report: Report;
+ report: OnyxEntry;
/** IOU Report for this action, if any */
iouReport?: OnyxEntry;
@@ -58,11 +58,11 @@ type ReportActionItemSingleProps = Partial & {
};
const showUserDetails = (accountID: string) => {
- Navigation.navigate(ROUTES.PROFILE.getRoute(accountID));
+ Navigation.navigate(ROUTES.PROFILE.getRoute(accountID, Navigation.getReportRHPActiveRoute()));
};
const showWorkspaceDetails = (reportID: string) => {
- Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(reportID));
+ Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(reportID, Navigation.getReportRHPActiveRoute()));
};
function ReportActionItemSingle({
@@ -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,11 +174,11 @@ function ReportActionItemSingle({
const showActorDetails = useCallback(() => {
if (isWorkspaceActor) {
- showWorkspaceDetails(reportID);
+ showWorkspaceDetails(reportID ?? '');
} else {
// Show participants page IOU report preview
if (iouReportID && displayAllActors) {
- Navigation.navigate(ROUTES.REPORT_PARTICIPANTS.getRoute(iouReportID));
+ Navigation.navigate(ROUTES.REPORT_PARTICIPANTS.getRoute(iouReportID, Navigation.getReportRHPActiveRoute()));
return;
}
showUserDetails(action?.delegateAccountID ? String(action.delegateAccountID) : String(actorAccountID));
diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx
index d01efdf28e9..9ff8762956d 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 48c578fd743..ff1c2431ca8 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/home/report/ReportDetailsExportPage.tsx b/src/pages/home/report/ReportDetailsExportPage.tsx
index 6f16786682a..bed7569cf88 100644
--- a/src/pages/home/report/ReportDetailsExportPage.tsx
+++ b/src/pages/home/report/ReportDetailsExportPage.tsx
@@ -29,6 +29,7 @@ type ExportSelectorType = SelectorType;
function ReportDetailsExportPage({route}: ReportDetailsExportPageProps) {
const connectionName = route?.params?.connectionName;
const reportID = route.params.reportID;
+ const backTo = route.params.backTo;
const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`);
const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`);
const policyID = report?.policyID;
@@ -81,7 +82,10 @@ function ReportDetailsExportPage({route}: ReportDetailsExportPageProps) {
if (!canBeExported) {
return (
-
+ Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(reportID, backTo))}
+ />
Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(reportID))}
+ onBackButtonPress={() => Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(reportID, backTo))}
title="common.export"
connectionName={connectionName}
onSelectRow={({value}) => {
diff --git a/src/pages/home/report/ReportDetailsShareCodePage.tsx b/src/pages/home/report/ReportDetailsShareCodePage.tsx
index efee188d0be..4caa29209fa 100644
--- a/src/pages/home/report/ReportDetailsShareCodePage.tsx
+++ b/src/pages/home/report/ReportDetailsShareCodePage.tsx
@@ -1,10 +1,13 @@
+import type {StackScreenProps} from '@react-navigation/stack';
import React from 'react';
import {withOnyx} from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
+import type {ReportDetailsNavigatorParamList} from '@libs/Navigation/types';
import * as ReportUtils from '@libs/ReportUtils';
import NotFoundPage from '@pages/ErrorPage/NotFoundPage';
import ShareCodePage from '@pages/ShareCodePage';
import ONYXKEYS from '@src/ONYXKEYS';
+import type SCREENS from '@src/SCREENS';
import type {Policy} from '@src/types/onyx';
import type {WithReportOrNotFoundProps} from './withReportOrNotFound';
import withReportOrNotFound from './withReportOrNotFound';
@@ -13,14 +16,17 @@ type ReportDetailsShareCodePageOnyxProps = {
policy: OnyxEntry;
};
-type ReportDetailsShareCodePageProps = ReportDetailsShareCodePageOnyxProps & WithReportOrNotFoundProps;
+type ReportDetailsShareCodePageProps = ReportDetailsShareCodePageOnyxProps &
+ WithReportOrNotFoundProps &
+ StackScreenProps;
-function ReportDetailsShareCodePage({report, policy}: ReportDetailsShareCodePageProps) {
+function ReportDetailsShareCodePage({report, policy, route}: ReportDetailsShareCodePageProps) {
if (ReportUtils.isSelfDM(report)) {
return ;
}
return (
diff --git a/src/pages/home/report/withReportOrNotFound.tsx b/src/pages/home/report/withReportOrNotFound.tsx
index c7cc6961b76..7947ab0a04b 100644
--- a/src/pages/home/report/withReportOrNotFound.tsx
+++ b/src/pages/home/report/withReportOrNotFound.tsx
@@ -7,7 +7,14 @@ import {withOnyx} from 'react-native-onyx';
import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import getComponentDisplayName from '@libs/getComponentDisplayName';
import * as ReportUtils from '@libs/ReportUtils';
-import type {ParticipantsNavigatorParamList, PrivateNotesNavigatorParamList, ReportDescriptionNavigatorParamList, RoomMembersNavigatorParamList} from '@navigation/types';
+import type {
+ ParticipantsNavigatorParamList,
+ PrivateNotesNavigatorParamList,
+ ReportDescriptionNavigatorParamList,
+ ReportDetailsNavigatorParamList,
+ ReportSettingsNavigatorParamList,
+ RoomMembersNavigatorParamList,
+} from '@navigation/types';
import NotFoundPage from '@pages/ErrorPage/NotFoundPage';
import * as Report from '@userActions/Report';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -36,8 +43,12 @@ type WithReportOrNotFoundProps = WithReportOrNotFoundOnyxProps & {
route:
| RouteProp
| RouteProp
+ | RouteProp
| RouteProp
| RouteProp
+ | RouteProp
+ | RouteProp
+ | RouteProp
| RouteProp;
/** The report currently being looked at */
diff --git a/src/pages/iou/SplitBillDetailsPage.tsx b/src/pages/iou/SplitBillDetailsPage.tsx
index efefc8ccc9e..b696fb38ff0 100644
--- a/src/pages/iou/SplitBillDetailsPage.tsx
+++ b/src/pages/iou/SplitBillDetailsPage.tsx
@@ -14,6 +14,7 @@ import ScreenWrapper from '@components/ScreenWrapper';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
+import Navigation from '@libs/Navigation/Navigation';
import type {SplitDetailsNavigatorParamList} from '@libs/Navigation/types';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
@@ -104,7 +105,10 @@ function SplitBillDetailsPage({personalDetails, report, route, reportActions, tr
return (
-
+ Navigation.goBack(route.params.backTo)}
+ />
{isScanning && (
diff --git a/src/pages/iou/request/step/IOURequestStepAmount.tsx b/src/pages/iou/request/step/IOURequestStepAmount.tsx
index a03c40e8c1f..2a0aa438fe9 100644
--- a/src/pages/iou/request/step/IOURequestStepAmount.tsx
+++ b/src/pages/iou/request/step/IOURequestStepAmount.tsx
@@ -62,7 +62,7 @@ type IOURequestStepAmountProps = IOURequestStepAmountOnyxProps &
function IOURequestStepAmount({
report,
route: {
- params: {iouType, reportID, transactionID, backTo, action, currency: selectedCurrency = ''},
+ params: {iouType, reportID, transactionID, backTo, pageIndex, action, currency: selectedCurrency = ''},
},
transaction,
policy,
@@ -133,9 +133,7 @@ function IOURequestStepAmount({
};
const navigateToCurrencySelectionPage = () => {
- Navigation.navigate(
- ROUTES.MONEY_REQUEST_STEP_CURRENCY.getRoute(action, iouType, transactionID, reportID, backTo ? 'confirm' : '', currency, Navigation.getActiveRouteWithoutParams()),
- );
+ Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CURRENCY.getRoute(action, iouType, transactionID, reportID, pageIndex, currency, Navigation.getActiveRoute()));
};
const navigateToParticipantPage = () => {
diff --git a/src/pages/iou/request/step/IOURequestStepCurrency.tsx b/src/pages/iou/request/step/IOURequestStepCurrency.tsx
index b51d6a8998a..c48bf91986f 100644
--- a/src/pages/iou/request/step/IOURequestStepCurrency.tsx
+++ b/src/pages/iou/request/step/IOURequestStepCurrency.tsx
@@ -8,11 +8,10 @@ import useLocalize from '@hooks/useLocalize';
import * as CurrencyUtils from '@libs/CurrencyUtils';
import Navigation from '@libs/Navigation/Navigation';
import * as ReportUtils from '@libs/ReportUtils';
+import {appendParam} from '@libs/Url';
import * as IOU from '@userActions/IOU';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
-import ROUTES, {getUrlWithBackToParam} from '@src/ROUTES';
-import type {Route} from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
import type {Transaction} from '@src/types/onyx';
import StepScreenWrapper from './StepScreenWrapper';
@@ -30,7 +29,7 @@ type IOURequestStepCurrencyProps = IOURequestStepCurrencyOnyxProps & WithFullTra
function IOURequestStepCurrency({
route: {
- params: {backTo, iouType, pageIndex, reportID, transactionID, action, currency: selectedCurrency = ''},
+ params: {backTo, pageIndex, transactionID, action, currency: selectedCurrency = ''},
},
draftTransaction,
recentlyUsedCurrencies,
@@ -44,15 +43,11 @@ function IOURequestStepCurrency({
// then the user needs taken back to the confirmation page instead of the initial amount page. This is because the route params
// are only able to handle one backTo param at a time and the user needs to go back to the amount page before going back
// to the confirmation page
- if (pageIndex === 'confirm') {
- const routeToAmountPageWithConfirmationAsBackTo = getUrlWithBackToParam(
- backTo as string,
- `/${ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID)}`,
- );
+ if (pageIndex === CONST.IOU.PAGE_INDEX.CONFIRM) {
if (selectedCurrencyValue) {
- Navigation.navigate(`${routeToAmountPageWithConfirmationAsBackTo}¤cy=${selectedCurrencyValue}` as Route);
+ Navigation.navigate(appendParam(backTo as string, 'currency', selectedCurrencyValue));
} else {
- Navigation.goBack(routeToAmountPageWithConfirmationAsBackTo as Route);
+ Navigation.goBack(backTo);
}
return;
}
@@ -61,7 +56,7 @@ function IOURequestStepCurrency({
const confirmCurrencySelection = (option: CurrencyListItem) => {
Keyboard.dismiss();
- if (pageIndex !== 'confirm') {
+ if (pageIndex !== CONST.IOU.PAGE_INDEX.CONFIRM) {
IOU.setMoneyRequestCurrency(transactionID, option.currencyCode, action === CONST.IOU.ACTION.EDIT);
}
diff --git a/src/pages/settings/AboutPage/AboutPage.tsx b/src/pages/settings/AboutPage/AboutPage.tsx
index 1f3f235a9c3..4232e08f70f 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 e51250f3538..f2c5f036664 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 38dec5fe064..46f280abf19 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/Report/NotificationPreferencePage.tsx b/src/pages/settings/Report/NotificationPreferencePage.tsx
index fd256d68513..416d710d496 100644
--- a/src/pages/settings/Report/NotificationPreferencePage.tsx
+++ b/src/pages/settings/Report/NotificationPreferencePage.tsx
@@ -1,6 +1,9 @@
+import type {RouteProp} from '@react-navigation/native';
+import {useRoute} from '@react-navigation/native';
import type {StackScreenProps} from '@react-navigation/stack';
-import React from 'react';
+import React, {useCallback} from 'react';
import {useOnyx} from 'react-native-onyx';
+import type {ValueOf} from 'type-fest';
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import ScreenWrapper from '@components/ScreenWrapper';
@@ -19,6 +22,7 @@ import type SCREENS from '@src/SCREENS';
type NotificationPreferencePageProps = WithReportOrNotFoundProps & StackScreenProps;
function NotificationPreferencePage({report}: NotificationPreferencePageProps) {
+ const route = useRoute>();
const {translate} = useLocalize();
const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report.reportID || -1}`);
const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report);
@@ -36,6 +40,18 @@ function NotificationPreferencePage({report}: NotificationPreferencePageProps) {
isSelected: preference === currentNotificationPreference,
}));
+ const goBack = useCallback(() => {
+ ReportUtils.goBackToDetailsPage(report, route.params.backTo);
+ }, [report, route.params.backTo]);
+
+ const updateNotificationPreference = useCallback(
+ (value: ValueOf) => {
+ ReportActions.updateNotificationPreference(report.reportID, currentNotificationPreference, value, undefined, undefined);
+ goBack();
+ },
+ [report.reportID, currentNotificationPreference, goBack],
+ );
+
return (
ReportUtils.goBackToDetailsPage(report)}
+ onBackButtonPress={goBack}
/>
- report && ReportActions.updateNotificationPreference(report.reportID, currentNotificationPreference, option.value, true, undefined, undefined, report)
- }
+ onSelectRow={(option) => updateNotificationPreference(option.value)}
shouldSingleExecuteRowSelect
initiallyFocusedOptionKey={notificationPreferenceOptions.find((locale) => locale.isSelected)?.keyForList}
/>
diff --git a/src/pages/settings/Report/ReportSettingsPage.tsx b/src/pages/settings/Report/ReportSettingsPage.tsx
index 6a9986b5550..c407788dce6 100644
--- a/src/pages/settings/Report/ReportSettingsPage.tsx
+++ b/src/pages/settings/Report/ReportSettingsPage.tsx
@@ -23,7 +23,8 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject';
type ReportSettingsPageProps = WithReportOrNotFoundProps & StackScreenProps;
-function ReportSettingsPage({report, policies}: ReportSettingsPageProps) {
+function ReportSettingsPage({report, policies, route}: ReportSettingsPageProps) {
+ const backTo = route.params.backTo;
const reportID = report?.reportID ?? '-1';
const styles = useThemeStyles();
const {translate} = useLocalize();
@@ -53,7 +54,7 @@ function ReportSettingsPage({report, policies}: ReportSettingsPageProps) {
Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(reportID))}
+ onBackButtonPress={() => Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(reportID, backTo))}
/>
{shouldShowNotificationPref && (
@@ -61,7 +62,7 @@ function ReportSettingsPage({report, policies}: ReportSettingsPageProps) {
shouldShowRightIcon
title={notificationPreference}
description={translate('notificationPreferencesPage.label')}
- onPress={() => Navigation.navigate(ROUTES.REPORT_SETTINGS_NOTIFICATION_PREFERENCES.getRoute(reportID))}
+ onPress={() => Navigation.navigate(ROUTES.REPORT_SETTINGS_NOTIFICATION_PREFERENCES.getRoute(reportID, backTo))}
/>
)}
{shouldShowWriteCapability &&
@@ -70,7 +71,7 @@ function ReportSettingsPage({report, policies}: ReportSettingsPageProps) {
shouldShowRightIcon
title={writeCapabilityText}
description={translate('writeCapabilityPage.label')}
- onPress={() => Navigation.navigate(ROUTES.REPORT_SETTINGS_WRITE_CAPABILITY.getRoute(reportID))}
+ onPress={() => Navigation.navigate(ROUTES.REPORT_SETTINGS_WRITE_CAPABILITY.getRoute(reportID, backTo))}
/>
) : (
@@ -95,7 +96,7 @@ function ReportSettingsPage({report, policies}: ReportSettingsPageProps) {
shouldShowRightIcon
title={translate(`newRoomPage.visibilityOptions.${report.visibility}`)}
description={translate('newRoomPage.visibility')}
- onPress={() => Navigation.navigate(ROUTES.REPORT_SETTINGS_VISIBILITY.getRoute(report.reportID))}
+ onPress={() => Navigation.navigate(ROUTES.REPORT_SETTINGS_VISIBILITY.getRoute(report.reportID, backTo))}
/>
) : (
diff --git a/src/pages/settings/Report/RoomNamePage.tsx b/src/pages/settings/Report/RoomNamePage.tsx
index 764ccbff6b3..a497d793f46 100644
--- a/src/pages/settings/Report/RoomNamePage.tsx
+++ b/src/pages/settings/Report/RoomNamePage.tsx
@@ -1,4 +1,5 @@
-import {useIsFocused} from '@react-navigation/native';
+import type {RouteProp} from '@react-navigation/native';
+import {useIsFocused, useRoute} from '@react-navigation/native';
import React, {useCallback, useRef} from 'react';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
@@ -15,12 +16,14 @@ import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import * as ErrorUtils from '@libs/ErrorUtils';
import Navigation from '@libs/Navigation/Navigation';
+import type {ReportSettingsNavigatorParamList} from '@libs/Navigation/types';
import * as ReportUtils from '@libs/ReportUtils';
import * as ValidationUtils from '@libs/ValidationUtils';
import * as ReportActions from '@userActions/Report';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
+import type SCREENS from '@src/SCREENS';
import INPUT_IDS from '@src/types/form/RoomNameForm';
import type {Report} from '@src/types/onyx';
@@ -34,10 +37,16 @@ type RoomNamePageProps = RoomNamePageOnyxProps & {
};
function RoomNamePage({report, reports}: RoomNamePageProps) {
+ const route = useRoute>();
const styles = useThemeStyles();
const roomNameInputRef = useRef(null);
const isFocused = useIsFocused();
const {translate} = useLocalize();
+ const reportID = report?.reportID ?? '-1';
+
+ const goBack = useCallback(() => {
+ Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(reportID, route.params.backTo));
+ }, [reportID, route.params.backTo]);
const validate = useCallback(
(values: FormOnyxValues) => {
@@ -69,6 +78,14 @@ function RoomNamePage({report, reports}: RoomNamePageProps) {
[report, reports, translate],
);
+ const updatePolicyRoomName = useCallback(
+ (values: FormOnyxValues) => {
+ ReportActions.updatePolicyRoomName(report, values.roomName);
+ goBack();
+ },
+ [report, goBack],
+ );
+
return (
roomNameInputRef.current?.focus()}
@@ -78,12 +95,12 @@ function RoomNamePage({report, reports}: RoomNamePageProps) {
Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(report?.reportID ?? '-1'))}
+ onBackButtonPress={goBack}
/>
report && ReportActions.updatePolicyRoomNameAndNavigate(report, values.roomName)}
+ onSubmit={updatePolicyRoomName}
validate={validate}
submitButtonText={translate('common.save')}
enabledWhenOffline
diff --git a/src/pages/settings/Report/VisibilityPage.tsx b/src/pages/settings/Report/VisibilityPage.tsx
index 5d09bec1788..07c05d1b8de 100644
--- a/src/pages/settings/Report/VisibilityPage.tsx
+++ b/src/pages/settings/Report/VisibilityPage.tsx
@@ -1,3 +1,5 @@
+import type {RouteProp} from '@react-navigation/native';
+import {useRoute} from '@react-navigation/native';
import type {StackScreenProps} from '@react-navigation/stack';
import React, {useCallback, useMemo, useRef, useState} from 'react';
import {useOnyx} from 'react-native-onyx';
@@ -21,6 +23,7 @@ import type {RoomVisibility} from '@src/types/onyx/Report';
type VisibilityProps = WithReportOrNotFoundProps & StackScreenProps;
function VisibilityPage({report}: VisibilityProps) {
+ const route = useRoute>();
const [showConfirmModal, setShowConfirmModal] = useState(false);
const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID || -1}`);
const shouldGoBackToDetailsPage = useRef(false);
@@ -42,6 +45,10 @@ function VisibilityPage({report}: VisibilityProps) {
[translate, report?.visibility],
);
+ const goBack = useCallback(() => {
+ ReportUtils.goBackToDetailsPage(report, route.params.backTo);
+ }, [report, route.params.backTo]);
+
const changeVisibility = useCallback(
(newVisibility: RoomVisibility) => {
if (!report) {
@@ -51,10 +58,10 @@ function VisibilityPage({report}: VisibilityProps) {
if (showConfirmModal) {
shouldGoBackToDetailsPage.current = true;
} else {
- ReportUtils.goBackToDetailsPage(report);
+ goBack();
}
},
- [report, showConfirmModal],
+ [report, showConfirmModal, goBack],
);
const hideModal = useCallback(() => {
@@ -69,7 +76,7 @@ function VisibilityPage({report}: VisibilityProps) {
ReportUtils.goBackToDetailsPage(report)}
+ onBackButtonPress={goBack}
/>
;
function WriteCapabilityPage({report, policy}: WriteCapabilityPageProps) {
+ const route = useRoute>();
const {translate} = useLocalize();
const writeCapabilityOptions = Object.values(CONST.REPORT.WRITE_CAPABILITIES).map((value) => ({
value,
@@ -40,6 +44,18 @@ function WriteCapabilityPage({report, policy}: WriteCapabilityPageProps) {
const isAbleToEdit = ReportUtils.canEditWriteCapability(report, policy);
+ const goBack = useCallback(() => {
+ Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(report.reportID, route.params.backTo));
+ }, [report.reportID, route.params.backTo]);
+
+ const updateWriteCapability = useCallback(
+ (newValue: ValueOf) => {
+ ReportActions.updateWriteCapability(report, newValue);
+ goBack();
+ },
+ [report, goBack],
+ );
+
return (
Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(report?.reportID ?? '-1'))}
+ onBackButtonPress={goBack}
/>
report && ReportActions.updateWriteCapabilityAndNavigate(report, option.value)}
+ onSelectRow={(option) => updateWriteCapability(option.value)}
shouldSingleExecuteRowSelect
initiallyFocusedOptionKey={writeCapabilityOptions.find((locale) => locale.isSelected)?.keyForList}
/>
diff --git a/src/pages/settings/Security/SecuritySettingsPage.tsx b/src/pages/settings/Security/SecuritySettingsPage.tsx
index 6403b0ac64e..e54e7d87309 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 ecc6a6cdc27..2cb99207f8e 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 16fa481b16a..c34db3fa77a 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 79f7e45d8cd..c7c2ca956ae 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';
@@ -24,6 +23,7 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
import useWaitForNavigation from '@hooks/useWaitForNavigation';
import {setShouldMaskOnyxState} from '@libs/actions/MaskOnyx';
+import * as PersistedRequests from '@libs/actions/PersistedRequests';
import ExportOnyxState from '@libs/ExportOnyxState';
import Navigation from '@libs/Navigation/Navigation';
import * as App from '@userActions/App';
@@ -39,14 +39,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 +48,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 +102,7 @@ function TroubleshootPage({shouldStoreLogs, shouldMaskOnyxState}: TroubleshootPa
Navigation.goBack(ROUTES.SETTINGS)}
icon={Illustrations.Lightbulb}
/>
@@ -157,8 +154,20 @@ function TroubleshootPage({shouldStoreLogs, shouldMaskOnyxState}: TroubleshootPa
isVisible={isConfirmationModalVisible}
onConfirm={() => {
setIsConfirmationModalVisible(false);
+ // Requests in a sequential queue should be called even if the Onyx state is reset, so we do not lose any pending data.
+ // However, the OpenApp request must be called before any other request in a queue to ensure data consistency.
+ // To do that, sequential queue is cleared together with other keys, and then it's restored once the OpenApp request is resolved.
+ const sequentialQueue = PersistedRequests.getAll();
Onyx.clear(App.KEYS_TO_PRESERVE).then(() => {
- App.openApp();
+ App.openApp().then(() => {
+ if (!sequentialQueue) {
+ return;
+ }
+
+ sequentialQueue.forEach((request) => {
+ PersistedRequests.save(request);
+ });
+ });
});
}}
onCancel={() => setIsConfirmationModalVisible(false)}
@@ -176,12 +185,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 cf13c29ffb2..3ef77458de1 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/tasks/NewTaskDescriptionPage.tsx b/src/pages/tasks/NewTaskDescriptionPage.tsx
index f18829e8c80..0132442b597 100644
--- a/src/pages/tasks/NewTaskDescriptionPage.tsx
+++ b/src/pages/tasks/NewTaskDescriptionPage.tsx
@@ -34,14 +34,15 @@ type NewTaskDescriptionPageOnyxProps = {
type NewTaskDescriptionPageProps = NewTaskDescriptionPageOnyxProps & StackScreenProps;
-function NewTaskDescriptionPage({task}: NewTaskDescriptionPageProps) {
+function NewTaskDescriptionPage({task, route}: NewTaskDescriptionPageProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const {inputCallbackRef, inputRef} = useAutoFocusInput();
+ const goBack = () => Navigation.goBack(ROUTES.NEW_TASK.getRoute(route.params?.backTo));
const onSubmit = (values: FormOnyxValues) => {
TaskActions.setDescriptionValue(values.taskDescription);
- Navigation.goBack(ROUTES.NEW_TASK);
+ goBack();
};
const validate = (values: FormOnyxValues): FormInputErrors => {
@@ -63,8 +64,7 @@ function NewTaskDescriptionPage({task}: NewTaskDescriptionPageProps) {
<>
TaskActions.dismissModalAndClearOutTaskInfo()}
- onBackButtonPress={() => Navigation.goBack(ROUTES.NEW_TASK)}
+ onBackButtonPress={goBack}
/>
;
-function NewTaskDetailsPage({task}: NewTaskDetailsPageProps) {
+function NewTaskDetailsPage({task, route}: NewTaskDetailsPageProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const [taskTitle, setTaskTitle] = useState(task?.title ?? '');
@@ -42,6 +42,7 @@ function NewTaskDetailsPage({task}: NewTaskDetailsPageProps) {
const {inputCallbackRef} = useAutoFocusInput();
+ const backTo = route.params?.backTo;
const skipConfirmation = task?.skipConfirmation && task?.assigneeAccountID && task?.parentReportID;
const buttonText = skipConfirmation ? translate('newTaskPage.assignTask') : translate('common.next');
@@ -84,7 +85,7 @@ function NewTaskDetailsPage({task}: NewTaskDetailsPageProps) {
task.assigneeChatReport,
);
} else {
- Navigation.navigate(ROUTES.NEW_TASK);
+ Navigation.navigate(ROUTES.NEW_TASK.getRoute(backTo));
}
};
@@ -96,9 +97,8 @@ function NewTaskDetailsPage({task}: NewTaskDetailsPageProps) {
>
TaskActions.dismissModalAndClearOutTaskInfo()}
shouldShowBackButton
- onBackButtonPress={() => TaskActions.dismissModalAndClearOutTaskInfo()}
+ onBackButtonPress={() => TaskActions.dismissModalAndClearOutTaskInfo(backTo)}
/>
;
-function NewTaskPage({task, reports, personalDetails}: NewTaskPageProps) {
+function NewTaskPage({task, reports, personalDetails, route}: NewTaskPageProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const [assignee, setAssignee] = useState();
@@ -62,6 +62,7 @@ function NewTaskPage({task, reports, personalDetails}: NewTaskPageProps) {
const {paddingBottom} = useStyledSafeAreaInsets();
+ const backTo = route.params?.backTo;
const confirmButtonRef = useRef(null);
const focusTimeoutRef = useRef(null);
useFocusEffect(
@@ -153,10 +154,9 @@ function NewTaskPage({task, reports, personalDetails}: NewTaskPageProps) {
>
TaskActions.dismissModalAndClearOutTaskInfo()}
shouldShowBackButton
onBackButtonPress={() => {
- Navigation.goBack(ROUTES.NEW_TASK_DETAILS);
+ Navigation.goBack(ROUTES.NEW_TASK_DETAILS.getRoute(backTo));
}}
/>
{hasDestinationError && (
@@ -180,14 +180,14 @@ function NewTaskPage({task, reports, personalDetails}: NewTaskPageProps) {
Navigation.navigate(ROUTES.NEW_TASK_TITLE)}
+ onPress={() => Navigation.navigate(ROUTES.NEW_TASK_TITLE.getRoute(backTo))}
shouldShowRightIcon
rightLabel={translate('common.required')}
/>
Navigation.navigate(ROUTES.NEW_TASK_DESCRIPTION)}
+ onPress={() => Navigation.navigate(ROUTES.NEW_TASK_DESCRIPTION.getRoute(backTo))}
shouldShowRightIcon
shouldParseTitle
numberOfLinesTitle={2}
@@ -198,7 +198,7 @@ function NewTaskPage({task, reports, personalDetails}: NewTaskPageProps) {
title={assignee?.displayName ?? ''}
description={assignee?.displayName ? LocalePhoneNumber.formatPhoneNumber(assignee?.subtitle) : translate('task.assignee')}
icon={assignee?.icons}
- onPress={() => Navigation.navigate(ROUTES.NEW_TASK_ASSIGNEE)}
+ onPress={() => Navigation.navigate(ROUTES.NEW_TASK_ASSIGNEE.getRoute(backTo))}
shouldShowRightIcon
titleWithTooltips={assigneeTooltipDetails}
/>
diff --git a/src/pages/tasks/NewTaskTitlePage.tsx b/src/pages/tasks/NewTaskTitlePage.tsx
index cdc9fc27596..5afd919cac5 100644
--- a/src/pages/tasks/NewTaskTitlePage.tsx
+++ b/src/pages/tasks/NewTaskTitlePage.tsx
@@ -29,12 +29,13 @@ type NewTaskTitlePageOnyxProps = {
};
type NewTaskTitlePageProps = NewTaskTitlePageOnyxProps & StackScreenProps;
-function NewTaskTitlePage({task}: NewTaskTitlePageProps) {
+function NewTaskTitlePage({task, route}: NewTaskTitlePageProps) {
const styles = useThemeStyles();
const {inputCallbackRef} = useAutoFocusInput();
const {translate} = useLocalize();
+ const goBack = () => Navigation.goBack(ROUTES.NEW_TASK.getRoute(route.params?.backTo));
const validate = (values: FormOnyxValues): FormInputErrors => {
const errors = {};
@@ -52,7 +53,7 @@ function NewTaskTitlePage({task}: NewTaskTitlePageProps) {
// the response
const onSubmit = (values: FormOnyxValues) => {
TaskActions.setTitleValue(values.taskTitle);
- Navigation.goBack(ROUTES.NEW_TASK);
+ goBack();
};
return (
@@ -63,9 +64,8 @@ function NewTaskTitlePage({task}: NewTaskTitlePageProps) {
>
TaskActions.dismissModalAndClearOutTaskInfo()}
shouldShowBackButton
- onBackButtonPress={() => Navigation.goBack(ROUTES.NEW_TASK)}
+ onBackButtonPress={goBack}
/>
>();
const {translate} = useLocalize();
const session = useSession();
+ const backTo = route.params?.backTo;
const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT);
const [task] = useOnyx(ONYXKEYS.TASK);
const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false});
@@ -201,14 +202,14 @@ function TaskAssigneeSelectorModal() {
OptionsListUtils.isCurrentUser({...option, accountID: option?.accountID ?? -1, login: option?.login ?? undefined}),
);
InteractionManager.runAfterInteractions(() => {
- Navigation.goBack(ROUTES.NEW_TASK);
+ Navigation.goBack(ROUTES.NEW_TASK.getRoute(backTo));
});
}
},
- [session?.accountID, task?.shareDestination, report],
+ [session?.accountID, task?.shareDestination, report, backTo],
);
- const handleBackButtonPress = useCallback(() => (route.params?.reportID ? Navigation.dismissModal() : Navigation.goBack(ROUTES.NEW_TASK)), [route.params]);
+ const handleBackButtonPress = useCallback(() => Navigation.goBack(!route.params?.reportID ? ROUTES.NEW_TASK.getRoute(backTo) : backTo), [route.params, backTo]);
const isOpen = ReportUtils.isOpenTaskReport(report);
const canModifyTask = TaskActions.canModifyTask(report, currentUserPersonalDetails.accountID);
diff --git a/src/pages/tasks/TaskDescriptionPage.tsx b/src/pages/tasks/TaskDescriptionPage.tsx
index 230185f2b4f..acd1329b26e 100644
--- a/src/pages/tasks/TaskDescriptionPage.tsx
+++ b/src/pages/tasks/TaskDescriptionPage.tsx
@@ -1,4 +1,5 @@
-import {useFocusEffect} from '@react-navigation/native';
+import type {RouteProp} from '@react-navigation/native';
+import {useFocusEffect, useRoute} from '@react-navigation/native';
import React, {useCallback, useRef} from 'react';
import {View} from 'react-native';
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
@@ -15,6 +16,7 @@ import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import * as ErrorUtils from '@libs/ErrorUtils';
import Navigation from '@libs/Navigation/Navigation';
+import type {ReportDescriptionNavigatorParamList} from '@libs/Navigation/types';
import Parser from '@libs/Parser';
import * as ReportUtils from '@libs/ReportUtils';
import updateMultilineInputRange from '@libs/updateMultilineInputRange';
@@ -24,12 +26,14 @@ import variables from '@styles/variables';
import * as Task from '@userActions/Task';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
+import type SCREENS from '@src/SCREENS';
import INPUT_IDS from '@src/types/form/EditTaskForm';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
type TaskDescriptionPageProps = WithReportOrNotFoundProps & WithCurrentUserPersonalDetailsProps;
function TaskDescriptionPage({report, currentUserPersonalDetails}: TaskDescriptionPageProps) {
+ const route = useRoute>();
const styles = useThemeStyles();
const {translate} = useLocalize();
@@ -95,7 +99,10 @@ function TaskDescriptionPage({report, currentUserPersonalDetails}: TaskDescripti
testID={TaskDescriptionPage.displayName}
>
-
+ Navigation.goBack(route.params.backTo)}
+ />
{
}
Task.setShareDestinationValue(optionItem?.reportID);
- Navigation.goBack(ROUTES.NEW_TASK);
+ Navigation.goBack(ROUTES.NEW_TASK.getRoute());
};
const reportFilter = (reportOptions: Array>) =>
@@ -130,7 +130,7 @@ function TaskShareDestinationSelectorModal() {
<>
Navigation.goBack(ROUTES.NEW_TASK)}
+ onBackButtonPress={() => Navigation.goBack(ROUTES.NEW_TASK.getRoute())}
/>
>();
const styles = useThemeStyles();
const {translate} = useLocalize();
@@ -79,7 +84,10 @@ function TaskTitlePage({report, currentUserPersonalDetails}: TaskTitlePageProps)
>
{({didScreenTransitionEnd}) => (
-
+ Navigation.goBack(route.params.backTo)}
+ />
(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)),
@@ -563,6 +565,10 @@ function WorkspaceMembersPage({personalDetails, route, policy, currentUserPerson
};
const threeDotsMenuItems = useMemo(() => {
+ if (!isPolicyAdmin) {
+ return [];
+ }
+
const menuItems = [
{
icon: Expensicons.Table,
@@ -583,13 +589,18 @@ function WorkspaceMembersPage({personalDetails, route, policy, currentUserPerson
Modal.close(() => setIsOfflineModalVisible(true));
return;
}
- Member.downloadMembersCSV(policyID);
+
+ Modal.close(() => {
+ Member.downloadMembersCSV(policyID, () => {
+ setIsDownloadFailureModalVisible(true);
+ });
+ });
},
},
];
return menuItems;
- }, [policyID, translate, isOffline]);
+ }, [policyID, translate, isOffline, isPolicyAdmin]);
const selectionModeHeader = selectionMode?.isEnabled && shouldUseNarrowLayout;
@@ -603,7 +614,7 @@ function WorkspaceMembersPage({personalDetails, route, policy, currentUserPerson
testID={WorkspaceMembersPage.displayName}
shouldShowLoading={false}
shouldShowOfflineIndicatorInWideScreen
- shouldShowThreeDotsButton
+ shouldShowThreeDotsButton={isPolicyAdmin}
threeDotsMenuItems={threeDotsMenuItems}
threeDotsAnchorPosition={styles.threeDotsPopoverOffsetNoCloseButton(windowWidth)}
shouldShowNonAdmin
@@ -646,6 +657,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/categories/ImportCategoriesPage.tsx b/src/pages/workspace/categories/ImportCategoriesPage.tsx
index c6e5439ec96..d0eecc54ab9 100644
--- a/src/pages/workspace/categories/ImportCategoriesPage.tsx
+++ b/src/pages/workspace/categories/ImportCategoriesPage.tsx
@@ -1,7 +1,10 @@
import type {StackScreenProps} from '@react-navigation/stack';
import React from 'react';
import ImportSpreedsheet from '@components/ImportSpreadsheet';
+import usePolicy from '@hooks/usePolicy';
import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
+import * as PolicyUtils from '@libs/PolicyUtils';
+import NotFoundPage from '@pages/ErrorPage/NotFoundPage';
import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
@@ -9,6 +12,12 @@ type ImportCategoriesPageProps = StackScreenProps;
+ }
return (
;
+ }
+
const spreadsheetColumns = spreadsheet?.data;
if (!spreadsheetColumns) {
return;
diff --git a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx
index f82591a4fc0..929870d6528 100644
--- a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx
+++ b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx
@@ -302,8 +302,9 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) {
);
const threeDotsMenuItems = useMemo(() => {
- const menuItems = [
- {
+ const menuItems = [];
+ if (!PolicyUtils.hasAccountingConnections(policy)) {
+ menuItems.push({
icon: Expensicons.Table,
text: translate('spreadsheet.importSpreadsheet'),
onSelected: () => {
@@ -313,8 +314,8 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) {
}
Navigation.navigate(ROUTES.WORKSPACE_CATEGORIES_IMPORT.getRoute(policyId));
},
- },
- ];
+ });
+ }
if (hasVisibleCategories) {
menuItems.push({
icon: Expensicons.Download,
@@ -334,7 +335,7 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) {
}
return menuItems;
- }, [policyId, translate, isOffline, hasVisibleCategories]);
+ }, [policyId, translate, isOffline, hasVisibleCategories, policy]);
const selectionModeHeader = selectionMode?.isEnabled && shouldUseNarrowLayout;
diff --git a/src/pages/workspace/companyCards/assignCard/AssigneeStep.tsx b/src/pages/workspace/companyCards/assignCard/AssigneeStep.tsx
index 5be83784039..a57395d69ce 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 cfdb09e3df8..9c53889d651 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 7bc4f9d9e60..8ccf6e61eaa 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 4eef95267a4..e1fdd2b2968 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 4fd11d62968..d4fbbae52d1 100644
--- a/src/styles/index.ts
+++ b/src/styles/index.ts
@@ -5132,16 +5132,11 @@ const styles = (theme: ThemeColors) =>
height: '100%',
},
- emptyStateScrollView: {
- height: '100%',
- flex: 1,
- },
-
emptyStateForeground: {
margin: 32,
justifyContent: 'center',
alignItems: 'center',
- flex: 1,
+ flexGrow: 1,
},
emptyStateContent: {
diff --git a/tests/perf-test/ReportScreen.perf-test.tsx b/tests/perf-test/ReportScreen.perf-test.tsx
index 8b1d2d5de40..95ac9729e60 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', () => ({
diff --git a/tests/ui/PaginationTest.tsx b/tests/ui/PaginationTest.tsx
index 9d120433daa..a7ce0632b60 100644
--- a/tests/ui/PaginationTest.tsx
+++ b/tests/ui/PaginationTest.tsx
@@ -339,6 +339,7 @@ describe('Pagination', () => {
// Simulate the maintainVisibleContentPosition scroll adjustment, so it is now possible to scroll down more.
scrollToOffset(500);
+ await waitForBatchedUpdatesWithAct();
scrollToOffset(0);
await waitForBatchedUpdatesWithAct();
@@ -354,6 +355,7 @@ describe('Pagination', () => {
mockGetNewerActions(0);
scrollToOffset(500);
+ await waitForBatchedUpdatesWithAct();
scrollToOffset(0);
await waitForBatchedUpdatesWithAct();
diff --git a/tests/unit/DateUtilsTest.ts b/tests/unit/DateUtilsTest.ts
index f3abed2e337..52ef39ed4da 100644
--- a/tests/unit/DateUtilsTest.ts
+++ b/tests/unit/DateUtilsTest.ts
@@ -132,6 +132,7 @@ describe('DateUtils', () => {
it('canUpdateTimezone should return false when lastUpdatedTimezoneTime is less than 5 minutes ago', () => {
// Use fake timers to control the current time
jest.useFakeTimers();
+ DateUtils.setTimezoneUpdated();
jest.setSystemTime(addMinutes(new Date(), 4));
const isUpdateTimezoneAllowed = DateUtils.canUpdateTimezone();
expect(isUpdateTimezoneAllowed).toBe(false);
diff --git a/tests/unit/markPullRequestsAsDeployedTest.ts b/tests/unit/markPullRequestsAsDeployedTest.ts
index 4d491db2e53..45fa83a3673 100644
--- a/tests/unit/markPullRequestsAsDeployedTest.ts
+++ b/tests/unit/markPullRequestsAsDeployedTest.ts
@@ -84,6 +84,9 @@ function mockGetInputDefaultImplementation(key: string): boolean | string {
case 'DESKTOP':
case 'WEB':
return 'success';
+ case 'DATE':
+ case 'NOTE':
+ return '';
default:
throw new Error('Trying to access invalid input');
}