diff --git a/Mobile-Expensify b/Mobile-Expensify
index 7eac4848078b..1c83cec4504d 160000
--- a/Mobile-Expensify
+++ b/Mobile-Expensify
@@ -1 +1 @@
-Subproject commit 7eac4848078b4208ba36a72ce4ce73ec319ff6f8
+Subproject commit 1c83cec4504d7c129db6be88ac190bd017acb12e
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 38049ccacc98..7d7f511abf75 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -114,8 +114,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1009023605
- versionName "9.2.36-5"
+ versionCode 1009023704
+ versionName "9.2.37-4"
// Supported language variants must be declared here to avoid from being removed during the compilation.
// This also helps us to not include unnecessary language variants in the APK.
resConfigs "en", "es"
diff --git a/contributingGuides/NAVIGATION.md b/contributingGuides/NAVIGATION.md
index bad97b5b7494..6926c3da2420 100644
--- a/contributingGuides/NAVIGATION.md
+++ b/contributingGuides/NAVIGATION.md
@@ -16,7 +16,8 @@ The navigation in the app is built on top of the `react-navigation` library. To
- [Debugging](#debugging)
- [Reading state when it changes](#reading-state-when-it-changes)
- [Finding the code that calls the navigation function](#finding-the-code-that-calls-the-navigation-function)
- - [Using `backTo` route param](#using-backto-route-param)
+ - [How to remove backTo from URL](#how-to-remove-backto-from-url)
+ - [Separating routes for each screen instance](#separating-routes-for-each-screen-instance)
- [Generating state from a path](#generating-state-from-a-path)
- [Setting the correct screen underneath RHP](#setting-the-correct-screen-underneath-rhp)
- [Performance solutions](#performance-solutions)
@@ -71,7 +72,7 @@ Navigation.navigate(
);
// Navigation with forceReplace - replaces current screen instead of pushing a new one
-Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID: nextReportID, backTo}), {forceReplace: true});
+Navigation.navigate(ROUTES.SETTINGS_WALLET, {forceReplace: true});
// Navigation with a callback to handle anonymous users
interceptAnonymousUser(() => {
@@ -434,10 +435,13 @@ const handleStateChange = (state: NavigationState | undefined) => {
The easiest way to find the piece of code from which the navigation method was called is to use a debugger and breakpoints. You should attach a breakpoint in the navigation method and check the call stack, this way you can easily find the navigation method that caused the problem.
-## Using `backTo` route param
+## How to remove backTo from URL
> [!WARNING]
-> **Deprecated**: The `backTo` parameter is deprecated and should not be used in new implementations. Most problems that `backTo` solved can be resolved by adding one or more routes for a single screen. If you don't know how to solve your problem, contact someone from the navigation team.
+> **Deprecated**: The `backTo` parameter is deprecated and should not be used in new implementations. Most problems that `backTo` solved can be resolved by adding one or more routes for a single screen. If you don't know how to solve your problem, contact someone from the navigation team. Old documentation on how to use `backTo` can be found below.
+
+
+Using `backTo` route param
When a particular screen can be opened from two or more different pages, we can use `backTo` route parameter to handle such case.
@@ -494,6 +498,96 @@ function NewSettingsScreen({route}: NewSettingsScreenNavigationProps) {
);
}
+```
+
+
+### Separating routes for each screen instance
+
+Often, you will need to reuse a single screen across multiple navigation flows. For example, the `VerifyAccountPage` can be viewed in many different RHP flows. The proper approach to implementing such a mechanism is to create a new route for each screen instance within a single flow.
+
+Considerations when removing `backTo` from a URL:
+
+- For RHP screens, check if the correct central screen is under the overlay after refreshing the page. More information on how to set the default screen underneath RHP can be found (here)[#setting-the-correct-screen-underneath-rhp].
+- Ensure that after refreshing the page and pressing the back button in the application, you return to the page from which you initially accessed the currently displayed screen.
+- If you use the same component for different routes, be sure to define the correct props type. Here's the example of `ReportScreen` that can be viewed in full screen width in the Inbox tab and in the Reports tab in the RHP.
+
+```ts
+type ReportScreenNavigationProps =
+ | PlatformStackScreenProps
+ | PlatformStackScreenProps;
+```
+
+An example of a screen that is reused in several flows is `VerifyAccountPage`.
+
+1. Binding one component to multiple screens.
+
+`src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx`
+
+```ts
+const TravelModalStackNavigator = createModalStackNavigator({
+ // ...
+ [SCREENS.TRAVEL.VERIFY_ACCOUNT]: () => require('../../../../pages/Travel/VerifyAccountPage').default,
+});
+
+const TwoFactorAuthenticatorStackNavigator = createModalStackNavigator({
+ // ...
+ [SCREENS.TWO_FACTOR_AUTH.VERIFY_ACCOUNT]: () => require('../../../../pages/settings/Security/TwoFactorAuth/VerifyAccountPage').default,
+});
+```
+
+2. Custom component behavior depending on the current route.
+
+If we want the component's behavior to change based on the current route, we can extract the component shared by each route and pass properties to it that define the custom behavior, as is done for `VerifyAccountPage`.
+
+`VerifyAccountPageBase` is a shared component that receives `navigateBackTo` and `navigateForwardTo` props defining behavior that is custom across different flows.
+
+Here's an example of reusing this component for Wallet and Travel flows:
+
+1. `src/pages/settings/Wallet/VerifyAccountPage.tsx`.
+
+```ts
+import React from 'react';
+import VerifyAccountPageBase from '@pages/settings/VerifyAccountPageBase';
+import ROUTES from '@src/ROUTES';
+
+function VerifyAccountPage() {
+ return (
+
+ );
+}
+
+VerifyAccountPage.displayName = 'VerifyAccountPage';
+
+export default VerifyAccountPage;
+```
+
+2. `src/pages/Travel/VerifyAccountPage.tsx`.
+
+```ts
+import type {StackScreenProps} from '@react-navigation/stack';
+import React from 'react';
+import type {TravelNavigatorParamList} from '@libs/Navigation/types';
+import VerifyAccountPageBase from '@pages/settings/VerifyAccountPageBase';
+import ROUTES from '@src/ROUTES';
+import type SCREENS from '@src/SCREENS';
+
+type VerifyAccountPageProps = StackScreenProps;
+
+function VerifyAccountPage({route}: VerifyAccountPageProps) {
+ return (
+
+ );
+}
+
+VerifyAccountPage.displayName = 'VerifyAccountPage';
+export default VerifyAccountPage;
+
```
## Generating state from a path
@@ -507,7 +601,7 @@ In Expensify, we use an extended implementation of this function because:
- When opening a link leading to an onboarding screen, all previous screens in this flow have to be present in the navigation state.
- In case of opening the RHP, appropriate screens should be pushed to the navigation to be displayed below the overlay. A guide on how to set up a good screen for RHP can be found [here](#how-to-set-a-correct-screen-below-the-rhp).
- When opening the settings of a specific workspace, the workspace list needs to be pushed to the state.
-- When the `backTo` parameter is in the URL, we need to build a state also for the screen we want to return to.
+- When the `backTo` parameter is in the URL, we need to build a state also for the screen we want to return to. (`backTo` parameter is deprecated, more information can be found [here](#how-to-properly-remove-backto-from-url))
Here are examples how the state is generated based on route:
@@ -623,7 +717,7 @@ In the above example, we can see that when building a state from a link leading
## Setting the correct screen underneath RHP
-RHP screens can usually be opened from a specific central screen. Of course there are cases where one RHP screen can be used in different tabs (then using `backTo` parameter comes in handy). However, most often one RHP screen has a specific central screen assigned underneath.
+RHP screens can usually be opened from a specific central screen. Of course there are cases where one RHP screen can be used in different tabs. However, most often one RHP screen has a specific central screen assigned underneath.
> [!WARNING]
> **Deprecated**: The `backTo` parameter is deprecated and should not be used in new implementations. Most problems that `backTo` solved can be resolved by adding one or more routes for a single screen. If you don't know how to solve your problem, contact someone from the navigation team.
diff --git a/docs/articles/Unlisted/Compliance-Documentation.md b/docs/articles/Unlisted/Compliance-Documentation.md
index 7036d65c1910..b2c7475ebad8 100644
--- a/docs/articles/Unlisted/Compliance-Documentation.md
+++ b/docs/articles/Unlisted/Compliance-Documentation.md
@@ -10,7 +10,7 @@ Expensify is committed to keeping your data secure and transparent. You can acce
You can view or download the following audit documents to verify our security and operational controls:
-- [Bridge Letter for SOC 1 and SOC 2](https://s3-us-west-1.amazonaws.com/concierge-responses-expensify-com/uploads%2F1759782614043-Bridge_Letter_for_SOC_1_and_SOC_2_-_Sept_30_2025.pdf)
+- [Bridge Letter for SOC 1 and SOC 2 – June 2025](https://drive.google.com/file/d/1kxttniCMLFah4uPNjhknxs0Zor6tWGWE/view?usp=drive_link)
- [SOC 1 Type 2 Report – September 30, 2024](https://s3-us-west-1.amazonaws.com/concierge-responses-expensify-com/uploads%2F1733950182002-SOC+1+Type+2+Report+09-30-24+-+Expensify.pdf)
- [SOC 2 Type 2 Report – September 30, 2024](https://s3-us-west-1.amazonaws.com/concierge-responses-expensify-com/uploads%2F1733950193162-SOC+2+Type+2+Report+09-30-24+-+Expensify.pdf)
diff --git a/docs/redirects.csv b/docs/redirects.csv
index 08e5a82fcf4b..a3284338ba5c 100644
--- a/docs/redirects.csv
+++ b/docs/redirects.csv
@@ -892,4 +892,3 @@ https://help.expensify.com/classic,https://help.expensify.com/expensify-classic/
https://help.expensify.com/articles/new-expensify/expensify-card/Enable-Expensify-Card-notifications,https://help.expensify.com/articles/new-expensify/expensify-card/Expensify-Card-Notifications
https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/bank-accounts.md/Add-or-remove-a-business-bank-account,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/bank-accounts/Connect-US-Business-Bank-Account
https://help.expensify.com/articles/Unlisted/Compliance-Documentation,https://help.expensify.com/articles/new-expensify/settings/Encryption-and-Data-Security
-https://drive.google.com/file/d/1kxttniCMLFah4uPNjhknxs0Zor6tWGWE/view,https://s3-us-west-1.amazonaws.com/concierge-responses-expensify-com/uploads%2F1759782614043-Bridge_Letter_for_SOC_1_and_SOC_2_-_Sept_30_2025.pdf
diff --git a/eslint.config.js b/eslint.config.js
index b25598057449..805e877da6e2 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -311,7 +311,7 @@ const config = defineConfig([
{
selector: 'CallExpression[callee.name="getUrlWithBackToParam"]',
message:
- 'Usage of getUrlWithBackToParam function is prohibited. This is legacy code and no new occurrences should be added. Please look into documentation and use alternative routing methods instead.',
+ 'Usage of getUrlWithBackToParam function is prohibited. This is legacy code and no new occurrences should be added. Please look into the `How to remove backTo from URL` section in contributingGuides/NAVIGATION.md. and use alternative routing methods instead.',
},
// These are the original rules from AirBnB's style guide, modified to allow for...of loops and for...in loops
@@ -548,6 +548,20 @@ const config = defineConfig([
},
},
+ {
+ files: ['src/libs/Navigation/types.ts'],
+ rules: {
+ 'no-restricted-syntax': [
+ 'error',
+ {
+ selector: 'TSPropertySignature[key.name="backTo"]',
+ message:
+ 'The `backTo` route param is deprecated. Do not add new `backTo` properties to screen param lists. Please look into the `How to remove backTo from URL` section in contributingGuides/NAVIGATION.md. and use alternative routing methods instead.',
+ },
+ ],
+ },
+ },
+
globalIgnores([
'!**/.storybook',
'!**/.github',
diff --git a/ios/AppIcon-staging.icon/Assets/Adhoc-Icon-Tinted-1024x1024.png b/ios/AppIcon-staging.icon/Assets/Adhoc-Icon-Tinted-1024x1024.png
index 5a6d1d18c4f4..1d2272ff1a58 100644
Binary files a/ios/AppIcon-staging.icon/Assets/Adhoc-Icon-Tinted-1024x1024.png and b/ios/AppIcon-staging.icon/Assets/Adhoc-Icon-Tinted-1024x1024.png differ
diff --git a/ios/AppIcon-staging.icon/Assets/App Icon.png b/ios/AppIcon-staging.icon/Assets/App Icon.png
index 2c70f1f84775..26b2d3aafedb 100644
Binary files a/ios/AppIcon-staging.icon/Assets/App Icon.png and b/ios/AppIcon-staging.icon/Assets/App Icon.png differ
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index 2fcdf8852bd7..66bc34e0f8f7 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -23,7 +23,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 9.2.36
+ 9.2.37
CFBundleSignature
????
CFBundleURLTypes
@@ -44,7 +44,7 @@
CFBundleVersion
- 9.2.36.5
+ 9.2.37.4
FullStory
OrgId
diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist
index 9dc421d894b8..abd58d30ed8a 100644
--- a/ios/NotificationServiceExtension/Info.plist
+++ b/ios/NotificationServiceExtension/Info.plist
@@ -11,9 +11,9 @@
CFBundleName
$(PRODUCT_NAME)
CFBundleShortVersionString
- 9.2.36
+ 9.2.37
CFBundleVersion
- 9.2.36.5
+ 9.2.37.4
NSExtension
NSExtensionPointIdentifier
diff --git a/ios/ShareViewController/Info.plist b/ios/ShareViewController/Info.plist
index 183b76d760fe..7ad21c32b81b 100644
--- a/ios/ShareViewController/Info.plist
+++ b/ios/ShareViewController/Info.plist
@@ -11,9 +11,9 @@
CFBundleName
$(PRODUCT_NAME)
CFBundleShortVersionString
- 9.2.36
+ 9.2.37
CFBundleVersion
- 9.2.36.5
+ 9.2.37.4
NSExtension
NSExtensionAttributes
diff --git a/package-lock.json b/package-lock.json
index 82c78d0be3ea..e7437bf065db 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "9.2.36-5",
+ "version": "9.2.37-4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "9.2.36-5",
+ "version": "9.2.37-4",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
@@ -63,7 +63,7 @@
"date-fns-tz": "^3.2.0",
"dom-serializer": "^0.2.2",
"domhandler": "^5.0.3",
- "expensify-common": "2.0.160",
+ "expensify-common": "2.0.162",
"expo": "53.0.11",
"expo-asset": "^11.1.2",
"expo-av": "^15.1.5",
@@ -117,7 +117,7 @@
"react-native-localize": "^3.5.4",
"react-native-nitro-modules": "0.29.4",
"react-native-nitro-sqlite": "9.1.11",
- "react-native-onyx": "3.0.6",
+ "react-native-onyx": "3.0.7",
"react-native-pager-view": "6.9.1",
"react-native-pdf": "7.0.2",
"react-native-performance": "^5.1.4",
@@ -21772,9 +21772,9 @@
}
},
"node_modules/expensify-common": {
- "version": "2.0.160",
- "resolved": "https://registry.npmjs.org/expensify-common/-/expensify-common-2.0.160.tgz",
- "integrity": "sha512-lpEOgukBTkrJNZanUGJce1D6r7tDjdhClYd0UEDuWMT1Hgs9U9qigr8dUWPfC/Vp1bivFvNyhu62+C4I1kFWdA==",
+ "version": "2.0.162",
+ "resolved": "https://registry.npmjs.org/expensify-common/-/expensify-common-2.0.162.tgz",
+ "integrity": "sha512-0XS77iCssXx5taDuLxvGFeSCYj9XOtQ7IH6PssyaQPNTWgPf0er9sAKD4KG8HmXx1kgldTB0Amp8IYcf09IsEQ==",
"license": "MIT",
"dependencies": {
"awesome-phonenumber": "^5.4.0",
@@ -32384,9 +32384,9 @@
}
},
"node_modules/react-native-onyx": {
- "version": "3.0.6",
- "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-3.0.6.tgz",
- "integrity": "sha512-AR8tClMVMSOMvZm/7acsJERph1+l488JiT4GCu4XGn9QjMpmL039ohRjO0/mmr++rGp6zC6I61TDW/LyUxHC1w==",
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-3.0.7.tgz",
+ "integrity": "sha512-vnnf46biD3saa5Whzz4KlJ/Y36UE7nVeRKSr3wLmmfIa5BheyIoExL3fxKhV6mIjiRrWbPCr6kEEksN/CRYD3Q==",
"license": "MIT",
"dependencies": {
"ascii-table": "0.0.9",
diff --git a/package.json b/package.json
index 9ae317b64799..fc05fb10565d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "9.2.36-5",
+ "version": "9.2.37-4",
"author": "Expensify, Inc.",
"homepage": "https://new.expensify.com",
"description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",
@@ -134,7 +134,7 @@
"date-fns-tz": "^3.2.0",
"dom-serializer": "^0.2.2",
"domhandler": "^5.0.3",
- "expensify-common": "2.0.160",
+ "expensify-common": "2.0.162",
"expo": "53.0.11",
"expo-asset": "^11.1.2",
"expo-av": "^15.1.5",
@@ -188,7 +188,7 @@
"react-native-localize": "^3.5.4",
"react-native-nitro-modules": "0.29.4",
"react-native-nitro-sqlite": "9.1.11",
- "react-native-onyx": "3.0.6",
+ "react-native-onyx": "3.0.7",
"react-native-pager-view": "6.9.1",
"react-native-pdf": "7.0.2",
"react-native-performance": "^5.1.4",
diff --git a/src/CONST/index.ts b/src/CONST/index.ts
index e1843b2c469e..d08f02a939d9 100755
--- a/src/CONST/index.ts
+++ b/src/CONST/index.ts
@@ -1746,6 +1746,7 @@ const CONST = {
MAX_PENDING_TIME_MS: 10 * 1000,
RECHECK_INTERVAL_MS: 60 * 1000,
MAX_REQUEST_RETRIES: 10,
+ MAX_OPEN_APP_REQUEST_RETRIES: 2,
NETWORK_STATUS: {
ONLINE: 'online',
OFFLINE: 'offline',
@@ -7305,24 +7306,11 @@ const CONTINUATION_DETECTION_SEARCH_FILTER_KEYS = [
CONST.SEARCH.SYNTAX_FILTER_KEYS.ATTENDEE,
] as SearchFilterKey[];
-const FEATURE_IDS = {
- CATEGORIES: 'categories',
- ACCOUNTING: 'accounting',
- COMPANY_CARDS: 'company-cards',
- TAGS: 'tags',
- WORKFLOWS: 'workflows',
- INVOICES: 'invoices',
- RULES: 'rules',
- PER_DIEM: 'per-diem',
- DISTANCE_RATES: 'distance-rates',
- EXPENSIFY_CARD: 'expensify-card',
-};
-
const TASK_TO_FEATURE: Record = {
- [CONST.ONBOARDING_TASK_TYPE.SETUP_CATEGORIES]: FEATURE_IDS.CATEGORIES,
- [CONST.ONBOARDING_TASK_TYPE.ADD_ACCOUNTING_INTEGRATION]: FEATURE_IDS.ACCOUNTING,
- [CONST.ONBOARDING_TASK_TYPE.CONNECT_CORPORATE_CARD]: FEATURE_IDS.COMPANY_CARDS,
- [CONST.ONBOARDING_TASK_TYPE.SETUP_TAGS]: FEATURE_IDS.TAGS,
+ [CONST.ONBOARDING_TASK_TYPE.SETUP_CATEGORIES]: CONST.POLICY.MORE_FEATURES.ARE_CATEGORIES_ENABLED,
+ [CONST.ONBOARDING_TASK_TYPE.ADD_ACCOUNTING_INTEGRATION]: CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED,
+ [CONST.ONBOARDING_TASK_TYPE.CONNECT_CORPORATE_CARD]: CONST.POLICY.MORE_FEATURES.ARE_COMPANY_CARDS_ENABLED,
+ [CONST.ONBOARDING_TASK_TYPE.SETUP_TAGS]: CONST.POLICY.MORE_FEATURES.ARE_TAGS_ENABLED,
};
const FRAUD_PROTECTION_EVENT = {
@@ -7358,6 +7346,6 @@ type CancellationType = ValueOf;
export type {Country, IOUAction, IOUType, IOURequestType, SubscriptionType, FeedbackSurveyOptionID, CancellationType, OnboardingInvite, OnboardingAccounting, IOUActionParams};
-export {CONTINUATION_DETECTION_SEARCH_FILTER_KEYS, TASK_TO_FEATURE, FEATURE_IDS, FRAUD_PROTECTION_EVENT};
+export {CONTINUATION_DETECTION_SEARCH_FILTER_KEYS, TASK_TO_FEATURE, FRAUD_PROTECTION_EVENT};
export default CONST;
diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts
index f2af8e9330ba..267649095ee7 100755
--- a/src/ONYXKEYS.ts
+++ b/src/ONYXKEYS.ts
@@ -48,6 +48,9 @@ const ONYXKEYS = {
/** Keeps track if there is modal currently visible or not */
MODAL: 'modal',
+ /** Keeps track if OpenApp failure modal is opened */
+ IS_OPEN_APP_FAILURE_MODAL_OPEN: 'isOpenAppFailureModalOpen',
+
/** Stores the PIN for an activated UK/EU Expensify card to be shown once after activation */
ACTIVATED_CARD_PIN: 'activatedCardPin',
@@ -697,6 +700,9 @@ const ONYXKEYS = {
/** Stores the information about the state of issuing a new card */
ISSUE_NEW_EXPENSIFY_CARD: 'issueNewExpensifyCard_',
+
+ /** Used for identifying user as admin of a domain */
+ SHARED_NVP_PRIVATE_ADMIN_ACCESS: 'sharedNVP_private_admin_access_',
},
/** List of Form ids */
@@ -1081,6 +1087,7 @@ type OnyxCollectionValuesMapping = {
[ONYXKEYS.COLLECTION.LAST_SELECTED_EXPENSIFY_CARD_FEED]: OnyxTypes.FundID;
[ONYXKEYS.COLLECTION.NVP_EXPENSIFY_ON_CARD_WAITLIST]: OnyxTypes.CardOnWaitlist;
[ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD]: OnyxTypes.IssueNewCard;
+ [ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_ADMIN_ACCESS]: boolean;
};
type OnyxValuesMapping = {
@@ -1104,6 +1111,7 @@ type OnyxValuesMapping = {
[ONYXKEYS.CREDENTIALS]: OnyxTypes.Credentials;
[ONYXKEYS.STASHED_CREDENTIALS]: OnyxTypes.Credentials;
[ONYXKEYS.MODAL]: OnyxTypes.Modal;
+ [ONYXKEYS.IS_OPEN_APP_FAILURE_MODAL_OPEN]: boolean;
[ONYXKEYS.FULLSCREEN_VISIBILITY]: boolean;
[ONYXKEYS.NETWORK]: OnyxTypes.Network;
[ONYXKEYS.NEW_GROUP_CHAT_DRAFT]: OnyxTypes.NewGroupChatDraft;
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index bb54b133d106..4aba22226059 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -3083,7 +3083,7 @@ const ROUTES = {
},
POLICY_ACCOUNTING_SAGE_INTACCT_MAPPINGS_TYPE: {
route: 'workspaces/:policyID/accounting/sage-intacct/import/mapping-type/:mapping',
- getRoute: (policyID: string, mapping: string) => `workspaces/${policyID}/accounting/sage-intacct/import/mapping-type/${mapping}` as const,
+ getRoute: (policyID: string | undefined, mapping: string) => `workspaces/${policyID}/accounting/sage-intacct/import/mapping-type/${mapping}` as const,
},
POLICY_ACCOUNTING_SAGE_INTACCT_IMPORT_TAX: {
route: 'workspaces/:policyID/accounting/sage-intacct/import/tax',
diff --git a/src/components/AnimatedSubmitButton/index.tsx b/src/components/AnimatedSubmitButton/index.tsx
index aabe97dca511..8567f82b7616 100644
--- a/src/components/AnimatedSubmitButton/index.tsx
+++ b/src/components/AnimatedSubmitButton/index.tsx
@@ -22,9 +22,12 @@ type AnimatedSubmitButtonProps = {
// Function to call when the animation finishes
onAnimationFinish: () => void;
+
+ // Whether the button should be disabled
+ isDisabled?: boolean;
};
-function AnimatedSubmitButton({success, text, onPress, isSubmittingAnimationRunning, onAnimationFinish}: AnimatedSubmitButtonProps) {
+function AnimatedSubmitButton({success, text, onPress, isSubmittingAnimationRunning, onAnimationFinish, isDisabled}: AnimatedSubmitButtonProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const isAnimationRunning = isSubmittingAnimationRunning;
@@ -128,6 +131,7 @@ function AnimatedSubmitButton({success, text, onPress, isSubmittingAnimationRunn
text={text}
onPress={onPress}
icon={icon}
+ isDisabled={isDisabled}
/>
)}
diff --git a/src/components/BrokenConnectionDescription.tsx b/src/components/BrokenConnectionDescription.tsx
index 9287934ebdfb..ff1c2d800fc7 100644
--- a/src/components/BrokenConnectionDescription.tsx
+++ b/src/components/BrokenConnectionDescription.tsx
@@ -1,15 +1,14 @@
import React from 'react';
import type {OnyxEntry} from 'react-native-onyx';
+import useEnvironment from '@hooks/useEnvironment';
import useLocalize from '@hooks/useLocalize';
-import useThemeStyles from '@hooks/useThemeStyles';
import useTransactionViolations from '@hooks/useTransactionViolations';
import {isPolicyAdmin as isPolicyAdminPolicyUtils} from '@libs/PolicyUtils';
import {isCurrentUserSubmitter, isReportApproved, isReportManuallyReimbursed} from '@libs/ReportUtils';
-import Navigation from '@navigation/Navigation';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
import type {Policy, Report} from '@src/types/onyx';
-import TextLink from './TextLink';
+import RenderHTML from './RenderHTML';
type BrokenConnectionDescriptionProps = {
/** Transaction id of the corresponding report */
@@ -23,13 +22,14 @@ type BrokenConnectionDescriptionProps = {
};
function BrokenConnectionDescription({transactionID, policy, report}: BrokenConnectionDescriptionProps) {
- const styles = useThemeStyles();
const {translate} = useLocalize();
const transactionViolations = useTransactionViolations(transactionID);
+ const {environmentURL} = useEnvironment();
const brokenConnection530Error = transactionViolations?.find((violation) => violation.data?.rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION_530);
const brokenConnectionError = transactionViolations?.find((violation) => violation.data?.rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION);
const isPolicyAdmin = isPolicyAdminPolicyUtils(policy);
+ const workspaceCompanyCardRoute = `${environmentURL}/${ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(policy?.id)}`;
if (!brokenConnection530Error && !brokenConnectionError) {
return '';
@@ -40,16 +40,7 @@ function BrokenConnectionDescription({transactionID, policy, report}: BrokenConn
}
if (isPolicyAdmin && !isCurrentUserSubmitter(report)) {
- return (
- <>
- {`${translate('violations.adminBrokenConnectionError')}`}
- Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(policy?.id))}
- >{`${translate('workspace.common.companyCards')}`}
- .
- >
- );
+ return ;
}
if (isReportApproved({report}) || isReportManuallyReimbursed(report)) {
diff --git a/src/components/Domain/DomainsListRow.tsx b/src/components/Domain/DomainsListRow.tsx
new file mode 100644
index 000000000000..79cecbc180f9
--- /dev/null
+++ b/src/components/Domain/DomainsListRow.tsx
@@ -0,0 +1,54 @@
+import React from 'react';
+import {View} from 'react-native';
+import Icon from '@components/Icon';
+import * as Expensicons from '@components/Icon/Expensicons';
+import TextWithTooltip from '@components/TextWithTooltip';
+import useTheme from '@hooks/useTheme';
+import useThemeStyles from '@hooks/useThemeStyles';
+
+type DomainsListRowProps = {
+ /** Name of the domain */
+ title: string;
+
+ /** Whether the row is hovered, so we can modify its style */
+ isHovered: boolean;
+
+ /** Whether the icon at the end of the row should be displayed */
+ shouldShowRightIcon: boolean;
+};
+
+function DomainsListRow({title, isHovered, shouldShowRightIcon}: DomainsListRowProps) {
+ const styles = useThemeStyles();
+ const theme = useTheme();
+
+ return (
+
+
+
+
+
+
+ {shouldShowRightIcon && (
+
+
+
+ )}
+
+ );
+}
+
+DomainsListRow.displayName = 'DomainsListRow';
+
+export default DomainsListRow;
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx
index a6130522957f..48edc09d7a39 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx
@@ -43,7 +43,7 @@ function MentionUserRenderer({style, tnode, TDefaultRenderer, currentUserPersona
if (!isEmpty(htmlAttribAccountID) && personalDetails?.[htmlAttribAccountID]) {
const user = personalDetails[htmlAttribAccountID];
accountID = parseInt(htmlAttribAccountID, 10);
- mentionDisplayText = getDisplayNameOrDefault(user) || formatPhoneNumber(user?.login ?? '');
+ mentionDisplayText = formatPhoneNumber(user?.login ?? '') || getDisplayNameOrDefault(user);
mentionDisplayText = getShortMentionIfFound(mentionDisplayText, htmlAttributeAccountID, currentUserPersonalDetails, user?.login ?? '') ?? '';
navigationRoute = ROUTES.PROFILE.getRoute(accountID, Navigation.getReportRHPActiveRoute());
} else if ('data' in tnode && !isEmptyObject(tnode.data)) {
@@ -56,15 +56,9 @@ function MentionUserRenderer({style, tnode, TDefaultRenderer, currentUserPersona
Str.removeSMSDomain(getShortMentionIfFound(mentionDisplayText, htmlAttributeAccountID, currentUserPersonalDetails) ?? ''),
);
- accountID = getAccountIDsByLogins([mentionDisplayText], false)?.at(0) ?? -1;
- if (accountID !== -1) {
- const user = personalDetails?.[accountID];
- mentionDisplayText = getDisplayNameOrDefault(user) || formatPhoneNumber(user?.login ?? '');
- mentionDisplayText = getShortMentionIfFound(mentionDisplayText, htmlAttributeAccountID, currentUserPersonalDetails, user?.login ?? '') ?? '';
- } else {
- mentionDisplayText = Str.removeSMSDomain(mentionDisplayText);
- }
+ accountID = getAccountIDsByLogins([mentionDisplayText])?.at(0) ?? -1;
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
return null;
@@ -120,7 +114,7 @@ function MentionUserRenderer({style, tnode, TDefaultRenderer, currentUserPersona
testID="mention-user"
href={`/${navigationRoute}`}
>
- {accountID && accountID !== -1 ? `@${mentionDisplayText}` : }
+ {htmlAttribAccountID ? `@${mentionDisplayText}` : }
diff --git a/src/components/LocaleContextProvider.tsx b/src/components/LocaleContextProvider.tsx
index 11cda3464b7b..5a4fb048812a 100644
--- a/src/components/LocaleContextProvider.tsx
+++ b/src/components/LocaleContextProvider.tsx
@@ -64,6 +64,8 @@ type LocaleContextProps = {
preferredLocale: Locale | undefined;
};
+type LocalizedTranslate = LocaleContextProps['translate'];
+
const LocaleContext = createContext({
translate: () => '',
numberFormat: () => '',
@@ -224,4 +226,4 @@ LocaleContextProvider.displayName = 'LocaleContextProvider';
export {LocaleContext, LocaleContextProvider};
-export type {Locale, LocaleContextProps};
+export type {Locale, LocaleContextProps, LocalizedTranslate};
diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx
index c4c38f091f35..85763d819d4f 100644
--- a/src/components/MoneyReportHeader.tsx
+++ b/src/components/MoneyReportHeader.tsx
@@ -6,6 +6,7 @@ import {InteractionManager, View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
+import useDeleteTransactions from '@hooks/useDeleteTransactions';
import useDuplicateTransactionsAndViolations from '@hooks/useDuplicateTransactionsAndViolations';
import useGetIOUReportFromReportAction from '@hooks/useGetIOUReportFromReportAction';
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
@@ -22,6 +23,7 @@ import useReportIsArchived from '@hooks/useReportIsArchived';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useSearchShouldCalculateTotals from '@hooks/useSearchShouldCalculateTotals';
import useSelectedTransactionsActions from '@hooks/useSelectedTransactionsActions';
+import useStrictPolicyRules from '@hooks/useStrictPolicyRules';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useTransactionsAndViolationsForReport from '@hooks/useTransactionsAndViolationsForReport';
@@ -37,9 +39,9 @@ import {getThreadReportIDsForTransactions, getTotalAmountForIOUReportPreviewButt
import Navigation from '@libs/Navigation/Navigation';
import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types';
import type {ReportsSplitNavigatorParamList, SearchFullscreenNavigatorParamList, SearchReportParamList} from '@libs/Navigation/types';
-import {buildOptimisticNextStepForPreventSelfApprovalsEnabled} from '@libs/NextStepUtils';
-import {selectPaymentType} from '@libs/PaymentUtils';
+import {buildOptimisticNextStepForPreventSelfApprovalsEnabled, buildOptimisticNextStepForStrictPolicyRuleViolations} from '@libs/NextStepUtils';
import type {KYCFlowEvent, TriggerKYCFlow} from '@libs/PaymentUtils';
+import {selectPaymentType} from '@libs/PaymentUtils';
import Permissions from '@libs/Permissions';
import {getConnectedIntegration, getValidConnectedIntegration} from '@libs/PolicyUtils';
import {getIOUActionForReportID, getOriginalMessage, getReportAction, isMoneyRequestAction} from '@libs/ReportActionsUtils';
@@ -65,6 +67,7 @@ import {
navigateOnDeleteExpense,
navigateToDetailsPage,
rejectMoneyRequestReason,
+ shouldBlockSubmitDueToStrictPolicyRules,
} from '@libs/ReportUtils';
import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils';
import {
@@ -86,7 +89,6 @@ import {
canApproveIOU,
cancelPayment,
canIOUBePaid as canIOUBePaidAction,
- deleteMoneyRequest,
dismissRejectUseExplanation,
getNavigationUrlOnMoneyRequestDelete,
initSplitExpense,
@@ -199,6 +201,9 @@ function MoneyReportHeader({
const expensifyIcons = useMemoizedLazyExpensifyIcons(['Buildings'] as const);
const [lastDistanceExpenseType] = useOnyx(ONYXKEYS.NVP_LAST_DISTANCE_EXPENSE_TYPE, {canBeMissing: true});
const exportTemplates = useMemo(() => getExportTemplates(integrationsExportTemplates ?? [], csvExportLayouts ?? {}, policy), [integrationsExportTemplates, csvExportLayouts, policy]);
+ const {areStrictPolicyRulesEnabled} = useStrictPolicyRules();
+ const [allTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {canBeMissing: false});
+ const [allReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: false});
const requestParentReportAction = useMemo(() => {
if (!reportActions || !transactionThreadReport?.parentReportActionID) {
@@ -215,6 +220,10 @@ function MoneyReportHeader({
return Object.values(reportTransactions);
}, [reportTransactions]);
+ const shouldBlockSubmit = useMemo(() => {
+ return shouldBlockSubmitDueToStrictPolicyRules(moneyRequestReport?.reportID, violations, areStrictPolicyRulesEnabled, transactions);
+ }, [moneyRequestReport?.reportID, violations, areStrictPolicyRulesEnabled, transactions]);
+
const iouTransactionID = isMoneyRequestAction(requestParentReportAction) ? getOriginalMessage(requestParentReportAction)?.IOUTransactionID : undefined;
const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(iouTransactionID)}`, {
canBeMissing: true,
@@ -230,6 +239,7 @@ function MoneyReportHeader({
);
const {duplicateTransactions, duplicateTransactionViolations} = useDuplicateTransactionsAndViolations(transactions.map((t) => t.transactionID));
+ const {deleteTransactions} = useDeleteTransactions({report: chatReport, reportActions, policy});
const isExported = useMemo(() => isExportedUtils(reportActions), [reportActions]);
// wrapped in useMemo to improve performance because this is an operation on array
const integrationNameFromExportMessage = useMemo(() => {
@@ -313,7 +323,7 @@ function MoneyReportHeader({
const [isDownloadErrorModalVisible, setIsDownloadErrorModalVisible] = useState(false);
const [isRejectEducationalModalVisible, setIsRejectEducationalModalVisible] = useState(false);
- const {selectedTransactionIDs, removeTransaction, clearSelectedTransactions, currentSearchQueryJSON, currentSearchKey} = useSearchContext();
+ const {selectedTransactionIDs, removeTransaction, clearSelectedTransactions, currentSearchQueryJSON, currentSearchKey, currentSearchHash} = useSearchContext();
const shouldCalculateTotals = useSearchShouldCalculateTotals(currentSearchKey, currentSearchQueryJSON?.similarSearchHash, true);
const {wideRHPRouteKeys} = useContext(WideRHPContext);
@@ -388,7 +398,11 @@ function MoneyReportHeader({
// to avoid any flicker during transitions between online/offline states
const nextApproverAccountID = getNextApproverAccountID(moneyRequestReport);
const isSubmitterSameAsNextApprover = isReportOwner(moneyRequestReport) && nextApproverAccountID === moneyRequestReport?.ownerAccountID;
- const optimisticNextStep = isSubmitterSameAsNextApprover && policy?.preventSelfApproval ? buildOptimisticNextStepForPreventSelfApprovalsEnabled() : nextStep;
+ let optimisticNextStep = isSubmitterSameAsNextApprover && policy?.preventSelfApproval ? buildOptimisticNextStepForPreventSelfApprovalsEnabled() : nextStep;
+
+ if (shouldBlockSubmit && isReportOwner(moneyRequestReport)) {
+ optimisticNextStep = buildOptimisticNextStepForStrictPolicyRuleViolations();
+ }
const shouldShowNextStep = isFromPaidPolicy && !isInvoiceReport && !shouldShowStatusBar;
const {nonHeldAmount, fullAmount, hasValidNonHeldAmount} = getNonHeldAndFullAmount(moneyRequestReport, shouldShowPayButton);
@@ -465,7 +479,7 @@ function MoneyReportHeader({
if (currentSearchQueryJSON) {
search({
searchKey: currentSearchKey,
- shouldCalculateTotals: true,
+ shouldCalculateTotals,
offset: 0,
queryJSON: currentSearchQueryJSON,
isOffline,
@@ -705,7 +719,7 @@ function MoneyReportHeader({
success
text={translate('common.submit')}
onPress={() => {
- if (!moneyRequestReport) {
+ if (!moneyRequestReport || shouldBlockSubmit) {
return;
}
startSubmittingAnimation();
@@ -722,6 +736,7 @@ function MoneyReportHeader({
}}
isSubmittingAnimationRunning={isSubmittingAnimationRunning}
onAnimationFinish={stopAnimation}
+ isDisabled={shouldBlockSubmit}
/>
),
[CONST.REPORT.PRIMARY_ACTIONS.APPROVE]: (
@@ -974,7 +989,7 @@ function MoneyReportHeader({
}
const currentTransaction = transactions.at(0);
- initSplitExpense(currentTransaction);
+ initSplitExpense(allTransactions, allReports, currentTransaction);
},
},
[CONST.REPORT.SECONDARY_ACTIONS.MERGE]: {
@@ -1304,15 +1319,7 @@ function MoneyReportHeader({
// Money request should be deleted when interactions are done, to not show the not found page before navigating to goBackRoute
// eslint-disable-next-line @typescript-eslint/no-deprecated
InteractionManager.runAfterInteractions(() => {
- deleteMoneyRequest(
- transaction?.transactionID,
- requestParentReportAction,
- duplicateTransactions,
- duplicateTransactionViolations,
- iouReport,
- chatIOUReport,
- isChatIOUReportArchived,
- );
+ deleteTransactions([transaction.transactionID], duplicateTransactions, duplicateTransactionViolations, currentSearchHash, false);
removeTransaction(transaction.transactionID);
});
goBackRoute = getNavigationUrlOnMoneyRequestDelete(transaction.transactionID, requestParentReportAction, iouReport, chatIOUReport, isChatIOUReportArchived, false);
diff --git a/src/components/MoneyReportHeaderStatusBar.tsx b/src/components/MoneyReportHeaderStatusBar.tsx
index 455f9e443610..f0855a85fd53 100644
--- a/src/components/MoneyReportHeaderStatusBar.tsx
+++ b/src/components/MoneyReportHeaderStatusBar.tsx
@@ -3,7 +3,7 @@ import {View} from 'react-native';
import type {ValueOf} from 'type-fest';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
-import * as NextStepUtils from '@libs/NextStepUtils';
+import {parseMessage} from '@libs/NextStepUtils';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import type ReportNextStep from '@src/types/onyx/ReportNextStep';
@@ -14,7 +14,7 @@ import RenderHTML from './RenderHTML';
type MoneyReportHeaderStatusBarProps = {
/** The next step for the report */
- nextStep: ReportNextStep;
+ nextStep: ReportNextStep | undefined;
};
type IconName = ValueOf;
@@ -29,15 +29,15 @@ function MoneyReportHeaderStatusBar({nextStep}: MoneyReportHeaderStatusBarProps)
const styles = useThemeStyles();
const theme = useTheme();
const messageContent = useMemo(() => {
- const messageArray = nextStep.message;
- return NextStepUtils.parseMessage(messageArray);
- }, [nextStep.message]);
+ const messageArray = nextStep?.message;
+ return parseMessage(messageArray);
+ }, [nextStep?.message]);
return (
{
- // Set the default tax code when conditions change
- if (!shouldShowTax || !transaction || !transactionID) {
+ if (!transactionID) {
return;
}
- const defaultTaxCode = getDefaultTaxCode(policy, transaction);
- const currentTaxCode = transaction.taxCode ?? '';
-
- // Update tax code if it's different from what should be the default
- if (defaultTaxCode !== currentTaxCode) {
- setMoneyRequestTaxRate(transactionID, defaultTaxCode ?? '');
- }
- }, [customUnitRateID, policy, shouldShowTax, transaction, transactionID]);
+ setMoneyRequestTaxRate(transactionID, defaultTaxCode);
+ }, [defaultTaxCode, transactionID]);
const isMovingTransactionFromTrackExpense = isMovingTransactionFromTrackExpenseUtil(action);
diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx
index 0bdb46715530..5f62518741ee 100644
--- a/src/components/MoneyRequestHeader.tsx
+++ b/src/components/MoneyRequestHeader.tsx
@@ -5,6 +5,7 @@ import {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
+import useDeleteTransactions from '@hooks/useDeleteTransactions';
import useDuplicateTransactionsAndViolations from '@hooks/useDuplicateTransactionsAndViolations';
import useGetIOUReportFromReportAction from '@hooks/useGetIOUReportFromReportAction';
import useLoadingBarVisibility from '@hooks/useLoadingBarVisibility';
@@ -15,7 +16,7 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useTransactionViolations from '@hooks/useTransactionViolations';
-import {deleteMoneyRequest, deleteTrackExpense, initSplitExpense, markRejectViolationAsResolved} from '@libs/actions/IOU';
+import {deleteTrackExpense, initSplitExpense, markRejectViolationAsResolved} from '@libs/actions/IOU';
import {setupMergeTransactionData} from '@libs/actions/MergeTransaction';
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
import Navigation from '@libs/Navigation/Navigation';
@@ -111,8 +112,11 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre
const isOnHold = isOnHoldTransactionUtils(transaction);
const isDuplicate = isDuplicateTransactionUtils(transaction);
const reportID = report?.reportID;
- const {removeTransaction} = useSearchContext();
+ const {removeTransaction, currentSearchHash} = useSearchContext();
const {isExpenseSplit} = getOriginalTransactionWithSplitInfo(transaction);
+ const [allTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {canBeMissing: false});
+ const [allReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: false});
+ const {deleteTransactions} = useDeleteTransactions({report: parentReport, reportActions: parentReportAction ? [parentReportAction] : [], policy});
const {isDelegateAccessRestricted, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext);
const isReportInRHP = route.name === SCREENS.SEARCH.REPORT_RHP;
@@ -308,7 +312,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre
icon: Expensicons.ArrowSplit,
value: CONST.REPORT.SECONDARY_ACTIONS.SPLIT,
onSelected: () => {
- initSplitExpense(transaction);
+ initSplitExpense(allTransactions, allReports, transaction);
},
},
[CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.MERGE]: {
@@ -455,16 +459,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre
isChatIOUReportArchived,
});
} else {
- deleteMoneyRequest(
- transaction.transactionID,
- parentReportAction,
- duplicateTransactions,
- duplicateTransactionViolations,
- iouReport,
- chatIOUReport,
- isChatIOUReportArchived,
- true,
- );
+ deleteTransactions([transaction.transactionID], duplicateTransactions, duplicateTransactionViolations, currentSearchHash, true);
removeTransaction(transaction.transactionID);
}
onBackButtonPress();
diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportNavigation.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportNavigation.tsx
index 0c54e5e674dc..4deda521d669 100644
--- a/src/components/MoneyRequestReportView/MoneyRequestReportNavigation.tsx
+++ b/src/components/MoneyRequestReportView/MoneyRequestReportNavigation.tsx
@@ -3,6 +3,7 @@ import {View} from 'react-native';
import PrevNextButtons from '@components/PrevNextButtons';
import Text from '@components/Text';
import useArchivedReportsIdSet from '@hooks/useArchivedReportsIdSet';
+import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import useThemeStyles from '@hooks/useThemeStyles';
@@ -22,8 +23,7 @@ type MoneyRequestReportNavigationProps = {
function MoneyRequestReportNavigation({reportID, shouldDisplayNarrowVersion}: MoneyRequestReportNavigationProps) {
const [lastSearchQuery] = useOnyx(ONYXKEYS.REPORT_NAVIGATION_LAST_SEARCH_QUERY, {canBeMissing: true});
const [currentSearchResults] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${lastSearchQuery?.queryJSON?.hash}`, {canBeMissing: true});
- const [accountID] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: false, selector: (s) => s?.accountID});
-
+ const currentUserDetails = useCurrentUserPersonalDetails();
const {localeCompare, formatPhoneNumber} = useLocalize();
const [exportReportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS, {
@@ -37,7 +37,17 @@ function MoneyRequestReportNavigation({reportID, shouldDisplayNarrowVersion}: Mo
const {type, status, sortBy, sortOrder, groupBy} = lastSearchQuery?.queryJSON ?? {};
let results: Array = [];
if (!!type && !!groupBy && !!currentSearchResults?.data && !!currentSearchResults?.search) {
- const searchData = getSections(type, currentSearchResults.data, accountID, formatPhoneNumber, groupBy, exportReportActions, lastSearchQuery?.searchKey, archivedReportsIdSet);
+ const searchData = getSections(
+ type,
+ currentSearchResults.data,
+ currentUserDetails.accountID,
+ currentUserDetails.email ?? '',
+ formatPhoneNumber,
+ groupBy,
+ exportReportActions,
+ lastSearchQuery?.searchKey,
+ archivedReportsIdSet,
+ );
results = getSortedSections(type, status ?? '', searchData, localeCompare, sortBy, sortOrder, groupBy).map((value) => value.reportID);
}
const allReports = results;
diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx
index 1f377c76c4b6..af8b31c5608a 100644
--- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx
+++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx
@@ -9,12 +9,13 @@ import * as Expensicons from '@components/Icon/Expensicons';
import MenuItem from '@components/MenuItem';
import Modal from '@components/Modal';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
-import {usePersonalDetails, useSession} from '@components/OnyxListItemProvider';
+import {usePersonalDetails} from '@components/OnyxListItemProvider';
import {useSearchContext} from '@components/Search/SearchContext';
import type {SearchColumnType, SortOrder} from '@components/Search/types';
import Text from '@components/Text';
import {WideRHPContext} from '@components/WideRHPContextProvider';
import useCopySelectionHelper from '@hooks/useCopySelectionHelper';
+import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useLocalize from '@hooks/useLocalize';
import useMobileSelectionMode from '@hooks/useMobileSelectionMode';
import useReportIsArchived from '@hooks/useReportIsArchived';
@@ -152,7 +153,7 @@ function MoneyRequestReportTransactionList({
const formattedCompanySpendAmount = convertToDisplayString(nonReimbursableSpend, report?.currency);
const shouldShowBreakdown = !!nonReimbursableSpend && !!reimbursableSpend;
const transactionsWithoutPendingDelete = useMemo(() => transactions.filter((t) => !isTransactionPendingDelete(t)), [transactions]);
- const session = useSession();
+ const currentUserDetails = useCurrentUserPersonalDetails();
const isReportArchived = useReportIsArchived(report?.reportID);
const shouldShowAddExpenseButton = canAddTransaction(report, isReportArchived) && isCurrentUserSubmitter(report);
const addExpenseDropdownOptions = useMemo(() => getAddExpenseDropdownOptions(report?.reportID, policy), [report?.reportID, policy]);
@@ -176,7 +177,7 @@ function MoneyRequestReportTransactionList({
for (const transaction of transactions) {
const transactionViolations = violations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction.transactionID}`];
if (transactionViolations) {
- const filteredTransactionViolations = transactionViolations.filter((violation) => shouldShowViolation(report, policy, violation.name));
+ const filteredTransactionViolations = transactionViolations.filter((violation) => shouldShowViolation(report, policy, violation.name, currentUserDetails.email ?? ''));
if (filteredTransactionViolations.length > 0) {
filtered[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction.transactionID}`] = filteredTransactionViolations;
@@ -185,7 +186,7 @@ function MoneyRequestReportTransactionList({
}
return filtered;
- }, [violations, report, policy, transactions]);
+ }, [violations, report, policy, transactions, currentUserDetails.email]);
const toggleTransaction = useCallback(
(transactionID: string) => {
@@ -230,9 +231,9 @@ function MoneyRequestReportTransactionList({
}, [newTransactions, sortBy, sortOrder, transactions, localeCompare, report]);
const columnsToShow = useMemo(() => {
- const columns = getColumnsToShow(session?.accountID, transactions, true);
+ const columns = getColumnsToShow(currentUserDetails?.accountID, transactions, true);
return (Object.keys(columns) as SearchColumnType[]).filter((column) => columns[column]);
- }, [transactions, session?.accountID]);
+ }, [transactions, currentUserDetails?.accountID]);
/**
* Navigate to the transaction thread for a transaction, creating one optimistically if it doesn't yet exist.
diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx
index 888be96db9c4..9e816cf999ec 100644
--- a/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx
+++ b/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx
@@ -48,9 +48,6 @@ type MoneyRequestReportViewProps = {
/** Whether Report footer (that includes Composer) should be displayed */
shouldDisplayReportFooter: boolean;
- /** Whether we should wait for the report to sync */
- shouldWaitForReportSync: boolean;
-
/** The `backTo` route that should be used when clicking back button */
backToRoute: Route | undefined;
};
@@ -83,7 +80,7 @@ function InitialLoadingSkeleton({styles}: {styles: ThemeStyles}) {
);
}
-function MoneyRequestReportView({report, policy, reportMetadata, shouldDisplayReportFooter, backToRoute, shouldWaitForReportSync}: MoneyRequestReportViewProps) {
+function MoneyRequestReportView({report, policy, reportMetadata, shouldDisplayReportFooter, backToRoute}: MoneyRequestReportViewProps) {
const styles = useThemeStyles();
const {isOffline} = useNetwork();
@@ -108,7 +105,7 @@ function MoneyRequestReportView({report, policy, reportMetadata, shouldDisplayRe
const transactionThreadReportID = getOneTransactionThreadReportID(report, chatReport, reportActions ?? [], isOffline, reportTransactionIDs);
const isSentMoneyReport = useMemo(() => reportActions.some((action) => isSentMoneyReportAction(action)), [reportActions]);
- const newTransactions = useNewTransactions(reportMetadata?.hasOnceLoadedReportActions, shouldWaitForReportSync ? [] : transactions);
+ const newTransactions = useNewTransactions(reportMetadata?.hasOnceLoadedReportActions, transactions);
const parentReportAction = useParentReportAction(report);
@@ -167,7 +164,7 @@ function MoneyRequestReportView({report, policy, reportMetadata, shouldDisplayRe
[backToRoute, isLoadingInitialReportActions, isTransactionThreadView, parentReportAction, policy, report, reportActions, transactionThreadReportID],
);
- if (!!(isLoadingInitialReportActions && reportActions.length === 0 && !isOffline) || shouldWaitForTransactions || shouldWaitForReportSync) {
+ if (!!(isLoadingInitialReportActions && reportActions.length === 0 && !isOffline) || shouldWaitForTransactions) {
return ;
}
diff --git a/src/components/NumberWithSymbolForm.tsx b/src/components/NumberWithSymbolForm.tsx
index 10fd1775e6ca..9829ab19aa35 100644
--- a/src/components/NumberWithSymbolForm.tsx
+++ b/src/components/NumberWithSymbolForm.tsx
@@ -450,7 +450,10 @@ function NumberWithSymbolForm({
);
return (
-
+
{shouldWrapInputInContainer ? (
void;
+};
+
+function BaseOpenAppFailureModal({onRefreshAndTryAgainButtonPress}: BaseOpenAppFailureModalProps) {
+ const [isOpenAppFailureModalOpen = false] = useOnyx(ONYXKEYS.IS_OPEN_APP_FAILURE_MODAL_OPEN, {canBeMissing: true});
+ const styles = useThemeStyles();
+ const {translate} = useLocalize();
+
+ // We need to use isSmallScreenWidth instead of shouldUseNarrowLayout to be consistent with BaseModal component
+ // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
+ const {isSmallScreenWidth} = useResponsiveLayout();
+
+ return (
+ setIsOpenAppFailureModalOpen(false)}
+ >
+
+
+
+ {`${translate('openAppFailureModal.subtitle')} `}
+
+ {CONST.EMAIL.CONCIERGE}
+
+
+
+
+
+ );
+}
+
+BaseOpenAppFailureModal.displayName = 'BaseOpenAppFailureModal';
+
+export default BaseOpenAppFailureModal;
diff --git a/src/components/OpenAppFailureModal/index.native.tsx b/src/components/OpenAppFailureModal/index.native.tsx
new file mode 100644
index 000000000000..894e10497b65
--- /dev/null
+++ b/src/components/OpenAppFailureModal/index.native.tsx
@@ -0,0 +1,34 @@
+import React, {useEffect} from 'react';
+import {AppState} from 'react-native';
+import {openApp} from '@libs/actions/App';
+import {setIsOpenAppFailureModalOpen} from '@libs/actions/isOpenAppFailureModalOpen';
+import BaseOpenAppFailureModal from './BaseOpenAppFailureModal';
+
+/** Triggers OpenApp reconnection */
+const retryOpenApp = () => {
+ setIsOpenAppFailureModalOpen(false);
+ openApp();
+};
+
+function OpenAppFailureModal() {
+ useEffect(() => {
+ // Close OpenAppFailureModal if app goes inactive
+ const appStateSubscription = AppState.addEventListener('change', (nextAppState) => {
+ if (!nextAppState.match(/inactive|background/)) {
+ return;
+ }
+
+ setIsOpenAppFailureModalOpen(false);
+ });
+
+ return () => {
+ appStateSubscription.remove();
+ };
+ }, []);
+
+ return ;
+}
+
+OpenAppFailureModal.displayName = 'OpenAppFailureModal';
+
+export default OpenAppFailureModal;
diff --git a/src/components/OpenAppFailureModal/index.tsx b/src/components/OpenAppFailureModal/index.tsx
new file mode 100644
index 000000000000..7e72180167f4
--- /dev/null
+++ b/src/components/OpenAppFailureModal/index.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+import BaseOpenAppFailureModal from './BaseOpenAppFailureModal';
+
+/** Reloads the app to trigger OpenApp reconnection */
+const reloadApp = () => {
+ window.location.reload();
+};
+
+function OpenAppFailureModal() {
+ return ;
+}
+
+OpenAppFailureModal.displayName = 'OpenAppFailureModal';
+
+export default OpenAppFailureModal;
diff --git a/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx
index d26dc238a6b2..15501376f714 100644
--- a/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx
+++ b/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx
@@ -30,6 +30,7 @@ import usePaymentAnimations from '@hooks/usePaymentAnimations';
import usePolicy from '@hooks/usePolicy';
import useReportIsArchived from '@hooks/useReportIsArchived';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
+import useStrictPolicyRules from '@hooks/useStrictPolicyRules';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
@@ -135,6 +136,7 @@ function MoneyRequestReportPreviewContent({
const StyleUtils = useStyleUtils();
const {translate} = useLocalize();
const {isOffline} = useNetwork();
+ const {areStrictPolicyRulesEnabled} = useStrictPolicyRules();
const {shouldUseNarrowLayout} = useResponsiveLayout();
const currentUserDetails = useCurrentUserPersonalDetails();
@@ -473,6 +475,7 @@ function MoneyRequestReportPreviewContent({
return getReportPreviewAction(
violations,
isIouReportArchived || isChatReportArchived,
+ currentUserDetails.email ?? '',
iouReport,
policy,
transactions,
@@ -480,6 +483,7 @@ function MoneyRequestReportPreviewContent({
isPaidAnimationRunning,
isApprovedAnimationRunning,
isSubmittingAnimationRunning,
+ areStrictPolicyRulesEnabled,
);
}, [
isPaidAnimationRunning,
@@ -492,6 +496,8 @@ function MoneyRequestReportPreviewContent({
isIouReportArchived,
invoiceReceiverPolicy,
isChatReportArchived,
+ areStrictPolicyRulesEnabled,
+ currentUserDetails.email,
]);
const addExpenseDropdownOptions = useMemo(
diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx
index bdf6dc71d0fc..e5e2d9beb1b0 100644
--- a/src/components/ReportActionItem/MoneyRequestView.tsx
+++ b/src/components/ReportActionItem/MoneyRequestView.tsx
@@ -133,6 +133,8 @@ function MoneyRequestView({
const {getReportRHPActiveRoute} = useActiveRoute();
const [lastVisitedPath] = useOnyx(ONYXKEYS.LAST_VISITED_PATH, {canBeMissing: true});
+ const [allTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {canBeMissing: false});
+
const parentReportID = report?.parentReportID;
const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${parentReportID}`];
const [parentReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, {
@@ -479,7 +481,7 @@ function MoneyRequestView({
}
if (isExpenseSplit) {
- initSplitExpense(transaction);
+ initSplitExpense(allTransactions, allReports, transaction);
return;
}
@@ -511,7 +513,7 @@ function MoneyRequestView({
}
if (isExpenseSplit) {
- initSplitExpense(transaction);
+ initSplitExpense(allTransactions, allReports, transaction);
return;
}
@@ -670,7 +672,7 @@ function MoneyRequestView({
}
if (isExpenseSplit) {
- initSplitExpense(transaction);
+ initSplitExpense(allTransactions, allReports, transaction);
return;
}
diff --git a/src/components/ReportActionItem/TaskPreview.tsx b/src/components/ReportActionItem/TaskPreview.tsx
index b7be9f1b44ea..d9ee9f9dc091 100644
--- a/src/components/ReportActionItem/TaskPreview.tsx
+++ b/src/components/ReportActionItem/TaskPreview.tsx
@@ -144,7 +144,7 @@ function TaskPreview({
disabled={!isTaskActionable}
onPress={callFunctionIfActionIsAllowed(() => {
if (isTaskCompleted) {
- reopenTask(taskReport, taskReportID);
+ reopenTask(taskReport, currentUserPersonalDetails.accountID, taskReportID);
} else {
completeTask(taskReport, taskReportID);
}
diff --git a/src/components/ReportActionItem/TaskView.tsx b/src/components/ReportActionItem/TaskView.tsx
index 08576412d4eb..59fa04a23acd 100644
--- a/src/components/ReportActionItem/TaskView.tsx
+++ b/src/components/ReportActionItem/TaskView.tsx
@@ -128,7 +128,7 @@ function TaskView({report, parentReport, action}: TaskViewProps) {
return;
}
if (isCompleted) {
- reopenTask(report);
+ reopenTask(report, currentUserPersonalDetails.accountID);
} else {
completeTask(report);
}
diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx
index c3a9e05df529..7ad0654ea481 100644
--- a/src/components/Search/index.tsx
+++ b/src/components/Search/index.tsx
@@ -1,5 +1,4 @@
import {findFocusedRoute, useFocusEffect, useIsFocused, useNavigation} from '@react-navigation/native';
-import {accountIDSelector} from '@selectors/Session';
import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react';
import type {NativeScrollEvent, NativeSyntheticEvent, StyleProp, ViewStyle} from 'react-native';
import {View} from 'react-native';
@@ -244,6 +243,7 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS
const [reportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS, {canBeMissing: true});
const [outstandingReportsByPolicyID] = useOnyx(ONYXKEYS.DERIVED.OUTSTANDING_REPORTS_BY_POLICY_ID, {canBeMissing: true});
const [violations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {canBeMissing: true});
+ const {accountID, email} = useCurrentUserPersonalDetails();
const [activePaidPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {
selector: activePaidPoliciesSelector,
@@ -276,7 +276,7 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS
const transactionViolations = violations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction.transactionID}`];
if (transactionViolations) {
const filteredTransactionViolations = transactionViolations.filter((violation) =>
- shouldShowViolation(report as OnyxEntry, policy as OnyxEntry, violation.name),
+ shouldShowViolation(report as OnyxEntry, policy as OnyxEntry, violation.name, email ?? ''),
);
if (filteredTransactionViolations.length > 0) {
@@ -287,7 +287,7 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS
}
return filtered;
- }, [violations, searchResults]);
+ }, [violations, searchResults, email]);
const archivedReportsIdSet = useArchivedReportsIdSet();
@@ -298,7 +298,6 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS
});
const {defaultCardFeed} = useCardFeedsForDisplay();
- const [accountID] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: false, selector: accountIDSelector});
const suggestedSearches = useMemo(() => getSuggestedSearches(accountID, defaultCardFeed?.id), [defaultCardFeed?.id, accountID]);
const searchKey = useMemo(() => Object.values(suggestedSearches).find((search) => search.similarSearchHash === similarSearchHash)?.key, [suggestedSearches, similarSearchHash]);
@@ -403,9 +402,9 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS
return [[], 0];
}
- const data1 = getSections(type, searchResults.data, accountID, formatPhoneNumber, groupBy, exportReportActions, searchKey, archivedReportsIdSet, queryJSON);
+ const data1 = getSections(type, searchResults.data, accountID, email ?? '', formatPhoneNumber, groupBy, exportReportActions, searchKey, archivedReportsIdSet, queryJSON);
return [data1, data1.length];
- }, [searchKey, exportReportActions, groupBy, isDataLoaded, searchResults, type, archivedReportsIdSet, formatPhoneNumber, accountID, queryJSON]);
+ }, [searchKey, exportReportActions, groupBy, isDataLoaded, searchResults, type, archivedReportsIdSet, formatPhoneNumber, accountID, queryJSON, email]);
useEffect(() => {
/** We only want to display the skeleton for the status filters the first time we load them for a specific data type */
@@ -618,8 +617,7 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS
// If we're trying to open a transaction without a transaction thread, let's create the thread and navigate the user
if (isTransactionItem && item.transactionThreadReportID === CONST.REPORT.UNREPORTED_REPORT_ID) {
- const iouReportAction = getIOUActionForTransactionID(reportActionsArray, item.transactionID);
- createAndOpenSearchTransactionThread(item, iouReportAction, hash, backTo);
+ createAndOpenSearchTransactionThread(item, hash, backTo, transactionPreviewData);
return;
}
@@ -680,7 +678,11 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS
if (isTransactionGroupListItemType(item)) {
const firstTransaction = item.transactions.at(0);
if (item.isOneTransactionReport && firstTransaction && transactionPreviewData) {
- setOptimisticDataForTransactionThreadPreview(firstTransaction, transactionPreviewData);
+ if (firstTransaction.transactionThreadReportID === CONST.REPORT.UNREPORTED_REPORT_ID) {
+ createAndOpenSearchTransactionThread(firstTransaction, hash, backTo, transactionPreviewData, false);
+ } else {
+ setOptimisticDataForTransactionThreadPreview(firstTransaction, transactionPreviewData);
+ }
}
requestAnimationFrame(() => Navigation.navigate(ROUTES.SEARCH_MONEY_REQUEST_REPORT.getRoute({reportID, backTo})));
return;
@@ -704,7 +706,7 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS
requestAnimationFrame(() => Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID, backTo})));
},
- [isMobileSelectionModeEnabled, type, toggleTransaction, reportActionsArray, hash, queryJSON, handleSearch, searchKey, markReportIDAsExpense],
+ [isMobileSelectionModeEnabled, type, toggleTransaction, hash, queryJSON, handleSearch, searchKey, markReportIDAsExpense],
);
const currentColumns = useMemo(() => {
diff --git a/src/pages/workspace/categories/SpendCategorySelectorListItem.tsx b/src/components/SelectionList/ListItem/SpendCategorySelectorListItem.tsx
similarity index 83%
rename from src/pages/workspace/categories/SpendCategorySelectorListItem.tsx
rename to src/components/SelectionList/ListItem/SpendCategorySelectorListItem.tsx
index d30be7adfe2b..41c2610d744a 100644
--- a/src/pages/workspace/categories/SpendCategorySelectorListItem.tsx
+++ b/src/components/SelectionList/ListItem/SpendCategorySelectorListItem.tsx
@@ -1,10 +1,10 @@
import React from 'react';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
-import BaseListItem from '@components/SelectionListWithSections/BaseListItem';
-import type {BaseListItemProps, ListItem} from '@components/SelectionListWithSections/types';
import useThemeStyles from '@hooks/useThemeStyles';
+import BaseListItem from './BaseListItem';
+import type {ListItem, SpendCategorySelectorListItemProps} from './types';
-function SpendCategorySelectorListItem({item, onSelectRow, isFocused}: BaseListItemProps) {
+function SpendCategorySelectorListItem({item, onSelectRow, isFocused}: SpendCategorySelectorListItemProps) {
const styles = useThemeStyles();
const {groupID, categoryID} = item;
diff --git a/src/components/SelectionList/ListItem/types.ts b/src/components/SelectionList/ListItem/types.ts
index 8a7b06e2823d..050ce0b1a0cf 100644
--- a/src/components/SelectionList/ListItem/types.ts
+++ b/src/components/SelectionList/ListItem/types.ts
@@ -7,7 +7,11 @@ import type {BrickRoad} from '@libs/WorkspacesSettingsUtils';
import type CursorStyles from '@styles/utils/cursor/types';
import type {Errors, Icon, PendingAction} from '@src/types/onyx/OnyxCommon';
import type {ReceiptErrors} from '@src/types/onyx/Transaction';
+import type BaseListItem from './BaseListItem';
+import type MultiSelectListItem from './MultiSelectListItem';
import type RadioListItem from './RadioListItem';
+import type SingleSelectListItem from './SingleSelectListItem';
+import type SpendCategorySelectorListItem from './SpendCategorySelectorListItem';
type ListItem = {
/** Text to display */
@@ -230,7 +234,7 @@ type ListItemProps = CommonListItemProps & {
shouldUseDefaultRightHandSideCheckmark?: boolean;
};
-type ValidListItem = typeof RadioListItem;
+type ValidListItem = typeof RadioListItem | typeof BaseListItem | typeof MultiSelectListItem | typeof SingleSelectListItem | typeof SpendCategorySelectorListItem;
type BaseListItemProps = CommonListItemProps & {
item: TItem;
@@ -259,6 +263,8 @@ type SingleSelectListItemProps = ListItemProps;
type MultiSelectListItemProps = ListItemProps;
+type SpendCategorySelectorListItemProps = ListItemProps;
+
type UserListItemProps = ListItemProps & ForwardedFSClassProps;
export type {
@@ -271,5 +277,6 @@ export type {
ValidListItem,
SingleSelectListItemProps,
MultiSelectListItemProps,
+ SpendCategorySelectorListItemProps,
UserListItemProps,
};
diff --git a/src/components/SelectionListWithSections/Search/TransactionGroupListExpanded.tsx b/src/components/SelectionListWithSections/Search/TransactionGroupListExpanded.tsx
index 328fb3fb2fc7..8fcbe8066258 100644
--- a/src/components/SelectionListWithSections/Search/TransactionGroupListExpanded.tsx
+++ b/src/components/SelectionListWithSections/Search/TransactionGroupListExpanded.tsx
@@ -10,13 +10,13 @@ import type {ListItem, TransactionGroupListExpandedProps, TransactionListItemTyp
import Text from '@components/Text';
import TransactionItemRow from '@components/TransactionItemRow';
import {WideRHPContext} from '@components/WideRHPContextProvider';
+import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useLocalize from '@hooks/useLocalize';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import {getReportIDForTransaction} from '@libs/MoneyRequestReportUtils';
import Navigation from '@libs/Navigation/Navigation';
-import {getReportAction} from '@libs/ReportActionsUtils';
import {createAndOpenSearchTransactionThread, getColumnsToShow} from '@libs/SearchUIUtils';
import {getTransactionViolations} from '@libs/TransactionUtils';
import {setActiveTransactionIDs} from '@userActions/TransactionThreadNavigation';
@@ -46,6 +46,7 @@ function TransactionGroupListExpanded({
}: TransactionGroupListExpandedProps) {
const theme = useTheme();
const styles = useThemeStyles();
+ const currentUserDetails = useCurrentUserPersonalDetails();
const {translate} = useLocalize();
const {currentSearchHash} = useSearchContext();
const transactionsSnapshotMetadata = useMemo(() => {
@@ -106,8 +107,7 @@ function TransactionGroupListExpanded({
const navigateToTransactionThread = () => {
if (transactionItem.transactionThreadReportID === CONST.REPORT.UNREPORTED_REPORT_ID) {
- const iouAction = getReportAction(transactionItem.report?.reportID, transactionItem.moneyRequestReportActionID);
- createAndOpenSearchTransactionThread(transactionItem, iouAction, currentSearchHash, backTo);
+ createAndOpenSearchTransactionThread(transactionItem, currentSearchHash, backTo);
return;
}
markReportIDAsExpense(reportID);
@@ -181,7 +181,7 @@ function TransactionGroupListExpanded({
({
const styles = useThemeStyles();
const {formatPhoneNumber} = useLocalize();
const {selectedTransactions} = useSearchContext();
+ const currentUserDetails = useCurrentUserPersonalDetails();
const oneTransactionItem = groupItem.isOneTransactionReport ? groupItem.transactions.at(0) : undefined;
const [parentReport] = originalUseOnyx(`${ONYXKEYS.COLLECTION.REPORT}${oneTransactionItem?.reportID}`, {canBeMissing: true});
@@ -96,12 +98,18 @@ function TransactionGroupListItem({
if (!transactionsSnapshot?.data) {
return [];
}
- const sectionData = getSections(CONST.SEARCH.DATA_TYPES.EXPENSE, transactionsSnapshot?.data, accountID, formatPhoneNumber) as TransactionListItemType[];
+ const sectionData = getSections(
+ CONST.SEARCH.DATA_TYPES.EXPENSE,
+ transactionsSnapshot?.data,
+ accountID,
+ currentUserDetails.email ?? '',
+ formatPhoneNumber,
+ ) as TransactionListItemType[];
return sectionData.map((transactionItem) => ({
...transactionItem,
isSelected: selectedTransactionIDsSet.has(transactionItem.transactionID),
}));
- }, [isGroupByReports, transactionsSnapshot?.data, accountID, formatPhoneNumber, groupItem.transactions, selectedTransactionIDsSet]);
+ }, [isGroupByReports, transactionsSnapshot?.data, accountID, formatPhoneNumber, groupItem.transactions, selectedTransactionIDsSet, currentUserDetails.email]);
const selectedItemsLength = useMemo(() => {
return transactions.reduce((acc, transaction) => {
diff --git a/src/components/SelectionListWithSections/Search/TransactionListItem.tsx b/src/components/SelectionListWithSections/Search/TransactionListItem.tsx
index b83d787037ad..44212380ca97 100644
--- a/src/components/SelectionListWithSections/Search/TransactionListItem.tsx
+++ b/src/components/SelectionListWithSections/Search/TransactionListItem.tsx
@@ -11,6 +11,7 @@ import {useSearchContext} from '@components/Search/SearchContext';
import type {ListItem, TransactionListItemProps, TransactionListItemType} from '@components/SelectionListWithSections/types';
import TransactionItemRow from '@components/TransactionItemRow';
import useAnimatedHighlightStyle from '@hooks/useAnimatedHighlightStyle';
+import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useOnyx from '@hooks/useOnyx';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useStyleUtils from '@hooks/useStyleUtils';
@@ -70,6 +71,7 @@ function TransactionListItem({
const [parentReportAction] = originalUseOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionItem.reportID}`, {selector: parentReportActionSelector, canBeMissing: true}, [
transactionItem,
]);
+ const currentUserDetails = useCurrentUserPersonalDetails();
const transactionPreviewData: TransactionPreviewData = useMemo(
() => ({hasParentReport: !!parentReport, hasTransaction: !!transaction, hasParentReportAction: !!parentReportAction, hasTransactionThreadReport: !!transactionThreadReport}),
[parentReport, transaction, parentReportAction, transactionThreadReport],
@@ -99,9 +101,11 @@ function TransactionListItem({
const transactionViolations = useMemo(() => {
return (violations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionItem.transactionID}`] ?? []).filter(
- (violation: TransactionViolation) => !isViolationDismissed(transactionItem, violation) && shouldShowViolation(snapshotReport, snapshotPolicy as Policy, violation.name, false),
+ (violation: TransactionViolation) =>
+ !isViolationDismissed(transactionItem, violation, currentUserDetails.email ?? '') &&
+ shouldShowViolation(snapshotReport, snapshotPolicy as Policy, violation.name, currentUserDetails.email ?? '', false),
);
- }, [snapshotPolicy, snapshotReport, transactionItem, violations]);
+ }, [snapshotPolicy, snapshotReport, transactionItem, violations, currentUserDetails.email]);
const handleActionButtonPress = useCallback(() => {
handleActionButtonPressUtil(
diff --git a/src/components/SelectionListWithSections/types.ts b/src/components/SelectionListWithSections/types.ts
index 3ed19fc3d8be..39e70490e721 100644
--- a/src/components/SelectionListWithSections/types.ts
+++ b/src/components/SelectionListWithSections/types.ts
@@ -21,7 +21,6 @@ import type {SearchColumnType, SearchGroupBy, SearchQueryJSON} from '@components
import type {ForwardedFSClassProps} from '@libs/Fullstory/types';
import type {BrickRoad} from '@libs/WorkspacesSettingsUtils';
import type UnreportedExpenseListItem from '@pages/UnreportedExpenseListItem';
-import type SpendCategorySelectorListItem from '@pages/workspace/categories/SpendCategorySelectorListItem';
// eslint-disable-next-line no-restricted-imports
import type CursorStyles from '@styles/utils/cursor/types';
import type {TransactionPreviewData} from '@userActions/Search';
@@ -581,8 +580,7 @@ type ValidListItem =
| typeof SearchQueryListItem
| typeof SearchRouterItem
| typeof TravelDomainListItem
- | typeof UnreportedExpenseListItem
- | typeof SpendCategorySelectorListItem;
+ | typeof UnreportedExpenseListItem;
type Section = {
/** Title of the section */
diff --git a/src/components/TaskHeaderActionButton.tsx b/src/components/TaskHeaderActionButton.tsx
index 63f4000bc5cd..c755e907f3e8 100644
--- a/src/components/TaskHeaderActionButton.tsx
+++ b/src/components/TaskHeaderActionButton.tsx
@@ -1,5 +1,6 @@
import React from 'react';
import {View} from 'react-native';
+import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useLocalize from '@hooks/useLocalize';
import useParentReport from '@hooks/useParentReport';
import useReportIsArchived from '@hooks/useReportIsArchived';
@@ -10,7 +11,6 @@ import {callFunctionIfActionIsAllowed} from '@userActions/Session';
import {canActionTask, completeTask, reopenTask} from '@userActions/Task';
import type * as OnyxTypes from '@src/types/onyx';
import Button from './Button';
-import {useSession} from './OnyxListItemProvider';
type TaskHeaderActionButtonProps = {
/** The report currently being looked at */
@@ -20,10 +20,10 @@ type TaskHeaderActionButtonProps = {
function TaskHeaderActionButton({report}: TaskHeaderActionButtonProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
- const session = useSession();
+ const currentUserPersonalDetails = useCurrentUserPersonalDetails();
const parentReport = useParentReport(report.reportID);
const isParentReportArchived = useReportIsArchived(parentReport?.reportID);
- const isTaskActionable = canActionTask(report, session?.accountID, parentReport, isParentReportArchived);
+ const isTaskActionable = canActionTask(report, currentUserPersonalDetails?.accountID, parentReport, isParentReportArchived);
if (!canWriteInReport(report)) {
return null;
@@ -41,7 +41,7 @@ function TaskHeaderActionButton({report}: TaskHeaderActionButtonProps) {
return;
}
if (isCompletedTaskReport(report)) {
- reopenTask(report);
+ reopenTask(report, currentUserPersonalDetails.accountID);
} else {
completeTask(report);
}
diff --git a/src/components/TestDrive/TestDriveDemo.tsx b/src/components/TestDrive/TestDriveDemo.tsx
index 1bf5702604bd..81daaf5881a1 100644
--- a/src/components/TestDrive/TestDriveDemo.tsx
+++ b/src/components/TestDrive/TestDriveDemo.tsx
@@ -43,7 +43,7 @@ function TestDriveDemo() {
// eslint-disable-next-line @typescript-eslint/no-deprecated
InteractionManager.runAfterInteractions(() => {
setIsVisible(true);
- completeTestDriveTask(viewTourTaskReport, viewTourTaskParentReport, isViewTourTaskParentReportArchived);
+ completeTestDriveTask(viewTourTaskReport, viewTourTaskParentReport, isViewTourTaskParentReportArchived, currentUserPersonalDetails.accountID);
});
// This should fire only during mount.
diff --git a/src/components/TestToolMenu.tsx b/src/components/TestToolMenu.tsx
index e6232e5d123d..e79f76399c90 100644
--- a/src/components/TestToolMenu.tsx
+++ b/src/components/TestToolMenu.tsx
@@ -2,16 +2,13 @@ import React from 'react';
import useIsAuthenticated from '@hooks/useIsAuthenticated';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
-import usePermissions from '@hooks/usePermissions';
import useThemeStyles from '@hooks/useThemeStyles';
import {isUsingStagingApi} from '@libs/ApiUtils';
import {setShouldFailAllRequests, setShouldForceOffline, setShouldSimulatePoorConnection} from '@userActions/Network';
import {expireSessionWithDelay, invalidateAuthToken, invalidateCredentials} from '@userActions/Session';
-import {setIsDebugModeEnabled, setShouldBlockTransactionThreadReportCreation, setShouldUseStagingServer} from '@userActions/User';
+import {setIsDebugModeEnabled, setShouldUseStagingServer} from '@userActions/User';
import CONFIG from '@src/CONFIG';
-import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
-import type {Account as AccountOnyx} from '@src/types/onyx';
import Button from './Button';
import SoftKillTestToolRow from './SoftKillTestToolRow';
import Switch from './Switch';
@@ -19,22 +16,11 @@ import TestCrash from './TestCrash';
import TestToolRow from './TestToolRow';
import Text from './Text';
-const ACCOUNT_DEFAULT: AccountOnyx = {
- isSubscribedToNewsletter: false,
- validated: false,
- isFromPublicDomain: false,
- isUsingExpensifyCard: false,
-};
-
function TestToolMenu() {
- const {isBetaEnabled} = usePermissions();
const [network] = useOnyx(ONYXKEYS.NETWORK, {canBeMissing: true});
- const [account = ACCOUNT_DEFAULT] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true});
const [isUsingImportedState] = useOnyx(ONYXKEYS.IS_USING_IMPORTED_STATE, {canBeMissing: true});
const [shouldUseStagingServer = isUsingStagingApi()] = useOnyx(ONYXKEYS.SHOULD_USE_STAGING_SERVER, {canBeMissing: true});
const [isDebugModeEnabled = false] = useOnyx(ONYXKEYS.IS_DEBUG_MODE_ENABLED, {canBeMissing: true});
- const shouldBlockTransactionThreadReportCreation = !!account?.shouldBlockTransactionThreadReportCreation;
- const shouldShowTransactionThreadReportToggle = isBetaEnabled(CONST.BETAS.NO_OPTIMISTIC_TRANSACTION_THREADS);
const styles = useThemeStyles();
const {translate} = useLocalize();
@@ -51,17 +37,6 @@ function TestToolMenu() {
{isAuthenticated && (
<>
- {/* When toggled, the app won't create the transaction thread report. It should be removed together with CONST.BETAS.NO_OPTIMISTIC_TRANSACTION_THREADS beta */}
- {shouldShowTransactionThreadReportToggle && (
-
- setShouldBlockTransactionThreadReportCreation(!shouldBlockTransactionThreadReportCreation)}
- />
-
- )}
-
{/* When toggled the app will be put into debug mode. */}
,
+ duplicateTransactionViolations: OnyxCollection,
+ currentSearchHash?: number,
+ isSingleTransactionView?: boolean,
+ ): string[] => {
+ if (!transactionIDs.length) {
+ return [];
+ }
+
+ const iouActions = reportActions.filter((action) => isMoneyRequestAction(action));
+
+ const transactionsWithActions = transactionIDs.map((transactionID) => ({
+ transactionID,
+ transaction: allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`],
+ action: iouActions.find((action) => {
+ const IOUTransactionID = getOriginalMessage(action)?.IOUTransactionID;
+ return transactionID === IOUTransactionID;
+ }),
+ }));
+
+ const deletedTransactionIDs: string[] = [];
+ const deletedTransactionThreadReportIDs = new Set();
+ const {splitTransactionsByOriginalTransactionID, nonSplitTransactions} = transactionsWithActions.reduce(
+ (acc, item) => {
+ const {transaction} = item;
+ const {isExpenseSplit} = getOriginalTransactionWithSplitInfo(transaction);
+ const originalTransactionID = transaction?.comment?.originalTransactionID;
+
+ if (isExpenseSplit && originalTransactionID) {
+ acc.splitTransactionsByOriginalTransactionID[originalTransactionID] ??= [];
+ acc.splitTransactionsByOriginalTransactionID[originalTransactionID].push(item);
+ } else {
+ acc.nonSplitTransactions.push(item);
+ }
+
+ return acc;
+ },
+ {splitTransactionsByOriginalTransactionID: {}, nonSplitTransactions: []} as {
+ splitTransactionsByOriginalTransactionID: Record>;
+ nonSplitTransactions: Array<{transactionID: string; action?: ReportAction; transaction?: Transaction}>;
+ },
+ );
+
+ Object.keys(splitTransactionsByOriginalTransactionID).forEach((transactionID) => {
+ const splitIDs = (splitTransactionsByOriginalTransactionID[transactionID] ?? []).map((transaction) => transaction.transactionID);
+ const childTransactions = getChildTransactions(allTransactions, allReports, transactionID).filter(
+ (transaction) => !splitIDs.includes(transaction?.transactionID ?? String(CONST.DEFAULT_NUMBER_ID)),
+ );
+
+ if (childTransactions.length === 0) {
+ nonSplitTransactions.push(...splitTransactionsByOriginalTransactionID[transactionID]);
+ return;
+ }
+ const originalTransaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`];
+ const originalTransactionIouActions = getIOUActionForTransactions([transactionID], report?.reportID);
+ const iouReportID = isMoneyRequestAction(originalTransactionIouActions.at(0)) ? getOriginalMessage(originalTransactionIouActions.at(0))?.IOUReportID : undefined;
+ const iouReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`];
+ const reportNameValuePairs = allReportNameValuePairs?.[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${iouReportID}`];
+ const isChatIOUReportArchived = isArchivedReport(reportNameValuePairs);
+ const chatReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${iouReport?.chatReportID}`];
+ const policyRecentlyUsedCategories =
+ allPolicyRecentlyUsedCategories?.[
+ `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES}${getNonEmptyStringOnyxID(getIOURequestPolicyID(originalTransaction, report))}`
+ ] ?? [];
+ updateSplitTransactions({
+ allTransactionsList: allTransactions,
+ allReportsList: allReports,
+ allReportNameValuePairsList: allReportNameValuePairs,
+ transactionData: {
+ reportID: report?.reportID ?? String(CONST.DEFAULT_NUMBER_ID),
+ originalTransactionID: transactionID,
+ splitExpenses: childTransactions.map((childTransaction) => initSplitExpenseItemData(childTransaction)),
+ },
+ hash: currentSearchHash ?? 0,
+ policyCategories,
+ policy,
+ policyRecentlyUsedCategories,
+ iouReport,
+ chatReport,
+ firstIOU: originalTransactionIouActions.at(0),
+ isChatReportArchived: isChatIOUReportArchived,
+ isNewDotRevertSplitsEnabled: isBetaEnabled(CONST.BETAS.NEWDOT_REVERT_SPLITS),
+ });
+ });
+
+ nonSplitTransactions.forEach(({transactionID, action}) => {
+ if (!action) {
+ return;
+ }
+ const iouReportID = isMoneyRequestAction(action) ? getOriginalMessage(action)?.IOUReportID : undefined;
+ const iouReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`];
+ const chatReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${iouReport?.chatReportID}`];
+ const chatIOUReportID = chatReport?.reportID;
+ const isChatIOUReportArchived = archivedReportsIdSet.has(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${chatIOUReportID}`);
+ deleteMoneyRequest(
+ transactionID,
+ action,
+ duplicateTransactions,
+ duplicateTransactionViolations,
+ iouReport,
+ chatReport,
+ isChatIOUReportArchived,
+ isSingleTransactionView,
+ deletedTransactionIDs,
+ transactionIDs,
+ );
+ deletedTransactionIDs.push(transactionID);
+ if (action.childReportID) {
+ deletedTransactionThreadReportIDs.add(action.childReportID);
+ }
+ });
+
+ return Array.from(deletedTransactionThreadReportIDs);
+ },
+ [reportActions, isBetaEnabled, allTransactions, allReports, report, allReportNameValuePairs, allPolicyRecentlyUsedCategories, policyCategories, policy, archivedReportsIdSet],
+ );
+
+ return {
+ deleteTransactions,
+ };
+}
+
+export default useDeleteTransactions;
diff --git a/src/hooks/useSelectedTransactionsActions.ts b/src/hooks/useSelectedTransactionsActions.ts
index c9e5840af291..14b2cbf4ff9d 100644
--- a/src/hooks/useSelectedTransactionsActions.ts
+++ b/src/hooks/useSelectedTransactionsActions.ts
@@ -2,12 +2,12 @@ import {useCallback, useMemo, useState} from 'react';
import * as Expensicons from '@components/Icon/Expensicons';
import type {PopoverMenuItem} from '@components/PopoverMenu';
import {useSearchContext} from '@components/Search/SearchContext';
-import {deleteMoneyRequest, unholdRequest} from '@libs/actions/IOU';
+import {unholdRequest} from '@libs/actions/IOU';
import {setupMergeTransactionData} from '@libs/actions/MergeTransaction';
import {exportReportToCSV} from '@libs/actions/Report';
import {getExportTemplates} from '@libs/actions/Search';
import Navigation from '@libs/Navigation/Navigation';
-import {getIOUActionForTransactionID, getOriginalMessage, isDeletedAction, isMoneyRequestAction} from '@libs/ReportActionsUtils';
+import {getIOUActionForTransactionID, isDeletedAction} from '@libs/ReportActionsUtils';
import {isMergeAction} from '@libs/ReportSecondaryActionUtils';
import {
canDeleteCardTransactionByLiabilityType,
@@ -24,7 +24,7 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {Policy, Report, ReportAction, Session, Transaction} from '@src/types/onyx';
-import useArchivedReportsIdSet from './useArchivedReportsIdSet';
+import useDeleteTransactions from './useDeleteTransactions';
import useDuplicateTransactionsAndViolations from './useDuplicateTransactionsAndViolations';
import useLocalize from './useLocalize';
import useNetworkWithOfflineStatus from './useNetworkWithOfflineStatus';
@@ -57,17 +57,17 @@ function useSelectedTransactionsActions({
beginExportWithTemplate: (templateName: string, templateType: string, transactionIDList: string[], policyID?: string) => void;
}) {
const {isOffline} = useNetworkWithOfflineStatus();
- const {selectedTransactionIDs, clearSelectedTransactions} = useSearchContext();
+ const {selectedTransactionIDs, clearSelectedTransactions, currentSearchHash} = useSearchContext();
const [allTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {canBeMissing: false});
const [outstandingReportsByPolicyID] = useOnyx(ONYXKEYS.DERIVED.OUTSTANDING_REPORTS_BY_POLICY_ID, {canBeMissing: true});
const [lastVisitedPath] = useOnyx(ONYXKEYS.LAST_VISITED_PATH, {canBeMissing: true});
- const [allReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: true});
const [integrationsExportTemplates] = useOnyx(ONYXKEYS.NVP_INTEGRATION_SERVER_EXPORT_TEMPLATES, {canBeMissing: true});
const [csvExportLayouts] = useOnyx(ONYXKEYS.NVP_CSV_EXPORT_LAYOUTS, {canBeMissing: true});
const {duplicateTransactions, duplicateTransactionViolations} = useDuplicateTransactionsAndViolations(selectedTransactionIDs);
const isReportArchived = useReportIsArchived(report?.reportID);
+ const {deleteTransactions} = useDeleteTransactions({report, reportActions, policy});
const selectedTransactions = useMemo(
() =>
selectedTransactionIDs.reduce((acc, transactionID) => {
@@ -85,8 +85,6 @@ function useSelectedTransactionsActions({
const isTrackExpenseThread = isTrackExpenseReport(report);
const isInvoice = isInvoiceReport(report);
- const archivedReportsIdSet = useArchivedReportsIdSet();
-
let iouType: IOUType = CONST.IOU.TYPE.SUBMIT;
if (isTrackExpenseThread) {
@@ -97,48 +95,11 @@ function useSelectedTransactionsActions({
}
const handleDeleteTransactions = useCallback(() => {
- const iouActions = reportActions.filter((action) => isMoneyRequestAction(action));
-
- const transactionsWithActions = selectedTransactionIDs.map((transactionID) => ({
- transactionID,
- action: iouActions.find((action) => {
- const IOUTransactionID = getOriginalMessage(action)?.IOUTransactionID;
- return transactionID === IOUTransactionID;
- }),
- }));
- const deletedTransactionIDs: string[] = [];
- const deletedTransactionThreadReportIDs = new Set();
- transactionsWithActions.forEach(({transactionID, action}) => {
- if (!action) {
- return;
- }
-
- const iouReportID = isMoneyRequestAction(action) ? getOriginalMessage(action)?.IOUReportID : undefined;
- const iouReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`];
- const chatReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${iouReport?.chatReportID}`];
- const chatIOUReportID = chatReport?.reportID;
- const isChatIOUReportArchived = archivedReportsIdSet.has(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${chatIOUReportID}`);
- deleteMoneyRequest(
- transactionID,
- action,
- duplicateTransactions,
- duplicateTransactionViolations,
- iouReport,
- chatReport,
- isChatIOUReportArchived,
- false,
- deletedTransactionIDs,
- selectedTransactionIDs,
- );
- deletedTransactionIDs.push(transactionID);
- if (action.childReportID) {
- deletedTransactionThreadReportIDs.add(action.childReportID);
- }
- });
+ const deletedThreadReportIDs = deleteTransactions(selectedTransactionIDs, duplicateTransactions, duplicateTransactionViolations, currentSearchHash, false);
clearSelectedTransactions(true);
setIsDeleteModalVisible(false);
- Navigation.removeReportScreen(deletedTransactionThreadReportIDs);
- }, [duplicateTransactions, duplicateTransactionViolations, reportActions, selectedTransactionIDs, clearSelectedTransactions, allReports, archivedReportsIdSet]);
+ Navigation.removeReportScreen(new Set(deletedThreadReportIDs));
+ }, [deleteTransactions, selectedTransactionIDs, duplicateTransactions, duplicateTransactionViolations, currentSearchHash, clearSelectedTransactions]);
const showDeleteModal = useCallback(() => {
setIsDeleteModalVisible(true);
diff --git a/src/hooks/useStrictPolicyRules.ts b/src/hooks/useStrictPolicyRules.ts
new file mode 100644
index 000000000000..fa2062dbd501
--- /dev/null
+++ b/src/hooks/useStrictPolicyRules.ts
@@ -0,0 +1,37 @@
+import {Str} from 'expensify-common';
+import ONYXKEYS from '@src/ONYXKEYS';
+import useOnyx from './useOnyx';
+
+type UseStrictPolicyRulesResult = {
+ /** Whether the user's domain has strict policy rules enabled (strictly enforce workspace rules) */
+ areStrictPolicyRulesEnabled: boolean;
+};
+
+/**
+ * Hook to check if strict policy rules are enabled for the user's domain security group.
+ * When enabled, users cannot submit reports that have policy violations.
+ */
+function useStrictPolicyRules(): UseStrictPolicyRulesResult {
+ const [myDomainSecurityGroups] = useOnyx(ONYXKEYS.MY_DOMAIN_SECURITY_GROUPS, {canBeMissing: true});
+ const [securityGroups] = useOnyx(ONYXKEYS.COLLECTION.SECURITY_GROUP, {canBeMissing: true});
+ const [session] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: true});
+
+ // Get the user's domain from their email
+ const userDomain = session?.email ? Str.extractEmailDomain(session.email) : undefined;
+
+ // Get the security group ID for the user's domain
+ const securityGroupID = userDomain && myDomainSecurityGroups?.[userDomain];
+
+ // Get the security group details
+ const securityGroupKey = `${ONYXKEYS.COLLECTION.SECURITY_GROUP}${securityGroupID}`;
+ const securityGroup = securityGroupID ? securityGroups?.[securityGroupKey] : null;
+
+ // Check if strict policy rules are enabled
+ const areStrictPolicyRulesEnabled = securityGroup?.enableStrictPolicyRules === true;
+
+ return {
+ areStrictPolicyRulesEnabled,
+ };
+}
+
+export default useStrictPolicyRules;
diff --git a/src/hooks/useTransactionViolations.ts b/src/hooks/useTransactionViolations.ts
index 904df6f4326f..b4fa29a09db3 100644
--- a/src/hooks/useTransactionViolations.ts
+++ b/src/hooks/useTransactionViolations.ts
@@ -4,6 +4,7 @@ import {isViolationDismissed, shouldShowViolation} from '@libs/TransactionUtils'
import ONYXKEYS from '@src/ONYXKEYS';
import type {TransactionViolation, TransactionViolations} from '@src/types/onyx';
import getEmptyArray from '@src/types/utils/getEmptyArray';
+import useCurrentUserPersonalDetails from './useCurrentUserPersonalDetails';
import useOnyx from './useOnyx';
function useTransactionViolations(transactionID?: string, shouldShowRterForSettledReport = true): TransactionViolations {
@@ -19,13 +20,16 @@ function useTransactionViolations(transactionID?: string, shouldShowRterForSettl
const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${iouReport?.policyID}`, {
canBeMissing: true,
});
+ const currentUserDetails = useCurrentUserPersonalDetails();
return useMemo(
() =>
transactionViolations.filter(
- (violation: TransactionViolation) => !isViolationDismissed(transaction, violation) && shouldShowViolation(iouReport, policy, violation.name, shouldShowRterForSettledReport),
+ (violation: TransactionViolation) =>
+ !isViolationDismissed(transaction, violation, currentUserDetails.email ?? '') &&
+ shouldShowViolation(iouReport, policy, violation.name, currentUserDetails.email ?? '', shouldShowRterForSettledReport),
),
- [transaction, transactionViolations, iouReport, policy, shouldShowRterForSettledReport],
+ [transaction, transactionViolations, iouReport, policy, shouldShowRterForSettledReport, currentUserDetails.email],
);
}
diff --git a/src/hooks/useTransactionsAndViolationsForReport.ts b/src/hooks/useTransactionsAndViolationsForReport.ts
index e05c493b8e39..a2e8ff0f91d9 100644
--- a/src/hooks/useTransactionsAndViolationsForReport.ts
+++ b/src/hooks/useTransactionsAndViolationsForReport.ts
@@ -4,11 +4,13 @@ import {getTransactionViolations} from '@libs/TransactionUtils';
import ONYXKEYS from '@src/ONYXKEYS';
import type {TransactionViolations} from '@src/types/onyx';
import type {ReportTransactionsAndViolations} from '@src/types/onyx/DerivedValues';
+import useCurrentUserPersonalDetails from './useCurrentUserPersonalDetails';
const DEFAULT_RETURN_VALUE: ReportTransactionsAndViolations = {transactions: {}, violations: {}};
function useTransactionsAndViolationsForReport(reportID?: string) {
const allReportsTransactionsAndViolations = useAllReportsTransactionsAndViolations();
+ const currentUserDetails = useCurrentUserPersonalDetails();
const {transactions, violations} = reportID ? (allReportsTransactionsAndViolations?.[reportID] ?? DEFAULT_RETURN_VALUE) : DEFAULT_RETURN_VALUE;
@@ -20,14 +22,14 @@ function useTransactionsAndViolationsForReport(reportID?: string) {
// This is our accumulator, it's okay to reassign
// eslint-disable-next-line no-param-reassign
- filteredTransactionViolations[transactionViolationKey] = getTransactionViolations(transaction, violations) ?? [];
+ filteredTransactionViolations[transactionViolationKey] = getTransactionViolations(transaction, violations, currentUserDetails.email ?? '') ?? [];
return filteredTransactionViolations;
},
{} as Record,
);
return {transactions, violations: filteredViolations};
- }, [transactions, violations]);
+ }, [transactions, violations, currentUserDetails?.email]);
return transactionsAndViolations;
}
diff --git a/src/languages/de.ts b/src/languages/de.ts
index 40f268ebd5dd..326a2dd2720c 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -237,6 +237,7 @@ import type {
TermsParams,
ThreadRequestReportNameParams,
ThreadSentMoneyReportNameParams,
+ ToggleImportTitleParams,
TotalAmountGreaterOrLessThanOriginalParams,
ToValidateLoginParams,
TransferParams,
@@ -674,6 +675,7 @@ const translations = {
pinned: 'Angeheftet',
read: 'Gelesen',
copyToClipboard: 'In die Zwischenablage kopieren',
+ domains: 'Domänen',
},
supportalNoAccess: {
title: 'Nicht so schnell',
@@ -911,17 +913,17 @@ const translations = {
beginningOfChatHistoryUserRoom: ({reportName, reportDetailsLink}: BeginningOfChatHistoryUserRoomParams) =>
`Dieser Chatraum ist für alles, was mit ${reportName} zu tun hat.`,
beginningOfChatHistoryInvoiceRoom: ({invoicePayer, invoiceReceiver}: BeginningOfChatHistoryInvoiceRoomParams) =>
- `Dieser Chat ist für Rechnungen zwischen ${invoicePayer} und ${invoiceReceiver}. Verwenden Sie die Schaltfläche +, um eine Rechnung zu senden.`,
+ `Dieser Chat ist für Rechnungen zwischen ${invoicePayer} und ${invoiceReceiver}. Verwenden Sie die Schaltfläche ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}, um eine Rechnung zu senden.`,
beginningOfChatHistory: 'Dieser Chat ist mit',
beginningOfChatHistoryPolicyExpenseChat: ({workspaceName, submitterDisplayName}: BeginningOfChatHistoryPolicyExpenseChatParams) =>
- `Hier wird ${submitterDisplayName} die Ausgaben an ${workspaceName} übermitteln. Verwenden Sie einfach die Schaltfläche +.`,
+ `Hier wird ${submitterDisplayName} die Ausgaben an ${workspaceName} übermitteln. Verwenden Sie einfach die Schaltfläche ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}.`,
beginningOfChatHistorySelfDM: 'Dies ist Ihr persönlicher Bereich. Nutzen Sie ihn für Notizen, Aufgaben, Entwürfe und Erinnerungen.',
beginningOfChatHistorySystemDM: 'Willkommen! Lassen Sie uns mit der Einrichtung beginnen.',
chatWithAccountManager: 'Hier mit Ihrem Kundenbetreuer chatten',
sayHello: 'Hallo!',
yourSpace: 'Ihr Bereich',
welcomeToRoom: ({roomName}: WelcomeToRoomParams) => `Willkommen in ${roomName}!`,
- usePlusButton: ({additionalText}: UsePlusButtonParams) => ` Verwenden Sie die + Taste, um ${additionalText} einen Ausgabenposten hinzuzufügen.`,
+ usePlusButton: ({additionalText}: UsePlusButtonParams) => ` Verwenden Sie die ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE} Taste, um ${additionalText} einen Ausgabenposten hinzuzufügen.`,
askConcierge: 'Stellen Sie Fragen und erhalten Sie rund um die Uhr Unterstützung in Echtzeit.',
conciergeSupport: '24/7 Support',
create: 'erstellen',
@@ -1699,7 +1701,6 @@ const translations = {
testCrash: 'Testabsturz',
resetToOriginalState: 'Auf den ursprünglichen Zustand zurücksetzen',
usingImportedState: 'Sie verwenden importierten Status. Drücken Sie hier, um ihn zu löschen.',
- shouldBlockTransactionThreadReportCreation: 'Erstellung von Transaktions-Thread-Berichten blockieren',
debugMode: 'Debug-Modus',
invalidFile: 'Ungültige Datei',
invalidFileDescription: 'Die Datei, die Sie importieren möchten, ist ungültig. Bitte versuchen Sie es erneut.',
@@ -2444,7 +2445,7 @@ ${amount} für ${merchant} - ${date}`,
title: 'Reiche eine Ausgabe ein',
description:
'*Reiche eine Ausgabe ein*, indem du einen Betrag eingibst oder einen Beleg scannst.\n\n' +
- `1. Klicke auf den +-Button.\n` +
+ `1. Klicke auf den ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}-Button.\n` +
'2. Wähle *Ausgabe erstellen*.\n' +
'3. Betrag eingeben oder Beleg scannen.\n' +
`4. Gib die E-Mail oder Telefonnummer deines Chefs ein.\n` +
@@ -2455,7 +2456,7 @@ ${amount} für ${merchant} - ${date}`,
title: 'Reiche eine Ausgabe ein',
description:
'*Reiche eine Ausgabe ein*, indem du einen Betrag eingibst oder einen Beleg scannst.\n\n' +
- `1. Klicke auf den +-Button.\n` +
+ `1. Klicke auf den ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}-Button.\n` +
'2. Wähle *Ausgabe erstellen*.\n' +
'3. Betrag eingeben oder Beleg scannen.\n' +
'4. Details bestätigen.\n' +
@@ -2466,7 +2467,7 @@ ${amount} für ${merchant} - ${date}`,
title: 'Verfolge eine Ausgabe',
description:
'*Verfolge eine Ausgabe* in jeder Währung – mit oder ohne Beleg.\n\n' +
- `1. Klicke auf den +-Button.\n` +
+ `1. Klicke auf den ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}-Button.\n` +
'2. Wähle *Ausgabe erstellen*.\n' +
'3. Betrag eingeben oder Beleg scannen.\n' +
'4. Wähle deinen *persönlichen* Bereich.\n' +
@@ -2550,7 +2551,7 @@ ${amount} für ${merchant} - ${date}`,
title: 'Starte einen Chat',
description:
'*Starte einen Chat* mit jeder Person über E-Mail oder Telefonnummer.\n\n' +
- `1. Klicke auf den +-Button.\n` +
+ `1. Klicke auf den ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}-Button.\n` +
'2. Wähle *Chat starten*.\n' +
'3. Gib eine E-Mail oder Telefonnummer ein.\n\n' +
'Falls die Person Expensify noch nicht nutzt, wird sie automatisch eingeladen.\n\n' +
@@ -2560,7 +2561,7 @@ ${amount} für ${merchant} - ${date}`,
title: 'Teile eine Ausgabe',
description:
'*Teile Ausgaben* mit einer oder mehreren Personen.\n\n' +
- `1. Klicke auf den +-Button.\n` +
+ `1. Klicke auf den ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}-Button.\n` +
'2. Wähle *Chat starten*.\n' +
'3. Gib E-Mail-Adressen oder Telefonnummern ein.\n' +
'4. Klicke im Chat auf den grauen *+*-Button > *Ausgabe teilen*.\n' +
@@ -2580,7 +2581,7 @@ ${amount} für ${merchant} - ${date}`,
title: 'Erstelle deinen ersten Bericht',
description:
'So erstellst du einen Bericht:\n\n' +
- `1. Klicke auf den +-Button.\n` +
+ `1. Klicke auf den ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}-Button.\n` +
'2. Wähle *Bericht erstellen*.\n' +
'3. Klicke auf *Ausgabe hinzufügen*.\n' +
'4. Füge deine erste Ausgabe hinzu.\n\n' +
@@ -2935,8 +2936,8 @@ ${amount} für ${merchant} - ${date}`,
needSSNFull9: 'Wir haben Schwierigkeiten, Ihre SSN zu verifizieren. Bitte geben Sie die vollständigen neun Ziffern Ihrer SSN ein.',
weCouldNotVerify: 'Wir konnten nicht verifizieren',
pleaseFixIt: 'Bitte korrigieren Sie diese Informationen, bevor Sie fortfahren.',
- failedKYCTextBefore: 'Wir konnten Ihre Identität nicht verifizieren. Bitte versuchen Sie es später erneut oder wenden Sie sich an',
- failedKYCTextAfter: 'wenn Sie Fragen haben.',
+ failedKYCMessage: ({conciergeEmail}: {conciergeEmail: string}) =>
+ `Wir konnten Ihre Identität nicht verifizieren. Bitte versuchen Sie es später erneut oder wenden Sie sich an ${conciergeEmail}, wenn Sie Fragen haben.`,
},
termsStep: {
headerTitle: 'Bedingungen und Gebühren',
@@ -4411,8 +4412,7 @@ ${amount} für ${merchant} - ${date}`,
employeeDefaultDescription: 'Die Standardabteilung des Mitarbeiters wird auf seine Ausgaben in Sage Intacct angewendet, falls eine vorhanden ist.',
displayedAsTagDescription: 'Die Abteilung wird für jede einzelne Ausgabe in einem Bericht eines Mitarbeiters auswählbar sein.',
displayedAsReportFieldDescription: 'Die Abteilungsauswahl gilt für alle Ausgaben im Bericht eines Mitarbeiters.',
- toggleImportTitleFirstPart: 'Wählen Sie, wie Sage Intacct behandelt werden soll',
- toggleImportTitleSecondPart: 'in Expensify.',
+ toggleImportTitle: ({mappingTitle}: ToggleImportTitleParams) => `Wählen Sie, wie Sage Intacct behandelt werden soll ${mappingTitle} in Expensify.`,
expenseTypes: 'Ausgabenarten',
expenseTypesDescription: 'Ihre Sage Intacct-Ausgabenarten werden in Expensify als Kategorien importiert.',
accountTypesDescription: 'Ihr Sage Intacct-Kontenplan wird in Expensify als Kategorien importiert.',
@@ -5732,7 +5732,7 @@ ${amount} für ${merchant} - ${date}`,
chatWithYourAdmin: 'Mit Ihrem Administrator chatten',
chatInAdmins: 'Im #admins chatten',
addPaymentCard: 'Zahlungskarte hinzufügen',
- goToSubscriptions: 'Zu den Abonnements',
+ goToSubscription: 'Zum Abonnement',
},
rules: {
individualExpenseRules: {
@@ -6154,7 +6154,7 @@ ${amount} für ${merchant} - ${date}`,
searchResults: {
emptyResults: {
title: 'Nichts zu zeigen',
- subtitle: `Versuchen Sie, Ihre Suchkriterien anzupassen oder etwas mit dem + Button zu erstellen.`,
+ subtitle: `Versuchen Sie, Ihre Suchkriterien anzupassen oder etwas mit dem grünen ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE} Button zu erstellen.`,
},
emptyExpenseResults: {
title: 'Sie haben noch keine Ausgaben erstellt.',
@@ -6773,7 +6773,8 @@ ${amount} für ${merchant} - ${date}`,
return '';
},
brokenConnection530Error: 'Beleg ausstehend aufgrund einer unterbrochenen Bankverbindung',
- adminBrokenConnectionError: 'Beleg ausstehend aufgrund einer unterbrochenen Bankverbindung. Bitte beheben in',
+ adminBrokenConnectionError: ({workspaceCompanyCardRoute}: {workspaceCompanyCardRoute: string}) =>
+ `Beleg ausstehend aufgrund einer unterbrochenen Bankverbindung. Bitte beheben Sie dies unter Firmenkarten.`,
memberBrokenConnectionError: 'Beleg ausstehend aufgrund einer unterbrochenen Bankverbindung. Bitte bitten Sie einen Workspace-Administrator, das Problem zu lösen.',
markAsCashToIgnore: 'Als Barzahlung markieren, um zu ignorieren und Zahlung anzufordern.',
smartscanFailed: ({canEdit = true}) => `Beleg-Scan fehlgeschlagen.${canEdit ? 'Details manuell eingeben.' : ''}`,
@@ -7333,6 +7334,11 @@ ${amount} für ${merchant} - ${date}`,
selectAvatar: 'Avatar auswählen',
chooseCustomAvatar: 'Oder wählen Sie einen eigenen Avatar',
},
+ openAppFailureModal: {
+ title: 'Etwas ist schiefgelaufen...',
+ subtitle: `Wir konnten nicht alle Ihre Daten laden. Wir wurden benachrichtigt und untersuchen das Problem. Wenn das weiterhin besteht, wenden Sie sich bitte an`,
+ refreshAndTryAgain: 'Aktualisieren und erneut versuchen',
+ },
};
// IMPORTANT: This line is manually replaced in generate translation files by scripts/generateTranslations.ts,
// so if you change it here, please update it there as well.
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 6eaea0c93fc4..89ddcd3e0d78 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -226,6 +226,7 @@ import type {
TermsParams,
ThreadRequestReportNameParams,
ThreadSentMoneyReportNameParams,
+ ToggleImportTitleParams,
TotalAmountGreaterOrLessThanOriginalParams,
ToValidateLoginParams,
TransferParams,
@@ -667,6 +668,7 @@ const translations = {
pinned: 'Pinned',
read: 'Read',
copyToClipboard: 'Copy to clipboard',
+ domains: 'Domains',
},
supportalNoAccess: {
title: 'Not so fast',
@@ -896,17 +898,17 @@ const translations = {
beginningOfChatHistoryUserRoom: ({reportName, reportDetailsLink}: BeginningOfChatHistoryUserRoomParams) =>
`This chat room is for anything ${reportName} related.`,
beginningOfChatHistoryInvoiceRoom: ({invoicePayer, invoiceReceiver}: BeginningOfChatHistoryInvoiceRoomParams) =>
- `This chat is for invoices between ${invoicePayer} and ${invoiceReceiver}. Use the + button to send an invoice.`,
+ `This chat is for invoices between ${invoicePayer} and ${invoiceReceiver}. Use the ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE} button to send an invoice.`,
beginningOfChatHistory: 'This chat is with ',
beginningOfChatHistoryPolicyExpenseChat: ({workspaceName, submitterDisplayName}: BeginningOfChatHistoryPolicyExpenseChatParams) =>
- `This is where ${submitterDisplayName} will submit expenses to ${workspaceName}. Just use the + button.`,
+ `This is where ${submitterDisplayName} will submit expenses to ${workspaceName}. Just use the ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE} button.`,
beginningOfChatHistorySelfDM: 'This is your personal space. Use it for notes, tasks, drafts, and reminders.',
beginningOfChatHistorySystemDM: "Welcome! Let's get you set up.",
chatWithAccountManager: 'Chat with your account manager here',
sayHello: 'Say hello!',
yourSpace: 'Your space',
welcomeToRoom: ({roomName}: WelcomeToRoomParams) => `Welcome to ${roomName}!`,
- usePlusButton: ({additionalText}: UsePlusButtonParams) => ` Use the + button to ${additionalText} an expense.`,
+ usePlusButton: ({additionalText}: UsePlusButtonParams) => ` Use the ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE} button to ${additionalText} an expense.`,
askConcierge: ' Ask questions and get 24/7 realtime support.',
conciergeSupport: '24/7 support',
create: 'create',
@@ -1680,7 +1682,6 @@ const translations = {
testCrash: 'Test crash',
resetToOriginalState: 'Reset to original state',
usingImportedState: 'You are using imported state. Press here to clear it.',
- shouldBlockTransactionThreadReportCreation: 'Block transaction thread report creation',
debugMode: 'Debug mode',
invalidFile: 'Invalid file',
invalidFileDescription: 'The file you are trying to import is not valid. Please try again.',
@@ -2406,7 +2407,7 @@ const translations = {
description:
'*Submit an expense* by entering an amount or scanning a receipt.\n' +
'\n' +
- `1. Click the + button.\n` +
+ `1. Click the ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE} button.\n` +
'2. Choose *Create expense*.\n' +
'3. Enter an amount or scan a receipt.\n' +
`4. Add your boss's email or phone number.\n` +
@@ -2419,7 +2420,7 @@ const translations = {
description:
'*Submit an expense* by entering an amount or scanning a receipt.\n' +
'\n' +
- `1. Click the + button.\n` +
+ `1. Click the ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE} button.\n` +
'2. Choose *Create expense*.\n' +
'3. Enter an amount or scan a receipt.\n' +
'4. Confirm details.\n' +
@@ -2432,7 +2433,7 @@ const translations = {
description:
'*Track an expense* in any currency, whether you have a receipt or not.\n' +
'\n' +
- `1. Click the + button.\n` +
+ `1. Click the ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE} button.\n` +
'2. Choose *Create expense*.\n' +
'3. Enter an amount or scan a receipt.\n' +
'4. Choose your *personal* space.\n' +
@@ -2531,7 +2532,7 @@ const translations = {
description:
'*Start a chat* with anyone using their email or phone number.\n' +
'\n' +
- `1. Click the + button.\n` +
+ `1. Click the ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE} button.\n` +
'2. Choose *Start chat*.\n' +
'3. Enter an email or phone number.\n' +
'\n' +
@@ -2545,7 +2546,7 @@ const translations = {
description:
'*Split expenses* with one or more people.\n' +
'\n' +
- `1. Click the + button.\n` +
+ `1. Click the ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE} button.\n` +
'2. Choose *Start chat*.\n' +
'3. Enter emails or phone numbers.\n' +
'4. Click the grey *+* button in the chat > *Split expense*.\n' +
@@ -2568,7 +2569,7 @@ const translations = {
description:
'Here’s how to create a report:\n' +
'\n' +
- `1. Click the + button.\n` +
+ `1. Click the ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE} button.\n` +
'2. Choose *Create report*.\n' +
'3. Click *Add expense*.\n' +
'4. Add your first expense.\n' +
@@ -2668,6 +2669,11 @@ const translations = {
ensureYourEmailClient: `Ensure your email client allows expensify.com emails. You can find directions on how to complete this step here but you may need your IT department to help configure your email settings.`,
onceTheAbove: `Once the above steps are completed, please reach out to ${CONST.EMAIL.CONCIERGE} to unblock your login.`,
},
+ openAppFailureModal: {
+ title: 'Something went wrong...',
+ subtitle: `We have not been able to load all of your data. We have been notified and are looking into the problem. If this persists, please reach out to`,
+ refreshAndTryAgain: 'Refresh and try again',
+ },
smsDeliveryFailurePage: {
smsDeliveryFailureMessage: ({login}: OurEmailProviderParams) =>
`We've been unable to deliver SMS messages to ${login}, so we've suspended it temporarily. Please try validating your number:`,
@@ -2918,8 +2924,8 @@ const translations = {
needSSNFull9: "We're having trouble verifying your SSN. Please enter the full nine digits of your SSN.",
weCouldNotVerify: "We couldn't verify",
pleaseFixIt: 'Please fix this information before continuing',
- failedKYCTextBefore: "We weren't able to verify your identity. Please try again later or reach out to ",
- failedKYCTextAfter: ' if you have any questions.',
+ failedKYCMessage: ({conciergeEmail}: {conciergeEmail: string}) =>
+ `We weren't able to verify your identity. Please try again later or reach out to ${conciergeEmail} if you have any questions.`,
},
termsStep: {
headerTitle: 'Terms and fees',
@@ -4383,8 +4389,7 @@ const translations = {
employeeDefaultDescription: "The employee's default department will be applied to their expenses in Sage Intacct if one exists.",
displayedAsTagDescription: "Department will be selectable for each individual expense on an employee's report.",
displayedAsReportFieldDescription: "Department selection will apply to all expenses on an employee's report.",
- toggleImportTitleFirstPart: 'Choose how to handle Sage Intacct ',
- toggleImportTitleSecondPart: ' in Expensify.',
+ toggleImportTitle: ({mappingTitle}: ToggleImportTitleParams) => `Choose how to handle Sage Intacct ${mappingTitle} in Expensify.`,
expenseTypes: 'Expense types',
expenseTypesDescription: 'Your Sage Intacct expense types will import into Expensify as categories.',
accountTypesDescription: 'Your Sage Intacct chart of accounts will import into Expensify as categories.',
@@ -5690,7 +5695,7 @@ const translations = {
chatWithYourAdmin: 'Chat with your admin',
chatInAdmins: 'Chat in #admins',
addPaymentCard: 'Add payment card',
- goToSubscriptions: 'Go to Subscriptions',
+ goToSubscription: 'Go to Subscription',
},
rules: {
individualExpenseRules: {
@@ -6111,7 +6116,7 @@ const translations = {
searchResults: {
emptyResults: {
title: 'Nothing to show',
- subtitle: `Try adjusting your search criteria or creating something with the + button.`,
+ subtitle: `Try adjusting your search criteria or creating something with the green ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE} button.`,
},
emptyExpenseResults: {
title: "You haven't created any expenses yet",
@@ -6723,7 +6728,8 @@ const translations = {
return '';
},
brokenConnection530Error: 'Receipt pending due to broken bank connection',
- adminBrokenConnectionError: 'Receipt pending due to broken bank connection. Please resolve in ',
+ adminBrokenConnectionError: ({workspaceCompanyCardRoute}: {workspaceCompanyCardRoute: string}) =>
+ `Receipt pending due to broken bank connection. Please resolve in Company cards.`,
memberBrokenConnectionError: 'Receipt pending due to broken bank connection. Please ask a workspace admin to resolve.',
markAsCashToIgnore: 'Mark as cash to ignore and request payment.',
smartscanFailed: ({canEdit = true}) => `Receipt scanning failed.${canEdit ? ' Enter details manually.' : ''}`,
diff --git a/src/languages/es.ts b/src/languages/es.ts
index cea091df354f..dfed9336e906 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -224,6 +224,7 @@ import type {
TermsParams,
ThreadRequestReportNameParams,
ThreadSentMoneyReportNameParams,
+ ToggleImportTitleParams,
TotalAmountGreaterOrLessThanOriginalParams,
ToValidateLoginParams,
TransferParams,
@@ -653,6 +654,7 @@ const translations = {
pinned: 'Fijado',
read: 'Leído',
copyToClipboard: 'Copiar al portapapeles',
+ domains: 'Dominios',
},
supportalNoAccess: {
title: 'No tan rápido',
@@ -883,17 +885,17 @@ const translations = {
beginningOfChatHistoryUserRoom: ({reportName, reportDetailsLink}: BeginningOfChatHistoryUserRoomParams) =>
`Esta sala de chat es para cualquier cosa relacionada con ${reportName}.`,
beginningOfChatHistoryInvoiceRoom: ({invoicePayer, invoiceReceiver}: BeginningOfChatHistoryInvoiceRoomParams) =>
- `Este chat es para facturas entre ${invoicePayer} y ${invoiceReceiver}. Usa el botón + para enviar una factura.`,
+ `Este chat es para facturas entre ${invoicePayer} y ${invoiceReceiver}. Usa el botón ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE} para enviar una factura.`,
beginningOfChatHistory: 'Este chat es con ',
beginningOfChatHistoryPolicyExpenseChat: ({workspaceName, submitterDisplayName}: BeginningOfChatHistoryPolicyExpenseChatParams) =>
- `Aquí es donde ${submitterDisplayName} enviará los gastos al espacio de trabajo ${workspaceName}. Solo usa el botón +.`,
+ `Aquí es donde ${submitterDisplayName} enviará los gastos al espacio de trabajo ${workspaceName}. Solo usa el botón ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}.`,
beginningOfChatHistorySelfDM: 'Este es tu espacio personal. Úsalo para notas, tareas, borradores y recordatorios.',
beginningOfChatHistorySystemDM: '¡Bienvenido! Vamos a configurar tu cuenta.',
chatWithAccountManager: 'Chatea con tu gestor de cuenta aquí',
sayHello: '¡Saluda!',
yourSpace: 'Tu espacio',
welcomeToRoom: ({roomName}: WelcomeToRoomParams) => `¡Bienvenido a ${roomName}!`,
- usePlusButton: ({additionalText}: UsePlusButtonParams) => ` Usa el botón + para ${additionalText} un gasto`,
+ usePlusButton: ({additionalText}: UsePlusButtonParams) => ` Usa el botón ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE} para ${additionalText} un gasto`,
askConcierge: ' Haz preguntas y obtén soporte en tiempo real las 24/7.',
conciergeSupport: 'Soporte 24/7',
create: 'crear',
@@ -1413,7 +1415,6 @@ const translations = {
dates: 'Fechas',
rates: 'Tasas',
submitsTo: ({name}: SubmitsToParams) => `Se envía a ${name}`,
-
reject: {
educationalTitle: '¿Debes retener o rechazar?',
educationalText: 'Si no estás listo para aprobar o pagar un gasto, puedes retenerlo o rechazarlo.',
@@ -1671,7 +1672,6 @@ const translations = {
testCrash: 'Prueba de fallo',
resetToOriginalState: 'Restablecer al estado original',
usingImportedState: 'Estás utilizando el estado importado. Pulsa aquí para borrarlo.',
- shouldBlockTransactionThreadReportCreation: 'Bloquear la creación de informes de hilos de transacciones',
debugMode: 'Modo depuración',
invalidFile: 'Archivo inválido',
invalidFileDescription: 'El archivo que ests intentando importar no es válido. Por favor, inténtalo de nuevo.',
@@ -2407,7 +2407,7 @@ ${amount} para ${merchant} - ${date}`,
title: 'Envía un gasto',
description:
'*Envía un gasto* introduciendo una cantidad o escaneando un recibo.\n\n' +
- `1. Haz clic en el botón +.\n` +
+ `1. Haz clic en el botón ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}.\n` +
'2. Elige *Crear gasto*.\n' +
'3. Introduce una cantidad o escanea un recibo.\n' +
'4. Añade el correo o teléfono de tu jefe.\n' +
@@ -2418,7 +2418,7 @@ ${amount} para ${merchant} - ${date}`,
title: 'Envía un gasto',
description:
'*Envía un gasto* introduciendo una cantidad o escaneando un recibo.\n\n' +
- `1. Haz clic en el botón +.\n` +
+ `1. Haz clic en el botón ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}.\n` +
'2. Elige *Crear gasto*.\n' +
'3. Introduce una cantidad o escanea un recibo.\n' +
'4. Confirma los detalles.\n' +
@@ -2429,7 +2429,7 @@ ${amount} para ${merchant} - ${date}`,
title: 'Organiza un gasto',
description:
'*Organiza un gasto* en cualquier moneda, tengas recibo o no.\n\n' +
- `1. Haz clic en el botón +.\n` +
+ `1. Haz clic en el botón ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}.\n` +
'2. Elige *Crear gasto*.\n' +
'3. Introduce una cantidad o escanea un recibo.\n' +
'4. Elige tu espacio *personal*.\n' +
@@ -2513,7 +2513,7 @@ ${amount} para ${merchant} - ${date}`,
title: 'Inicia un chat',
description:
'*Inicia un chat* con cualquier persona usando su correo o número.\n\n' +
- `1. Haz clic en el botón +.\n` +
+ `1. Haz clic en el botón ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}.\n` +
'2. Elige *Iniciar chat*.\n' +
'3. Introduce un correo o teléfono.\n\n' +
'Si aún no usan Expensify, se les invitará automáticamente.\n\n' +
@@ -2523,7 +2523,7 @@ ${amount} para ${merchant} - ${date}`,
title: 'Divide un gasto',
description:
'*Divide gastos* con una o más personas.\n\n' +
- `1. Haz clic en el botón +.\n` +
+ `1. Haz clic en el botón ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}.\n` +
'2. Elige *Iniciar chat*.\n' +
'3. Introduce correos o teléfonos.\n' +
'4. Haz clic en el botón gris *+* en el chat > *Dividir gasto*.\n' +
@@ -2544,7 +2544,7 @@ ${amount} para ${merchant} - ${date}`,
description:
'Así es como puedes crear un informe:\n' +
'\n' +
- `1. Haz clic en el botón +.\n` +
+ `1. Haz clic en el botón ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}.\n` +
'2. Elige *Crear informe*.\n' +
'3. Haz clic en *Añadir gasto*.\n' +
'4. Añade tu primer gasto.\n' +
@@ -2912,8 +2912,8 @@ ${amount} para ${merchant} - ${date}`,
needSSNFull9: 'Estamos teniendo problemas para verificar tu número de seguridad social. Introduce los 9 dígitos del número de seguridad social.',
weCouldNotVerify: 'No se pudo verificar',
pleaseFixIt: 'Corrige esta información antes de continuar.',
- failedKYCTextBefore: 'No se ha podido verificar correctamente tu identidad. Vuelve a intentarlo más tarde o comunicate con ',
- failedKYCTextAfter: ' si tienes alguna pregunta.',
+ failedKYCMessage: ({conciergeEmail}: {conciergeEmail: string}) =>
+ `No se ha podido verificar correctamente tu identidad. Vuelve a intentarlo más tarde o comunícate con ${conciergeEmail} si tienes alguna pregunta.`,
},
termsStep: {
headerTitle: 'Condiciones y tarifas',
@@ -4396,8 +4396,7 @@ ${amount} para ${merchant} - ${date}`,
employeeDefaultDescription: 'El departamento por defecto del empleado se aplicará a sus gastos en Sage Intacct si existe.',
displayedAsTagDescription: 'Se podrá seleccionar el departamento para cada gasto individual en el informe de un empleado.',
displayedAsReportFieldDescription: 'La selección de departamento se aplicará a todos los gastos que figuren en el informe de un empleado.',
- toggleImportTitleFirstPart: 'Elija cómo gestionar Sage Intacct ',
- toggleImportTitleSecondPart: ' en Expensify.',
+ toggleImportTitle: ({mappingTitle}: ToggleImportTitleParams) => `Elija cómo gestionar Sage Intacct ${mappingTitle} en Expensify.`,
expenseTypes: 'Tipos de gastos',
expenseTypesDescription: 'Los tipos de gastos de Sage Intacct se importan a Expensify como categorías.',
accountTypesDescription: 'Su plan de cuentas de Sage Intacct se importará a Expensify como categorías.',
@@ -5733,7 +5732,7 @@ ${amount} para ${merchant} - ${date}`,
chatWithYourAdmin: 'Chatea con tu administrador',
chatInAdmins: 'Chatea en #admins',
addPaymentCard: 'Agregar tarjeta de pago',
- goToSubscriptions: 'Ir a Suscripciones',
+ goToSubscription: 'Ir a Suscripción',
},
rules: {
individualExpenseRules: {
@@ -6140,7 +6139,7 @@ ${amount} para ${merchant} - ${date}`,
searchResults: {
emptyResults: {
title: 'No hay nada que ver aquí',
- subtitle: `Intenta ajustar tus criterios de búsqueda o crear algo con el botón +.`,
+ subtitle: `Intenta ajustar tus criterios de búsqueda o crear algo con el botón verde ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}.`,
},
emptyExpenseResults: {
title: 'Aún no has creado ningún gasto',
@@ -7221,7 +7220,8 @@ ${amount} para ${merchant} - ${date}`,
return '';
},
brokenConnection530Error: 'Recibo pendiente debido a una conexión bancaria rota',
- adminBrokenConnectionError: 'Recibo pendiente debido a una conexión bancaria rota. Por favor, resuélvelo en ',
+ adminBrokenConnectionError: ({workspaceCompanyCardRoute}: {workspaceCompanyCardRoute: string}) =>
+ `Recibo pendiente debido a una conexión bancaria rota. Por favor, resuélvelo en Tarjetas de empresa.`,
memberBrokenConnectionError: 'Recibo pendiente debido a una conexión bancaria rota. Por favor, pide a un administrador del espacio de trabajo que lo resuelva.',
markAsCashToIgnore: 'Márcalo como efectivo para ignorar y solicitar el pago.',
smartscanFailed: ({canEdit = true}) => `No se pudo escanear el recibo.${canEdit ? ' Introduce los datos manualmente.' : ''}`,
@@ -7770,6 +7770,11 @@ ${amount} para ${merchant} - ${date}`,
exportInProgress: 'Exportación en curso',
conciergeWillSend: 'Concierge te enviará el archivo en breve.',
},
+ openAppFailureModal: {
+ title: 'Algo salió mal...',
+ subtitle: `No hemos podido cargar todos sus datos. Hemos sido notificados y estamos investigando el problema. Si esto persiste, por favor comuníquese con`,
+ refreshAndTryAgain: 'Actualizar e intentar de nuevo',
+ },
};
export default translations satisfies TranslationDeepObject;
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index 1c1910eced4f..054dac238d58 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -237,6 +237,7 @@ import type {
TermsParams,
ThreadRequestReportNameParams,
ThreadSentMoneyReportNameParams,
+ ToggleImportTitleParams,
TotalAmountGreaterOrLessThanOriginalParams,
ToValidateLoginParams,
TransferParams,
@@ -672,6 +673,7 @@ const translations = {
pinned: 'Épinglé',
read: 'Lu',
copyToClipboard: 'Copier dans le presse-papiers',
+ domains: 'Domaines',
},
supportalNoAccess: {
title: 'Pas si vite',
@@ -908,17 +910,17 @@ const translations = {
beginningOfChatHistoryUserRoom: ({reportName, reportDetailsLink}: BeginningOfChatHistoryUserRoomParams) =>
`Ce salon de discussion est destiné à tout ce qui concerne ${reportName}.`,
beginningOfChatHistoryInvoiceRoom: ({invoicePayer, invoiceReceiver}: BeginningOfChatHistoryInvoiceRoomParams) =>
- `Ce chat concerne les factures entre ${invoicePayer} et ${invoiceReceiver}. Utilisez le bouton + pour envoyer une facture.`,
+ `Ce chat concerne les factures entre ${invoicePayer} et ${invoiceReceiver}. Utilisez le bouton ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE} pour envoyer une facture.`,
beginningOfChatHistory: 'Ce chat est avec',
beginningOfChatHistoryPolicyExpenseChat: ({workspaceName, submitterDisplayName}: BeginningOfChatHistoryPolicyExpenseChatParams) =>
- `C'est ici que ${submitterDisplayName} soumettra ses dépenses à ${workspaceName}. Il suffit d'utiliser le bouton +.`,
+ `C'est ici que ${submitterDisplayName} soumettra ses dépenses à ${workspaceName}. Il suffit d'utiliser le bouton ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}.`,
beginningOfChatHistorySelfDM: "C'est votre espace personnel. Utilisez-le pour des notes, des tâches, des brouillons et des rappels.",
beginningOfChatHistorySystemDM: 'Bienvenue ! Commençons votre configuration.',
chatWithAccountManager: 'Discutez avec votre gestionnaire de compte ici',
sayHello: 'Dites bonjour !',
yourSpace: 'Votre espace',
welcomeToRoom: ({roomName}: WelcomeToRoomParams) => `Bienvenue dans ${roomName} !`,
- usePlusButton: ({additionalText}: UsePlusButtonParams) => ` Utilisez le bouton + pour ${additionalText} une dépense.`,
+ usePlusButton: ({additionalText}: UsePlusButtonParams) => ` Utilisez le bouton ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE} pour ${additionalText} une dépense.`,
askConcierge: 'Posez des questions et obtenez une assistance en temps réel 24h/24 et 7j/7.',
conciergeSupport: 'Support 24h/24 et 7j/7',
create: 'créer',
@@ -1695,7 +1697,6 @@ const translations = {
testCrash: 'Test crash',
resetToOriginalState: "Réinitialiser à l'état d'origine",
usingImportedState: 'Vous utilisez un état importé. Appuyez ici pour le réinitialiser.',
- shouldBlockTransactionThreadReportCreation: 'Bloquer la création de rapports de fil de transaction',
debugMode: 'Mode débogage',
invalidFile: 'Fichier invalide',
invalidFileDescription: "Le fichier que vous essayez d'importer n'est pas valide. Veuillez réessayer.",
@@ -2441,7 +2442,7 @@ ${amount} pour ${merchant} - ${date}`,
title: 'Soumettre une dépense',
description:
'*Soumettez une dépense* en saisissant un montant ou en scannant un reçu.\n\n' +
- `1. Cliquez sur le bouton +.\n` +
+ `1. Cliquez sur le bouton ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}.\n` +
'2. Choisissez *Créer une dépense*.\n' +
'3. Saisissez un montant ou scannez un reçu.\n' +
`4. Ajoutez l’email ou numéro de téléphone de votre responsable.\n` +
@@ -2452,7 +2453,7 @@ ${amount} pour ${merchant} - ${date}`,
title: 'Soumettre une dépense',
description:
'*Soumettez une dépense* en saisissant un montant ou en scannant un reçu.\n\n' +
- `1. Cliquez sur le bouton +.\n` +
+ `1. Cliquez sur le bouton ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}.\n` +
'2. Choisissez *Créer une dépense*.\n' +
'3. Saisissez un montant ou scannez un reçu.\n' +
'4. Confirmez les détails.\n' +
@@ -2463,7 +2464,7 @@ ${amount} pour ${merchant} - ${date}`,
title: 'Suivre une dépense',
description:
'*Suivez une dépense* dans n’importe quelle devise, avec ou sans reçu.\n\n' +
- `1. Cliquez sur le bouton +.\n` +
+ `1. Cliquez sur le bouton ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}.\n` +
'2. Choisissez *Créer une dépense*.\n' +
'3. Saisissez un montant ou scannez un reçu.\n' +
'4. Choisissez votre espace *personnel*.\n' +
@@ -2546,7 +2547,7 @@ ${amount} pour ${merchant} - ${date}`,
title: 'Démarrer un chat',
description:
'*Démarrez un chat* avec quelqu’un grâce à son email ou numéro.\n\n' +
- `1. Cliquez sur le bouton +.\n` +
+ `1. Cliquez sur le bouton ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}.\n` +
'2. Choisissez *Démarrer un chat*.\n' +
'3. Entrez un email ou numéro de téléphone.\n\n' +
'S’ils ne sont pas encore sur Expensify, une invitation sera envoyée automatiquement.\n\n' +
@@ -2556,7 +2557,7 @@ ${amount} pour ${merchant} - ${date}`,
title: 'Partager une dépense',
description:
'*Partagez une dépense* avec une ou plusieurs personnes.\n\n' +
- `1. Cliquez sur le bouton +.\n` +
+ `1. Cliquez sur le bouton ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}.\n` +
'2. Choisissez *Démarrer un chat*.\n' +
'3. Entrez des emails ou numéros de téléphone.\n' +
'4. Cliquez sur le bouton gris *+* > *Partager une dépense*.\n' +
@@ -2576,7 +2577,7 @@ ${amount} pour ${merchant} - ${date}`,
title: 'Créer votre premier rapport',
description:
'Voici comment créer un rapport :\n\n' +
- `1. Cliquez sur le bouton +.\n` +
+ `1. Cliquez sur le bouton ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}.\n` +
'2. Choisissez *Créer un rapport*.\n' +
'3. Cliquez sur *Ajouter une dépense*.\n' +
'4. Ajoutez votre première dépense.\n\n' +
@@ -2933,8 +2934,8 @@ ${amount} pour ${merchant} - ${date}`,
needSSNFull9: 'Nous rencontrons des difficultés pour vérifier votre SSN. Veuillez entrer les neuf chiffres complets de votre SSN.',
weCouldNotVerify: "Nous n'avons pas pu vérifier",
pleaseFixIt: 'Veuillez corriger ces informations avant de continuer.',
- failedKYCTextBefore: "Nous n'avons pas pu vérifier votre identité. Veuillez réessayer plus tard ou contacter",
- failedKYCTextAfter: 'si vous avez des questions.',
+ failedKYCMessage: ({conciergeEmail}: {conciergeEmail: string}) =>
+ `Nous n'avons pas pu vérifier votre identité. Veuillez réessayer plus tard ou contacter ${conciergeEmail} si vous avez des questions.`,
},
termsStep: {
headerTitle: 'Conditions et frais',
@@ -4415,8 +4416,7 @@ ${amount} pour ${merchant} - ${date}`,
employeeDefaultDescription: "Le département par défaut de l'employé sera appliqué à ses dépenses dans Sage Intacct si un existe.",
displayedAsTagDescription: "Le département sera sélectionnable pour chaque dépense individuelle sur le rapport d'un employé.",
displayedAsReportFieldDescription: "La sélection du département s'appliquera à toutes les dépenses sur le rapport d'un employé.",
- toggleImportTitleFirstPart: 'Choisissez comment gérer Sage Intacct',
- toggleImportTitleSecondPart: 'dans Expensify.',
+ toggleImportTitle: ({mappingTitle}: ToggleImportTitleParams) => `Choisissez comment gérer Sage Intacct ${mappingTitle} dans Expensify.`,
expenseTypes: 'Types de dépenses',
expenseTypesDescription: 'Vos types de dépenses Sage Intacct seront importés dans Expensify en tant que catégories.',
accountTypesDescription: 'Votre plan comptable Sage Intacct sera importé dans Expensify en tant que catégories.',
@@ -5738,7 +5738,7 @@ ${amount} pour ${merchant} - ${date}`,
chatWithYourAdmin: 'Discutez avec votre administrateur',
chatInAdmins: 'Discuter dans #admins',
addPaymentCard: 'Ajouter une carte de paiement',
- goToSubscriptions: 'Aller aux abonnements',
+ goToSubscription: "Accéder à l'abonnement",
},
rules: {
individualExpenseRules: {
@@ -6160,7 +6160,7 @@ ${amount} pour ${merchant} - ${date}`,
searchResults: {
emptyResults: {
title: 'Rien à afficher',
- subtitle: `Essayez d'ajuster vos critères de recherche ou de créer quelque chose avec le bouton +.`,
+ subtitle: `Essayez d'ajuster vos critères de recherche ou de créer quelque chose avec le bouton vert ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}.`,
},
emptyExpenseResults: {
title: "Vous n'avez pas encore créé de dépenses.",
@@ -6777,7 +6777,8 @@ ${amount} pour ${merchant} - ${date}`,
return '';
},
brokenConnection530Error: "Reçu en attente en raison d'une connexion bancaire interrompue",
- adminBrokenConnectionError: "Reçu en attente en raison d'une connexion bancaire défaillante. Veuillez résoudre dans",
+ adminBrokenConnectionError: ({workspaceCompanyCardRoute}: {workspaceCompanyCardRoute: string}) =>
+ `Reçu en attente en raison d'une connexion bancaire rompue. Veuillez résoudre ce problème dans Cartes d'entreprise.`,
memberBrokenConnectionError: "Reçu en attente en raison d'une connexion bancaire défectueuse. Veuillez demander à un administrateur de l'espace de travail de résoudre le problème.",
markAsCashToIgnore: 'Marquer comme espèce pour ignorer et demander un paiement.',
smartscanFailed: ({canEdit = true}) => `Échec de la numérisation du reçu.${canEdit ? 'Saisir les détails manuellement.' : ''}`,
@@ -7335,6 +7336,11 @@ ${amount} pour ${merchant} - ${date}`,
selectAvatar: 'Sélectionner un avatar',
chooseCustomAvatar: 'Ou choisissez un avatar personnalisé',
},
+ openAppFailureModal: {
+ title: "Quelque chose s'est mal passé...",
+ subtitle: `Nous n'avons pas pu charger toutes vos données. Nous avons été informés et examinons le problème. Si cela persiste, veuillez contacter`,
+ refreshAndTryAgain: 'Actualisez puis réessayez',
+ },
};
// IMPORTANT: This line is manually replaced in generate translation files by scripts/generateTranslations.ts,
// so if you change it here, please update it there as well.
diff --git a/src/languages/it.ts b/src/languages/it.ts
index 1009ddd1e5b4..0600ac7157b9 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -237,6 +237,7 @@ import type {
TermsParams,
ThreadRequestReportNameParams,
ThreadSentMoneyReportNameParams,
+ ToggleImportTitleParams,
TotalAmountGreaterOrLessThanOriginalParams,
ToValidateLoginParams,
TransferParams,
@@ -672,6 +673,7 @@ const translations = {
pinned: 'Fissato',
read: 'Letto',
copyToClipboard: 'Copia negli appunti',
+ domains: 'Domini',
},
supportalNoAccess: {
title: 'Non così in fretta',
@@ -905,17 +907,17 @@ const translations = {
beginningOfChatHistoryUserRoom: ({reportName, reportDetailsLink}: BeginningOfChatHistoryUserRoomParams) =>
`Questa chat è per tutto ciò che riguarda ${reportName}.`,
beginningOfChatHistoryInvoiceRoom: ({invoicePayer, invoiceReceiver}: BeginningOfChatHistoryInvoiceRoomParams) =>
- `Questa chat è per le fatture tra ${invoicePayer} e ${invoiceReceiver}. Utilizzare il pulsante + per inviare una fattura.`,
+ `Questa chat è per le fatture tra ${invoicePayer} e ${invoiceReceiver}. Utilizzare il pulsante ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE} per inviare una fattura.`,
beginningOfChatHistory: 'Questa chat è con',
beginningOfChatHistoryPolicyExpenseChat: ({workspaceName, submitterDisplayName}: BeginningOfChatHistoryPolicyExpenseChatParams) =>
- `È qui che ${submitterDisplayName} presenterà le spese a ${workspaceName}. Basta usare il pulsante +.`,
+ `È qui che ${submitterDisplayName} presenterà le spese a ${workspaceName}. Basta usare il pulsante ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}.`,
beginningOfChatHistorySelfDM: 'Questo è il tuo spazio personale. Usalo per appunti, compiti, bozze e promemoria.',
beginningOfChatHistorySystemDM: 'Benvenuto! Iniziamo con la configurazione.',
chatWithAccountManager: 'Chatta con il tuo account manager qui',
sayHello: 'Ciao!',
yourSpace: 'Il tuo spazio',
welcomeToRoom: ({roomName}: WelcomeToRoomParams) => `Benvenuto in ${roomName}!`,
- usePlusButton: ({additionalText}: UsePlusButtonParams) => ` Usa il pulsante + per ${additionalText} una spesa.`,
+ usePlusButton: ({additionalText}: UsePlusButtonParams) => ` Usa il pulsante ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE} per ${additionalText} una spesa.`,
askConcierge: 'Fai domande e ricevi supporto in tempo reale 24/7.',
conciergeSupport: 'Supporto 24/7',
create: 'creare',
@@ -1688,7 +1690,6 @@ const translations = {
testCrash: 'Test crash',
resetToOriginalState: 'Ripristina allo stato originale',
usingImportedState: 'Stai utilizzando uno stato importato. Premi qui per cancellarlo.',
- shouldBlockTransactionThreadReportCreation: 'Blocca la creazione di report del thread di transazione',
debugMode: 'Modalità debug',
invalidFile: 'File non valido',
invalidFileDescription: 'Il file che stai cercando di importare non è valido. Per favore riprova.',
@@ -2434,7 +2435,7 @@ ${amount} per ${merchant} - ${date}`,
description:
'*Invia una spesa* inserendo un importo o scansionando una ricevuta.\n' +
'\n' +
- `1. Clicca sul pulsante +.\n` +
+ `1. Clicca sul pulsante ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}.\n` +
'2. Scegli *Crea spesa*.\n' +
'3. Inserisci un importo o scansiona una ricevuta.\n' +
`4. Aggiungi l’email o il numero di telefono del tuo responsabile.\n` +
@@ -2447,7 +2448,7 @@ ${amount} per ${merchant} - ${date}`,
description:
'*Invia una spesa* inserendo un importo o scansionando una ricevuta.\n' +
'\n' +
- `1. Clicca sul pulsante +.\n` +
+ `1. Clicca sul pulsante ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}.\n` +
'2. Scegli *Crea spesa*.\n' +
'3. Inserisci un importo o scansiona una ricevuta.\n' +
'4. Conferma i dettagli.\n' +
@@ -2460,7 +2461,7 @@ ${amount} per ${merchant} - ${date}`,
description:
'*Monitora una spesa* in qualsiasi valuta, con o senza ricevuta.\n' +
'\n' +
- `1. Clicca sul pulsante +.\n` +
+ `1. Clicca sul pulsante ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}.\n` +
'2. Scegli *Crea spesa*.\n' +
'3. Inserisci un importo o scansiona una ricevuta.\n' +
'4. Scegli il tuo spazio *personale*.\n' +
@@ -2555,7 +2556,7 @@ ${amount} per ${merchant} - ${date}`,
description:
'*Avvia una chat* con chiunque utilizzando la loro email o numero di telefono.\n' +
'\n' +
- `1. Clicca sul pulsante +.\n` +
+ `1. Clicca sul pulsante ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}.\n` +
'2. Scegli *Avvia chat*.\n' +
'3. Inserisci un’email o numero di telefono.\n' +
'\n' +
@@ -2568,7 +2569,7 @@ ${amount} per ${merchant} - ${date}`,
description:
'*Dividi le spese* con una o più persone.\n' +
'\n' +
- `1. Clicca sul pulsante +.\n` +
+ `1. Clicca sul pulsante ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}.\n` +
'2. Scegli *Avvia chat*.\n' +
'3. Inserisci email o numeri di telefono.\n' +
'4. Clicca sul pulsante grigio *+* nella chat > *Dividi spesa*.\n' +
@@ -2590,7 +2591,7 @@ ${amount} per ${merchant} - ${date}`,
description:
'Ecco come creare un report:\n' +
'\n' +
- `1. Clicca sul pulsante +.\n` +
+ `1. Clicca sul pulsante ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}.\n` +
'2. Scegli *Crea report*.\n' +
'3. Clicca su *Aggiungi spesa*.\n' +
'4. Aggiungi la tua prima spesa.\n' +
@@ -2944,8 +2945,8 @@ ${amount} per ${merchant} - ${date}`,
needSSNFull9: 'Stiamo riscontrando problemi nel verificare il tuo SSN. Inserisci tutti i nove numeri del tuo SSN.',
weCouldNotVerify: 'Non siamo riusciti a verificare',
pleaseFixIt: 'Si prega di correggere queste informazioni prima di continuare.',
- failedKYCTextBefore: 'Non siamo riusciti a verificare la tua identità. Per favore riprova più tardi o contatta',
- failedKYCTextAfter: 'se hai domande.',
+ failedKYCMessage: ({conciergeEmail}: {conciergeEmail: string}) =>
+ `Non siamo riusciti a verificare la tua identità. Per favore riprova più tardi o contatta ${conciergeEmail} se hai domande.`,
},
termsStep: {
headerTitle: 'Termini e tariffe',
@@ -4423,8 +4424,7 @@ ${amount} per ${merchant} - ${date}`,
employeeDefaultDescription: 'Il dipartimento predefinito del dipendente verrà applicato alle sue spese in Sage Intacct, se esiste.',
displayedAsTagDescription: 'Il dipartimento sarà selezionabile per ogni singola spesa nel rapporto di un dipendente.',
displayedAsReportFieldDescription: 'La selezione del dipartimento verrà applicata a tutte le spese nel rapporto di un dipendente.',
- toggleImportTitleFirstPart: 'Scegli come gestire Sage Intacct',
- toggleImportTitleSecondPart: 'in Expensify.',
+ toggleImportTitle: ({mappingTitle}: ToggleImportTitleParams) => `Scegli come gestire Sage Intacct ${mappingTitle} in Expensify.`,
expenseTypes: 'Tipi di spesa',
expenseTypesDescription: 'I tuoi tipi di spesa Sage Intacct verranno importati in Expensify come categorie.',
accountTypesDescription: 'Il tuo piano dei conti di Sage Intacct verrà importato in Expensify come categorie.',
@@ -5741,7 +5741,7 @@ ${amount} per ${merchant} - ${date}`,
chatWithYourAdmin: 'Chatta con il tuo amministratore',
chatInAdmins: 'Chatta in #admins',
addPaymentCard: 'Aggiungi carta di pagamento',
- goToSubscriptions: 'Vai agli abbonamenti',
+ goToSubscription: "Vai all'abbonamento",
},
rules: {
individualExpenseRules: {
@@ -6166,7 +6166,7 @@ ${amount} per ${merchant} - ${date}`,
searchResults: {
emptyResults: {
title: 'Niente da mostrare',
- subtitle: `Prova a modificare i criteri di ricerca o a creare qualcosa con il pulsante +.`,
+ subtitle: `Prova a modificare i criteri di ricerca o a creare qualcosa con il pulsante verde ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}.`,
},
emptyExpenseResults: {
title: 'Non hai ancora creato nessuna spesa.',
@@ -6783,7 +6783,8 @@ ${amount} per ${merchant} - ${date}`,
return '';
},
brokenConnection530Error: 'Ricevuta in sospeso a causa di una connessione bancaria interrotta',
- adminBrokenConnectionError: 'Ricevuta in sospeso a causa di una connessione bancaria interrotta. Si prega di risolvere in',
+ adminBrokenConnectionError: ({workspaceCompanyCardRoute}: {workspaceCompanyCardRoute: string}) =>
+ `Ricevuta in sospeso a causa di una connessione bancaria interrotta. Risolvi il problema in Carte aziendali.`,
memberBrokenConnectionError: 'Ricevuta in sospeso a causa di una connessione bancaria interrotta. Si prega di chiedere a un amministratore dello spazio di lavoro di risolvere.',
markAsCashToIgnore: 'Segna come contante per ignorare e richiedere il pagamento.',
smartscanFailed: ({canEdit = true}) => `Scansione della ricevuta fallita.${canEdit ? 'Inserisci i dettagli manualmente.' : ''}`,
@@ -7340,6 +7341,11 @@ ${amount} per ${merchant} - ${date}`,
selectAvatar: 'Seleziona un avatar',
chooseCustomAvatar: 'Oppure scegli un avatar personalizzato',
},
+ openAppFailureModal: {
+ title: 'Qualcosa è andato storto...',
+ subtitle: `Non siamo riusciti a caricare tutti i tuoi dati. Siamo stati avvisati e stiamo esaminando il problema. Se il problema persiste, contatta`,
+ refreshAndTryAgain: 'Aggiorna e riprova',
+ },
};
// IMPORTANT: This line is manually replaced in generate translation files by scripts/generateTranslations.ts,
// so if you change it here, please update it there as well.
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index 6ea73797a6ae..6409bd809a3e 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -237,6 +237,7 @@ import type {
TermsParams,
ThreadRequestReportNameParams,
ThreadSentMoneyReportNameParams,
+ ToggleImportTitleParams,
TotalAmountGreaterOrLessThanOriginalParams,
ToValidateLoginParams,
TransferParams,
@@ -672,6 +673,7 @@ const translations = {
pinned: '固定済み',
read: '既読',
copyToClipboard: 'クリップボードにコピー',
+ domains: 'ドメイン',
},
supportalNoAccess: {
title: 'ちょっと待ってください',
@@ -906,17 +908,17 @@ const translations = {
beginningOfChatHistoryUserRoom: ({reportName, reportDetailsLink}: BeginningOfChatHistoryUserRoomParams) =>
`このチャットルームは、${reportName}に関することなら何でもどうぞ。`,
beginningOfChatHistoryInvoiceRoom: ({invoicePayer, invoiceReceiver}: BeginningOfChatHistoryInvoiceRoomParams) =>
- `このチャットは、${invoicePayer}と${invoiceReceiver}間の請求書用です。請求書を送信するには、+ ボタンを使用してください。`,
+ `このチャットは、${invoicePayer}と${invoiceReceiver}間の請求書用です。請求書を送信するには、${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE} ボタンを使用してください。`,
beginningOfChatHistory: 'このチャットは',
beginningOfChatHistoryPolicyExpenseChat: ({workspaceName, submitterDisplayName}: BeginningOfChatHistoryPolicyExpenseChatParams) =>
- `ここで${submitterDisplayName}が${workspaceName}に経費を提出します。+ボタンをクリックしてください。`,
+ `ここで${submitterDisplayName}が${workspaceName}に経費を提出します。${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}ボタンをクリックしてください。`,
beginningOfChatHistorySelfDM: 'これはあなたの個人スペースです。メモ、タスク、下書き、リマインダーに使用してください。',
beginningOfChatHistorySystemDM: 'ようこそ!セットアップを始めましょう。',
chatWithAccountManager: 'こちらでアカウントマネージャーとチャットしてください',
sayHello: 'こんにちは!',
yourSpace: 'あなたのスペース',
welcomeToRoom: ({roomName}: WelcomeToRoomParams) => `${roomName}へようこそ!`,
- usePlusButton: ({additionalText}: UsePlusButtonParams) => ` + ボタンを使用して経費を${additionalText}します。`,
+ usePlusButton: ({additionalText}: UsePlusButtonParams) => ` ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE} ボタンを使用して経費を${additionalText}します。`,
askConcierge: '質問をして、24時間365日リアルタイムサポートを受けましょう。',
conciergeSupport: '24時間年中無休サポート',
create: '作成する',
@@ -1689,7 +1691,6 @@ const translations = {
testCrash: 'クラッシュのテスト',
resetToOriginalState: '元の状態にリセット',
usingImportedState: 'インポートされた状態を使用しています。ここを押してクリアしてください。',
- shouldBlockTransactionThreadReportCreation: 'トランザクションスレッドレポートの作成をブロック',
debugMode: 'デバッグモード',
invalidFile: '無効なファイル',
invalidFileDescription: 'インポートしようとしているファイルは無効です。もう一度お試しください。',
@@ -2929,8 +2930,8 @@ ${date} - ${merchant}に${amount}`,
needSSNFull9: 'SSNの確認に問題が発生しています。SSNの9桁すべてを入力してください。',
weCouldNotVerify: '確認できませんでした',
pleaseFixIt: '続行する前にこの情報を修正してください。',
- failedKYCTextBefore: '本人確認ができませんでした。後でもう一度お試しいただくか、にお問い合わせください。',
- failedKYCTextAfter: 'ご質問がある場合。',
+ failedKYCMessage: ({conciergeEmail}: {conciergeEmail: string}) =>
+ `本人確認ができませんでした。後でもう一度お試しいただくか、${conciergeEmail} にお問い合わせください。ご質問がある場合。`,
},
termsStep: {
headerTitle: '利用規約と手数料',
@@ -4386,8 +4387,7 @@ ${date} - ${merchant}に${amount}`,
employeeDefaultDescription: '従業員のデフォルト部門は、Sage Intacct に存在する場合、その経費に適用されます。',
displayedAsTagDescription: '部門は、従業員のレポートの各経費ごとに選択可能になります。',
displayedAsReportFieldDescription: '部門の選択は、従業員のレポート上のすべての経費に適用されます。',
- toggleImportTitleFirstPart: 'Sage Intacctの処理方法を選択',
- toggleImportTitleSecondPart: 'in Expensify.',
+ toggleImportTitle: ({mappingTitle}: ToggleImportTitleParams) => `Sage Intacct ${mappingTitle}をExpensifyでどのように処理するかを選択します。`,
expenseTypes: '経費タイプ',
expenseTypesDescription: 'あなたのSage Intacctの経費タイプは、Expensifyにカテゴリーとしてインポートされます。',
accountTypesDescription: 'あなたのSage Intacct勘定科目表は、Expensifyにカテゴリとしてインポートされます。',
@@ -5687,7 +5687,7 @@ ${date} - ${merchant}に${amount}`,
chatWithYourAdmin: '管理者とチャットする',
chatInAdmins: '#adminsでチャットする',
addPaymentCard: '支払いカードを追加',
- goToSubscriptions: 'サブスクリプションに移動',
+ goToSubscription: 'サブスクリプションに移動',
},
rules: {
individualExpenseRules: {
@@ -6101,7 +6101,7 @@ ${date} - ${merchant}に${amount}`,
searchResults: {
emptyResults: {
title: '表示するものがありません',
- subtitle: `検索条件を調整するか、+ボタンで何かを作成してみてください。`,
+ subtitle: `検索条件を調整するか、緑色の${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}ボタンで何かを作成してみてください。`,
},
emptyExpenseResults: {
title: 'まだ経費が作成されていません。',
@@ -6715,7 +6715,8 @@ ${date} - ${merchant}に${amount}`,
return '';
},
brokenConnection530Error: '銀行接続の不具合により領収書が保留中です。',
- adminBrokenConnectionError: '銀行接続の不具合により領収書が保留されています。で解決してください。',
+ adminBrokenConnectionError: ({workspaceCompanyCardRoute}: {workspaceCompanyCardRoute: string}) =>
+ `銀行接続の問題により領収書が保留されています。会社のカードで解決してください。`,
memberBrokenConnectionError: '銀行接続が壊れているため、領収書が保留中です。ワークスペース管理者に解決を依頼してください。',
markAsCashToIgnore: '現金としてマークして無視し、支払いをリクエストします。',
smartscanFailed: ({canEdit = true}) => `領収書のスキャンに失敗しました。${canEdit ? '詳細を手動で入力してください。' : ''}`,
@@ -7266,6 +7267,11 @@ ${date} - ${merchant}に${amount}`,
selectAvatar: 'アバターを選択',
chooseCustomAvatar: 'またはカスタムアバターを選択',
},
+ openAppFailureModal: {
+ title: '問題が発生しました...',
+ subtitle: `すべてのデータを読み込むことができませんでした。通知を受けており、問題を調査しています。この状態が続く場合は、お問い合わせください。`,
+ refreshAndTryAgain: '再読み込みして、もう一度お試しください',
+ },
};
// IMPORTANT: This line is manually replaced in generate translation files by scripts/generateTranslations.ts,
// so if you change it here, please update it there as well.
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index 9937951bc6e0..d42dd74a254f 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -237,6 +237,7 @@ import type {
TermsParams,
ThreadRequestReportNameParams,
ThreadSentMoneyReportNameParams,
+ ToggleImportTitleParams,
TotalAmountGreaterOrLessThanOriginalParams,
ToValidateLoginParams,
TransferParams,
@@ -671,6 +672,7 @@ const translations = {
pinned: 'Vastgezet',
read: 'Gelezen',
copyToClipboard: 'Kopiëren naar klembord',
+ domains: 'Domeinen',
},
supportalNoAccess: {
title: 'Niet zo snel',
@@ -904,17 +906,17 @@ const translations = {
beginningOfChatHistoryUserRoom: ({reportName, reportDetailsLink}: BeginningOfChatHistoryUserRoomParams) =>
`Deze chatroom is voor alles wat met ${reportName} te maken heeft.`,
beginningOfChatHistoryInvoiceRoom: ({invoicePayer, invoiceReceiver}: BeginningOfChatHistoryInvoiceRoomParams) =>
- `Deze chat is voor facturen tussen ${invoicePayer} en ${invoiceReceiver}. Gebruik de + knop om een factuur te sturen.`,
+ `Deze chat is voor facturen tussen ${invoicePayer} en ${invoiceReceiver}. Gebruik de ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE} knop om een factuur te sturen.`,
beginningOfChatHistory: 'Deze chat is met',
beginningOfChatHistoryPolicyExpenseChat: ({workspaceName, submitterDisplayName}: BeginningOfChatHistoryPolicyExpenseChatParams) =>
- `Dit is waar ${submitterDisplayName} kosten zal indienen bij ${workspaceName}. Gebruik gewoon de + knop.`,
+ `Dit is waar ${submitterDisplayName} kosten zal indienen bij ${workspaceName}. Gebruik gewoon de ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE} knop.`,
beginningOfChatHistorySelfDM: 'Dit is je persoonlijke ruimte. Gebruik het voor notities, taken, concepten en herinneringen.',
beginningOfChatHistorySystemDM: 'Welkom! Laten we je instellen.',
chatWithAccountManager: 'Chat hier met uw accountmanager',
sayHello: 'Zeg hallo!',
yourSpace: 'Uw ruimte',
welcomeToRoom: ({roomName}: WelcomeToRoomParams) => `Welkom bij ${roomName}!`,
- usePlusButton: ({additionalText}: UsePlusButtonParams) => ` Gebruik de + knop om een uitgave te ${additionalText}.`,
+ usePlusButton: ({additionalText}: UsePlusButtonParams) => ` Gebruik de ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE} knop om een uitgave te ${additionalText}.`,
askConcierge: 'Stel vragen en krijg 24/7 realtime ondersteuning.',
conciergeSupport: '24/7 ondersteuning',
create: 'maken',
@@ -1690,7 +1692,6 @@ const translations = {
testCrash: 'Test crash',
resetToOriginalState: 'Reset naar oorspronkelijke staat',
usingImportedState: 'U gebruikt geïmporteerde status. Druk hier om het te wissen.',
- shouldBlockTransactionThreadReportCreation: 'Creatie van transactie thread rapporten blokkeren',
debugMode: 'Debug-modus',
invalidFile: 'Ongeldig bestand',
invalidFileDescription: 'Het bestand dat je probeert te importeren is niet geldig. Probeer het opnieuw.',
@@ -2435,7 +2436,7 @@ ${amount} voor ${merchant} - ${date}`,
description:
'*Dien een uitgave in* door een bedrag in te voeren of een bon te scannen.\n' +
'\n' +
- `1. Klik op de +-knop.\n` +
+ `1. Klik op de ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}-knop.\n` +
'2. Kies *Uitgave aanmaken*.\n' +
'3. Voer een bedrag in of scan een bon.\n' +
`4. Voeg het e-mailadres of telefoonnummer van uw baas toe.\n` +
@@ -2448,7 +2449,7 @@ ${amount} voor ${merchant} - ${date}`,
description:
'*Dien een uitgave in* door een bedrag in te voeren of een bon te scannen.\n' +
'\n' +
- `1. Klik op de +-knop.\n` +
+ `1. Klik op de ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}-knop.\n` +
'2. Kies *Uitgave aanmaken*.\n' +
'3. Voer een bedrag in of scan een bon.\n' +
'4. Bevestig de details.\n' +
@@ -2461,7 +2462,7 @@ ${amount} voor ${merchant} - ${date}`,
description:
'*Volg een uitgave* in elke valuta, of u nu een bon heeft of niet.\n' +
'\n' +
- `1. Klik op de +-knop.\n` +
+ `1. Klik op de ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}-knop.\n` +
'2. Kies *Uitgave aanmaken*.\n' +
'3. Voer een bedrag in of scan een bon.\n' +
'4. Kies uw *persoonlijke* ruimte.\n' +
@@ -2556,7 +2557,7 @@ ${amount} voor ${merchant} - ${date}`,
description:
'*Start een chat* met iedereen met behulp van hun e-mailadres of telefoonnummer.\n' +
'\n' +
- `1. Klik op de +-knop.\n` +
+ `1. Klik op de ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}-knop.\n` +
'2. Kies *Start chat*.\n' +
'3. Voer een e-mailadres of telefoonnummer in.\n' +
'\n' +
@@ -2569,7 +2570,7 @@ ${amount} voor ${merchant} - ${date}`,
description:
'*Splits uitgaven* met één of meer personen.\n' +
'\n' +
- `1. Klik op de +-knop.\n` +
+ `1. Klik op de ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}-knop.\n` +
'2. Kies *Start chat*.\n' +
'3. Voer e-mailadressen of telefoonnummers in.\n' +
'4. Klik op de grijze *+*-knop in de chat > *Splits uitgave*.\n' +
@@ -2591,7 +2592,7 @@ ${amount} voor ${merchant} - ${date}`,
description:
'Zo maakt u een rapport:\n' +
'\n' +
- `1. Klik op de +-knop.\n` +
+ `1. Klik op de ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}-knop.\n` +
'2. Kies *Rapport aanmaken*.\n' +
'3. Klik op *Uitgave toevoegen*.\n' +
'4. Voeg uw eerste uitgave toe.\n' +
@@ -2944,8 +2945,8 @@ ${amount} voor ${merchant} - ${date}`,
needSSNFull9: 'We hebben problemen met het verifiëren van uw SSN. Voer alstublieft de volledige negen cijfers van uw SSN in.',
weCouldNotVerify: 'We konden niet verifiëren',
pleaseFixIt: 'Pas deze informatie aan voordat u verdergaat.',
- failedKYCTextBefore: 'We konden uw identiteit niet verifiëren. Probeer het later opnieuw of neem contact op met',
- failedKYCTextAfter: 'als je vragen hebt.',
+ failedKYCMessage: ({conciergeEmail}: {conciergeEmail: string}) =>
+ `We konden uw identiteit niet verifiëren. Probeer het later opnieuw of neem contact op met ${conciergeEmail} als je vragen hebt.`,
},
termsStep: {
headerTitle: 'Voorwaarden en kosten',
@@ -4421,8 +4422,7 @@ ${amount} voor ${merchant} - ${date}`,
employeeDefaultDescription: 'De standaardafdeling van de werknemer wordt toegepast op hun uitgaven in Sage Intacct indien deze bestaat.',
displayedAsTagDescription: 'Afdeling zal selecteerbaar zijn voor elke individuele uitgave op het rapport van een werknemer.',
displayedAsReportFieldDescription: 'Afdelingsselectie zal van toepassing zijn op alle uitgaven in het rapport van een werknemer.',
- toggleImportTitleFirstPart: 'Kies hoe Sage Intacct te beheren',
- toggleImportTitleSecondPart: 'in Expensify.',
+ toggleImportTitle: ({mappingTitle}: ToggleImportTitleParams) => `Kies hoe Sage Intacct ${mappingTitle} in Expensify te beheren.`,
expenseTypes: 'Uitgavensoorten',
expenseTypesDescription: 'Uw Sage Intacct-uitgavensoorten worden in Expensify geïmporteerd als categorieën.',
accountTypesDescription: 'Uw Sage Intacct-rekeningschema wordt in Expensify geïmporteerd als categorieën.',
@@ -5729,7 +5729,7 @@ ${amount} voor ${merchant} - ${date}`,
chatWithYourAdmin: 'Chat met je beheerder',
chatInAdmins: 'Chat in #admins',
addPaymentCard: 'Betaalpas toevoegen',
- goToSubscriptions: 'Ga naar abonnementen',
+ goToSubscription: 'Ga naar het abonnement',
},
rules: {
individualExpenseRules: {
@@ -6150,7 +6150,7 @@ ${amount} voor ${merchant} - ${date}`,
searchResults: {
emptyResults: {
title: 'Niets om te laten zien',
- subtitle: `Probeer je zoekcriteria aan te passen of iets te maken met de + knop.`,
+ subtitle: `Probeer je zoekcriteria aan te passen of iets te maken met de groene ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE} knop.`,
},
emptyExpenseResults: {
title: 'Je hebt nog geen uitgaven gemaakt.',
@@ -6767,7 +6767,8 @@ ${amount} voor ${merchant} - ${date}`,
return '';
},
brokenConnection530Error: 'Ontvangst in behandeling vanwege verbroken bankverbinding',
- adminBrokenConnectionError: 'Ontvangst in afwachting vanwege verbroken bankverbinding. Los dit alstublieft op in',
+ adminBrokenConnectionError: ({workspaceCompanyCardRoute}: {workspaceCompanyCardRoute: string}) =>
+ `Bon in afwachting vanwege een verbroken bankverbinding. Los dit op in Bedrijfspassen.`,
memberBrokenConnectionError: 'Ontvangst in afwachting vanwege een verbroken bankverbinding. Vraag een werkruimtebeheerder om het op te lossen.',
markAsCashToIgnore: 'Markeren als contant om te negeren en betaling aan te vragen.',
smartscanFailed: ({canEdit = true}) => `Bonnetjes scannen mislukt.${canEdit ? 'Voer gegevens handmatig in.' : ''}`,
@@ -7315,6 +7316,11 @@ ${amount} voor ${merchant} - ${date}`,
conciergeWillSend: 'Concierge stuurt je het bestand binnenkort.',
},
avatarPage: {title: 'Profielfoto bewerken', upload: 'Uploaden', uploadPhoto: 'Foto uploaden', selectAvatar: 'Selecteer avatar', chooseCustomAvatar: 'Of kies een aangepaste avatar'},
+ openAppFailureModal: {
+ title: 'Er is iets misgegaan...',
+ subtitle: `We hebben niet al uw gegevens kunnen laden. We zijn op de hoogte gesteld en onderzoeken het probleem. Als dit aanhoudt, neem dan contact op met`,
+ refreshAndTryAgain: 'Vernieuw en probeer het opnieuw',
+ },
};
// IMPORTANT: This line is manually replaced in generate translation files by scripts/generateTranslations.ts,
// so if you change it here, please update it there as well.
diff --git a/src/languages/params.ts b/src/languages/params.ts
index 511aa478608f..26c31ecb6c9f 100644
--- a/src/languages/params.ts
+++ b/src/languages/params.ts
@@ -943,12 +943,17 @@ type MergeAccountIntoParams = {
login: string;
};
+type ToggleImportTitleParams = {
+ mappingTitle: string;
+};
+
type FocusModeUpdateParams = {
priorityModePageUrl: string;
};
export type {
SettlementAccountReconciliationParams,
+ ToggleImportTitleParams,
ContactMethodsRouteParams,
ContactMethodParams,
SplitExpenseEditTitleParams,
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index 435659f3a865..9811d470457c 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -237,6 +237,7 @@ import type {
TermsParams,
ThreadRequestReportNameParams,
ThreadSentMoneyReportNameParams,
+ ToggleImportTitleParams,
TotalAmountGreaterOrLessThanOriginalParams,
ToValidateLoginParams,
TransferParams,
@@ -672,6 +673,7 @@ const translations = {
pinned: 'Przypięte',
read: 'Przeczytane',
copyToClipboard: 'Skopiuj do schowka',
+ domains: 'Domeny',
},
supportalNoAccess: {
title: 'Nie tak szybko',
@@ -906,17 +908,17 @@ const translations = {
beginningOfChatHistoryUserRoom: ({reportName, reportDetailsLink}: BeginningOfChatHistoryUserRoomParams) =>
`Ten czat jest przeznaczony do wszystkiego, co związane z ${reportName}.`,
beginningOfChatHistoryInvoiceRoom: ({invoicePayer, invoiceReceiver}: BeginningOfChatHistoryInvoiceRoomParams) =>
- `Ten czat służy do wystawiania faktur między ${invoicePayer} i ${invoiceReceiver}. Użyj przycisku +, aby wysłać fakturę.`,
+ `Ten czat służy do wystawiania faktur między ${invoicePayer} i ${invoiceReceiver}. Użyj przycisku ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}, aby wysłać fakturę.`,
beginningOfChatHistory: 'Ta rozmowa jest z',
beginningOfChatHistoryPolicyExpenseChat: ({workspaceName, submitterDisplayName}: BeginningOfChatHistoryPolicyExpenseChatParams) =>
- `W tym miejscu ${submitterDisplayName} będzie przesyłać wydatki do ${workspaceName}. Wystarczy użyć przycisku +.`,
+ `W tym miejscu ${submitterDisplayName} będzie przesyłać wydatki do ${workspaceName}. Wystarczy użyć przycisku ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}.`,
beginningOfChatHistorySelfDM: 'To jest Twoja przestrzeń osobista. Używaj jej do notatek, zadań, szkiców i przypomnień.',
beginningOfChatHistorySystemDM: 'Witamy! Zacznijmy konfigurację.',
chatWithAccountManager: 'Czat z Twoim opiekunem konta tutaj',
sayHello: 'Powiedz cześć!',
yourSpace: 'Twoja przestrzeń',
welcomeToRoom: ({roomName}: WelcomeToRoomParams) => `Witamy w ${roomName}!`,
- usePlusButton: ({additionalText}: UsePlusButtonParams) => ` Użyj przycisku +, aby ${additionalText} wydatek.`,
+ usePlusButton: ({additionalText}: UsePlusButtonParams) => ` Użyj przycisku ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}, aby ${additionalText} wydatek.`,
askConcierge: 'Zadawaj pytania i otrzymuj wsparcie w czasie rzeczywistym 24/7.',
conciergeSupport: 'Całodobowe wsparcie',
create: 'utwórz',
@@ -1687,7 +1689,6 @@ const translations = {
testCrash: 'Test awarii',
resetToOriginalState: 'Przywróć do stanu początkowego',
usingImportedState: 'Używasz zaimportowanego stanu. Naciśnij tutaj, aby go wyczyścić.',
- shouldBlockTransactionThreadReportCreation: 'Blokuj tworzenie raportów wątku transakcji',
debugMode: 'Tryb debugowania',
invalidFile: 'Nieprawidłowy plik',
invalidFileDescription: 'Plik, który próbujesz zaimportować, jest nieprawidłowy. Spróbuj ponownie.',
@@ -2429,7 +2430,7 @@ ${amount} dla ${merchant} - ${date}`,
description:
'*Dien een uitgave in* door een bedrag in te voeren of een bon te scannen.\n' +
'\n' +
- `1. Klik op de +-knop.\n` +
+ `1. Klik op de ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}-knop.\n` +
'2. Kies *Uitgave aanmaken*.\n' +
'3. Voer een bedrag in of scan een bon.\n' +
`4. Voeg het e-mailadres of telefoonnummer van uw baas toe.\n` +
@@ -2442,7 +2443,7 @@ ${amount} dla ${merchant} - ${date}`,
description:
'*Dien een uitgave in* door een bedrag in te voeren of een bon te scannen.\n' +
'\n' +
- `1. Klik op de +-knop.\n` +
+ `1. Klik op de ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}-knop.\n` +
'2. Kies *Uitgave aanmaken*.\n' +
'3. Voer een bedrag in of scan een bon.\n' +
'4. Bevestig de details.\n' +
@@ -2455,7 +2456,7 @@ ${amount} dla ${merchant} - ${date}`,
description:
'*Volg een uitgave* in elke valuta, of u nu een bon heeft of niet.\n' +
'\n' +
- `1. Klik op de +-knop.\n` +
+ `1. Klik op de ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}-knop.\n` +
'2. Kies *Uitgave aanmaken*.\n' +
'3. Voer een bedrag in of scan een bon.\n' +
'4. Kies uw *persoonlijke* ruimte.\n' +
@@ -2550,7 +2551,7 @@ ${amount} dla ${merchant} - ${date}`,
description:
'*Start een chat* met iedereen met behulp van hun e-mailadres of telefoonnummer.\n' +
'\n' +
- `1. Klik op de +-knop.\n` +
+ `1. Klik op de ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}-knop.\n` +
'2. Kies *Start chat*.\n' +
'3. Voer een e-mailadres of telefoonnummer in.\n' +
'\n' +
@@ -2563,7 +2564,7 @@ ${amount} dla ${merchant} - ${date}`,
description:
'*Splits uitgaven* met één of meer personen.\n' +
'\n' +
- `1. Klik op de +-knop.\n` +
+ `1. Klik op de ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}-knop.\n` +
'2. Kies *Start chat*.\n' +
'3. Voer e-mailadressen of telefoonnummers in.\n' +
'4. Klik op de grijze *+*-knop in de chat > *Splits uitgave*.\n' +
@@ -2585,7 +2586,7 @@ ${amount} dla ${merchant} - ${date}`,
description:
'Zo maakt u een rapport:\n' +
'\n' +
- `1. Klik op de +-knop.\n` +
+ `1. Klik op de ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}-knop.\n` +
'2. Kies *Rapport aanmaken*.\n' +
'3. Klik op *Uitgave toevoegen*.\n' +
'4. Voeg uw eerste uitgave toe.\n' +
@@ -2937,8 +2938,8 @@ ${amount} dla ${merchant} - ${date}`,
needSSNFull9: 'Mamy problem z weryfikacją Twojego numeru SSN. Proszę wprowadzić pełne dziewięć cyfr swojego numeru SSN.',
weCouldNotVerify: 'Nie mogliśmy zweryfikować',
pleaseFixIt: 'Proszę poprawić te informacje przed kontynuowaniem',
- failedKYCTextBefore: 'Nie udało nam się zweryfikować Twojej tożsamości. Spróbuj ponownie później lub skontaktuj się z',
- failedKYCTextAfter: 'jeśli masz jakieś pytania.',
+ failedKYCMessage: ({conciergeEmail}: {conciergeEmail: string}) =>
+ `Nie udało nam się zweryfikować Twojej tożsamości. Spróbuj ponownie później lub skontaktuj się z ${conciergeEmail}, jeśli masz jakieś pytania.`,
},
termsStep: {
headerTitle: 'Warunki i opłaty',
@@ -4409,8 +4410,8 @@ ${amount} dla ${merchant} - ${date}`,
employeeDefaultDescription: 'Domyślny dział pracownika zostanie zastosowany do jego wydatków w Sage Intacct, jeśli taki istnieje.',
displayedAsTagDescription: 'Dział będzie można wybrać dla każdego indywidualnego wydatku w raporcie pracownika.',
displayedAsReportFieldDescription: 'Wybór działu będzie dotyczył wszystkich wydatków w raporcie pracownika.',
- toggleImportTitleFirstPart: 'Wybierz, jak obsługiwać Sage Intacct',
- toggleImportTitleSecondPart: 'w Expensify.',
+ toggleImportTitle: ({mappingTitle}: ToggleImportTitleParams) => `Wybierz, jak obsługiwać Sage Intacct ${mappingTitle} w Expensify.`,
+
expenseTypes: 'Typy wydatków',
expenseTypesDescription: 'Twoje typy wydatków Sage Intacct zostaną zaimportowane do Expensify jako kategorie.',
accountTypesDescription: 'Twój plan kont Sage Intacct zostanie zaimportowany do Expensify jako kategorie.',
@@ -5718,7 +5719,7 @@ ${amount} dla ${merchant} - ${date}`,
chatWithYourAdmin: 'Porozmawiaj ze swoim administratorem',
chatInAdmins: 'Czat w #admins',
addPaymentCard: 'Dodaj kartę płatniczą',
- goToSubscriptions: 'Przejdź do subskrypcji',
+ goToSubscription: 'Przejdź do subskrypcji',
},
rules: {
individualExpenseRules: {
@@ -6136,7 +6137,7 @@ ${amount} dla ${merchant} - ${date}`,
searchResults: {
emptyResults: {
title: 'Brak danych do wyświetlenia',
- subtitle: `Spróbuj dostosować kryteria wyszukiwania lub utwórz coś za pomocą przycisku +.`,
+ subtitle: `Spróbuj dostosować kryteria wyszukiwania lub utwórz coś za pomocą zielonego przycisku ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}.`,
},
emptyExpenseResults: {
title: 'Nie utworzyłeś jeszcze żadnych wydatków.',
@@ -6753,7 +6754,8 @@ ${amount} dla ${merchant} - ${date}`,
return '';
},
brokenConnection530Error: 'Paragon oczekuje z powodu zerwanego połączenia z bankiem',
- adminBrokenConnectionError: 'Paragon oczekuje z powodu przerwanego połączenia z bankiem. Proszę rozwiązać w',
+ adminBrokenConnectionError: ({workspaceCompanyCardRoute}: {workspaceCompanyCardRoute: string}) =>
+ `Paragon oczekuje z powodu zerwanego połączenia bankowego. Rozwiąż problem w Kartach firmowych.`,
memberBrokenConnectionError: 'Paragon oczekuje z powodu zerwanego połączenia z bankiem. Proszę poprosić administratora przestrzeni roboczej o rozwiązanie problemu.',
markAsCashToIgnore: 'Oznacz jako gotówkę, aby zignorować i zażądać płatności.',
smartscanFailed: ({canEdit = true}) => `Skanowanie paragonu nie powiodło się.${canEdit ? 'Wprowadź dane ręcznie.' : ''}`,
@@ -7302,6 +7304,11 @@ ${amount} dla ${merchant} - ${date}`,
conciergeWillSend: 'Concierge wkrótce prześle plik.',
},
avatarPage: {title: 'Edytuj zdjęcie profilowe', upload: 'Prześlij', uploadPhoto: 'Prześlij zdjęcie', selectAvatar: 'Wybierz awatar', chooseCustomAvatar: 'Lub wybierz własny awatar'},
+ openAppFailureModal: {
+ title: 'Coś poszło nie tak...',
+ subtitle: `Nie udało nam się wczytać wszystkich Twoich danych. Zostaliśmy o tym powiadomieni i badamy problem. Jeśli problem będzie się utrzymywał, skontaktuj się z`,
+ refreshAndTryAgain: 'Odśwież i spróbuj ponownie',
+ },
};
// IMPORTANT: This line is manually replaced in generate translation files by scripts/generateTranslations.ts,
// so if you change it here, please update it there as well.
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index 6bbb94ec75b8..ba4dc61e45ff 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -237,6 +237,7 @@ import type {
TermsParams,
ThreadRequestReportNameParams,
ThreadSentMoneyReportNameParams,
+ ToggleImportTitleParams,
TotalAmountGreaterOrLessThanOriginalParams,
ToValidateLoginParams,
TransferParams,
@@ -674,6 +675,7 @@ const translations = {
pinned: 'Fixado',
read: 'Lido',
copyToClipboard: 'Copiar para a área de transferência',
+ domains: 'Domínios',
},
supportalNoAccess: {
title: 'Não tão rápido',
@@ -907,17 +909,17 @@ const translations = {
beginningOfChatHistoryUserRoom: ({reportName, reportDetailsLink}: BeginningOfChatHistoryUserRoomParams) =>
`Esta sala de bate-papo é para qualquer coisa relacionada ao ${reportName}.`,
beginningOfChatHistoryInvoiceRoom: ({invoicePayer, invoiceReceiver}: BeginningOfChatHistoryInvoiceRoomParams) =>
- `Este bate-papo é para faturas entre ${invoicePayer} e a ${invoiceReceiver}. Use o botão + para enviar uma fatura.`,
+ `Este bate-papo é para faturas entre ${invoicePayer} e a ${invoiceReceiver}. Use o botão ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE} para enviar uma fatura.`,
beginningOfChatHistory: 'Este chat é com',
beginningOfChatHistoryPolicyExpenseChat: ({workspaceName, submitterDisplayName}: BeginningOfChatHistoryPolicyExpenseChatParams) =>
- `É aqui que ${submitterDisplayName} enviará as despesas para a ${workspaceName}. Basta usar o botão +.`,
+ `É aqui que ${submitterDisplayName} enviará as despesas para a ${workspaceName}. Basta usar o botão ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}.`,
beginningOfChatHistorySelfDM: 'Este é o seu espaço pessoal. Use-o para anotações, tarefas, rascunhos e lembretes.',
beginningOfChatHistorySystemDM: 'Bem-vindo! Vamos configurá-lo.',
chatWithAccountManager: 'Converse com o seu gerente de conta aqui',
sayHello: 'Diga olá!',
yourSpace: 'Seu espaço',
welcomeToRoom: ({roomName}: WelcomeToRoomParams) => `Bem-vindo(a) ao ${roomName}!`,
- usePlusButton: ({additionalText}: UsePlusButtonParams) => ` Use o botão + para ${additionalText} uma despesa.`,
+ usePlusButton: ({additionalText}: UsePlusButtonParams) => ` Use o botão ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE} para ${additionalText} uma despesa.`,
askConcierge: 'Faça perguntas e receba suporte em tempo real 24/7.',
conciergeSupport: 'Suporte 24/7',
create: 'criar',
@@ -1684,7 +1686,6 @@ const translations = {
testCrash: 'Teste de falha',
resetToOriginalState: 'Redefinir para o estado original',
usingImportedState: 'Você está usando um estado importado. Clique aqui para limpá-lo.',
- shouldBlockTransactionThreadReportCreation: 'Bloquear a criação de relatórios de thread de transação',
debugMode: 'Modo de depuração',
invalidFile: 'Arquivo inválido',
invalidFileDescription: 'O arquivo que você está tentando importar não é válido. Por favor, tente novamente.',
@@ -2427,7 +2428,7 @@ ${amount} para ${merchant} - ${date}`,
description:
'*Envie uma despesa* inserindo um valor ou digitalizando um recibo.\n' +
'\n' +
- `1. Clique no botão +.\n` +
+ `1. Clique no botão ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}.\n` +
'2. Escolha *Criar despesa*.\n' +
'3. Insira um valor ou digitalize um recibo.\n' +
`4. Adicione o e-mail ou número de telefone do seu chefe.\n` +
@@ -2440,7 +2441,7 @@ ${amount} para ${merchant} - ${date}`,
description:
'*Envie uma despesa* inserindo um valor ou digitalizando um recibo.\n' +
'\n' +
- `1. Clique no botão +.\n` +
+ `1. Clique no botão ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}.\n` +
'2. Escolha *Criar despesa*.\n' +
'3. Insira um valor ou digitalize um recibo.\n' +
'4. Confirme os detalhes.\n' +
@@ -2453,7 +2454,7 @@ ${amount} para ${merchant} - ${date}`,
description:
'*Rastreie uma despesa* em qualquer moeda, com ou sem recibo.\n' +
'\n' +
- `1. Clique no botão +.\n` +
+ `1. Clique no botão ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}.\n` +
'2. Escolha *Criar despesa*.\n' +
'3. Insira um valor ou digitalize um recibo.\n' +
'4. Escolha seu espaço *pessoal*.\n' +
@@ -2548,7 +2549,7 @@ ${amount} para ${merchant} - ${date}`,
description:
'*Inicie um bate-papo* com qualquer pessoa usando seu e-mail ou número de telefone.\n' +
'\n' +
- `1. Clique no botão +.\n` +
+ `1. Clique no botão ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}.\n` +
'2. Escolha *Iniciar bate-papo*.\n' +
'3. Insira um e-mail ou número de telefone.\n' +
'\n' +
@@ -2561,7 +2562,7 @@ ${amount} para ${merchant} - ${date}`,
description:
'*Divida despesas* com uma ou mais pessoas.\n' +
'\n' +
- `1. Clique no botão +.\n` +
+ `1. Clique no botão ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}.\n` +
'2. Escolha *Iniciar bate-papo*.\n' +
'3. Insira e-mails ou números de telefone.\n' +
'4. Clique no botão cinza *+* no bate-papo > *Dividir despesa*.\n' +
@@ -2583,7 +2584,7 @@ ${amount} para ${merchant} - ${date}`,
description:
'Veja como criar um relatório:\n' +
'\n' +
- `1. Clique no botão +.\n` +
+ `1. Clique no botão ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}.\n` +
'2. Escolha *Criar relatório*.\n' +
'3. Clique em *Adicionar despesa*.\n' +
'4. Adicione sua primeira despesa.\n' +
@@ -2937,8 +2938,8 @@ ${amount} para ${merchant} - ${date}`,
needSSNFull9: 'Estamos tendo problemas para verificar seu SSN. Por favor, insira os nove dígitos completos do seu SSN.',
weCouldNotVerify: 'Não conseguimos verificar',
pleaseFixIt: 'Por favor, corrija esta informação antes de continuar.',
- failedKYCTextBefore: 'Não conseguimos verificar sua identidade. Por favor, tente novamente mais tarde ou entre em contato com',
- failedKYCTextAfter: 'se você tiver alguma dúvida.',
+ failedKYCMessage: ({conciergeEmail}: {conciergeEmail: string}) =>
+ `Não conseguimos verificar sua identidade. Por favor, tente novamente mais tarde ou entre em contato com ${conciergeEmail} se você tiver alguma dúvida.`,
},
termsStep: {
headerTitle: 'Termos e taxas',
@@ -4409,8 +4410,8 @@ ${amount} para ${merchant} - ${date}`,
employeeDefaultDescription: 'O departamento padrão do funcionário será aplicado às suas despesas no Sage Intacct, se existir.',
displayedAsTagDescription: 'O departamento será selecionável para cada despesa individual no relatório de um funcionário.',
displayedAsReportFieldDescription: 'A seleção de departamento será aplicada a todas as despesas no relatório de um funcionário.',
- toggleImportTitleFirstPart: 'Escolha como lidar com o Sage Intacct',
- toggleImportTitleSecondPart: 'in Expensify.',
+ toggleImportTitle: ({mappingTitle}: ToggleImportTitleParams) => `Escolha como lidar com o Sage Intacct ${mappingTitle} in Expensify.`,
+
expenseTypes: 'Tipos de despesas',
expenseTypesDescription: 'Seus tipos de despesas do Sage Intacct serão importados para o Expensify como categorias.',
accountTypesDescription: 'Seu plano de contas do Sage Intacct será importado para o Expensify como categorias.',
@@ -5724,7 +5725,7 @@ ${amount} para ${merchant} - ${date}`,
chatWithYourAdmin: 'Converse com seu administrador',
chatInAdmins: 'Converse em #admins',
addPaymentCard: 'Adicionar cartão de pagamento',
- goToSubscriptions: 'Ir para Assinaturas',
+ goToSubscription: 'Ir para a assinatura',
},
rules: {
individualExpenseRules: {
@@ -6145,7 +6146,7 @@ ${amount} para ${merchant} - ${date}`,
searchResults: {
emptyResults: {
title: 'Nada para mostrar',
- subtitle: `Tente ajustar seus critérios de busca ou criar algo com o botão +.`,
+ subtitle: `Tente ajustar seus critérios de busca ou criar algo com o botão verde ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE}.`,
},
emptyExpenseResults: {
title: 'Você ainda não criou nenhuma despesa ainda',
@@ -6762,7 +6763,8 @@ ${amount} para ${merchant} - ${date}`,
return '';
},
brokenConnection530Error: 'Recibo pendente devido a conexão bancária interrompida',
- adminBrokenConnectionError: 'Recibo pendente devido a uma conexão bancária interrompida. Por favor, resolva em',
+ adminBrokenConnectionError: ({workspaceCompanyCardRoute}: {workspaceCompanyCardRoute: string}) =>
+ `Recibo pendente devido a uma conexão bancária interrompida. Resolva em Cartões da empresa.`,
memberBrokenConnectionError: 'Recibo pendente devido a uma conexão bancária interrompida. Por favor, peça a um administrador do espaço de trabalho para resolver.',
markAsCashToIgnore: 'Marcar como dinheiro para ignorar e solicitar pagamento.',
smartscanFailed: ({canEdit = true}) => `Falha na digitalização do recibo.${canEdit ? 'Insira os detalhes manualmente.' : ''}`,
@@ -7309,6 +7311,7 @@ ${amount} para ${merchant} - ${date}`,
exportInProgress: 'Exportação em andamento',
conciergeWillSend: 'Concierge enviará o arquivo em breve.',
},
+
avatarPage: {
title: 'Editar foto de perfil',
upload: 'Carregar',
@@ -7316,6 +7319,11 @@ ${amount} para ${merchant} - ${date}`,
selectAvatar: 'Selecionar avatar',
chooseCustomAvatar: 'Ou escolha um avatar personalizado',
},
+ openAppFailureModal: {
+ title: 'Algo deu errado...',
+ subtitle: `Não conseguimos carregar todos os seus dados. Fomos notificados e estamos investigando o problema. Se isso persistir, entre em contato com`,
+ refreshAndTryAgain: 'Atualize e tente novamente',
+ },
};
// IMPORTANT: This line is manually replaced in generate translation files by scripts/generateTranslations.ts,
// so if you change it here, please update it there as well.
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index f733a827b037..230eceb7578b 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -237,6 +237,7 @@ import type {
TermsParams,
ThreadRequestReportNameParams,
ThreadSentMoneyReportNameParams,
+ ToggleImportTitleParams,
TotalAmountGreaterOrLessThanOriginalParams,
ToValidateLoginParams,
TransferParams,
@@ -673,6 +674,7 @@ const translations = {
pinned: '已固定',
read: '已读',
copyToClipboard: '复制到剪贴板',
+ domains: '域名',
},
supportalNoAccess: {
title: '慢一点',
@@ -905,17 +907,17 @@ const translations = {
beginningOfChatHistoryUserRoom: ({reportName, reportDetailsLink}: BeginningOfChatHistoryUserRoomParams) =>
`本聊天室用于与 ${reportName} 有关的任何内容。`,
beginningOfChatHistoryInvoiceRoom: ({invoicePayer, invoiceReceiver}: BeginningOfChatHistoryInvoiceRoomParams) =>
- `该聊天用于 ${invoicePayer} 和 ${invoiceReceiver} 之间的发票。使用 + 按钮发送发票。`,
+ `该聊天用于 ${invoicePayer} 和 ${invoiceReceiver} 之间的发票。使用 ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE} 按钮发送发票。`,
beginningOfChatHistory: '此聊天是与',
beginningOfChatHistoryPolicyExpenseChat: ({workspaceName, submitterDisplayName}: BeginningOfChatHistoryPolicyExpenseChatParams) =>
- `这是${submitterDisplayName} 向${workspaceName} 提交费用的地方。使用 + 按钮即可。`,
+ `这是${submitterDisplayName} 向${workspaceName} 提交费用的地方。使用 ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE} 按钮即可。`,
beginningOfChatHistorySelfDM: '这是您的个人空间。用于记录笔记、任务、草稿和提醒。',
beginningOfChatHistorySystemDM: '欢迎!让我们为您进行设置。',
chatWithAccountManager: '在这里与您的客户经理聊天',
sayHello: '说你好!',
yourSpace: '您的空间',
welcomeToRoom: ({roomName}: WelcomeToRoomParams) => `欢迎来到${roomName}!`,
- usePlusButton: ({additionalText}: UsePlusButtonParams) => ` 使用 + 按钮${additionalText}一笔费用。`,
+ usePlusButton: ({additionalText}: UsePlusButtonParams) => ` 使用 ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE} 按钮${additionalText}一笔费用。`,
askConcierge: '随时提问并获得全天候实时支持。',
conciergeSupport: '24/7 支持',
create: '创建',
@@ -1670,7 +1672,6 @@ const translations = {
testCrash: '测试崩溃',
resetToOriginalState: '重置为原始状态',
usingImportedState: '您正在使用导入的状态。点击这里清除它。',
- shouldBlockTransactionThreadReportCreation: '阻止创建交易线程报告',
debugMode: '调试模式',
invalidFile: '文件无效',
invalidFileDescription: '您尝试导入的文件无效。请再试一次。',
@@ -2903,8 +2904,8 @@ ${merchant}的${amount} - ${date}`,
needSSNFull9: '我们无法验证您的SSN。请输入您SSN的完整九位数字。',
weCouldNotVerify: '我们无法验证',
pleaseFixIt: '请在继续之前修正此信息',
- failedKYCTextBefore: '我们无法验证您的身份。请稍后再试或联系',
- failedKYCTextAfter: '如果您有任何问题。',
+ failedKYCMessage: ({conciergeEmail}: {conciergeEmail: string}) =>
+ `我们无法验证您的身份。请稍后再试或联系 ${conciergeEmail} 如果您有任何问题。`,
},
termsStep: {
headerTitle: '条款和费用',
@@ -4333,8 +4334,7 @@ ${merchant}的${amount} - ${date}`,
employeeDefaultDescription: '如果存在,员工的默认部门将应用于他们在 Sage Intacct 中的费用。',
displayedAsTagDescription: '部门将可在员工报告的每一笔费用中选择。',
displayedAsReportFieldDescription: '部门选择将适用于员工报告中的所有费用。',
- toggleImportTitleFirstPart: '选择如何处理 Sage Intacct',
- toggleImportTitleSecondPart: '在Expensify中。',
+ toggleImportTitle: ({mappingTitle}: ToggleImportTitleParams) => `选择如何处理 Sage Intacct ${mappingTitle} 在Expensify中。`,
expenseTypes: '费用类型',
expenseTypesDescription: '您的 Sage Intacct 费用类型将作为类别导入到 Expensify。',
accountTypesDescription: '您的 Sage Intacct 科目表将作为类别导入到 Expensify 中。',
@@ -5621,7 +5621,7 @@ ${merchant}的${amount} - ${date}`,
chatWithYourAdmin: '与您的管理员聊天',
chatInAdmins: '在#admins中聊天',
addPaymentCard: '添加支付卡',
- goToSubscriptions: '前往订阅',
+ goToSubscription: '前往订阅',
},
rules: {
individualExpenseRules: {
@@ -6023,7 +6023,7 @@ ${merchant}的${amount} - ${date}`,
searchResults: {
emptyResults: {
title: '无内容显示',
- subtitle: `尝试调整您的搜索条件或使用 + 按钮创建内容。`,
+ subtitle: `尝试调整您的搜索条件或使用绿色的 ${CONST.CUSTOM_EMOJIS.GLOBAL_CREATE} 按钮创建内容。`,
},
emptyExpenseResults: {
title: '您还没有创建任何费用',
@@ -6629,7 +6629,8 @@ ${merchant}的${amount} - ${date}`,
return '';
},
brokenConnection530Error: '由于银行连接中断,收据待处理',
- adminBrokenConnectionError: '由于银行连接中断,收据待处理。请在',
+ adminBrokenConnectionError: ({workspaceCompanyCardRoute}: {workspaceCompanyCardRoute: string}) =>
+ `由于银行连接中断,收据正在等待处理中。请前往 公司卡 解决。`,
memberBrokenConnectionError: '由于银行连接中断,收据待处理。请联系工作区管理员解决。',
markAsCashToIgnore: '标记为现金以忽略并请求付款。',
smartscanFailed: ({canEdit = true}) => `扫描收据失败。${canEdit ? '手动输入详细信息。' : ''}`,
@@ -7164,6 +7165,7 @@ ${merchant}的${amount} - ${date}`,
conciergeWillSend: 'Concierge 很快会将文件发送给您。',
},
avatarPage: {title: '编辑个人资料图片', upload: '上传', uploadPhoto: '上传照片', selectAvatar: '选择头像', chooseCustomAvatar: '或选择自定义头像'},
+ openAppFailureModal: {title: '出了点问题...', subtitle: `我们未能加载您的所有数据。我们已收到通知,正在调查此问题。如果问题仍然存在,请联系`, refreshAndTryAgain: '刷新并重试'},
};
// IMPORTANT: This line is manually replaced in generate translation files by scripts/generateTranslations.ts,
// so if you change it here, please update it there as well.
diff --git a/src/libs/API/parameters/CreateWorkspaceParams.ts b/src/libs/API/parameters/CreateWorkspaceParams.ts
index 2c84acc79b07..6836b599ae75 100644
--- a/src/libs/API/parameters/CreateWorkspaceParams.ts
+++ b/src/libs/API/parameters/CreateWorkspaceParams.ts
@@ -16,8 +16,8 @@ type CreateWorkspaceParams = {
file?: File;
companySize?: string;
userReportedIntegration?: string;
- areDistanceRatesEnabled?: boolean;
memberData?: string;
+ features?: string;
};
export default CreateWorkspaceParams;
diff --git a/src/libs/API/parameters/SplitTransactionParams.ts b/src/libs/API/parameters/SplitTransactionParams.ts
index 8b611cd8d038..3ffc0db281eb 100644
--- a/src/libs/API/parameters/SplitTransactionParams.ts
+++ b/src/libs/API/parameters/SplitTransactionParams.ts
@@ -1,7 +1,6 @@
import type {Comment} from '@src/types/onyx/Transaction';
-type SplitTransactionSplitsParam = Array<{
- amount: number;
+type SplitTransactionSplitParam = {
transactionID: string;
category?: string;
tag?: string;
@@ -15,11 +14,15 @@ type SplitTransactionSplitsParam = Array<{
reimbursable?: boolean;
billable?: boolean;
reportID?: string;
-}>;
+};
+
+type SplitTransactionSplitsParam = SplitTransactionSplitParam[];
type SplitTransactionParams = {
transactionID: string;
[key: string]: string | boolean;
};
-export type {SplitTransactionParams, SplitTransactionSplitsParam};
+type RevertSplitTransactionParams = Omit & {comment?: string};
+
+export type {SplitTransactionParams, SplitTransactionSplitsParam, RevertSplitTransactionParams};
diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts
index 6594e89f1d4c..694ff0430b8b 100644
--- a/src/libs/API/parameters/index.ts
+++ b/src/libs/API/parameters/index.ts
@@ -176,7 +176,7 @@ export type {default as CompleteSplitBillParams} from './CompleteSplitBillParams
export type {default as UpdateMoneyRequestParams} from './UpdateMoneyRequestParams';
export type {default as RequestMoneyParams} from './RequestMoneyParams';
export type {default as SplitBillParams} from './SplitBillParams';
-export type {SplitTransactionParams, SplitTransactionSplitsParam} from './SplitTransactionParams';
+export type {SplitTransactionParams, SplitTransactionSplitsParam, RevertSplitTransactionParams} from './SplitTransactionParams';
export type {default as DeleteMoneyRequestParams} from './DeleteMoneyRequestParams';
export type {default as CreateDistanceRequestParams} from './CreateDistanceRequestParams';
export type {default as StartSplitBillParams} from './StartSplitBillParams';
diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts
index ddd5e9cf3cea..c65d2d2ad017 100644
--- a/src/libs/API/types.ts
+++ b/src/libs/API/types.ts
@@ -216,6 +216,7 @@ const WRITE_COMMANDS = {
SPLIT_BILL_AND_OPEN_REPORT: 'SplitBillAndOpenReport',
UPDATE_SPLIT_TRANSACTION: 'UpdateSplitTransaction',
SPLIT_TRANSACTION: 'Transaction_Split',
+ REVERT_SPLIT_TRANSACTION: 'RevertSplitTransaction',
DELETE_MONEY_REQUEST: 'DeleteMoneyRequest',
REJECT_MONEY_REQUEST: 'RejectMoneyRequest',
MARK_TRANSACTION_VIOLATION_AS_RESOLVED: 'MarkTransactionViolationAsResolved',
@@ -716,6 +717,7 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.SPLIT_BILL]: Parameters.SplitBillParams;
[WRITE_COMMANDS.SPLIT_BILL_AND_OPEN_REPORT]: Parameters.SplitBillParams;
[WRITE_COMMANDS.SPLIT_TRANSACTION]: Parameters.SplitTransactionParams;
+ [WRITE_COMMANDS.REVERT_SPLIT_TRANSACTION]: Parameters.RevertSplitTransactionParams;
[WRITE_COMMANDS.UPDATE_SPLIT_TRANSACTION]: Parameters.SplitTransactionParams;
[WRITE_COMMANDS.DELETE_MONEY_REQUEST]: Parameters.DeleteMoneyRequestParams;
[WRITE_COMMANDS.REJECT_MONEY_REQUEST]: Parameters.RejectMoneyRequestParams;
diff --git a/src/libs/ExportOnyxState/common.ts b/src/libs/ExportOnyxState/common.ts
index 9b4a7d718890..e5088a1f8107 100644
--- a/src/libs/ExportOnyxState/common.ts
+++ b/src/libs/ExportOnyxState/common.ts
@@ -1,11 +1,92 @@
import {Str} from 'expensify-common';
import type {ValueOf} from 'type-fest';
import ONYXKEYS from '@src/ONYXKEYS';
-import type {Credentials, Session} from '@src/types/onyx';
import type OnyxState from '@src/types/onyx/OnyxState';
import type {MaskOnyxState} from './types';
const MASKING_PATTERN = '***';
+
+type ExportRule = {
+ allowList: string[];
+ maskList: string[];
+};
+
+const ONYX_KEY_EXPORT_RULES: Record = {
+ [ONYXKEYS.SESSION]: {
+ allowList: ['email', 'accountID', 'loading', 'creationDate', 'errors'],
+ maskList: [],
+ },
+ [ONYXKEYS.STASHED_SESSION]: {
+ allowList: ['email', 'accountID', 'loading', 'creationDate', 'errors'],
+ maskList: [],
+ },
+ [ONYXKEYS.CREDENTIALS]: {
+ allowList: ['login', 'accountID'],
+ maskList: [],
+ },
+ [ONYXKEYS.STASHED_CREDENTIALS]: {
+ allowList: ['login', 'accountID'],
+ maskList: [],
+ },
+ [ONYXKEYS.ACCOUNT]: {
+ allowList: ['validated', 'isFromPublicDomain', 'isUsingExpensifyCard'],
+ maskList: ['primaryLogin'],
+ },
+ [ONYXKEYS.PERSONAL_DETAILS_LIST]: {
+ allowList: ['accountID', 'timezone', 'status', 'pronouns'],
+ maskList: ['firstName', 'lastName', 'displayName', 'avatar', 'login'],
+ },
+ [ONYXKEYS.COLLECTION.REPORT]: {
+ allowList: [
+ 'reportID',
+ 'type',
+ 'chatType',
+ 'lastActorAccountID',
+ 'participants',
+ 'pendingFields',
+ 'ownerAccountID',
+ 'stateNum',
+ 'statusNum',
+ 'isOwnPolicyExpenseChat',
+ 'participantAccountIDs',
+ 'total',
+ 'currency',
+ 'created',
+ ],
+ maskList: ['reportName', 'description', 'ownerAccountID', 'managerID'],
+ },
+ [ONYXKEYS.COLLECTION.TRANSACTION]: {
+ allowList: ['transactionID', 'reportID', 'amount', 'currency', 'created', 'category', 'tag', 'billable'],
+ maskList: ['merchant', 'description', 'comment'],
+ },
+ [ONYXKEYS.COLLECTION.POLICY]: {
+ allowList: ['id', 'type', 'role', 'outputCurrency', 'isPolicyExpenseChatEnabled', 'areCategoriesEnabled', 'areTagsEnabled'],
+ maskList: ['name', 'avatar'],
+ },
+ [ONYXKEYS.USER_WALLET]: {
+ allowList: ['currentBalance', 'availableBalance', 'tierName'],
+ maskList: [],
+ },
+ [ONYXKEYS.BANK_ACCOUNT_LIST]: {
+ allowList: ['accountType', 'currency'],
+ maskList: ['accountNumber', 'routingNumber', 'addressName'],
+ },
+ [ONYXKEYS.CARD_LIST]: {
+ allowList: ['accountID', 'bank', 'isVirtual', 'cardID'],
+ maskList: ['lastFourPAN', 'nameOnCard'],
+ },
+};
+
+const onyxKeysToRemove: Array> = [
+ ONYXKEYS.NVP_PRIVATE_PUSH_NOTIFICATION_ID,
+ ONYXKEYS.NVP_PRIVATE_STRIPE_CUSTOMER_ID,
+ ONYXKEYS.NVP_PRIVATE_BILLING_DISPUTE_PENDING,
+ ONYXKEYS.NVP_PRIVATE_BILLING_STATUS,
+ ONYXKEYS.PLAID_LINK_TOKEN,
+ ONYXKEYS.ONFIDO_TOKEN,
+ ONYXKEYS.ONFIDO_APPLICANT_ID,
+];
+
const keysToMask = [
'addressCity',
'addressName',
@@ -49,8 +130,6 @@ const keysToMask = [
'zipCode',
];
-const onyxKeysToRemove: Array> = [ONYXKEYS.NVP_PRIVATE_PUSH_NOTIFICATION_ID];
-
const emailRegex = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/;
const emailMap = new Map();
@@ -97,32 +176,34 @@ function replaceEmailInString(text: string, emailReplacement: string) {
return text.replace(emailRegex, emailReplacement);
}
-const maskSessionDetails = (session: Session): Session => {
- const allowList = ['email', 'accountID', 'loading', 'creationDate', 'errors'];
- const maskedData: OnyxState = {};
+const processOnyxKeyWithRule = (key: string, data: unknown, rule: ExportRule): unknown => {
+ if (data === null || data === undefined) {
+ return data;
+ }
- Object.keys(session).forEach((key) => {
- if (allowList.includes(key)) {
- maskedData[key] = session[key as keyof Session];
- return;
- }
- maskedData[key] = MASKING_PATTERN;
- });
- return maskedData as Session;
-};
+ if (Array.isArray(data)) {
+ return data.map((item: unknown) => (typeof item === 'object' ? processOnyxKeyWithRule(key, item, rule) : item));
+ }
-const maskCredentials = (credentials: Credentials): Credentials => {
- const allowList = ['login', 'accountID'];
- const maskedData: OnyxState = {};
+ if (typeof data === 'object') {
+ const processedData: Record = {};
- Object.keys(credentials).forEach((key) => {
- if (allowList.includes(key)) {
- maskedData[key] = credentials[key as keyof Credentials];
- return;
- }
- maskedData[key] = MASKING_PATTERN;
- });
- return maskedData as Credentials;
+ Object.keys(data as Record).forEach((fieldKey) => {
+ const fieldValue = (data as Record)[fieldKey];
+
+ if (rule.maskList.includes(fieldKey)) {
+ processedData[fieldKey] = maskValuePreservingLength(fieldValue);
+ } else if (rule.allowList.includes(fieldKey)) {
+ processedData[fieldKey] = fieldValue;
+ } else {
+ processedData[fieldKey] = MASKING_PATTERN;
+ }
+ });
+
+ return processedData;
+ }
+
+ return data;
};
const maskEmail = (email: string) => {
@@ -204,29 +285,24 @@ const removePrivateOnyxKeys = (onyxState: OnyxState): OnyxState => {
};
const maskOnyxState: MaskOnyxState = (data, isMaskingFragileDataEnabled) => {
- let onyxState = data;
+ let onyxState = {...data};
- // Mask session details by default
- if (onyxState.session) {
- onyxState.session = maskSessionDetails(onyxState.session as Session);
- }
- if (onyxState.stashedSession) {
- onyxState.stashedSession = maskSessionDetails(onyxState.stashedSession as Session);
- }
-
- // Remove private/sensitive Onyx keys
onyxState = removePrivateOnyxKeys(onyxState);
- // Mask credentials
- if (onyxState.credentials) {
- onyxState.credentials = maskCredentials(onyxState.credentials as Credentials);
- }
+ Object.keys(onyxState).forEach((key) => {
+ let ruleKey = key;
+ const collectionKey = Object.values(ONYXKEYS.COLLECTION).find((cKey) => key.startsWith(cKey));
+ if (collectionKey) {
+ ruleKey = collectionKey;
+ }
- if (onyxState.stashedCredentials) {
- onyxState.stashedCredentials = maskCredentials(onyxState.stashedCredentials as Credentials);
- }
+ const rule = ONYX_KEY_EXPORT_RULES[ruleKey];
+
+ if (rule) {
+ onyxState[key] = processOnyxKeyWithRule(key, onyxState[key], rule);
+ }
+ });
- // Mask fragile data other than session details if the user has enabled the option
if (isMaskingFragileDataEnabled) {
onyxState = maskFragileData(onyxState) as OnyxState;
}
diff --git a/src/libs/Formula.ts b/src/libs/Formula.ts
index 4f517f9619b1..ddd9f5b96326 100644
--- a/src/libs/Formula.ts
+++ b/src/libs/Formula.ts
@@ -3,6 +3,7 @@ import type {ValueOf} from 'type-fest';
import CONST from '@src/CONST';
import type {Policy, Report, Transaction} from '@src/types/onyx';
import {getCurrencySymbol} from './CurrencyUtils';
+import {formatDate} from './FormulaDatetime';
import {getAllReportActions} from './ReportActionsUtils';
import {getReportTransactions} from './ReportUtils';
import {getCreated, isPartialTransaction} from './TransactionUtils';
@@ -237,7 +238,10 @@ function compute(formula?: string, context?: FormulaContext): string {
*/
function computeReportPart(part: FormulaPart, context: FormulaContext): string {
const {report, policy} = context;
- const [field, format] = part.fieldPath;
+ const [field, ...additionalPath] = part.fieldPath;
+ // Reconstruct format string by joining additional path elements with ':'
+ // This handles format strings with colons like 'HH:mm:ss'
+ const format = additionalPath.length > 0 ? additionalPath.join(':') : undefined;
if (!field) {
return part.definition;
@@ -353,54 +357,6 @@ function getSubstring(value: string, args: string[]): string {
return value.substring(start);
}
-/**
- * Format a date value with support for multiple date formats
- */
-function formatDate(dateString: string | undefined, format = 'yyyy-MM-dd'): string {
- if (!dateString) {
- return '';
- }
-
- try {
- const date = new Date(dateString);
- if (Number.isNaN(date.getTime())) {
- return '';
- }
-
- const year = date.getFullYear();
- const month = date.getMonth() + 1;
- const day = date.getDate();
- const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
- const shortMonthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
-
- switch (format) {
- case 'M/dd/yyyy':
- return `${month}/${day.toString().padStart(2, '0')}/${year}`;
- case 'MMMM dd, yyyy':
- return `${monthNames.at(month - 1)} ${day.toString().padStart(2, '0')}, ${year}`;
- case 'dd MMM yyyy':
- return `${day.toString().padStart(2, '0')} ${shortMonthNames.at(month - 1)} ${year}`;
- case 'yyyy/MM/dd':
- return `${year}/${month.toString().padStart(2, '0')}/${day.toString().padStart(2, '0')}`;
- case 'MMMM, yyyy':
- return `${monthNames.at(month - 1)}, ${year}`;
- case 'yy/MM/dd':
- return `${year.toString().slice(-2)}/${month.toString().padStart(2, '0')}/${day.toString().padStart(2, '0')}`;
- case 'dd/MM/yy':
- return `${day.toString().padStart(2, '0')}/${month.toString().padStart(2, '0')}/${year.toString().slice(-2)}`;
- case 'yyyy':
- return year.toString();
- case 'MM/dd/yyyy':
- return `${month.toString().padStart(2, '0')}/${day.toString().padStart(2, '0')}/${year}`;
- case 'yyyy-MM-dd':
- default:
- return `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`;
- }
- } catch {
- return '';
- }
-}
-
/**
* Format an amount value
*/
diff --git a/src/libs/FormulaDatetime.ts b/src/libs/FormulaDatetime.ts
new file mode 100644
index 000000000000..2c2de2256f8f
--- /dev/null
+++ b/src/libs/FormulaDatetime.ts
@@ -0,0 +1,252 @@
+import {format as dateFnsFormat} from 'date-fns';
+
+/**
+ * Get ordinal suffix for a day (st, nd, rd, th)
+ */
+function getOrdinalSuffix(day: number): string {
+ if (day >= 11 && day <= 13) {
+ return 'th';
+ }
+ switch (day % 10) {
+ case 1:
+ return 'st';
+ case 2:
+ return 'nd';
+ case 3:
+ return 'rd';
+ default:
+ return 'th';
+ }
+}
+
+/**
+ * Calculate ISO week number for a given date
+ * Uses the "first Thursday" rule: Week 1 is the week containing the year's first Thursday
+ */
+function calculateISOWeekNumber(date: Date): number {
+ // Create a copy to avoid modifying the original date
+ const target = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
+
+ // Set to nearest Thursday: current date + 4 - current day number
+ // Make Sunday's day number 7 (getUTCDay returns 0 for Sunday)
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+ const dayNum = target.getUTCDay() || 7;
+ target.setUTCDate(target.getUTCDate() + 4 - dayNum);
+
+ // Get first day of year for the Thursday's year (in UTC)
+ const yearStart = new Date(Date.UTC(target.getUTCFullYear(), 0, 1));
+
+ // Calculate full weeks to nearest Thursday
+ const weekNum = Math.ceil(((target.getTime() - yearStart.getTime()) / 86400000 + 1) / 7);
+
+ return weekNum;
+}
+
+/**
+ * Calculate day of year (0-indexed)
+ */
+function calculateDayOfYear(date: Date): number {
+ const year = date.getFullYear();
+ const yearStart = new Date(year, 0, 1);
+ return Math.floor((date.getTime() - yearStart.getTime()) / (1000 * 60 * 60 * 24));
+}
+
+/**
+ * Get localized month and day names using date-fns
+ */
+function getLocalizedNames(date: Date) {
+ return {
+ fullMonthName: dateFnsFormat(date, 'MMMM'),
+ shortMonthName: dateFnsFormat(date, 'MMM'),
+ fullDayName: dateFnsFormat(date, 'EEEE'),
+ shortDayName: dateFnsFormat(date, 'EEE'),
+ };
+}
+
+/**
+ * Get time components in UTC timezone (to match Classic/OldDot behavior)
+ */
+function getUTCTimeComponents(date: Date) {
+ // Use UTC methods to match Classic behavior
+ const hours = date.getUTCHours();
+ const minutes = date.getUTCMinutes();
+ const seconds = date.getUTCSeconds();
+
+ let hours12 = hours;
+ if (hours === 0) {
+ hours12 = 12;
+ } else if (hours > 12) {
+ hours12 = hours - 12;
+ }
+
+ const meridiem = hours >= 12 ? 'pm' : 'am';
+ const meridiemUpperCase = hours >= 12 ? 'PM' : 'AM';
+
+ return {
+ hours,
+ minutes,
+ seconds,
+ hours12,
+ meridiem,
+ meridiemUpperCase,
+ };
+}
+
+/**
+ * Format date as RFC 2822/RFC 5322 format
+ * Example: "Thu, 08 Jan 2025 15:30:45 +0000"
+ */
+function formatRFC2822(date: Date): string {
+ const {shortDayName, shortMonthName} = getLocalizedNames(date);
+ const day = date.getDate().toString().padStart(2, '0');
+ const year = date.getFullYear();
+
+ const hours = date.getUTCHours().toString().padStart(2, '0');
+ const minutes = date.getUTCMinutes().toString().padStart(2, '0');
+ const seconds = date.getUTCSeconds().toString().padStart(2, '0');
+
+ // Get timezone offset in format +0000 or -0500
+ const timezoneOffset = -date.getTimezoneOffset();
+ const offsetSign = timezoneOffset >= 0 ? '+' : '-';
+ const offsetHours = Math.floor(Math.abs(timezoneOffset) / 60)
+ .toString()
+ .padStart(2, '0');
+ const offsetMinutes = (Math.abs(timezoneOffset) % 60).toString().padStart(2, '0');
+ const timezoneString = `${offsetSign}${offsetHours}${offsetMinutes}`;
+
+ return `${shortDayName}, ${day} ${shortMonthName} ${year} ${hours}:${minutes}:${seconds} ${timezoneString}`;
+}
+
+/**
+ * Create token definitions for date formatting
+ */
+function createDateTokens(date: Date): Array<{token: string; value: string}> {
+ const year = date.getFullYear();
+ const month = date.getMonth() + 1;
+ const day = date.getDate();
+ const dayOfWeek = date.getDay(); // 0 = Sunday, 6 = Saturday
+ const isoDayOfWeek = dayOfWeek === 0 ? 7 : dayOfWeek; // 1 = Monday, 7 = Sunday
+ const dayOfYear = calculateDayOfYear(date);
+ const daysInMonth = new Date(year, month, 0).getDate();
+ const weekNumber = calculateISOWeekNumber(date);
+
+ // Get localized names
+ const {fullMonthName, shortMonthName, fullDayName, shortDayName} = getLocalizedNames(date);
+
+ // Get time components in UTC timezone (to match Classic/OldDot behavior)
+ const {hours, minutes, seconds, hours12, meridiem, meridiemUpperCase} = getUTCTimeComponents(date);
+
+ return [
+ // Year formats (longest to shortest)
+ {token: 'yyyy', value: year.toString()},
+ {token: 'YYYY', value: year.toString()},
+ {token: 'yy', value: year.toString().slice(-2)},
+ {token: 'Y', value: year.toString()},
+ {token: 'y', value: year.toString().slice(-2)},
+
+ // Month formats (longest to shortest)
+ {token: 'MMMM', value: fullMonthName},
+ {token: 'MMM', value: shortMonthName},
+ {token: 'MM', value: month.toString().padStart(2, '0')},
+ {token: 'M', value: shortMonthName}, // Short textual representation (Jan-Dec)
+ {token: 'F', value: fullMonthName},
+ {token: 'n', value: month.toString()},
+
+ // Day formats (longest to shortest)
+ {token: 'dddd', value: fullDayName},
+ {token: 'ddd', value: shortDayName},
+ {token: 'dd', value: day.toString().padStart(2, '0')},
+ {token: 'd', value: day.toString().padStart(2, '0')},
+ {token: 'j', value: day.toString()},
+ {token: 'l', value: fullDayName},
+ {token: 'D', value: shortDayName},
+ {token: 'w', value: dayOfWeek.toString()},
+ {token: 'N', value: isoDayOfWeek.toString()},
+ {token: 'z', value: dayOfYear.toString()},
+ {token: 'W', value: weekNumber.toString().padStart(2, '0')},
+ {token: 'S', value: getOrdinalSuffix(day)},
+
+ // Time formats (longest to shortest)
+ {token: 'tt', value: meridiemUpperCase},
+ {token: 'hh', value: hours12.toString().padStart(2, '0')},
+ {token: 'HH', value: hours.toString().padStart(2, '0')},
+ {token: 'mm', value: minutes.toString().padStart(2, '0')},
+ {token: 'ss', value: seconds.toString().padStart(2, '0')},
+ {token: 'H', value: hours.toString().padStart(2, '0')}, // WITH leading zeros (00-23)
+ {token: 'h', value: hours12.toString().padStart(2, '0')}, // WITH leading zeros (01-12)
+ {token: 'G', value: hours.toString()}, // WITHOUT leading zeros (0-23)
+ {token: 'g', value: hours12.toString()}, // WITHOUT leading zeros (1-12)
+ {token: 'i', value: minutes.toString().padStart(2, '0')},
+ {token: 't', value: daysInMonth.toString()},
+ {token: 's', value: seconds.toString().padStart(2, '0')},
+ {token: 'A', value: meridiemUpperCase},
+ {token: 'a', value: meridiem},
+
+ // Full Date/Time formats
+ {token: 'c', value: date.toISOString()}, // ISO 8601: 2025-01-08T15:30:45.123Z
+ {token: 'r', value: formatRFC2822(date)}, // RFC 2822: Thu, 08 Jan 2025 15:30:45 +0000
+ {token: 'U', value: Math.floor(date.getTime() / 1000).toString()}, // Unix timestamp
+ ];
+}
+
+/**
+ * Apply two-phase token replacement to prevent conflicts
+ */
+function applyTokenReplacement(format: string, tokens: Array<{token: string; value: string}>): string {
+ let result = format;
+
+ // Sort tokens by length (longest first) to prevent conflicts
+ const sortedTokens = [...tokens].sort((a, b) => b.token.length - a.token.length);
+
+ // Phase 1: Replace tokens with unique placeholders
+ const placeholderMap: Record = {};
+ for (let i = 0; i < sortedTokens.length; i++) {
+ const tokenData = sortedTokens.at(i);
+ if (!tokenData) {
+ continue;
+ }
+
+ const {token, value} = tokenData;
+ const placeholder = `###${i.toString().padStart(3, '0')}###`;
+ const regex = new RegExp(token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g');
+
+ if (result.includes(token)) {
+ result = result.replace(regex, placeholder);
+ placeholderMap[placeholder] = value;
+ }
+ }
+
+ // Phase 2: Replace placeholders with actual values
+ for (const [placeholder, value] of Object.entries(placeholderMap)) {
+ result = result.replace(new RegExp(placeholder.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), value);
+ }
+
+ return result;
+}
+
+/**
+ * Format a date value with comprehensive token-based date format support
+ * Supports 40+ date format tokens with localization and timezone handling
+ */
+function formatDate(dateString: string | undefined, format = 'yyyy-MM-dd'): string {
+ if (!dateString) {
+ return '';
+ }
+
+ try {
+ const date = new Date(dateString);
+ if (Number.isNaN(date.getTime())) {
+ return '';
+ }
+
+ // Create tokens for the date
+ const tokens = createDateTokens(date);
+
+ // Apply token replacement
+ return applyTokenReplacement(format, tokens);
+ } catch {
+ return '';
+ }
+}
+
+export {formatDate, getOrdinalSuffix, calculateISOWeekNumber, calculateDayOfYear, getLocalizedNames, getUTCTimeComponents, createDateTokens, applyTokenReplacement};
diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx
index 3405e2a94fea..7ec799c93e9b 100644
--- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx
+++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx
@@ -6,6 +6,7 @@ import DelegateNoAccessModalProvider from '@components/DelegateNoAccessModalProv
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import {InitialURLContext} from '@components/InitialURLContextProvider';
import LockedAccountModalProvider from '@components/LockedAccountModalProvider';
+import OpenAppFailureModal from '@components/OpenAppFailureModal';
import OptionsListContextProvider from '@components/OptionListContextProvider';
import PriorityModeController from '@components/PriorityModeController';
import {SearchContextProvider} from '@components/Search/SearchContext';
@@ -691,6 +692,7 @@ function AuthScreens() {
/>
+
);
diff --git a/src/libs/Navigation/AppNavigator/createRootStackNavigator/RootStackRouter.ts b/src/libs/Navigation/AppNavigator/createRootStackNavigator/RootStackRouter.ts
index 3f8b77f9a859..30c7603edba5 100644
--- a/src/libs/Navigation/AppNavigator/createRootStackNavigator/RootStackRouter.ts
+++ b/src/libs/Navigation/AppNavigator/createRootStackNavigator/RootStackRouter.ts
@@ -1,7 +1,7 @@
import type {CommonActions, RouterConfigOptions, StackActionType, StackNavigationState} from '@react-navigation/native';
import {findFocusedRoute, StackRouter} from '@react-navigation/native';
import type {ParamListBase} from '@react-navigation/routers';
-import * as Localize from '@libs/Localize';
+import type {LocalizedTranslate} from '@components/LocaleContextProvider';
import {isFullScreenName, isOnboardingFlowName} from '@libs/Navigation/helpers/isNavigatorName';
import isSideModalNavigator from '@libs/Navigation/helpers/isSideModalNavigator';
import * as Welcome from '@userActions/Welcome';
@@ -51,7 +51,7 @@ function isPreloadAction(action: RootStackNavigatorAction): action is PreloadAct
return action.type === CONST.NAVIGATION.ACTION_TYPE.PRELOAD;
}
-function shouldPreventReset(state: StackNavigationState, action: CommonActions.Action | StackActionType) {
+function shouldPreventReset(state: StackNavigationState, action: CommonActions.Action | StackActionType, translate: LocalizedTranslate) {
if (action.type !== CONST.NAVIGATION_ACTIONS.RESET || !action?.payload) {
return false;
}
@@ -60,8 +60,7 @@ function shouldPreventReset(state: StackNavigationState, action:
// We want to prevent the user from navigating back to a non-onboarding screen if they are currently on an onboarding screen
if (isOnboardingFlowName(currentFocusedRoute?.name) && !isOnboardingFlowName(targetFocusedRoute?.name)) {
- // eslint-disable-next-line @typescript-eslint/no-deprecated
- Welcome.setOnboardingErrorMessage(Localize.translateLocal('onboarding.purpose.errorBackButton'));
+ Welcome.setOnboardingErrorMessage(translate('onboarding.purpose.errorBackButton'));
return true;
}
@@ -114,7 +113,7 @@ function RootStackRouter(options: RootStackNavigatorRouterOptions) {
}
// Don't let the user navigate back to a non-onboarding screen if they are currently on an onboarding screen and it's not finished.
- if (shouldPreventReset(state, action)) {
+ if (shouldPreventReset(state, action, options.translate)) {
syncBrowserHistory(state);
return state;
}
diff --git a/src/libs/Navigation/AppNavigator/createRootStackNavigator/types.ts b/src/libs/Navigation/AppNavigator/createRootStackNavigator/types.ts
index 155afa8c1399..7d157c6d9748 100644
--- a/src/libs/Navigation/AppNavigator/createRootStackNavigator/types.ts
+++ b/src/libs/Navigation/AppNavigator/createRootStackNavigator/types.ts
@@ -1,4 +1,5 @@
import type {CommonActions, StackActionType, StackRouterOptions} from '@react-navigation/native';
+import type {LocalizedTranslate} from '@components/LocaleContextProvider';
import type {WorkspaceScreenName} from '@libs/Navigation/types';
import type CONST from '@src/CONST';
@@ -48,7 +49,7 @@ type DismissModalActionType = RootStackNavigatorActionType & {
type: typeof CONST.NAVIGATION.ACTION_TYPE.DISMISS_MODAL;
};
-type RootStackNavigatorRouterOptions = StackRouterOptions;
+type RootStackNavigatorRouterOptions = StackRouterOptions & {translate: LocalizedTranslate};
type RootStackNavigatorAction = CommonActions.Action | StackActionType | RootStackNavigatorActionType;
diff --git a/src/libs/Navigation/PlatformStackNavigation/createPlatformStackNavigatorComponent/index.tsx b/src/libs/Navigation/PlatformStackNavigation/createPlatformStackNavigatorComponent/index.tsx
index 730e269d507a..fb3e563051e8 100644
--- a/src/libs/Navigation/PlatformStackNavigation/createPlatformStackNavigatorComponent/index.tsx
+++ b/src/libs/Navigation/PlatformStackNavigation/createPlatformStackNavigatorComponent/index.tsx
@@ -3,6 +3,7 @@ import {StackRouter, useNavigationBuilder} from '@react-navigation/native';
import type {StackNavigationEventMap, StackNavigationOptions} from '@react-navigation/stack';
import {StackView} from '@react-navigation/stack';
import React, {useMemo} from 'react';
+import useLocalize from '@hooks/useLocalize';
import {addCustomHistoryRouterExtension} from '@libs/Navigation/AppNavigator/customHistory';
import convertToWebNavigationOptions from '@libs/Navigation/PlatformStackNavigation/navigationOptions/convertToWebNavigationOptions';
import type {
@@ -37,6 +38,8 @@ function createPlatformStackNavigatorComponent) {
+ const {translate} = useLocalize();
+
const {
navigation,
state: originalState,
@@ -62,6 +65,7 @@ function createPlatformStackNavigatorComponent = (typeof SIDEBAR_TO_SPLIT)[T];
type BackToParams = {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
type ConsoleNavigatorParamList = {
[SCREENS.PUBLIC_CONSOLE_DEBUG]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes;
};
};
@@ -93,25 +95,31 @@ type SettingsNavigatorParamList = {
country?: Country | '';
};
[SCREENS.SETTINGS.PROFILE.ADDRESS_COUNTRY]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
country: string;
};
[SCREENS.SETTINGS.PROFILE.CONTACT_METHODS]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes;
};
[SCREENS.SETTINGS.PROFILE.CONTACT_METHOD_DETAILS]: {
contactMethod: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
shouldSkipInitialValidation?: string;
};
[SCREENS.SETTINGS.PROFILE.NEW_CONTACT_METHOD]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.SETTINGS.PROFILE.NEW_CONTACT_METHOD_CONFIRM_MAGIC_CODE]: {
newContactMethod: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.SETTINGS.PROFILE.CONTACT_METHOD_VERIFY_ACCOUNT]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
forwardTo?: Routes;
};
@@ -125,10 +133,12 @@ type SettingsNavigatorParamList = {
};
[SCREENS.SETTINGS.MERGE_ACCOUNTS.ACCOUNT_VALIDATE]: {
login: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
forwardTo?: Routes;
};
[SCREENS.SETTINGS.MERGE_ACCOUNTS.MERGE_RESULT]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
result: ValueOf;
login: string;
@@ -137,11 +147,13 @@ type SettingsNavigatorParamList = {
[SCREENS.SETTINGS.LOCK.UNLOCK_ACCOUNT]: undefined;
[SCREENS.SETTINGS.LOCK.FAILED_TO_LOCK_ACCOUNT]: undefined;
[SCREENS.SETTINGS.CONSOLE]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes;
};
[SCREENS.SETTINGS.SHARE_LOG]: {
/** URL of the generated file to share logs in a report */
source: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes;
};
[SCREENS.SETTINGS.WALLET.CARDS_DIGITAL_DETAILS_UPDATE_ADDRESS]: undefined;
@@ -156,6 +168,7 @@ type SettingsNavigatorParamList = {
[SCREENS.SETTINGS.WALLET.REPORT_VIRTUAL_CARD_FRAUD]: {
/** cardID of selected card */
cardID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.SETTINGS.WALLET.REPORT_VIRTUAL_CARD_FRAUD_CONFIRMATION]: {
@@ -190,6 +203,7 @@ type SettingsNavigatorParamList = {
[SCREENS.WORKSPACE.ADDRESS]: {
policyID: string;
country?: Country | '';
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.NAME]: undefined;
@@ -197,6 +211,7 @@ type SettingsNavigatorParamList = {
[SCREENS.WORKSPACE.SHARE]: undefined;
[SCREENS.WORKSPACE.INVITE]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.MEMBERS_IMPORT]: {
@@ -210,48 +225,58 @@ type SettingsNavigatorParamList = {
};
[SCREENS.WORKSPACE.INVITE_MESSAGE]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.INVITE_MESSAGE_ROLE]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.CATEGORY_CREATE]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.SETTINGS_CATEGORIES.SETTINGS_CATEGORY_CREATE]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.CATEGORY_EDIT]: {
policyID: string;
categoryName: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.SETTINGS_CATEGORIES.SETTINGS_CATEGORY_EDIT]: {
policyID: string;
categoryName: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.CATEGORY_PAYROLL_CODE]: {
policyID: string;
categoryName: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.SETTINGS_CATEGORIES.SETTINGS_CATEGORY_PAYROLL_CODE]: {
policyID: string;
categoryName: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.CATEGORY_GL_CODE]: {
policyID: string;
categoryName: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.SETTINGS_CATEGORIES.SETTINGS_CATEGORY_GL_CODE]: {
policyID: string;
categoryName: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.CATEGORY_DEFAULT_TAX_RATE]: {
@@ -277,21 +302,25 @@ type SettingsNavigatorParamList = {
[SCREENS.WORKSPACE.CATEGORY_SETTINGS]: {
policyID: string;
categoryName: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.SETTINGS_CATEGORIES.SETTINGS_CATEGORY_SETTINGS]: {
policyID: string;
categoryName: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.UPGRADE]: {
policyID?: string;
featureName?: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
categoryId?: string;
};
[SCREENS.WORKSPACE.DOWNGRADE]: {
policyID?: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.PAY_AND_DOWNGRADE]: {
@@ -299,34 +328,42 @@ type SettingsNavigatorParamList = {
};
[SCREENS.WORKSPACE.CATEGORIES_SETTINGS]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.SETTINGS_CATEGORIES.SETTINGS_CATEGORIES_SETTINGS]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.CATEGORIES_IMPORT]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.SETTINGS_CATEGORIES.SETTINGS_CATEGORIES_IMPORT]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.CATEGORIES_IMPORTED]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.SETTINGS_CATEGORIES.SETTINGS_CATEGORIES_IMPORTED]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.TAG_CREATE]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.SETTINGS_TAGS.SETTINGS_TAG_CREATE]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.DISTANCE_RATE_DETAILS]: {
@@ -351,41 +388,54 @@ type SettingsNavigatorParamList = {
};
[SCREENS.WORKSPACE.TAGS_SETTINGS]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.SETTINGS_TAGS.SETTINGS_TAGS_SETTINGS]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.TAGS_IMPORT]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.SETTINGS_TAGS.SETTINGS_TAGS_IMPORT]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.TAGS_IMPORT_OPTIONS]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.TAGS_IMPORT_MULTI_LEVEL_SETTINGS]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.TAGS_IMPORTED]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
+ backTo?: Routes;
+ };
+ [SCREENS.SETTINGS_TAGS.SETTINGS_TAGS_IMPORTED]: {
+ policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
- [SCREENS.SETTINGS_TAGS.SETTINGS_TAGS_IMPORTED]: {policyID: string; backTo?: Routes};
[SCREENS.WORKSPACE.TAGS_IMPORTED_MULTI_LEVEL]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.TAG_SETTINGS]: {
policyID: string;
orderWeight: number;
tagName: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
parentTagsFilter?: string;
};
@@ -393,63 +443,74 @@ type SettingsNavigatorParamList = {
policyID: string;
orderWeight: number;
tagName: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
parentTagsFilter?: string;
};
[SCREENS.WORKSPACE.TAG_LIST_VIEW]: {
policyID: string;
orderWeight: number;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.SETTINGS_TAGS.SETTINGS_TAG_LIST_VIEW]: {
policyID: string;
orderWeight: number;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.TAGS_EDIT]: {
policyID: string;
orderWeight: number;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.SETTINGS_TAGS.SETTINGS_TAGS_EDIT]: {
policyID: string;
orderWeight: number;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.TAG_EDIT]: {
policyID: string;
orderWeight: number;
tagName: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.SETTINGS_TAGS.SETTINGS_TAG_EDIT]: {
policyID: string;
orderWeight: number;
tagName: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.TAG_APPROVER]: {
policyID: string;
orderWeight: number;
tagName: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.SETTINGS_TAGS.SETTINGS_TAG_APPROVER]: {
policyID: string;
orderWeight: number;
tagName: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.TAG_GL_CODE]: {
policyID: string;
orderWeight: number;
tagName: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.SETTINGS_TAGS.SETTINGS_TAG_GL_CODE]: {
policyID: string;
orderWeight: number;
tagName: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.SETTINGS.SUBSCRIPTION.SIZE]: {
@@ -515,17 +576,20 @@ type SettingsNavigatorParamList = {
[SCREENS.WORKSPACE.OWNER_CHANGE_SUCCESS]: {
policyID: string;
accountID: number;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.OWNER_CHANGE_ERROR]: {
policyID: string;
accountID: number;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.OWNER_CHANGE_CHECK]: {
policyID: string;
accountID: number;
error: ValueOf;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.CREATE_DISTANCE_RATE]: {
@@ -556,26 +620,32 @@ type SettingsNavigatorParamList = {
};
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_EXPORT]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_EXPORT_DATE_SELECT]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_EXPORT_INVOICE_ACCOUNT_SELECT]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_EXPORT_OUT_OF_POCKET_EXPENSES_ACCOUNT_SELECT]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_EXPORT_OUT_OF_POCKET_EXPENSES]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_EXPORT_OUT_OF_POCKET_EXPENSES_SELECT]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_NON_REIMBURSABLE_DEFAULT_VENDOR_SELECT]: {
@@ -583,26 +653,32 @@ type SettingsNavigatorParamList = {
};
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT_SELECT]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT_COMPANY_CARD_SELECT]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_EXPORT_PREFERRED_EXPORTER]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_DESKTOP_COMPANY_CARD_EXPENSE_ACCOUNT_SELECT]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_DESKTOP_COMPANY_CARD_EXPENSE_ACCOUNT_COMPANY_CARD_SELECT]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_DESKTOP_NON_REIMBURSABLE_DEFAULT_VENDOR_SELECT]: {
@@ -610,34 +686,42 @@ type SettingsNavigatorParamList = {
};
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_DESKTOP_COMPANY_CARD_EXPENSE_ACCOUNT]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_DESKTOP_ADVANCED]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_DESKTOP_EXPORT_DATE_SELECT]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_DESKTOP_EXPORT_PREFERRED_EXPORTER]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_DESKTOP_EXPORT_OUT_OF_POCKET_EXPENSES_ACCOUNT_SELECT]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_DESKTOP_EXPORT_OUT_OF_POCKET_EXPENSES]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_DESKTOP_EXPORT_OUT_OF_POCKET_EXPENSES_SELECT]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_DESKTOP_EXPORT]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_DESKTOP_SETUP_MODAL]: {
@@ -693,10 +777,12 @@ type SettingsNavigatorParamList = {
};
[SCREENS.WORKSPACE.ACCOUNTING.XERO_EXPORT]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.XERO_EXPORT_PURCHASE_BILL_DATE_SELECT]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.XERO_ADVANCED]: {
@@ -704,10 +790,12 @@ type SettingsNavigatorParamList = {
};
[SCREENS.WORKSPACE.ACCOUNTING.XERO_BILL_STATUS_SELECTOR]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.XERO_EXPORT_BANK_ACCOUNT_SELECT]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.XERO_INVOICE_ACCOUNT_SELECTOR]: {
@@ -715,6 +803,7 @@ type SettingsNavigatorParamList = {
};
[SCREENS.WORKSPACE.ACCOUNTING.XERO_EXPORT_PREFERRED_EXPORTER_SELECT]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.XERO_BILL_PAYMENT_ACCOUNT_SELECTOR]: {
@@ -722,6 +811,7 @@ type SettingsNavigatorParamList = {
};
[SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_PREREQUISITES]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.ENTER_SAGE_INTACCT_CREDENTIALS]: {
@@ -778,47 +868,57 @@ type SettingsNavigatorParamList = {
};
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_EXPORT]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_PREFERRED_EXPORTER_SELECT]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_DATE_SELECT]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_EXPORT_EXPENSES]: {
policyID: string;
expenseType: ValueOf;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_EXPORT_EXPENSES_DESTINATION_SELECT]: {
policyID: string;
expenseType: ValueOf;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_EXPORT_EXPENSES_VENDOR_SELECT]: {
policyID: string;
expenseType: ValueOf;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_EXPORT_EXPENSES_PAYABLE_ACCOUNT_SELECT]: {
policyID: string;
expenseType: ValueOf;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_EXPORT_EXPENSES_JOURNAL_POSTING_PREFERENCE_SELECT]: {
policyID: string;
expenseType: ValueOf;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_RECEIVABLE_ACCOUNT_SELECT]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_INVOICE_ITEM_PREFERENCE_SELECT]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_INVOICE_ITEM_SELECT]: {
@@ -881,39 +981,48 @@ type SettingsNavigatorParamList = {
};
[SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_EXPORT]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_PREFERRED_EXPORTER]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_EXPORT_DATE]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_REIMBURSABLE_EXPENSES]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_NON_REIMBURSABLE_EXPENSES]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_REIMBURSABLE_DESTINATION]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_NON_REIMBURSABLE_DESTINATION]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_DEFAULT_VENDOR]: {
policyID: string;
reimbursable: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_NON_REIMBURSABLE_CREDIT_CARD_ACCOUNT]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_ADVANCED]: {
@@ -937,6 +1046,7 @@ type SettingsNavigatorParamList = {
[SCREENS.SETTINGS.DELEGATE.DELEGATE_ROLE]: {
login: string;
role?: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.SETTINGS.DELEGATE.UPDATE_DELEGATE_ROLE]: {
@@ -961,14 +1071,17 @@ type SettingsNavigatorParamList = {
cardID: string;
};
[SCREENS.KEYBOARD_SHORTCUTS]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes;
};
[SCREENS.SETTINGS.EXIT_SURVEY.REASON]: undefined;
[SCREENS.SETTINGS.EXIT_SURVEY.RESPONSE]: {
[EXIT_SURVEY_REASON_FORM_INPUT_IDS.REASON]: ValueOf;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes;
};
[SCREENS.SETTINGS.EXIT_SURVEY.CONFIRM]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes;
};
[SCREENS.WORKSPACE.TAX_CREATE]: {
@@ -1005,12 +1118,14 @@ type SettingsNavigatorParamList = {
[SCREENS.WORKSPACE.COMPANY_CARDS_BANK_CONNECTION]: {
policyID: string;
bankName: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes;
};
[SCREENS.WORKSPACE.COMPANY_CARD_DETAILS]: {
policyID: string;
bank: CompanyCardFeed;
cardID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.COMPANY_CARD_NAME]: {
@@ -1022,10 +1137,12 @@ type SettingsNavigatorParamList = {
policyID: string;
cardID: string;
bank: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.EXPENSIFY_CARD_ISSUE_NEW]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.EXPENSIFY_CARD_BANK_ACCOUNT]: {
@@ -1036,6 +1153,7 @@ type SettingsNavigatorParamList = {
};
[SCREENS.WORKSPACE.EXPENSIFY_CARD_SETTINGS_ACCOUNT]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.EXPENSIFY_CARD_SETTINGS_FREQUENCY]: {
@@ -1047,6 +1165,7 @@ type SettingsNavigatorParamList = {
[SCREENS.WORKSPACE.COMPANY_CARDS_ASSIGN_CARD]: {
policyID: string;
feed: CompanyCardFeed;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.COMPANY_CARDS_SETTINGS_FEED_NAME]: {
@@ -1058,41 +1177,49 @@ type SettingsNavigatorParamList = {
[SCREENS.WORKSPACE.EXPENSIFY_CARD_DETAILS]: {
policyID: string;
cardID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.EXPENSIFY_CARD_NAME]: {
policyID: string;
cardID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.EXPENSIFY_CARD_LIMIT]: {
policyID: string;
cardID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.EXPENSIFY_CARD_LIMIT_TYPE]: {
policyID: string;
cardID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.EXPENSIFY_CARD.EXPENSIFY_CARD_DETAILS]: {
policyID: string;
cardID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.EXPENSIFY_CARD.EXPENSIFY_CARD_NAME]: {
policyID: string;
cardID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.EXPENSIFY_CARD.EXPENSIFY_CARD_LIMIT]: {
policyID: string;
cardID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.EXPENSIFY_CARD.EXPENSIFY_CARD_LIMIT_TYPE]: {
policyID: string;
cardID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.REPORTS_DEFAULT_TITLE]: {
@@ -1166,18 +1293,22 @@ type SettingsNavigatorParamList = {
type TwoFactorAuthNavigatorParamList = {
[SCREENS.TWO_FACTOR_AUTH.ROOT]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
forwardTo?: string;
};
[SCREENS.TWO_FACTOR_AUTH.VERIFY_ACCOUNT]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
forwardTo?: Routes;
};
[SCREENS.TWO_FACTOR_AUTH.VERIFY]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
forwardTo?: string;
};
[SCREENS.TWO_FACTOR_AUTH.SUCCESS]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
forwardTo?: string;
};
@@ -1203,6 +1334,7 @@ type ProfileNavigatorParamList = {
accountID: string;
reportID: string;
login?: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes;
};
};
@@ -1210,12 +1342,14 @@ type ProfileNavigatorParamList = {
type NewReportWorkspaceSelectionNavigatorParamList = {
[SCREENS.NEW_REPORT_WORKSPACE_SELECTION.ROOT]: {
isMovingExpenses?: boolean;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
};
type SetDefaultWorkspaceNavigatorParamList = {
[SCREENS.SET_DEFAULT_WORKSPACE.ROOT]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
};
@@ -1223,16 +1357,19 @@ type SetDefaultWorkspaceNavigatorParamList = {
type ReportDetailsNavigatorParamList = {
[SCREENS.REPORT_DETAILS.ROOT]: {
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.REPORT_DETAILS.SHARE_CODE]: {
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.REPORT_DETAILS.EXPORT]: {
reportID: string;
policyID: string;
connectionName: ConnectionName;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
};
@@ -1240,6 +1377,7 @@ type ReportDetailsNavigatorParamList = {
type ReportChangeWorkspaceNavigatorParamList = {
[SCREENS.REPORT_CHANGE_WORKSPACE.ROOT]: {
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
};
@@ -1247,22 +1385,27 @@ type ReportChangeWorkspaceNavigatorParamList = {
type ReportSettingsNavigatorParamList = {
[SCREENS.REPORT_SETTINGS.ROOT]: {
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.REPORT_SETTINGS.NAME]: {
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.REPORT_SETTINGS.NOTIFICATION_PREFERENCES]: {
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.REPORT_SETTINGS.WRITE_CAPABILITY]: {
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.REPORT_SETTINGS.VISIBILITY]: {
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
};
@@ -1270,6 +1413,7 @@ type ReportSettingsNavigatorParamList = {
type ReportDescriptionNavigatorParamList = {
[SCREENS.REPORT_DESCRIPTION_ROOT]: {
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
};
@@ -1277,20 +1421,24 @@ type ReportDescriptionNavigatorParamList = {
type ParticipantsNavigatorParamList = {
[SCREENS.REPORT_PARTICIPANTS.ROOT]: {
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.REPORT_PARTICIPANTS.INVITE]: {
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.REPORT_PARTICIPANTS.DETAILS]: {
reportID: string;
accountID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.REPORT_PARTICIPANTS.ROLE]: {
reportID: string;
accountID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
};
@@ -1298,16 +1446,19 @@ type ParticipantsNavigatorParamList = {
type RoomMembersNavigatorParamList = {
[SCREENS.ROOM_MEMBERS.ROOT]: {
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.ROOM_MEMBERS.INVITE]: {
reportID: string;
role?: 'accountant';
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.ROOM_MEMBERS.DETAILS]: {
reportID: string;
accountID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
};
@@ -1317,12 +1468,14 @@ type MoneyRequestNavigatorParamList = {
iouType: IOUType;
transactionID: string;
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes;
};
[SCREENS.MONEY_REQUEST.EDIT_REPORT]: {
action: IOUAction;
iouType: IOUType;
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes;
shouldTurnOffSelectionMode?: boolean;
};
@@ -1331,6 +1484,7 @@ type MoneyRequestNavigatorParamList = {
iouType: IOUType;
transactionID: string;
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes;
reportActionID?: string;
};
@@ -1338,6 +1492,7 @@ type MoneyRequestNavigatorParamList = {
iouType: IOUType;
transactionID: string;
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes;
};
[SCREENS.MONEY_REQUEST.STEP_PARTICIPANTS]: {
@@ -1345,6 +1500,7 @@ type MoneyRequestNavigatorParamList = {
iouType: Exclude;
transactionID: string;
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes;
};
[SCREENS.MONEY_REQUEST.STEP_DATE]: {
@@ -1352,6 +1508,7 @@ type MoneyRequestNavigatorParamList = {
iouType: Exclude;
transactionID: string;
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes;
reportActionID?: string;
};
@@ -1360,6 +1517,7 @@ type MoneyRequestNavigatorParamList = {
iouType: Exclude;
transactionID: string;
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes;
reportActionID: string;
};
@@ -1369,6 +1527,7 @@ type MoneyRequestNavigatorParamList = {
transactionID: string;
reportActionID: string;
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes;
};
[SCREENS.MONEY_REQUEST.STEP_TAX_AMOUNT]: {
@@ -1376,6 +1535,7 @@ type MoneyRequestNavigatorParamList = {
iouType: Exclude;
transactionID: string;
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes;
currency?: string;
};
@@ -1384,6 +1544,7 @@ type MoneyRequestNavigatorParamList = {
iouType: Exclude;
transactionID: string;
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes;
reportActionID: string;
orderWeight: string;
@@ -1393,11 +1554,14 @@ type MoneyRequestNavigatorParamList = {
iouType: Exclude;
transactionID: string;
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes;
};
[SCREENS.MONEY_REQUEST.STEP_WAYPOINT]: {
iouType: IOUType;
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes | undefined;
action: IOUAction;
pageIndex: string;
@@ -1408,6 +1572,7 @@ type MoneyRequestNavigatorParamList = {
iouType: Exclude;
transactionID: string;
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes;
reportActionID?: string;
};
@@ -1419,6 +1584,7 @@ type MoneyRequestNavigatorParamList = {
iouType: IOUType;
transactionID: string;
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes;
backToReport?: string;
reportActionID?: string;
@@ -1428,6 +1594,7 @@ type MoneyRequestNavigatorParamList = {
iouType: IOUType;
transactionID: string;
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes;
backToReport?: string;
reportActionID?: string;
@@ -1437,6 +1604,7 @@ type MoneyRequestNavigatorParamList = {
iouType: IOUType;
transactionID: string;
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes;
backToReport?: string;
reportActionID?: string;
@@ -1448,6 +1616,7 @@ type MoneyRequestNavigatorParamList = {
// These are not used in the screen, but are needed for the navigation
// for IOURequestStepDistance and IOURequestStepAmount components
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: never;
action: never;
currency: never;
@@ -1465,6 +1634,7 @@ type MoneyRequestNavigatorParamList = {
iouType: IOUType;
reportID: string;
transactionID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes;
action: IOUAction;
pageIndex?: string;
@@ -1476,6 +1646,7 @@ type MoneyRequestNavigatorParamList = {
action: IOUAction;
iouType: ValueOf;
transactionID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes;
reportID: string;
reportActionID?: string;
@@ -1486,6 +1657,7 @@ type MoneyRequestNavigatorParamList = {
transactionID: string;
reportID: string;
pageIndex?: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
participantsAutoAssigned?: string;
backToReport?: string;
@@ -1496,11 +1668,13 @@ type MoneyRequestNavigatorParamList = {
transactionID: string;
reportID: string;
pageIndex: number;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes;
backToReport?: string;
};
[SCREENS.MONEY_REQUEST.RECEIPT_VIEW]: {
transactionID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes;
};
[SCREENS.MONEY_REQUEST.STEP_CURRENCY]: {
@@ -1509,6 +1683,7 @@ type MoneyRequestNavigatorParamList = {
transactionID: string;
reportID: string;
pageIndex?: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
currency?: string;
};
@@ -1520,6 +1695,7 @@ type MoneyRequestNavigatorParamList = {
reportID: string;
/** Link to previous page */
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: ExpensifyRoute;
/** Hash that includes info about what is searched for */
@@ -1533,6 +1709,7 @@ type MoneyRequestNavigatorParamList = {
reportID: string;
/** Link to previous page */
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: ExpensifyRoute;
};
[SCREENS.MONEY_REQUEST.STEP_ATTENDEES]: {
@@ -1540,6 +1717,7 @@ type MoneyRequestNavigatorParamList = {
iouType: Exclude;
transactionID: string;
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes;
};
[SCREENS.MONEY_REQUEST.STEP_ACCOUNTANT]: {
@@ -1547,6 +1725,7 @@ type MoneyRequestNavigatorParamList = {
iouType: Exclude;
transactionID: string;
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes;
};
[SCREENS.MONEY_REQUEST.STEP_UPGRADE]: {
@@ -1554,6 +1733,7 @@ type MoneyRequestNavigatorParamList = {
iouType: Exclude;
transactionID: string;
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes;
upgradePath?: ValueOf;
shouldSubmitExpense?: boolean;
@@ -1563,6 +1743,7 @@ type MoneyRequestNavigatorParamList = {
iouType: Exclude;
transactionID: string;
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes | undefined;
};
[SCREENS.MONEY_REQUEST.STEP_TIME]: {
@@ -1570,11 +1751,13 @@ type MoneyRequestNavigatorParamList = {
iouType: Exclude;
transactionID: string;
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes | undefined;
};
[SCREENS.MONEY_REQUEST.STEP_SUBRATE]: {
iouType: Exclude;
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes | undefined;
action: IOUAction;
pageIndex: string;
@@ -1585,6 +1768,7 @@ type MoneyRequestNavigatorParamList = {
iouType: Exclude;
transactionID: string;
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes | undefined;
};
[SCREENS.MONEY_REQUEST.STEP_TIME_EDIT]: {
@@ -1592,11 +1776,13 @@ type MoneyRequestNavigatorParamList = {
iouType: Exclude;
transactionID: string;
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes | undefined;
};
[SCREENS.MONEY_REQUEST.STEP_SUBRATE_EDIT]: {
iouType: Exclude;
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes | undefined;
action: IOUAction;
pageIndex: string;
@@ -1609,6 +1795,7 @@ type MoneyRequestNavigatorParamList = {
// These are not used in the screen, but are needed for the navigation
// for IOURequestStepDistanceMap component
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: never;
action: never;
currency: never;
@@ -1620,9 +1807,11 @@ type MoneyRequestNavigatorParamList = {
type WorkspaceConfirmationNavigatorParamList = {
[SCREENS.WORKSPACE_CONFIRMATION.ROOT]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.CURRENCY.SELECTION]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
};
@@ -1638,19 +1827,24 @@ type WorkspaceDuplicateNavigatorParamList = {
type NewTaskNavigatorParamList = {
[SCREENS.NEW_TASK.ROOT]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.NEW_TASK.TASK_ASSIGNEE_SELECTOR]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.NEW_TASK.TASK_SHARE_DESTINATION_SELECTOR]: undefined;
[SCREENS.NEW_TASK.DETAILS]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.NEW_TASK.TITLE]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.NEW_TASK.DESCRIPTION]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
};
@@ -1664,10 +1858,12 @@ type TeachersUniteNavigatorParamList = {
type TaskDetailsNavigatorParamList = {
[SCREENS.TASK.TITLE]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.TASK.ASSIGNEE]: {
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
};
@@ -1680,6 +1876,7 @@ type SplitDetailsNavigatorParamList = {
[SCREENS.SPLIT_DETAILS.ROOT]: {
reportID: string;
reportActionID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.SPLIT_DETAILS.EDIT_REQUEST]: {
@@ -1698,6 +1895,7 @@ type AddPersonalBankAccountNavigatorParamList = {
type ReimbursementAccountNavigatorParamList = {
[SCREENS.REIMBURSEMENT_ACCOUNT_ROOT]: {
stepToOpen?: ReimbursementAccountStepToOpen;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
policyID?: string;
};
@@ -1722,6 +1920,7 @@ type FlagCommentNavigatorParamList = {
[SCREENS.FLAG_COMMENT_ROOT]: {
reportID: string;
reportActionID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
};
@@ -1731,6 +1930,7 @@ type EditRequestNavigatorParamList = {
fieldID: string;
reportID: string;
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
};
@@ -1749,17 +1949,20 @@ type FeatureTrainingNavigatorParamList = {
type ReferralDetailsNavigatorParamList = {
[SCREENS.REFERRAL_DETAILS]: {
contentType: ValueOf;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: string;
};
};
type PrivateNotesNavigatorParamList = {
[SCREENS.PRIVATE_NOTES.LIST]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.PRIVATE_NOTES.EDIT]: {
reportID: string;
accountID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
};
@@ -1767,34 +1970,42 @@ type PrivateNotesNavigatorParamList = {
type TransactionDuplicateNavigatorParamList = {
[SCREENS.TRANSACTION_DUPLICATE.REVIEW]: {
threadReportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.TRANSACTION_DUPLICATE.MERCHANT]: {
threadReportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.TRANSACTION_DUPLICATE.CATEGORY]: {
threadReportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.TRANSACTION_DUPLICATE.TAG]: {
threadReportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.TRANSACTION_DUPLICATE.DESCRIPTION]: {
threadReportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.TRANSACTION_DUPLICATE.TAX_CODE]: {
threadReportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.TRANSACTION_DUPLICATE.BILLABLE]: {
threadReportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.TRANSACTION_DUPLICATE.REIMBURSABLE]: {
threadReportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
};
@@ -1802,18 +2013,22 @@ type TransactionDuplicateNavigatorParamList = {
type MergeTransactionNavigatorParamList = {
[SCREENS.MERGE_TRANSACTION.LIST_PAGE]: {
transactionID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.MERGE_TRANSACTION.RECEIPT_PAGE]: {
transactionID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.MERGE_TRANSACTION.DETAILS_PAGE]: {
transactionID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.MERGE_TRANSACTION.CONFIRMATION_PAGE]: {
transactionID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
};
@@ -1877,6 +2092,7 @@ type TravelNavigatorParamList = {
[SCREENS.TRAVEL.TRIP_SUMMARY]: {
reportID: string;
transactionID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: string;
};
[SCREENS.TRAVEL.TRIP_DETAILS]: {
@@ -1884,6 +2100,7 @@ type TravelNavigatorParamList = {
transactionID: string;
sequenceIndex: number;
pnr: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: string;
};
[SCREENS.TRAVEL.TCS]: {
@@ -1894,15 +2111,19 @@ type TravelNavigatorParamList = {
};
[SCREENS.TRAVEL.WORKSPACE_ADDRESS]: {
domain: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.TRAVEL.PUBLIC_DOMAIN_ERROR]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.TRAVEL.UPGRADE]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.TRAVEL.DOMAIN_SELECTOR]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.TRAVEL.VERIFY_ACCOUNT]: {
@@ -1917,6 +2138,7 @@ type ReportsSplitNavigatorParamList = {
reportActionID?: string;
openOnAdminRoom?: boolean;
referrer?: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.REPORT_ATTACHMENTS]: AttachmentModalScreensParamList[typeof SCREENS.REPORT_ATTACHMENTS];
@@ -1927,22 +2149,28 @@ type SettingsSplitNavigatorParamList = {
[SCREENS.SETTINGS.PREFERENCES.ROOT]: undefined;
[SCREENS.SETTINGS.SECURITY]: undefined;
[SCREENS.SETTINGS.PROFILE.ROOT]?: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.SETTINGS.WALLET.ROOT]: undefined;
[SCREENS.SETTINGS.ABOUT]: undefined;
[SCREENS.SETTINGS.TROUBLESHOOT]: undefined;
[SCREENS.SETTINGS.SAVE_THE_WORLD]: undefined;
- [SCREENS.SETTINGS.SUBSCRIPTION.ROOT]?: {backTo?: Routes};
+ [SCREENS.SETTINGS.SUBSCRIPTION.ROOT]?: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
+ backTo?: Routes;
+ };
};
type WorkspaceSplitNavigatorParamList = {
[SCREENS.WORKSPACE.INITIAL]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.PROFILE]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.EXPENSIFY_CARD]: {
@@ -1957,6 +2185,7 @@ type WorkspaceSplitNavigatorParamList = {
[SCREENS.WORKSPACE.RECEIPT_PARTNERS_INVITE]: {
policyID: string;
integration: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.RECEIPT_PARTNERS_CHANGE_BILLING_ACCOUNT]: {
@@ -1967,15 +2196,18 @@ type WorkspaceSplitNavigatorParamList = {
[SCREENS.WORKSPACE.RECEIPT_PARTNERS_INVITE_EDIT]: {
policyID: string;
integration: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.COMPANY_CARDS_TRANSACTION_START_DATE]: {
policyID: string;
feed: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.PER_DIEM]: {
@@ -1986,6 +2218,7 @@ type WorkspaceSplitNavigatorParamList = {
};
[SCREENS.WORKSPACE.WORKFLOWS_APPROVALS_NEW]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.WORKFLOWS_APPROVALS_EDIT]: {
@@ -1994,11 +2227,13 @@ type WorkspaceSplitNavigatorParamList = {
};
[SCREENS.WORKSPACE.WORKFLOWS_APPROVALS_EXPENSES_FROM]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.WORKFLOWS_APPROVALS_APPROVER]: {
policyID: string;
approverIndex: number;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_FREQUENCY]: {
@@ -2021,10 +2256,12 @@ type WorkspaceSplitNavigatorParamList = {
};
[SCREENS.WORKSPACE.CATEGORIES]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.SETTINGS_CATEGORIES.SETTINGS_CATEGORIES_ROOT]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.MORE_FEATURES]: {
@@ -2032,10 +2269,12 @@ type WorkspaceSplitNavigatorParamList = {
};
[SCREENS.WORKSPACE.TAGS]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.SETTINGS_TAGS.SETTINGS_TAGS_ROOT]: {
policyID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE.TAXES]: {
@@ -2078,42 +2317,55 @@ type WorkspaceSplitNavigatorParamList = {
type OnboardingModalNavigatorParamList = {
[SCREENS.ONBOARDING.PERSONAL_DETAILS]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: string;
};
[SCREENS.ONBOARDING.PRIVATE_DOMAIN]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: string;
};
[SCREENS.ONBOARDING.WORKSPACES]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: string;
};
[SCREENS.ONBOARDING.PURPOSE]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: string;
};
[SCREENS.ONBOARDING.EMPLOYEES]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: string;
};
[SCREENS.ONBOARDING.ACCOUNTING]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: string;
};
[SCREENS.ONBOARDING.INTERESTED_FEATURES]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: string;
};
[SCREENS.ONBOARDING.WORK_EMAIL]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: string;
};
[SCREENS.ONBOARDING.WORK_EMAIL_VALIDATION]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: string;
};
[SCREENS.ONBOARDING.WORKSPACE_OPTIONAL]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: string;
};
[SCREENS.ONBOARDING.WORKSPACE_CONFIRMATION]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: string;
};
[SCREENS.ONBOARDING.WORKSPACE_CURRENCY]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: string;
};
[SCREENS.ONBOARDING.WORKSPACE_INVITE]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: string;
};
};
@@ -2216,6 +2468,7 @@ type AttachmentModalScreensParamList = {
};
[SCREENS.PROFILE_AVATAR]: AttachmentModalContainerModalProps & {
accountID: number;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE_AVATAR]: AttachmentModalContainerModalProps & {
@@ -2250,6 +2503,7 @@ type AuthScreensParamList = SharedScreensParamList &
[SCREENS.TRACK_EXPENSE]: undefined;
[SCREENS.SUBMIT_EXPENSE]: undefined;
[SCREENS.WORKSPACES_LIST]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.WORKSPACE_JOIN_USER]: {
@@ -2280,6 +2534,7 @@ type SearchReportParamList = {
[SCREENS.SEARCH.REPORT_RHP]: {
reportID: string;
reportActionID?: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.SEARCH.TRANSACTION_HOLD_REASON_RHP]: {
@@ -2290,6 +2545,7 @@ type SearchReportParamList = {
reportID: string;
/** Link to previous page */
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: ExpensifyRoute;
/** Hash that includes info about what is searched for */
@@ -2297,6 +2553,7 @@ type SearchReportParamList = {
};
[SCREENS.SEARCH.MONEY_REQUEST_REPORT_HOLD_TRANSACTIONS]: {
/** Link to previous page */
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo: Routes;
/** Selected transactions' report ID */
reportID: string;
@@ -2311,6 +2568,7 @@ type SearchFullscreenNavigatorParamList = {
};
[SCREENS.SEARCH.MONEY_REQUEST_REPORT]: {
reportID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
};
@@ -2338,12 +2596,14 @@ type SplitExpenseParamList = {
reportID: string;
transactionID: string;
splitExpenseTransactionID?: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.MONEY_REQUEST.SPLIT_EXPENSE_EDIT]: {
reportID: string;
transactionID: string;
splitExpenseTransactionID: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
};
@@ -2371,11 +2631,13 @@ type DebugParamList = {
fieldName: string;
fieldValue?: string;
policyID?: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: string;
};
[SCREENS.DEBUG.DETAILS_DATE_TIME_PICKER_PAGE]: {
fieldName: string;
fieldValue?: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: string;
};
[SCREENS.DEBUG.TRANSACTION]: {
@@ -2410,6 +2672,7 @@ type ReportChangeApproverParamList = {
type TestToolsModalModalNavigatorParamList = {
[SCREENS.TEST_TOOLS_MODAL.ROOT]: {
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
};
diff --git a/src/libs/Network/SequentialQueue.ts b/src/libs/Network/SequentialQueue.ts
index efcba7a1e3be..86aa65cd96fa 100644
--- a/src/libs/Network/SequentialQueue.ts
+++ b/src/libs/Network/SequentialQueue.ts
@@ -1,5 +1,6 @@
import type {OnyxUpdate} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
+import {setIsOpenAppFailureModalOpen} from '@libs/actions/isOpenAppFailureModalOpen';
import {
deleteRequestsByIndices as deletePersistedRequestsByIndices,
endRequestAndRemoveFromQueue as endPersistedRequestAndRemoveFromQueue,
@@ -191,6 +192,9 @@ function process(): Promise {
Log.info('[SequentialQueue] Removing persisted request because it failed too many times.', false, {error, request: requestToProcess});
endPersistedRequestAndRemoveFromQueue(requestToProcess);
sequentialQueueRequestThrottle.clear();
+ if (requestToProcess.command === WRITE_COMMANDS.OPEN_APP) {
+ setIsOpenAppFailureModalOpen(true);
+ }
return process();
});
});
diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts
index a0e3a7ecbb59..6468318bc2bc 100644
--- a/src/libs/NextStepUtils.ts
+++ b/src/libs/NextStepUtils.ts
@@ -137,6 +137,20 @@ function buildOptimisticNextStepForPreventSelfApprovalsEnabled() {
return optimisticNextStep;
}
+function buildOptimisticNextStepForStrictPolicyRuleViolations() {
+ const optimisticNextStep: ReportNextStep = {
+ type: 'alert',
+ icon: CONST.NEXT_STEP.ICONS.HOURGLASS,
+ message: [
+ {
+ text: 'Waiting for you to fix the issues. Your admins have restricted submission of expenses with violations.',
+ },
+ ],
+ };
+
+ return optimisticNextStep;
+}
+
/**
* Please don't use this function anymore, let's use buildNextStepNew instead
*
@@ -882,5 +896,6 @@ export {
// eslint-disable-next-line @typescript-eslint/no-deprecated
buildNextStep,
buildOptimisticNextStepForPreventSelfApprovalsEnabled,
+ buildOptimisticNextStepForStrictPolicyRuleViolations,
buildNextStepNew,
};
diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts
index 7739cd221546..a74597f286fb 100644
--- a/src/libs/PersonalDetailsUtils.ts
+++ b/src/libs/PersonalDetailsUtils.ts
@@ -36,23 +36,6 @@ Onyx.connect({
},
});
-let hiddenTranslation = '';
-let youTranslation = '';
-
-Onyx.connect({
- key: ONYXKEYS.ARE_TRANSLATIONS_LOADING,
- initWithStoredValues: false,
- callback: (value) => {
- if (value ?? true) {
- return;
- }
- // eslint-disable-next-line @typescript-eslint/no-deprecated
- hiddenTranslation = translateLocal('common.hidden');
- // eslint-disable-next-line @typescript-eslint/no-deprecated
- youTranslation = translateLocal('common.you').toLowerCase();
- },
-});
-
const regexMergedAccount = new RegExp(CONST.REGEX.MERGED_ACCOUNT_PREFIX);
function getDisplayNameOrDefault(
@@ -60,7 +43,8 @@ function getDisplayNameOrDefault(
defaultValue = '',
shouldFallbackToHidden = true,
shouldAddCurrentUserPostfix = false,
- youAfterTranslation = youTranslation,
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
+ youAfterTranslation = translateLocal('common.you').toLowerCase(),
): string {
let displayName = passedPersonalDetails?.displayName ?? '';
@@ -100,8 +84,8 @@ function getDisplayNameOrDefault(
if (login) {
return login;
}
-
- return shouldFallbackToHidden ? hiddenTranslation : '';
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
+ return shouldFallbackToHidden ? translateLocal('common.hidden') : '';
}
/**
@@ -151,12 +135,12 @@ function getPersonalDetailByEmail(email: string): PersonalDetails | undefined {
* @param logins Array of user logins
* @returns Array of accountIDs according to passed logins
*/
-function getAccountIDsByLogins(logins: string[], shouldGenerateAccountID = true): number[] {
+function getAccountIDsByLogins(logins: string[]): number[] {
return logins.reduce((foundAccountIDs, login) => {
const currentDetail = personalDetails.find((detail) => detail?.login === login?.toLowerCase());
if (!currentDetail) {
// generate an account ID because in this case the detail is probably new, so we don't have a real accountID yet
- foundAccountIDs.push(shouldGenerateAccountID ? generateAccountID(login) : -1);
+ foundAccountIDs.push(generateAccountID(login));
} else {
foundAccountIDs.push(Number(currentDetail.accountID));
}
diff --git a/src/libs/ReportPreviewActionUtils.ts b/src/libs/ReportPreviewActionUtils.ts
index cbedb77265a1..6da0c7401933 100644
--- a/src/libs/ReportPreviewActionUtils.ts
+++ b/src/libs/ReportPreviewActionUtils.ts
@@ -26,6 +26,7 @@ import {
isReportManuallyReimbursed,
isSettled,
requiresManualSubmission,
+ shouldBlockSubmitDueToStrictPolicyRules,
} from './ReportUtils';
import {getSession} from './SessionUtils';
import {allHavePendingRTERViolation, isPending, isScanning, shouldShowBrokenConnectionViolationForMultipleTransactions} from './TransactionUtils';
@@ -180,9 +181,9 @@ function canExport(report: Report, violations: OnyxCollection, isReportArchived: boolean, policy?: Policy, transactions?: Transaction[]) {
+function canReview(report: Report, violations: OnyxCollection, isReportArchived: boolean, currentUserEmail: string, policy?: Policy, transactions?: Transaction[]) {
const hasAnyViolations = hasMissingSmartscanFields(report.reportID, transactions) || hasAnyViolationsUtil(report.reportID, violations);
- const hasVisibleViolations = hasAnyViolations && ViolationsUtils.hasVisibleViolationsForUser(report, violations, policy, transactions);
+ const hasVisibleViolations = hasAnyViolations && ViolationsUtils.hasVisibleViolationsForUser(report, violations, currentUserEmail, policy, transactions);
const isSubmitter = isCurrentUserSubmitter(report);
const isOpen = isOpenExpenseReport(report);
const isReimbursed = isSettled(report);
@@ -214,9 +215,11 @@ function canReview(report: Report, violations: OnyxCollection,
isReportArchived: boolean,
+ currentUserEmail: string,
report?: Report,
policy?: Policy,
transactions?: Transaction[],
@@ -224,6 +227,7 @@ function getReportPreviewAction(
isPaidAnimationRunning?: boolean,
isApprovedAnimationRunning?: boolean,
isSubmittingAnimationRunning?: boolean,
+ areStrictPolicyRulesEnabled?: boolean,
): ValueOf {
if (!report) {
return CONST.REPORT.REPORT_PREVIEW_ACTIONS.VIEW;
@@ -241,6 +245,13 @@ function getReportPreviewAction(
if (isAddExpenseAction(report, transactions ?? [], isReportArchived)) {
return CONST.REPORT.REPORT_PREVIEW_ACTIONS.ADD_EXPENSE;
}
+
+ // When strict policy rules are enabled and there are violations, show REVIEW button instead of SUBMIT
+ const shouldBlockSubmit = shouldBlockSubmitDueToStrictPolicyRules(report.reportID, violations, areStrictPolicyRulesEnabled ?? false, transactions);
+ if (shouldBlockSubmit && canReview(report, violations, isReportArchived, currentUserEmail, policy, transactions)) {
+ return CONST.REPORT.REPORT_PREVIEW_ACTIONS.REVIEW;
+ }
+
if (canSubmit(report, violations, isReportArchived, policy, transactions)) {
return CONST.REPORT.REPORT_PREVIEW_ACTIONS.SUBMIT;
}
@@ -253,7 +264,7 @@ function getReportPreviewAction(
if (canExport(report, violations, policy)) {
return CONST.REPORT.REPORT_PREVIEW_ACTIONS.EXPORT_TO_ACCOUNTING;
}
- if (canReview(report, violations, isReportArchived, policy, transactions)) {
+ if (canReview(report, violations, isReportArchived, currentUserEmail, policy, transactions)) {
return CONST.REPORT.REPORT_PREVIEW_ACTIONS.REVIEW;
}
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index 1a49d4ffb866..9fcc787e3459 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -22,6 +22,7 @@ import * as defaultGroupAvatars from '@components/Icon/GroupDefaultAvatars';
import * as defaultWorkspaceAvatars from '@components/Icon/WorkspaceDefaultAvatars';
import type {LocaleContextProps} from '@components/LocaleContextProvider';
import type {MoneyRequestAmountInputProps} from '@components/MoneyRequestAmountInput';
+import type {TransactionWithOptionalSearchFields} from '@components/TransactionItemRow';
import type {ThemeColors} from '@styles/theme/types';
import type {IOUAction, IOUType, OnboardingAccounting} from '@src/CONST';
import CONST, {TASK_TO_FEATURE} from '@src/CONST';
@@ -242,7 +243,6 @@ import {
wasActionTakenByCurrentUser,
} from './ReportActionsUtils';
import type {LastVisibleMessage} from './ReportActionsUtils';
-import {getSession} from './SessionUtils';
import {shouldRestrictUserBillableActions} from './SubscriptionUtils';
import {
getAttendees,
@@ -1191,10 +1191,10 @@ function getChatType(report: OnyxInputOrEntry | Participant): ValueOf | SearchReport {
+function getReportOrDraftReport(reportID: string | undefined, searchReports?: SearchReport[], fallbackReport?: Report): OnyxEntry | SearchReport {
const searchReport = searchReports?.find((report) => report.reportID === reportID);
const onyxReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`];
- return searchReport ?? onyxReport ?? allReportsDraft?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT}${reportID}`];
+ return searchReport ?? onyxReport ?? allReportsDraft?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT}${reportID}`] ?? fallbackReport;
}
function reportTransactionsSelector(transactions: OnyxCollection, reportID: string | undefined): Transaction[] {
@@ -4260,7 +4260,7 @@ function getMoneyRequestReportName({
*/
function getTransactionDetails(
- transaction: OnyxInputOrEntry,
+ transaction: OnyxInputOrEntry | TransactionWithOptionalSearchFields,
createdDateFormat: string = CONST.DATE.FNS_FORMAT_STRING,
policy: OnyxEntry = undefined,
allowNegativeAmount = false,
@@ -4269,20 +4269,16 @@ function getTransactionDetails(
if (!transaction) {
return;
}
- const report = getReportOrDraftReport(transaction?.reportID);
+
+ const report = getReportOrDraftReport(transaction?.reportID, undefined, 'report' in transaction ? transaction.report : undefined);
const isManualDistanceRequest = isManualDistanceRequestTransactionUtils(transaction);
+ const isFromExpenseReport = !isEmptyObject(report) && isExpenseReport(report);
return {
created: getFormattedCreated(transaction, createdDateFormat),
- amount: getTransactionAmount(
- transaction,
- !isEmptyObject(report) && isExpenseReport(report),
- transaction?.reportID === CONST.REPORT.UNREPORTED_REPORT_ID,
- allowNegativeAmount,
- disableOppositeConversion,
- ),
+ amount: getTransactionAmount(transaction, isFromExpenseReport, transaction?.reportID === CONST.REPORT.UNREPORTED_REPORT_ID, allowNegativeAmount, disableOppositeConversion),
attendees: getAttendees(transaction),
- taxAmount: getTaxAmount(transaction, !isEmptyObject(report) && isExpenseReport(report)),
+ taxAmount: getTaxAmount(transaction, isFromExpenseReport),
taxCode: getTaxCode(transaction),
currency: getCurrency(transaction),
comment: getDescription(transaction),
@@ -4936,6 +4932,17 @@ function getReportPreviewMessage(
// This covers group chats where the last action is a track expense action
const linkedTransaction = getLinkedTransaction(iouReportAction);
if (isEmptyObject(linkedTransaction)) {
+ const originalMessage = getOriginalMessage(iouReportAction);
+ const amount = originalMessage?.amount;
+ const currency = originalMessage?.currency;
+ const comment = originalMessage?.comment;
+
+ if (amount && currency) {
+ const formattedAmount = convertToDisplayString(amount, currency);
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
+ return translateLocal('iou.trackedAmount', {formattedAmount, comment});
+ }
+
return reportActionMessage;
}
@@ -6193,7 +6200,7 @@ function buildOptimisticAddCommentReportAction(
* @param type - The type of action in the child report
*/
-function updateOptimisticParentReportAction(parentReportAction: OnyxEntry, lastVisibleActionCreated: string, type: string): UpdateOptimisticParentReportAction {
+function updateOptimisticParentReportAction(parentReportAction: OnyxEntry, lastVisibleActionCreated: string, type: string, deleteBy = 1): UpdateOptimisticParentReportAction {
let childVisibleActionCount = parentReportAction?.childVisibleActionCount ?? 0;
let childCommenterCount = parentReportAction?.childCommenterCount ?? 0;
let childOldestFourAccountIDs = parentReportAction?.childOldestFourAccountIDs;
@@ -6211,7 +6218,7 @@ function updateOptimisticParentReportAction(parentReportAction: OnyxEntry 0) {
- childVisibleActionCount -= 1;
+ childVisibleActionCount -= deleteBy;
}
if (childVisibleActionCount === 0) {
@@ -6863,7 +6870,7 @@ function buildOptimisticIOUReportAction(params: BuildOptimisticIOUReportActionPa
automatic: false,
isAttachmentOnly: false,
originalMessage,
- reportActionID: linkedExpenseReportAction?.reportActionID ?? reportActionID ?? rand64(),
+ reportActionID: reportActionID ?? rand64(),
shouldShow: true,
created,
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
@@ -7226,7 +7233,7 @@ function buildOptimisticActionableTrackExpenseWhisper(iouAction: OptimisticIOURe
return {
actionName: CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_TRACK_EXPENSE_WHISPER,
actorAccountID,
- avatar: getDefaultAvatarURL(actorAccountID),
+ avatar: getDefaultAvatarURL(actorAccountID, targetEmail),
created: DateUtils.addMillisecondsFromDateTime(currentTime, 1),
lastModified: DateUtils.addMillisecondsFromDateTime(currentTime, 1),
message: [
@@ -8585,7 +8592,7 @@ function shouldDisplayViolationsRBRInLHN(report: OnyxEntry, transactionV
return (
!isInvoiceReport(potentialReport) &&
- ViolationsUtils.hasVisibleViolationsForUser(potentialReport, transactionViolations, policy, transactions) &&
+ ViolationsUtils.hasVisibleViolationsForUser(potentialReport, transactionViolations, currentUserEmail ?? '', policy, transactions) &&
(hasViolations(potentialReport.reportID, transactionViolations, true) ||
hasWarningTypeViolations(potentialReport.reportID, transactionViolations, true) ||
hasNoticeTypeViolations(potentialReport.reportID, transactionViolations, true))
@@ -8671,6 +8678,22 @@ function hasReportViolations(reportID: string | undefined) {
return Object.values(reportViolations ?? {}).some((violations) => !isEmptyObject(violations));
}
+/**
+ * Checks if submission should be blocked due to strict policy rules being enabled and violations present.
+ * When a user's domain has "strictly enforce workspace rules" enabled, they cannot submit reports with violations.
+ */
+function shouldBlockSubmitDueToStrictPolicyRules(
+ reportID: string | undefined,
+ transactionViolations: OnyxCollection,
+ areStrictPolicyRulesEnabled: boolean,
+ reportTransactions?: Transaction[] | SearchTransaction[],
+) {
+ if (!areStrictPolicyRulesEnabled) {
+ return false;
+ }
+ return hasAnyViolations(reportID, transactionViolations, reportTransactions as SearchTransaction[]);
+}
+
type ReportErrorsAndReportActionThatRequiresAttention = {
errors: ErrorFields;
reportAction?: OnyxEntry;
@@ -10012,13 +10035,13 @@ 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, backTo?: string) {
- if (isEmpty(report) || isEmpty(session) || !session.accountID) {
+function navigateToPrivateNotes(report: OnyxEntry, accountID: number, backTo?: string) {
+ if (isEmpty(report) || !accountID) {
return;
}
- const currentUserPrivateNote = report.privateNotes?.[session.accountID]?.note ?? '';
+ const currentUserPrivateNote = report.privateNotes?.[accountID]?.note ?? '';
if (isEmpty(currentUserPrivateNote)) {
- Navigation.navigate(ROUTES.PRIVATE_NOTES_EDIT.getRoute(report.reportID, session.accountID, backTo));
+ Navigation.navigate(ROUTES.PRIVATE_NOTES_EDIT.getRoute(report.reportID, accountID, backTo));
return;
}
Navigation.navigate(ROUTES.PRIVATE_NOTES_LIST.getRoute(report.reportID, backTo));
@@ -10196,6 +10219,7 @@ function getOptimisticDataForParentReportAction(report: Report | undefined, last
const ancestors = getAllAncestorReportActionIDs(report, true);
const totalAncestor = ancestors.reportIDs.length;
+ let previousActionDeleted = false;
return Array.from(Array(totalAncestor), (_, index) => {
const ancestorReport = getReportOrDraftReport(ancestors.reportIDs.at(index));
@@ -10208,12 +10232,15 @@ function getOptimisticDataForParentReportAction(report: Report | undefined, last
if (!ancestorReportAction?.reportActionID || isEmptyObject(ancestorReportAction)) {
return null;
}
+ const updatedReportAction = updateOptimisticParentReportAction(ancestorReportAction, lastVisibleActionCreated, type, previousActionDeleted ? index + 1 : undefined);
+
+ previousActionDeleted = isDeletedAction(ancestorReportAction) && updatedReportAction.childVisibleActionCount === 0;
return {
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${ancestorReport.reportID}`,
value: {
- [ancestorReportAction.reportActionID]: updateOptimisticParentReportAction(ancestorReportAction, lastVisibleActionCreated, type),
+ [ancestorReportAction.reportActionID]: updatedReportAction,
},
};
});
@@ -11837,12 +11864,11 @@ function canRejectReportAction(currentUserLogin: string, report: Report, policy?
const isReportApprover = isApproverUtils(policy, currentUserLogin);
const isReportBeingProcessed = isProcessingReport(report);
- const isReportPayer = isPayer(getSession(), report, false, policy);
const isIOU = isIOUReport(report);
const isInvoice = isInvoiceReport(report);
const isCurrentUserManager = report?.managerID === currentUserAccountID;
- const userCanReject = (isReportApprover && isCurrentUserManager) || isReportPayer;
+ const userCanReject = isReportApprover && isCurrentUserManager;
if (!userCanReject) {
return false; // must be approver or payer
@@ -12547,6 +12573,7 @@ export {
doesReportContainRequestsFromMultipleUsers,
hasUnresolvedCardFraudAlert,
getUnresolvedCardFraudAlertAction,
+ shouldBlockSubmitDueToStrictPolicyRules,
};
export type {
Ancestor,
diff --git a/src/libs/RequestThrottle.ts b/src/libs/RequestThrottle.ts
index c4589bb07afa..c9cf3710eb18 100644
--- a/src/libs/RequestThrottle.ts
+++ b/src/libs/RequestThrottle.ts
@@ -1,4 +1,5 @@
import CONST from '@src/CONST';
+import {WRITE_COMMANDS} from './API/types';
import Log from './Log';
import type {RequestError} from './Network/SequentialQueue';
import {generateRandomInt} from './NumberUtils';
@@ -43,7 +44,8 @@ class RequestThrottle {
sleep(error: RequestError, command: string): Promise {
this.requestRetryCount++;
return new Promise((resolve, reject) => {
- if (this.requestRetryCount <= CONST.NETWORK.MAX_REQUEST_RETRIES) {
+ const maxRequestRetries = command === WRITE_COMMANDS.OPEN_APP ? CONST.NETWORK.MAX_OPEN_APP_REQUEST_RETRIES : CONST.NETWORK.MAX_REQUEST_RETRIES;
+ if (this.requestRetryCount <= maxRequestRetries) {
const currentRequestWaitTime = this.getRequestWaitTime();
Log.info(
`[RequestThrottle - ${this.name}] Retrying request after error: '${error.name}', '${error.message}', '${error.status}'. Command: ${command}. Retry count: ${this.requestRetryCount}. Wait time: ${currentRequestWaitTime}`,
diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts
index 77aea0cd2f44..21aaa81351fc 100644
--- a/src/libs/SearchUIUtils.ts
+++ b/src/libs/SearchUIUtils.ts
@@ -61,8 +61,9 @@ import type {
import type IconAsset from '@src/types/utils/IconAsset';
import {hasSynchronizationErrorMessage} from './actions/connections';
import {canApproveIOU, canIOUBePaid, canSubmitReport} from './actions/IOU';
-import {createNewReport, createTransactionThreadReport, openReport} from './actions/Report';
-import {updateSearchResultsWithTransactionThreadReportID} from './actions/Search';
+import {createNewReport, createTransactionThreadReport} from './actions/Report';
+import type {TransactionPreviewData} from './actions/Search';
+import {setOptimisticDataForTransactionThreadPreview, updateSearchResultsWithTransactionThreadReportID} from './actions/Search';
import type {CardFeedForDisplay} from './CardFeedUtils';
import {getCardFeedsForDisplay} from './CardFeedUtils';
import {convertToDisplayString, getCurrencySymbol} from './CurrencyUtils';
@@ -937,12 +938,16 @@ function getIOUReportName(data: OnyxTypes.SearchResults['data'], reportItem: Sea
});
}
-function getTransactionViolations(allViolations: OnyxCollection, transaction: SearchTransaction): OnyxTypes.TransactionViolation[] {
+function getTransactionViolations(
+ allViolations: OnyxCollection,
+ transaction: SearchTransaction,
+ currentUserEmail: string,
+): OnyxTypes.TransactionViolation[] {
const transactionViolations = allViolations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction.transactionID}`];
if (!transactionViolations) {
return [];
}
- return transactionViolations.filter((violation) => !isViolationDismissed(transaction, violation));
+ return transactionViolations.filter((violation) => !isViolationDismissed(transaction, violation, currentUserEmail));
}
/**
@@ -955,6 +960,7 @@ function getTransactionsSections(
data: OnyxTypes.SearchResults['data'],
currentSearch: SearchKey,
currentAccountID: number | undefined,
+ currentUserEmail: string,
formatPhoneNumber: LocaleContextProps['formatPhoneNumber'],
): TransactionListItemType[] {
const shouldShowMerchant = getShouldShowMerchant(data);
@@ -998,14 +1004,14 @@ function getTransactionsSections(
if (shouldShow) {
const policy = data[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`];
const shouldShowBlankTo = !report || isOpenExpenseReport(report);
- const transactionViolations = getTransactionViolations(allViolations, transactionItem);
+ const transactionViolations = getTransactionViolations(allViolations, transactionItem, currentUserEmail);
// Use Map.get() for faster lookups with default values
const from = personalDetailsMap.get(transactionItem.accountID.toString()) ?? emptyPersonalDetails;
const to = transactionItem.managerID && !shouldShowBlankTo ? (personalDetailsMap.get(transactionItem.managerID.toString()) ?? emptyPersonalDetails) : emptyPersonalDetails;
const {formattedFrom, formattedTo, formattedTotal, formattedMerchant, date} = getTransactionItemCommonFormattedProperties(transactionItem, from, to, policy, formatPhoneNumber);
- const allActions = getActions(data, allViolations, key, currentSearch, currentAccountID);
+ const allActions = getActions(data, allViolations, key, currentSearch, currentAccountID, currentUserEmail);
const transactionSection: TransactionListItemType = {
...transactionItem,
keyForList: transactionItem.transactionID,
@@ -1113,6 +1119,7 @@ function getActions(
key: string,
currentSearch: SearchKey,
currentAccountID: number | undefined,
+ currentUserEmail: string,
reportActions: OnyxTypes.ReportAction[] = [],
): SearchTransactionAction[] {
const isTransaction = isTransactionEntry(key);
@@ -1170,11 +1177,11 @@ function getActions(
const isChatReportArchived = isArchivedReport(chatReportRNVP);
const hasAnyViolationsForReport = hasAnyViolations(report.reportID, allViolations, allReportTransactions);
- const hasVisibleViolationsForReport = hasAnyViolationsForReport && ViolationsUtils.hasVisibleViolationsForUser(report, allViolations, policy, allReportTransactions);
+ const hasVisibleViolationsForReport = hasAnyViolationsForReport && ViolationsUtils.hasVisibleViolationsForUser(report, allViolations, currentUserEmail, policy, allReportTransactions);
// Only check for violations if we need to (when user has permission to review)
if ((isSubmitter || isApprover || isAdmin) && hasVisibleViolationsForReport) {
- if (isSubmitter && !isApprover && !isAdmin && !canReview(report, allViolations, isIOUReportArchived || isChatReportArchived, policy, allReportTransactions)) {
+ if (isSubmitter && !isApprover && !isAdmin && !canReview(report, allViolations, isIOUReportArchived || isChatReportArchived, currentUserEmail, policy, allReportTransactions)) {
allActions.push(CONST.SEARCH.ACTION_TYPES.VIEW);
} else {
allActions.push(CONST.SEARCH.ACTION_TYPES.REVIEW);
@@ -1298,17 +1305,20 @@ function getTaskSections(
}
/** Creates transaction thread report and navigates to it from the search page */
-function createAndOpenSearchTransactionThread(item: TransactionListItemType, iouReportAction: OnyxEntry, hash: number, backTo: string) {
- // We know that iou report action exists, but it wasn't loaded yet. We need to load iou report to have the necessary data in the onyx.
- const iouReportID = item.report?.reportID ?? item.reportID;
- if (!iouReportAction && iouReportID && iouReportID !== CONST.REPORT.UNREPORTED_REPORT_ID) {
- openReport(iouReportID);
- }
- const transactionThreadReport = createTransactionThreadReport(item.report, iouReportAction ?? ({reportActionID: item.moneyRequestReportActionID} as OnyxTypes.ReportAction));
+function createAndOpenSearchTransactionThread(item: TransactionListItemType, hash: number, backTo: string, transactionPreviewData?: TransactionPreviewData, shouldNavigate = true) {
+ const previewData = transactionPreviewData
+ ? {...transactionPreviewData, hasTransactionThreadReport: true}
+ : {hasTransaction: false, hasParentReport: false, hasParentReportAction: false, hasTransactionThreadReport: true};
+ setOptimisticDataForTransactionThreadPreview(item, previewData);
+
+ const transactionThreadReport = createTransactionThreadReport(item.report, {reportActionID: item.moneyRequestReportActionID} as OnyxTypes.ReportAction);
if (transactionThreadReport?.reportID) {
updateSearchResultsWithTransactionThreadReportID(hash, item.transactionID, transactionThreadReport?.reportID);
}
- Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID: transactionThreadReport?.reportID, backTo}));
+
+ if (shouldNavigate) {
+ Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID: transactionThreadReport?.reportID, backTo}));
+ }
}
/**
@@ -1386,6 +1396,7 @@ function getReportSections(
data: OnyxTypes.SearchResults['data'],
currentSearch: SearchKey,
currentAccountID: number | undefined,
+ currentUserEmail: string,
formatPhoneNumber: LocaleContextProps['formatPhoneNumber'],
reportActions: Record = {},
): TransactionGroupListItemType[] {
@@ -1440,7 +1451,7 @@ function getReportSections(
if (shouldShow) {
const reportPendingAction = reportItem?.pendingAction ?? reportItem?.pendingFields?.preview;
const shouldShowBlankTo = !reportItem || isOpenExpenseReport(reportItem);
- const allActions = getActions(data, allViolations, key, currentSearch, currentAccountID, actions);
+ const allActions = getActions(data, allViolations, key, currentSearch, currentAccountID, currentUserEmail, actions);
reportIDToTransactions[reportKey] = {
...reportItem,
action: allActions.at(0) ?? CONST.SEARCH.ACTION_TYPES.VIEW,
@@ -1463,7 +1474,7 @@ function getReportSections(
const report = data[`${ONYXKEYS.COLLECTION.REPORT}${transactionItem.reportID}`];
const policy = data[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`];
const shouldShowBlankTo = !report || isOpenExpenseReport(report);
- const transactionViolations = getTransactionViolations(allViolations, transactionItem);
+ const transactionViolations = getTransactionViolations(allViolations, transactionItem, currentUserEmail);
const actions = Object.values(reportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionItem.reportID}`] ?? {});
const from = data.personalDetailsList?.[transactionItem.accountID];
@@ -1471,7 +1482,7 @@ function getReportSections(
const {formattedFrom, formattedTo, formattedTotal, formattedMerchant, date} = getTransactionItemCommonFormattedProperties(transactionItem, from, to, policy, formatPhoneNumber);
- const allActions = getActions(data, allViolations, key, currentSearch, currentAccountID, actions);
+ const allActions = getActions(data, allViolations, key, currentSearch, currentAccountID, currentUserEmail, actions);
const transaction = {
...transactionItem,
action: allActions.at(0) ?? CONST.SEARCH.ACTION_TYPES.VIEW,
@@ -1631,6 +1642,7 @@ function getSections(
type: SearchDataTypes,
data: OnyxTypes.SearchResults['data'],
currentAccountID: number | undefined,
+ currentUserEmail: string,
formatPhoneNumber: LocaleContextProps['formatPhoneNumber'],
groupBy?: SearchGroupBy,
reportActions: Record = {},
@@ -1650,7 +1662,7 @@ function getSections(
// eslint-disable-next-line default-case
switch (groupBy) {
case CONST.SEARCH.GROUP_BY.REPORTS:
- return getReportSections(data, currentSearch, currentAccountID, formatPhoneNumber, reportActions);
+ return getReportSections(data, currentSearch, currentAccountID, currentUserEmail, formatPhoneNumber, reportActions);
case CONST.SEARCH.GROUP_BY.FROM:
return getMemberSections(data, queryJSON);
case CONST.SEARCH.GROUP_BY.CARD:
@@ -1660,7 +1672,7 @@ function getSections(
}
}
- return getTransactionsSections(data, currentSearch, currentAccountID, formatPhoneNumber);
+ return getTransactionsSections(data, currentSearch, currentAccountID, currentUserEmail, formatPhoneNumber);
}
/**
diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts
index 5055141533b5..412edc51bfbf 100644
--- a/src/libs/SidebarUtils.ts
+++ b/src/libs/SidebarUtils.ts
@@ -241,7 +241,7 @@ function getReportsToDisplayInLHN(
const reportsToDisplay: ReportsToDisplayInLHN = {};
Object.entries(allReportsDictValues).forEach(([reportID, report]) => {
- if (!report) {
+ if (!report?.reportID) {
return;
}
@@ -295,7 +295,7 @@ function updateReportsToDisplayInLHN({
const displayedReportsCopy = {...displayedReports};
updatedReportsKeys.forEach((reportID) => {
const report = reports?.[reportID];
- if (!report) {
+ if (!report?.reportID) {
return;
}
diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts
index 9865acc3b2da..6e2720623c66 100644
--- a/src/libs/TransactionUtils/index.ts
+++ b/src/libs/TransactionUtils/index.ts
@@ -144,13 +144,13 @@ Onyx.connect({
callback: (value) => (allTransactionViolations = value),
});
-let currentUserEmail = '';
-let currentUserAccountID = -1;
+let deprecatedCurrentUserEmail = '';
+let deprecatedCurrentUserAccountID = -1;
Onyx.connect({
key: ONYXKEYS.SESSION,
callback: (val) => {
- currentUserEmail = val?.email ?? '';
- currentUserAccountID = val?.accountID ?? CONST.DEFAULT_NUMBER_ID;
+ deprecatedCurrentUserEmail = val?.email ?? '';
+ deprecatedCurrentUserAccountID = val?.accountID ?? CONST.DEFAULT_NUMBER_ID;
},
});
@@ -799,7 +799,7 @@ function isFetchingWaypointsFromServer(transaction: OnyxInputOrEntry, policyParam: OnyxEntry = undefined) {
if (transaction && isDistanceRequest(transaction)) {
@@ -807,7 +807,7 @@ function isUnreportedAndHasInvalidDistanceRateTransaction(transaction: OnyxInput
// eslint-disable-next-line @typescript-eslint/no-deprecated
const policy = policyParam ?? getPolicy(report?.policyID);
const {rate} = DistanceRequestUtils.getRate({transaction, policy});
- const isUnreportedExpense = !transaction.reportID || transaction.reportID === CONST.REPORT.UNREPORTED_REPORT_ID;
+ const isUnreportedExpense = !transaction.reportID || transaction.reportID === CONST.REPORT.UNREPORTED_REPORT_ID || String(transaction.reportID) === CONST.REPORT.SPLIT_REPORT_ID;
if (isUnreportedExpense && !rate) {
return true;
@@ -854,7 +854,7 @@ function getAttendees(transaction: OnyxInputOrEntry): Attendee[] {
const creatorAccountID = report?.ownerAccountID;
if (creatorAccountID) {
- const [creatorDetails] = getPersonalDetailsByIDs({accountIDs: [creatorAccountID], currentUserAccountID});
+ const [creatorDetails] = getPersonalDetailsByIDs({accountIDs: [creatorAccountID], currentUserAccountID: deprecatedCurrentUserAccountID});
const creatorEmail = creatorDetails?.login ?? '';
const creatorDisplayName = creatorDetails?.displayName ?? creatorEmail;
@@ -1054,12 +1054,18 @@ function hasMissingSmartscanFields(transaction: OnyxInputOrEntry, r
/**
* Get all transaction violations of the transaction with given transactionID.
*/
-function getTransactionViolations(transaction: OnyxEntry, transactionViolations: OnyxCollection): TransactionViolations | undefined {
+function getTransactionViolations(
+ transaction: OnyxEntry,
+ transactionViolations: OnyxCollection,
+ currentUserEmail?: string,
+): TransactionViolations | undefined {
if (!transaction || !transactionViolations) {
return undefined;
}
- return transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transaction.transactionID]?.filter((violation) => !isViolationDismissed(transaction, violation));
+ return transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transaction.transactionID]?.filter(
+ (violation) => !isViolationDismissed(transaction, violation, currentUserEmail),
+ );
}
function getTransactionViolationsOfTransaction(transactionID: string) {
@@ -1138,7 +1144,13 @@ function shouldShowBrokenConnectionViolationForMultipleTransactions(
/**
* Check if the user should see the violation
*/
-function shouldShowViolation(iouReport: OnyxEntry, policy: OnyxEntry, violationName: ViolationName, shouldShowRterForSettledReport = true): boolean {
+function shouldShowViolation(
+ iouReport: OnyxEntry,
+ policy: OnyxEntry,
+ violationName: ViolationName,
+ currentUserEmail: string,
+ shouldShowRterForSettledReport = true,
+): boolean {
const isSubmitter = isCurrentUserSubmitter(iouReport);
const isPolicyMember = isPolicyMemberPolicyUtils(policy, currentUserEmail);
const isReportOpen = isOpenExpenseReport(iouReport);
@@ -1307,11 +1319,11 @@ function isOnHold(transaction: OnyxEntry): boolean {
/**
* Checks if a violation is dismissed for the given transaction
*/
-function isViolationDismissed(transaction: OnyxEntry, violation: TransactionViolation | undefined): boolean {
+function isViolationDismissed(transaction: OnyxEntry, violation: TransactionViolation | undefined, currentUserEmail?: string): boolean {
if (!transaction || !violation) {
return false;
}
- return !!transaction?.comment?.dismissedViolations?.[violation.name]?.[currentUserEmail];
+ return !!transaction?.comment?.dismissedViolations?.[violation.name]?.[currentUserEmail ?? deprecatedCurrentUserEmail];
}
/**
@@ -1969,9 +1981,9 @@ function isTransactionPendingDelete(transaction: OnyxEntry): boolea
/**
* Retrieves all “child” transactions associated with a given original transaction
*/
-function getChildTransactions(originalTransactionID: string | undefined) {
- return Object.values(allTransactions ?? {}).filter((currentTransaction) => {
- const currentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${currentTransaction?.reportID}`];
+function getChildTransactions(transactions: OnyxCollection, reports: OnyxCollection, originalTransactionID: string | undefined) {
+ return Object.values(transactions ?? {}).filter((currentTransaction) => {
+ const currentReport = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${currentTransaction?.reportID}`];
return (
currentTransaction?.comment?.originalTransactionID === originalTransactionID &&
!!currentReport &&
diff --git a/src/libs/UserUtils.ts b/src/libs/UserUtils.ts
index d26f37dcd302..843077e64b83 100644
--- a/src/libs/UserUtils.ts
+++ b/src/libs/UserUtils.ts
@@ -1,4 +1,4 @@
-import {Str} from 'expensify-common';
+import {md5, Str} from 'expensify-common';
import type {OnyxEntry} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import * as defaultAvatars from '@components/Icon/DefaultAvatars';
@@ -116,7 +116,7 @@ function generateAccountID(searchValue: string): number {
return hashText(searchValue, 2 ** 32);
}
-function getAccountIDHashBucket(accountID = -1, avatarURL?: string) {
+function getAccountIDHashBucket(accountID = -1, accountEmail?: string, avatarURL?: string) {
// There are 24 possible default avatars, so we choose which one this user has based
// on a simple modulo operation of their login number. Note that Avatar count starts at 1.
@@ -127,6 +127,9 @@ function getAccountIDHashBucket(accountID = -1, avatarURL?: string) {
const match = avatarURL.match(/(default-avatar_|avatar_)(\d+)(?=\.)/);
const lastDigit = match && parseInt(match[2], 10);
accountIDHashBucket = lastDigit as AvatarRange;
+ } else if (accountEmail) {
+ const intVal = Number.parseInt(md5(accountEmail).substring(0, 4), 16);
+ accountIDHashBucket = ((intVal % CONST.DEFAULT_AVATAR_COUNT) + 1) as AvatarRange;
} else if (accountID > 0) {
accountIDHashBucket = ((accountID % CONST.DEFAULT_AVATAR_COUNT) + 1) as AvatarRange;
}
@@ -136,7 +139,7 @@ function getAccountIDHashBucket(accountID = -1, avatarURL?: string) {
/**
* Helper method to return the default avatar associated with the given accountID
*/
-function getDefaultAvatar(accountID: number = CONST.DEFAULT_NUMBER_ID, avatarURL?: string): IconAsset | undefined {
+function getDefaultAvatar(accountID: number = CONST.DEFAULT_NUMBER_ID, accountEmail?: string, avatarURL?: string): IconAsset | undefined {
if (accountID === CONST.ACCOUNT_ID.CONCIERGE) {
return ConciergeAvatar;
}
@@ -144,7 +147,7 @@ function getDefaultAvatar(accountID: number = CONST.DEFAULT_NUMBER_ID, avatarURL
return NotificationsAvatar;
}
- const accountIDHashBucket = getAccountIDHashBucket(accountID, avatarURL);
+ const accountIDHashBucket = getAccountIDHashBucket(accountID, accountEmail, avatarURL);
if (!accountIDHashBucket) {
return;
}
@@ -155,8 +158,8 @@ function getDefaultAvatar(accountID: number = CONST.DEFAULT_NUMBER_ID, avatarURL
/**
* Helper method to return default avatar name associated with the accountID
*/
-function getDefaultAvatarName(accountID: number = CONST.DEFAULT_NUMBER_ID, avatarURL?: string): string {
- const accountIDHashBucket = getAccountIDHashBucket(accountID, avatarURL);
+function getDefaultAvatarName(accountID: number = CONST.DEFAULT_NUMBER_ID, accountEmail?: string, avatarURL?: string): string {
+ const accountIDHashBucket = getAccountIDHashBucket(accountID, accountEmail, avatarURL);
const avatarPrefix = `default-avatar`;
return `${avatarPrefix}_${accountIDHashBucket}`;
@@ -165,12 +168,12 @@ function getDefaultAvatarName(accountID: number = CONST.DEFAULT_NUMBER_ID, avata
/**
* Helper method to return default avatar URL associated with the accountID
*/
-function getDefaultAvatarURL(accountID: number = CONST.DEFAULT_NUMBER_ID, avatarURL?: string): string {
+function getDefaultAvatarURL(accountID: number = CONST.DEFAULT_NUMBER_ID, accountEmail?: string, avatarURL?: string): string {
if (Number(accountID) === CONST.ACCOUNT_ID.CONCIERGE) {
return CONST.CONCIERGE_ICON_URL;
}
- return `${CONST.CLOUDFRONT_URL}/images/avatars/${getDefaultAvatarName(accountID, avatarURL)}.png`;
+ return `${CONST.CLOUDFRONT_URL}/images/avatars/${getDefaultAvatarName(accountID, accountEmail, avatarURL)}.png`;
}
/**
@@ -215,9 +218,10 @@ function isDefaultAvatar(avatarSource?: AvatarSource): avatarSource is string |
*
* @param avatarSource - the avatar source from user's personalDetails
* @param accountID - the accountID of the user
+ * @param accountEmail - the email of the user, for consistency with BE logic
*/
-function getAvatar(avatarSource?: AvatarSource, accountID?: number): AvatarSource | undefined {
- return isDefaultAvatar(avatarSource) ? getDefaultAvatar(accountID, avatarSource) : avatarSource;
+function getAvatar(avatarSource?: AvatarSource, accountID?: number, accountEmail?: string): AvatarSource | undefined {
+ return isDefaultAvatar(avatarSource) ? getDefaultAvatar(accountID, accountEmail, avatarSource) : avatarSource;
}
/**
@@ -227,16 +231,16 @@ function getAvatar(avatarSource?: AvatarSource, accountID?: number): AvatarSourc
* @param avatarSource - the avatar source from user's personalDetails
* @param accountID - the accountID of the user
*/
-function getAvatarUrl(avatarSource: AvatarSource | undefined, accountID: number): AvatarSource {
- return isDefaultAvatar(avatarSource) ? getDefaultAvatarURL(accountID, avatarSource) : avatarSource;
+function getAvatarUrl(avatarSource: AvatarSource | undefined, accountID: number, accountEmail?: string): AvatarSource {
+ return isDefaultAvatar(avatarSource) ? getDefaultAvatarURL(accountID, accountEmail, avatarSource) : avatarSource;
}
/**
* Avatars uploaded by users will have a _128 appended so that the asset server returns a small version.
* This removes that part of the URL so the full version of the image can load.
*/
-function getFullSizeAvatar(avatarSource: AvatarSource | undefined, accountID?: number): AvatarSource | undefined {
- const source = getAvatar(avatarSource, accountID);
+function getFullSizeAvatar(avatarSource: AvatarSource | undefined, accountID?: number, accountEmail?: string): AvatarSource | undefined {
+ const source = getAvatar(avatarSource, accountID, accountEmail);
if (typeof source !== 'string') {
return source;
}
@@ -247,8 +251,8 @@ function getFullSizeAvatar(avatarSource: AvatarSource | undefined, accountID?: n
* Small sized avatars end with _128.. This adds the _128 at the end of the
* source URL (before the file type) if it doesn't exist there already.
*/
-function getSmallSizeAvatar(avatarSource?: AvatarSource, accountID?: number): AvatarSource | undefined {
- const source = getAvatar(avatarSource, accountID);
+function getSmallSizeAvatar(avatarSource?: AvatarSource, accountID?: number, accountEmail?: string): AvatarSource | undefined {
+ const source = getAvatar(avatarSource, accountID, accountEmail);
if (typeof source !== 'string') {
return source;
}
diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts
index 62a32b1cf104..c7da8f1239ee 100644
--- a/src/libs/Violations/ViolationsUtils.ts
+++ b/src/libs/Violations/ViolationsUtils.ts
@@ -619,7 +619,13 @@ const ViolationsUtils = {
* Checks if any transactions in the report have violations that should be visible to the current user.
* Filters violations based on user role (submitter, admin, policy member) and report state.
*/
- hasVisibleViolationsForUser(report: OnyxEntry, violations: OnyxCollection, policy?: OnyxEntry, transactions?: Transaction[]): boolean {
+ hasVisibleViolationsForUser(
+ report: OnyxEntry,
+ violations: OnyxCollection,
+ currentUserEmail: string,
+ policy?: OnyxEntry,
+ transactions?: Transaction[],
+ ): boolean {
if (!report || !violations || !transactions) {
return false;
}
@@ -633,7 +639,7 @@ const ViolationsUtils = {
// Check if any violation should be shown based on user role and violation type
return transactionViolations.some((violation: TransactionViolation) => {
- return shouldShowViolation(report, policy, violation.name);
+ return shouldShowViolation(report, policy, violation.name, currentUserEmail);
});
});
},
diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts
index 3e05e8a4937f..c3fa75846ef5 100644
--- a/src/libs/actions/IOU.ts
+++ b/src/libs/actions/IOU.ts
@@ -33,6 +33,7 @@ import type {
RequestMoneyParams,
ResolveDuplicatesParams,
RetractReportParams,
+ RevertSplitTransactionParams,
SendInvoiceParams,
SendMoneyParams,
SetNameValuePairParams,
@@ -718,6 +719,27 @@ type StartSplitBilActionParams = {
policyRecentlyUsedCategories?: OnyxEntry;
};
+type UpdateSplitTransactionsParams = {
+ allTransactionsList: OnyxCollection;
+ allReportsList: OnyxCollection;
+ allReportNameValuePairsList: OnyxCollection;
+ transactionData: {
+ reportID: string;
+ originalTransactionID: string;
+ splitExpenses: SplitExpense[];
+ splitExpensesTotal?: number;
+ };
+ hash: number;
+ policyCategories: OnyxTypes.PolicyCategories | undefined;
+ policy: OnyxTypes.Policy | undefined;
+ policyRecentlyUsedCategories: OnyxTypes.RecentlyUsedCategories | undefined;
+ iouReport: OnyxEntry;
+ chatReport: OnyxEntry;
+ firstIOU: OnyxEntry | undefined;
+ isChatReportArchived?: boolean;
+ isNewDotRevertSplitsEnabled: boolean;
+};
+
type ReplaceReceipt = {
transactionID: string;
file?: File;
@@ -5784,7 +5806,7 @@ function shareTrackedExpense(trackedExpenseParams: TrackedExpenseParams) {
optimisticData: addAccountantToWorkspaceOptimisticData,
successData: addAccountantToWorkspaceSuccessData,
failureData: addAccountantToWorkspaceFailureData,
- } = buildUpdateWorkspaceMembersRoleOnyxData(policyID, [accountantAccountID], CONST.POLICY.ROLE.ADMIN);
+ } = buildUpdateWorkspaceMembersRoleOnyxData(policyID, [accountantEmail], [accountantAccountID], CONST.POLICY.ROLE.ADMIN);
optimisticData?.push(...addAccountantToWorkspaceOptimisticData);
successData?.push(...addAccountantToWorkspaceSuccessData);
failureData?.push(...addAccountantToWorkspaceFailureData);
@@ -13179,7 +13201,7 @@ function initSplitExpenseItemData(
/**
* Create a draft transaction to set up split expense details for the split expense flow
*/
-function initSplitExpense(transaction: OnyxEntry) {
+function initSplitExpense(transactions: OnyxCollection, reports: OnyxCollection, transaction: OnyxEntry) {
if (!transaction) {
return;
}
@@ -13189,9 +13211,9 @@ function initSplitExpense(transaction: OnyxEntry) {
const {isExpenseSplit} = getOriginalTransactionWithSplitInfo(transaction);
if (isExpenseSplit) {
const originalTransactionID = transaction.comment?.originalTransactionID;
- const originalTransaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${originalTransactionID}`];
+ const originalTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${originalTransactionID}`];
- const relatedTransactions = getChildTransactions(originalTransactionID);
+ const relatedTransactions = getChildTransactions(transactions, reports, originalTransactionID);
const transactionDetails = getTransactionDetails(originalTransaction);
const splitExpenses = relatedTransactions.map((currentTransaction) => initSplitExpenseItemData(currentTransaction));
@@ -13386,38 +13408,43 @@ function clearSplitTransactionDraftErrors(transactionID: string | undefined) {
});
}
-function saveSplitTransactions(
- originalTransactionID: string,
- draftTransaction: OnyxEntry,
- hash: number,
- policyCategories: OnyxTypes.PolicyCategories | undefined,
- policy: OnyxTypes.Policy | undefined,
- policyRecentlyUsedCategories: OnyxTypes.RecentlyUsedCategories | undefined,
- iouReport: OnyxEntry,
- chatReport: OnyxEntry,
- firstIOU: OnyxEntry | undefined,
- isChatReportArchived: boolean | undefined,
-) {
- const transactionReport = getReportOrDraftReport(draftTransaction?.reportID);
+function updateSplitTransactions({
+ allTransactionsList,
+ allReportsList,
+ allReportNameValuePairsList,
+ transactionData,
+ hash,
+ policyCategories,
+ policy,
+ policyRecentlyUsedCategories,
+ iouReport,
+ chatReport,
+ firstIOU,
+ isChatReportArchived,
+ isNewDotRevertSplitsEnabled,
+}: UpdateSplitTransactionsParams) {
+ const transactionReport = getReportOrDraftReport(transactionData?.reportID);
const parentTransactionReport = getReportOrDraftReport(transactionReport?.parentReportID);
const expenseReport = transactionReport?.type === CONST.REPORT.TYPE.EXPENSE ? transactionReport : parentTransactionReport;
- const originalTransaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${originalTransactionID}`];
+ const originalTransactionID = transactionData?.originalTransactionID ?? CONST.IOU.OPTIMISTIC_TRANSACTION_ID;
+ const originalTransaction = allTransactionsList?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${originalTransactionID}`];
const originalTransactionDetails = getTransactionDetails(originalTransaction);
const policyTags = getPolicyTagsData(expenseReport?.policyID);
const participants = getMoneyRequestParticipantsFromReport(expenseReport);
- const splitExpenses = draftTransaction?.comment?.splitExpenses ?? [];
+ const splitExpenses = transactionData?.splitExpenses ?? [];
// List of all child transactions that have been created after split
- const originalChildTransactions = getChildTransactions(originalTransactionID);
- const isCreationOfSplits = originalChildTransactions.length === 0;
+ const originalChildTransactions = getChildTransactions(allTransactionsList, allReportsList, originalTransactionID);
const processedChildTransactionIDs: string[] = [];
const reportTotal = transactionReport?.total ?? 0;
- const splitExpensesTotal = draftTransaction?.comment?.splitExpensesTotal ?? 0;
+ const splitExpensesTotal = transactionData?.splitExpensesTotal ?? 0;
- const isReverseSplitOperation = splitExpenses.length === 1 && originalChildTransactions.length > 0;
+ const isCreationOfSplits = originalChildTransactions.length === 0;
+ const hasEditableSplitExpensesLeft = splitExpenses.some((expense) => (expense.statusNum ?? 0) < CONST.REPORT.STATUS_NUM.SUBMITTED);
+ const isReverseSplitOperation = splitExpenses.length === 1 && originalChildTransactions.length > 0 && hasEditableSplitExpensesLeft && isNewDotRevertSplitsEnabled;
let changesInReportTotal = 0;
// Validate custom unit rate before proceeding with split
@@ -13461,7 +13488,7 @@ function saveSplitTransactions(
splitExpenses.forEach((splitExpense, index) => {
const existingTransactionID = isReverseSplitOperation ? originalTransactionID : splitExpense.transactionID;
- const splitTransaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${existingTransactionID}`];
+ const splitTransaction = allTransactionsList?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${existingTransactionID}`];
if (splitTransaction) {
processedChildTransactionIDs.push(splitTransaction.transactionID);
}
@@ -13486,19 +13513,19 @@ function saveSplitTransactions(
transactionParams: {
amount: Math.abs(originalTransaction?.amount ?? 0),
modifiedAmount: splitExpense.amount ?? 0,
- currency: draftTransaction?.currency ?? CONST.CURRENCY.USD,
+ currency: originalTransactionDetails?.currency ?? CONST.CURRENCY.USD,
created: splitExpense.created,
merchant: splitExpense.merchant ?? '',
comment: splitExpense.description,
category: splitExpense.category,
tag: splitExpense.tags?.[0],
originalTransactionID,
- attendees: draftTransaction?.comment?.attendees,
+ attendees: originalTransactionDetails?.attendees,
source: CONST.IOU.TYPE.SPLIT,
linkedTrackedExpenseReportAction: currentReportAction,
pendingAction: splitTransaction ? null : CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
pendingFields: splitTransaction ? splitTransaction.pendingFields : undefined,
- reimbursable: draftTransaction?.reimbursable,
+ reimbursable: originalTransactionDetails?.reimbursable,
},
parentChatReport: getReportOrDraftReport(getReportOrDraftReport(expenseReport?.chatReportID)?.parentReportID),
existingTransaction: originalTransaction,
@@ -13507,13 +13534,13 @@ function saveSplitTransactions(
if (isReverseSplitOperation) {
requestMoneyInformation.transactionParams = {
amount: splitExpense.amount ?? 0,
- currency: draftTransaction?.currency ?? CONST.CURRENCY.USD,
+ currency: originalTransactionDetails?.currency ?? CONST.CURRENCY.USD,
created: splitExpense.created,
merchant: splitExpense.merchant ?? '',
comment: splitExpense.description,
category: splitExpense.category,
tag: splitExpense.tags?.[0],
- attendees: draftTransaction?.comment?.attendees,
+ attendees: originalTransactionDetails?.attendees as Attendee[],
linkedTrackedExpenseReportAction: currentReportAction,
};
requestMoneyInformation.existingTransaction = undefined;
@@ -13599,9 +13626,12 @@ function saveSplitTransactions(
const undeletedTransactions = originalChildTransactions.filter(
(currentTransaction) => !processedChildTransactionIDs.includes(currentTransaction?.transactionID ?? CONST.IOU.OPTIMISTIC_TRANSACTION_ID),
);
+
undeletedTransactions.forEach((undeletedTransaction) => {
- const splitTransaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${undeletedTransaction?.transactionID}`];
+ const splitTransaction = allTransactionsList?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${undeletedTransaction?.transactionID}`];
const splitReportActions = getAllReportActions(splitTransaction?.reportID);
+ const reportNameValuePairs = allReportNameValuePairsList?.[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${splitTransaction?.reportID}`];
+ const isReportArchived = isArchivedReport(reportNameValuePairs);
const currentReportAction = Object.values(splitReportActions).find((action) => {
const transactionID = isMoneyRequestAction(action) ? (getOriginalMessage(action)?.IOUTransactionID ?? CONST.DEFAULT_NUMBER_ID) : CONST.DEFAULT_NUMBER_ID;
return transactionID === undeletedTransaction?.transactionID;
@@ -13611,7 +13641,17 @@ function saveSplitTransactions(
optimisticData: deleteExpenseOptimisticData,
failureData: deleteExpenseFailureData,
successData: deleteExpenseSuccessData,
- } = getDeleteTrackExpenseInformation(splitTransaction?.reportID ?? String(CONST.DEFAULT_NUMBER_ID), undeletedTransaction?.transactionID, currentReportAction, undefined);
+ } = getDeleteTrackExpenseInformation(
+ splitTransaction?.reportID ?? String(CONST.DEFAULT_NUMBER_ID),
+ undeletedTransaction?.transactionID,
+ currentReportAction,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ isReportArchived,
+ );
optimisticData.push(...(deleteExpenseOptimisticData ?? []));
successData.push(...(deleteExpenseSuccessData ?? []));
@@ -13693,30 +13733,45 @@ function saveSplitTransactions(
});
}
- // Prepare splitApiParams for the Transaction_Split API call which requires a specific format for the splits
- // The format is: splits[0][amount], splits[0][category], splits[0][tag] etc.
- const splitApiParams = {} as Record;
- splits.forEach((split, i) => {
- Object.entries(split).forEach(([key, value]) => {
- splitApiParams[`splits[${i}][${key}]`] = value !== null && typeof value === 'object' ? JSON.stringify(value) : value;
+ if (isReverseSplitOperation) {
+ const parameters = {
+ ...splits.at(0),
+ comment: splits.at(0)?.comment?.comment,
+ } as RevertSplitTransactionParams;
+ API.write(WRITE_COMMANDS.REVERT_SPLIT_TRANSACTION, parameters, {optimisticData, successData, failureData});
+ } else {
+ // Prepare splitApiParams for the Transaction_Split API call which requires a specific format for the splits
+ // The format is: splits[0][amount], splits[0][category], splits[0][tag] etc.
+ const splitApiParams = {} as Record;
+ splits.forEach((split, i) => {
+ Object.entries(split).forEach(([key, value]) => {
+ splitApiParams[`splits[${i}][${key}]`] = value !== null && typeof value === 'object' ? JSON.stringify(value) : value;
+ });
});
- });
- const parameters: SplitTransactionParams = {
- ...splitApiParams,
- transactionID: originalTransactionID,
- };
+ const parameters: SplitTransactionParams = {
+ ...splitApiParams,
+ transactionID: originalTransactionID,
+ };
- if (isCreationOfSplits) {
- API.write(WRITE_COMMANDS.SPLIT_TRANSACTION, parameters, {optimisticData, successData, failureData});
- } else {
- // eslint-disable-next-line rulesdir/no-multiple-api-calls
- API.write(WRITE_COMMANDS.UPDATE_SPLIT_TRANSACTION, parameters, {optimisticData, successData, failureData});
+ if (isCreationOfSplits) {
+ // eslint-disable-next-line rulesdir/no-multiple-api-calls
+ API.write(WRITE_COMMANDS.SPLIT_TRANSACTION, parameters, {optimisticData, successData, failureData});
+ } else {
+ // eslint-disable-next-line rulesdir/no-multiple-api-calls
+ API.write(WRITE_COMMANDS.UPDATE_SPLIT_TRANSACTION, parameters, {optimisticData, successData, failureData});
+ }
}
// eslint-disable-next-line @typescript-eslint/no-deprecated
InteractionManager.runAfterInteractions(() => removeDraftSplitTransaction(originalTransactionID));
+}
+function updateSplitTransactionsFromSplitExpensesFlow(params: UpdateSplitTransactionsParams) {
+ updateSplitTransactions(params);
+ const transactionReport = getReportOrDraftReport(params.transactionData?.reportID);
+ const parentTransactionReport = getReportOrDraftReport(transactionReport?.parentReportID);
+ const expenseReport = transactionReport?.type === CONST.REPORT.TYPE.EXPENSE ? transactionReport : parentTransactionReport;
const isSearchPageTopmostFullScreenRoute = isSearchTopmostFullScreenRoute();
- const transactionThreadReportID = firstIOU?.childReportID;
+ const transactionThreadReportID = params.firstIOU?.childReportID;
const transactionThreadReportScreen = Navigation.getReportRouteByID(transactionThreadReportID);
if (isSearchPageTopmostFullScreenRoute || !transactionReport?.parentReportID) {
@@ -14011,7 +14066,8 @@ export {
initSplitExpenseItemData,
addSplitExpenseField,
updateSplitExpenseAmountField,
- saveSplitTransactions,
+ updateSplitTransactions,
+ updateSplitTransactionsFromSplitExpensesFlow,
initDraftSplitExpenseDataForEdit,
removeSplitExpenseField,
updateSplitExpenseField,
diff --git a/src/libs/actions/PersonalDetails.ts b/src/libs/actions/PersonalDetails.ts
index 9751e46a28c1..71e7aff717cd 100644
--- a/src/libs/actions/PersonalDetails.ts
+++ b/src/libs/actions/PersonalDetails.ts
@@ -435,13 +435,13 @@ function updateAvatar(
/**
* Replaces the user's avatar image with a default avatar
*/
-function deleteAvatar(currentUserPersonalDetails: Pick) {
+function deleteAvatar(currentUserPersonalDetails: Pick) {
if (!currentUserPersonalDetails.accountID) {
return;
}
// We want to use the old dot avatar here as this affects both platforms.
- const defaultAvatar = UserUtils.getDefaultAvatarURL(currentUserPersonalDetails.accountID);
+ const defaultAvatar = UserUtils.getDefaultAvatarURL(currentUserPersonalDetails.accountID, currentUserPersonalDetails.email);
const optimisticData: OnyxUpdate[] = [
{
diff --git a/src/libs/actions/Policy/Category.ts b/src/libs/actions/Policy/Category.ts
index ff4c916013b3..f23f4297f9fa 100644
--- a/src/libs/actions/Policy/Category.ts
+++ b/src/libs/actions/Policy/Category.ts
@@ -43,8 +43,9 @@ function appendSetupCategoriesOnboardingData(
setupCategoryTaskReport: OnyxEntry,
setupCategoryTaskParentReport: OnyxEntry,
isSetupCategoriesTaskParentReportArchived: boolean,
+ currentUserAccountID: number,
) {
- const finishOnboardingTaskData = getFinishOnboardingTaskOnyxData(setupCategoryTaskReport, setupCategoryTaskParentReport, isSetupCategoriesTaskParentReportArchived);
+ const finishOnboardingTaskData = getFinishOnboardingTaskOnyxData(setupCategoryTaskReport, setupCategoryTaskParentReport, isSetupCategoriesTaskParentReportArchived, currentUserAccountID);
onyxData.optimisticData?.push(...(finishOnboardingTaskData.optimisticData ?? []));
onyxData.successData?.push(...(finishOnboardingTaskData.successData ?? []));
onyxData.failureData?.push(...(finishOnboardingTaskData.failureData ?? []));
@@ -316,6 +317,7 @@ function setWorkspaceCategoryEnabled(
isSetupCategoriesTaskParentReportArchived: boolean,
setupCategoryTaskReport: OnyxEntry,
setupCategoryTaskParentReport: OnyxEntry,
+ currentUserAccountID: number,
policyCategories: PolicyCategories = {},
policyTagLists: PolicyTagLists = {},
allTransactionViolations: OnyxCollection = {},
@@ -385,7 +387,7 @@ function setWorkspaceCategoryEnabled(
};
pushTransactionViolationsOnyxData(onyxData, policyID, policyTagLists, policyCategories, allTransactionViolations, {}, optimisticPolicyCategoriesData);
- appendSetupCategoriesOnboardingData(onyxData, setupCategoryTaskReport, setupCategoryTaskParentReport, isSetupCategoriesTaskParentReportArchived);
+ appendSetupCategoriesOnboardingData(onyxData, setupCategoryTaskReport, setupCategoryTaskParentReport, isSetupCategoriesTaskParentReportArchived, currentUserAccountID);
const parameters = {
policyID,
@@ -591,9 +593,10 @@ function createPolicyCategory(
isSetupCategoriesTaskParentReportArchived: boolean,
setupCategoryTaskReport: OnyxEntry,
setupCategoryTaskParentReport: OnyxEntry,
+ currentUserAccountID: number,
) {
const onyxData = buildOptimisticPolicyCategories(policyID, [categoryName]);
- appendSetupCategoriesOnboardingData(onyxData, setupCategoryTaskReport, setupCategoryTaskParentReport, isSetupCategoriesTaskParentReportArchived);
+ appendSetupCategoriesOnboardingData(onyxData, setupCategoryTaskReport, setupCategoryTaskParentReport, isSetupCategoriesTaskParentReportArchived, currentUserAccountID);
const parameters = {
policyID,
categories: JSON.stringify([{name: categoryName}]),
@@ -975,6 +978,7 @@ function deleteWorkspaceCategories(
isSetupCategoriesTaskParentReportArchived: boolean,
setupCategoryTaskReport: OnyxEntry,
setupCategoryTaskParentReport: OnyxEntry,
+ currentUserAccountID: number,
policyTagLists: PolicyTagLists = {},
policyCategories: PolicyCategories = {},
transactionViolations: OnyxCollection = {},
@@ -1024,7 +1028,7 @@ function deleteWorkspaceCategories(
const optimisticPolicyData: Partial = shouldDisableRequiresCategory ? {requiresCategory: false} : {};
pushTransactionViolationsOnyxData(onyxData, policyID, policyTagLists, policyCategories, transactionViolations, optimisticPolicyData, optimisticPolicyCategoriesData);
- appendSetupCategoriesOnboardingData(onyxData, setupCategoryTaskReport, setupCategoryTaskParentReport, isSetupCategoriesTaskParentReportArchived);
+ appendSetupCategoriesOnboardingData(onyxData, setupCategoryTaskReport, setupCategoryTaskParentReport, isSetupCategoriesTaskParentReportArchived, currentUserAccountID);
const parameters = {
policyID,
diff --git a/src/libs/actions/Policy/Member.ts b/src/libs/actions/Policy/Member.ts
index ab9a40eccf46..6e941e65a631 100644
--- a/src/libs/actions/Policy/Member.ts
+++ b/src/libs/actions/Policy/Member.ts
@@ -29,17 +29,7 @@ import * as ReportUtils from '@libs/ReportUtils';
import * as FormActions from '@userActions/FormActions';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
-import type {
- ImportedSpreadsheetMemberData,
- InvitedEmailsToAccountIDs,
- PersonalDetailsList,
- Policy,
- PolicyEmployee,
- PolicyOwnershipChangeChecks,
- Report,
- ReportAction,
- ReportActions,
-} from '@src/types/onyx';
+import type {ImportedSpreadsheetMemberData, InvitedEmailsToAccountIDs, Policy, PolicyEmployee, PolicyOwnershipChangeChecks, Report, ReportAction, ReportActions} from '@src/types/onyx';
import type {PendingAction} from '@src/types/onyx/OnyxCommon';
import type {JoinWorkspaceResolution} from '@src/types/onyx/OriginalMessage';
import type {ApprovalRule} from '@src/types/onyx/Policy';
@@ -55,7 +45,6 @@ type OnyxDataReturnType = {
};
type WorkspaceMembersRoleData = {
- accountID: number;
email: string;
role: ValueOf;
};
@@ -110,12 +99,6 @@ Onyx.connect({
},
});
-let allPersonalDetails: OnyxEntry;
-Onyx.connect({
- key: ONYXKEYS.PERSONAL_DETAILS_LIST,
- callback: (val) => (allPersonalDetails = val),
-});
-
let policyOwnershipChecks: Record;
Onyx.connect({
key: ONYXKEYS.POLICY_OWNERSHIP_CHANGE_CHECKS,
@@ -134,12 +117,6 @@ function isApprover(policy: OnyxEntry, employeeLogin: string) {
);
}
-/** Temporary function alias for isApprover with employeeAccountID */
-function isApproverTemp(policy: OnyxEntry, employeeAccountID: number) {
- const employeeLogin = allPersonalDetails?.[employeeAccountID]?.login;
- return isApprover(policy, employeeLogin ?? '');
-}
-
/**
* Returns the policy of the report
* @deprecated Get the data straight from Onyx - This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850
@@ -434,20 +411,19 @@ function resetAccountingPreferredExporter(policyID: string, loginList: string[])
* Remove the passed members from the policy employeeList
* Please see https://github.com/Expensify/App/blob/main/README.md#Security for more details
*/
-function removeMembers(accountIDs: number[], policyID: string) {
- // In case user selects only themselves (admin), their email will be filtered out and the members
- // array passed will be empty, prevent the function from proceeding in that case as there is no one to remove
- if (accountIDs.length === 0) {
+function removeMembers(policyID: string, selectedMemberEmails: string[], policyMemberEmailsToAccountIDs: Record) {
+ if (selectedMemberEmails.length === 0) {
return;
}
+ const accountIDs = selectedMemberEmails.map((email) => policyMemberEmailsToAccountIDs[email]).filter((id) => id !== undefined);
+
const policyKey = `${ONYXKEYS.COLLECTION.POLICY}${policyID}` as const;
// This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850
// eslint-disable-next-line @typescript-eslint/no-deprecated
const policy = getPolicy(policyID);
const workspaceChats = ReportUtils.getWorkspaceChats(policyID, accountIDs);
- const emailList = accountIDs.map((accountID) => allPersonalDetails?.[accountID]?.login).filter((login) => !!login) as string[];
const optimisticClosedReportActions = workspaceChats.map(() =>
ReportUtils.buildOptimisticClosedReportAction(sessionEmail, policy?.name ?? '', CONST.REPORT.ARCHIVE_REASON.REMOVED_FROM_POLICY),
);
@@ -457,18 +433,19 @@ function removeMembers(accountIDs: number[], policyID: string) {
CONST.REPORT.CHAT_TYPE.POLICY_ADMINS,
policy?.id,
policy?.name ?? '',
- accountIDs.filter((accountID) => {
- const login = allPersonalDetails?.[accountID]?.login;
- const role = login ? policy?.employeeList?.[login]?.role : '';
- return role === CONST.POLICY.ROLE.ADMIN || role === CONST.POLICY.ROLE.AUDITOR;
- }),
+ selectedMemberEmails
+ .filter((login) => {
+ const role = login ? policy?.employeeList?.[login]?.role : '';
+ return role === CONST.POLICY.ROLE.ADMIN || role === CONST.POLICY.ROLE.AUDITOR;
+ })
+ .map((login) => policyMemberEmailsToAccountIDs[login]),
);
- const preferredExporterOnyxData = resetAccountingPreferredExporter(policyID, emailList);
+ const preferredExporterOnyxData = resetAccountingPreferredExporter(policyID, selectedMemberEmails);
const optimisticMembersState: OnyxCollectionInputValue = {};
const successMembersState: OnyxCollectionInputValue = {};
const failureMembersState: OnyxCollectionInputValue = {};
- emailList.forEach((email) => {
+ selectedMemberEmails.forEach((email) => {
optimisticMembersState[email] = {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE};
successMembersState[email] = null;
failureMembersState[email] = {errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.people.error.genericRemove')};
@@ -478,7 +455,7 @@ function removeMembers(accountIDs: number[], policyID: string) {
const employee = policy?.employeeList?.[employeeEmail];
optimisticMembersState[employeeEmail] = optimisticMembersState[employeeEmail] ?? {};
failureMembersState[employeeEmail] = failureMembersState[employeeEmail] ?? {};
- if (employee?.submitsTo && emailList.includes(employee?.submitsTo)) {
+ if (employee?.submitsTo && selectedMemberEmails.includes(employee?.submitsTo)) {
optimisticMembersState[employeeEmail] = {
...optimisticMembersState[employeeEmail],
submitsTo: policy?.owner,
@@ -488,7 +465,7 @@ function removeMembers(accountIDs: number[], policyID: string) {
submitsTo: employee?.submitsTo,
};
}
- if (employee?.forwardsTo && emailList.includes(employee?.forwardsTo)) {
+ if (employee?.forwardsTo && selectedMemberEmails.includes(employee?.forwardsTo)) {
optimisticMembersState[employeeEmail] = {
...optimisticMembersState[employeeEmail],
forwardsTo: policy?.owner,
@@ -498,7 +475,7 @@ function removeMembers(accountIDs: number[], policyID: string) {
forwardsTo: employee?.forwardsTo,
};
}
- if (employee?.overLimitForwardsTo && emailList.includes(employee?.overLimitForwardsTo)) {
+ if (employee?.overLimitForwardsTo && selectedMemberEmails.includes(employee?.overLimitForwardsTo)) {
optimisticMembersState[employeeEmail] = {
...optimisticMembersState[employeeEmail],
overLimitForwardsTo: policy?.owner,
@@ -511,7 +488,7 @@ function removeMembers(accountIDs: number[], policyID: string) {
});
const approvalRules: ApprovalRule[] = policy?.rules?.approvalRules ?? [];
- const optimisticApprovalRules = approvalRules.filter((rule) => !emailList.includes(rule?.approver ?? ''));
+ const optimisticApprovalRules = approvalRules.filter((rule) => !selectedMemberEmails.includes(rule?.approver ?? ''));
const optimisticData: OnyxUpdate[] = [
{
@@ -519,7 +496,7 @@ function removeMembers(accountIDs: number[], policyID: string) {
key: policyKey,
value: {
employeeList: optimisticMembersState,
- approver: emailList.includes(policy?.approver ?? '') ? policy?.owner : policy?.approver,
+ approver: selectedMemberEmails.includes(policy?.approver ?? '') ? policy?.owner : policy?.approver,
rules: {
...(policy?.rules ?? {}),
approvalRules: optimisticApprovalRules,
@@ -634,7 +611,7 @@ function removeMembers(accountIDs: number[], policyID: string) {
if (!isEmptyObject(policy?.primaryLoginsInvited ?? {})) {
// Take the current policy members and remove them optimistically
const employeeListEmails = Object.keys(allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]?.employeeList ?? {});
- const remainingLogins = employeeListEmails.filter((email) => !emailList.includes(email));
+ const remainingLogins = employeeListEmails.filter((email) => !selectedMemberEmails.includes(email));
const invitedPrimaryToSecondaryLogins: Record = {};
if (policy?.primaryLoginsInvited) {
@@ -675,28 +652,21 @@ function removeMembers(accountIDs: number[], policyID: string) {
});
const params: DeleteMembersFromWorkspaceParams = {
- emailList: emailList.join(','),
+ emailList: selectedMemberEmails.join(','),
policyID,
};
API.write(WRITE_COMMANDS.DELETE_MEMBERS_FROM_WORKSPACE, params, {optimisticData, successData, failureData});
}
-function buildUpdateWorkspaceMembersRoleOnyxData(policyID: string, accountIDs: number[], newRole: ValueOf) {
+function buildUpdateWorkspaceMembersRoleOnyxData(policyID: string, selectedMemberEmails: string[], selectedMemberAccountIDs: number[], newRole: ValueOf) {
const previousEmployeeList = {...allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]?.employeeList};
- const memberRoles: WorkspaceMembersRoleData[] = accountIDs.reduce((result: WorkspaceMembersRoleData[], accountID: number) => {
- if (!allPersonalDetails?.[accountID]?.login) {
- return result;
- }
-
- result.push({
- accountID,
- email: allPersonalDetails?.[accountID]?.login ?? '',
+ const memberRoles: WorkspaceMembersRoleData[] = selectedMemberEmails.map((login: string) => {
+ return {
+ email: login,
role: newRole,
- });
-
- return result;
- }, []);
+ };
+ });
const optimisticData: OnyxUpdate[] = [
{
@@ -758,7 +728,7 @@ function buildUpdateWorkspaceMembersRoleOnyxData(policyID: string, accountIDs: n
const failureDataParticipants: Record = {...adminRoom.participants};
const optimisticParticipants: Record = {};
if (newRole === CONST.POLICY.ROLE.ADMIN || newRole === CONST.POLICY.ROLE.AUDITOR) {
- accountIDs.forEach((accountID) => {
+ selectedMemberAccountIDs.forEach((accountID) => {
if (adminRoom?.participants?.[accountID]) {
return;
}
@@ -766,7 +736,7 @@ function buildUpdateWorkspaceMembersRoleOnyxData(policyID: string, accountIDs: n
failureDataParticipants[accountID] = null;
});
} else {
- accountIDs.forEach((accountID) => {
+ selectedMemberAccountIDs.forEach((accountID) => {
if (!adminRoom?.participants?.[accountID]) {
return;
}
@@ -794,8 +764,8 @@ function buildUpdateWorkspaceMembersRoleOnyxData(policyID: string, accountIDs: n
return {optimisticData, successData, failureData, memberRoles};
}
-function updateWorkspaceMembersRole(policyID: string, accountIDs: number[], newRole: ValueOf) {
- const {optimisticData, successData, failureData, memberRoles} = buildUpdateWorkspaceMembersRoleOnyxData(policyID, accountIDs, newRole);
+function updateWorkspaceMembersRole(policyID: string, selectedMemberEmails: string[], selectedMemberAccountIDs: number[], newRole: ValueOf) {
+ const {optimisticData, successData, failureData, memberRoles} = buildUpdateWorkspaceMembersRoleOnyxData(policyID, selectedMemberEmails, selectedMemberAccountIDs, newRole);
const params: UpdateWorkspaceMembersRoleParams = {
policyID,
@@ -1152,11 +1122,10 @@ function askToJoinPolicy(policyID: string) {
/**
* Removes an error after trying to delete a member
*/
-function clearDeleteMemberError(policyID: string, accountID: number) {
- const email = allPersonalDetails?.[accountID]?.login ?? '';
+function clearDeleteMemberError(policyID: string, login: string) {
Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {
employeeList: {
- [email]: {
+ [login]: {
pendingAction: null,
errors: null,
},
@@ -1167,11 +1136,10 @@ function clearDeleteMemberError(policyID: string, accountID: number) {
/**
* Removes an error after trying to add a member
*/
-function clearAddMemberError(policyID: string, accountID: number) {
- const email = allPersonalDetails?.[accountID]?.login ?? '';
+function clearAddMemberError(policyID: string, login: string, accountID: number) {
Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {
employeeList: {
- [email]: null,
+ [login]: null,
},
});
Onyx.merge(`${ONYXKEYS.PERSONAL_DETAILS_LIST}`, {
@@ -1394,5 +1362,4 @@ export {
clearWorkspaceInviteRoleDraft,
setImportedSpreadsheetMemberData,
clearImportedSpreadsheetMemberData,
- isApproverTemp,
};
diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts
index 0b51f01bfece..7bd6d88f1738 100644
--- a/src/libs/actions/Policy/Policy.ts
+++ b/src/libs/actions/Policy/Policy.ts
@@ -70,7 +70,6 @@ import type {
} from '@libs/API/parameters';
import type SetPolicyCashExpenseModeParams from '@libs/API/parameters/SetPolicyCashExpenseModeParams';
import type UpdatePolicyMembersCustomFieldsParams from '@libs/API/parameters/UpdatePolicyMembersCustomFieldsParams';
-import type {ApiRequestCommandParameters} from '@libs/API/types';
import {READ_COMMANDS, SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
import type {CustomRNImageManipulatorResult} from '@libs/cropOrRotateImage/types';
import * as CurrencyUtils from '@libs/CurrencyUtils';
@@ -100,7 +99,6 @@ import type {PolicySelector} from '@pages/home/sidebar/FloatingActionButtonAndPo
import type {Feature} from '@pages/OnboardingInterestedFeatures/types';
import * as PaymentMethods from '@userActions/PaymentMethods';
import * as PersistedRequests from '@userActions/PersistedRequests';
-import type {EnablePolicyFeatureCommand} from '@userActions/RequestConflictUtils';
import {buildTaskData} from '@userActions/Task';
import {getOnboardingMessages} from '@userActions/Welcome/OnboardingFlow';
import type {OnboardingCompanySize, OnboardingPurpose} from '@userActions/Welcome/OnboardingFlow';
@@ -179,10 +177,10 @@ type BuildPolicyDataOptions = {
shouldAddOnboardingTasks?: boolean;
companySize?: OnboardingCompanySize;
userReportedIntegration?: OnboardingAccounting;
- featuresMap?: Feature[];
+ featuresMap?: Array>;
lastUsedPaymentMethod?: LastPaymentMethodType;
- areDistanceRatesEnabled?: boolean;
adminParticipant?: Participant;
+ hasOutstandingChildRequest?: boolean;
};
type DuplicatePolicyDataOptions = {
@@ -2045,8 +2043,8 @@ function buildPolicyData(options: BuildPolicyDataOptions = {}) {
userReportedIntegration,
featuresMap,
lastUsedPaymentMethod,
- areDistanceRatesEnabled,
adminParticipant,
+ hasOutstandingChildRequest = true,
} = options;
const workspaceName = policyName || generateDefaultWorkspaceName(policyOwnerEmail);
@@ -2080,6 +2078,8 @@ function buildPolicyData(options: BuildPolicyDataOptions = {}) {
const isCorporateIntegration = userReportedIntegration && (CONST.POLICY.CONNECTIONS.CORPORATE as readonly string[]).includes(userReportedIntegration);
const workspaceType = isCorporateFeature || isCorporateIntegration ? CONST.POLICY.TYPE.CORPORATE : CONST.POLICY.TYPE.TEAM;
+ const areDistanceRatesEnabled = !!featuresMap?.find((feature) => feature.id === CONST.POLICY.MORE_FEATURES.ARE_DISTANCE_RATES_ENABLED && feature.enabled);
+
// WARNING: The data below should be kept in sync with the API so we create the policy with the correct configuration.
const optimisticData: OnyxUpdate[] = [
{
@@ -2393,6 +2393,11 @@ function buildPolicyData(options: BuildPolicyDataOptions = {}) {
// We need to clone the file to prevent non-indexable errors.
const clonedFile = file ? (createFile(file) as File) : undefined;
+ const features = featuresMap?.reduce>((acc, feature) => {
+ acc[feature.id] = !!feature.enabled;
+ return acc;
+ }, {});
+
const params: CreateWorkspaceParams = {
policyID,
adminsChatReportID,
@@ -2410,7 +2415,7 @@ function buildPolicyData(options: BuildPolicyDataOptions = {}) {
file: clonedFile,
companySize,
userReportedIntegration: userReportedIntegration ?? undefined,
- areDistanceRatesEnabled,
+ features: features ? JSON.stringify(features) : undefined,
};
if (
@@ -2449,7 +2454,7 @@ function buildPolicyData(options: BuildPolicyDataOptions = {}) {
}
if (adminParticipant?.login) {
- const employeeWorkspaceChat = createPolicyExpenseChats(policyID, {[adminParticipant.login]: adminParticipant.accountID ?? CONST.DEFAULT_NUMBER_ID}, true);
+ const employeeWorkspaceChat = createPolicyExpenseChats(policyID, {[adminParticipant.login]: adminParticipant.accountID ?? CONST.DEFAULT_NUMBER_ID}, hasOutstandingChildRequest);
params.memberData = JSON.stringify({
accountID: Number(adminParticipant.accountID),
email: adminParticipant.login,
@@ -6367,69 +6372,6 @@ function setIsComingFromGlobalReimbursementsFlow(value: boolean) {
Onyx.set(ONYXKEYS.IS_COMING_FROM_GLOBAL_REIMBURSEMENTS_FLOW, value);
}
-function updateFeature(
- request: {
- endpoint: EnablePolicyFeatureCommand | typeof WRITE_COMMANDS.TOGGLE_POLICY_PER_DIEM;
- parameters: ApiRequestCommandParameters[EnablePolicyFeatureCommand | typeof WRITE_COMMANDS.TOGGLE_POLICY_PER_DIEM];
- },
- policyID: string,
-) {
- if (request.endpoint === WRITE_COMMANDS.TOGGLE_POLICY_PER_DIEM) {
- API.write(WRITE_COMMANDS.TOGGLE_POLICY_PER_DIEM, {
- policyID,
- enabled: request.parameters.enabled,
- customUnitID: generateCustomUnitID(),
- });
- return;
- }
- // eslint-disable-next-line rulesdir/no-multiple-api-calls
- API.writeWithNoDuplicatesEnableFeatureConflicts(request.endpoint, request.parameters);
-}
-
-function updateInterestedFeatures(features: Feature[], policyID: string, type: string | undefined) {
- let shouldUpgradeToCorporate = false;
-
- const requests: Array<{
- endpoint: EnablePolicyFeatureCommand | typeof WRITE_COMMANDS.TOGGLE_POLICY_PER_DIEM;
- parameters: ApiRequestCommandParameters[EnablePolicyFeatureCommand | typeof WRITE_COMMANDS.TOGGLE_POLICY_PER_DIEM];
- }> = [];
-
- features.forEach((feature) => {
- // If the feature is not enabled by default but it's enabled now, we need to enable it
- if (!feature.enabledByDefault && feature.enabled) {
- if (feature.requiresUpdate && !shouldUpgradeToCorporate) {
- shouldUpgradeToCorporate = true;
- }
- requests.push({
- endpoint: feature.apiEndpoint,
- parameters: {
- policyID,
- enabled: true,
- },
- });
- }
- // If the feature is enabled by default but it's not enabled now, we need to disable it
- if (feature.enabledByDefault && !feature.enabled) {
- requests.push({
- endpoint: feature.apiEndpoint,
- parameters: {
- policyID,
- enabled: false,
- },
- });
- }
- });
-
- const isCorporate = type === CONST.POLICY.TYPE.CORPORATE;
- if (shouldUpgradeToCorporate && !isCorporate) {
- API.write(WRITE_COMMANDS.UPGRADE_TO_CORPORATE, {policyID});
- }
-
- requests.forEach((request) => {
- updateFeature(request, policyID);
- });
-}
-
function clearPolicyTitleFieldError(policyID: string) {
if (!policyID) {
return;
@@ -6571,7 +6513,6 @@ export {
setPolicyAttendeeTrackingEnabled,
setPolicyReimbursableMode,
getCashExpenseReimbursableMode,
- updateInterestedFeatures,
clearPolicyTitleFieldError,
inviteWorkspaceEmployeesToUber,
setWorkspaceConfirmationCurrency,
diff --git a/src/libs/actions/QuickActionNavigation.ts b/src/libs/actions/QuickActionNavigation.ts
index 4f9ecab58b92..a7201904b4aa 100644
--- a/src/libs/actions/QuickActionNavigation.ts
+++ b/src/libs/actions/QuickActionNavigation.ts
@@ -12,6 +12,7 @@ type NavigateToQuickActionParams = {
quickAction: QuickAction;
selectOption: (onSelected: () => void, shouldRestrictAction: boolean) => void;
lastDistanceExpenseType?: DistanceExpenseType;
+ currentUserAccountID: number;
};
function getQuickActionRequestType(action: QuickActionName | undefined, lastDistanceExpenseType?: DistanceExpenseType): IOURequestType | undefined {
@@ -34,7 +35,7 @@ function getQuickActionRequestType(action: QuickActionName | undefined, lastDist
}
function navigateToQuickAction(params: NavigateToQuickActionParams) {
- const {isValidReport, quickAction, selectOption, lastDistanceExpenseType} = params;
+ const {isValidReport, quickAction, selectOption, lastDistanceExpenseType, currentUserAccountID} = params;
const reportID = isValidReport && quickAction?.chatReportID ? quickAction?.chatReportID : generateReportID();
const requestType = getQuickActionRequestType(quickAction?.action, lastDistanceExpenseType);
@@ -53,7 +54,7 @@ function navigateToQuickAction(params: NavigateToQuickActionParams) {
selectOption(() => startMoneyRequest(CONST.IOU.TYPE.PAY, reportID, undefined, true), false);
break;
case CONST.QUICK_ACTIONS.ASSIGN_TASK:
- selectOption(() => startOutCreateTaskQuickAction(isValidReport ? reportID : '', quickAction.targetAccountID ?? CONST.DEFAULT_NUMBER_ID), false);
+ selectOption(() => startOutCreateTaskQuickAction(currentUserAccountID, isValidReport ? reportID : '', quickAction.targetAccountID ?? CONST.DEFAULT_NUMBER_ID), false);
break;
case CONST.QUICK_ACTIONS.TRACK_MANUAL:
case CONST.QUICK_ACTIONS.TRACK_SCAN:
diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts
index 71fb6f57db50..084017d66a7c 100644
--- a/src/libs/actions/Task.ts
+++ b/src/libs/actions/Task.ts
@@ -43,16 +43,19 @@ type ShareDestination = {
shouldUseFullTitleToDisplay: boolean;
};
-let currentUserEmail = '';
-let currentUserAccountID = -1;
-
-Onyx.connect({
- key: ONYXKEYS.SESSION,
- callback: (value) => {
- currentUserEmail = value?.email ?? '';
- currentUserAccountID = value?.accountID ?? CONST.DEFAULT_NUMBER_ID;
- },
-});
+type CreateTaskAndNavigateParams = {
+ parentReportID: string | undefined;
+ title: string;
+ description: string;
+ assigneeEmail: string;
+ currentUserAccountID: number;
+ currentUserEmail: string;
+ assigneeAccountID?: number;
+ assigneeChatReport?: OnyxEntry;
+ policyID?: string;
+ isCreatedUsingMarkdown?: boolean;
+ quickAction?: OnyxEntry;
+};
let allPersonalDetails: OnyxEntry;
Onyx.connect({
@@ -108,17 +111,20 @@ function clearOutTaskInfo(skipConfirmation = false) {
* 3a. The CreatedReportAction for the assignee chat report
* 3b. The TaskReportAction on the assignee chat report
*/
-function createTaskAndNavigate(
- parentReportID: string | undefined,
- title: string,
- description: string,
- assigneeEmail: string,
- assigneeAccountID = 0,
- assigneeChatReport?: OnyxEntry,
- policyID: string = CONST.POLICY.OWNER_EMAIL_FAKE,
- isCreatedUsingMarkdown = false,
- quickAction: OnyxEntry = {},
-) {
+function createTaskAndNavigate(params: CreateTaskAndNavigateParams) {
+ const {
+ parentReportID,
+ title,
+ description,
+ assigneeEmail,
+ currentUserAccountID,
+ currentUserEmail,
+ assigneeAccountID = 0,
+ assigneeChatReport,
+ policyID = CONST.POLICY.OWNER_EMAIL_FAKE,
+ isCreatedUsingMarkdown = false,
+ quickAction = {},
+ } = params;
if (!parentReportID) {
return;
}
@@ -465,7 +471,7 @@ function completeTask(taskReport: OnyxEntry, reportIDFromActio
/**
* Reopen a closed task
*/
-function reopenTask(taskReport: OnyxEntry, reportIDFromAction?: string) {
+function reopenTask(taskReport: OnyxEntry, currentUserAccountID: number, reportIDFromAction?: string) {
const taskReportID = taskReport?.reportID ?? reportIDFromAction;
if (!taskReportID) {
return;
@@ -628,7 +634,14 @@ function editTask(report: OnyxTypes.Report, {title, description}: OnyxTypes.Task
API.write(WRITE_COMMANDS.EDIT_TASK, parameters, {optimisticData, successData, failureData});
}
-function editTaskAssignee(report: OnyxTypes.Report, sessionAccountID: number, assigneeEmail: string, assigneeAccountID: number | null = 0, assigneeChatReport?: OnyxEntry) {
+function editTaskAssignee(
+ report: OnyxTypes.Report,
+ sessionAccountID: number,
+ assigneeEmail: string,
+ currentUserAccountID: number,
+ assigneeAccountID: number | null = 0,
+ assigneeChatReport?: OnyxEntry,
+) {
// Create the EditedReportAction on the task
const editTaskReportAction = ReportUtils.buildOptimisticChangedTaskAssigneeReportAction(assigneeAccountID ?? CONST.DEFAULT_NUMBER_ID);
const reportName = report.reportName?.trim();
@@ -814,7 +827,7 @@ function setAssigneeChatReport(chatReport: OnyxTypes.Report, isOptimisticReport
}
}
-function setNewOptimisticAssignee(assigneeLogin: string, assigneeAccountID: number) {
+function setNewOptimisticAssignee(assigneeLogin: string, assigneeAccountID: number, currentUserAccountID: number) {
const report: ReportUtils.OptimisticChatReport = ReportUtils.buildOptimisticChatReport({
participantList: [assigneeAccountID, currentUserAccountID],
reportName: '',
@@ -843,6 +856,7 @@ function setNewOptimisticAssignee(assigneeLogin: string, assigneeAccountID: numb
function setAssigneeValue(
assigneeEmail: string,
assigneeAccountID: number,
+ currentUserAccountID: number,
shareToReportID?: string,
chatReport?: OnyxEntry,
isCurrentUser = false,
@@ -863,7 +877,7 @@ function setAssigneeValue(
}
// If chat report is still not found we need to build new optimistic chat report
if (!report) {
- report = setNewOptimisticAssignee(assigneeEmail, assigneeAccountID).assigneeReport;
+ report = setNewOptimisticAssignee(assigneeEmail, assigneeAccountID, currentUserAccountID).assigneeReport;
}
const reportMetadata = ReportUtils.getReportMetadata(report?.reportID);
@@ -905,14 +919,14 @@ function setParentReportID(parentReportID: string) {
/**
* Clears out the task info from the store and navigates to the NewTaskDetails page
*/
-function clearOutTaskInfoAndNavigate(reportID?: string, chatReport?: OnyxEntry, accountID = 0, skipConfirmation = false) {
+function clearOutTaskInfoAndNavigate(currentUserAccountID: number, reportID?: string, chatReport?: OnyxEntry, accountID = 0, skipConfirmation = false) {
clearOutTaskInfo(skipConfirmation);
if (reportID && reportID !== '0') {
setParentReportID(reportID);
}
if (accountID > 0) {
const accountLogin = allPersonalDetails?.[accountID]?.login ?? '';
- setAssigneeValue(accountLogin, accountID, reportID, chatReport, accountID === currentUserAccountID, skipConfirmation);
+ setAssigneeValue(accountLogin, accountID, currentUserAccountID, reportID, chatReport, accountID === currentUserAccountID, skipConfirmation);
}
Navigation.navigate(ROUTES.NEW_TASK_DETAILS.getRoute(Navigation.getReportRHPActiveRoute()));
}
@@ -920,12 +934,12 @@ function clearOutTaskInfoAndNavigate(reportID?: string, chatReport?: OnyxEntry): stri
/**
* Cancels a task by setting the report state to SUBMITTED and status to CLOSED
*/
-function deleteTask(report: OnyxEntry, isReportArchived: boolean) {
+function deleteTask(report: OnyxEntry, isReportArchived: boolean, currentUserAccountID: number) {
if (!report) {
return;
}
@@ -1292,7 +1306,12 @@ function clearTaskErrors(reportID: string | undefined) {
});
}
-function getFinishOnboardingTaskOnyxData(taskReport: OnyxEntry, taskParentReport: OnyxEntry, isParentReportArchived: boolean): OnyxData {
+function getFinishOnboardingTaskOnyxData(
+ taskReport: OnyxEntry,
+ taskParentReport: OnyxEntry,
+ isParentReportArchived: boolean,
+ currentUserAccountID: number,
+): OnyxData {
if (taskReport && canActionTask(taskReport, currentUserAccountID, taskParentReport, isParentReportArchived)) {
if (taskReport) {
if (taskReport.stateNum !== CONST.REPORT.STATE_NUM.APPROVED || taskReport.statusNum !== CONST.REPORT.STATUS_NUM.APPROVED) {
@@ -1307,10 +1326,11 @@ function completeTestDriveTask(
viewTourTaskReport: OnyxEntry,
viewTourTaskParentReport: OnyxEntry,
isViewTourTaskParentReportArchived: boolean,
+ currentUserAccountID: number,
shouldUpdateSelfTourViewedOnlyLocally = false,
) {
setSelfTourViewed(shouldUpdateSelfTourViewedOnlyLocally);
- getFinishOnboardingTaskOnyxData(viewTourTaskReport, viewTourTaskParentReport, isViewTourTaskParentReportArchived);
+ getFinishOnboardingTaskOnyxData(viewTourTaskReport, viewTourTaskParentReport, isViewTourTaskParentReportArchived, currentUserAccountID);
}
export {
diff --git a/src/libs/actions/Tour.ts b/src/libs/actions/Tour.ts
index 147941067d32..a19e566ca481 100644
--- a/src/libs/actions/Tour.ts
+++ b/src/libs/actions/Tour.ts
@@ -15,6 +15,7 @@ function startTestDrive(
viewTourTaskReport: OnyxEntry,
viewTourTaskParentReport: OnyxEntry,
isViewTourTaskParentReportArchived: boolean,
+ currentUserAccountID: number,
) {
// eslint-disable-next-line @typescript-eslint/no-deprecated
InteractionManager.runAfterInteractions(() => {
@@ -26,7 +27,7 @@ function startTestDrive(
introSelected?.choice === CONST.ONBOARDING_CHOICES.TRACK_WORKSPACE ||
(introSelected?.choice === CONST.ONBOARDING_CHOICES.SUBMIT && introSelected.inviteType === CONST.ONBOARDING_INVITE_TYPES.WORKSPACE)
) {
- completeTestDriveTask(viewTourTaskReport, viewTourTaskParentReport, isViewTourTaskParentReportArchived, shouldUpdateSelfTourViewedOnlyLocally);
+ completeTestDriveTask(viewTourTaskReport, viewTourTaskParentReport, isViewTourTaskParentReportArchived, currentUserAccountID, shouldUpdateSelfTourViewedOnlyLocally);
Navigation.navigate(ROUTES.TEST_DRIVE_DEMO_ROOT);
} else {
Navigation.navigate(ROUTES.TEST_DRIVE_MODAL_ROOT.route);
diff --git a/src/libs/actions/User.ts b/src/libs/actions/User.ts
index cc53208c4925..68fe1e6c76d6 100644
--- a/src/libs/actions/User.ts
+++ b/src/libs/actions/User.ts
@@ -1410,10 +1410,6 @@ function setIsDebugModeEnabled(isDebugModeEnabled: boolean) {
Onyx.set(ONYXKEYS.IS_DEBUG_MODE_ENABLED, isDebugModeEnabled);
}
-function setShouldBlockTransactionThreadReportCreation(shouldBlockTransactionThreadReportCreation: boolean) {
- Onyx.merge(ONYXKEYS.ACCOUNT, {shouldBlockTransactionThreadReportCreation});
-}
-
function lockAccount() {
const optimisticData: OnyxUpdate[] = [
{
@@ -1505,7 +1501,6 @@ export {
addPendingContactMethod,
clearValidateCodeActionError,
setIsDebugModeEnabled,
- setShouldBlockTransactionThreadReportCreation,
resetValidateActionCodeSent,
lockAccount,
requestUnlockAccount,
diff --git a/src/libs/actions/connections/SageIntacct.ts b/src/libs/actions/connections/SageIntacct.ts
index 165577e3164d..fdcafb7deebd 100644
--- a/src/libs/actions/connections/SageIntacct.ts
+++ b/src/libs/actions/connections/SageIntacct.ts
@@ -139,7 +139,10 @@ function getCommandForMapping(mappingName: ValueOf
-
-
- {translate('additionalDetailsStep.failedKYCTextBefore')}
-
- {CONST.EMAIL.CONCIERGE}
-
- {translate('additionalDetailsStep.failedKYCTextAfter')}
-
+
+
+
);
diff --git a/src/pages/OnboardingInterestedFeatures/BaseOnboardingInterestedFeatures.tsx b/src/pages/OnboardingInterestedFeatures/BaseOnboardingInterestedFeatures.tsx
index 5b0390be1e58..4c44d3194b7f 100644
--- a/src/pages/OnboardingInterestedFeatures/BaseOnboardingInterestedFeatures.tsx
+++ b/src/pages/OnboardingInterestedFeatures/BaseOnboardingInterestedFeatures.tsx
@@ -19,14 +19,13 @@ import useOnyx from '@hooks/useOnyx';
import usePermissions from '@hooks/usePermissions';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
-import {createWorkspace, generatePolicyID, updateInterestedFeatures} from '@libs/actions/Policy/Policy';
+import {createWorkspace, generatePolicyID} from '@libs/actions/Policy/Policy';
import {completeOnboarding} from '@libs/actions/Report';
import {setOnboardingAdminsChatReportID, setOnboardingPolicyID} from '@libs/actions/Welcome';
-import {WRITE_COMMANDS} from '@libs/API/types';
import {navigateAfterOnboardingWithMicrotaskQueue} from '@libs/navigateAfterOnboarding';
import Navigation from '@libs/Navigation/Navigation';
import {isPaidGroupPolicy, isPolicyAdmin} from '@libs/PolicyUtils';
-import CONST, {FEATURE_IDS} from '@src/CONST';
+import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
@@ -59,69 +58,59 @@ function BaseOnboardingInterestedFeatures({shouldUseNativeStyles}: BaseOnboardin
const features: Feature[] = useMemo(() => {
return [
{
- id: FEATURE_IDS.CATEGORIES,
+ id: CONST.POLICY.MORE_FEATURES.ARE_CATEGORIES_ENABLED,
title: translate('workspace.moreFeatures.categories.title'),
icon: illustrations.FolderOpen,
enabledByDefault: true,
- apiEndpoint: WRITE_COMMANDS.ENABLE_POLICY_CATEGORIES,
},
{
- id: FEATURE_IDS.ACCOUNTING,
+ id: CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED,
title: translate('workspace.moreFeatures.connections.title'),
icon: illustrations.Accounting,
enabledByDefault: !!userReportedIntegration,
- apiEndpoint: WRITE_COMMANDS.ENABLE_POLICY_CONNECTIONS,
},
{
- id: FEATURE_IDS.COMPANY_CARDS,
+ id: CONST.POLICY.MORE_FEATURES.ARE_COMPANY_CARDS_ENABLED,
title: translate('workspace.moreFeatures.companyCards.title'),
icon: illustrations.CompanyCard,
enabledByDefault: true,
- apiEndpoint: WRITE_COMMANDS.ENABLE_POLICY_COMPANY_CARDS,
},
{
- id: FEATURE_IDS.WORKFLOWS,
+ id: CONST.POLICY.MORE_FEATURES.ARE_WORKFLOWS_ENABLED,
title: translate('workspace.moreFeatures.workflows.title'),
icon: illustrations.Workflows,
enabledByDefault: true,
- apiEndpoint: WRITE_COMMANDS.ENABLE_POLICY_WORKFLOWS,
},
{
- id: FEATURE_IDS.INVOICES,
+ id: CONST.POLICY.MORE_FEATURES.ARE_INVOICES_ENABLED,
title: translate('workspace.moreFeatures.invoices.title'),
icon: illustrations.InvoiceBlue,
- apiEndpoint: WRITE_COMMANDS.ENABLE_POLICY_INVOICING,
},
{
- id: FEATURE_IDS.RULES,
+ id: CONST.POLICY.MORE_FEATURES.ARE_RULES_ENABLED,
title: translate('workspace.moreFeatures.rules.title'),
icon: illustrations.Rules,
- apiEndpoint: WRITE_COMMANDS.SET_POLICY_RULES_ENABLED,
requiresUpdate: true,
},
{
- id: FEATURE_IDS.DISTANCE_RATES,
+ id: CONST.POLICY.MORE_FEATURES.ARE_DISTANCE_RATES_ENABLED,
title: translate('workspace.moreFeatures.distanceRates.title'),
icon: illustrations.Car,
- apiEndpoint: WRITE_COMMANDS.ENABLE_POLICY_DISTANCE_RATES,
},
{
- id: FEATURE_IDS.EXPENSIFY_CARD,
+ id: CONST.POLICY.MORE_FEATURES.ARE_EXPENSIFY_CARDS_ENABLED,
title: translate('workspace.moreFeatures.expensifyCard.title'),
icon: illustrations.HandCard,
- apiEndpoint: WRITE_COMMANDS.ENABLE_POLICY_EXPENSIFY_CARDS,
},
{
- id: FEATURE_IDS.TAGS,
+ id: CONST.POLICY.MORE_FEATURES.ARE_TAGS_ENABLED,
title: translate('workspace.moreFeatures.tags.title'),
icon: illustrations.Tag,
- apiEndpoint: WRITE_COMMANDS.ENABLE_POLICY_TAGS,
},
{
- id: FEATURE_IDS.PER_DIEM,
+ id: CONST.POLICY.MORE_FEATURES.ARE_PER_DIEM_RATES_ENABLED,
title: translate('workspace.moreFeatures.perDiem.title'),
icon: illustrations.PerDiem,
- apiEndpoint: WRITE_COMMANDS.TOGGLE_POLICY_PER_DIEM,
requiresUpdate: true,
},
];
@@ -144,7 +133,7 @@ function BaseOnboardingInterestedFeatures({shouldUseNativeStyles}: BaseOnboardin
}
const shouldCreateWorkspace = !onboardingPolicyID && !paidGroupPolicy;
- const newUserReportedIntegration = selectedFeatures.some((feature) => feature === 'accounting') ? userReportedIntegration : undefined;
+ const newUserReportedIntegration = selectedFeatures.some((feature) => feature === CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED) ? userReportedIntegration : undefined;
const featuresMap = features.map((feature) => ({
...feature,
enabled: selectedFeatures.includes(feature.id),
@@ -152,7 +141,7 @@ function BaseOnboardingInterestedFeatures({shouldUseNativeStyles}: BaseOnboardin
// We need `adminsChatReportID` for `completeOnboarding`, but at the same time, we don't want to call `createWorkspace` more than once.
// If we have already created a workspace, we want to reuse the `onboardingAdminsChatReportID` and `onboardingPolicyID`.
- const {adminsChatReportID, policyID, type} = shouldCreateWorkspace
+ const {adminsChatReportID, policyID} = shouldCreateWorkspace
? createWorkspace({
policyOwnerEmail: undefined,
makeMeAdmin: true,
@@ -166,11 +155,7 @@ function BaseOnboardingInterestedFeatures({shouldUseNativeStyles}: BaseOnboardin
userReportedIntegration: newUserReportedIntegration,
featuresMap,
})
- : {adminsChatReportID: onboardingAdminsChatReportID, policyID: onboardingPolicyID, type: undefined};
-
- if (policyID) {
- updateInterestedFeatures(featuresMap, policyID, type);
- }
+ : {adminsChatReportID: onboardingAdminsChatReportID, policyID: onboardingPolicyID};
if (shouldCreateWorkspace) {
setOnboardingAdminsChatReportID(adminsChatReportID);
@@ -227,14 +212,14 @@ function BaseOnboardingInterestedFeatures({shouldUseNativeStyles}: BaseOnboardin
// Create items for enabled features
const enabledFeatures: Feature[] = features
- .filter((feature) => !!feature.enabledByDefault || feature.id === FEATURE_IDS.ACCOUNTING)
+ .filter((feature) => !!feature.enabledByDefault || feature.id === CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED)
.map((feature) => ({
...feature,
}));
// Create items for features they may be interested in
const mayBeInterestedFeatures: Feature[] = features
- .filter((feature) => !feature.enabledByDefault && feature.id !== FEATURE_IDS.ACCOUNTING)
+ .filter((feature) => !feature.enabledByDefault && feature.id !== CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED)
.map((feature) => ({
...feature,
}));
diff --git a/src/pages/OnboardingInterestedFeatures/types.ts b/src/pages/OnboardingInterestedFeatures/types.ts
index ac2d50e73b69..a13dd2e4add5 100644
--- a/src/pages/OnboardingInterestedFeatures/types.ts
+++ b/src/pages/OnboardingInterestedFeatures/types.ts
@@ -1,5 +1,3 @@
-import type {EnablePolicyFeatureCommand} from '@libs/actions/RequestConflictUtils';
-import type {WRITE_COMMANDS} from '@libs/API/types';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
import type {OnboardingModalNavigatorParamList} from '@libs/Navigation/types';
import type SCREENS from '@src/SCREENS';
@@ -17,7 +15,6 @@ type Feature = {
title: string;
icon: IconAsset;
enabledByDefault?: boolean;
- apiEndpoint: EnablePolicyFeatureCommand | typeof WRITE_COMMANDS.TOGGLE_POLICY_PER_DIEM;
requiresUpdate?: boolean;
enabled?: boolean;
};
diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx
index ab37fd6b9a0d..c14252210698 100755
--- a/src/pages/ProfilePage.tsx
+++ b/src/pages/ProfilePage.tsx
@@ -270,7 +270,7 @@ function ProfilePage({route}: ProfilePageProps) {
title={`${translate('privateNotes.title')}`}
titleStyle={styles.flex1}
icon={Expensicons.Pencil}
- onPress={() => navigateToPrivateNotes(report, session, navigateBackTo)}
+ onPress={() => navigateToPrivateNotes(report, session?.accountID ?? CONST.DEFAULT_NUMBER_ID, navigateBackTo)}
wrapperStyle={styles.breakAll}
shouldShowRightIcon
brickRoadIndicator={hasErrorInPrivateNotes(report) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx
index 5ff691ce20ed..5b4257e2d187 100644
--- a/src/pages/ReportDetailsPage.tsx
+++ b/src/pages/ReportDetailsPage.tsx
@@ -22,6 +22,8 @@ import ReportActionAvatars from '@components/ReportActionAvatars';
import ScreenWrapper from '@components/ScreenWrapper';
import ScrollView from '@components/ScrollView';
import {useSearchContext} from '@components/Search/SearchContext';
+import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
+import useDeleteTransactions from '@hooks/useDeleteTransactions';
import useDuplicateTransactionsAndViolations from '@hooks/useDuplicateTransactionsAndViolations';
import useGetIOUReportFromReportAction from '@hooks/useGetIOUReportFromReportAction';
import useLocalize from '@hooks/useLocalize';
@@ -97,7 +99,7 @@ import {
} from '@libs/ReportUtils';
import StringUtils from '@libs/StringUtils';
import {isDemoTransaction} from '@libs/TransactionUtils';
-import {deleteMoneyRequest, deleteTrackExpense, getNavigationUrlAfterTrackExpenseDelete, getNavigationUrlOnMoneyRequestDelete} from '@userActions/IOU';
+import {deleteTrackExpense, getNavigationUrlAfterTrackExpenseDelete, getNavigationUrlOnMoneyRequestDelete} from '@userActions/IOU';
import {
clearAvatarErrors,
clearPolicyRoomNameErrors,
@@ -170,7 +172,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail
const [transactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`, {canBeMissing: true});
const [isDebugModeEnabled = false] = useOnyx(ONYXKEYS.IS_DEBUG_MODE_ENABLED, {canBeMissing: true});
const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {canBeMissing: false});
- const [session] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: false});
+ const currentUserPersonalDetails = useCurrentUserPersonalDetails();
const [isLastMemberLeavingGroupModalVisible, setIsLastMemberLeavingGroupModalVisible] = useState(false);
const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false);
const isPolicyAdmin = useMemo(() => isPolicyAdminUtil(policy), [policy]);
@@ -191,8 +193,8 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail
const isTrackExpenseReport = useMemo(() => isTrackExpenseReportUtil(report), [report]);
const isCanceledTaskReport = isCanceledTaskReportUtil(report, parentReportAction);
const isParentReportArchived = useReportIsArchived(parentReport?.reportID);
- const isTaskModifiable = canModifyTask(report, session?.accountID, isParentReportArchived);
- const isTaskActionable = canActionTask(report, session?.accountID, parentReport, isParentReportArchived);
+ const isTaskModifiable = canModifyTask(report, currentUserPersonalDetails?.accountID, isParentReportArchived);
+ const isTaskActionable = canActionTask(report, currentUserPersonalDetails?.accountID, parentReport, isParentReportArchived);
const canEditReportDescription = useMemo(() => canEditReportDescriptionUtil(report, policy), [report, policy]);
const shouldShowReportDescription = isChatRoom && (canEditReportDescription || report.description !== '') && (isTaskReport ? isTaskModifiable : true);
const isExpenseReport = isMoneyRequestReport || isInvoiceReport || isMoneyRequest;
@@ -259,7 +261,9 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail
const {iouReport, chatReport: chatIOUReport, isChatIOUReportArchived} = useGetIOUReportFromReportAction(requestParentReportAction);
const isActionOwner =
- typeof requestParentReportAction?.actorAccountID === 'number' && typeof session?.accountID === 'number' && requestParentReportAction.actorAccountID === session?.accountID;
+ typeof requestParentReportAction?.actorAccountID === 'number' &&
+ typeof currentUserPersonalDetails?.accountID === 'number' &&
+ requestParentReportAction.actorAccountID === currentUserPersonalDetails?.accountID;
const isDeletedParentAction = isDeletedAction(requestParentReportAction);
const moneyRequestReport: OnyxEntry = useMemo(() => {
@@ -282,6 +286,12 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail
const iouTransactionID = isMoneyRequestAction(requestParentReportAction) ? getOriginalMessage(requestParentReportAction)?.IOUTransactionID : undefined;
const [iouTransaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${iouTransactionID}`, {canBeMissing: true});
const {duplicateTransactions, duplicateTransactionViolations} = useDuplicateTransactionsAndViolations(iouTransactionID ? [iouTransactionID] : []);
+ const {deleteTransactions} = useDeleteTransactions({
+ report: parentReport,
+ reportActions: requestParentReportAction ? [requestParentReportAction] : [],
+ policy,
+ });
+ const {currentSearchHash} = useSearchContext();
const isCardTransactionCanBeDeleted = canDeleteCardTransactionByLiabilityType(iouTransaction);
const shouldShowDeleteButton = shouldShowTaskDeleteButton || (canDeleteRequest && isCardTransactionCanBeDeleted) || isDemoTransaction(iouTransaction);
const [reportAttributes] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {canBeMissing: true, selector: reportsSelector});
@@ -316,7 +326,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail
}, [isPolicyEmployee, isRootGroupChat, report, isPolicyAdmin]);
const shouldShowLeaveButton = canLeaveChat(report, policy, !!reportNameValuePairs?.private_isArchived);
- const shouldShowGoToWorkspace = shouldShowPolicy(policy, false, session?.email) && !policy?.isJoinRequestPending;
+ const shouldShowGoToWorkspace = shouldShowPolicy(policy, false, currentUserPersonalDetails?.email) && !policy?.isJoinRequestPending;
const reportName = Parser.htmlToText(getReportName(report, undefined, undefined, undefined, undefined, reportAttributes));
@@ -456,7 +466,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail
icon: Expensicons.Pencil,
isAnonymousAction: false,
shouldShowRightIcon: true,
- action: () => navigateToPrivateNotes(report, session, backTo),
+ action: () => navigateToPrivateNotes(report, currentUserPersonalDetails.accountID, backTo),
brickRoadIndicator: hasErrorInPrivateNotes(report) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined,
});
}
@@ -471,7 +481,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail
isAnonymousAction: false,
action: callFunctionIfActionIsAllowed(() => {
Navigation.goBack(backTo);
- reopenTask(report);
+ reopenTask(report, currentUserPersonalDetails?.accountID);
}),
});
}
@@ -554,7 +564,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail
parentReportAction,
iouTransactionID,
moneyRequestReport?.reportID,
- session,
+ currentUserPersonalDetails.accountID,
isTaskActionable,
isRootGroupChat,
leaveChat,
@@ -778,7 +788,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail
const deleteTransaction = useCallback(() => {
if (caseID === CASES.DEFAULT) {
- deleteTask(report, isReportArchived);
+ deleteTask(report, isReportArchived, currentUserPersonalDetails.accountID);
return;
}
@@ -802,33 +812,27 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail
isChatReportArchived: isMoneyRequestReportArchived,
isChatIOUReportArchived,
});
- } else {
- deleteMoneyRequest(
- iouTransactionID,
- requestParentReportAction,
- duplicateTransactions,
- duplicateTransactionViolations,
- iouReport,
- chatIOUReport,
- isChatIOUReportArchived,
- isSingleTransactionView,
- );
+ } else if (iouTransactionID) {
+ deleteTransactions([iouTransactionID], duplicateTransactions, duplicateTransactionViolations, currentSearchHash, isSingleTransactionView);
removeTransaction(iouTransactionID);
}
}, [
- duplicateTransactions,
- duplicateTransactionViolations,
caseID,
+ requestParentReportAction,
+ report,
+ isReportArchived,
+ currentUserPersonalDetails.accountID,
iouTransactionID,
+ duplicateTransactions,
+ duplicateTransactionViolations,
isSingleTransactionView,
moneyRequestReport,
removeTransaction,
- report,
- requestParentReportAction,
- isReportArchived,
isMoneyRequestReportArchived,
iouReport,
chatIOUReport,
+ deleteTransactions,
+ currentSearchHash,
isChatIOUReportArchived,
]);
diff --git a/src/pages/RestrictedAction/Workspace/WorkspaceOwnerRestrictedAction/index.native.tsx b/src/pages/RestrictedAction/Workspace/WorkspaceOwnerRestrictedAction/index.native.tsx
index 9754bbb2b3dc..02f0ced2c63e 100644
--- a/src/pages/RestrictedAction/Workspace/WorkspaceOwnerRestrictedAction/index.native.tsx
+++ b/src/pages/RestrictedAction/Workspace/WorkspaceOwnerRestrictedAction/index.native.tsx
@@ -20,7 +20,7 @@ function WorkspaceOwnerRestrictedAction() {
const styles = useThemeStyles();
const activeRoute = useMemo(() => Navigation.getActiveRoute(), []);
- const goToSubscriptions = useCallback(() => {
+ const goToSubscription = useCallback(() => {
Navigation.closeRHPFlow();
Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION.getRoute(activeRoute));
}, [activeRoute]);
@@ -52,8 +52,8 @@ function WorkspaceOwnerRestrictedAction() {
{translate('workspace.restrictedAction.addPaymentCardToContinueUsingWorkspace')}
{translate('workspace.restrictedAction.youWillNeedToAddOrUpdatePaymentCard')}
diff --git a/src/pages/ScheduleCall/ScheduleCallConfirmationPage.tsx b/src/pages/ScheduleCall/ScheduleCallConfirmationPage.tsx
index f8b61024b54b..ad0610a2262d 100644
--- a/src/pages/ScheduleCall/ScheduleCallConfirmationPage.tsx
+++ b/src/pages/ScheduleCall/ScheduleCallConfirmationPage.tsx
@@ -61,7 +61,7 @@ function ScheduleCallConfirmationPage() {
accountID: scheduleCallDraft.guide.accountID,
login: scheduleCallDraft.guide.email,
displayName: scheduleCallDraft.guide.email,
- avatar: getDefaultAvatarURL(scheduleCallDraft.guide.accountID),
+ avatar: getDefaultAvatarURL(scheduleCallDraft.guide.accountID, scheduleCallDraft.guide.email),
})
: null,
[personalDetails, scheduleCallDraft?.guide?.accountID, scheduleCallDraft?.guide?.email],
diff --git a/src/pages/Search/EmptySearchView.tsx b/src/pages/Search/EmptySearchView.tsx
index 99d9ae707f12..805717e94f50 100644
--- a/src/pages/Search/EmptySearchView.tsx
+++ b/src/pages/Search/EmptySearchView.tsx
@@ -288,6 +288,7 @@ function EmptySearchViewContent({
viewTourTaskReport,
viewTourTaskParentReport,
isViewTourTaskParentReportArchived,
+ currentUserPersonalDetails.accountID,
);
};
diff --git a/src/pages/Search/SearchMoneyRequestReportPage.tsx b/src/pages/Search/SearchMoneyRequestReportPage.tsx
index 17ffd1d7e9aa..c20ef96f56a2 100644
--- a/src/pages/Search/SearchMoneyRequestReportPage.tsx
+++ b/src/pages/Search/SearchMoneyRequestReportPage.tsx
@@ -50,7 +50,6 @@ function SearchMoneyRequestReportPage({route}: SearchMoneyRequestPageProps) {
const reportIDFromRoute = getNonEmptyStringOnyxID(route.params?.reportID);
const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportIDFromRoute}`, {allowStaleData: true, canBeMissing: true});
- const shouldWaitForReportSync = report?.reportID !== reportIDFromRoute;
const [reportMetadata = defaultReportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportIDFromRoute}`, {canBeMissing: true, allowStaleData: true});
const [policies = getEmptyObject>>()] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {allowStaleData: true, canBeMissing: false});
@@ -134,7 +133,6 @@ function SearchMoneyRequestReportPage({route}: SearchMoneyRequestPageProps) {
reportMetadata={reportMetadata}
policy={policy}
shouldDisplayReportFooter={isCurrentReportLoadedFromOnyx}
- shouldWaitForReportSync={shouldWaitForReportSync}
key={report?.reportID}
backToRoute={route.params.backTo}
/>
@@ -171,7 +169,6 @@ function SearchMoneyRequestReportPage({route}: SearchMoneyRequestPageProps) {
reportMetadata={reportMetadata}
policy={policy}
shouldDisplayReportFooter={isCurrentReportLoadedFromOnyx}
- shouldWaitForReportSync={shouldWaitForReportSync}
key={report?.reportID}
backToRoute={route.params.backTo}
/>
diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx
index 1a62f5de57cf..fa4851a4f3d6 100644
--- a/src/pages/Search/SearchPage.tsx
+++ b/src/pages/Search/SearchPage.tsx
@@ -651,7 +651,7 @@ function SearchPage({route}: SearchPageProps) {
const metadata = searchResults?.search;
const shouldShowOfflineIndicator = !!searchResults?.data;
- const shouldShowFooter = !!metadata?.count;
+ const shouldShowFooter = !!metadata?.count || selectedTransactionsKeys.length > 0;
const offlineIndicatorStyle = useMemo(() => {
if (shouldShowFooter) {
@@ -699,11 +699,11 @@ function SearchPage({route}: SearchPageProps) {
}, []);
const footerData = useMemo(() => {
- const shouldUseClientTotal = selectedTransactionsKeys.length > 0 && !areAllMatchingItemsSelected;
-
- const currency = metadata?.currency;
+ const shouldUseClientTotal = !metadata?.count || (selectedTransactionsKeys.length > 0 && !areAllMatchingItemsSelected);
+ const selectedTransactionItems = Object.values(selectedTransactions);
+ const currency = metadata?.currency ?? selectedTransactionItems.at(0)?.convertedCurrency;
const count = shouldUseClientTotal ? selectedTransactionsKeys.length : metadata?.count;
- const total = shouldUseClientTotal ? Object.values(selectedTransactions).reduce((acc, transaction) => acc - (transaction.convertedAmount ?? 0), 0) : metadata?.total;
+ const total = shouldUseClientTotal ? selectedTransactionItems.reduce((acc, transaction) => acc - (transaction.convertedAmount ?? 0), 0) : metadata?.total;
return {count, total, currency};
}, [areAllMatchingItemsSelected, metadata?.count, metadata?.currency, metadata?.total, selectedTransactions, selectedTransactionsKeys.length]);
diff --git a/src/pages/Search/SearchPageNarrow.tsx b/src/pages/Search/SearchPageNarrow.tsx
index db6978ed5dee..fc0ecc66dd28 100644
--- a/src/pages/Search/SearchPageNarrow.tsx
+++ b/src/pages/Search/SearchPageNarrow.tsx
@@ -78,7 +78,7 @@ function SearchPageNarrow({
const {windowHeight} = useWindowDimensions();
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
- const {clearSelectedTransactions} = useSearchContext();
+ const {clearSelectedTransactions, selectedTransactions} = useSearchContext();
const [searchRouterListVisible, setSearchRouterListVisible] = useState(false);
const {isOffline} = useNetwork();
const currentSearchResultsKey = queryJSON?.hash ?? CONST.DEFAULT_NUMBER_ID;
@@ -166,7 +166,7 @@ function SearchPageNarrow({
);
}
- const shouldShowFooter = !!metadata?.count;
+ const shouldShowFooter = !!metadata?.count || Object.keys(selectedTransactions).length > 0;
const isDataLoaded = isSearchDataLoaded(searchResults, queryJSON);
const shouldShowLoadingState = !isOffline && (!isDataLoaded || !!currentSearchResults?.search?.isLoading);
diff --git a/src/pages/Share/SubmitDetailsPage.tsx b/src/pages/Share/SubmitDetailsPage.tsx
index a9089f5577f7..6e7cb02c3ee3 100644
--- a/src/pages/Share/SubmitDetailsPage.tsx
+++ b/src/pages/Share/SubmitDetailsPage.tsx
@@ -52,7 +52,6 @@ function SubmitDetailsPage({
const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${getIOURequestPolicyID(transaction, report)}`, {canBeMissing: false});
const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${getIOURequestPolicyID(transaction, report)}`, {canBeMissing: false});
const [lastLocationPermissionPrompt] = useOnyx(ONYXKEYS.NVP_LAST_LOCATION_PERMISSION_PROMPT, {canBeMissing: false});
- const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true});
const [reportAttributesDerived] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {canBeMissing: true, selector: reportsSelector});
const [currentDate] = useOnyx(ONYXKEYS.CURRENT_DATE, {canBeMissing: true});
const [validFilesToUpload] = useOnyx(ONYXKEYS.VALIDATED_FILE_OBJECT, {canBeMissing: true});
@@ -68,7 +67,7 @@ function SubmitDetailsPage({
const [errorMessage, setErrorMessage] = useState(undefined);
const {isBetaEnabled} = usePermissions();
- const shouldGenerateTransactionThreadReport = !isBetaEnabled(CONST.BETAS.NO_OPTIMISTIC_TRANSACTION_THREADS) || !account?.shouldBlockTransactionThreadReportCreation;
+ const shouldGenerateTransactionThreadReport = !isBetaEnabled(CONST.BETAS.NO_OPTIMISTIC_TRANSACTION_THREADS);
const fileUri = shouldUsePreValidatedFile ? (validFilesToUpload?.uri ?? '') : (currentAttachment?.content ?? '');
const fileName = shouldUsePreValidatedFile ? getFileName(validFilesToUpload?.uri ?? CONST.ATTACHMENT_IMAGE_DEFAULT_NAME) : getFileName(currentAttachment?.content ?? '');
diff --git a/src/pages/TransactionMerge/ConfirmationPage.tsx b/src/pages/TransactionMerge/ConfirmationPage.tsx
index b454f5777d33..b222ad4fe865 100644
--- a/src/pages/TransactionMerge/ConfirmationPage.tsx
+++ b/src/pages/TransactionMerge/ConfirmationPage.tsx
@@ -15,14 +15,15 @@ import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import useThemeStyles from '@hooks/useThemeStyles';
import {mergeTransactionRequest} from '@libs/actions/MergeTransaction';
-import {buildMergedTransactionData, getSourceTransactionFromMergeTransaction, getTargetTransactionFromMergeTransaction, getTransactionThreadReportID} from '@libs/MergeTransactionUtils';
+import {buildMergedTransactionData, getSourceTransactionFromMergeTransaction, getTargetTransactionFromMergeTransaction} from '@libs/MergeTransactionUtils';
import Navigation from '@libs/Navigation/Navigation';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
import type {MergeTransactionNavigatorParamList} from '@libs/Navigation/types';
+import {getIOUActionForTransactionID} from '@libs/ReportActionsUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type SCREENS from '@src/SCREENS';
-import type {Transaction} from '@src/types/onyx';
+import type {ReportActions, Transaction} from '@src/types/onyx';
import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue';
type ConfirmationPageProps = PlatformStackScreenProps;
@@ -42,8 +43,18 @@ function ConfirmationPage({route}: ConfirmationPageProps) {
const [sourceTransaction = getSourceTransactionFromMergeTransaction(mergeTransaction)] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${mergeTransaction?.sourceTransactionID}`, {
canBeMissing: true,
});
-
- const targetTransactionThreadReportID = getTransactionThreadReportID(targetTransaction);
+ const targetTransactionThreadReportIDSelector = useCallback(
+ (reportActionsList: OnyxEntry) => {
+ const reportActions = Object.values(reportActionsList ?? {});
+ const transactionIOUReportAction = mergeTransaction?.targetTransactionID ? getIOUActionForTransactionID(reportActions, mergeTransaction.targetTransactionID) : undefined;
+ return transactionIOUReportAction?.childReportID;
+ },
+ [mergeTransaction?.targetTransactionID],
+ );
+ const [targetTransactionThreadReportID] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${targetTransaction?.reportID}`, {
+ canBeMissing: true,
+ selector: targetTransactionThreadReportIDSelector,
+ });
const targetTransactionThreadReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${targetTransactionThreadReportID}`];
const policyID = targetTransactionThreadReport?.policyID;
const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {canBeMissing: true});
diff --git a/src/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx
index 352a8f1af8f3..85b8469b7fda 100644
--- a/src/pages/home/HeaderView.tsx
+++ b/src/pages/home/HeaderView.tsx
@@ -21,6 +21,7 @@ import HelpButton from '@components/SidePanel/HelpComponents/HelpButton';
import TaskHeaderActionButton from '@components/TaskHeaderActionButton';
import Text from '@components/Text';
import Tooltip from '@components/Tooltip';
+import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useHasTeam2025Pricing from '@hooks/useHasTeam2025Pricing';
import useLoadingBarVisibility from '@hooks/useLoadingBarVisibility';
import useLocalize from '@hooks/useLocalize';
@@ -109,6 +110,7 @@ function HeaderView({report, parentReportAction, onNavigationMenuButtonClicked,
const [firstDayFreeTrial] = useOnyx(ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL, {canBeMissing: true});
const [lastDayFreeTrial] = useOnyx(ONYXKEYS.NVP_LAST_DAY_FREE_TRIAL, {canBeMissing: true});
const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true});
+ const currentUserPersonalDetails = useCurrentUserPersonalDetails();
const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID}`, {canBeMissing: true});
const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${report?.reportID}`, {canBeMissing: true});
const isReportArchived = isArchivedReport(reportNameValuePairs);
@@ -362,7 +364,7 @@ function HeaderView({report, parentReportAction, onNavigationMenuButtonClicked,
isVisible={isDeleteTaskConfirmModalVisible}
onConfirm={() => {
setIsDeleteTaskConfirmModalVisible(false);
- deleteTask(report, isReportArchived);
+ deleteTask(report, isReportArchived, currentUserPersonalDetails.accountID);
}}
onCancel={() => setIsDeleteTaskConfirmModalVisible(false)}
title={translate('task.deleteTask')}
diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx
index 6d37f0fa9a7b..4b33d1ceb6dd 100644
--- a/src/pages/home/ReportScreen.tsx
+++ b/src/pages/home/ReportScreen.tsx
@@ -70,6 +70,7 @@ import {
getReportOfflinePendingActionAndErrors,
getReportTransactions,
isAdminRoom,
+ isAnnounceRoom,
isChatThread,
isConciergeChatReport,
isGroupChat,
@@ -204,9 +205,10 @@ function ReportScreen({route, navigation}: ReportScreenProps) {
if (!lastAccessedReportID) {
return;
}
-
- Log.info(`[ReportScreen] no reportID found in params, setting it to lastAccessedReportID: ${lastAccessedReportID}`);
- navigation.setParams({reportID: lastAccessedReportID});
+ Navigation.isNavigationReady().then(() => {
+ Log.info(`[ReportScreen] no reportID found in params, setting it to lastAccessedReportID: ${lastAccessedReportID}`);
+ navigation.setParams({reportID: lastAccessedReportID});
+ });
}, [isBetaEnabled, navigation, route]);
const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {canBeMissing: true});
@@ -660,7 +662,13 @@ function ReportScreen({route, navigation}: ReportScreenProps) {
const prevOnyxReportID = prevReport?.reportID;
const wasReportRemoved = !!prevOnyxReportID && prevOnyxReportID === reportIDFromRoute && !onyxReportID;
const isRemovalExpectedForReportType =
- isEmpty(report) && (isMoneyRequest(prevReport) || isMoneyRequestReport(prevReport) || isPolicyExpenseChat(prevReport) || isGroupChat(prevReport) || isAdminRoom(prevReport));
+ isEmpty(report) &&
+ (isMoneyRequest(prevReport) ||
+ isMoneyRequestReport(prevReport) ||
+ isPolicyExpenseChat(prevReport) ||
+ isGroupChat(prevReport) ||
+ isAdminRoom(prevReport) ||
+ isAnnounceRoom(prevReport));
const didReportClose = wasReportRemoved && prevReport.statusNum === CONST.REPORT.STATUS_NUM.OPEN && report?.statusNum === CONST.REPORT.STATUS_NUM.CLOSED;
const isTopLevelPolicyRoomWithNoStatus = !report?.statusNum && !prevReport?.parentReportID && prevReport?.chatType === CONST.REPORT.CHAT_TYPE.POLICY_ROOM;
const isClosedTopLevelPolicyRoom = wasReportRemoved && prevReport.statusNum === CONST.REPORT.STATUS_NUM.OPEN && isTopLevelPolicyRoomWithNoStatus;
diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx
index 94e28dc6a3da..1dee57738f30 100644
--- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx
+++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx
@@ -9,12 +9,14 @@ import type {OnyxEntry} from 'react-native-onyx';
import {Actions, ActionSheetAwareScrollViewContext} from '@components/ActionSheetAwareScrollView';
import ConfirmModal from '@components/ConfirmModal';
import PopoverWithMeasuredContent from '@components/PopoverWithMeasuredContent';
+import {useSearchContext} from '@components/Search/SearchContext';
+import useDeleteTransactions from '@hooks/useDeleteTransactions';
import useDuplicateTransactionsAndViolations from '@hooks/useDuplicateTransactionsAndViolations';
import useGetIOUReportFromReportAction from '@hooks/useGetIOUReportFromReportAction';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import useReportIsArchived from '@hooks/useReportIsArchived';
-import {deleteMoneyRequest, deleteTrackExpense} from '@libs/actions/IOU';
+import {deleteTrackExpense} from '@libs/actions/IOU';
import {deleteAppReport, deleteReportComment} from '@libs/actions/Report';
import calculateAnchorPosition from '@libs/calculateAnchorPosition';
import {getOriginalMessage, isMoneyRequestAction, isReportPreviewAction, isTrackExpenseAction} from '@libs/ReportActionsUtils';
@@ -178,7 +180,7 @@ function PopoverReportActionContextMenu({ref}: PopoverReportActionContextMenuPro
event,
selection,
contextMenuAnchor,
- report = {},
+ report: currentReport = {},
reportAction = {},
callbacks = {},
disabledOptions = [],
@@ -186,7 +188,7 @@ function PopoverReportActionContextMenu({ref}: PopoverReportActionContextMenuPro
isOverflowMenu = false,
withoutOverlay = true,
} = showContextMenuParams;
- const {reportID, originalReportID, isArchivedRoom = false, isChronos = false, isPinnedChat = false, isUnreadChat = false} = report;
+ const {reportID, originalReportID, isArchivedRoom = false, isChronos = false, isPinnedChat = false, isUnreadChat = false} = currentReport;
const {reportActionID, draftMessage, isThreadReportParentAction: isThreadReportParentActionParam = false} = reportAction;
const {onShow = () => {}, onHide = () => {}, setIsEmojiPickerActive = () => {}} = callbacks;
setIsContextMenuOpening(true);
@@ -304,6 +306,13 @@ function PopoverReportActionContextMenu({ref}: PopoverReportActionContextMenuPro
const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportIDRef.current}`, {
canBeMissing: true,
});
+ const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`, {canBeMissing: true});
+ const {currentSearchHash} = useSearchContext();
+ const {deleteTransactions} = useDeleteTransactions({
+ report,
+ reportActions: reportActionRef.current ? [reportActionRef.current] : [],
+ policy,
+ });
const confirmDeleteAndHideModal = useCallback(() => {
callbackWhenDeleteModalHide.current = runAndResetCallback(onConfirmDeleteModal.current);
@@ -324,8 +333,8 @@ function PopoverReportActionContextMenu({ref}: PopoverReportActionContextMenuPro
isChatReportArchived: isReportArchived,
isChatIOUReportArchived,
});
- } else {
- deleteMoneyRequest(originalMessage?.IOUTransactionID, reportAction, duplicateTransactions, duplicateTransactionViolations, iouReport, chatReport, isChatIOUReportArchived);
+ } else if (originalMessage?.IOUTransactionID) {
+ deleteTransactions([originalMessage.IOUTransactionID], duplicateTransactions, duplicateTransactionViolations, currentSearchHash);
}
} else if (isReportPreviewAction(reportAction)) {
deleteAppReport(reportAction.childReportID);
@@ -338,7 +347,18 @@ function PopoverReportActionContextMenu({ref}: PopoverReportActionContextMenuPro
DeviceEventEmitter.emit(`deletedReportAction_${reportIDRef.current}`, reportAction?.reportActionID);
setIsDeleteCommentConfirmModalVisible(false);
- }, [duplicateTransactions, duplicateTransactionViolations, isReportArchived, isOriginalReportArchived, isChatIOUReportArchived, iouReport, chatReport, report]);
+ }, [
+ report,
+ iouReport,
+ chatReport,
+ duplicateTransactions,
+ duplicateTransactionViolations,
+ isReportArchived,
+ isChatIOUReportArchived,
+ deleteTransactions,
+ currentSearchHash,
+ isOriginalReportArchived,
+ ]);
const hideDeleteModal = () => {
callbackWhenDeleteModalHide.current = () => (onCancelDeleteModal.current = runAndResetCallback(onCancelDeleteModal.current));
diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx
index 84555c60dec6..3a5fc61cee8e 100644
--- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx
+++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx
@@ -276,10 +276,10 @@ function AttachmentPickerWithMenuItems({
icon: Expensicons.Task,
text: translate('newTaskPage.assignTask'),
shouldCallAfterModalHide: shouldUseNarrowLayout,
- onSelected: () => clearOutTaskInfoAndNavigate(reportID, report),
+ onSelected: () => clearOutTaskInfoAndNavigate(currentUserPersonalDetails.accountID, reportID, report),
},
];
- }, [report, reportID, translate, shouldUseNarrowLayout]);
+ }, [report, translate, shouldUseNarrowLayout, currentUserPersonalDetails.accountID, reportID]);
const onPopoverMenuClose = () => {
setMenuVisibility(false);
diff --git a/src/pages/home/report/ReportActionsView.tsx b/src/pages/home/report/ReportActionsView.tsx
index c9dcf70a248c..5b30f3ea8c57 100755
--- a/src/pages/home/report/ReportActionsView.tsx
+++ b/src/pages/home/report/ReportActionsView.tsx
@@ -111,7 +111,7 @@ function ReportActionsView({
const {shouldUseNarrowLayout} = useResponsiveLayout();
const isFocused = useIsFocused();
- const [isNavigatingToLinkedMessage, setNavigatingToLinkedMessage] = useState(!!reportActionID);
+ const [isNavigatingToLinkedMessage, setNavigatingToLinkedMessage] = useState(false);
const prevShouldUseNarrowLayoutRef = useRef(shouldUseNarrowLayout);
const reportID = report.reportID;
const isReportFullyVisible = useMemo((): boolean => getIsReportFullyVisible(isFocused), [isFocused]);
@@ -279,16 +279,16 @@ function ReportActionsView({
useEffect(() => {
let timerID: NodeJS.Timeout;
- if (isTheFirstReportActionIsLinked) {
+ if (!isTheFirstReportActionIsLinked && reportActionID) {
setNavigatingToLinkedMessage(true);
- } else {
// After navigating to the linked reportAction, apply this to correctly set
// `autoscrollToTopThreshold` prop when linking to a specific reportAction.
- // eslint-disable-next-line @typescript-eslint/no-deprecated
InteractionManager.runAfterInteractions(() => {
// Using a short delay to ensure the view is updated after interactions
timerID = setTimeout(() => setNavigatingToLinkedMessage(false), 10);
});
+ } else {
+ setNavigatingToLinkedMessage(false);
}
return () => {
@@ -297,7 +297,7 @@ function ReportActionsView({
}
clearTimeout(timerID);
};
- }, [isTheFirstReportActionIsLinked]);
+ }, [isTheFirstReportActionIsLinked, reportActionID]);
// Show skeleton while loading initial report actions when data is incomplete/missing and online
const shouldShowSkeletonForInitialLoad = isLoadingInitialReportActions && (isReportDataIncomplete || isMissingReportActions) && !isOffline;
diff --git a/src/pages/home/report/ReportFooter.tsx b/src/pages/home/report/ReportFooter.tsx
index 64d3fb6e9aff..6ffe045a57ca 100644
--- a/src/pages/home/report/ReportFooter.tsx
+++ b/src/pages/home/report/ReportFooter.tsx
@@ -25,7 +25,6 @@ import useWindowDimensions from '@hooks/useWindowDimensions';
import {addComment} from '@libs/actions/Report';
import {createTaskAndNavigate, setNewOptimisticAssignee} from '@libs/actions/Task';
import {isEmailPublicDomain} from '@libs/LoginUtils';
-import {getCurrentUserEmail} from '@libs/Network/NetworkStore';
import {addDomainToShortMention} from '@libs/ParsingUtils';
import {isPolicyAdmin} from '@libs/PolicyUtils';
import {
@@ -121,7 +120,7 @@ function ReportFooter({
const allPersonalDetails = usePersonalDetails();
const {availableLoginsList} = useShortMentionsList();
- const currentUserEmail = getCurrentUserEmail();
+ const currentUserEmail = personalDetail.email ?? '';
const handleCreateTask = useCallback(
(text: string): boolean => {
@@ -146,7 +145,7 @@ function ReportFooter({
assignee = Object.values(allPersonalDetails ?? {}).find((value) => value?.login === mentionWithDomain) ?? undefined;
if (!Object.keys(assignee ?? {}).length) {
const assigneeAccountID = generateAccountID(mentionWithDomain);
- const optimisticDataForNewAssignee = setNewOptimisticAssignee(mentionWithDomain, assigneeAccountID);
+ const optimisticDataForNewAssignee = setNewOptimisticAssignee(mentionWithDomain, assigneeAccountID, personalDetail.accountID);
assignee = optimisticDataForNewAssignee.assignee;
assigneeChatReport = optimisticDataForNewAssignee.assigneeReport;
}
@@ -156,10 +155,22 @@ function ReportFooter({
title = `@${mentionWithDomain} ${title}`;
}
}
- createTaskAndNavigate(report.reportID, title, '', assignee?.login ?? '', assignee?.accountID, assigneeChatReport, report.policyID, true, quickAction);
+ createTaskAndNavigate({
+ parentReportID: report.reportID,
+ title,
+ description: '',
+ assigneeEmail: assignee?.login ?? '',
+ currentUserAccountID: personalDetail.accountID,
+ currentUserEmail,
+ assigneeAccountID: assignee?.accountID,
+ assigneeChatReport,
+ policyID: report.policyID,
+ isCreatedUsingMarkdown: true,
+ quickAction,
+ });
return true;
},
- [allPersonalDetails, availableLoginsList, currentUserEmail, quickAction, report.policyID, report.reportID],
+ [allPersonalDetails, availableLoginsList, currentUserEmail, personalDetail.accountID, quickAction, report.policyID, report.reportID],
);
const onSubmitComment = useCallback(
diff --git a/src/pages/home/sidebar/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/FloatingActionButtonAndPopover.tsx
index c4ff1c951ca4..7a6e698e3efb 100644
--- a/src/pages/home/sidebar/FloatingActionButtonAndPopover.tsx
+++ b/src/pages/home/sidebar/FloatingActionButtonAndPopover.tsx
@@ -370,7 +370,7 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu, ref
showDelegateNoAccessModal();
return;
}
- navigateToQuickAction({isValidReport, quickAction, selectOption, lastDistanceExpenseType});
+ navigateToQuickAction({isValidReport, quickAction, selectOption, lastDistanceExpenseType, currentUserAccountID: currentUserPersonalDetails.accountID});
});
};
return [
@@ -417,23 +417,24 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu, ref
translate,
styles.pt3,
styles.pb2,
- quickActionAvatars,
quickAction,
policyChatForActivePolicy,
+ quickActionReport,
+ quickActionPolicy,
+ isReportArchived,
+ isRestrictedToPreferredPolicy,
quickActionTitle,
+ quickActionAvatars,
quickActionSubtitle,
- quickActionPolicy,
- quickActionReport,
- isValidReport,
- selectOption,
shouldUseNarrowLayout,
isDelegateAccessRestricted,
- showDelegateNoAccessModal,
- isReportArchived,
+ isValidReport,
+ selectOption,
lastDistanceExpenseType,
- allTransactionDrafts,
+ currentUserPersonalDetails.accountID,
+ showDelegateNoAccessModal,
reportID,
- isRestrictedToPreferredPolicy,
+ allTransactionDrafts,
]);
const isTravelEnabled = useMemo(() => {
@@ -571,6 +572,7 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu, ref
viewTourTaskReport,
viewTourTaskParentReport,
isViewTourTaskParentReportArchived,
+ currentUserPersonalDetails.accountID,
),
),
},
diff --git a/src/pages/iou/SplitExpensePage.tsx b/src/pages/iou/SplitExpensePage.tsx
index 11a81fe82697..a83b0214d10c 100644
--- a/src/pages/iou/SplitExpensePage.tsx
+++ b/src/pages/iou/SplitExpensePage.tsx
@@ -27,8 +27,8 @@ import {
getIOURequestPolicyID,
initDraftSplitExpenseDataForEdit,
initSplitExpenseItemData,
- saveSplitTransactions,
updateSplitExpenseAmountField,
+ updateSplitTransactionsFromSplitExpensesFlow,
} from '@libs/actions/IOU';
import {convertToBackendAmount, convertToDisplayString} from '@libs/CurrencyUtils';
import DateUtils from '@libs/DateUtils';
@@ -74,6 +74,8 @@ function SplitExpensePage({route}: SplitExpensePageProps) {
const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(transactionID)}`, {canBeMissing: false});
const [currencyList] = useOnyx(ONYXKEYS.CURRENCY_LIST, {canBeMissing: true});
const [allTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {canBeMissing: false});
+ const [allReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: false});
+ const [allReportNameValuePairs] = useOnyx(ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS, {canBeMissing: true});
const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(reportID)}`, {canBeMissing: true});
const [policyRecentlyUsedCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES}${getIOURequestPolicyID(transaction, report)}`, {canBeMissing: true});
@@ -93,7 +95,7 @@ function SplitExpensePage({route}: SplitExpensePageProps) {
const iouActions = getIOUActionForTransactions([originalTransactionID], expenseReport?.reportID);
const {iouReport, chatReport, isChatIOUReportArchived} = useGetIOUReportFromReportAction(iouActions.at(0));
- const childTransactions = useMemo(() => getChildTransactions(transactionID), [transactionID]);
+ const childTransactions = useMemo(() => getChildTransactions(allTransactions, allReports, transactionID), [allReports, allTransactions, transactionID]);
const splitFieldDataFromChildTransactions = useMemo(() => childTransactions.map((currentTransaction) => initSplitExpenseItemData(currentTransaction)), [childTransactions]);
const splitFieldDataFromOriginalTransaction = useMemo(() => initSplitExpenseItemData(transaction), [transaction]);
@@ -159,18 +161,26 @@ function SplitExpensePage({route}: SplitExpensePageProps) {
return;
}
- saveSplitTransactions(
- originalTransactionID,
- draftTransaction,
- currentSearchHash,
+ updateSplitTransactionsFromSplitExpensesFlow({
+ allTransactionsList: allTransactions,
+ allReportsList: allReports,
+ allReportNameValuePairsList: allReportNameValuePairs,
+ transactionData: {
+ reportID: draftTransaction?.reportID ?? String(CONST.DEFAULT_NUMBER_ID),
+ originalTransactionID: draftTransaction?.comment?.originalTransactionID ?? String(CONST.DEFAULT_NUMBER_ID),
+ splitExpenses,
+ splitExpensesTotal: draftTransaction?.comment?.splitExpensesTotal ?? 0,
+ },
+ hash: currentSearchHash,
policyCategories,
- expenseReportPolicy,
+ policy: expenseReportPolicy,
policyRecentlyUsedCategories,
iouReport,
chatReport,
- iouActions.at(0),
- isChatIOUReportArchived,
- );
+ firstIOU: iouActions.at(0),
+ isChatReportArchived: isChatIOUReportArchived,
+ isNewDotRevertSplitsEnabled: isBetaEnabled(CONST.BETAS.NEWDOT_REVERT_SPLITS),
+ });
}, [
splitExpenses,
childTransactions.length,
@@ -181,6 +191,8 @@ function SplitExpensePage({route}: SplitExpensePageProps) {
isPerDiem,
isCard,
splitFieldDataFromChildTransactions,
+ allTransactions,
+ allReports,
currentSearchHash,
policyCategories,
expenseReportPolicy,
@@ -193,7 +205,6 @@ function SplitExpensePage({route}: SplitExpensePageProps) {
iouReport,
iouActions,
chatReport,
- originalTransactionID,
]);
const onSplitExpenseAmountChange = useCallback(
diff --git a/src/pages/iou/request/step/IOURequestStepAmount.tsx b/src/pages/iou/request/step/IOURequestStepAmount.tsx
index c09dfe8bdb1f..59ca04cf8cee 100644
--- a/src/pages/iou/request/step/IOURequestStepAmount.tsx
+++ b/src/pages/iou/request/step/IOURequestStepAmount.tsx
@@ -90,7 +90,6 @@ function IOURequestStepAmount({
const [splitDraftTransaction] = useOnyx(`${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`, {canBeMissing: true});
const [skipConfirmation] = useOnyx(`${ONYXKEYS.COLLECTION.SKIP_CONFIRMATION}${transactionID}`, {canBeMissing: true});
const defaultExpensePolicy = useDefaultExpensePolicy();
- const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true});
const personalPolicy = usePersonalPolicy();
const {duplicateTransactions, duplicateTransactionViolations} = useDuplicateTransactionsAndViolations(transactionID ? [transactionID] : []);
const [reportAttributesDerived] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {canBeMissing: true, selector: reportsSelector});
@@ -108,7 +107,7 @@ function IOURequestStepAmount({
const currency = isValidCurrencyCode(selectedCurrency) ? selectedCurrency : originalCurrency;
// eslint-disable-next-line rulesdir/no-negated-variables
const shouldShowNotFoundPage = useShowNotFoundPageInIOUStep(action, iouType, reportActionID, report, transaction);
- const shouldGenerateTransactionThreadReport = !isBetaEnabled(CONST.BETAS.NO_OPTIMISTIC_TRANSACTION_THREADS) || !account?.shouldBlockTransactionThreadReportCreation;
+ const shouldGenerateTransactionThreadReport = !isBetaEnabled(CONST.BETAS.NO_OPTIMISTIC_TRANSACTION_THREADS);
// For quick button actions, we'll skip the confirmation page unless the report is archived or this is a workspace request, as
// the user will have to add a merchant.
diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx
index 72398aadbf49..3f2f94dea373 100644
--- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx
+++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx
@@ -158,7 +158,6 @@ function IOURequestStepConfirmation({
const [reportAttributesDerived] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {canBeMissing: true, selector: reportsSelector});
const [recentlyUsedDestinations] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_DESTINATIONS}${realPolicyID}`, {canBeMissing: true});
const [policyRecentlyUsedCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES}${realPolicyID}`, {canBeMissing: true});
- const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true});
/*
* We want to use a report from the transaction if it exists
@@ -250,7 +249,7 @@ function IOURequestStepConfirmation({
[transaction?.participants, iouType, personalDetails, reportAttributesDerived],
);
const isPolicyExpenseChat = useMemo(() => participants?.some((participant) => participant.isPolicyExpenseChat), [participants]);
- const shouldGenerateTransactionThreadReport = !isBetaEnabled(CONST.BETAS.NO_OPTIMISTIC_TRANSACTION_THREADS) || !account?.shouldBlockTransactionThreadReportCreation;
+ const shouldGenerateTransactionThreadReport = !isBetaEnabled(CONST.BETAS.NO_OPTIMISTIC_TRANSACTION_THREADS);
const formHasBeenSubmitted = useRef(false);
useFetchRoute(transaction, transaction?.comment?.waypoints, action, shouldUseTransactionDraft(action) ? CONST.TRANSACTION.STATE.DRAFT : CONST.TRANSACTION.STATE.CURRENT);
@@ -487,7 +486,7 @@ function IOURequestStepConfirmation({
!!item.linkedTrackedExpenseReportID && archivedReportsIdSet.has(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${item.linkedTrackedExpenseReportID}`);
if (isTestDriveReceipt) {
- completeTestDriveTask(viewTourTaskReport, viewTourTaskParentReport, isViewTourTaskParentReportArchived);
+ completeTestDriveTask(viewTourTaskReport, viewTourTaskParentReport, isViewTourTaskParentReportArchived, currentUserPersonalDetails.accountID);
}
requestMoneyIOUActions({
diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx
index 3b62f9ee376b..d93eff26bece 100644
--- a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx
+++ b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx
@@ -125,7 +125,6 @@ function IOURequestStepScan({
const defaultExpensePolicy = useDefaultExpensePolicy();
const [dismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING, {canBeMissing: true});
const [reportAttributesDerived] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {canBeMissing: true, selector: reportsSelector});
- const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true});
const platform = getPlatform(true);
const [mutedPlatforms = getEmptyObject>>()] = useOnyx(ONYXKEYS.NVP_MUTED_PLATFORMS, {canBeMissing: true});
const isPlatformMuted = mutedPlatforms[platform];
@@ -133,7 +132,7 @@ function IOURequestStepScan({
const [didCapturePhoto, setDidCapturePhoto] = useState(false);
const [shouldShowMultiScanEducationalPopup, setShouldShowMultiScanEducationalPopup] = useState(false);
const {shouldStartLocationPermissionFlow} = useIOUUtils();
- const shouldGenerateTransactionThreadReport = !isBetaEnabled(CONST.BETAS.NO_OPTIMISTIC_TRANSACTION_THREADS) || !account?.shouldBlockTransactionThreadReportCreation;
+ const shouldGenerateTransactionThreadReport = !isBetaEnabled(CONST.BETAS.NO_OPTIMISTIC_TRANSACTION_THREADS);
const defaultTaxCode = getDefaultTaxCode(policy, initialTransaction);
const transactionTaxCode = (initialTransaction?.taxCode ? initialTransaction?.taxCode : defaultTaxCode) ?? '';
diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.tsx
index e9303a355349..9e78940ac2f4 100644
--- a/src/pages/iou/request/step/IOURequestStepScan/index.tsx
+++ b/src/pages/iou/request/step/IOURequestStepScan/index.tsx
@@ -124,7 +124,6 @@ function IOURequestStepScan({
const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID}`, {canBeMissing: true});
const policy = usePolicy(report?.policyID);
const personalPolicy = usePersonalPolicy();
- const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true});
const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {canBeMissing: false});
const [skipConfirmation] = useOnyx(`${ONYXKEYS.COLLECTION.SKIP_CONFIRMATION}${initialTransactionID}`, {canBeMissing: true});
const defaultExpensePolicy = useDefaultExpensePolicy();
@@ -134,7 +133,7 @@ function IOURequestStepScan({
const canUseMultiScan = isStartingScan && iouType !== CONST.IOU.TYPE.SPLIT;
const isReplacingReceipt = (isEditing && hasReceipt(initialTransaction)) || (!!initialTransaction?.receipt && !!backTo);
const {shouldStartLocationPermissionFlow} = useIOUUtils();
- const shouldGenerateTransactionThreadReport = !isBetaEnabled(CONST.BETAS.NO_OPTIMISTIC_TRANSACTION_THREADS) || !account?.shouldBlockTransactionThreadReportCreation;
+ const shouldGenerateTransactionThreadReport = !isBetaEnabled(CONST.BETAS.NO_OPTIMISTIC_TRANSACTION_THREADS);
const [optimisticTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, {
selector: transactionDraftValuesSelector,
diff --git a/src/pages/iou/request/step/IOURequestStepSubrate.tsx b/src/pages/iou/request/step/IOURequestStepSubrate.tsx
index dfe469147fc4..f9a46aa76f5c 100644
--- a/src/pages/iou/request/step/IOURequestStepSubrate.tsx
+++ b/src/pages/iou/request/step/IOURequestStepSubrate.tsx
@@ -137,6 +137,7 @@ function IOURequestStepSubrate({
const selectedSubrate = allPossibleSubrates.find(({id}) => id === subrateVal);
const name = selectedSubrate?.name ?? '';
const rate = selectedSubrate?.rate ?? 0;
+ const transactionReportID = transaction?.participants?.at(0)?.reportID ?? transaction?.reportID ?? reportID;
if (parsedIndex === filledSubrateCount) {
addSubrate(transaction, pageIndex, quantityInt, subrateVal, name, rate);
@@ -147,7 +148,7 @@ function IOURequestStepSubrate({
if (backTo) {
goBack();
} else {
- Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(action, iouType, transactionID, reportID));
+ Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(action, iouType, transactionID, transactionReportID));
}
};
diff --git a/src/pages/iou/request/step/IOURequestStepUpgrade.tsx b/src/pages/iou/request/step/IOURequestStepUpgrade.tsx
index 4539198214bf..dd10c0d29e81 100644
--- a/src/pages/iou/request/step/IOURequestStepUpgrade.tsx
+++ b/src/pages/iou/request/step/IOURequestStepUpgrade.tsx
@@ -116,8 +116,14 @@ function IOURequestStepUpgrade({
policyID: undefined,
engagementChoice: CONST.ONBOARDING_CHOICES.TRACK_WORKSPACE,
currency: currentUserPersonalDetails?.localCurrencyCode ?? '',
- areDistanceRatesEnabled: isDistanceRateUpgrade,
+ featuresMap: [
+ {
+ id: CONST.POLICY.MORE_FEATURES.ARE_DISTANCE_RATES_ENABLED,
+ enabled: isDistanceRateUpgrade,
+ },
+ ],
adminParticipant,
+ hasOutstandingChildRequest: false,
});
setIsUpgraded(true);
policyDataRef.current = policyData;
diff --git a/src/pages/settings/Profile/Avatar/AvatarPage.tsx b/src/pages/settings/Profile/Avatar/AvatarPage.tsx
index 7ff7798d8745..67a5ffa5dc42 100644
--- a/src/pages/settings/Profile/Avatar/AvatarPage.tsx
+++ b/src/pages/settings/Profile/Avatar/AvatarPage.tsx
@@ -132,6 +132,7 @@ function ProfileAvatar() {
avatar: currentUserPersonalDetails?.avatar,
fallbackIcon: currentUserPersonalDetails?.fallbackIcon,
accountID: currentUserPersonalDetails?.accountID,
+ email: currentUserPersonalDetails?.email,
});
setSelected(undefined);
setImageData({...EMPTY_FILE});
diff --git a/src/pages/settings/Profile/ProfilePage.tsx b/src/pages/settings/Profile/ProfilePage.tsx
index 141663426ff5..b9fd04208919 100755
--- a/src/pages/settings/Profile/ProfilePage.tsx
+++ b/src/pages/settings/Profile/ProfilePage.tsx
@@ -216,6 +216,7 @@ function ProfilePage() {
avatar: currentUserPersonalDetails?.avatar,
fallbackIcon: currentUserPersonalDetails?.fallbackIcon,
accountID: currentUserPersonalDetails?.accountID,
+ email: currentUserPersonalDetails?.email,
});
}}
size={CONST.AVATAR_SIZE.X_LARGE}
diff --git a/src/pages/settings/Report/VisibilityPage.tsx b/src/pages/settings/Report/VisibilityPage.tsx
index a86d9bcce11a..f3c89ce745a3 100644
--- a/src/pages/settings/Report/VisibilityPage.tsx
+++ b/src/pages/settings/Report/VisibilityPage.tsx
@@ -4,8 +4,8 @@ import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView
import ConfirmModal from '@components/ConfirmModal';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import ScreenWrapper from '@components/ScreenWrapper';
-import SelectionList from '@components/SelectionListWithSections';
-import RadioListItem from '@components/SelectionListWithSections/RadioListItem';
+import SelectionList from '@components/SelectionList';
+import RadioListItem from '@components/SelectionList/ListItem/RadioListItem';
import useLocalize from '@hooks/useLocalize';
import useReportIsArchived from '@hooks/useReportIsArchived';
import type {PlatformStackRouteProp, PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
@@ -77,7 +77,7 @@ function VisibilityPage({report}: VisibilityProps) {
/>
{
if (option.value === CONST.REPORT.VISIBILITY.PUBLIC) {
setShowConfirmModal(true);
@@ -86,7 +86,7 @@ function VisibilityPage({report}: VisibilityProps) {
changeVisibility(option.value);
}}
shouldSingleExecuteRowSelect
- initiallyFocusedOptionKey={visibilityOptions.find((visibility) => visibility.isSelected)?.keyForList}
+ initiallyFocusedItemKey={visibilityOptions.find((visibility) => visibility.isSelected)?.keyForList}
ListItem={RadioListItem}
/>
d.email === login);
const addDelegateErrors = account?.delegatedAccess?.errorFields?.addDelegate?.[login];
const validateLoginError = getLatestError(addDelegateErrors);
+ const [session] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: false});
useEffect(() => {
if (!currentDelegate || !!currentDelegate.pendingFields?.email || !!addDelegateErrors) {
@@ -52,7 +53,7 @@ function ConfirmDelegateMagicCodePage({route}: ConfirmDelegateMagicCodePageProps
title={translate('delegate.makeSureItIsYou')}
sendValidateCode={() => requestValidateCodeAction()}
handleSubmitForm={(validateCode) => addDelegate({email: login, role, validateCode, delegatedAccess: account?.delegatedAccess})}
- descriptionPrimary={translate('delegate.enterMagicCode', {contactMethod: account?.primaryLogin ?? ''})}
+ descriptionPrimary={translate('delegate.enterMagicCode', {contactMethod: account?.primaryLogin ?? session?.email ?? ''})}
/>
);
}
diff --git a/src/pages/settings/Security/AddDelegate/UpdateDelegateRole/UpdateDelegateMagicCodePage.tsx b/src/pages/settings/Security/AddDelegate/UpdateDelegateRole/UpdateDelegateMagicCodePage.tsx
index 04e4fa7fecf9..bbb380ace521 100644
--- a/src/pages/settings/Security/AddDelegate/UpdateDelegateRole/UpdateDelegateMagicCodePage.tsx
+++ b/src/pages/settings/Security/AddDelegate/UpdateDelegateRole/UpdateDelegateMagicCodePage.tsx
@@ -21,6 +21,7 @@ function UpdateDelegateMagicCodePage({route}: UpdateDelegateMagicCodePageProps)
const login = route.params.login;
const newRole = route.params.newRole as ValueOf;
const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true});
+ const [session] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: true});
const [validateCodeAction] = useOnyx(ONYXKEYS.VALIDATE_ACTION_CODE, {canBeMissing: true});
const currentDelegate = account?.delegatedAccess?.delegates?.find((d) => d.email === login);
const updateDelegateErrors = account?.delegatedAccess?.errorFields?.updateDelegateRole?.[login];
@@ -49,7 +50,7 @@ function UpdateDelegateMagicCodePage({route}: UpdateDelegateMagicCodePageProps)
title={translate('delegate.makeSureItIsYou')}
sendValidateCode={() => requestValidateCodeAction()}
handleSubmitForm={(validateCode) => updateDelegateRole({email: login, role: newRole, validateCode, delegatedAccess: account?.delegatedAccess})}
- descriptionPrimary={translate('delegate.enterMagicCode', {contactMethod: account?.primaryLogin ?? ''})}
+ descriptionPrimary={translate('delegate.enterMagicCode', {contactMethod: account?.primaryLogin ?? session?.email ?? ''})}
/>
);
}
diff --git a/src/pages/tasks/NewTaskDetailsPage.tsx b/src/pages/tasks/NewTaskDetailsPage.tsx
index df7283fe98f4..082376a64ca7 100644
--- a/src/pages/tasks/NewTaskDetailsPage.tsx
+++ b/src/pages/tasks/NewTaskDetailsPage.tsx
@@ -7,6 +7,7 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton';
import ScreenWrapper from '@components/ScreenWrapper';
import TextInput from '@components/TextInput';
import useAutoFocusInput from '@hooks/useAutoFocusInput';
+import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import useThemeStyles from '@hooks/useThemeStyles';
@@ -29,6 +30,7 @@ type NewTaskDetailsPageProps = PlatformStackScreenProps getAssignee(task?.assigneeAccountID ?? CONST.DEFAULT_NUMBER_ID, personalDetails), [task?.assigneeAccountID, personalDetails]);
@@ -97,17 +99,19 @@ function NewTaskPage({route}: NewTaskPageProps) {
return;
}
- createTaskAndNavigate(
- parentReport?.reportID,
- task.title,
- task?.description ?? '',
- task?.assignee ?? '',
- task.assigneeAccountID,
- task.assigneeChatReport,
- parentReport?.policyID,
- false,
+ createTaskAndNavigate({
+ parentReportID: parentReport?.reportID,
+ title: task.title,
+ description: task?.description ?? '',
+ assigneeEmail: task?.assignee ?? '',
+ currentUserAccountID: currentUserPersonalDetails.accountID,
+ currentUserEmail: currentUserPersonalDetails.email ?? '',
+ assigneeAccountID: task.assigneeAccountID,
+ assigneeChatReport: task.assigneeChatReport,
+ policyID: parentReport?.policyID,
+ isCreatedUsingMarkdown: false,
quickAction,
- );
+ });
};
return (
diff --git a/src/pages/tasks/TaskAssigneeSelectorModal.tsx b/src/pages/tasks/TaskAssigneeSelectorModal.tsx
index 4232d524b8ab..60502f039359 100644
--- a/src/pages/tasks/TaskAssigneeSelectorModal.tsx
+++ b/src/pages/tasks/TaskAssigneeSelectorModal.tsx
@@ -5,7 +5,6 @@ import {InteractionManager, View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
-import {useSession} from '@components/OnyxListItemProvider';
import ScreenWrapper from '@components/ScreenWrapper';
import SelectionList from '@components/SelectionListWithSections';
import type {ListItem} from '@components/SelectionListWithSections/types';
@@ -37,7 +36,6 @@ function TaskAssigneeSelectorModal() {
const styles = useThemeStyles();
const route = useRoute>();
const {translate} = useLocalize();
- const session = useSession();
const backTo = route.params?.backTo;
const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: false});
const [task] = useOnyx(ONYXKEYS.TASK, {canBeMissing: false});
@@ -57,16 +55,16 @@ function TaskAssigneeSelectorModal() {
});
const optionsWithoutCurrentUser = useMemo(() => {
- if (!session?.accountID) {
+ if (!currentUserPersonalDetails?.accountID) {
return availableOptions;
}
return {
...availableOptions,
- personalDetails: availableOptions.personalDetails.filter((detail) => detail.accountID !== session.accountID),
- recentReports: availableOptions.recentReports.filter((report) => report.accountID !== session.accountID),
+ personalDetails: availableOptions.personalDetails.filter((detail) => detail.accountID !== currentUserPersonalDetails.accountID),
+ recentReports: availableOptions.recentReports.filter((report) => report.accountID !== currentUserPersonalDetails.accountID),
};
- }, [availableOptions, session?.accountID]);
+ }, [availableOptions, currentUserPersonalDetails?.accountID]);
const headerMessage = useMemo(() => {
return getHeaderMessage(
@@ -149,12 +147,20 @@ function TaskAssigneeSelectorModal() {
const assigneeChatReport = setAssigneeValue(
option?.login ?? '',
option?.accountID ?? CONST.DEFAULT_NUMBER_ID,
+ currentUserPersonalDetails.accountID,
report.reportID,
undefined, // passing null as report because for editing task the report will be task details report page not the actual report where task was created
isCurrentUser({...option, accountID: option?.accountID ?? CONST.DEFAULT_NUMBER_ID, login: option?.login ?? ''}),
);
// Pass through the selected assignee
- editTaskAssignee(report, session?.accountID ?? CONST.DEFAULT_NUMBER_ID, option?.login ?? '', option?.accountID, assigneeChatReport);
+ editTaskAssignee(
+ report,
+ currentUserPersonalDetails?.accountID ?? CONST.DEFAULT_NUMBER_ID,
+ option?.login ?? '',
+ currentUserPersonalDetails.accountID,
+ option?.accountID,
+ assigneeChatReport,
+ );
}
// eslint-disable-next-line @typescript-eslint/no-deprecated
InteractionManager.runAfterInteractions(() => {
@@ -165,6 +171,7 @@ function TaskAssigneeSelectorModal() {
setAssigneeValue(
option?.login ?? '',
option.accountID ?? CONST.DEFAULT_NUMBER_ID,
+ currentUserPersonalDetails.accountID,
task?.shareDestination ?? '',
undefined, // passing null as report is null in this condition
isCurrentUser({...option, accountID: option?.accountID ?? CONST.DEFAULT_NUMBER_ID, login: option?.login ?? undefined}),
@@ -175,7 +182,7 @@ function TaskAssigneeSelectorModal() {
});
}
},
- [session?.accountID, task?.shareDestination, report, backTo],
+ [report, currentUserPersonalDetails.accountID, task?.shareDestination, backTo],
);
const handleBackButtonPress = useCallback(() => Navigation.goBack(!route.params?.reportID ? ROUTES.NEW_TASK.getRoute(backTo) : backTo), [route.params, backTo]);
diff --git a/src/pages/workspace/WorkspaceMembersPage.tsx b/src/pages/workspace/WorkspaceMembersPage.tsx
index c22df9171f91..cc72643571b6 100644
--- a/src/pages/workspace/WorkspaceMembersPage.tsx
+++ b/src/pages/workspace/WorkspaceMembersPage.tsx
@@ -3,7 +3,6 @@ import {deepEqual} from 'fast-equals';
import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react';
import type {TextInput} from 'react-native';
import {InteractionManager, View} from 'react-native';
-import type {OnyxEntry} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import Button from '@components/Button';
import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu';
@@ -38,7 +37,7 @@ import {
clearInviteDraft,
clearWorkspaceOwnerChangeFlow,
downloadMembersCSV,
- isApproverTemp,
+ isApprover,
openWorkspaceMembersPage,
removeMembers,
updateWorkspaceMembersRole,
@@ -50,7 +49,7 @@ import Navigation from '@libs/Navigation/Navigation';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
import type {WorkspaceSplitNavigatorParamList} from '@libs/Navigation/types';
import {isPersonalDetailsReady, sortAlphabetically} from '@libs/OptionsListUtils';
-import {getAccountIDsByLogins, getDisplayNameOrDefault, getPersonalDetailsByIDs} from '@libs/PersonalDetailsUtils';
+import {getDisplayNameOrDefault, getPersonalDetailsByIDs} from '@libs/PersonalDetailsUtils';
import {getMemberAccountIDsForWorkspace, isDeletedPolicyEmployee, isExpensifyTeam, isPaidGroupPolicy, isPolicyAdmin as isPolicyAdminUtils} from '@libs/PolicyUtils';
import {getDisplayNameForParticipant} from '@libs/ReportUtils';
import tokenizedSearch from '@libs/tokenizedSearch';
@@ -61,8 +60,8 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
-import type {PersonalDetails, PersonalDetailsList, PolicyEmployee, PolicyEmployeeList} from '@src/types/onyx';
-import type {Errors, PendingAction} from '@src/types/onyx/OnyxCommon';
+import type {PersonalDetails, PolicyEmployee, PolicyEmployeeList} from '@src/types/onyx';
+import type {PendingAction} from '@src/types/onyx/OnyxCommon';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import MemberRightIcon from './MemberRightIcon';
import type {WithPolicyAndFullscreenLoadingProps} from './withPolicyAndFullscreenLoading';
@@ -79,26 +78,14 @@ function invertObject(object: Record): Record {
return Object.fromEntries(invertedEntries);
}
-type MemberOption = Omit & {accountID: number};
+type MemberOption = Omit & {accountID: number; login: string};
function WorkspaceMembersPage({personalDetails, route, policy}: WorkspaceMembersPageProps) {
- const {policyMemberEmailsToAccountIDs, employeeListDetails} = useMemo(() => {
- const emailsToAccountIDs = getMemberAccountIDsForWorkspace(policy?.employeeList, true);
- const details = Object.keys(policy?.employeeList ?? {}).reduce>((acc, email) => {
- const employee = policy?.employeeList?.[email];
- const accountID = emailsToAccountIDs[email];
- if (!employee) {
- return acc;
- }
- acc[accountID] = employee;
- return acc;
- }, {});
- return {policyMemberEmailsToAccountIDs: emailsToAccountIDs, employeeListDetails: details};
- }, [policy?.employeeList]);
+ const policyMemberEmailsToAccountIDs = useMemo(() => getMemberAccountIDsForWorkspace(policy?.employeeList, true), [policy?.employeeList]);
+ const employeeListDetails = useMemo(() => policy?.employeeList ?? ({} as PolicyEmployeeList), [policy?.employeeList]);
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
const styles = useThemeStyles();
const [removeMembersConfirmModalVisible, setRemoveMembersConfirmModalVisible] = useState(false);
- const [errors, setErrors] = useState({});
const {isOffline} = useNetwork();
const prevIsOffline = usePrevious(isOffline);
const accountIDs = useMemo(() => Object.values(policyMemberEmailsToAccountIDs ?? {}).map((accountID) => Number(accountID)), [policyMemberEmailsToAccountIDs]);
@@ -107,22 +94,20 @@ function WorkspaceMembersPage({personalDetails, route, policy}: WorkspaceMembers
const [isOfflineModalVisible, setIsOfflineModalVisible] = useState(false);
const [isDownloadFailureModalVisible, setIsDownloadFailureModalVisible] = useState(false);
const isOfflineAndNoMemberDataAvailable = isEmptyObject(policy?.employeeList) && isOffline;
- const prevPersonalDetails = usePrevious(personalDetails);
const {translate, formatPhoneNumber, localeCompare} = useLocalize();
const {isAccountLocked, showLockedAccountModal} = useContext(LockedAccountContext);
const filterEmployees = useCallback(
- (employee?: PolicyEmployee) => {
+ (employee: PolicyEmployee | undefined) => {
if (!employee?.email) {
return false;
}
- const employeeAccountID = getAccountIDsByLogins([employee.email]).at(0);
- if (!employeeAccountID) {
+ if (employee.email === policy?.owner || employee.email === currentUserPersonalDetails.login) {
return false;
}
- const isPendingDelete = employeeListDetails?.[employeeAccountID]?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE;
- return accountIDs.includes(employeeAccountID) && !isPendingDelete;
+ const isPendingDelete = employee.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE;
+ return !isPendingDelete;
},
- [accountIDs, employeeListDetails],
+ [currentUserPersonalDetails.login, policy?.owner],
);
const [selectedEmployees, setSelectedEmployees] = useFilteredSelection(employeeListDetails, filterEmployees);
@@ -159,29 +144,20 @@ function WorkspaceMembersPage({personalDetails, route, policy}: WorkspaceMembers
const canSelectMultiple = isPolicyAdmin && (shouldUseNarrowLayout ? isMobileSelectionModeEnabled : true);
const confirmModalPrompt = useMemo(() => {
- const approverAccountID = selectedEmployees.find((selectedEmployee) => isApproverTemp(policy, selectedEmployee));
- if (!approverAccountID) {
+ const approverEmail = selectedEmployees.find((selectedEmployee) => isApprover(policy, selectedEmployee));
+ if (!approverEmail) {
+ const firstSelectedEmployeeAccountID = policyMemberEmailsToAccountIDs[selectedEmployees[0]];
return translate('workspace.people.removeMembersPrompt', {
count: selectedEmployees.length,
- memberName: formatPhoneNumber(getPersonalDetailsByIDs({accountIDs: selectedEmployees, currentUserAccountID}).at(0)?.displayName ?? ''),
+ memberName: formatPhoneNumber(getPersonalDetailsByIDs({accountIDs: [firstSelectedEmployeeAccountID], currentUserAccountID}).at(0)?.displayName ?? ''),
});
}
+ const approverAccountID = policyMemberEmailsToAccountIDs[approverEmail];
return translate('workspace.people.removeMembersWarningPrompt', {
memberName: getDisplayNameForParticipant({accountID: approverAccountID}),
ownerName: getDisplayNameForParticipant({accountID: policy?.ownerAccountID}),
});
- }, [selectedEmployees, translate, policy, currentUserAccountID, formatPhoneNumber]);
- /**
- * Get filtered personalDetails list with current employeeList
- */
- const filterPersonalDetails = (members: OnyxEntry, details: OnyxEntry): PersonalDetailsList =>
- Object.keys(members ?? {}).reduce((acc, key) => {
- const memberAccountIdKey = policyMemberEmailsToAccountIDs[key] ?? '';
- if (details?.[memberAccountIdKey]) {
- acc[memberAccountIdKey] = details[memberAccountIdKey];
- }
- return acc;
- }, {} as PersonalDetailsList);
+ }, [selectedEmployees, policyMemberEmailsToAccountIDs, translate, policy, formatPhoneNumber, currentUserAccountID]);
/**
* Get members for the current workspace
*/
@@ -189,51 +165,17 @@ function WorkspaceMembersPage({personalDetails, route, policy}: WorkspaceMembers
openWorkspaceMembersPage(route.params.policyID, Object.keys(getMemberAccountIDsForWorkspace(policy?.employeeList)));
}, [route.params.policyID, policy?.employeeList]);
- /**
- * Check if the current selection includes members that cannot be removed
- */
- const validateSelection = useCallback(() => {
- const newErrors: Errors = {};
- selectedEmployees.forEach((member) => {
- if (member !== policy?.ownerAccountID && member !== session?.accountID) {
- return;
- }
- newErrors[member] = translate('workspace.people.error.cannotRemove');
- });
- setErrors(newErrors);
- }, [selectedEmployees, policy?.ownerAccountID, session?.accountID, translate]);
-
useEffect(() => {
getWorkspaceMembers();
}, [getWorkspaceMembers]);
useEffect(() => {
- validateSelection();
- }, [validateSelection]);
-
- useEffect(() => {
- if (removeMembersConfirmModalVisible && !deepEqual(accountIDs, prevAccountIDs)) {
- setRemoveMembersConfirmModalVisible(false);
+ if (!removeMembersConfirmModalVisible || deepEqual(accountIDs, prevAccountIDs)) {
+ return;
}
- setSelectedEmployees((prevSelectedEmployees) => {
- // Filter all personal details in order to use the elements needed for the current workspace
- const currentPersonalDetails = filterPersonalDetails(policy?.employeeList ?? {}, personalDetails);
- // We need to filter the previous selected employees by the new personal details, since unknown/new user id's change when transitioning from offline to online
- const prevSelectedElements = prevSelectedEmployees.map((id) => {
- const prevItem = prevPersonalDetails?.[id];
- const res = Object.values(currentPersonalDetails).find((item) => prevItem?.login === item?.login);
- return res?.accountID ?? id;
- });
-
- const currentSelectedElements = Object.entries(getMemberAccountIDsForWorkspace(policy?.employeeList))
- .filter((employee) => policy?.employeeList?.[employee[0]]?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE)
- .map((employee) => employee[1]);
-
- // This is an equivalent of the lodash intersection function. The reduce method below is used to filter the items that exist in both arrays.
- return [prevSelectedElements, currentSelectedElements].reduce((prev, members) => prev.filter((item) => members.includes(item)));
- });
+ setRemoveMembersConfirmModalVisible(false);
// eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
- }, [policy?.employeeList, policyMemberEmailsToAccountIDs]);
+ }, [accountIDs]);
useEffect(() => {
const isReconnecting = prevIsOffline && !isOffline;
@@ -260,19 +202,13 @@ function WorkspaceMembersPage({personalDetails, route, policy}: WorkspaceMembers
* Please see https://github.com/Expensify/App/blob/main/README.md#Security for more details
*/
const removeUsers = () => {
- if (!isEmptyObject(errors)) {
- return;
- }
-
- // Remove the admin from the list
- const accountIDsToRemove = session?.accountID ? selectedEmployees.filter((id) => id !== session.accountID) : selectedEmployees;
-
- // Check if any of the account IDs are approvers
- const hasApprovers = accountIDsToRemove.some((accountID) => isApproverTemp(policy, accountID));
+ // Check if any of the members are approvers
+ const hasApprovers = selectedEmployees.some((email) => isApprover(policy, email));
if (hasApprovers) {
const ownerEmail = ownerDetails.login;
- accountIDsToRemove.forEach((accountID) => {
+ selectedEmployees.forEach((login) => {
+ const accountID = policyMemberEmailsToAccountIDs[login];
const removedApprover = personalDetails?.[accountID];
if (!removedApprover?.login || !ownerEmail) {
return;
@@ -297,7 +233,7 @@ function WorkspaceMembersPage({personalDetails, route, policy}: WorkspaceMembers
// eslint-disable-next-line @typescript-eslint/no-deprecated
InteractionManager.runAfterInteractions(() => {
setSelectedEmployees([]);
- removeMembers(accountIDsToRemove, route.params.policyID);
+ removeMembers(policyID, selectedEmployees, policyMemberEmailsToAccountIDs);
});
};
@@ -305,9 +241,6 @@ function WorkspaceMembersPage({personalDetails, route, policy}: WorkspaceMembers
* Show the modal to confirm removal of the selected members
*/
const askForConfirmationToRemove = () => {
- if (!isEmptyObject(errors)) {
- return;
- }
setRemoveMembersConfirmModalVisible(true);
};
@@ -316,46 +249,42 @@ function WorkspaceMembersPage({personalDetails, route, policy}: WorkspaceMembers
*/
const toggleAllUsers = (memberList: MemberOption[]) => {
const enabledAccounts = memberList.filter((member) => !member.isDisabled && !member.isDisabledCheckbox);
- const someSelected = enabledAccounts.some((member) => selectedEmployees.includes(member.accountID));
+ const someSelected = selectedEmployees.length > 0;
if (someSelected) {
setSelectedEmployees([]);
} else {
- const everyAccountId = enabledAccounts.map((member) => member.accountID);
- setSelectedEmployees(everyAccountId);
+ const everyLogin = enabledAccounts.map((member) => member.login);
+ setSelectedEmployees(everyLogin);
}
-
- validateSelection();
};
/**
* Add user from the selectedEmployees list
*/
const addUser = useCallback(
- (accountID: number) => {
- setSelectedEmployees((prevSelected) => [...prevSelected, accountID]);
- validateSelection();
+ (login: string) => {
+ setSelectedEmployees((prevSelected) => [...prevSelected, login]);
},
- [validateSelection, setSelectedEmployees],
+ [setSelectedEmployees],
);
/**
* Remove user from the selectedEmployees list
*/
const removeUser = useCallback(
- (accountID: number) => {
- setSelectedEmployees((prevSelected) => prevSelected.filter((id) => id !== accountID));
- validateSelection();
+ (login: string) => {
+ setSelectedEmployees((prevSelected) => prevSelected.filter((email) => email !== login));
},
- [validateSelection, setSelectedEmployees],
+ [setSelectedEmployees],
);
/**
* Toggle user from the selectedEmployees list
*/
const toggleUser = useCallback(
- (accountID: number, pendingAction?: PendingAction) => {
- if (accountID === policy?.ownerAccountID && accountID !== session?.accountID) {
+ (login: string, pendingAction?: PendingAction) => {
+ if (login === policy?.owner && login !== currentUserPersonalDetails.login) {
return;
}
@@ -364,13 +293,13 @@ function WorkspaceMembersPage({personalDetails, route, policy}: WorkspaceMembers
}
// Add or remove the user if the checkbox is enabled
- if (selectedEmployees.includes(accountID)) {
- removeUser(accountID);
+ if (selectedEmployees.includes(login)) {
+ removeUser(login);
} else {
- addUser(accountID);
+ addUser(login);
}
},
- [selectedEmployees, addUser, removeUser, policy?.ownerAccountID, session?.accountID],
+ [policy?.owner, currentUserPersonalDetails.login, selectedEmployees, removeUser, addUser],
);
/** Opens the member details page */
@@ -394,9 +323,9 @@ function WorkspaceMembersPage({personalDetails, route, policy}: WorkspaceMembers
const dismissError = useCallback(
(item: MemberOption) => {
if (item.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) {
- clearDeleteMemberError(route.params.policyID, item.accountID);
+ clearDeleteMemberError(route.params.policyID, item.login);
} else {
- clearAddMemberError(route.params.policyID, item.accountID);
+ clearAddMemberError(route.params.policyID, item.login, item.accountID);
}
},
[route.params.policyID],
@@ -433,8 +362,9 @@ function WorkspaceMembersPage({personalDetails, route, policy}: WorkspaceMembers
const isPendingDeleteOrError = isPolicyAdmin && (policyEmployee.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || !isEmptyObject(policyEmployee.errors));
result.push({
- keyForList: String(accountID),
+ keyForList: details.login ?? '',
accountID,
+ login: details.login ?? '',
isDisabledCheckbox: !(isPolicyAdmin && accountID !== policy?.ownerAccountID && accountID !== session?.accountID),
isDisabled: isPendingDeleteOrError,
isInteractive: !details.isOptimisticPersonalDetail,
@@ -493,7 +423,7 @@ function WorkspaceMembersPage({personalDetails, route, policy}: WorkspaceMembers
if (isEmptyObject(invitedEmailsToAccountIDsDraft) || accountIDs === prevAccountIDs) {
return;
}
- const invitedEmails = Object.values(invitedEmailsToAccountIDsDraft).map(String);
+ const invitedEmails = Object.keys(invitedEmailsToAccountIDsDraft);
selectionListRef.current?.scrollAndHighlightItem?.(invitedEmails);
clearInviteDraft(route.params.policyID);
}, [invitedEmailsToAccountIDsDraft, isFocused, accountIDs, prevAccountIDs, route.params.policyID]);
@@ -555,17 +485,14 @@ function WorkspaceMembersPage({personalDetails, route, policy}: WorkspaceMembers
};
const changeUserRole = (role: ValueOf) => {
- if (!isEmptyObject(errors)) {
- return;
- }
-
- const accountIDsToUpdate = selectedEmployees.filter((accountID) => {
- const email = personalDetails?.[accountID]?.login ?? '';
- return policy?.employeeList?.[email]?.role !== role;
+ const loginsToUpdate = selectedEmployees.filter((login) => {
+ return policy?.employeeList?.[login]?.role !== role;
});
+ const accountIDsToUpdate = loginsToUpdate.map((login) => policyMemberEmailsToAccountIDs[login]).filter((id) => id !== undefined);
+
setSelectedEmployees([]);
- updateWorkspaceMembersRole(route.params.policyID, accountIDsToUpdate, role);
+ updateWorkspaceMembersRole(route.params.policyID, loginsToUpdate, accountIDsToUpdate, role);
};
const getBulkActionsButtonOptions = () => {
@@ -800,17 +727,17 @@ function WorkspaceMembersPage({personalDetails, route, policy}: WorkspaceMembers
ref={selectionListRef}
canSelectMultiple={canSelectMultiple}
sections={[{data: filteredData, isDisabled: false}]}
- selectedItems={selectedEmployees.map(String)}
+ selectedItems={selectedEmployees}
ListItem={TableListItem}
shouldUseDefaultRightHandSideCheckmark={false}
turnOnSelectionModeOnLongPress={isPolicyAdmin}
- onTurnOnSelectionMode={(item) => item && toggleUser(item?.accountID)}
+ onTurnOnSelectionMode={(item) => item && toggleUser(item.login)}
shouldUseUserSkeletonView
disableKeyboardShortcuts={removeMembersConfirmModalVisible}
headerMessage={shouldUseNarrowLayout ? headerMessage : undefined}
onSelectRow={openMemberDetails}
shouldSingleExecuteRowSelect={!isPolicyAdmin}
- onCheckboxPress={(item) => toggleUser(item.accountID)}
+ onCheckboxPress={(item) => toggleUser(item.login)}
onSelectAll={filteredData.length > 0 ? () => toggleAllUsers(filteredData) : undefined}
onDismissError={dismissError}
showLoadingPlaceholder={isLoading}
diff --git a/src/pages/workspace/WorkspacesListPage.tsx b/src/pages/workspace/WorkspacesListPage.tsx
index 7eb887c2f76e..a9aa479662b8 100755
--- a/src/pages/workspace/WorkspacesListPage.tsx
+++ b/src/pages/workspace/WorkspacesListPage.tsx
@@ -1,10 +1,12 @@
import {useIsFocused, useRoute} from '@react-navigation/native';
+import {Str} from 'expensify-common';
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {FlatList, InteractionManager, View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import Button from '@components/Button';
import ConfirmModal from '@components/ConfirmModal';
+import DomainsListRow from '@components/Domain/DomainsListRow';
import EmptyStateComponent from '@components/EmptyStateComponent';
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import * as Expensicons from '@components/Icon/Expensicons';
@@ -38,6 +40,7 @@ import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useTransactionViolationOfWorkspace from '@hooks/useTransactionViolationOfWorkspace';
import {isConnectionInProgress} from '@libs/actions/connections';
+import {openOldDotLink} from '@libs/actions/Link';
import {clearWorkspaceOwnerChangeFlow, requestWorkspaceOwnerChange} from '@libs/actions/Policy/Member';
import {calculateBillNewDot, clearDeleteWorkspaceError, clearDuplicateWorkspace, clearErrors, deleteWorkspace, leaveWorkspace, removeWorkspace} from '@libs/actions/Policy/Policy';
import {callFunctionIfActionIsAllowed} from '@libs/actions/Session';
@@ -75,7 +78,7 @@ import type {PolicyDetailsForNonMembers} from '@src/types/onyx/Policy';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import WorkspacesListRow from './WorkspacesListRow';
-type WorkspaceItem = ListItem &
+type WorkspaceItem = {listItemType: 'workspace'} & ListItem &
Required> &
Pick &
Pick &
@@ -87,9 +90,11 @@ type WorkspaceItem = ListItem &
policyID?: string;
isJoinRequestPending?: boolean;
};
+type DomainItem = {listItemType: 'domain'; title: string; action: () => void; disabled: boolean} & Pick;
+type WorkspaceOrDomainListItem = WorkspaceItem | DomainItem | {listItemType: 'domains-header' | 'workspaces-empty-state'};
-// eslint-disable-next-line react/no-unused-prop-types
-type GetMenuItem = {item: WorkspaceItem; index: number};
+type GetWorkspaceMenuItem = {item: WorkspaceItem; index: number};
+type GetDomainMenuItem = {item: DomainItem; index: number};
/**
* Dismisses the errors on one item
@@ -131,6 +136,9 @@ function WorkspacesListPage() {
const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true});
const [reimbursementAccountError] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {canBeMissing: true, selector: reimbursementAccountErrorSelector});
+ const [allDomains] = useOnyx(ONYXKEYS.COLLECTION.DOMAIN, {canBeMissing: false});
+ const [adminAccess] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_ADMIN_ACCESS, {canBeMissing: false});
+
// This hook preloads the screens of adjacent tabs to make changing tabs faster.
usePreloadFullScreenNavigators();
@@ -249,8 +257,8 @@ function WorkspacesListPage() {
/**
* Gets the menu item for each workspace
*/
- const getMenuItem = useCallback(
- ({item, index}: GetMenuItem) => {
+ const getWorkspaceMenuItem = useCallback(
+ ({item, index}: GetWorkspaceMenuItem) => {
const isAdmin = isPolicyAdmin(item as unknown as PolicyType, session?.email);
const isOwner = item.ownerAccountID === session?.accountID;
const isDefault = activePolicyID === item.policyID;
@@ -379,11 +387,7 @@ function WorkspacesListPage() {
translate,
policies,
fundList,
- styles.ph5,
- styles.mb2,
- styles.mh5,
- styles.hoveredComponentBG,
- styles.offlineFeedbackDeleted,
+ styles,
loadingSpinnerIconIndex,
shouldCalculateBillNewDot,
setIsDeletingPaidWorkspace,
@@ -396,6 +400,36 @@ function WorkspacesListPage() {
],
);
+ /**
+ * Gets the menu item for each domain
+ */
+ const getDomainMenuItem = useCallback(
+ ({item, index}: GetDomainMenuItem) => (
+