diff --git a/.github/actions/javascript/markPullRequestsAsDeployed/action.yml b/.github/actions/javascript/markPullRequestsAsDeployed/action.yml
index f0ca77bdbf00..40dfc05e5448 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 9f97e4a72d20..b38b04141395 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 71a5c7d5c6ee..e6424c89833a 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 2ab19d13183a..53afe03720f7 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 000000000000..3893d3cf3f7c
--- /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/__mocks__/react-native-haptic-feedback.ts b/__mocks__/react-native-haptic-feedback.ts
new file mode 100644
index 000000000000..6d20b410d835
--- /dev/null
+++ b/__mocks__/react-native-haptic-feedback.ts
@@ -0,0 +1,5 @@
+import type HapticFeedback from 'react-native-haptic-feedback';
+
+const RNHapticFeedback: typeof HapticFeedback = {trigger: jest.fn()};
+
+export default RNHapticFeedback;
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 0594d6afc211..833f8290e5e6 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 1009003904
- versionName "9.0.39-4"
+ versionCode 1009004001
+ versionName "9.0.40-1"
// 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/gradle.properties b/android/gradle.properties
index 87333d20f743..46cd98554d29 100644
--- a/android/gradle.properties
+++ b/android/gradle.properties
@@ -55,3 +55,5 @@ MYAPP_UPLOAD_KEY_ALIAS=ReactNativeChat-Key-Alias
disableFrameProcessors=true
android.nonTransitiveRClass=false
+
+org.gradle.parallel=true
diff --git a/docs/articles/new-expensify/workspaces/Add-approvals.md b/docs/articles/new-expensify/workspaces/Add-approvals.md
new file mode 100644
index 000000000000..5d8c1f733287
--- /dev/null
+++ b/docs/articles/new-expensify/workspaces/Add-approvals.md
@@ -0,0 +1,73 @@
+---
+title: Add approvals
+description: Add approvals to your workspace to require additional approval before authorizing payments.
+---
+
+
+# Add approvals
+
+Each Expensify workspace can be configured to require additional approvals before payments are authorized. Once approvals are enabled on a workspace, admins will be able to set a default approval workflow to apply to all members of the workspace, as well as set custom approval workflows for specific members.
+
+When workspace members submit expenses, the expenses will require approval from each approver in their approval workflow before payment is authorized.
+
+## Add approvals on a workspace
+
+**To enable Add approvals on a workspace you are an admin on:**
+
+1. Click your profile image or icon in the bottom left menu
+2. Click **Workspaces** in the left menu
+3. Select the workspace where you want to add approvals
+4. Click **Workflows** in the left menu
+5. Click the toggle next to **Add approvals**
+
+Toggling on **Add approvals** will reveal an option to set a default approval workflow.
+
+## Configure approval workflows
+
+**To configure the default approval workflow for the workspace:**
+
+1. Click your profile image or icon in the bottom left menu
+2. Click **Workspaces** in the left menu
+3. Select the workspace where you want to set the approval workflow
+4. Click **Workflows** in the left menu
+5. Under **Expenses from Everyone**, click on **First approver**
+6. Select the workspace member who should be the first approver in the approval workflow
+7. Under **Additional approver**, continue selecting workspace members until all the desired approvers are listed
+8. Click **Save**
+
+Note: When Add approvals is enabled, the workspace must have a default approval workflow.
+
+**To set an approval workflow that applies only to specific workspace members:**
+
+1. Click your profile image or icon in the bottom left menu
+2. Click **Workspaces** in the left menu
+3. Select the workspace where you want to add approvals
+4. Click **Workflows** in the left menu
+5. Under **Add approvals**, click on **Add approval workflow**
+6. Choose the workspace member whose expenses should go through the custom approval workfow
+7. Click **Next**
+8. Choose the workspace member who should be the first approver on submitted expenses in the approval workflow
+9. Click **Next**
+10. Click **Additional approver** to continue selecting workspace members until all the desired approvers are listed
+11. Click **Add workflow** to save it
+
+## Edit or delete approval workflows
+
+**To edit an approval workflow:**
+
+1. On the **Workflows** page, click the approval workflow that should be edited
+2. Click on the Approver field for the approval level where the edit should be made
+3. Choose the workspace member who should be set as the approver for that level, or deselect them to remove the approval level from the workflow
+4. Click **Save**
+
+**To delete an approval workflow:**
+
+1. On the **Workflows** page, click the approval workflow that shoudld be deleted
+2. Click **Delete**
+3. In the window that appears,click **Delete** again
+
+# FAQ
+
+## Can an employee have more than one approval workflow?
+No, each employee can have only one approval workflow
+
diff --git a/docs/articles/new-expensify/workspaces/Set-distance-rates.md b/docs/articles/new-expensify/workspaces/Set-distance-rates.md
new file mode 100644
index 000000000000..c434f34d2cef
--- /dev/null
+++ b/docs/articles/new-expensify/workspaces/Set-distance-rates.md
@@ -0,0 +1,50 @@
+---
+title: Set Distance Rates
+description: Set distance rates on your Expensify workspace
+---
+
+
+# Set Distance eates
+
+Each Expensify workspace can be configured with one or more distance rates. Once distance rates are enabled on your workspace, employees will be able to choose between the available rates to create distance expenses.
+
+## Enable distance rates on a workspace
+
+**To enable distance rates on a workspace you are an admin on:**
+
+1. Click your profile image or icon in the bottom left menu
+2. Click **Workspaces** in the left menu
+3. Select the workspace where you want to enable distance rates
+4. Click **More features** in the left menu
+5. Click the toggle next to **Distance rates**
+
+After toggling on distance rates, you will see a new **Distance rates** option in the left menu.
+
+## Add, delete, or edit distance rates
+
+**To add a distance rate:**
+
+1. Click your profile image or icon in the bottom left menu
+2. Click **Workspaces** in the left menu
+3. Select the workspace where you want to add distance rates
+4. Click **Distance rates** in the left menu
+5. Click **Add rate** in the top right
+6. Enter a value, then click **Save**
+
+**To enable, disable, edit or delete a single distance rate:**
+
+1. Click the distance rate on the **Distance rates** settings page
+2. To enable or disable the distance rate, click the toggle next to **Enable rate**, then click **Save**
+3. To edit the rate amount, click on the amount field, enter the new value, then click **Save**
+4. To permanently delete the distance rate, click **Delete**
+
+Note: When Distance rates is enabled, the workspace must have at least one enabled distance rate.
+
+**To enable, disable, edit or delete distance rates in bulk:**
+
+1. On the **Distance rates** settings page, click the checkboxes next to the distance rates that should me modified
+2. Click βx selectedβ at the top right
+3. To enable or disable all the selected distance rates, click **Enable rates** or **Disable rates**
+4. To permanently delete the distance rates, click **Delete rates**
+
+Note: When Distance rates are enabled, the workspace must have at least one enabled distance rate.
diff --git a/ios/NewApp_AdHoc.mobileprovision.gpg b/ios/NewApp_AdHoc.mobileprovision.gpg
index 78fb5d53d9e9..d2f181f6b7f4 100644
Binary files a/ios/NewApp_AdHoc.mobileprovision.gpg and b/ios/NewApp_AdHoc.mobileprovision.gpg differ
diff --git a/ios/NewApp_AdHoc_Notification_Service.mobileprovision.gpg b/ios/NewApp_AdHoc_Notification_Service.mobileprovision.gpg
index ac2c76a118d5..c0afa40ecb29 100644
Binary files a/ios/NewApp_AdHoc_Notification_Service.mobileprovision.gpg and b/ios/NewApp_AdHoc_Notification_Service.mobileprovision.gpg differ
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index 5f6051c85745..4eeb658f3347 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.4
+
9.0.40.1
FullStory
OrgId
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index c29a15f26438..7dc1b1416139 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.4
+ 9.0.40.1
diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist
index 2b43cf12b38a..83fa9ece1deb 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.4
+
9.0.40.1
NSExtension
NSExtensionPointIdentifier
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index a801a7c4de1c..beac64acd083 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -2451,7 +2451,7 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- - RNReactNativeHapticFeedback (2.3.1):
+ - RNReactNativeHapticFeedback (2.3.3):
- DoubleConversion
- glog
- hermes-engine
@@ -3233,7 +3233,7 @@ SPEC CHECKSUMS:
RNLocalize: d4b8af4e442d4bcca54e68fc687a2129b4d71a81
rnmapbox-maps: 460d6ff97ae49c7d5708c3212c6521697c36a0c4
RNPermissions: 0b1429b55af59d1d08b75a8be2459f65a8ac3f28
- RNReactNativeHapticFeedback: 31833c3ef341d716dbbd9d64e940f0c230db46f6
+ RNReactNativeHapticFeedback: 73756a3477a5a622fa16862a3ab0d0fc5e5edff5
RNReanimated: 76901886830e1032f16bbf820153f7dc3f02d51d
RNScreens: de6e57426ba0e6cbc3fb5b4f496e7f08cb2773c2
RNShare: bd4fe9b95d1ee89a200778cc0753ebe650154bb0
diff --git a/package-lock.json b/package-lock.json
index c1896b581645..6af9c1981bc8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "9.0.39-4",
+ "version": "9.0.40-1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "9.0.39-4",
+ "version": "9.0.40-1",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
@@ -85,7 +85,7 @@
"react-native-fs": "^2.20.0",
"react-native-gesture-handler": "2.18.0",
"react-native-google-places-autocomplete": "2.5.6",
- "react-native-haptic-feedback": "^2.3.1",
+ "react-native-haptic-feedback": "^2.3.3",
"react-native-image-picker": "^7.0.3",
"react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#cb392140db4953a283590d7cf93b4d0461baa2a9",
"react-native-key-command": "^1.0.8",
@@ -34543,9 +34543,9 @@
}
},
"node_modules/react-native-haptic-feedback": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/react-native-haptic-feedback/-/react-native-haptic-feedback-2.3.1.tgz",
- "integrity": "sha512-dPfjV4iVHfhVyfG+nRd88ygjahbdup7KFZDM5L2aNIAzqbNtKxHZn5O1pHegwSj1t15VJliu0GyTX7XpBDeXUw==",
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/react-native-haptic-feedback/-/react-native-haptic-feedback-2.3.3.tgz",
+ "integrity": "sha512-svS4D5PxfNv8o68m9ahWfwje5NqukM3qLS48+WTdhbDkNUkOhP9rDfDSRHzlhk4zq+ISjyw95EhLeh8NkKX5vQ==",
"workspaces": [
"example"
],
diff --git a/package.json b/package.json
index 577eac08c6d5..deff4054ce01 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "9.0.39-4",
+ "version": "9.0.40-1",
"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.",
@@ -142,7 +142,7 @@
"react-native-fs": "^2.20.0",
"react-native-gesture-handler": "2.18.0",
"react-native-google-places-autocomplete": "2.5.6",
- "react-native-haptic-feedback": "^2.3.1",
+ "react-native-haptic-feedback": "^2.3.3",
"react-native-image-picker": "^7.0.3",
"react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#cb392140db4953a283590d7cf93b4d0461baa2a9",
"react-native-key-command": "^1.0.8",
diff --git a/patches/react-native-haptic-feedback+2.3.1.patch b/patches/react-native-haptic-feedback+2.3.1.patch
deleted file mode 100644
index 799bdaf7e53e..000000000000
--- a/patches/react-native-haptic-feedback+2.3.1.patch
+++ /dev/null
@@ -1,56 +0,0 @@
-diff --git a/node_modules/react-native-haptic-feedback/ios/RNHapticFeedback/RNHapticFeedback.h b/node_modules/react-native-haptic-feedback/ios/RNHapticFeedback/RNHapticFeedback.h
-index c1498b9..250df1f 100644
---- a/node_modules/react-native-haptic-feedback/ios/RNHapticFeedback/RNHapticFeedback.h
-+++ b/node_modules/react-native-haptic-feedback/ios/RNHapticFeedback/RNHapticFeedback.h
-@@ -1,5 +1,5 @@
- #ifdef RCT_NEW_ARCH_ENABLED
--#import "RNHapticFeedbackSpec.h"
-+#import
-
- @interface RNHapticFeedback : NSObject
- #else
-diff --git a/node_modules/react-native-haptic-feedback/ios/RNHapticFeedback/RNHapticFeedbackSpec.h b/node_modules/react-native-haptic-feedback/ios/RNHapticFeedback/RNHapticFeedbackSpec.h
-deleted file mode 100644
-index 6f0f81d..0000000
---- a/node_modules/react-native-haptic-feedback/ios/RNHapticFeedback/RNHapticFeedbackSpec.h
-+++ /dev/null
-@@ -1,15 +0,0 @@
--//
--// RNHapticFeedbackSpec.h
--// RNHapticFeedback
--//
--// Created by Michael Kuczera on 05.08.24.
--// Copyright Β© 2024 Facebook. All rights reserved.
--//
--#import
--
--@protocol NativeHapticFeedbackSpec
--
--// Indicates whether the device supports haptic feedback
--- (Boolean)supportsHaptic;
--
--@end
-diff --git a/node_modules/react-native-haptic-feedback/package.json b/node_modules/react-native-haptic-feedback/package.json
-index 86dfaa4..9cec8e4 100644
---- a/node_modules/react-native-haptic-feedback/package.json
-+++ b/node_modules/react-native-haptic-feedback/package.json
-@@ -6,18 +6,7 @@
- "source": "src/index.ts",
- "main": "./lib/commonjs/index.js",
- "module": "./lib/module/index.js",
-- "exports": {
-- ".": {
-- "import": {
-- "types": "./lib/typescript/module/src/index.d.ts",
-- "default": "./lib/module/index.js"
-- },
-- "require": {
-- "types": "./lib/typescript/commonjs/src/index.d.ts",
-- "default": "./lib/commonjs/index.js"
-- }
-- }
-- },
-+ "types": "./lib/typescript/module/src/index.d.ts",
- "scripts": {
- "typecheck": "tsc --noEmit --project tsconfig.test.json",
- "test": "jest",
diff --git a/src/CONST.ts b/src/CONST.ts
index 9ee9ec4d9147..7d0cd3c4cf54 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -171,7 +171,7 @@ const CONST = {
},
// Note: Group and Self-DM excluded as these are not tied to a Workspace
- WORKSPACE_ROOM_TYPES: [chatTypes.POLICY_ADMINS, chatTypes.POLICY_ANNOUNCE, chatTypes.DOMAIN_ALL, chatTypes.POLICY_ROOM, chatTypes.POLICY_EXPENSE_CHAT],
+ WORKSPACE_ROOM_TYPES: [chatTypes.POLICY_ADMINS, chatTypes.POLICY_ANNOUNCE, chatTypes.DOMAIN_ALL, chatTypes.POLICY_ROOM, chatTypes.POLICY_EXPENSE_CHAT, chatTypes.INVOICE],
ANDROID_PACKAGE_NAME,
WORKSPACE_ENABLE_FEATURE_REDIRECT_DELAY: 100,
ANIMATED_HIGHLIGHT_ENTRY_DELAY: 50,
@@ -2097,6 +2097,9 @@ const CONST = {
ACCESS_VARIANTS: {
CREATE: 'create',
},
+ PAGE_INDEX: {
+ CONFIRM: 'confirm',
+ },
PAYMENT_SELECTED: {
BBA: 'BBA',
PBA: 'PBA',
@@ -4556,7 +4559,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/ROUTES.ts b/src/ROUTES.ts
index c0ec944b71e1..2b959ba4ddba 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,12 @@ 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}/${encodeURIComponent(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 +301,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 +321,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 +407,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?',
@@ -495,6 +496,10 @@ const ROUTES = {
getRoute: (action: IOUAction, iouType: IOUType, orderWeight: number, transactionID: string, reportID: string, backTo = '', reportActionID?: string) =>
getUrlWithBackToParam(`${action as string}/${iouType as string}/tag/${orderWeight}/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}`, backTo),
},
+ SETTINGS_TAGS_ROOT: {
+ route: 'settings/:policyID/tags',
+ getRoute: (policyID: string, backTo = '') => getUrlWithBackToParam(`settings/${policyID}/tags`, backTo),
+ },
MONEY_REQUEST_STEP_WAYPOINT: {
route: ':action/:iouType/waypoint/:transactionID/:reportID/:pageIndex',
getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID?: string, pageIndex = '', backTo = '') =>
@@ -536,12 +541,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',
@@ -1097,7 +1117,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',
@@ -1126,39 +1149,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 920bd48dd42e..395f1c4d5fb1 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -244,6 +244,8 @@ const SCREENS = {
SETTINGS_CATEGORIES_ROOT: 'Settings_Categories',
},
+ SETTINGS_TAGS_ROOT: 'Settings_Tags',
+
REPORT_SETTINGS: {
ROOT: 'Report_Settings_Root',
NAME: 'Report_Settings_Name',
diff --git a/src/components/AccountSwitcherSkeletonView/index.tsx b/src/components/AccountSwitcherSkeletonView/index.tsx
index 3faf7e563f3c..379a4094e032 100644
--- a/src/components/AccountSwitcherSkeletonView/index.tsx
+++ b/src/components/AccountSwitcherSkeletonView/index.tsx
@@ -22,7 +22,7 @@ function AccountSwitcherSkeletonView({shouldAnimate = true, avatarSize = CONST.A
const StyleUtils = useStyleUtils();
const avatarPlaceholderSize = StyleUtils.getAvatarSize(avatarSize);
const avatarPlaceholderRadius = avatarPlaceholderSize / 2;
- const startPositionX = 30;
+ const startPositionX = avatarPlaceholderRadius;
return (
diff --git a/src/components/AvatarWithDisplayName.tsx b/src/components/AvatarWithDisplayName.tsx
index 1cd1bfb36d83..07845eca37ba 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/Button/validateSubmitShortcut/index.ts b/src/components/Button/validateSubmitShortcut/index.ts
index f8cea44f73d6..29ba071c25f2 100644
--- a/src/components/Button/validateSubmitShortcut/index.ts
+++ b/src/components/Button/validateSubmitShortcut/index.ts
@@ -11,7 +11,7 @@ import type ValidateSubmitShortcut from './types';
const validateSubmitShortcut: ValidateSubmitShortcut = (isDisabled, isLoading, event) => {
const eventTarget = event?.target as HTMLElement;
- if (isDisabled || isLoading || eventTarget.nodeName === 'TEXTAREA') {
+ if (isDisabled || isLoading || eventTarget.nodeName === 'TEXTAREA' || (eventTarget?.contentEditable === 'true' && eventTarget.ariaMultiLine)) {
return false;
}
diff --git a/src/components/EmptyStateComponent/index.tsx b/src/components/EmptyStateComponent/index.tsx
index 876f1a745403..8eb991b17b63 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 && (
;
type SharedProps = {
SkeletonComponent: ValidSkeletons;
title: string;
+ titleStyles?: StyleProp;
subtitle: string | React.ReactNode;
buttonText?: string;
buttonAction?: () => void;
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx
index 6acef20cd833..c66b8ae13ee9 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx
@@ -61,7 +61,7 @@ function MentionUserRenderer({style, tnode, TDefaultRenderer, currentUserPersona
accountID = parseInt(htmlAttribAccountID, 10);
mentionDisplayText = LocalePhoneNumber.formatPhoneNumber(user?.login ?? '') || PersonalDetailsUtils.getDisplayNameOrDefault(user);
mentionDisplayText = getShortMentionIfFound(mentionDisplayText, htmlAttributeAccountID, user?.login ?? '');
- navigationRoute = ROUTES.PROFILE.getRoute(htmlAttribAccountID);
+ navigationRoute = ROUTES.PROFILE.getRoute(htmlAttribAccountID, Navigation.getReportRHPActiveRoute());
} else if ('data' in tnodeClone && !isEmptyObject(tnodeClone.data)) {
// We need to remove the LTR unicode and leading @ from data as it is not part of the login
mentionDisplayText = tnodeClone.data.replace(CONST.UNICODE.LTR, '').slice(1);
@@ -69,7 +69,7 @@ function MentionUserRenderer({style, tnode, TDefaultRenderer, currentUserPersona
asMutable(tnodeClone).data = tnodeClone.data.replace(mentionDisplayText, Str.removeSMSDomain(getShortMentionIfFound(mentionDisplayText, htmlAttributeAccountID)));
accountID = PersonalDetailsUtils.getAccountIDsByLogins([mentionDisplayText])?.[0];
- navigationRoute = ROUTES.PROFILE.getRoute(accountID, undefined, mentionDisplayText);
+ navigationRoute = ROUTES.PROFILE.getRoute(accountID, Navigation.getReportRHPActiveRoute(), mentionDisplayText);
mentionDisplayText = Str.removeSMSDomain(mentionDisplayText);
} else {
// If neither an account ID or email is provided, don't render anything
@@ -94,7 +94,11 @@ function MentionUserRenderer({style, tnode, TDefaultRenderer, currentUserPersona
}}
onPress={(event) => {
event.preventDefault();
- Navigation.navigate(navigationRoute);
+ if (!isEmpty(htmlAttribAccountID)) {
+ Navigation.navigate(ROUTES.PROFILE.getRoute(htmlAttribAccountID, Navigation.getReportRHPActiveRoute()));
+ return;
+ }
+ Navigation.navigate(ROUTES.PROFILE.getRoute(accountID, Navigation.getReportRHPActiveRoute(), mentionDisplayText));
}}
role={CONST.ROLE.LINK}
accessibilityLabel={`/${navigationRoute}`}
diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx
index 353bacdc0a25..322f28aa246d 100644
--- a/src/components/LHNOptionsList/OptionRowLHN.tsx
+++ b/src/components/LHNOptionsList/OptionRowLHN.tsx
@@ -187,7 +187,7 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti
shiftHorizontal={variables.gbrTooltipShiftHorizontal}
shiftVertical={variables.composerTooltipShiftVertical}
wrapperStyle={styles.quickActionTooltipWrapper}
- onPressOverlay={() => User.dismissGBRTooltip()}
+ onHideTooltip={() => User.dismissGBRTooltip()}
>
diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx
index 2524658d6ffc..a0fd8511eb3a 100644
--- a/src/components/MenuItem.tsx
+++ b/src/components/MenuItem.tsx
@@ -330,6 +330,9 @@ type MenuItemBaseProps = {
/** Should selected item be marked with checkmark */
shouldShowSelectedItemCheck?: boolean;
+
+ /** Handles what to do when hiding the tooltip */
+ onHideTooltip?: () => void;
};
type MenuItemProps = (IconProps | AvatarProps | NoIcon) & MenuItemBaseProps;
@@ -428,6 +431,7 @@ function MenuItem(
tooltipShiftVertical = 0,
renderTooltipContent,
shouldShowSelectedItemCheck = false,
+ onHideTooltip,
}: MenuItemProps,
ref: PressableRef,
) {
@@ -559,6 +563,7 @@ function MenuItem(
shiftHorizontal={tooltipShiftHorizontal}
shiftVertical={tooltipShiftVertical}
shouldAutoDismiss
+ onHideTooltip={onHideTooltip}
>
diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx
index d5570cb18872..b1340531c7f2 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';
@@ -53,14 +54,12 @@ 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) {
+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}`);
@@ -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();
@@ -426,7 +425,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
danger
shouldEnableNewFocusManagement
/>
- {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 62e4f1e8b589..e6b957f16b60 100644
--- a/src/components/MoneyRequestConfirmationListFooter.tsx
+++ b/src/components/MoneyRequestConfirmationListFooter.tsx
@@ -299,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}
@@ -326,9 +326,7 @@ function MoneyRequestConfirmationListFooter({
title={iouComment}
description={translate('common.description')}
onPress={() => {
- Navigation.navigate(
- ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams(), reportActionID),
- );
+ Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRoute(), reportActionID));
}}
style={[styles.moneyRequestMenuItem]}
titleStyle={styles.flex1}
@@ -404,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}
@@ -428,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}
@@ -447,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}
@@ -472,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}
@@ -495,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}
/>
@@ -512,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 ab7004ce4d17..d2db257dc776 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';
@@ -35,14 +36,12 @@ 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) {
+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}${
@@ -107,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();
@@ -152,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(reportID ?? ''));
+ Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_PAGE.getRoute(reportID ?? '', Navigation.getReportRHPActiveRoute()));
}}
/>
)}
@@ -174,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(reportID ?? ''));
+ Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_PAGE.getRoute(reportID ?? '', Navigation.getReportRHPActiveRoute()));
}}
/>
@@ -188,7 +187,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, shouldUseNarrow
)}
- {shouldUseNarrowLayout && shouldShowHoldMenu && (
+ {isSmallScreenWidth && shouldShowHoldMenu && (
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 8546aa8165c9..19ee17bba3b4 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 15f9cee3705c..af54e2940d3f 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 f2460cae71ed..0a740e65747f 100644
--- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx
+++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx
@@ -51,25 +51,19 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject';
import type {MoneyRequestPreviewProps, PendingMessageProps} from './types';
function MoneyRequestPreviewContent({
- iouReport,
isBillSplit,
- session,
action,
- personalDetails,
- chatReport,
- transaction,
contextMenuAnchor,
chatReportID,
reportID,
onPreviewPressed,
containerStyles,
- walletTerms,
checkIfContextMenuActive = () => {},
shouldShowPendingConversionMessage = false,
isHovered = false,
isWhisper = false,
- transactionViolations,
shouldDisplayContextMenu = true,
+ iouReportID,
}: MoneyRequestPreviewProps) {
const theme = useTheme();
const styles = useThemeStyles();
@@ -78,6 +72,16 @@ function MoneyRequestPreviewContent({
const {windowWidth} = useWindowDimensions();
const route = useRoute>();
const {shouldUseNarrowLayout} = useResponsiveLayout();
+ const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST);
+ const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID || '-1'}`);
+ const [session] = useOnyx(ONYXKEYS.SESSION);
+ const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${iouReportID || '-1'}`);
+
+ const isMoneyRequestAction = ReportActionsUtils.isMoneyRequestAction(action);
+ const transactionID = isMoneyRequestAction ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID : '-1';
+ const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`);
+ const [walletTerms] = useOnyx(ONYXKEYS.WALLET_TERMS);
+ const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS);
const sessionAccountID = session?.accountID;
const managerID = iouReport?.managerID ?? -1;
@@ -283,24 +287,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/MoneyRequestPreview/index.tsx b/src/components/ReportActionItem/MoneyRequestPreview/index.tsx
index c01206f83f55..f902948b2cb5 100644
--- a/src/components/ReportActionItem/MoneyRequestPreview/index.tsx
+++ b/src/components/ReportActionItem/MoneyRequestPreview/index.tsx
@@ -1,44 +1,18 @@
import lodashIsEmpty from 'lodash/isEmpty';
import React from 'react';
-import {withOnyx} from 'react-native-onyx';
-import * as ReportActionsUtils from '@libs/ReportActionsUtils';
+import {useOnyx} from 'react-native-onyx';
import ONYXKEYS from '@src/ONYXKEYS';
import MoneyRequestPreviewContent from './MoneyRequestPreviewContent';
-import type {MoneyRequestPreviewOnyxProps, MoneyRequestPreviewProps} from './types';
+import type {MoneyRequestPreviewProps} from './types';
function MoneyRequestPreview(props: MoneyRequestPreviewProps) {
+ const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${props.iouReportID || '-1'}`);
// We should not render the component if there is no iouReport and it's not a split or track expense.
// Moved outside of the component scope to allow for easier use of hooks in the main component.
// eslint-disable-next-line react/jsx-props-no-spreading
- return lodashIsEmpty(props.iouReport) && !(props.isBillSplit || props.isTrackExpense) ? null : ;
+ return lodashIsEmpty(iouReport) && !(props.isBillSplit || props.isTrackExpense) ? null : ;
}
MoneyRequestPreview.displayName = 'MoneyRequestPreview';
-export default withOnyx({
- personalDetails: {
- key: ONYXKEYS.PERSONAL_DETAILS_LIST,
- },
- chatReport: {
- key: ({chatReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`,
- },
- iouReport: {
- key: ({iouReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`,
- },
- session: {
- key: ONYXKEYS.SESSION,
- },
- transaction: {
- key: ({action}) => {
- const isMoneyRequestAction = ReportActionsUtils.isMoneyRequestAction(action);
- const transactionID = isMoneyRequestAction ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID : 0;
- return `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`;
- },
- },
- walletTerms: {
- key: ONYXKEYS.WALLET_TERMS,
- },
- transactionViolations: {
- key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS,
- },
-})(MoneyRequestPreview);
+export default MoneyRequestPreview;
diff --git a/src/components/ReportActionItem/MoneyRequestPreview/types.ts b/src/components/ReportActionItem/MoneyRequestPreview/types.ts
index 021ae5d188d9..c40b45c6d2bd 100644
--- a/src/components/ReportActionItem/MoneyRequestPreview/types.ts
+++ b/src/components/ReportActionItem/MoneyRequestPreview/types.ts
@@ -1,33 +1,9 @@
import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native';
-import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import type {ContextMenuAnchor} from '@pages/home/report/ContextMenu/ReportActionContextMenu';
import type * as OnyxTypes from '@src/types/onyx';
import type IconAsset from '@src/types/utils/IconAsset';
-type MoneyRequestPreviewOnyxProps = {
- /** All of the personal details for everyone */
- personalDetails: OnyxEntry;
-
- /** Chat report associated with iouReport */
- chatReport: OnyxEntry;
-
- /** IOU report data object */
- iouReport: OnyxEntry;
-
- /** Session info for the currently logged in user. */
- session: OnyxEntry;
-
- /** The transaction attached to the action.message.iouTransactionID */
- transaction: OnyxEntry;
-
- /** The transaction violations attached to the action.message.iouTransactionID */
- transactionViolations: OnyxCollection;
-
- /** Information about the user accepting the terms for payments */
- walletTerms: OnyxEntry;
-};
-
-type MoneyRequestPreviewProps = MoneyRequestPreviewOnyxProps & {
+type MoneyRequestPreviewProps = {
/** The active IOUReport, used for Onyx subscription */
// The iouReportID is used inside withOnyx HOC
// eslint-disable-next-line react/no-unused-prop-types
@@ -90,4 +66,4 @@ type PendingProps = {
type PendingMessageProps = PendingProps | NoPendingProps;
-export type {MoneyRequestPreviewProps, MoneyRequestPreviewOnyxProps, PendingMessageProps};
+export type {MoneyRequestPreviewProps, PendingMessageProps};
diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx
index 9559a19d08a6..549fd55ac1bb 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/TaskView.tsx b/src/components/ReportActionItem/TaskView.tsx
index 2822e34a3d04..9a2906aa7d62 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 96f705ea2d52..68f060d22e6c 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 704f72055410..023e7315e115 100644
--- a/src/components/Search/SearchPageHeader.tsx
+++ b/src/components/Search/SearchPageHeader.tsx
@@ -59,7 +59,7 @@ function HeaderWrapper({icon, title, subtitle, children, subtitleStyles = {}}: H
}
subtitle={
{subtitle}
diff --git a/src/components/Tooltip/BaseGenericTooltip/index.native.tsx b/src/components/Tooltip/BaseGenericTooltip/index.native.tsx
index e42f95874b42..f586c20cba49 100644
--- a/src/components/Tooltip/BaseGenericTooltip/index.native.tsx
+++ b/src/components/Tooltip/BaseGenericTooltip/index.native.tsx
@@ -34,7 +34,7 @@ function BaseGenericTooltip({
},
wrapperStyle = {},
shouldUseOverlay = false,
- onPressOverlay = () => {},
+ onHideTooltip = () => {},
}: BaseGenericTooltipProps) {
// The width of tooltip's inner content. Has to be undefined in the beginning
// as a width of 0 will cause the content to be rendered of a width of 0,
@@ -102,7 +102,7 @@ function BaseGenericTooltip({
return (
- {shouldUseOverlay && }
+ {shouldUseOverlay && }
{},
+ onHideTooltip = () => {},
}: BaseGenericTooltipProps) {
// The width of tooltip's inner content. Has to be undefined in the beginning
// as a width of 0 will cause the content to be rendered of a width of 0,
@@ -119,7 +119,7 @@ function BaseGenericTooltip({
return ReactDOM.createPortal(
<>
- {shouldUseOverlay && }
+ {shouldUseOverlay && }
void;
} & Pick<
SharedTooltipProps,
- 'renderTooltipContent' | 'maxWidth' | 'numberOfLines' | 'text' | 'shouldForceRenderingBelow' | 'wrapperStyle' | 'anchorAlignment' | 'shouldUseOverlay' | 'onPressOverlay'
+ 'renderTooltipContent' | 'maxWidth' | 'numberOfLines' | 'text' | 'shouldForceRenderingBelow' | 'wrapperStyle' | 'anchorAlignment' | 'shouldUseOverlay' | 'onHideTooltip'
>;
// eslint-disable-next-line import/prefer-default-export
diff --git a/src/components/Tooltip/EducationalTooltip/BaseEducationalTooltip.tsx b/src/components/Tooltip/EducationalTooltip/BaseEducationalTooltip.tsx
index d0ff254324ae..ef5327feba31 100644
--- a/src/components/Tooltip/EducationalTooltip/BaseEducationalTooltip.tsx
+++ b/src/components/Tooltip/EducationalTooltip/BaseEducationalTooltip.tsx
@@ -1,7 +1,9 @@
import React, {memo, useEffect, useRef, useState} from 'react';
import type {LayoutRectangle, NativeSyntheticEvent} from 'react-native';
+import {useOnyx} from 'react-native-onyx';
import GenericTooltip from '@components/Tooltip/GenericTooltip';
import type {EducationalTooltipProps} from '@components/Tooltip/types';
+import ONYXKEYS from '@src/ONYXKEYS';
import measureTooltipCoordinate from './measureTooltipCoordinate';
type LayoutChangeEventWithTarget = NativeSyntheticEvent<{layout: LayoutRectangle; target: HTMLElement}>;
@@ -10,11 +12,14 @@ type LayoutChangeEventWithTarget = NativeSyntheticEvent<{layout: LayoutRectangle
* A component used to wrap an element intended for displaying a tooltip.
* This tooltip would show immediately without user's interaction and hide after 5 seconds.
*/
-function BaseEducationalTooltip({children, shouldAutoDismiss = false, shouldRender = false, ...props}: EducationalTooltipProps) {
+function BaseEducationalTooltip({children, onHideTooltip, shouldAutoDismiss = false, ...props}: EducationalTooltipProps) {
const hideTooltipRef = useRef<() => void>();
const [shouldMeasure, setShouldMeasure] = useState(false);
const show = useRef<() => void>();
+ const [modal] = useOnyx(ONYXKEYS.MODAL);
+
+ const shouldShow = !modal?.willAlertModalBecomeVisible && !modal?.isVisible;
useEffect(
() => () => {
@@ -33,27 +38,38 @@ function BaseEducationalTooltip({children, shouldAutoDismiss = false, shouldRend
return;
}
- const timerID = setTimeout(hideTooltipRef.current, 5000);
+ // If the modal is open, hide the tooltip immediately and clear the timeout
+ if (!shouldShow) {
+ hideTooltipRef.current();
+ return;
+ }
+
+ // Automatically hide tooltip after 5 seconds if shouldAutoDismiss is true
+ const timerID = setTimeout(() => {
+ hideTooltipRef.current?.();
+ onHideTooltip?.();
+ }, 5000);
return () => {
clearTimeout(timerID);
};
- }, [shouldAutoDismiss]);
+ }, [shouldAutoDismiss, shouldShow, onHideTooltip]);
useEffect(() => {
- if (!shouldRender || !shouldMeasure) {
+ if (!shouldMeasure || !shouldShow) {
return;
}
// When tooltip is used inside an animated view (e.g. popover), we need to wait for the animation to finish before measuring content.
setTimeout(() => {
show.current?.();
}, 500);
- }, [shouldMeasure, shouldRender]);
+ }, [shouldMeasure, shouldShow]);
return (
{({showTooltip, hideTooltip, updateTargetBounds}) => {
// eslint-disable-next-line react-compiler/react-compiler
diff --git a/src/components/Tooltip/EducationalTooltip/index.tsx b/src/components/Tooltip/EducationalTooltip/index.tsx
index 03500f768dd9..a97e36a5904c 100644
--- a/src/components/Tooltip/EducationalTooltip/index.tsx
+++ b/src/components/Tooltip/EducationalTooltip/index.tsx
@@ -2,7 +2,11 @@ import React from 'react';
import type {TooltipExtendedProps} from '@components/Tooltip/types';
import BaseEducationalTooltip from './BaseEducationalTooltip';
-function EducationalTooltip({children, ...props}: TooltipExtendedProps) {
+function EducationalTooltip({children, shouldRender = false, ...props}: TooltipExtendedProps) {
+ if (!shouldRender) {
+ return children;
+ }
+
return (
{},
+ onHideTooltip = () => {},
}: GenericTooltipProps) {
const {preferredLocale} = useLocalize();
const {windowWidth} = useWindowDimensions();
@@ -150,8 +150,8 @@ function GenericTooltip({
}
setShouldUseOverlay(false);
hideTooltip();
- onPressOverlayProp();
- }, [shouldUseOverlay, onPressOverlayProp, hideTooltip]);
+ onHideTooltip();
+ }, [shouldUseOverlay, onHideTooltip, hideTooltip]);
useImperativeHandle(TooltipRefManager.ref, () => ({hideTooltip}), [hideTooltip]);
@@ -183,7 +183,7 @@ function GenericTooltip({
wrapperStyle={wrapperStyle}
anchorAlignment={anchorAlignment}
shouldUseOverlay={shouldUseOverlay}
- onPressOverlay={onPressOverlay}
+ onHideTooltip={onPressOverlay}
/>
)}
diff --git a/src/components/Tooltip/types.ts b/src/components/Tooltip/types.ts
index 0462b36fa524..0924f5d46a28 100644
--- a/src/components/Tooltip/types.ts
+++ b/src/components/Tooltip/types.ts
@@ -40,8 +40,8 @@ type SharedTooltipProps = {
/** Should render a fullscreen transparent overlay */
shouldUseOverlay?: boolean;
- /** Callback to fire when the transparent overlay is pressed */
- onPressOverlay?: () => void;
+ /** Handles what to do when hiding the tooltip */
+ onHideTooltip?: () => void;
};
type GenericTooltipState = {
diff --git a/src/hooks/useReviewDuplicatesNavigation.tsx b/src/hooks/useReviewDuplicatesNavigation.tsx
index e14731024c17..de905647e440 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 78824edb341a..2edcf5018470 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',
@@ -3172,6 +3174,7 @@ export default {
disableTags: 'Disable tags',
addTag: 'Add tag',
editTag: 'Edit tag',
+ editTags: 'Edit tags',
subtitle: 'Tags add more detailed ways to classify costs.',
emptyTags: {
title: "You haven't created any tags",
diff --git a/src/languages/es.ts b/src/languages/es.ts
index d205a4eee866..2c038e01c48d 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',
@@ -3221,6 +3223,7 @@ export default {
disableTags: 'Desactivar etiquetas',
addTag: 'AΓ±adir etiqueta',
editTag: 'Editar etiqueta',
+ editTags: 'Editar etiquetas',
subtitle: 'Las etiquetas aΓ±aden formas mΓ‘s detalladas de clasificar los costos.',
emptyTags: {
title: 'No has creado ninguna etiqueta',
diff --git a/src/libs/API/index.ts b/src/libs/API/index.ts
index 65fd2b6ad015..be1706886b1f 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/ResolveDuplicatesParams.ts b/src/libs/API/parameters/ResolveDuplicatesParams.ts
new file mode 100644
index 000000000000..d225f227c0d7
--- /dev/null
+++ b/src/libs/API/parameters/ResolveDuplicatesParams.ts
@@ -0,0 +1,24 @@
+type ResolveDuplicatesParams = {
+ /** The ID of the transaction that we want to keep */
+ transactionID: string;
+
+ /** The list of other duplicated transactions */
+ transactionIDList: string[];
+ created: string;
+ merchant: string;
+ amount: number;
+ currency: string;
+ category: string;
+ comment: string;
+ billable: boolean;
+ reimbursable: boolean;
+ tag: string;
+
+ /** The reportActionID of the dismissed violation action in the kept transaction thread report */
+ dismissedViolationReportActionID: string;
+
+ /** The ID list of the hold report actions corresponding to the transactionIDList */
+ reportActionIDList: string[];
+};
+
+export default ResolveDuplicatesParams;
diff --git a/src/libs/API/parameters/SaveSearch.ts b/src/libs/API/parameters/SaveSearch.ts
index e0ad38dd8363..9dd3416320c7 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/parameters/index.ts b/src/libs/API/parameters/index.ts
index e5cde1b77be7..9f51cab3f360 100644
--- a/src/libs/API/parameters/index.ts
+++ b/src/libs/API/parameters/index.ts
@@ -238,6 +238,7 @@ export type {default as SendInvoiceParams} from './SendInvoiceParams';
export type {default as PayInvoiceParams} from './PayInvoiceParams';
export type {default as MarkAsCashParams} from './MarkAsCashParams';
export type {default as TransactionMergeParams} from './TransactionMergeParams';
+export type {default as ResolveDuplicatesParams} from './ResolveDuplicatesParams';
export type {default as UpdateSubscriptionTypeParams} from './UpdateSubscriptionTypeParams';
export type {default as SignUpUserParams} from './SignUpUserParams';
export type {default as UpdateSubscriptionAutoRenewParams} from './UpdateSubscriptionAutoRenewParams';
diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts
index 88372382d3c8..b72b77ae4739 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',
@@ -284,6 +285,7 @@ const WRITE_COMMANDS = {
PAY_INVOICE: 'PayInvoice',
MARK_AS_CASH: 'MarkAsCash',
TRANSACTION_MERGE: 'Transaction_Merge',
+ RESOLVE_DUPLICATES: 'ResolveDuplicates',
UPDATE_SUBSCRIPTION_TYPE: 'UpdateSubscriptionType',
SIGN_UP_USER: 'SignUpUser',
UPDATE_SUBSCRIPTION_AUTO_RENEW: 'UpdateSubscriptionAutoRenew',
@@ -633,6 +635,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;
@@ -701,6 +704,7 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.PAY_INVOICE]: Parameters.PayInvoiceParams;
[WRITE_COMMANDS.MARK_AS_CASH]: Parameters.MarkAsCashParams;
[WRITE_COMMANDS.TRANSACTION_MERGE]: Parameters.TransactionMergeParams;
+ [WRITE_COMMANDS.RESOLVE_DUPLICATES]: Parameters.ResolveDuplicatesParams;
[WRITE_COMMANDS.UPDATE_SUBSCRIPTION_TYPE]: Parameters.UpdateSubscriptionTypeParams;
[WRITE_COMMANDS.SIGN_UP_USER]: Parameters.SignUpUserParams;
[WRITE_COMMANDS.UPDATE_SUBSCRIPTION_AUTO_RENEW]: Parameters.UpdateSubscriptionAutoRenewParams;
@@ -872,7 +876,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 +933,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 6b43a549256d..2de905ff6047 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 03c582d6b16b..b31d59804c51 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/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx
index dee84a4f201f..98e490524bef 100644
--- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx
+++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx
@@ -1,7 +1,7 @@
import React, {memo, useEffect, useMemo, useRef, useState} from 'react';
import {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
-import Onyx, {withOnyx} from 'react-native-onyx';
+import Onyx, {useOnyx} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import ActiveGuidesEventListener from '@components/ActiveGuidesEventListener';
import ComposeProviders from '@components/ComposeProviders';
@@ -50,6 +50,7 @@ import SCREENS from '@src/SCREENS';
import type * as OnyxTypes from '@src/types/onyx';
import type {SelectedTimezone, Timezone} from '@src/types/onyx/PersonalDetails';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
+import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue';
import type ReactComponentModule from '@src/types/utils/ReactComponentModule';
import beforeRemoveReportOpenedFromSearchRHP from './beforeRemoveReportOpenedFromSearchRHP';
import CENTRAL_PANE_SCREENS from './CENTRAL_PANE_SCREENS';
@@ -65,17 +66,6 @@ import OnboardingModalNavigator from './Navigators/OnboardingModalNavigator';
import RightModalNavigator from './Navigators/RightModalNavigator';
import WelcomeVideoModalNavigator from './Navigators/WelcomeVideoModalNavigator';
-type AuthScreensProps = {
- /** Session of currently logged in user */
- session: OnyxEntry;
-
- /** The report ID of the last opened public room as anonymous user */
- lastOpenedPublicRoomID: OnyxEntry;
-
- /** The last Onyx update ID was applied to the client */
- initialLastUpdateIDAppliedToClient: OnyxEntry;
-};
-
const loadReportAttachments = () => require('../../../pages/home/report/ReportAttachments').default;
const loadValidateLoginPage = () => require('../../../pages/ValidateLoginPage').default;
const loadLogOutPreviousUserPage = () => require('../../../pages/LogOutPreviousUserPage').default;
@@ -223,7 +213,10 @@ const modalScreenListenersWithCancelSearch = {
},
};
-function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDAppliedToClient}: AuthScreensProps) {
+function AuthScreens() {
+ const [session, sessionStatus] = useOnyx(ONYXKEYS.SESSION);
+ const [lastOpenedPublicRoomID, lastOpenedPublicRoomIDStatus] = useOnyx(ONYXKEYS.LAST_OPENED_PUBLIC_ROOM_ID);
+ const [initialLastUpdateIDAppliedToClient, initialLastUpdateIDAppliedToClientStatus] = useOnyx(ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT);
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const {shouldUseNarrowLayout, onboardingIsMediumOrLargerScreenWidth, isSmallScreenWidth} = useResponsiveLayout();
@@ -405,6 +398,9 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie
// Prevent unnecessary scrolling
cardStyle: styles.cardStyleNavigator,
};
+ if (isLoadingOnyxValue(sessionStatus, lastOpenedPublicRoomIDStatus, initialLastUpdateIDAppliedToClientStatus)) {
+ return;
+ }
return (
@@ -583,16 +579,4 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie
AuthScreens.displayName = 'AuthScreens';
-const AuthScreensMemoized = memo(AuthScreens, () => true);
-
-export default withOnyx({
- session: {
- key: ONYXKEYS.SESSION,
- },
- lastOpenedPublicRoomID: {
- key: ONYXKEYS.LAST_OPENED_PUBLIC_ROOM_ID,
- },
- initialLastUpdateIDAppliedToClient: {
- key: ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT,
- },
-})(AuthScreensMemoized);
+export default memo(AuthScreens, () => true);
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
index 4108addac0f3..e1c645118003 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
@@ -87,6 +87,7 @@ const MoneyRequestModalStackNavigator = createModalStackNavigator require('../../../../pages/iou/request/step/IOURequestStepMerchant').default,
[SCREENS.MONEY_REQUEST.STEP_PARTICIPANTS]: () => require('../../../../pages/iou/request/step/IOURequestStepParticipants').default,
[SCREENS.SETTINGS_CATEGORIES.SETTINGS_CATEGORIES_ROOT]: () => require('../../../../pages/workspace/categories/WorkspaceCategoriesPage').default,
+ [SCREENS.SETTINGS_TAGS_ROOT]: () => require('../../../../pages/workspace/tags/WorkspaceTagsPage').default,
[SCREENS.MONEY_REQUEST.STEP_SCAN]: () => require('../../../../pages/iou/request/step/IOURequestStepScan').default,
[SCREENS.MONEY_REQUEST.STEP_TAG]: () => require('../../../../pages/iou/request/step/IOURequestStepTag').default,
[SCREENS.MONEY_REQUEST.STEP_WAYPOINT]: () => require('../../../../pages/iou/request/step/IOURequestStepWaypoint').default,
diff --git a/src/libs/Navigation/Navigation.ts b/src/libs/Navigation/Navigation.ts
index 4c61b953f572..d5e9c5229a89 100644
--- a/src/libs/Navigation/Navigation.ts
+++ b/src/libs/Navigation/Navigation.ts
@@ -20,6 +20,7 @@ import getTopmostBottomTabRoute from './getTopmostBottomTabRoute';
import getTopmostCentralPaneRoute from './getTopmostCentralPaneRoute';
import originalGetTopmostReportActionId from './getTopmostReportActionID';
import originalGetTopmostReportId from './getTopmostReportId';
+import isReportOpenInRHP from './isReportOpenInRHP';
import linkingConfig from './linkingConfig';
import getMatchingBottomTabRouteForState from './linkingConfig/getMatchingBottomTabRouteForState';
import linkTo from './linkTo';
@@ -157,6 +158,13 @@ function getActiveRoute(): string {
return '';
}
+function getReportRHPActiveRoute(): string {
+ if (isReportOpenInRHP(navigationRef.getRootState())) {
+ return getActiveRoute();
+ }
+ return '';
+}
+
/**
* Check whether the passed route is currently Active or not.
*
@@ -419,6 +427,7 @@ export default {
isActiveRoute,
getActiveRoute,
getActiveRouteWithoutParams,
+ getReportRHPActiveRoute,
closeAndNavigate,
goBack,
isNavigationReady,
diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts
index f90ddbe2f818..319ec60d143e 100644
--- a/src/libs/Navigation/linkingConfig/config.ts
+++ b/src/libs/Navigation/linkingConfig/config.ts
@@ -955,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]: {
@@ -1012,6 +1012,7 @@ const config: LinkingOptions['config'] = {
},
},
[SCREENS.SETTINGS_CATEGORIES.SETTINGS_CATEGORIES_ROOT]: ROUTES.SETTINGS_CATEGORIES_ROOT.route,
+ [SCREENS.SETTINGS_TAGS_ROOT]: ROUTES.SETTINGS_TAGS_ROOT.route,
[SCREENS.MONEY_REQUEST.STEP_SEND_FROM]: ROUTES.MONEY_REQUEST_STEP_SEND_FROM.route,
[SCREENS.MONEY_REQUEST.STEP_COMPANY_INFO]: ROUTES.MONEY_REQUEST_STEP_COMPANY_INFO.route,
[SCREENS.MONEY_REQUEST.STEP_AMOUNT]: ROUTES.MONEY_REQUEST_STEP_AMOUNT.route,
@@ -1110,7 +1111,12 @@ const config: LinkingOptions['config'] = {
},
[SCREENS.RIGHT_MODAL.EDIT_REQUEST]: {
screens: {
- [SCREENS.EDIT_REQUEST.REPORT_FIELD]: ROUTES.EDIT_REPORT_FIELD_REQUEST.route,
+ [SCREENS.EDIT_REQUEST.REPORT_FIELD]: {
+ path: ROUTES.EDIT_REPORT_FIELD_REQUEST.route,
+ parse: {
+ fieldID: (fieldID: string) => decodeURIComponent(fieldID),
+ },
+ },
},
},
[SCREENS.RIGHT_MODAL.SIGN_IN]: {
@@ -1125,7 +1131,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 1326a0c86709..39cc50affaa7 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -829,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;
};
};
@@ -989,6 +1023,7 @@ type MoneyRequestNavigatorParamList = {
backTo: never;
action: never;
currency: never;
+ pageIndex?: string;
};
[SCREENS.MONEY_REQUEST.START]: {
iouType: IOUType;
@@ -1002,6 +1037,7 @@ type MoneyRequestNavigatorParamList = {
transactionID: string;
backTo: Routes;
action: IOUAction;
+ pageIndex?: string;
currency?: string;
};
[SCREENS.MONEY_REQUEST.STEP_DISTANCE_RATE]: {
@@ -1040,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 = {
@@ -1056,9 +1102,12 @@ type TeachersUniteNavigatorParamList = {
};
type TaskDetailsNavigatorParamList = {
- [SCREENS.TASK.TITLE]: undefined;
+ [SCREENS.TASK.TITLE]: {
+ backTo?: Routes;
+ };
[SCREENS.TASK.ASSIGNEE]: {
reportID: string;
+ backTo?: Routes;
};
};
@@ -1070,6 +1119,7 @@ type SplitDetailsNavigatorParamList = {
[SCREENS.SPLIT_DETAILS.ROOT]: {
reportID: string;
reportActionID: string;
+ backTo?: Routes;
};
[SCREENS.SPLIT_DETAILS.EDIT_REQUEST]: {
field: string;
@@ -1103,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 = {
@@ -1130,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 aa9d8c59fb5b..fe90aa87495e 100644
--- a/src/libs/Network/NetworkStore.ts
+++ b/src/libs/Network/NetworkStore.ts
@@ -100,9 +100,9 @@ function getAuthToken(): string | null | undefined {
function isSupportRequest(command: string): boolean {
return [
WRITE_COMMANDS.OPEN_APP,
+ WRITE_COMMANDS.SEARCH,
SIDE_EFFECT_REQUEST_COMMANDS.RECONNECT_APP,
SIDE_EFFECT_REQUEST_COMMANDS.OPEN_REPORT,
- READ_COMMANDS.SEARCH,
READ_COMMANDS.OPEN_CARD_DETAILS_PAGE,
READ_COMMANDS.OPEN_POLICY_CATEGORIES_PAGE,
READ_COMMANDS.OPEN_POLICY_COMPANY_CARDS_PAGE,
diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts
index 7f13e1297b6f..ba435cc57b8f 100644
--- a/src/libs/NextStepUtils.ts
+++ b/src/libs/NextStepUtils.ts
@@ -8,9 +8,9 @@ import ONYXKEYS from '@src/ONYXKEYS';
import type {Policy, Report, ReportNextStep} from '@src/types/onyx';
import type {Message} from '@src/types/onyx/ReportNextStep';
import type DeepValueOf from '@src/types/utils/DeepValueOf';
+import {getNextApproverAccountID} from './actions/IOU';
import DateUtils from './DateUtils';
import EmailUtils from './EmailUtils';
-import * as PersonalDetailsUtils from './PersonalDetailsUtils';
import * as PolicyUtils from './PolicyUtils';
import * as ReportUtils from './ReportUtils';
@@ -67,18 +67,10 @@ function parseMessage(messages: Message[] | undefined) {
return `${formattedHtml} `;
}
-function getNextApproverDisplayName(policy: Policy, ownerAccountID: number, submitToAccountID: number, report: OnyxEntry) {
- const approvalChain = ReportUtils.getApprovalChain(policy, ownerAccountID, report?.total ?? 0);
- if (approvalChain.length === 0) {
- return ReportUtils.getDisplayNameForParticipant(submitToAccountID);
- }
-
- const nextApproverEmail = approvalChain.length === 1 ? approvalChain[0] : approvalChain[approvalChain.indexOf(currentUserEmail) + 1];
- if (!nextApproverEmail) {
- return ReportUtils.getDisplayNameForParticipant(submitToAccountID);
- }
+function getNextApproverDisplayName(report: OnyxEntry) {
+ const approverAccountID = getNextApproverAccountID(report);
- return PersonalDetailsUtils.getPersonalDetailByEmail(nextApproverEmail)?.displayName ?? nextApproverEmail;
+ return ReportUtils.getDisplayNameForParticipant(approverAccountID) ?? ReportUtils.getPersonalDetailsForAccountID(approverAccountID).login;
}
/**
@@ -98,9 +90,8 @@ function buildNextStep(report: OnyxEntry, predictedNextStatus: ValueOf & {
preferChatroomsOverThreads?: boolean;
preferPolicyExpenseChat?: boolean;
+ preferRecentExpenseReports?: boolean;
};
/**
@@ -1573,7 +1574,11 @@ function createOptionFromReport(report: Report, personalDetails: OnyxEntry option?.lastIOUCreationDate ?? '' : '',
+ preferRecentExpenseReports ? (option) => option?.isPolicyExpenseChat : 0,
],
- ['asc'],
+ ['asc', 'desc', 'desc'],
);
}
@@ -1923,6 +1937,8 @@ function getOptions(
let recentReportOptions = [];
let personalDetailsOptions: ReportUtils.OptionData[] = [];
+ const preferRecentExpenseReports = action === CONST.IOU.ACTION.CREATE;
+
if (includeRecentReports) {
for (const reportOption of allReportOptions) {
/**
@@ -1945,9 +1961,11 @@ function getOptions(
reportOption.isPolicyExpenseChat && reportOption.ownerAccountID === currentUserAccountID && includeOwnedWorkspaceChats && !reportOption.private_isArchived;
const shouldShowInvoiceRoom =
- includeInvoiceRooms && ReportUtils.isInvoiceRoom(reportOption.item) && ReportUtils.isPolicyAdmin(reportOption.policyID ?? '', policies) && !reportOption.private_isArchived;
- // TODO: Uncomment the following line when the invoices screen is ready - https://github.com/Expensify/App/issues/45175.
- // && PolicyUtils.canSendInvoiceFromWorkspace(reportOption.policyID);
+ includeInvoiceRooms &&
+ ReportUtils.isInvoiceRoom(reportOption.item) &&
+ ReportUtils.isPolicyAdmin(reportOption.policyID ?? '', policies) &&
+ !reportOption.private_isArchived &&
+ PolicyUtils.canSendInvoiceFromWorkspace(reportOption.policyID);
/**
Exclude the report option if it doesn't meet any of the following conditions:
@@ -1981,6 +1999,22 @@ function getOptions(
recentReportOptions.push(reportOption);
}
+ // Add a field to sort the recent reports by the time of last IOU request for create actions
+ if (preferRecentExpenseReports) {
+ const reportPreviewAction = allSortedReportActions[reportOption.reportID]?.find((reportAction) =>
+ ReportActionUtils.isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW),
+ );
+
+ if (reportPreviewAction) {
+ const iouReportID = ReportActionUtils.getIOUReportIDFromReportActionPreview(reportPreviewAction);
+ const iouReportActions = allSortedReportActions[iouReportID] ?? [];
+ const lastIOUAction = iouReportActions.find((iouAction) => iouAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU);
+ if (lastIOUAction) {
+ reportOption.lastIOUCreationDate = lastIOUAction.lastModified;
+ }
+ }
+ }
+
// Add this login to the exclude list so it won't appear when we process the personal details
if (reportOption.login) {
optionsToExclude.push({login: reportOption.login});
@@ -2029,7 +2063,11 @@ function getOptions(
recentReportOptions.push(...personalDetailsOptions);
personalDetailsOptions = [];
}
- recentReportOptions = orderOptions(recentReportOptions, searchValue, {preferChatroomsOverThreads: true, preferPolicyExpenseChat: !!action});
+ recentReportOptions = orderOptions(recentReportOptions, searchValue, {
+ preferChatroomsOverThreads: true,
+ preferPolicyExpenseChat: !!action,
+ preferRecentExpenseReports,
+ });
}
return {
@@ -2393,6 +2431,7 @@ function filterOptions(options: Options, searchInputValue: string, config?: Filt
excludeLogins = [],
preferChatroomsOverThreads = false,
preferPolicyExpenseChat = false,
+ preferRecentExpenseReports = false,
} = config ?? {};
if (searchInputValue.trim() === '' && maxRecentReportsToShow > 0) {
return {...options, recentReports: options.recentReports.slice(0, maxRecentReportsToShow)};
@@ -2474,7 +2513,7 @@ function filterOptions(options: Options, searchInputValue: string, config?: Filt
return {
personalDetails,
- recentReports: orderOptions(recentReports, searchValue, {preferChatroomsOverThreads, preferPolicyExpenseChat}),
+ recentReports: orderOptions(recentReports, searchValue, {preferChatroomsOverThreads, preferPolicyExpenseChat, preferRecentExpenseReports}),
userToInvite,
currentUserOption: matchResults.currentUserOption,
categoryOptions: [],
diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts
index d276909103cb..4d13051d107c 100644
--- a/src/libs/PolicyUtils.ts
+++ b/src/libs/PolicyUtils.ts
@@ -588,9 +588,7 @@ function canSendInvoiceFromWorkspace(policyID: string | undefined): boolean {
/** Whether the user can send invoice */
function canSendInvoice(policies: OnyxCollection | null, currentUserLogin: string | undefined): boolean {
- return getActiveAdminWorkspaces(policies, currentUserLogin).length > 0;
- // TODO: Uncomment the following line when the invoices screen is ready - https://github.com/Expensify/App/issues/45175.
- // return getActiveAdminWorkspaces(policies).some((policy) => canSendInvoiceFromWorkspace(policy.id));
+ return getActiveAdminWorkspaces(policies, currentUserLogin).some((policy) => canSendInvoiceFromWorkspace(policy.id));
}
function hasDependentTags(policy: OnyxEntry, policyTagList: OnyxEntry) {
diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts
index f584f694edd0..61fe8387cce7 100644
--- a/src/libs/ReportActionsUtils.ts
+++ b/src/libs/ReportActionsUtils.ts
@@ -994,20 +994,20 @@ const iouRequestTypes = new Set>([
CONST.IOU.REPORT_ACTION_TYPE.TRACK,
]);
-/**
- * Gets the reportID for the transaction thread associated with a report by iterating over the reportActions and identifying the IOU report actions.
- * Returns a reportID if there is exactly one transaction thread for the report, and null otherwise.
- */
-function getOneTransactionThreadReportID(reportID: string, reportActions: OnyxEntry | ReportAction[], isOffline: boolean | undefined = undefined): string | undefined {
- // If the report is not an IOU, Expense report, or Invoice, it shouldn't be treated as one-transaction report.
+function getMoneyRequestActions(
+ reportID: string,
+ reportActions: OnyxEntry | ReportAction[],
+ isOffline: boolean | undefined = undefined,
+): Array> {
+ // If the report is not an IOU, Expense report, or Invoice, it shouldn't have money request actions.
const report = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`];
if (report?.type !== CONST.REPORT.TYPE.IOU && report?.type !== CONST.REPORT.TYPE.EXPENSE && report?.type !== CONST.REPORT.TYPE.INVOICE) {
- return;
+ return [];
}
const reportActionsArray = Array.isArray(reportActions) ? reportActions : Object.values(reportActions ?? {});
if (!reportActionsArray.length) {
- return;
+ return [];
}
const iouRequestActions = [];
@@ -1035,6 +1035,15 @@ function getOneTransactionThreadReportID(reportID: string, reportActions: OnyxEn
iouRequestActions.push(action);
}
}
+ return iouRequestActions;
+}
+
+/**
+ * Gets the reportID for the transaction thread associated with a report by iterating over the reportActions and identifying the IOU report actions.
+ * Returns a reportID if there is exactly one transaction thread for the report, and null otherwise.
+ */
+function getOneTransactionThreadReportID(reportID: string, reportActions: OnyxEntry | ReportAction[], isOffline: boolean | undefined = undefined): string | undefined {
+ const iouRequestActions = getMoneyRequestActions(reportID, reportActions, isOffline);
// If we don't have any IOU request actions, or we have more than one IOU request actions, this isn't a oneTransaction report
if (!iouRequestActions.length || iouRequestActions.length > 1) {
@@ -1054,6 +1063,27 @@ function getOneTransactionThreadReportID(reportID: string, reportActions: OnyxEn
return singleAction.childReportID;
}
+/**
+ * Returns true if all transactions on the report have the same ownerID
+ */
+function hasSameActorForAllTransactions(reportID: string, reportActions: OnyxEntry | ReportAction[], isOffline: boolean | undefined = undefined): boolean {
+ const iouRequestActions = getMoneyRequestActions(reportID, reportActions, isOffline);
+ if (!iouRequestActions.length) {
+ return true;
+ }
+
+ let actorID: number | undefined;
+
+ for (const action of iouRequestActions) {
+ if (actorID !== undefined && actorID !== action?.actorAccountID) {
+ return false;
+ }
+ actorID = action?.actorAccountID;
+ }
+
+ return true;
+}
+
/**
* When we delete certain reports, we want to check whether there are any visible actions left to display.
* If there are no visible actions left (including system messages), we can hide the report from view entirely
@@ -1698,7 +1728,7 @@ function isCardIssuedAction(reportAction: OnyxEntry) {
}
function getCardIssuedMessage(reportAction: OnyxEntry, shouldRenderHTML = false) {
- const assigneeAccountID = (reportAction?.originalMessage as IssueNewCardOriginalMessage)?.assigneeAccountID;
+ const assigneeAccountID = (getOriginalMessage(reportAction) as IssueNewCardOriginalMessage)?.assigneeAccountID;
const assigneeDetails = PersonalDetailsUtils.getPersonalDetailsByIDs([assigneeAccountID], currentUserAccountID ?? -1)[0];
const assignee = shouldRenderHTML ? ` ` : assigneeDetails?.firstName ?? assigneeDetails.login ?? '';
@@ -1834,6 +1864,7 @@ export {
getRenamedAction,
isCardIssuedAction,
getCardIssuedMessage,
+ hasSameActorForAllTransactions,
};
export type {LastVisibleMessage};
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index 5c0451f2ea01..584d5d09b682 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -459,6 +459,7 @@ type OptionData = {
tabIndex?: 0 | -1;
isConciergeChat?: boolean;
isBold?: boolean;
+ lastIOUCreationDate?: string;
} & Report;
type OnyxDataTaskAssigneeChat = {
@@ -1580,12 +1581,20 @@ function hasOnlyNonReimbursableTransactions(iouReportID: string | undefined): bo
return transactions.every((transaction) => !TransactionUtils.getReimbursable(transaction));
}
+/**
+ * Checks if a report has only transactions with same ownerID
+ */
+function isSingleActorMoneyReport(reportID: string): boolean {
+ const reportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] ?? ([] as ReportAction[]);
+ return !!ReportActionsUtils.hasSameActorForAllTransactions(reportID, reportActions);
+}
+
/**
* Checks if a report has only one transaction associated with it
*/
function isOneTransactionReport(reportID: string): boolean {
const reportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] ?? ([] as ReportAction[]);
- return ReportActionsUtils.getOneTransactionThreadReportID(reportID, reportActions) !== null;
+ return !!ReportActionsUtils.getOneTransactionThreadReportID(reportID, reportActions);
}
/*
@@ -2143,7 +2152,14 @@ function getGroupChatName(participants?: SelectedParticipant[], shouldApplyLimit
return report.reportName;
}
- let participantAccountIDs = participants?.map((participant) => participant.accountID) ?? Object.keys(report?.participants ?? {}).map(Number);
+ const pendingMemberAccountIDs = new Set(
+ report?.pendingChatMembers?.filter((member) => member.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE).map((member) => member.accountID),
+ );
+ let participantAccountIDs =
+ participants?.map((participant) => participant.accountID) ??
+ Object.keys(report?.participants ?? {})
+ .map(Number)
+ .filter((accountID) => !pendingMemberAccountIDs.has(accountID.toString()));
if (shouldApplyLimit) {
participantAccountIDs = participantAccountIDs.slice(0, 5);
}
@@ -2210,7 +2226,7 @@ function getIcons(
if (isChatThread(report)) {
const parentReportAction = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`]?.[report.parentReportActionID];
- const actorAccountID = getReportActionActorAccountID(parentReportAction, report);
+ const actorAccountID = getReportActionActorAccountID(parentReportAction);
const actorDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(allPersonalDetails?.[actorAccountID ?? -1], '', false);
const actorIcon = {
id: actorAccountID,
@@ -2305,7 +2321,7 @@ function getIcons(
const isManager = currentUserAccountID === report?.managerID;
// For one transaction IOUs, display a simplified report icon
- if (isOneTransactionReport(report?.reportID ?? '-1')) {
+ if (isOneTransactionReport(report?.reportID ?? '-1') || isSingleActorMoneyReport(report?.reportID ?? '-1')) {
return [ownerIcon];
}
@@ -3755,9 +3771,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 +3936,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 +3986,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 +3995,16 @@ function goBackFromPrivateNotes(report: OnyxEntry, session: OnyxEntry, policy: OnyxEntry): boolean {
return true;
}
- if (isExpenseReport(report) && isOneTransactionReport(report?.reportID ?? '-1')) {
+ if (isExpenseReport(report)) {
return true;
}
@@ -6656,7 +6670,7 @@ function shouldReportShowSubscript(report: OnyxEntry): boolean {
return true;
}
- if (isInvoiceRoom(report)) {
+ if (isInvoiceRoom(report) || isInvoiceReport(report)) {
return true;
}
@@ -7138,16 +7152,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));
}
/**
@@ -7538,10 +7552,10 @@ function canLeaveChat(report: OnyxEntry, policy: OnyxEntry): boo
return (isChatThread(report) && !!getReportNotificationPreference(report)) || isUserCreatedPolicyRoom(report) || isNonAdminOrOwnerOfPolicyExpenseChat(report, policy);
}
-function getReportActionActorAccountID(reportAction: OnyxInputOrEntry, iouReport: OnyxInputOrEntry | undefined): number | undefined {
+function getReportActionActorAccountID(reportAction: OnyxInputOrEntry): number | undefined {
switch (reportAction?.actionName) {
case CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW:
- return !isEmptyObject(iouReport) ? iouReport.managerID : reportAction?.childManagerAccountID;
+ return reportAction?.childOwnerAccountID ?? reportAction?.actorAccountID;
case CONST.REPORT.ACTIONS.TYPE.SUBMITTED:
return reportAction?.adminAccountID ?? reportAction?.actorAccountID;
@@ -7810,8 +7824,8 @@ function hasMissingInvoiceBankAccount(iouReportID: string): boolean {
return invoiceReport?.ownerAccountID === currentUserAccountID && isEmptyObject(getPolicy(invoiceReport?.policyID)?.invoice?.bankAccount ?? {}) && isSettled(iouReportID);
}
-function isExpenseReportManagerWithoutParentAccess(report: OnyxEntry) {
- return isExpenseReport(report) && report?.hasParentAccess === false && report?.managerID === currentUserAccountID;
+function isExpenseReportWithoutParentAccess(report: OnyxEntry) {
+ return isExpenseReport(report) && report?.hasParentAccess === false;
}
export {
@@ -8009,7 +8023,7 @@ export {
isEmptyReport,
isRootGroupChat,
isExpenseReport,
- isExpenseReportManagerWithoutParentAccess,
+ isExpenseReportWithoutParentAccess,
isExpenseRequest,
isExpensifyOnlyParticipantInReport,
isGroupChat,
@@ -8122,6 +8136,7 @@ export {
isIndividualInvoiceRoom,
isAuditor,
hasMissingInvoiceBankAccount,
+ isSingleActorMoneyReport,
};
export type {
diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts
index d056f111695e..e120f7026fce 100644
--- a/src/libs/SidebarUtils.ts
+++ b/src/libs/SidebarUtils.ts
@@ -129,10 +129,10 @@ function getOrderedReportIDs(
return;
}
const isSystemChat = ReportUtils.isSystemChat(report);
- const isExpenseReportManagerWithoutParentAccess = ReportUtils.isExpenseReportManagerWithoutParentAccess(report);
+ const isExpenseReportWithoutParentAccess = ReportUtils.isExpenseReportWithoutParentAccess(report);
const shouldOverrideHidden =
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
- hasValidDraftComment(report.reportID) || hasErrorsOtherThanFailedReceipt || isFocused || isSystemChat || report.isPinned || isExpenseReportManagerWithoutParentAccess;
+ hasValidDraftComment(report.reportID) || hasErrorsOtherThanFailedReceipt || isFocused || isSystemChat || report.isPinned || isExpenseReportWithoutParentAccess;
if (isHidden && !shouldOverrideHidden) {
return;
}
diff --git a/src/libs/TripReservationUtils.ts b/src/libs/TripReservationUtils.ts
index 13bc3293cd9d..b57774799315 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 a0f60752913e..6b6f1a5f6dc6 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 4221c9db2897..c422313a1946 100644
--- a/src/libs/actions/IOU.ts
+++ b/src/libs/actions/IOU.ts
@@ -17,6 +17,7 @@ import type {
PayMoneyRequestParams,
ReplaceReceiptParams,
RequestMoneyParams,
+ ResolveDuplicatesParams,
SendInvoiceParams,
SendMoneyParams,
SetNameValuePairParams,
@@ -42,6 +43,7 @@ import Navigation from '@libs/Navigation/Navigation';
import * as NextStepUtils from '@libs/NextStepUtils';
import {rand64} from '@libs/NumberUtils';
import * as OptionsListUtils from '@libs/OptionsListUtils';
+import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import * as PhoneNumber from '@libs/PhoneNumber';
import * as PolicyUtils from '@libs/PolicyUtils';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
@@ -1247,8 +1249,8 @@ function buildOnyxDataForInvoice(
key: `${ONYXKEYS.COLLECTION.POLICY}${policy?.id}`,
value: {
invoice: {
- companyName: undefined,
- companyWebsite: undefined,
+ companyName: null,
+ companyWebsite: null,
pendingFields: {
companyName: null,
companyWebsite: null,
@@ -7013,6 +7015,24 @@ function isLastApprover(approvalChain: string[]): boolean {
return approvalChain[approvalChain.length - 1] === currentUserEmail;
}
+function getNextApproverAccountID(report: OnyxEntry) {
+ const ownerAccountID = report?.ownerAccountID ?? -1;
+ const policy = PolicyUtils.getPolicy(report?.policyID);
+ const approvalChain = ReportUtils.getApprovalChain(policy, ownerAccountID, report?.total ?? 0);
+ const submitToAccountID = PolicyUtils.getSubmitToAccountID(policy, ownerAccountID);
+
+ if (approvalChain.length === 0) {
+ return submitToAccountID;
+ }
+
+ const nextApproverEmail = approvalChain.length === 1 ? approvalChain[0] : approvalChain[approvalChain.indexOf(currentUserEmail) + 1];
+ if (!nextApproverEmail) {
+ return submitToAccountID;
+ }
+
+ return PersonalDetailsUtils.getAccountIDsByLogins([nextApproverEmail])[0];
+}
+
function approveMoneyRequest(expenseReport: OnyxEntry, full?: boolean) {
if (expenseReport?.policyID && SubscriptionUtils.shouldRestrictUserBillableActions(expenseReport.policyID)) {
Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(expenseReport.policyID));
@@ -7031,6 +7051,7 @@ function approveMoneyRequest(expenseReport: OnyxEntry, full?:
const predictedNextStatus = isLastApprover(approvalChain) ? CONST.REPORT.STATUS_NUM.APPROVED : CONST.REPORT.STATUS_NUM.SUBMITTED;
const predictedNextState = isLastApprover(approvalChain) ? CONST.REPORT.STATE_NUM.APPROVED : CONST.REPORT.STATE_NUM.SUBMITTED;
+ const managerID = isLastApprover(approvalChain) ? expenseReport?.managerID : getNextApproverAccountID(expenseReport);
const optimisticNextStep = NextStepUtils.buildNextStep(expenseReport, predictedNextStatus);
const chatReport = ReportUtils.getReportOrDraftReport(expenseReport?.chatReportID);
@@ -7054,6 +7075,7 @@ function approveMoneyRequest(expenseReport: OnyxEntry, full?:
lastMessageHtml: ReportActionsUtils.getReportActionHtml(optimisticApprovedReportAction),
stateNum: predictedNextState,
statusNum: predictedNextStatus,
+ managerID,
pendingFields: {
partial: full ? null : CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
},
@@ -8011,6 +8033,21 @@ function getIOURequestPolicyID(transaction: OnyxEntry, re
return workspaceSender?.policyID ?? report?.policyID ?? '-1';
}
+function getIOUActionForTransactions(transactionIDList: string[], iouReportID: string): Array> {
+ return Object.values(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`] ?? {})?.filter(
+ (reportAction): reportAction is ReportAction => {
+ if (!ReportActionsUtils.isMoneyRequestAction(reportAction)) {
+ return false;
+ }
+ const message = ReportActionsUtils.getOriginalMessage(reportAction);
+ if (!message?.IOUTransactionID) {
+ return false;
+ }
+ return transactionIDList.includes(message.IOUTransactionID);
+ },
+ );
+}
+
/** Merge several transactions into one by updating the fields of the one we want to keep and deleting the rest */
function mergeDuplicates(params: TransactionMergeParams) {
const originalSelectedTransaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${params.transactionID}`];
@@ -8095,18 +8132,7 @@ function mergeDuplicates(params: TransactionMergeParams) {
},
};
- const iouActionsToDelete = Object.values(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${params.reportID}`] ?? {})?.filter(
- (reportAction): reportAction is ReportAction => {
- if (!ReportActionsUtils.isMoneyRequestAction(reportAction)) {
- return false;
- }
- const message = ReportActionsUtils.getOriginalMessage(reportAction);
- if (!message?.IOUTransactionID) {
- return false;
- }
- return params.transactionIDList.includes(message.IOUTransactionID);
- },
- );
+ const iouActionsToDelete = getIOUActionForTransactions(params.transactionIDList, params.reportID);
const deletedTime = DateUtils.getDBTime();
const expenseReportActionsOptimisticData: OnyxUpdate = {
@@ -8167,8 +8193,128 @@ function mergeDuplicates(params: TransactionMergeParams) {
API.write(WRITE_COMMANDS.TRANSACTION_MERGE, params, {optimisticData, failureData});
}
+/** Instead of merging the duplicates, it updates the transaction we want to keep and puts the others on hold without deleting them */
+function resolveDuplicates(params: TransactionMergeParams) {
+ const originalSelectedTransaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${params.transactionID}`];
+
+ const optimisticTransactionData: OnyxUpdate = {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.TRANSACTION}${params.transactionID}`,
+ value: {
+ ...originalSelectedTransaction,
+ billable: params.billable,
+ comment: {
+ comment: params.comment,
+ },
+ category: params.category,
+ created: params.created,
+ currency: params.currency,
+ modifiedMerchant: params.merchant,
+ reimbursable: params.reimbursable,
+ tag: params.tag,
+ },
+ };
+
+ const failureTransactionData: OnyxUpdate = {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.TRANSACTION}${params.transactionID}`,
+ // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style
+ value: originalSelectedTransaction as OnyxTypes.Transaction,
+ };
+
+ const optimisticTransactionViolations: OnyxUpdate[] = [...params.transactionIDList, params.transactionID].map((id) => {
+ const violations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${id}`] ?? [];
+ const newViolation = {name: CONST.VIOLATIONS.HOLD, type: CONST.VIOLATION_TYPES.VIOLATION};
+ const updatedViolations = id === params.transactionID ? violations : [...violations, newViolation];
+ return {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${id}`,
+ value: updatedViolations.filter((violation) => violation.name !== CONST.VIOLATIONS.DUPLICATED_TRANSACTION),
+ };
+ });
+
+ const failureTransactionViolations: OnyxUpdate[] = [...params.transactionIDList, params.transactionID].map((id) => {
+ const violations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${id}`] ?? [];
+ return {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${id}`,
+ value: violations,
+ };
+ });
+
+ const iouActionList = getIOUActionForTransactions(params.transactionIDList, params.reportID);
+ const transactionThreadReportIDList = iouActionList.map((action) => action?.childReportID);
+ const orderedTransactionIDList = iouActionList.map((action) => {
+ const message = ReportActionsUtils.getOriginalMessage(action);
+ return message?.IOUTransactionID ?? '';
+ });
+
+ const optimisticHoldActions: OnyxUpdate[] = [];
+ const failureHoldActions: OnyxUpdate[] = [];
+ const reportActionIDList: string[] = [];
+ transactionThreadReportIDList.forEach((transactionThreadReportID) => {
+ const createdReportAction = ReportUtils.buildOptimisticHoldReportAction();
+ reportActionIDList.push(createdReportAction.reportActionID);
+ optimisticHoldActions.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportID}`,
+ value: {
+ [createdReportAction.reportActionID]: createdReportAction,
+ },
+ });
+ failureHoldActions.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportID}`,
+ value: {
+ [createdReportAction.reportActionID]: {
+ errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericHoldExpenseFailureMessage'),
+ },
+ },
+ });
+ });
+
+ const transactionThreadReportID = getIOUActionForTransactions([params.transactionID], params.reportID)?.[0]?.childReportID;
+ const optimisticReportAction = ReportUtils.buildOptimisticDismissedViolationReportAction({
+ reason: 'manual',
+ violationName: CONST.VIOLATIONS.DUPLICATED_TRANSACTION,
+ });
+
+ const optimisticReportActionData: OnyxUpdate = {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportID}`,
+ value: {
+ [optimisticReportAction.reportActionID]: optimisticReportAction,
+ },
+ };
+
+ const failureReportActionData: OnyxUpdate = {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportID}`,
+ value: {
+ [optimisticReportAction.reportActionID]: null,
+ },
+ };
+
+ const optimisticData: OnyxUpdate[] = [];
+ const failureData: OnyxUpdate[] = [];
+
+ optimisticData.push(optimisticTransactionData, ...optimisticTransactionViolations, ...optimisticHoldActions, optimisticReportActionData);
+ failureData.push(failureTransactionData, ...failureTransactionViolations, ...failureHoldActions, failureReportActionData);
+ const {reportID, transactionIDList, receiptID, ...otherParams} = params;
+
+ const parameters: ResolveDuplicatesParams = {
+ ...otherParams,
+ reportActionIDList,
+ transactionIDList: orderedTransactionIDList,
+ dismissedViolationReportActionID: optimisticReportAction.reportActionID,
+ };
+
+ API.write(WRITE_COMMANDS.RESOLVE_DUPLICATES, parameters, {optimisticData, failureData});
+}
+
export {
adjustRemainingSplitShares,
+ getNextApproverAccountID,
approveMoneyRequest,
canApproveIOU,
cancelPayment,
@@ -8237,5 +8383,7 @@ export {
updateMoneyRequestTaxAmount,
updateMoneyRequestTaxRate,
mergeDuplicates,
+ resolveDuplicates,
+ prepareToCleanUpMoneyRequest,
};
export type {GPSPoint as GpsPoint, IOURequestType};
diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts
index 2123f2a47764..db4fa4d417f6 100644
--- a/src/libs/actions/Policy/Policy.ts
+++ b/src/libs/actions/Policy/Policy.ts
@@ -224,7 +224,6 @@ function getPolicy(policyID: string | undefined): OnyxEntry {
/**
* Returns a primary policy for the user
*/
-// TODO: Use getInvoicePrimaryWorkspace when the invoices screen is ready - https://github.com/Expensify/App/issues/45175.
function getPrimaryPolicy(activePolicyID: OnyxEntry, currentUserLogin: string | undefined): Policy | undefined {
const activeAdminWorkspaces = PolicyUtils.getActiveAdminWorkspaces(allPolicies, currentUserLogin);
const primaryPolicy: Policy | null | undefined = activeAdminWorkspaces.find((policy) => policy.id === activePolicyID);
diff --git a/src/libs/actions/PriorityMode.ts b/src/libs/actions/PriorityMode.ts
index beec327a2e40..2aca5d9f9de8 100644
--- a/src/libs/actions/PriorityMode.ts
+++ b/src/libs/actions/PriorityMode.ts
@@ -57,11 +57,11 @@ Onyx.connect({
},
});
-let hasTriedFocusMode: boolean | null | undefined;
+let hasTriedFocusMode: boolean | undefined;
Onyx.connect({
key: ONYXKEYS.NVP_TRY_FOCUS_MODE,
callback: (val) => {
- hasTriedFocusMode = val ?? null;
+ hasTriedFocusMode = val;
// eslint-disable-next-line @typescript-eslint/no-use-before-define
checkRequiredData();
diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts
index b038f16d003d..e53cac804b90 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 873603b68739..722e88808033 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});
}
/**
diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts
index 41ccfee1786b..08568b6d5a02 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/pages/EditReportFieldPage.tsx b/src/pages/EditReportFieldPage.tsx
index 5690d3202136..ec4ce6568f70 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 1779fe8e085e..ac8eb7f862b6 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 055bba42c552..cc7ee9f54daa 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 2b732bb5506a..0d47e3fd8f35 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 d0049dfe5865..66c61b134aef 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 6ff710cf23b0..db978b70cad8 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 d2fb5ca365a0..17b84e8903ea 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 8ce9ec4d09a4..579e6f7ec104 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 6589a73c88e8..a70f17d1468c 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 8b2e89024d8c..475cf37a8847 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 19e631098e40..765c738bc574 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/EmptySearchView.tsx b/src/pages/Search/EmptySearchView.tsx
index edcf6ab23219..c1ea305c5b68 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 (
SearchActions.dismissSavedSearchRenameTooltip(),
renderTooltipContent: () => {
- SearchActions.dismissSavedSearchRenameTooltip();
return (
;
};
-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) {
Navigation.navigate(ROUTES.REFERRAL_DETAILS_MODAL.getRoute(CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SHARE_CODE, Navigation.getActiveRouteWithoutParams()))}
+ onPress={() => Navigation.navigate(ROUTES.REFERRAL_DETAILS_MODAL.getRoute(CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SHARE_CODE, Navigation.getActiveRoute()))}
shouldShowRightIcon
/>
diff --git a/src/pages/TransactionDuplicate/Confirmation.tsx b/src/pages/TransactionDuplicate/Confirmation.tsx
index 9830ea2a8745..b9a4adb729e2 100644
--- a/src/pages/TransactionDuplicate/Confirmation.tsx
+++ b/src/pages/TransactionDuplicate/Confirmation.tsx
@@ -14,7 +14,9 @@ import ScreenWrapper from '@components/ScreenWrapper';
import ScrollView from '@components/ScrollView';
import {ShowContextMenuContext} from '@components/ShowContextMenuContext';
import Text from '@components/Text';
+import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
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';
@@ -33,21 +35,34 @@ import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue';
function Confirmation() {
const styles = useThemeStyles();
const {translate} = useLocalize();
+ const currentUserPersonalDetails = useCurrentUserPersonalDetails();
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 [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${transaction?.reportID}`);
const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transaction?.reportID}`);
const reportAction = Object.values(reportActions ?? {}).find(
(action) => ReportActionsUtils.isMoneyRequestAction(action) && ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID === reviewDuplicates?.transactionID,
);
+ const isReportOwner = iouReport?.ownerAccountID === currentUserPersonalDetails?.accountID;
+
const transactionsMergeParams = useMemo(() => TransactionUtils.buildTransactionsMergeParams(reviewDuplicates, transaction), [reviewDuplicates, transaction]);
+
const mergeDuplicates = useCallback(() => {
IOU.mergeDuplicates(transactionsMergeParams);
Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportAction?.childReportID ?? '-1'));
}, [reportAction?.childReportID, transactionsMergeParams]);
+ const resolveDuplicates = useCallback(() => {
+ IOU.resolveDuplicates(transactionsMergeParams);
+ Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportAction?.childReportID ?? '-1'));
+ }, [transactionsMergeParams, reportAction?.childReportID]);
+
const contextValue = useMemo(
() => ({
transactionThreadReport: report,
@@ -83,7 +98,10 @@ function Confirmation() {
{({safeAreaPaddingBottomStyle}) => (
-
+
{
+ if (!isReportOwner) {
+ resolveDuplicates();
+ return;
+ }
+ mergeDuplicates();
+ }}
large
/>
diff --git a/src/pages/TransactionDuplicate/Review.tsx b/src/pages/TransactionDuplicate/Review.tsx
index 46f82589e309..e61f9382b895 100644
--- a/src/pages/TransactionDuplicate/Review.tsx
+++ b/src/pages/TransactionDuplicate/Review.tsx
@@ -49,7 +49,10 @@ function TransactionDuplicateReview() {
return (
-
+ Navigation.goBack(route.params.backTo)}
+ />
(index + 1).toString());
- const {currentScreenIndex, navigateToNextScreen} = useReviewDuplicatesNavigation(Object.keys(compareResult.change ?? {}), 'billable', route.params.threadReportID ?? '');
+ const {currentScreenIndex, goBack, navigateToNextScreen} = useReviewDuplicatesNavigation(
+ Object.keys(compareResult.change ?? {}),
+ 'billable',
+ route.params.threadReportID ?? '',
+ route.params.backTo,
+ );
const options = useMemo(
() =>
compareResult.change.billable?.map((billable) => ({
@@ -37,7 +42,10 @@ function ReviewBillable() {
return (
-
+
stepNames={stepNames}
label={translate('violations.isTransactionBillable')}
diff --git a/src/pages/TransactionDuplicate/ReviewCategory.tsx b/src/pages/TransactionDuplicate/ReviewCategory.tsx
index 12a5968e2c43..09cbdcd28327 100644
--- a/src/pages/TransactionDuplicate/ReviewCategory.tsx
+++ b/src/pages/TransactionDuplicate/ReviewCategory.tsx
@@ -18,7 +18,12 @@ function ReviewCategory() {
const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? '');
const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID);
const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString());
- const {currentScreenIndex, navigateToNextScreen} = useReviewDuplicatesNavigation(Object.keys(compareResult.change ?? {}), 'category', route.params.threadReportID ?? '');
+ const {currentScreenIndex, goBack, navigateToNextScreen} = useReviewDuplicatesNavigation(
+ Object.keys(compareResult.change ?? {}),
+ 'category',
+ route.params.threadReportID ?? '',
+ route.params.backTo,
+ );
const options = useMemo(
() =>
compareResult.change.category?.map((category) =>
@@ -41,7 +46,10 @@ function ReviewCategory() {
return (
-
+
stepNames={stepNames}
label={translate('violations.categoryToKeep')}
diff --git a/src/pages/TransactionDuplicate/ReviewDescription.tsx b/src/pages/TransactionDuplicate/ReviewDescription.tsx
index e6229afe48ac..3d74d8cc36e1 100644
--- a/src/pages/TransactionDuplicate/ReviewDescription.tsx
+++ b/src/pages/TransactionDuplicate/ReviewDescription.tsx
@@ -18,7 +18,12 @@ function ReviewDescription() {
const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? '');
const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID);
const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString());
- const {currentScreenIndex, navigateToNextScreen} = useReviewDuplicatesNavigation(Object.keys(compareResult.change ?? {}), 'description', route.params.threadReportID ?? '');
+ const {currentScreenIndex, goBack, navigateToNextScreen} = useReviewDuplicatesNavigation(
+ Object.keys(compareResult.change ?? {}),
+ 'description',
+ route.params.threadReportID ?? '',
+ route.params.backTo,
+ );
const options = useMemo(
() =>
compareResult.change.description?.map((description) =>
@@ -40,7 +45,10 @@ function ReviewDescription() {
return (
-
+
stepNames={stepNames}
label={translate('violations.descriptionToKeep')}
diff --git a/src/pages/TransactionDuplicate/ReviewMerchant.tsx b/src/pages/TransactionDuplicate/ReviewMerchant.tsx
index 80ae43a0d338..47dd43d1d334 100644
--- a/src/pages/TransactionDuplicate/ReviewMerchant.tsx
+++ b/src/pages/TransactionDuplicate/ReviewMerchant.tsx
@@ -18,7 +18,12 @@ function ReviewMerchant() {
const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? '');
const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID);
const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString());
- const {currentScreenIndex, navigateToNextScreen} = useReviewDuplicatesNavigation(Object.keys(compareResult.change ?? {}), 'merchant', route.params.threadReportID ?? '');
+ const {currentScreenIndex, goBack, navigateToNextScreen} = useReviewDuplicatesNavigation(
+ Object.keys(compareResult.change ?? {}),
+ 'merchant',
+ route.params.threadReportID ?? '',
+ route.params.backTo,
+ );
const options = useMemo(
() =>
compareResult.change.merchant?.map((merchant) =>
@@ -41,7 +46,10 @@ function ReviewMerchant() {
return (
-
+
stepNames={stepNames}
label={translate('violations.merchantToKeep')}
diff --git a/src/pages/TransactionDuplicate/ReviewReimbursable.tsx b/src/pages/TransactionDuplicate/ReviewReimbursable.tsx
index fbf7e43a2013..0b932e8085db 100644
--- a/src/pages/TransactionDuplicate/ReviewReimbursable.tsx
+++ b/src/pages/TransactionDuplicate/ReviewReimbursable.tsx
@@ -18,7 +18,12 @@ function ReviewReimbursable() {
const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? '');
const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID);
const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString());
- const {currentScreenIndex, navigateToNextScreen} = useReviewDuplicatesNavigation(Object.keys(compareResult.change ?? {}), 'reimbursable', route.params.threadReportID ?? '');
+ const {currentScreenIndex, goBack, navigateToNextScreen} = useReviewDuplicatesNavigation(
+ Object.keys(compareResult.change ?? {}),
+ 'reimbursable',
+ route.params.threadReportID ?? '',
+ route.params.backTo,
+ );
const options = useMemo(
() =>
compareResult.change.reimbursable?.map((reimbursable) => ({
@@ -37,7 +42,10 @@ function ReviewReimbursable() {
return (
-
+
stepNames={stepNames}
label={translate('violations.isTransactionReimbursable')}
diff --git a/src/pages/TransactionDuplicate/ReviewTag.tsx b/src/pages/TransactionDuplicate/ReviewTag.tsx
index 405c4fbc9964..03fb627abd8e 100644
--- a/src/pages/TransactionDuplicate/ReviewTag.tsx
+++ b/src/pages/TransactionDuplicate/ReviewTag.tsx
@@ -20,7 +20,12 @@ function ReviewTag() {
const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID);
const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString());
- const {currentScreenIndex, navigateToNextScreen} = useReviewDuplicatesNavigation(Object.keys(compareResult.change ?? {}), 'tag', route.params.threadReportID ?? '');
+ const {currentScreenIndex, goBack, navigateToNextScreen} = useReviewDuplicatesNavigation(
+ Object.keys(compareResult.change ?? {}),
+ 'tag',
+ route.params.threadReportID ?? '',
+ route.params.backTo,
+ );
const options = useMemo(
() =>
compareResult.change.tag?.map((tag) =>
@@ -42,7 +47,10 @@ function ReviewTag() {
return (
-
+
stepNames={stepNames}
label={translate('violations.tagToKeep')}
diff --git a/src/pages/TransactionDuplicate/ReviewTaxCode.tsx b/src/pages/TransactionDuplicate/ReviewTaxCode.tsx
index aa598bff8fcd..78b7c1934715 100644
--- a/src/pages/TransactionDuplicate/ReviewTaxCode.tsx
+++ b/src/pages/TransactionDuplicate/ReviewTaxCode.tsx
@@ -25,7 +25,12 @@ function ReviewTaxRate() {
const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? '');
const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID);
const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString());
- const {currentScreenIndex, navigateToNextScreen} = useReviewDuplicatesNavigation(Object.keys(compareResult.change ?? {}), 'taxCode', route.params.threadReportID ?? '');
+ const {currentScreenIndex, goBack, navigateToNextScreen} = useReviewDuplicatesNavigation(
+ Object.keys(compareResult.change ?? {}),
+ 'taxCode',
+ route.params.threadReportID ?? '',
+ route.params.backTo,
+ );
const transaction = TransactionUtils.getTransaction(transactionID);
const options = useMemo(
() =>
@@ -59,7 +64,10 @@ function ReviewTaxRate() {
return (
-
+
stepNames={stepNames}
label={translate('violations.taxCodeToKeep')}
diff --git a/src/pages/Travel/ManageTrips.tsx b/src/pages/Travel/ManageTrips.tsx
index 0591d8cf2fcf..9a83468feb14 100644
--- a/src/pages/Travel/ManageTrips.tsx
+++ b/src/pages/Travel/ManageTrips.tsx
@@ -1,4 +1,3 @@
-import {Str} from 'expensify-common';
import React, {useState} from 'react';
import {Linking, View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
@@ -12,12 +11,10 @@ import useLocalize from '@hooks/useLocalize';
import usePolicy from '@hooks/usePolicy';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
-import Navigation from '@libs/Navigation/Navigation';
+import * as TripsResevationUtils from '@libs/TripReservationUtils';
import colors from '@styles/theme/colors';
-import * as Link from '@userActions/Link';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
-import ROUTES from '@src/ROUTES';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
const tripsFeatures: FeatureListItem[] = [
@@ -35,9 +32,7 @@ function ManageTrips() {
const styles = useThemeStyles();
const {shouldUseNarrowLayout} = useResponsiveLayout();
const {translate} = useLocalize();
- const [travelSettings] = useOnyx(ONYXKEYS.NVP_TRAVEL_SETTINGS);
const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID);
- const [account] = useOnyx(ONYXKEYS.ACCOUNT);
const policy = usePolicy(activePolicyID);
const [ctaErrorMessage, setCtaErrorMessage] = useState('');
@@ -46,9 +41,6 @@ function ManageTrips() {
return ;
}
- const hasAcceptedTravelTerms = travelSettings?.hasAcceptedTerms;
- const hasPolicyAddress = !isEmptyObject(policy?.address);
-
const navigateToBookTravelDemo = () => {
Linking.openURL(CONST.BOOK_TRAVEL_DEMO_URL);
};
@@ -63,24 +55,7 @@ function ManageTrips() {
ctaText={translate('travel.bookTravel')}
ctaAccessibilityLabel={translate('travel.bookTravel')}
onCtaPress={() => {
- if (Str.isSMSLogin(account?.primaryLogin ?? '')) {
- setCtaErrorMessage(translate('travel.phoneError'));
- return;
- }
- if (!hasPolicyAddress) {
- Navigation.navigate(ROUTES.WORKSPACE_PROFILE_ADDRESS.getRoute(activePolicyID ?? '-1', Navigation.getActiveRoute()));
- return;
- }
- if (!hasAcceptedTravelTerms) {
- Navigation.navigate(ROUTES.TRAVEL_TCS);
- return;
- }
- if (ctaErrorMessage) {
- setCtaErrorMessage('');
- }
- Link.openTravelDotLink(activePolicyID)?.catch(() => {
- setCtaErrorMessage(translate('travel.errorMessage'));
- });
+ TripsResevationUtils.bookATrip(translate, setCtaErrorMessage, ctaErrorMessage);
}}
ctaErrorMessage={ctaErrorMessage}
illustration={LottieAnimations.TripsEmptyState}
diff --git a/src/pages/WorkspaceSwitcherPage/WorkspacesSectionHeader.tsx b/src/pages/WorkspaceSwitcherPage/WorkspacesSectionHeader.tsx
index 18c3be9678c4..9c714e54704c 100644
--- a/src/pages/WorkspaceSwitcherPage/WorkspacesSectionHeader.tsx
+++ b/src/pages/WorkspaceSwitcherPage/WorkspacesSectionHeader.tsx
@@ -30,7 +30,7 @@ function WorkspacesSectionHeader() {
{
const activeRoute = Navigation.getActiveRouteWithoutParams();
diff --git a/src/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx
index 0f4da0a0e302..180f7da970aa 100644
--- a/src/pages/home/HeaderView.tsx
+++ b/src/pages/home/HeaderView.tsx
@@ -66,7 +66,7 @@ function HeaderView({report, parentReportAction, reportID, onNavigationMenuButto
const isSelfDM = ReportUtils.isSelfDM(report);
const isGroupChat = ReportUtils.isGroupChat(report) || ReportUtils.isDeprecatedGroupDM(report);
- const participants = ReportUtils.getParticipantsAccountIDsForDisplay(report).slice(0, 5);
+ const participants = ReportUtils.getParticipantsAccountIDsForDisplay(report, false, true).slice(0, 5);
const isMultipleParticipant = participants.length > 1;
const participantPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(participants, personalDetails);
@@ -164,7 +164,7 @@ function HeaderView({report, parentReportAction, reportID, onNavigationMenuButto
)}
ReportUtils.navigateToDetailsPage(report)}
+ onPress={() => ReportUtils.navigateToDetailsPage(report, Navigation.getReportRHPActiveRoute())}
style={[styles.flexRow, styles.alignItemsCenter, styles.flex1]}
disabled={shouldDisableDetailPage}
accessibilityLabel={title}
@@ -218,11 +218,12 @@ function HeaderView({report, parentReportAction, reportID, onNavigationMenuButto
{isChatRoom && !!reportDescription && isEmptyObject(parentNavigationSubtitleData) && (
{
+ const activeRoute = Navigation.getReportRHPActiveRoute();
if (ReportUtils.canEditReportDescription(report, policy)) {
- Navigation.navigate(ROUTES.REPORT_DESCRIPTION.getRoute(reportID));
+ Navigation.navigate(ROUTES.REPORT_DESCRIPTION.getRoute(reportID, activeRoute));
return;
}
- Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(reportID));
+ Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(reportID, activeRoute));
}}
style={[styles.alignSelfStart, styles.mw100]}
accessibilityLabel={translate('reportDescriptionPage.roomDescription')}
@@ -242,7 +243,7 @@ function HeaderView({report, parentReportAction, reportID, onNavigationMenuButto
Navigation.navigate(ROUTES.WORKSPACE_PROFILE_DESCRIPTION.getRoute(report.policyID ?? '-1'));
return;
}
- Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(reportID));
+ Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(reportID, Navigation.getReportRHPActiveRoute()));
}}
style={[styles.alignSelfStart, styles.mw100]}
accessibilityLabel={translate('workspace.editor.descriptionInputLabel')}
diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx
index af4250bda48c..69c4402e959b 100644
--- a/src/pages/home/ReportScreen.tsx
+++ b/src/pages/home/ReportScreen.tsx
@@ -211,6 +211,7 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro
permissions,
invoiceReceiver: reportOnyx.invoiceReceiver,
policyAvatar: reportOnyx.policyAvatar,
+ pendingChatMembers: reportOnyx.pendingChatMembers,
},
[reportOnyx, permissions],
);
@@ -298,7 +299,6 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro
report={report}
policy={policy}
parentReportAction={parentReportAction}
- shouldUseNarrowLayout={shouldUseNarrowLayout}
onBackButtonPress={onBackButtonPress}
/>
);
@@ -318,7 +318,6 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro
policy={policy}
transactionThreadReportID={transactionThreadReportID}
reportActions={reportActions}
- shouldUseNarrowLayout={shouldUseNarrowLayout}
onBackButtonPress={onBackButtonPress}
/>
);
@@ -695,6 +694,7 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro
const lastRoute = usePrevious(route);
const lastReportActionIDFromRoute = usePrevious(reportActionIDFromRoute);
+
// Define here because reportActions are recalculated before mount, allowing data to display faster than useEffect can trigger.
// If we have cached reportActions, they will be shown immediately.
// We aim to display a loader first, then fetch relevant reportActions, and finally show them.
diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx
index 7053509b63e4..3248f400d1bc 100644
--- a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx
+++ b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx
@@ -556,12 +556,13 @@ const ContextMenuActions: ContextMenuAction[] = [
!isChronosReport &&
reportAction?.actorAccountID !== CONST.ACCOUNT_ID.CONCIERGE,
onPress: (closePopover, {reportID, reportAction}) => {
+ 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/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
index 2bbf04ec3d2b..69bceec2bd9f 100644
--- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
+++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
@@ -4,7 +4,7 @@ import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 're
import type {MeasureInWindowOnSuccessCallback, NativeSyntheticEvent, TextInputFocusEventData, TextInputSelectionChangeEventData} from 'react-native';
import {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
-import {withOnyx} from 'react-native-onyx';
+import {useOnyx} from 'react-native-onyx';
import {runOnUI, useSharedValue} from 'react-native-reanimated';
import type {Emoji} from '@assets/emojis/types';
import type {FileObject} from '@components/AttachmentModal';
@@ -63,16 +63,7 @@ type SuggestionsRef = {
getIsSuggestionsMenuVisible: () => boolean;
};
-type ReportActionComposeOnyxProps = {
- /** The NVP describing a user's block status */
- blockedFromConcierge: OnyxEntry;
-
- /** Whether the composer input should be shown */
- shouldShowComposeInput: OnyxEntry;
-};
-
-type ReportActionComposeProps = ReportActionComposeOnyxProps &
- WithCurrentUserPersonalDetailsProps &
+type ReportActionComposeProps = WithCurrentUserPersonalDetailsProps &
Pick & {
/** A method to call when the form is submitted */
onSubmit: (newComment: string) => void;
@@ -109,7 +100,6 @@ const willBlurTextInputOnTapOutside = willBlurTextInputOnTapOutsideFunc();
let onSubmitAction = noop;
function ReportActionCompose({
- blockedFromConcierge,
currentUserPersonalDetails,
disabled = false,
isComposerFullSize = false,
@@ -117,7 +107,6 @@ function ReportActionCompose({
pendingAction,
report,
reportID,
- shouldShowComposeInput = true,
isReportReadyForDisplay = true,
isEmptyChat,
lastReportAction,
@@ -133,7 +122,8 @@ function ReportActionCompose({
const actionButtonRef = useRef(null);
const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT;
const navigation = useNavigation();
-
+ const [blockedFromConcierge] = useOnyx(ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE);
+ const [shouldShowComposeInput = true] = useOnyx(ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT);
/**
* Updates the Highlight state of the composer
*/
@@ -425,7 +415,7 @@ function ReportActionCompose({
shouldRender={!shouldHideEducationalTooltip && shouldShowEducationalTooltip}
renderTooltipContent={renderWorkspaceChatTooltip}
shouldUseOverlay
- onPressOverlay={() => User.dismissWorkspaceTooltip()}
+ onHideTooltip={() => User.dismissWorkspaceTooltip()}
anchorAlignment={{
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT,
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM,
@@ -571,15 +561,6 @@ function ReportActionCompose({
ReportActionCompose.displayName = 'ReportActionCompose';
-export default withCurrentUserPersonalDetails(
- withOnyx({
- blockedFromConcierge: {
- key: ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE,
- },
- shouldShowComposeInput: {
- key: ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT,
- },
- })(memo(ReportActionCompose)),
-);
+export default withCurrentUserPersonalDetails(memo(ReportActionCompose));
export {onSubmitAction};
export type {SuggestionsRef, ComposerRef};
diff --git a/src/pages/home/report/ReportActionItemCreated.tsx b/src/pages/home/report/ReportActionItemCreated.tsx
index ec9f5ea9915f..4d4a560953ed 100644
--- a/src/pages/home/report/ReportActionItemCreated.tsx
+++ b/src/pages/home/report/ReportActionItemCreated.tsx
@@ -10,6 +10,7 @@ import ReportWelcomeText from '@components/ReportWelcomeText';
import useLocalize from '@hooks/useLocalize';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
+import Navigation from '@libs/Navigation/Navigation';
import * as ReportUtils from '@libs/ReportUtils';
import {navigateToConciergeChatAndDeleteReport} from '@userActions/Report';
import CONST from '@src/CONST';
@@ -69,7 +70,7 @@ function ReportActionItemCreated({report, personalDetails, policy, reportID}: Re
>
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 380355d08361..ac40889e6c6e 100644
--- a/src/pages/home/report/ReportActionItemSingle.tsx
+++ b/src/pages/home/report/ReportActionItemSingle.tsx
@@ -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({
@@ -81,19 +81,20 @@ function ReportActionItemSingle({
const StyleUtils = useStyleUtils();
const {translate} = useLocalize();
const personalDetails = usePersonalDetails() ?? CONST.EMPTY_OBJECT;
- const actorAccountID = ReportUtils.getReportActionActorAccountID(action, iouReport);
+ const actorAccountID = ReportUtils.getReportActionActorAccountID(action);
const [invoiceReceiverPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${report?.invoiceReceiver && 'policyID' in report.invoiceReceiver ? report.invoiceReceiver.policyID : -1}`);
-
let displayName = ReportUtils.getDisplayNameForParticipant(actorAccountID);
+ const icons = ReportUtils.getIcons(iouReport ?? null, personalDetails);
const {avatar, login, pendingFields, status, fallbackIcon} = personalDetails[actorAccountID ?? -1] ?? {};
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
let actorHint = (login || (displayName ?? '')).replace(CONST.REGEX.MERGED_ACCOUNT_PREFIX, '');
const isTripRoom = ReportUtils.isTripRoom(report);
const isReportPreviewAction = action?.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW;
- const displayAllActors = isReportPreviewAction && !isTripRoom;
+ const displayAllActors = isReportPreviewAction && !isTripRoom && ReportUtils.isIOUReport(iouReport ?? null) && icons.length > 1;
const isInvoiceReport = ReportUtils.isInvoiceReport(iouReport ?? null);
const isWorkspaceActor = isInvoiceReport || (ReportUtils.isPolicyExpenseChat(report) && (!actorAccountID || displayAllActors));
const ownerAccountID = iouReport?.ownerAccountID ?? action?.childOwnerAccountID;
+ const managerID = iouReport?.managerID ?? action?.childManagerAccountID;
let avatarSource = avatar;
let avatarId: number | string | undefined = actorAccountID;
@@ -130,10 +131,9 @@ function ReportActionItemSingle({
};
} else {
// The ownerAccountID and actorAccountID can be the same if a user submits an expense back from the IOU's original creator, in that case we need to use managerID to avoid displaying the same user twice
- const secondaryAccountId = ownerAccountID === actorAccountID || isInvoiceReport ? actorAccountID : ownerAccountID;
+ const secondaryAccountId = ownerAccountID === actorAccountID || isInvoiceReport ? managerID : ownerAccountID;
const secondaryUserAvatar = personalDetails?.[secondaryAccountId ?? -1]?.avatar ?? FallbackAvatar;
const secondaryDisplayName = ReportUtils.getDisplayNameForParticipant(secondaryAccountId);
-
secondaryAvatar = {
source: secondaryUserAvatar,
type: CONST.ICON_TYPE_AVATAR,
@@ -150,24 +150,41 @@ function ReportActionItemSingle({
} else {
secondaryAvatar = {name: '', source: '', type: 'avatar'};
}
- const icon = {
- source: avatarSource ?? FallbackAvatar,
- type: isWorkspaceActor ? CONST.ICON_TYPE_WORKSPACE : CONST.ICON_TYPE_AVATAR,
- name: primaryDisplayName ?? '',
- id: avatarId,
- };
+
+ const icon = useMemo(
+ () => ({
+ source: avatarSource ?? FallbackAvatar,
+ type: isWorkspaceActor ? CONST.ICON_TYPE_WORKSPACE : CONST.ICON_TYPE_AVATAR,
+ name: primaryDisplayName ?? '',
+ id: avatarId,
+ }),
+ [avatarSource, isWorkspaceActor, primaryDisplayName, avatarId],
+ );
// Since the display name for a report action message is delivered with the report history as an array of fragments
// we'll need to take the displayName from personal details and have it be in the same format for now. Eventually,
// we should stop referring to the report history items entirely for this information.
- const personArray = displayName
- ? [
- {
- type: 'TEXT',
- text: displayName,
- },
- ]
- : action?.person;
+ const personArray = useMemo(() => {
+ const baseArray = displayName
+ ? [
+ {
+ type: 'TEXT',
+ text: displayName,
+ },
+ ]
+ : action?.person ?? [];
+
+ if (displayAllActors) {
+ return [
+ ...baseArray,
+ {
+ type: 'TEXT',
+ text: secondaryAvatar.name ?? '',
+ },
+ ];
+ }
+ return baseArray;
+ }, [displayName, action?.person, displayAllActors, secondaryAvatar?.name]);
const reportID = report?.reportID;
const iouReportID = iouReport?.reportID;
@@ -178,7 +195,7 @@ function ReportActionItemSingle({
} 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));
@@ -192,45 +209,130 @@ function ReportActionItemSingle({
[action, isWorkspaceActor, actorAccountID],
);
- const getAvatar = () => {
- if (displayAllActors) {
+ const getAvatar = useMemo(() => {
+ return () => {
+ if (displayAllActors) {
+ return (
+
+ );
+ }
+ if (shouldShowSubscriptAvatar) {
+ return (
+
+ );
+ }
return (
-
+
+
+
+
+
);
- }
- if (shouldShowSubscriptAvatar) {
+ };
+ }, [
+ displayAllActors,
+ shouldShowSubscriptAvatar,
+ actorAccountID,
+ action?.delegateAccountID,
+ icon,
+ styles.actionAvatar,
+ fallbackIcon,
+ icons,
+ StyleUtils,
+ theme.appBG,
+ theme.hoverComponentBG,
+ theme.componentBG,
+ isHovered,
+ secondaryAvatar,
+ ]);
+
+ const getHeading = useMemo(() => {
+ return () => {
+ if (displayAllActors && secondaryAvatar.name && isReportPreviewAction) {
+ return (
+
+
+
+ {` & `}
+
+
+
+ );
+ }
return (
-
- );
- }
- return (
-
-
+ {personArray?.map((fragment) => (
+
+ ))}
-
- );
- };
+ );
+ };
+ }, [
+ displayAllActors,
+ secondaryAvatar,
+ isReportPreviewAction,
+ personArray,
+ styles.flexRow,
+ styles.flex1,
+ styles.chatItemMessageHeaderSender,
+ styles.pre,
+ action,
+ actorAccountID,
+ displayName,
+ icon,
+ ]);
+
const hasEmojiStatus = !displayAllActors && status?.emojiCode;
const formattedDate = DateUtils.getStatusUntilDate(status?.clearAfter ?? '');
const statusText = status?.text ?? '';
@@ -261,18 +363,7 @@ function ReportActionItemSingle({
accessibilityLabel={actorHint}
role={CONST.ROLE.BUTTON}
>
- {personArray?.map((fragment, index) => (
-
- ))}
+ {getHeading()}
{!!hasEmojiStatus && (
@@ -290,7 +381,5 @@ function ReportActionItemSingle({
);
}
-
ReportActionItemSingle.displayName = 'ReportActionItemSingle';
-
export default ReportActionItemSingle;
diff --git a/src/pages/home/report/ReportActionsListItemRenderer.tsx b/src/pages/home/report/ReportActionsListItemRenderer.tsx
index ff1c2431ca8b..63b2cb43d836 100644
--- a/src/pages/home/report/ReportActionsListItemRenderer.tsx
+++ b/src/pages/home/report/ReportActionsListItemRenderer.tsx
@@ -171,7 +171,7 @@ function ReportActionsListItemRenderer({
displayAsGroup={displayAsGroup}
shouldDisplayNewMarker={shouldDisplayNewMarker}
shouldShowSubscriptAvatar={
- ReportUtils.isPolicyExpenseChat(report) &&
+ (ReportUtils.isPolicyExpenseChat(report) || ReportUtils.isInvoiceRoom(report)) &&
[
CONST.REPORT.ACTIONS.TYPE.IOU,
CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW,
diff --git a/src/pages/home/report/ReportActionsView.tsx b/src/pages/home/report/ReportActionsView.tsx
index 1460942931fc..270a241778e1 100755
--- a/src/pages/home/report/ReportActionsView.tsx
+++ b/src/pages/home/report/ReportActionsView.tsx
@@ -4,7 +4,7 @@ import lodashIsEqual from 'lodash/isEqual';
import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react';
import {InteractionManager} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
-import {withOnyx} from 'react-native-onyx';
+import {useOnyx} from 'react-native-onyx';
import useCopySelectionHelper from '@hooks/useCopySelectionHelper';
import useInitialValue from '@hooks/useInitialValue';
import useNetwork from '@hooks/useNetwork';
@@ -34,18 +34,7 @@ import PopoverReactionList from './ReactionList/PopoverReactionList';
import ReportActionsList from './ReportActionsList';
import UserTypingEventListener from './UserTypingEventListener';
-type ReportActionsViewOnyxProps = {
- /** Session info for the currently logged in user. */
- session: OnyxEntry;
-
- /** Array of report actions for the transaction thread report associated with the current report */
- transactionThreadReportActions: OnyxTypes.ReportAction[];
-
- /** The transaction thread report associated with the current report, if any */
- transactionThreadReport: OnyxEntry;
-};
-
-type ReportActionsViewProps = ReportActionsViewOnyxProps & {
+type ReportActionsViewProps = {
/** The report currently being looked at */
report: OnyxTypes.Report;
@@ -79,11 +68,8 @@ let listOldID = Math.round(Math.random() * 100);
function ReportActionsView({
report,
- transactionThreadReport,
- session,
parentReportAction,
reportActions: allReportActions = [],
- transactionThreadReportActions = [],
isLoadingInitialReportActions = false,
isLoadingOlderReportActions = false,
hasLoadingOlderReportActionsError = false,
@@ -94,6 +80,11 @@ function ReportActionsView({
useCopySelectionHelper();
const reactionListRef = useContext(ReactionListContext);
const route = useRoute>();
+ const [session] = useOnyx(ONYXKEYS.SESSION);
+ const [transactionThreadReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportID ?? -1}`, {
+ selector: (reportActions: OnyxEntry) => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, true),
+ });
+ const [transactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID ?? -1}`);
const reportActionID = route?.params?.reportActionID;
const prevReportActionID = usePrevious(reportActionID);
const didLayout = useRef(false);
@@ -211,7 +202,7 @@ function ReportActionsView({
// Get a sorted array of reportActions for both the current report and the transaction thread report associated with this report (if there is one)
// so that we display transaction-level and report-level report actions in order in the one-transaction view
const combinedReportActions = useMemo(
- () => ReportActionsUtils.getCombinedReportActions(reportActionsToDisplay, transactionThreadReportID ?? null, transactionThreadReportActions),
+ () => ReportActionsUtils.getCombinedReportActions(reportActionsToDisplay, transactionThreadReportID ?? null, transactionThreadReportActions ?? []),
[reportActionsToDisplay, transactionThreadReportActions, transactionThreadReportID],
);
@@ -503,26 +494,14 @@ ReportActionsView.displayName = 'ReportActionsView';
ReportActionsView.initMeasured = false;
function arePropsEqual(oldProps: ReportActionsViewProps, newProps: ReportActionsViewProps): boolean {
- if (!lodashIsEqual(oldProps.transactionThreadReport, newProps.transactionThreadReport)) {
- return false;
- }
-
if (!lodashIsEqual(oldProps.reportActions, newProps.reportActions)) {
return false;
}
- if (!lodashIsEqual(oldProps.transactionThreadReportActions, newProps.transactionThreadReportActions)) {
- return false;
- }
-
if (!lodashIsEqual(oldProps.parentReportAction, newProps.parentReportAction)) {
return false;
}
- if (oldProps.session?.authTokenType !== newProps.session?.authTokenType) {
- return false;
- }
-
if (oldProps.isLoadingInitialReportActions !== newProps.isLoadingInitialReportActions) {
return false;
}
@@ -548,19 +527,4 @@ function arePropsEqual(oldProps: ReportActionsViewProps, newProps: ReportActions
const MemoizedReportActionsView = React.memo(ReportActionsView, arePropsEqual);
-export default Performance.withRenderTrace({id: ' rendering'})(
- withOnyx({
- session: {
- key: ONYXKEYS.SESSION,
- },
- transactionThreadReportActions: {
- key: ({transactionThreadReportID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportID ?? -1}`,
- canEvict: false,
- selector: (reportActions: OnyxEntry) => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, true),
- },
- transactionThreadReport: {
- key: ({transactionThreadReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID ?? -1}`,
- initialValue: {} as OnyxTypes.Report,
- },
- })(MemoizedReportActionsView),
-);
+export default Performance.withRenderTrace({id: ' rendering'})(MemoizedReportActionsView);
diff --git a/src/pages/home/report/ReportDetailsExportPage.tsx b/src/pages/home/report/ReportDetailsExportPage.tsx
index 6f16786682af..bed7569cf888 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 efee188d0beb..4caa29209fa9 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 c7cc6961b764..7947ab0a04b4 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 efefc8ccc9ea..b696fb38ff00 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/MoneyRequestParticipantsSelector.tsx b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx
index f10575f8c1d0..0168133154ee 100644
--- a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx
+++ b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx
@@ -169,9 +169,10 @@ function MoneyRequestParticipantsSelector({participants = CONST.EMPTY_ARRAY, onF
excludeLogins: CONST.EXPENSIFY_EMAILS,
maxRecentReportsToShow: CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW,
preferPolicyExpenseChat: isPaidGroupPolicy,
+ preferRecentExpenseReports: action === CONST.IOU.ACTION.CREATE,
});
return newOptions;
- }, [areOptionsInitialized, defaultOptions, debouncedSearchTerm, participants, isPaidGroupPolicy, canUseP2PDistanceRequests, iouRequestType, isCategorizeOrShareAction]);
+ }, [areOptionsInitialized, defaultOptions, debouncedSearchTerm, participants, isPaidGroupPolicy, canUseP2PDistanceRequests, iouRequestType, isCategorizeOrShareAction, action]);
/**
* Returns the sections needed for the OptionsSelector
@@ -257,8 +258,7 @@ function MoneyRequestParticipantsSelector({participants = CONST.EMPTY_ARRAY, onF
];
if (iouType === CONST.IOU.TYPE.INVOICE) {
- // TODO: Use getInvoicePrimaryWorkspace when the invoices screen is ready - https://github.com/Expensify/App/issues/45175.
- const policyID = option.item && ReportUtils.isInvoiceRoom(option.item) ? option.policyID : Policy.getPrimaryPolicy(activePolicyID, currentUserLogin)?.id;
+ const policyID = option.item && ReportUtils.isInvoiceRoom(option.item) ? option.policyID : Policy.getInvoicePrimaryWorkspace(activePolicyID, currentUserLogin)?.id;
newParticipants.push({
policyID,
isSender: true,
diff --git a/src/pages/iou/request/step/IOURequestStepAmount.tsx b/src/pages/iou/request/step/IOURequestStepAmount.tsx
index a03c40e8c1f3..2a0aa438fe98 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/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx
index 6c1457abef62..b08f9a6ced5f 100644
--- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx
+++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx
@@ -1,7 +1,7 @@
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
-import {withOnyx} from 'react-native-onyx';
+import {useOnyx} from 'react-native-onyx';
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import * as Expensicons from '@components/Icon/Expensicons';
@@ -32,7 +32,6 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
-import type {Policy, PolicyCategories, PolicyTagLists} from '@src/types/onyx';
import type {Participant} from '@src/types/onyx/IOU';
import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';
import type {Receipt} from '@src/types/onyx/Transaction';
@@ -41,33 +40,10 @@ import withFullTransactionOrNotFound from './withFullTransactionOrNotFound';
import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound';
import withWritableReportOrNotFound from './withWritableReportOrNotFound';
-type IOURequestStepConfirmationOnyxProps = {
- /** The policy of the report */
- policy: OnyxEntry;
-
- /** The draft policy of the report */
- policyDraft: OnyxEntry;
-
- /** The category configuration of the report's policy */
- policyCategories: OnyxEntry;
-
- /** The draft category configuration of the report's policy */
- policyCategoriesDraft: OnyxEntry;
-
- /** The tag configuration of the report's policy */
- policyTags: OnyxEntry;
-};
-
-type IOURequestStepConfirmationProps = IOURequestStepConfirmationOnyxProps &
- WithWritableReportOrNotFoundProps &
+type IOURequestStepConfirmationProps = WithWritableReportOrNotFoundProps &
WithFullTransactionOrNotFoundProps;
function IOURequestStepConfirmation({
- policy: policyReal,
- policyDraft,
- policyTags,
- policyCategories: policyCategoriesReal,
- policyCategoriesDraft,
report: reportReal,
reportDraft,
route: {
@@ -78,6 +54,12 @@ function IOURequestStepConfirmation({
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT;
+ const [policyDraft] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_DRAFTS}${IOU.getIOURequestPolicyID(transaction, reportDraft)}`);
+ const [policyReal] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${IOU.getIOURequestPolicyID(transaction, reportReal)}`);
+ const [policyCategoriesReal] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${IOU.getIOURequestPolicyID(transaction, reportReal)}`);
+ const [policyCategoriesDraft] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${IOU.getIOURequestPolicyID(transaction, reportDraft)}`);
+ const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${IOU.getIOURequestPolicyID(transaction, reportReal)}`);
+
const report = reportReal ?? reportDraft;
const policy = policyReal ?? policyDraft;
const policyCategories = policyCategoriesReal ?? policyCategoriesDraft;
@@ -168,7 +150,11 @@ function IOURequestStepConfirmation({
if (policyExpenseChat?.policyID && policy?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD) {
openDraftWorkspaceRequest(policyExpenseChat.policyID);
}
- }, [isOffline, participants, transaction?.billable, policy, transactionID]);
+ const senderPolicyParticipant = participants?.find((participant) => !!participant && 'isSender' in participant && participant.isSender);
+ if (senderPolicyParticipant?.policyID && policy?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD) {
+ openDraftWorkspaceRequest(senderPolicyParticipant.policyID);
+ }
+ }, [isOffline, participants, policy?.pendingAction]);
const defaultBillable = !!policy?.defaultBillable;
useEffect(() => {
@@ -660,25 +646,8 @@ function IOURequestStepConfirmation({
IOURequestStepConfirmation.displayName = 'IOURequestStepConfirmation';
-const IOURequestStepConfirmationWithOnyx = withOnyx({
- policy: {
- key: ({report, transaction}) => `${ONYXKEYS.COLLECTION.POLICY}${IOU.getIOURequestPolicyID(transaction, report)}`,
- },
- policyDraft: {
- key: ({reportDraft, transaction}) => `${ONYXKEYS.COLLECTION.POLICY_DRAFTS}${IOU.getIOURequestPolicyID(transaction, reportDraft)}`,
- },
- policyCategories: {
- key: ({report, transaction}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${IOU.getIOURequestPolicyID(transaction, report)}`,
- },
- policyCategoriesDraft: {
- key: ({reportDraft, transaction}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES_DRAFT}${IOU.getIOURequestPolicyID(transaction, reportDraft)}`,
- },
- policyTags: {
- key: ({report, transaction}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${IOU.getIOURequestPolicyID(transaction, report)}`,
- },
-})(IOURequestStepConfirmation);
/* eslint-disable rulesdir/no-negated-variables */
-const IOURequestStepConfirmationWithFullTransactionOrNotFound = withFullTransactionOrNotFound(IOURequestStepConfirmationWithOnyx);
+const IOURequestStepConfirmationWithFullTransactionOrNotFound = withFullTransactionOrNotFound(IOURequestStepConfirmation);
/* eslint-disable rulesdir/no-negated-variables */
const IOURequestStepConfirmationWithWritableReportOrNotFound = withWritableReportOrNotFound(IOURequestStepConfirmationWithFullTransactionOrNotFound);
export default IOURequestStepConfirmationWithWritableReportOrNotFound;
diff --git a/src/pages/iou/request/step/IOURequestStepCurrency.tsx b/src/pages/iou/request/step/IOURequestStepCurrency.tsx
index b51d6a8998a4..c48bf91986f4 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/iou/request/step/IOURequestStepSendFrom.tsx b/src/pages/iou/request/step/IOURequestStepSendFrom.tsx
index a5279bd14bdb..40699d5f8d2b 100644
--- a/src/pages/iou/request/step/IOURequestStepSendFrom.tsx
+++ b/src/pages/iou/request/step/IOURequestStepSendFrom.tsx
@@ -1,6 +1,5 @@
import React, {useMemo} from 'react';
-import {useOnyx, withOnyx} from 'react-native-onyx';
-import type {OnyxCollection} from 'react-native-onyx';
+import {useOnyx} from 'react-native-onyx';
import * as Expensicons from '@components/Icon/Expensicons';
import SelectionList from '@components/SelectionList';
import type {ListItem} from '@components/SelectionList/types';
@@ -14,7 +13,6 @@ import * as IOU from '@userActions/IOU';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type SCREENS from '@src/SCREENS';
-import type {Policy} from '@src/types/onyx';
import StepScreenWrapper from './StepScreenWrapper';
import withFullTransactionOrNotFound from './withFullTransactionOrNotFound';
import type {WithFullTransactionOrNotFoundProps} from './withFullTransactionOrNotFound';
@@ -25,26 +23,19 @@ type WorkspaceListItem = ListItem & {
value: string;
};
-type IOURequestStepSendFromOnyxProps = {
- /** The list of all policies */
- allPolicies: OnyxCollection;
-};
-
-type IOURequestStepSendFromProps = IOURequestStepSendFromOnyxProps &
- WithWritableReportOrNotFoundProps &
+type IOURequestStepSendFromProps = WithWritableReportOrNotFoundProps &
WithFullTransactionOrNotFoundProps;
-function IOURequestStepSendFrom({route, transaction, allPolicies}: IOURequestStepSendFromProps) {
+function IOURequestStepSendFrom({route, transaction}: IOURequestStepSendFromProps) {
const {translate} = useLocalize();
const {transactionID, backTo} = route.params;
const [currentUserLogin] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => session?.email});
+ const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY);
const selectedWorkspace = useMemo(() => transaction?.participants?.find((participant) => participant.isSender), [transaction]);
const workspaceOptions: WorkspaceListItem[] = useMemo(() => {
- const availableWorkspaces = PolicyUtils.getActiveAdminWorkspaces(allPolicies, currentUserLogin);
- // TODO: Uncomment the following line when the invoices screen is ready - https://github.com/Expensify/App/issues/45175.
- // .filter((policy) => PolicyUtils.canSendInvoiceFromWorkspace(policy.id));
+ const availableWorkspaces = PolicyUtils.getActiveAdminWorkspaces(allPolicies, currentUserLogin).filter((policy) => PolicyUtils.canSendInvoiceFromWorkspace(policy.id));
return availableWorkspaces
.sort((policy1, policy2) => sortWorkspacesBySelected({policyID: policy1.id, name: policy1.name}, {policyID: policy2.id, name: policy2.name}, selectedWorkspace?.policyID))
@@ -103,12 +94,4 @@ function IOURequestStepSendFrom({route, transaction, allPolicies}: IOURequestSte
IOURequestStepSendFrom.displayName = 'IOURequestStepSendFrom';
-export default withWritableReportOrNotFound(
- withFullTransactionOrNotFound(
- withOnyx({
- allPolicies: {
- key: ONYXKEYS.COLLECTION.POLICY,
- },
- })(IOURequestStepSendFrom),
- ),
-);
+export default withWritableReportOrNotFound(withFullTransactionOrNotFound(IOURequestStepSendFrom));
diff --git a/src/pages/iou/request/step/IOURequestStepTag.tsx b/src/pages/iou/request/step/IOURequestStepTag.tsx
index 6cf9e39d6015..90731e732d50 100644
--- a/src/pages/iou/request/step/IOURequestStepTag.tsx
+++ b/src/pages/iou/request/step/IOURequestStepTag.tsx
@@ -1,8 +1,13 @@
import React, {useMemo} from 'react';
-import type {OnyxEntry} from 'react-native-onyx';
-import {withOnyx} from 'react-native-onyx';
+import {View} from 'react-native';
+import {useOnyx} from 'react-native-onyx';
+import Button from '@components/Button';
+import FixedFooter from '@components/FixedFooter';
+import * as Illustrations from '@components/Icon/Illustrations';
+import {useSession} from '@components/OnyxProvider';
import TagPicker from '@components/TagPicker';
import Text from '@components/Text';
+import WorkspaceEmptyStateSection from '@components/WorkspaceEmptyStateSection';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import * as IOUUtils from '@libs/IOUUtils';
@@ -15,8 +20,8 @@ import * as TransactionUtils from '@libs/TransactionUtils';
import * as IOU from '@userActions/IOU';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
-import type * as OnyxTypes from '@src/types/onyx';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import StepScreenWrapper from './StepScreenWrapper';
import type {WithFullTransactionOrNotFoundProps} from './withFullTransactionOrNotFound';
@@ -24,43 +29,25 @@ import withFullTransactionOrNotFound from './withFullTransactionOrNotFound';
import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound';
import withWritableReportOrNotFound from './withWritableReportOrNotFound';
-type IOURequestStepTagOnyxProps = {
- /** The draft transaction that holds data to be persisted on the current transaction */
- splitDraftTransaction: OnyxEntry;
-
- /** The policy of the report */
- policy: OnyxEntry;
-
- /** The category configuration of the report's policy */
- policyCategories: OnyxEntry;
-
- /** Collection of tags attached to a policy */
- policyTags: OnyxEntry;
-
- /** The actions from the parent report */
- reportActions: OnyxEntry;
-
- /** Session info for the currently logged in user. */
- session: OnyxEntry;
-};
-
-type IOURequestStepTagProps = IOURequestStepTagOnyxProps &
- WithWritableReportOrNotFoundProps &
- WithFullTransactionOrNotFoundProps;
+type IOURequestStepTagProps = WithWritableReportOrNotFoundProps & WithFullTransactionOrNotFoundProps;
function IOURequestStepTag({
- policy,
- policyCategories,
- policyTags,
report,
route: {
params: {action, orderWeight: rawTagIndex, transactionID, backTo, iouType, reportActionID},
},
transaction,
- splitDraftTransaction,
- reportActions,
- session,
}: IOURequestStepTagProps) {
+ const [splitDraftTransaction] = useOnyx(`${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID ?? 0}`);
+ const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '-1'}`);
+ const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${report ? report.policyID : '-1'}`);
+ const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${report ? report.policyID : '-1'}`);
+ let reportID: string | undefined = '-1';
+ if (action === CONST.IOU.ACTION.EDIT) {
+ reportID = iouType === CONST.IOU.TYPE.SPLIT ? report?.reportID : report?.parentReportID;
+ }
+ const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, {canEvict: false});
+ const session = useSession();
const styles = useThemeStyles();
const {translate} = useLocalize();
@@ -77,11 +64,12 @@ function IOURequestStepTag({
const canEditSplitBill = isSplitBill && reportAction && session?.accountID === reportAction.actorAccountID && TransactionUtils.areRequiredFieldsEmpty(transaction);
const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]);
- const shouldShowTag = ReportUtils.isReportInGroupPolicy(report) && (transactionTag || OptionsListUtils.hasEnabledTags(policyTagLists));
+ const shouldShowTag = transactionTag || OptionsListUtils.hasEnabledTags(policyTagLists);
// eslint-disable-next-line rulesdir/no-negated-variables
const shouldShowNotFoundPage =
- !shouldShowTag || (isEditing && (isSplitBill ? !canEditSplitBill : !ReportActionsUtils.isMoneyRequestAction(reportAction) || !ReportUtils.canEditMoneyRequest(reportAction)));
+ !ReportUtils.isReportInGroupPolicy(report) ||
+ (isEditing && (isSplitBill ? !canEditSplitBill : !ReportActionsUtils.isMoneyRequestAction(reportAction) || !ReportUtils.canEditMoneyRequest(reportAction)));
const navigateBack = () => {
Navigation.goBack(backTo);
@@ -113,58 +101,52 @@ function IOURequestStepTag({
testID={IOURequestStepTag.displayName}
shouldShowNotFoundPage={shouldShowNotFoundPage}
>
- <>
- {translate('iou.tagSelection')}
-
- >
+ {!shouldShowTag && (
+
+
+ {PolicyUtils.isPolicyAdmin(policy) && (
+
+
+ Navigation.navigate(
+ ROUTES.SETTINGS_TAGS_ROOT.getRoute(
+ policy?.id ?? '-1',
+ ROUTES.MONEY_REQUEST_STEP_TAG.getRoute(action, iouType, tagListIndex, transactionID, report?.reportID ?? '-1', backTo, reportActionID),
+ ),
+ )
+ }
+ text={translate('workspace.tags.editTags')}
+ pressOnEnter
+ />
+
+ )}
+
+ )}
+ {shouldShowTag && (
+ <>
+ {translate('iou.tagSelection')}
+
+ >
+ )}
);
}
IOURequestStepTag.displayName = 'IOURequestStepTag';
-export default withWritableReportOrNotFound(
- withFullTransactionOrNotFound(
- withOnyx({
- splitDraftTransaction: {
- key: ({route}) => {
- const transactionID = route.params.transactionID ?? 0;
- return `${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`;
- },
- },
- policy: {
- key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '-1'}`,
- },
- policyCategories: {
- key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${report ? report.policyID : '-1'}`,
- },
- policyTags: {
- key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report ? report.policyID : '-1'}`,
- },
- reportActions: {
- key: ({
- report,
- route: {
- params: {action, iouType},
- },
- }) => {
- let reportID: string | undefined = '-1';
- if (action === CONST.IOU.ACTION.EDIT) {
- reportID = iouType === CONST.IOU.TYPE.SPLIT ? report?.reportID : report?.parentReportID;
- }
- return `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`;
- },
- canEvict: false,
- },
- session: {
- key: ONYXKEYS.SESSION,
- },
- })(IOURequestStepTag),
- ),
-);
+export default withWritableReportOrNotFound(withFullTransactionOrNotFound(IOURequestStepTag));
diff --git a/src/pages/iou/request/step/withWritableReportOrNotFound.tsx b/src/pages/iou/request/step/withWritableReportOrNotFound.tsx
index 8df530f3c81c..2361d58dc2be 100644
--- a/src/pages/iou/request/step/withWritableReportOrNotFound.tsx
+++ b/src/pages/iou/request/step/withWritableReportOrNotFound.tsx
@@ -2,7 +2,7 @@ import type {RouteProp} from '@react-navigation/core';
import type {ComponentType, ForwardedRef, RefAttributes} from 'react';
import React, {forwardRef, useEffect} from 'react';
import type {OnyxEntry} from 'react-native-onyx';
-import {withOnyx} from 'react-native-onyx';
+import {useOnyx} from 'react-native-onyx';
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import getComponentDisplayName from '@libs/getComponentDisplayName';
@@ -18,9 +18,6 @@ type WithWritableReportOrNotFoundOnyxProps = {
/** The report corresponding to the reportID in the route params */
report: OnyxEntry;
- /** Whether the reports are loading. When false it means they are ready to be used. */
- isLoadingApp: OnyxEntry;
-
/** The draft report corresponding to the reportID in the route params */
reportDraft: OnyxEntry;
};
@@ -54,13 +51,18 @@ export default function , keyof WithWritableReportOrNotFoundOnyxProps>> {
// eslint-disable-next-line rulesdir/no-negated-variables
- function WithWritableReportOrNotFound(props: TProps, ref: ForwardedRef) {
- const {report = {reportID: ''}, route, isLoadingApp = true, reportDraft} = props;
+ function WithWritableReportOrNotFound(props: Omit, ref: ForwardedRef) {
+ const {route} = props;
+ const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID ?? '-1'}`);
+ const [isLoadingApp = true] = useOnyx(ONYXKEYS.IS_LOADING_APP);
+ const [reportDraft] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_DRAFT}${route.params.reportID ?? '-1'}`);
+
const iouTypeParamIsInvalid = !Object.values(CONST.IOU.TYPE)
.filter((type) => shouldIncludeDeprecatedIOUType || (type !== CONST.IOU.TYPE.REQUEST && type !== CONST.IOU.TYPE.SEND))
.includes(route.params?.iouType);
const isEditing = 'action' in route.params && route.params?.action === CONST.IOU.ACTION.EDIT;
- const canUserPerformWriteAction = ReportUtils.canUserPerformWriteAction(report);
+
+ const canUserPerformWriteAction = ReportUtils.canUserPerformWriteAction(report ?? {reportID: ''});
useEffect(() => {
if (!!report?.reportID || !route.params.reportID || !!reportDraft || !isEditing) {
@@ -81,7 +83,9 @@ export default function
);
@@ -89,17 +93,7 @@ export default function , WithWritableReportOrNotFoundOnyxProps>({
- report: {
- key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID ?? '-1'}`,
- },
- isLoadingApp: {
- key: ONYXKEYS.IS_LOADING_APP,
- },
- reportDraft: {
- key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_DRAFT}${route.params.reportID ?? '-1'}`,
- },
- })(forwardRef(WithWritableReportOrNotFound));
+ return forwardRef(WithWritableReportOrNotFound);
}
export type {WithWritableReportOrNotFoundProps};
diff --git a/src/pages/settings/ExitSurvey/ExitSurveyResponsePage.tsx b/src/pages/settings/ExitSurvey/ExitSurveyResponsePage.tsx
index df89a1719ffe..1fb7cfbc94ab 100644
--- a/src/pages/settings/ExitSurvey/ExitSurveyResponsePage.tsx
+++ b/src/pages/settings/ExitSurvey/ExitSurveyResponsePage.tsx
@@ -1,5 +1,5 @@
import type {StackScreenProps} from '@react-navigation/stack';
-import React, {useCallback, useEffect, useState} from 'react';
+import React, {useCallback, useEffect} from 'react';
import {useOnyx} from 'react-native-onyx';
import FormProvider from '@components/Form/FormProvider';
import InputWrapper from '@components/Form/InputWrapper';
@@ -14,12 +14,9 @@ import useKeyboardState from '@hooks/useKeyboardState';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useSafeAreaInsets from '@hooks/useSafeAreaInsets';
-import useSafePaddingBottomStyle from '@hooks/useSafePaddingBottomStyle';
-import useStyledSafeAreaInsets from '@hooks/useStyledSafeAreaInsets';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
-import * as NumberUtils from '@libs/NumberUtils';
import StatusBar from '@libs/StatusBar';
import updateMultilineInputRange from '@libs/updateMultilineInputRange';
import Navigation from '@navigation/Navigation';
@@ -44,18 +41,10 @@ function ExitSurveyResponsePage({route, navigation}: ExitSurveyResponsePageProps
const {keyboardHeight} = useKeyboardState();
const {windowHeight} = useWindowDimensions();
const {inputCallbackRef, inputRef} = useAutoFocusInput();
- const [headerTitleHeight, setHeaderTitleHeight] = useState(0);
// Device safe area top and bottom insets.
// When the keyboard is shown, the bottom inset doesn't affect the height, so we take it out from the calculation.
- const {top: safeAreaInsetsTop, bottom: safeAreaInsetsBottom} = useSafeAreaInsets();
- const safeAreaInsetsBottomValue = !keyboardHeight ? safeAreaInsetsBottom : 0;
- // FormWrapper bottom padding
- const {paddingBottom: formPaddingBottom} = useStyledSafeAreaInsets();
- const formPaddingBottomValue = formPaddingBottom || styles.pb5.paddingBottom;
- // Extra bottom padding in FormAlertWithSubmitButton
- const safePaddingBottomStyle = useSafePaddingBottomStyle();
- const safePaddingBottomStyleValue = 'paddingBottom' in safePaddingBottomStyle ? (safePaddingBottomStyle.paddingBottom as number) : 0;
+ const {top: safeAreaInsetsTop} = useSafeAreaInsets();
const {reason, backTo} = route.params;
const {isOffline} = useNetwork({
@@ -93,27 +82,6 @@ function ExitSurveyResponsePage({route, navigation}: ExitSurveyResponsePageProps
// Minus the top margins on the form
formTopMarginsStyle.marginTop,
);
- const responseInputMaxHeight = NumberUtils.roundDownToLargestMultiple(
- formMaxHeight -
- safeAreaInsetsBottomValue -
- safePaddingBottomStyleValue -
- formPaddingBottomValue -
- // Minus the height of the text component
- headerTitleHeight -
- // Minus the response input margins (multiplied by 2 to create the effect of margins on top and bottom).
- // marginBottom does not work in this case because the TextInput is in a ScrollView and will push the button beneath it out of view,
- // so it's maxHeight is what dictates space between it and the button.
- baseResponseInputContainerStyle.marginTop * 2 -
- // Minus the approximate size of a default button
- variables.componentSizeLarge -
- // Minus the height above the button for the form error text, accounting for 2 lines max.
- variables.lineHeightNormal * 2 -
- // Minus the margin between the button and the form error text
- styles.mb3.marginBottom,
-
- // Round down to the largest number of full lines
- styles.baseTextInput.lineHeight,
- );
return (
}
{!isOffline && (
<>
- setHeaderTitleHeight(e.nativeEvent.layout.height)}
- >
- {translate(`exitSurvey.prompts.${reason}`)}
-
+ {translate(`exitSurvey.prompts.${reason}`)}
{
if (!el) {
diff --git a/src/pages/settings/Report/NotificationPreferencePage.tsx b/src/pages/settings/Report/NotificationPreferencePage.tsx
index fd256d685139..416d710d4966 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 6a9986b5550f..c407788dce65 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 764ccbff6b3c..a497d793f465 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 5d09bec1788e..07c05d1b8de1 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/Troubleshoot/TroubleshootPage.tsx b/src/pages/settings/Troubleshoot/TroubleshootPage.tsx
index d6d0489f7676..c7c2ca956ae1 100644
--- a/src/pages/settings/Troubleshoot/TroubleshootPage.tsx
+++ b/src/pages/settings/Troubleshoot/TroubleshootPage.tsx
@@ -23,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';
@@ -153,8 +154,20 @@ function TroubleshootPage() {
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)}
diff --git a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.tsx b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.tsx
index f23b136c76d0..f73c0a1602fb 100755
--- a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.tsx
+++ b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.tsx
@@ -2,8 +2,7 @@ import {useIsFocused} from '@react-navigation/native';
import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react';
import type {ForwardedRef} from 'react';
import {View} from 'react-native';
-import type {OnyxEntry} from 'react-native-onyx';
-import {withOnyx} from 'react-native-onyx';
+import {useOnyx} from 'react-native-onyx';
import Button from '@components/Button';
import SafariFormWrapper from '@components/Form/SafariFormWrapper';
import FormHelpMessage from '@components/FormHelpMessage';
@@ -30,24 +29,11 @@ import * as User from '@userActions/User';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
-import type {Account, Credentials, Session} from '@src/types/onyx';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import type ValidateCodeFormProps from './types';
-type BaseValidateCodeFormOnyxProps = {
- /** The details about the account that the user is signing in with */
- account: OnyxEntry;
-
- /** The credentials of the person logging in */
- credentials: OnyxEntry;
-
- /** Session info for the currently logged in user. */
- session: OnyxEntry;
-};
-
type BaseValidateCodeFormProps = WithToggleVisibilityViewProps &
- ValidateCodeFormProps &
- BaseValidateCodeFormOnyxProps & {
+ ValidateCodeFormProps & {
/** Specifies autocomplete hints for the system, so it can provide autofill */
autoComplete: 'sms-otp' | 'one-time-code';
};
@@ -60,10 +46,10 @@ type ValidateCodeFormVariant = 'validateCode' | 'twoFactorAuthCode' | 'recoveryC
type FormError = Partial>;
-function BaseValidateCodeForm(
- {account, credentials, session, autoComplete, isUsingRecoveryCode, setIsUsingRecoveryCode, isVisible}: BaseValidateCodeFormProps,
- forwardedRef: ForwardedRef,
-) {
+function BaseValidateCodeForm({autoComplete, isUsingRecoveryCode, setIsUsingRecoveryCode, isVisible}: BaseValidateCodeFormProps, forwardedRef: ForwardedRef) {
+ const [account] = useOnyx(ONYXKEYS.ACCOUNT);
+ const [credentials] = useOnyx(ONYXKEYS.CREDENTIALS);
+ const [session] = useOnyx(ONYXKEYS.SESSION);
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const {translate} = useLocalize();
@@ -345,10 +331,10 @@ function BaseValidateCodeForm(
)}
{hasError && }
({
- account: {key: ONYXKEYS.ACCOUNT},
- credentials: {key: ONYXKEYS.CREDENTIALS},
- session: {key: ONYXKEYS.SESSION},
- })(forwardRef(BaseValidateCodeForm)),
-);
+export default withToggleVisibilityView(forwardRef(BaseValidateCodeForm));
export type {BaseValidateCodeFormRef};
diff --git a/src/pages/tasks/NewTaskDescriptionPage.tsx b/src/pages/tasks/NewTaskDescriptionPage.tsx
index f18829e8c803..0132442b5978 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 cdc9fc27596a..5afd919cac57 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 230185f2b4f0..acd1329b26ec 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)}
+ />
- {(shouldUseNarrowLayout ? canSelectMultiple : selectedEmployees.length > 0) ? (
-
- shouldAlwaysShowDropdownMenu
- pressOnEnter
- customText={translate('workspace.common.selected', {selectedNumber: selectedEmployees.length})}
- buttonSize={CONST.DROPDOWN_BUTTON_SIZE.MEDIUM}
- onPress={() => null}
- options={getBulkActionsButtonOptions()}
- isSplitButton={false}
- style={[shouldUseNarrowLayout && styles.flexGrow1, shouldUseNarrowLayout && styles.mb3]}
- isDisabled={!selectedEmployees.length}
- />
- ) : (
-
- )}
-
+ return (shouldUseNarrowLayout ? canSelectMultiple : selectedEmployees.length > 0) ? (
+
+ shouldAlwaysShowDropdownMenu
+ pressOnEnter
+ customText={translate('workspace.common.selected', {selectedNumber: selectedEmployees.length})}
+ buttonSize={CONST.DROPDOWN_BUTTON_SIZE.MEDIUM}
+ onPress={() => null}
+ options={getBulkActionsButtonOptions()}
+ isSplitButton={false}
+ style={[shouldUseNarrowLayout && styles.flexGrow1, shouldUseNarrowLayout && styles.mb3]}
+ isDisabled={!selectedEmployees.length}
+ />
+ ) : (
+
);
};
const threeDotsMenuItems = useMemo(() => {
+ if (!isPolicyAdmin) {
+ return [];
+ }
+
const menuItems = [
{
icon: Expensicons.Table,
@@ -596,7 +596,7 @@ function WorkspaceMembersPage({personalDetails, route, policy, currentUserPerson
];
return menuItems;
- }, [policyID, translate, isOffline]);
+ }, [policyID, translate, isOffline, isPolicyAdmin]);
const selectionModeHeader = selectionMode?.isEnabled && shouldUseNarrowLayout;
@@ -610,7 +610,7 @@ function WorkspaceMembersPage({personalDetails, route, policy, currentUserPerson
testID={WorkspaceMembersPage.displayName}
shouldShowLoading={false}
shouldShowOfflineIndicatorInWideScreen
- shouldShowThreeDotsButton
+ shouldShowThreeDotsButton={isPolicyAdmin}
threeDotsMenuItems={threeDotsMenuItems}
threeDotsAnchorPosition={styles.threeDotsPopoverOffsetNoCloseButton(windowWidth)}
shouldShowNonAdmin
diff --git a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx
index 0182f1ea8827..a743140278f7 100644
--- a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx
+++ b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx
@@ -409,11 +409,13 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
title={translate('workspace.common.moreFeatures')}
shouldShowBackButton={shouldUseNarrowLayout}
/>
-
- {translate('workspace.moreFeatures.subtitle')}
-
- {sections.map(renderSection)}
+
+
+ {translate('workspace.moreFeatures.subtitle')}
+
+ {sections.map(renderSection)}
+
;
+ }
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 f82591a4fc04..929870d65280 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/styles/index.ts b/src/styles/index.ts
index a0100405a0bd..5740cd3ff21e 100644
--- a/src/styles/index.ts
+++ b/src/styles/index.ts
@@ -5140,16 +5140,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/ui/PaginationTest.tsx b/tests/ui/PaginationTest.tsx
index 9d120433daa1..bd5ad6b75a0b 100644
--- a/tests/ui/PaginationTest.tsx
+++ b/tests/ui/PaginationTest.tsx
@@ -238,6 +238,10 @@ async function signInAndGetApp(): Promise {
},
});
+ await Onyx.set(ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT, 1);
+
+ await Onyx.set(ONYXKEYS.LAST_OPENED_PUBLIC_ROOM_ID, '1');
+
// We manually setting the sidebar as loaded since the onLayout event does not fire in tests
AppActions.setSidebarLoaded();
});
@@ -339,6 +343,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 +359,7 @@ describe('Pagination', () => {
mockGetNewerActions(0);
scrollToOffset(500);
+ await waitForBatchedUpdatesWithAct();
scrollToOffset(0);
await waitForBatchedUpdatesWithAct();
diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx
index c9cdd81cf9ba..c133e000a51f 100644
--- a/tests/ui/UnreadIndicatorsTest.tsx
+++ b/tests/ui/UnreadIndicatorsTest.tsx
@@ -181,6 +181,10 @@ function signInAndGetAppWithUnreadChat(): Promise {
[USER_B_ACCOUNT_ID]: TestHelper.buildPersonalDetails(USER_B_EMAIL, USER_B_ACCOUNT_ID, 'B'),
});
+ await Onyx.set(ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT, 1);
+
+ await Onyx.set(ONYXKEYS.LAST_OPENED_PUBLIC_ROOM_ID, '1');
+
// We manually setting the sidebar as loaded since the onLayout event does not fire in tests
AppActions.setSidebarLoaded();
return waitForBatchedUpdatesWithAct();
diff --git a/tests/unit/DateUtilsTest.ts b/tests/unit/DateUtilsTest.ts
index f3abed2e3372..52ef39ed4da5 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 4d491db2e53e..45fa83a36734 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');
}