diff --git a/android/app/build.gradle b/android/app/build.gradle
index 5fb10a8173b6..919c08b99963 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -107,8 +107,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1001047703
- versionName "1.4.77-3"
+ versionCode 1001047708
+ versionName "1.4.77-8"
// 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/docs/Hidden/Instructions b/docs/Hidden/Instructions
new file mode 100644
index 000000000000..940c7ab60d10
--- /dev/null
+++ b/docs/Hidden/Instructions
@@ -0,0 +1 @@
+This folder is used to house articles that should not be live articles on the helpsite.
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index 9602b864b1ac..01d7c0869775 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -40,7 +40,7 @@
CFBundleVersion
- 1.4.77.3
+ 1.4.77.8
FullStory
OrgId
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index c123880655b2..40249a9864f8 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -19,6 +19,6 @@
CFBundleSignature
????
CFBundleVersion
- 1.4.77.3
+ 1.4.77.8
diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist
index 6cc928676c80..8e786dee39a1 100644
--- a/ios/NotificationServiceExtension/Info.plist
+++ b/ios/NotificationServiceExtension/Info.plist
@@ -13,7 +13,7 @@
CFBundleShortVersionString
1.4.77
CFBundleVersion
- 1.4.77.3
+ 1.4.77.8
NSExtension
NSExtensionPointIdentifier
diff --git a/package-lock.json b/package-lock.json
index a552305e84b4..d9cb3daafff5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "1.4.77-3",
+ "version": "1.4.77-8",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "1.4.77-3",
+ "version": "1.4.77-8",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
@@ -82,7 +82,7 @@
"react-error-boundary": "^4.0.11",
"react-fast-pdf": "1.0.13",
"react-map-gl": "^7.1.3",
- "react-native": "0.73.5",
+ "react-native": "0.73.4",
"react-native-android-location-enabler": "^2.0.1",
"react-native-blob-util": "0.19.4",
"react-native-collapsible": "^1.6.1",
@@ -7730,19 +7730,19 @@
}
},
"node_modules/@react-native-community/cli": {
- "version": "12.3.6",
- "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-12.3.6.tgz",
- "integrity": "sha512-647OSi6xBb8FbwFqX9zsJxOzu685AWtrOUWHfOkbKD+5LOpGORw+GQo0F9rWZnB68rLQyfKUZWJeaD00pGv5fw==",
- "dependencies": {
- "@react-native-community/cli-clean": "12.3.6",
- "@react-native-community/cli-config": "12.3.6",
- "@react-native-community/cli-debugger-ui": "12.3.6",
- "@react-native-community/cli-doctor": "12.3.6",
- "@react-native-community/cli-hermes": "12.3.6",
- "@react-native-community/cli-plugin-metro": "12.3.6",
- "@react-native-community/cli-server-api": "12.3.6",
- "@react-native-community/cli-tools": "12.3.6",
- "@react-native-community/cli-types": "12.3.6",
+ "version": "12.3.2",
+ "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-12.3.2.tgz",
+ "integrity": "sha512-WgoUWwLDcf/G1Su2COUUVs3RzAwnV/vUTdISSpAUGgSc57mPabaAoUctKTnfYEhCnE3j02k3VtaVPwCAFRO3TQ==",
+ "dependencies": {
+ "@react-native-community/cli-clean": "12.3.2",
+ "@react-native-community/cli-config": "12.3.2",
+ "@react-native-community/cli-debugger-ui": "12.3.2",
+ "@react-native-community/cli-doctor": "12.3.2",
+ "@react-native-community/cli-hermes": "12.3.2",
+ "@react-native-community/cli-plugin-metro": "12.3.2",
+ "@react-native-community/cli-server-api": "12.3.2",
+ "@react-native-community/cli-tools": "12.3.2",
+ "@react-native-community/cli-types": "12.3.2",
"chalk": "^4.1.2",
"commander": "^9.4.1",
"deepmerge": "^4.3.0",
@@ -7761,11 +7761,11 @@
}
},
"node_modules/@react-native-community/cli-clean": {
- "version": "12.3.6",
- "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-12.3.6.tgz",
- "integrity": "sha512-gUU29ep8xM0BbnZjwz9MyID74KKwutq9x5iv4BCr2im6nly4UMf1B1D+V225wR7VcDGzbgWjaezsJShLLhC5ig==",
+ "version": "12.3.2",
+ "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-12.3.2.tgz",
+ "integrity": "sha512-90k2hCX0ddSFPT7EN7h5SZj0XZPXP0+y/++v262hssoey3nhurwF57NGWN0XAR0o9BSW7+mBfeInfabzDraO6A==",
"dependencies": {
- "@react-native-community/cli-tools": "12.3.6",
+ "@react-native-community/cli-tools": "12.3.2",
"chalk": "^4.1.2",
"execa": "^5.0.0"
}
@@ -7835,11 +7835,11 @@
}
},
"node_modules/@react-native-community/cli-config": {
- "version": "12.3.6",
- "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-12.3.6.tgz",
- "integrity": "sha512-JGWSYQ9EAK6m2v0abXwFLEfsqJ1zkhzZ4CV261QZF9MoUNB6h57a274h1MLQR9mG6Tsh38wBUuNfEPUvS1vYew==",
+ "version": "12.3.2",
+ "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-12.3.2.tgz",
+ "integrity": "sha512-UUCzDjQgvAVL/57rL7eOuFUhd+d+6qfM7V8uOegQFeFEmSmvUUDLYoXpBa5vAK9JgQtSqMBJ1Shmwao+/oElxQ==",
"dependencies": {
- "@react-native-community/cli-tools": "12.3.6",
+ "@react-native-community/cli-tools": "12.3.2",
"chalk": "^4.1.2",
"cosmiconfig": "^5.1.0",
"deepmerge": "^4.3.0",
@@ -7958,28 +7958,29 @@
}
},
"node_modules/@react-native-community/cli-debugger-ui": {
- "version": "12.3.6",
- "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-12.3.6.tgz",
- "integrity": "sha512-SjUKKsx5FmcK9G6Pb6UBFT0s9JexVStK5WInmANw75Hm7YokVvHEgtprQDz2Uvy5znX5g2ujzrkIU//T15KQzA==",
+ "version": "12.3.2",
+ "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-12.3.2.tgz",
+ "integrity": "sha512-nSWQUL+51J682DlfcC1bjkUbQbGvHCC25jpqTwHIjmmVjYCX1uHuhPSqQKgPNdvtfOkrkACxczd7kVMmetxY2Q==",
"dependencies": {
"serve-static": "^1.13.1"
}
},
"node_modules/@react-native-community/cli-doctor": {
- "version": "12.3.6",
- "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-12.3.6.tgz",
- "integrity": "sha512-fvBDv2lTthfw4WOQKkdTop2PlE9GtfrlNnpjB818MhcdEnPjfQw5YaTUcnNEGsvGomdCs1MVRMgYXXwPSN6OvQ==",
- "dependencies": {
- "@react-native-community/cli-config": "12.3.6",
- "@react-native-community/cli-platform-android": "12.3.6",
- "@react-native-community/cli-platform-ios": "12.3.6",
- "@react-native-community/cli-tools": "12.3.6",
+ "version": "12.3.2",
+ "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-12.3.2.tgz",
+ "integrity": "sha512-GrAabdY4qtBX49knHFvEAdLtCjkmndjTeqhYO6BhsbAeKOtspcLT/0WRgdLIaKODRa61ADNB3K5Zm4dU0QrZOg==",
+ "dependencies": {
+ "@react-native-community/cli-config": "12.3.2",
+ "@react-native-community/cli-platform-android": "12.3.2",
+ "@react-native-community/cli-platform-ios": "12.3.2",
+ "@react-native-community/cli-tools": "12.3.2",
"chalk": "^4.1.2",
"command-exists": "^1.2.8",
"deepmerge": "^4.3.0",
"envinfo": "^7.10.0",
"execa": "^5.0.0",
"hermes-profile-transformer": "^0.0.6",
+ "ip": "^1.1.5",
"node-stream-zip": "^1.9.1",
"ora": "^5.4.1",
"semver": "^7.5.2",
@@ -8041,6 +8042,11 @@
"node": ">=8"
}
},
+ "node_modules/@react-native-community/cli-doctor/node_modules/ip": {
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz",
+ "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ=="
+ },
"node_modules/@react-native-community/cli-doctor/node_modules/strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
@@ -8064,14 +8070,15 @@
}
},
"node_modules/@react-native-community/cli-hermes": {
- "version": "12.3.6",
- "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-12.3.6.tgz",
- "integrity": "sha512-sNGwfOCl8OAIjWCkwuLpP8NZbuO0dhDI/2W7NeOGDzIBsf4/c4MptTrULWtGIH9okVPLSPX0NnRyGQ+mSwWyuQ==",
+ "version": "12.3.2",
+ "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-12.3.2.tgz",
+ "integrity": "sha512-SL6F9O8ghp4ESBFH2YAPLtIN39jdnvGBKnK4FGKpDCjtB3DnUmDsGFlH46S+GGt5M6VzfG2eeKEOKf3pZ6jUzA==",
"dependencies": {
- "@react-native-community/cli-platform-android": "12.3.6",
- "@react-native-community/cli-tools": "12.3.6",
+ "@react-native-community/cli-platform-android": "12.3.2",
+ "@react-native-community/cli-tools": "12.3.2",
"chalk": "^4.1.2",
- "hermes-profile-transformer": "^0.0.6"
+ "hermes-profile-transformer": "^0.0.6",
+ "ip": "^1.1.5"
}
},
"node_modules/@react-native-community/cli-hermes/node_modules/ansi-styles": {
@@ -8127,6 +8134,11 @@
"node": ">=8"
}
},
+ "node_modules/@react-native-community/cli-hermes/node_modules/ip": {
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz",
+ "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ=="
+ },
"node_modules/@react-native-community/cli-hermes/node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -8139,11 +8151,11 @@
}
},
"node_modules/@react-native-community/cli-platform-android": {
- "version": "12.3.6",
- "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-12.3.6.tgz",
- "integrity": "sha512-DeDDAB8lHpuGIAPXeeD9Qu2+/wDTFPo99c8uSW49L0hkmZJixzvvvffbGQAYk32H0TmaI7rzvzH+qzu7z3891g==",
+ "version": "12.3.2",
+ "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-12.3.2.tgz",
+ "integrity": "sha512-MZ5nO8yi/N+Fj2i9BJcJ9C/ez+9/Ir7lQt49DWRo9YDmzye66mYLr/P2l/qxsixllbbDi7BXrlLpxaEhMrDopg==",
"dependencies": {
- "@react-native-community/cli-tools": "12.3.6",
+ "@react-native-community/cli-tools": "12.3.2",
"chalk": "^4.1.2",
"execa": "^5.0.0",
"fast-xml-parser": "^4.2.4",
@@ -8216,11 +8228,11 @@
}
},
"node_modules/@react-native-community/cli-platform-ios": {
- "version": "12.3.6",
- "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-12.3.6.tgz",
- "integrity": "sha512-3eZ0jMCkKUO58wzPWlvAPRqezVKm9EPZyaPyHbRPWU8qw7JqkvnRlWIaYDGpjCJgVW4k2hKsEursLtYKb188tg==",
+ "version": "12.3.2",
+ "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-12.3.2.tgz",
+ "integrity": "sha512-OcWEAbkev1IL6SUiQnM6DQdsvfsKZhRZtoBNSj9MfdmwotVZSOEZJ+IjZ1FR9ChvMWayO9ns/o8LgoQxr1ZXeg==",
"dependencies": {
- "@react-native-community/cli-tools": "12.3.6",
+ "@react-native-community/cli-tools": "12.3.2",
"chalk": "^4.1.2",
"execa": "^5.0.0",
"fast-xml-parser": "^4.0.12",
@@ -8293,17 +8305,17 @@
}
},
"node_modules/@react-native-community/cli-plugin-metro": {
- "version": "12.3.6",
- "resolved": "https://registry.npmjs.org/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-12.3.6.tgz",
- "integrity": "sha512-3jxSBQt4fkS+KtHCPSyB5auIT+KKIrPCv9Dk14FbvOaEh9erUWEm/5PZWmtboW1z7CYeNbFMeXm9fM2xwtVOpg=="
+ "version": "12.3.2",
+ "resolved": "https://registry.npmjs.org/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-12.3.2.tgz",
+ "integrity": "sha512-FpFBwu+d2E7KRhYPTkKvQsWb2/JKsJv+t1tcqgQkn+oByhp+qGyXBobFB8/R3yYvRRDCSDhS+atWTJzk9TjM8g=="
},
"node_modules/@react-native-community/cli-server-api": {
- "version": "12.3.6",
- "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-12.3.6.tgz",
- "integrity": "sha512-80NIMzo8b2W+PL0Jd7NjiJW9mgaT8Y8wsIT/lh6mAvYH7mK0ecDJUYUTAAv79Tbo1iCGPAr3T295DlVtS8s4yQ==",
+ "version": "12.3.2",
+ "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-12.3.2.tgz",
+ "integrity": "sha512-iwa7EO9XFA/OjI5pPLLpI/6mFVqv8L73kNck3CNOJIUCCveGXBKK0VMyOkXaf/BYnihgQrXh+x5cxbDbggr7+Q==",
"dependencies": {
- "@react-native-community/cli-debugger-ui": "12.3.6",
- "@react-native-community/cli-tools": "12.3.6",
+ "@react-native-community/cli-debugger-ui": "12.3.2",
+ "@react-native-community/cli-tools": "12.3.2",
"compression": "^1.7.1",
"connect": "^3.6.5",
"errorhandler": "^1.5.1",
@@ -8448,9 +8460,9 @@
}
},
"node_modules/@react-native-community/cli-tools": {
- "version": "12.3.6",
- "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-12.3.6.tgz",
- "integrity": "sha512-FPEvZn19UTMMXUp/piwKZSh8cMEfO8G3KDtOwo53O347GTcwNrKjgZGtLSPELBX2gr+YlzEft3CoRv2Qmo83fQ==",
+ "version": "12.3.2",
+ "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-12.3.2.tgz",
+ "integrity": "sha512-nDH7vuEicHI2TI0jac/DjT3fr977iWXRdgVAqPZFFczlbs7A8GQvEdGnZ1G8dqRUmg+kptw0e4hwczAOG89JzQ==",
"dependencies": {
"appdirsjs": "^1.2.4",
"chalk": "^4.1.2",
@@ -8548,9 +8560,9 @@
}
},
"node_modules/@react-native-community/cli-types": {
- "version": "12.3.6",
- "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-12.3.6.tgz",
- "integrity": "sha512-xPqTgcUtZowQ8WKOkI9TLGBwH2bGggOC4d2FFaIRST3gTcjrEeGRNeR5aXCzJFIgItIft8sd7p2oKEdy90+01Q==",
+ "version": "12.3.2",
+ "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-12.3.2.tgz",
+ "integrity": "sha512-9D0UEFqLW8JmS16mjHJxUJWX8E+zJddrHILSH8AJHZ0NNHv4u2DXKdb0wFLMobFxGNxPT+VSOjc60fGvXzWHog==",
"dependencies": {
"joi": "^17.2.1"
}
@@ -9002,13 +9014,13 @@
}
},
"node_modules/@react-native/community-cli-plugin": {
- "version": "0.73.17",
- "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.73.17.tgz",
- "integrity": "sha512-F3PXZkcHg+1ARIr6FRQCQiB7ZAA+MQXGmq051metRscoLvgYJwj7dgC8pvgy0kexzUkHu5BNKrZeySzUft3xuQ==",
+ "version": "0.73.16",
+ "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.73.16.tgz",
+ "integrity": "sha512-eNH3v3qJJF6f0n/Dck90qfC9gVOR4coAXMTdYECO33GfgjTi+73vf/SBqlXw9HICH/RNZYGPM3wca4FRF7TYeQ==",
"dependencies": {
- "@react-native-community/cli-server-api": "12.3.6",
- "@react-native-community/cli-tools": "12.3.6",
- "@react-native/dev-middleware": "0.73.8",
+ "@react-native-community/cli-server-api": "12.3.2",
+ "@react-native-community/cli-tools": "12.3.2",
+ "@react-native/dev-middleware": "0.73.7",
"@react-native/metro-babel-transformer": "0.73.15",
"chalk": "^4.0.0",
"execa": "^5.1.1",
@@ -9094,9 +9106,8 @@
}
},
"node_modules/@react-native/dev-middleware": {
- "version": "0.73.8",
- "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.73.8.tgz",
- "integrity": "sha512-oph4NamCIxkMfUL/fYtSsE+JbGOnrlawfQ0kKtDQ5xbOjPKotKoXqrs1eGwozNKv7FfQ393stk1by9a6DyASSg==",
+ "version": "0.73.7",
+ "license": "MIT",
"dependencies": {
"@isaacs/ttlcache": "^1.4.1",
"@react-native/debugger-frontend": "0.73.3",
@@ -9107,8 +9118,7 @@
"node-fetch": "^2.2.0",
"open": "^7.0.3",
"serve-static": "^1.13.1",
- "temp-dir": "^2.0.0",
- "ws": "^6.2.2"
+ "temp-dir": "^2.0.0"
},
"engines": {
"node": ">=18"
@@ -9139,14 +9149,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/@react-native/dev-middleware/node_modules/ws": {
- "version": "6.2.2",
- "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz",
- "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==",
- "dependencies": {
- "async-limiter": "~1.0.0"
- }
- },
"node_modules/@react-native/gradle-plugin": {
"version": "0.73.4",
"license": "MIT",
@@ -16797,8 +16799,7 @@
},
"node_modules/colorette": {
"version": "1.4.0",
- "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz",
- "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g=="
+ "license": "MIT"
},
"node_modules/combined-stream": {
"version": "1.0.8",
@@ -17866,9 +17867,9 @@
}
},
"node_modules/dayjs": {
- "version": "1.11.11",
- "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz",
- "integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg=="
+ "version": "1.11.10",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz",
+ "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ=="
},
"node_modules/debounce": {
"version": "1.2.1",
@@ -20946,9 +20947,9 @@
"license": "MIT"
},
"node_modules/fast-xml-parser": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.0.tgz",
- "integrity": "sha512-kLY3jFlwIYwBNDojclKsNAC12sfD6NwW74QB2CoNGPvtVxjliYehVunB3HYyNi+n4Tt1dAcgwYvmKF/Z18flqg==",
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.6.tgz",
+ "integrity": "sha512-M2SovcRxD4+vC493Uc2GZVcZaj66CCJhWurC4viynVSTvrpErCShNcDz1lAho6n9REQKvL/ll4A4/fw6Y9z8nw==",
"funding": [
{
"type": "github",
@@ -26588,9 +26589,9 @@
}
},
"node_modules/joi": {
- "version": "17.13.1",
- "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.1.tgz",
- "integrity": "sha512-vaBlIKCyo4FCUtCm7Eu4QZd/q02bWcxfUO6YSXAZOWF6gzcLBeba8kwotUdYJjDLW8Cz8RywsSOqiNJZW0mNvg==",
+ "version": "17.12.3",
+ "resolved": "https://registry.npmjs.org/joi/-/joi-17.12.3.tgz",
+ "integrity": "sha512-2RRziagf555owrm9IRVtdKynOBeITiDpuZqIpgwqXShPncPKNiRQoiGsl/T8SQdq+8ugRzH2LqY67irr2y/d+g==",
"dependencies": {
"@hapi/hoek": "^9.3.0",
"@hapi/topo": "^5.1.0",
@@ -27355,6 +27356,14 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/logkitty/node_modules/camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/logkitty/node_modules/cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
@@ -27471,6 +27480,18 @@
"node": ">=8"
}
},
+ "node_modules/logkitty/node_modules/yargs-parser": {
+ "version": "18.1.3",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
+ "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
+ "dependencies": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/longest": {
"version": "1.0.1",
"license": "MIT",
@@ -30590,6 +30611,13 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/qrcode/node_modules/camelcase": {
+ "version": "5.3.1",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/qrcode/node_modules/cliui": {
"version": "6.0.0",
"license": "ISC",
@@ -30696,6 +30724,17 @@
"node": ">=8"
}
},
+ "node_modules/qrcode/node_modules/yargs-parser": {
+ "version": "18.1.3",
+ "license": "ISC",
+ "dependencies": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/qs": {
"version": "6.10.3",
"license": "BSD-3-Clause",
@@ -31115,17 +31154,17 @@
}
},
"node_modules/react-native": {
- "version": "0.73.5",
- "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.73.5.tgz",
- "integrity": "sha512-iHgDArmF4CrhL0qTj+Rn+CBN5pZWUL9lUGl8ub+V9Hwu/vnzQQh8rTMVSwVd2sV6N76KjpE5a4TfIAHkpIHhKg==",
+ "version": "0.73.4",
+ "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.73.4.tgz",
+ "integrity": "sha512-VtS+Yr6OOTIuJGDECIYWzNU8QpJjASQYvMtfa/Hvm/2/h5GdB6W9H9TOmh13x07Lj4AOhNMx3XSsz6TdrO4jIg==",
"dependencies": {
"@jest/create-cache-key-function": "^29.6.3",
- "@react-native-community/cli": "12.3.6",
- "@react-native-community/cli-platform-android": "12.3.6",
- "@react-native-community/cli-platform-ios": "12.3.6",
+ "@react-native-community/cli": "12.3.2",
+ "@react-native-community/cli-platform-android": "12.3.2",
+ "@react-native-community/cli-platform-ios": "12.3.2",
"@react-native/assets-registry": "0.73.1",
"@react-native/codegen": "0.73.3",
- "@react-native/community-cli-plugin": "0.73.17",
+ "@react-native/community-cli-plugin": "0.73.16",
"@react-native/gradle-plugin": "0.73.4",
"@react-native/js-polyfills": "0.73.1",
"@react-native/normalize-colors": "0.73.2",
@@ -37908,26 +37947,6 @@
"node": ">=12"
}
},
- "node_modules/yargs-parser": {
- "version": "18.1.3",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
- "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
- "dependencies": {
- "camelcase": "^5.0.0",
- "decamelize": "^1.2.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/yargs-parser/node_modules/camelcase": {
- "version": "5.3.1",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
- "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/yargs/node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
diff --git a/package.json b/package.json
index 5d1244a40dab..b3a31ab149a8 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.4.77-3",
+ "version": "1.4.77-8",
"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 @@
"react-error-boundary": "^4.0.11",
"react-fast-pdf": "1.0.13",
"react-map-gl": "^7.1.3",
- "react-native": "0.73.5",
+ "react-native": "0.73.4",
"react-native-android-location-enabler": "^2.0.1",
"react-native-blob-util": "0.19.4",
"react-native-collapsible": "^1.6.1",
@@ -302,7 +302,7 @@
"yaml": "^2.2.1"
},
"overrides": {
- "react-native": "0.73.5",
+ "react-native": "0.73.4",
"expo": "$expo",
"react-native-svg": "$react-native-svg"
},
diff --git a/src/CONST.ts b/src/CONST.ts
index 8e889b82c498..d00cc5e04540 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -662,9 +662,9 @@ const CONST = {
DELETED_ACCOUNT: 'DELETEDACCOUNT', // OldDot Action
DISMISSED_VIOLATION: 'DISMISSEDVIOLATION',
DONATION: 'DONATION', // OldDot Action
- EXPORTED_TO_CSV: 'EXPORTEDTOCSV', // OldDot Action
- EXPORTED_TO_INTEGRATION: 'EXPORTEDTOINTEGRATION', // OldDot Action
- EXPORTED_TO_QUICK_BOOKS: 'EXPORTEDTOQUICKBOOKS', // OldDot Action
+ EXPORTED_TO_CSV: 'EXPORTCSV', // OldDot Action
+ EXPORTED_TO_INTEGRATION: 'EXPORTINTEGRATION', // OldDot Action
+ EXPORTED_TO_QUICK_BOOKS: 'EXPORTED', // OldDot Action
FORWARDED: 'FORWARDED', // OldDot Action
HOLD: 'HOLD',
HOLD_COMMENT: 'HOLDCOMMENT',
@@ -1188,6 +1188,10 @@ const CONST = {
WEBP: 'image/webp',
JPEG: 'image/jpeg',
},
+ ATTACHMENT_TYPE: {
+ REPORT: 'r',
+ NOTE: 'n',
+ },
IMAGE_OBJECT_POSITION: {
TOP: 'top',
@@ -1307,12 +1311,13 @@ const CONST = {
SYNC: 'sync',
ENABLE_NEW_CATEGORIES: 'enableNewCategories',
EXPORT: 'export',
+ TENANT_ID: 'tenantID',
IMPORT_CUSTOMERS: 'importCustomers',
IMPORT_TAX_RATES: 'importTaxRates',
INVOICE_STATUS: {
- AWAITING_PAYMENT: 'AWT_PAYMENT',
DRAFT: 'DRAFT',
AWAITING_APPROVAL: 'AWT_APPROVAL',
+ AWAITING_PAYMENT: 'AWT_PAYMENT',
},
IMPORT_TRACKING_CATEGORIES: 'importTrackingCategories',
MAPPINGS: 'mappings',
@@ -1597,6 +1602,9 @@ const CONST = {
ACCOUNTANT: 'accountant',
},
},
+ ACCESS_VARIANTS: {
+ CREATE: 'create',
+ },
},
GROWL: {
@@ -1777,7 +1785,8 @@ const CONST = {
XERO: 'xero',
},
SYNC_STAGE_NAME: {
- STARTING_IMPORT: 'startingImport',
+ STARTING_IMPORT_QBO: 'startingImportQBO',
+ STARTING_IMPORT_XERO: 'startingImportXero',
QBO_IMPORT_MAIN: 'quickbooksOnlineImportMain',
QBO_IMPORT_CUSTOMERS: 'quickbooksOnlineImportCustomers',
QBO_IMPORT_EMPLOYEES: 'quickbooksOnlineImportEmployees',
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index ed6e1cd3ce38..a382c16c8136 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -196,7 +196,10 @@ const ROUTES = {
SETTINGS_STATUS_CLEAR_AFTER_DATE: 'settings/profile/status/clear-after/date',
SETTINGS_STATUS_CLEAR_AFTER_TIME: 'settings/profile/status/clear-after/time',
SETTINGS_TROUBLESHOOT: 'settings/troubleshoot',
- SETTINGS_CONSOLE: 'settings/troubleshoot/console',
+ SETTINGS_CONSOLE: {
+ route: 'settings/troubleshoot/console',
+ getRoute: (backTo?: string) => getUrlWithBackToParam(`settings/troubleshoot/console`, backTo),
+ },
SETTINGS_SHARE_LOG: {
route: 'settings/troubleshoot/console/share-log',
getRoute: (source: string) => `settings/troubleshoot/console/share-log?source=${encodeURI(source)}` as const,
@@ -248,9 +251,10 @@ const ROUTES = {
route: 'r/:reportID/details/shareCode',
getRoute: (reportID: string) => `r/${reportID}/details/shareCode` as const,
},
- REPORT_ATTACHMENTS: {
- route: 'r/:reportID/attachment',
- getRoute: (reportID: string, source: string) => `r/${reportID}/attachment?source=${encodeURIComponent(source)}` as const,
+ ATTACHMENTS: {
+ route: 'attachment',
+ getRoute: (reportID: string, type: ValueOf, url: string, accountID?: number) =>
+ `attachment?source=${encodeURIComponent(url)}&type=${type}${reportID ? `&reportID=${reportID}` : ''}${accountID ? `&accountID=${accountID}` : ''}` as const,
},
REPORT_PARTICIPANTS: {
route: 'r/:reportID/participants',
@@ -686,12 +690,12 @@ const ROUTES = {
getRoute: (policyID: string, orderWeight: number) => `settings/workspaces/${policyID}/tags/${orderWeight}/edit` as const,
},
WORKSPACE_TAG_EDIT: {
- route: 'settings/workspace/:policyID/tag/:tagName/edit',
- getRoute: (policyID: string, tagName: string) => `settings/workspace/${policyID}/tag/${encodeURIComponent(tagName)}/edit` as const,
+ route: 'settings/workspaces/:policyID/tag/:orderWeight/:tagName/edit',
+ getRoute: (policyID: string, orderWeight: number, tagName: string) => `settings/workspaces/${policyID}/tag/${orderWeight}/${encodeURIComponent(tagName)}/edit` as const,
},
WORKSPACE_TAG_SETTINGS: {
- route: 'settings/workspaces/:policyID/tag/:tagName',
- getRoute: (policyID: string, tagName: string) => `settings/workspaces/${policyID}/tag/${encodeURIComponent(tagName)}` as const,
+ route: 'settings/workspaces/:policyID/tag/:orderWeight/:tagName',
+ getRoute: (policyID: string, orderWeight: number, tagName: string) => `settings/workspaces/${policyID}/tag/${orderWeight}/${encodeURIComponent(tagName)}` as const,
},
WORKSPACE_TAG_LIST_VIEW: {
route: 'settings/workspaces/:policyID/tag-list/:orderWeight',
@@ -809,17 +813,14 @@ const ROUTES = {
route: 'settings/workspaces/:policyID/accounting/xero/import/tracking-categories',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/xero/import/tracking-categories` as const,
},
- POLICY_ACCOUNTING_XERO_TRACKING_CATEGORIES_MAP_COST_CENTERS: {
- route: 'settings/workspaces/:policyID/accounting/xero/import/tracking-categories/cost-centers',
- getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/xero/import/tracking-categories/cost-centers` as const,
- },
- POLICY_ACCOUNTING_XERO_TRACKING_CATEGORIES_MAP_REGION: {
- route: 'settings/workspaces/:policyID/accounting/xero/import/tracking-categories/region',
- getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/xero/import/tracking-categories/region` as const,
+ POLICY_ACCOUNTING_XERO_TRACKING_CATEGORIES_MAP: {
+ route: 'settings/workspaces/:policyID/accounting/xero/import/tracking-categories/mapping/:categoryId/:categoryName',
+ getRoute: (policyID: string, categoryId: string, categoryName: string) =>
+ `settings/workspaces/${policyID}/accounting/xero/import/tracking-categories/mapping/${categoryId}/${encodeURIComponent(categoryName)}` as const,
},
POLICY_ACCOUNTING_XERO_CUSTOMER: {
- route: '/settings/workspaces/:policyID/accounting/xero/import/customers',
- getRoute: (policyID: string) => `/settings/workspaces/${policyID}/accounting/xero/import/customers` as const,
+ route: 'settings/workspaces/:policyID/accounting/xero/import/customers',
+ getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/xero/import/customers` as const,
},
POLICY_ACCOUNTING_XERO_TAXES: {
route: 'settings/workspaces/:policyID/accounting/xero/import/taxes',
@@ -830,8 +831,8 @@ const ROUTES = {
getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/xero/export` as const,
},
POLICY_ACCOUNTING_XERO_PREFERRED_EXPORTER_SELECT: {
- route: '/settings/workspaces/:policyID/connections/xero/export/preferred-exporter/select',
- getRoute: (policyID: string) => `/settings/workspaces/${policyID}/connections/xero/export/preferred-exporter/select` as const,
+ route: 'settings/workspaces/:policyID/connections/xero/export/preferred-exporter/select',
+ getRoute: (policyID: string) => `settings/workspaces/${policyID}/connections/xero/export/preferred-exporter/select` as const,
},
POLICY_ACCOUNTING_XERO_EXPORT_PURCHASE_BILL_DATE_SELECT: {
route: 'settings/workspaces/:policyID/accounting/xero/export/purchase-bill-date-select',
@@ -845,6 +846,10 @@ const ROUTES = {
route: 'settings/workspaces/:policyID/accounting/xero/advanced',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/xero/advanced` as const,
},
+ POLICY_ACCOUNTING_XERO_BILL_STATUS_SELECTOR: {
+ route: 'settings/workspaces/:policyID/accounting/xero/export/purchase-bill-status-selector',
+ getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/xero/export/purchase-bill-status-selector` as const,
+ },
POLICY_ACCOUNTING_XERO_INVOICE_SELECTOR: {
route: 'settings/workspaces/:policyID/accounting/xero/advanced/invoice-account-selector',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/xero/advanced/invoice-account-selector` as const,
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index 4e7243d0eb2c..b4965196ab3b 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -7,7 +7,7 @@ import type DeepValueOf from './types/utils/DeepValueOf';
const PROTECTED_SCREENS = {
HOME: 'Home',
CONCIERGE: 'Concierge',
- REPORT_ATTACHMENTS: 'ReportAttachments',
+ ATTACHMENTS: 'Attachments',
} as const;
const SCREENS = {
@@ -248,11 +248,11 @@ const SCREENS = {
XERO_CUSTOMER: 'Policy_Acounting_Xero_Import_Customer',
XERO_TAXES: 'Policy_Accounting_Xero_Taxes',
XERO_TRACKING_CATEGORIES: 'Policy_Accounting_Xero_Tracking_Categories',
- XERO_MAP_COST_CENTERS: 'Policy_Accounting_Xero_Map_Cost_Centers',
- XERO_MAP_REGION: 'Policy_Accounting_Xero_Map_Region',
+ XERO_MAP_TRACKING_CATEGORY: 'Policy_Accounting_Xero_Map_Tracking_Category',
XERO_EXPORT: 'Policy_Accounting_Xero_Export',
XERO_EXPORT_PURCHASE_BILL_DATE_SELECT: 'Policy_Accounting_Xero_Export_Purchase_Bill_Date_Select',
XERO_ADVANCED: 'Policy_Accounting_Xero_Advanced',
+ XERO_BILL_STATUS_SELECTOR: 'Policy_Accounting_Xero_Export_Bill_Status_Selector',
XERO_INVOICE_ACCOUNT_SELECTOR: 'Policy_Accounting_Xero_Invoice_Account_Selector',
XERO_EXPORT_PREFERRED_EXPORTER_SELECT: 'Workspace_Accounting_Xero_Export_Preferred_Exporter_Select',
XERO_BILL_PAYMENT_ACCOUNT_SELECTOR: 'Policy_Accounting_Xero_Bill_Payment_Account_Selector',
diff --git a/src/components/AttachmentContext.ts b/src/components/AttachmentContext.ts
new file mode 100644
index 000000000000..4ed6bdc9084f
--- /dev/null
+++ b/src/components/AttachmentContext.ts
@@ -0,0 +1,22 @@
+import {createContext} from 'react';
+import type {ValueOf} from 'type-fest';
+import type CONST from '@src/CONST';
+
+type AttachmentContextProps = {
+ type?: ValueOf;
+ reportID?: string;
+ accountID?: number;
+};
+
+const AttachmentContext = createContext({
+ type: undefined,
+ reportID: undefined,
+ accountID: undefined,
+});
+
+AttachmentContext.displayName = 'AttachmentContext';
+
+export {
+ // eslint-disable-next-line import/prefer-default-export
+ AttachmentContext,
+};
diff --git a/src/components/AttachmentModal.tsx b/src/components/AttachmentModal.tsx
index af7f482198bb..19a9b845093d 100644
--- a/src/components/AttachmentModal.tsx
+++ b/src/components/AttachmentModal.tsx
@@ -5,6 +5,7 @@ import {GestureHandlerRootView} from 'react-native-gesture-handler';
import {withOnyx} from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
import {useSharedValue} from 'react-native-reanimated';
+import type {ValueOf} from 'type-fest';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
@@ -101,6 +102,12 @@ type AttachmentModalProps = AttachmentModalOnyxProps & {
/** The report that has this attachment */
report?: OnyxEntry | EmptyObject;
+ /** The type of the attachment */
+ type?: ValueOf;
+
+ /** If the attachment originates from a note, the accountID will represent the author of that note. */
+ accountID?: number;
+
/** Optional callback to fire when we want to do something after modal show. */
onModalShow?: () => void;
@@ -156,6 +163,8 @@ function AttachmentModal({
onModalClose = () => {},
isLoading = false,
shouldShowNotFoundPage = false,
+ type = undefined,
+ accountID = undefined,
}: AttachmentModalProps) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
@@ -519,6 +528,8 @@ function AttachmentModal({
)}
{!isEmptyObject(report) && !isReceiptAttachment ? (
, reportActions?: OnyxEntry) {
- const actions = [...(parentReportAction ? [parentReportAction] : []), ...ReportActionsUtils.getSortedReportActions(Object.values(reportActions ?? {}))];
+function extractAttachments(
+ type: ValueOf,
+ {reportID, accountID, parentReportAction, reportActions}: {reportID?: string; accountID?: number; parentReportAction?: OnyxEntry; reportActions?: OnyxEntry},
+) {
+ const report = getReport(reportID);
+ const privateNotes = report?.privateNotes;
+ const targetNote = privateNotes?.[Number(accountID)]?.note ?? '';
const attachments: Attachment[] = [];
// We handle duplicate image sources by considering the first instance as original. Selecting any duplicate
@@ -71,6 +78,14 @@ function extractAttachmentsFromReport(parentReportAction?: OnyxEntry {
if (!ReportActionsUtils.shouldReportActionBeVisible(action, key) || ReportActionsUtils.isMoneyRequestAction(action)) {
return;
@@ -86,4 +101,4 @@ function extractAttachmentsFromReport(parentReportAction?: OnyxEntry(null);
@@ -30,16 +31,21 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source,
useEffect(() => {
const parentReportAction = report.parentReportActionID && parentReportActions ? parentReportActions[report.parentReportActionID] : undefined;
- const attachmentsFromReport = extractAttachmentsFromReport(parentReportAction, reportActions);
+ let targetAttachments: Attachment[] = [];
+ if (type === CONST.ATTACHMENT_TYPE.NOTE && accountID) {
+ targetAttachments = extractAttachments(CONST.ATTACHMENT_TYPE.NOTE, {reportID: report.reportID, accountID});
+ } else {
+ targetAttachments = extractAttachments(CONST.ATTACHMENT_TYPE.REPORT, {parentReportAction, reportActions});
+ }
- const initialPage = attachmentsFromReport.findIndex(compareImage);
+ const initialPage = targetAttachments.findIndex(compareImage);
// Dismiss the modal when deleting an attachment during its display in preview.
if (initialPage === -1 && attachments.find(compareImage)) {
Navigation.dismissModal();
} else {
setPage(initialPage);
- setAttachments(attachmentsFromReport);
+ setAttachments(targetAttachments);
// Update the download button visibility in the parent modal
if (setDownloadButtonVisibility) {
@@ -47,8 +53,8 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source,
}
// Update the parent modal's state with the source and name from the mapped attachments
- if (attachmentsFromReport[initialPage] !== undefined && onNavigate) {
- onNavigate(attachmentsFromReport[initialPage]);
+ if (targetAttachments[initialPage] !== undefined && onNavigate) {
+ onNavigate(targetAttachments[initialPage]);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
diff --git a/src/components/Attachments/AttachmentCarousel/index.tsx b/src/components/Attachments/AttachmentCarousel/index.tsx
index 23b285faf10e..947569538d32 100644
--- a/src/components/Attachments/AttachmentCarousel/index.tsx
+++ b/src/components/Attachments/AttachmentCarousel/index.tsx
@@ -21,7 +21,7 @@ import ONYXKEYS from '@src/ONYXKEYS';
import CarouselActions from './CarouselActions';
import CarouselButtons from './CarouselButtons';
import CarouselItem from './CarouselItem';
-import extractAttachmentsFromReport from './extractAttachmentsFromReport';
+import extractAttachments from './extractAttachments';
import type {AttachmentCaraouselOnyxProps, AttachmentCarouselProps, UpdatePageProps} from './types';
import useCarouselArrows from './useCarouselArrows';
@@ -33,7 +33,7 @@ const viewabilityConfig = {
const MIN_FLING_VELOCITY = 500;
-function AttachmentCarousel({report, reportActions, parentReportActions, source, onNavigate, setDownloadButtonVisibility}: AttachmentCarouselProps) {
+function AttachmentCarousel({report, reportActions, parentReportActions, source, onNavigate, setDownloadButtonVisibility, type, accountID}: AttachmentCarouselProps) {
const theme = useTheme();
const {translate} = useLocalize();
const {isSmallScreenWidth, windowWidth} = useWindowDimensions();
@@ -57,9 +57,14 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source,
useEffect(() => {
const parentReportAction = report.parentReportActionID && parentReportActions ? parentReportActions[report.parentReportActionID] : undefined;
- const attachmentsFromReport = extractAttachmentsFromReport(parentReportAction, reportActions ?? undefined);
+ let targetAttachments: Attachment[] = [];
+ if (type === CONST.ATTACHMENT_TYPE.NOTE && accountID) {
+ targetAttachments = extractAttachments(CONST.ATTACHMENT_TYPE.NOTE, {reportID: report.reportID, accountID});
+ } else {
+ targetAttachments = extractAttachments(CONST.ATTACHMENT_TYPE.REPORT, {parentReportAction, reportActions: reportActions ?? undefined});
+ }
- if (isEqual(attachments, attachmentsFromReport)) {
+ if (isEqual(attachments, targetAttachments)) {
if (attachments.length === 0) {
setPage(-1);
setDownloadButtonVisibility?.(false);
@@ -67,14 +72,14 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source,
return;
}
- const initialPage = attachmentsFromReport.findIndex(compareImage);
+ const initialPage = targetAttachments.findIndex(compareImage);
// Dismiss the modal when deleting an attachment during its display in preview.
if (initialPage === -1 && attachments.find(compareImage)) {
Navigation.dismissModal();
} else {
setPage(initialPage);
- setAttachments(attachmentsFromReport);
+ setAttachments(targetAttachments);
// Update the download button visibility in the parent modal
if (setDownloadButtonVisibility) {
@@ -82,11 +87,11 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source,
}
// Update the parent modal's state with the source and name from the mapped attachments
- if (attachmentsFromReport[initialPage] !== undefined && onNavigate) {
- onNavigate(attachmentsFromReport[initialPage]);
+ if (targetAttachments[initialPage] !== undefined && onNavigate) {
+ onNavigate(targetAttachments[initialPage]);
}
}
- }, [reportActions, parentReportActions, compareImage, report.parentReportActionID, attachments, setDownloadButtonVisibility, onNavigate]);
+ }, [reportActions, parentReportActions, compareImage, report.parentReportActionID, attachments, setDownloadButtonVisibility, onNavigate, accountID, report.reportID, type]);
// Scroll position is affected when window width is resized, so we readjust it on width changes
useEffect(() => {
diff --git a/src/components/Attachments/AttachmentCarousel/types.ts b/src/components/Attachments/AttachmentCarousel/types.ts
index 8ba3489a5fcf..d31ebbd328cd 100644
--- a/src/components/Attachments/AttachmentCarousel/types.ts
+++ b/src/components/Attachments/AttachmentCarousel/types.ts
@@ -1,6 +1,8 @@
import type {ViewToken} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
+import type {ValueOf} from 'type-fest';
import type {Attachment, AttachmentSource} from '@components/Attachments/types';
+import type CONST from '@src/CONST';
import type {Report, ReportActions} from '@src/types/onyx';
type UpdatePageProps = {
@@ -28,6 +30,12 @@ type AttachmentCarouselProps = AttachmentCaraouselOnyxProps & {
/** The report currently being looked at */
report: Report;
+ /** The type of the attachment */
+ type?: ValueOf;
+
+ /** If the attachment originates from a note, the accountID will represent the author of that note. */
+ accountID?: number;
+
/** A callback that is called when swipe-down-to-close gesture happens */
onClose: () => void;
};
diff --git a/src/components/ConnectToQuickbooksOnlineButton/index.native.tsx b/src/components/ConnectToQuickbooksOnlineButton/index.native.tsx
index 7e1d81cc4071..1776a0401403 100644
--- a/src/components/ConnectToQuickbooksOnlineButton/index.native.tsx
+++ b/src/components/ConnectToQuickbooksOnlineButton/index.native.tsx
@@ -12,7 +12,7 @@ import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useThemeStyles from '@hooks/useThemeStyles';
import {removePolicyConnection} from '@libs/actions/connections';
-import {getQuickBooksOnlineSetupLink} from '@libs/actions/connections/QuickBooksOnline';
+import getQuickBooksOnlineSetupLink from '@libs/actions/connections/QuickBooksOnline';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Session} from '@src/types/onyx';
diff --git a/src/components/ConnectToQuickbooksOnlineButton/index.tsx b/src/components/ConnectToQuickbooksOnlineButton/index.tsx
index 37fea2b957a2..10c358ad79c0 100644
--- a/src/components/ConnectToQuickbooksOnlineButton/index.tsx
+++ b/src/components/ConnectToQuickbooksOnlineButton/index.tsx
@@ -6,8 +6,9 @@ import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useThemeStyles from '@hooks/useThemeStyles';
import {removePolicyConnection} from '@libs/actions/connections';
-import {getQuickBooksOnlineSetupLink} from '@libs/actions/connections/QuickBooksOnline';
+import getQuickBooksOnlineSetupLink from '@libs/actions/connections/QuickBooksOnline';
import * as Link from '@userActions/Link';
+import * as PolicyAction from '@userActions/Policy/Policy';
import CONST from '@src/CONST';
import type {ConnectToQuickbooksOnlineButtonProps} from './types';
@@ -27,6 +28,8 @@ function ConnectToQuickbooksOnlineButton({policyID, shouldDisconnectIntegrationB
setIsDisconnectModalOpen(true);
return;
}
+ // Since QBO doesn't support Taxes, we should disable them from the LHN when connecting to QBO
+ PolicyAction.enablePolicyTaxes(policyID, false);
Link.openLink(getQuickBooksOnlineSetupLink(policyID), environmentURL);
}}
isDisabled={isOffline}
diff --git a/src/components/ConnectionLayout.tsx b/src/components/ConnectionLayout.tsx
index 8abe0e5759fc..bcb2a0833086 100644
--- a/src/components/ConnectionLayout.tsx
+++ b/src/components/ConnectionLayout.tsx
@@ -1,13 +1,15 @@
+import {isEmpty} from 'lodash';
import React, {useMemo} from 'react';
import type {StyleProp, TextStyle, ViewStyle} from 'react-native';
import {View} from 'react-native';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
-import type {PolicyAccessVariant} from '@pages/workspace/AccessOrNotFoundWrapper';
+import * as PolicyUtils from '@libs/PolicyUtils';
+import type {AccessVariant} from '@pages/workspace/AccessOrNotFoundWrapper';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import type {TranslationPaths} from '@src/languages/types';
-import type {PolicyFeatureName} from '@src/types/onyx/Policy';
+import type {ConnectionName, PolicyFeatureName} from '@src/types/onyx/Policy';
import HeaderWithBackButton from './HeaderWithBackButton';
import ScreenWrapper from './ScreenWrapper';
import ScrollView from './ScrollView';
@@ -17,8 +19,8 @@ type ConnectionLayoutProps = {
/** Used to set the testID for tests */
displayName: string;
- /** Header title for the connection */
- headerTitle: TranslationPaths;
+ /** Header title to be translated for the connection component */
+ headerTitle?: TranslationPaths;
/** The subtitle to show in the header */
headerSubtitle?: string;
@@ -26,14 +28,14 @@ type ConnectionLayoutProps = {
/** React nodes that will be shown */
children?: React.ReactNode;
- /** Title of the connection component */
+ /** Title to be translated for the connection component */
title?: TranslationPaths;
/** The current policyID */
policyID: string;
/** Defines which types of access should be verified */
- accessVariants?: PolicyAccessVariant[];
+ accessVariants?: AccessVariant[];
/** The current feature name that the user tries to get access to */
featureName?: PolicyFeatureName;
@@ -44,18 +46,30 @@ type ConnectionLayoutProps = {
/** Style of the title text */
titleStyle?: StyleProp | undefined;
+ /** Whether to include safe area padding bottom or not */
+ shouldIncludeSafeAreaPaddingBottom?: boolean;
+
/** Whether to use ScrollView or not */
shouldUseScrollView?: boolean;
+
+ /** Used for dynamic header title translation with parameters */
+ headerTitleAlreadyTranslated?: string;
+
+ /** Used for dynamic title translation with parameters */
+ titleAlreadyTranslated?: string;
+
+ /** Name of the current connection */
+ connectionName: ConnectionName;
};
-type ConnectionLayoutContentProps = Pick;
+type ConnectionLayoutContentProps = Pick;
-function ConnectionLayoutContent({title, titleStyle, children}: ConnectionLayoutContentProps) {
+function ConnectionLayoutContent({title, titleStyle, children, titleAlreadyTranslated}: ConnectionLayoutContentProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
return (
<>
- {title && {translate(title)}}
+ {title && {titleAlreadyTranslated ?? translate(title)}}
{children}
>
);
@@ -72,20 +86,28 @@ function ConnectionLayout({
featureName,
contentContainerStyle,
titleStyle,
+ shouldIncludeSafeAreaPaddingBottom,
+ connectionName,
shouldUseScrollView = true,
+ headerTitleAlreadyTranslated,
+ titleAlreadyTranslated,
}: ConnectionLayoutProps) {
const {translate} = useLocalize();
+ const policy = PolicyUtils.getPolicy(policyID ?? '');
+ const isConnectionEmpty = isEmpty(policy.connections?.[connectionName]);
+
const renderSelectionContent = useMemo(
() => (
{children}
),
- [title, titleStyle, children],
+ [title, titleStyle, children, titleAlreadyTranslated],
);
return (
@@ -93,14 +115,15 @@ function ConnectionLayout({
policyID={policyID}
accessVariants={accessVariants}
featureName={featureName}
+ shouldBeBlocked={isConnectionEmpty}
>
Navigation.goBack()}
/>
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx
index 79eaa30ee922..221731bbeef6 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx
@@ -2,6 +2,7 @@ import React, {memo} from 'react';
import {withOnyx} from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
import type {CustomRendererProps, TBlock} from 'react-native-render-html';
+import {AttachmentContext} from '@components/AttachmentContext';
import * as Expensicons from '@components/Icon/Expensicons';
import PressableWithoutFocus from '@components/Pressable/PressableWithoutFocus';
import {ShowContextMenuContext, showContextMenuForReport} from '@components/ShowContextMenuContext';
@@ -78,19 +79,29 @@ function ImageRenderer({tnode}: ImageRendererProps) {
) : (
{({anchor, report, action, checkIfContextMenuActive}) => (
- {
- const route = ROUTES.REPORT_ATTACHMENTS.getRoute(report?.reportID ?? '', source);
- Navigation.navigate(route);
- }}
- onLongPress={(event) => showContextMenuForReport(event, anchor, report?.reportID ?? '', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))}
- shouldUseHapticsOnLongPress
- accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON}
- accessibilityLabel={translate('accessibilityHints.viewAttachment')}
- >
- {thumbnailImageComponent}
-
+
+ {({reportID, accountID, type}) => (
+ {
+ if (!source || !type) {
+ return;
+ }
+
+ if (reportID) {
+ const route = ROUTES.ATTACHMENTS?.getRoute(reportID, type, source, accountID);
+ Navigation.navigate(route);
+ }
+ }}
+ onLongPress={(event) => showContextMenuForReport(event, anchor, report?.reportID ?? '', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))}
+ shouldUseHapticsOnLongPress
+ accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON}
+ accessibilityLabel={translate('accessibilityHints.viewAttachment')}
+ >
+ {thumbnailImageComponent}
+
+ )}
+
)}
);
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VideoRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VideoRenderer.tsx
index 095dcb294857..52d14df46471 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VideoRenderer.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VideoRenderer.tsx
@@ -35,7 +35,7 @@ function VideoRenderer({tnode, key}: VideoRendererProps) {
videoDimensions={{width, height}}
videoDuration={duration}
onShowModalPress={() => {
- const route = ROUTES.REPORT_ATTACHMENTS.getRoute(report?.reportID ?? '', sourceURL);
+ const route = ROUTES.ATTACHMENTS.getRoute(report?.reportID ?? '', CONST.ATTACHMENT_TYPE.REPORT, sourceURL);
Navigation.navigate(route);
}}
/>
diff --git a/src/components/InitialURLContextProvider.tsx b/src/components/InitialURLContextProvider.tsx
index 710f045ede4e..a3df93844ca9 100644
--- a/src/components/InitialURLContextProvider.tsx
+++ b/src/components/InitialURLContextProvider.tsx
@@ -1,5 +1,6 @@
-import React, {createContext} from 'react';
+import React, {createContext, useEffect, useState} from 'react';
import type {ReactNode} from 'react';
+import {Linking} from 'react-native';
import type {Route} from '@src/ROUTES';
/** Initial url that will be opened when NewDot is embedded into Hybrid App. */
@@ -14,7 +15,16 @@ type InitialURLContextProviderProps = {
};
function InitialURLContextProvider({children, url}: InitialURLContextProviderProps) {
- return {children};
+ const [initialURL, setInitialURL] = useState(url);
+ useEffect(() => {
+ if (initialURL) {
+ return;
+ }
+ Linking.getInitialURL().then((initURL) => {
+ setInitialURL(initURL as Route);
+ });
+ }, [initialURL]);
+ return {children};
}
InitialURLContextProvider.displayName = 'InitialURLContextProvider';
diff --git a/src/components/MapView/MapView.tsx b/src/components/MapView/MapView.tsx
index 5174db6fd32f..2e304bb0214b 100644
--- a/src/components/MapView/MapView.tsx
+++ b/src/components/MapView/MapView.tsx
@@ -1,7 +1,7 @@
import {useFocusEffect, useNavigation} from '@react-navigation/native';
import type {MapState} from '@rnmapbox/maps';
import Mapbox, {MarkerView, setAccessToken} from '@rnmapbox/maps';
-import {forwardRef, memo, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react';
+import {forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import useThemeStyles from '@hooks/useThemeStyles';
@@ -30,7 +30,8 @@ const MapView = forwardRef(
const cameraRef = useRef(null);
const [isIdle, setIsIdle] = useState(false);
- const [currentPosition, setCurrentPosition] = useState(cachedUserLocation);
+ const initialLocation = useMemo(() => initialState && {longitude: initialState.location[0], latitude: initialState.location[1]}, [initialState]);
+ const [currentPosition, setCurrentPosition] = useState(cachedUserLocation ?? initialLocation);
const [userInteractedWithMap, setUserInteractedWithMap] = useState(false);
const shouldInitializeCurrentPosition = useRef(true);
@@ -42,13 +43,13 @@ const MapView = forwardRef(
const setCurrentPositionToInitialState: GeolocationErrorCallback = useCallback(
(error) => {
- if (error?.code !== GeolocationErrorCode.PERMISSION_DENIED || !initialState) {
+ if (error?.code !== GeolocationErrorCode.PERMISSION_DENIED || !initialLocation) {
return;
}
UserLocation.clearUserLocation();
- setCurrentPosition({longitude: initialState.location[0], latitude: initialState.location[1]});
+ setCurrentPosition(initialLocation);
},
- [initialState],
+ [initialLocation],
);
useFocusEffect(
diff --git a/src/components/MapView/MapView.website.tsx b/src/components/MapView/MapView.website.tsx
index e75d47305d90..f59463442ac8 100644
--- a/src/components/MapView/MapView.website.tsx
+++ b/src/components/MapView/MapView.website.tsx
@@ -5,7 +5,7 @@
import {useFocusEffect} from '@react-navigation/native';
import mapboxgl from 'mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';
-import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react';
+import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
import type {MapRef} from 'react-map-gl';
import Map, {Marker} from 'react-map-gl';
import {View} from 'react-native';
@@ -52,7 +52,8 @@ const MapView = forwardRef(
const StyleUtils = useStyleUtils();
const [mapRef, setMapRef] = useState(null);
- const [currentPosition, setCurrentPosition] = useState(cachedUserLocation);
+ const initialLocation = useMemo(() => ({longitude: initialState.location[0], latitude: initialState.location[1]}), [initialState]);
+ const [currentPosition, setCurrentPosition] = useState(cachedUserLocation ?? initialLocation);
const [userInteractedWithMap, setUserInteractedWithMap] = useState(false);
const [shouldResetBoundaries, setShouldResetBoundaries] = useState(false);
const setRef = useCallback((newRef: MapRef | null) => setMapRef(newRef), []);
@@ -66,13 +67,13 @@ const MapView = forwardRef(
const setCurrentPositionToInitialState: GeolocationErrorCallback = useCallback(
(error) => {
- if (error?.code !== GeolocationErrorCode.PERMISSION_DENIED || !initialState) {
+ if (error?.code !== GeolocationErrorCode.PERMISSION_DENIED || !initialLocation) {
return;
}
UserLocation.clearUserLocation();
- setCurrentPosition({longitude: initialState.location[0], latitude: initialState.location[1]});
+ setCurrentPosition(initialLocation);
},
- [initialState],
+ [initialLocation],
);
useFocusEffect(
diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx
index 6a92f483b396..7f9a729c161a 100644
--- a/src/components/MenuItem.tsx
+++ b/src/components/MenuItem.tsx
@@ -156,9 +156,15 @@ type MenuItemBaseProps = {
/** Error to display at the bottom of the component */
errorText?: MaybePhraseKey;
+ /** Any additional styles to pass to error text. */
+ errorTextStyle?: StyleProp;
+
/** Hint to display at the bottom of the component */
hintText?: MaybePhraseKey;
+ /** Should the error text red dot indicator be shown */
+ shouldShowRedDotIndicator?: boolean;
+
/** A boolean flag that gives the icon a green fill if true */
success?: boolean;
@@ -318,6 +324,8 @@ function MenuItem(
helperText,
helperTextStyle,
errorText,
+ errorTextStyle,
+ shouldShowRedDotIndicator,
hintText,
success = false,
focused = false,
@@ -707,9 +715,9 @@ function MenuItem(
{!!errorText && (
)}
{!!hintText && (
diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx
index bcf80c058af4..f701f11c25b4 100644
--- a/src/components/SelectionList/BaseSelectionList.tsx
+++ b/src/components/SelectionList/BaseSelectionList.tsx
@@ -426,8 +426,7 @@ function BaseSelectionList(
shouldPreventDefaultFocusOnSelectRow={shouldPreventDefaultFocusOnSelectRow}
// We're already handling the Enter key press in the useKeyboardShortcut hook, so we don't want the list item to submit the form
shouldPreventEnterKeySubmit
- // Change this because of lint
- rightHandSideComponent={rightHandSideComponent && (typeof rightHandSideComponent === 'function' ? rightHandSideComponent({} as TItem) : rightHandSideComponent)}
+ rightHandSideComponent={rightHandSideComponent}
keyForList={item.keyForList ?? ''}
isMultilineSupported={isRowMultilineSupported}
onFocus={() => {
diff --git a/src/components/SelectionScreen.tsx b/src/components/SelectionScreen.tsx
index a2ab477accef..c8c290b562b6 100644
--- a/src/components/SelectionScreen.tsx
+++ b/src/components/SelectionScreen.tsx
@@ -1,9 +1,11 @@
+import {isEmpty} from 'lodash';
import React from 'react';
import useLocalize from '@hooks/useLocalize';
-import type {PolicyAccessVariant} from '@pages/workspace/AccessOrNotFoundWrapper';
+import * as PolicyUtils from '@libs/PolicyUtils';
+import type {AccessVariant} from '@pages/workspace/AccessOrNotFoundWrapper';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import type {TranslationPaths} from '@src/languages/types';
-import type {PolicyFeatureName} from '@src/types/onyx/Policy';
+import type {ConnectionName, PolicyFeatureName} from '@src/types/onyx/Policy';
import HeaderWithBackButton from './HeaderWithBackButton';
import ScreenWrapper from './ScreenWrapper';
import SelectionList from './SelectionList';
@@ -45,13 +47,16 @@ type SelectionScreenProps = {
policyID: string;
/** Defines which types of access should be verified */
- accessVariants?: PolicyAccessVariant[];
+ accessVariants?: AccessVariant[];
/** The current feature name that the user tries to get access to */
featureName?: PolicyFeatureName;
/** Whether or not to block user from accessing the page */
shouldBeBlocked?: boolean;
+
+ /** Name of the current connection */
+ connectionName: ConnectionName;
};
function SelectionScreen({
@@ -67,14 +72,19 @@ function SelectionScreen({
accessVariants,
featureName,
shouldBeBlocked,
+ connectionName,
}: SelectionScreenProps) {
const {translate} = useLocalize();
+
+ const policy = PolicyUtils.getPolicy(policyID ?? '');
+ const isConnectionEmpty = isEmpty(policy.connections?.[connectionName]);
+
return (
- {disabled && (
+ {(Boolean(disabled) || Boolean(showLockIcon)) && (
;
+
+ /** Whether or not logs should be stored */
+ shouldStoreLogs: OnyxEntry;
};
type TestToolsModalProps = TestToolsModalOnyxProps;
-function TestToolsModal({isTestToolsModalOpen = false}: TestToolsModalProps) {
+function TestToolsModal({isTestToolsModalOpen = false, shouldStoreLogs = false}: TestToolsModalProps) {
const {isDevelopment} = useEnvironment();
const {windowWidth} = useWindowDimensions();
const StyleUtils = useStyleUtils();
@@ -46,6 +53,18 @@ function TestToolsModal({isTestToolsModalOpen = false}: TestToolsModalProps) {
+ {!!shouldStoreLogs && (
+
+
+ )}
);
@@ -57,4 +76,7 @@ export default withOnyx({
isTestToolsModalOpen: {
key: ONYXKEYS.IS_TEST_TOOLS_MODAL_OPEN,
},
+ shouldStoreLogs: {
+ key: ONYXKEYS.SHOULD_STORE_LOGS,
+ },
})(TestToolsModal);
diff --git a/src/languages/en.ts b/src/languages/en.ts
index e79bddc4deec..75574961c1e8 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -702,7 +702,7 @@ export default {
settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) =>
`${submitterDisplayName} added a bank account. The ${amount} payment has been made.`,
paidElsewhereWithAmount: ({payer, amount}: PaidElsewhereWithAmountParams) => `${payer ? `${payer} ` : ''}paid ${amount} elsewhere`,
- paidWithExpensifyWithAmount: ({payer, amount}: PaidWithExpensifyWithAmountParams) => `${payer ? `${payer} ` : ''}paid ${amount} using Expensify`,
+ paidWithExpensifyWithAmount: ({payer, amount}: PaidWithExpensifyWithAmountParams) => `${payer ? `${payer} ` : ''}paid ${amount} with Expensify`,
noReimbursableExpenses: 'This report has an invalid amount',
pendingConversionMessage: "Total will update when you're back online",
changedTheExpense: 'changed the expense',
@@ -947,6 +947,7 @@ export default {
noLogsAvailable: 'No logs available',
logSizeTooLarge: ({size}: LogSizeParams) => `Log size exceeds the limit of ${size} MB. Please use "Save log" to download the log file instead.`,
logs: 'Logs',
+ viewConsole: 'View console',
},
security: 'Security',
signOut: 'Sign out',
@@ -2052,10 +2053,8 @@ export default {
accountsSwitchDescription: 'Enabled categories are available for members to select when creating their expenses.',
trackingCategories: 'Tracking categories',
trackingCategoriesDescription: 'Choose whether to import tracking categories and see where they are displayed.',
- mapXeroCostCentersTo: 'Map Xero cost centers to',
- mapXeroRegionsTo: 'Map Xero regions to',
- mapXeroCostCentersToDescription: 'Choose where to map cost centers to when exporting to Xero.',
- mapXeroRegionsToDescription: 'Choose where to map employee regions when exporting expense reports to Xero.',
+ mapTrackingCategoryTo: ({categoryName}) => `Map Xero ${categoryName} to`,
+ mapTrackingCategoryToDescription: ({categoryName}) => `Choose where to map ${categoryName} to when exporting to Xero.`,
customers: 'Re-bill customers',
customersDescription: 'Import customer contacts. Billable expenses need tags for export. Expenses will carry the customer information to Xero for sales invoices.',
taxesDescription: 'Choose whether to import tax rates and tax defaults from your accounting integration.',
@@ -2112,10 +2111,12 @@ export default {
},
},
invoiceStatus: {
+ label: 'Purchase bill status',
+ description: 'When exported to Xero what state should purchase bills have.',
values: {
- [CONST.XERO_CONFIG.INVOICE_STATUS.AWAITING_PAYMENT]: 'Authorised',
[CONST.XERO_CONFIG.INVOICE_STATUS.DRAFT]: 'Draft',
- [CONST.XERO_CONFIG.INVOICE_STATUS.AWAITING_APPROVAL]: 'Submitted',
+ [CONST.XERO_CONFIG.INVOICE_STATUS.AWAITING_APPROVAL]: 'Awaiting approval',
+ [CONST.XERO_CONFIG.INVOICE_STATUS.AWAITING_PAYMENT]: 'Awaiting payment',
},
},
exportPreferredExporterNote: 'This can be any workspace admin, but must be a domain admin if you set different export accounts for individual company cards in domain settings.',
@@ -2194,6 +2195,12 @@ export default {
title: 'Accounting',
subtitle: 'Sync your chart of accounts and more.',
},
+ connectionsWarningModal: {
+ featureEnabledTitle: 'Not so fast...',
+ featureEnabledText: 'To enable or disable this feature change your accounting import settings.',
+ disconnectText: 'Disconnect your accounting connection from the workspace if you want to disable Accounting.',
+ manageSettings: 'Manage settings',
+ },
},
reportFields: {
delete: 'Delete field',
@@ -2249,6 +2256,7 @@ export default {
enable: 'Enable rate',
enableMultiple: 'Enable rates',
},
+ importedFromAccountingSoftware: 'The taxes below are imported from your',
},
emptyWorkspace: {
title: 'Create a workspace',
@@ -2349,6 +2357,17 @@ export default {
}
}
},
+ syncError: (integration?: ConnectionName): string => {
+ switch (integration) {
+ case CONST.POLICY.CONNECTIONS.NAME.QBO:
+ return "Couldn't connect to QuickBooks Online.";
+ case CONST.POLICY.CONNECTIONS.NAME.XERO:
+ return "Couldn't connect to Xero.";
+ default: {
+ return "Couldn't connect to integration.";
+ }
+ }
+ },
accounts: 'Chart of accounts',
taxes: 'Taxes',
imported: 'Imported',
@@ -2405,7 +2424,9 @@ export default {
return 'Checking QuickBooks Online connection';
case 'quickbooksOnlineImportMain':
return 'Importing your QuickBooks Online data';
- case 'startingImport':
+ case 'startingImportXero':
+ return 'Importing your Xero data';
+ case 'startingImportQBO':
return 'Importing your QuickBooks Online data';
case 'quickbooksOnlineSyncTitle':
return 'Synchronizing QuickBooks Online data';
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 374d9145acfe..39c6b6dc32b0 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -942,6 +942,7 @@ export default {
noLogsAvailable: 'No hay registros disponibles',
logSizeTooLarge: ({size}: LogSizeParams) => `El tamaño del registro excede el lÃmite de ${size} MB. Utilice "Guardar registro" para descargar el archivo de registro.`,
logs: 'Logs',
+ viewConsole: 'Ver consola',
},
security: 'Seguridad',
restoreStashed: 'Restablecer login guardado',
@@ -2084,10 +2085,8 @@ export default {
accountsSwitchDescription: 'Las categorÃas activas estarán disponibles para ser escogidas cuando se crea un gasto.',
trackingCategories: 'CategorÃas de seguimiento',
trackingCategoriesDescription: 'Elige si deseas importar categorÃas de seguimiento y ver dónde se muestran.',
- mapXeroCostCentersTo: 'Asignar centros de coste de Xero a',
- mapXeroRegionsTo: 'Asignar regiones de Xero a',
- mapXeroCostCentersToDescription: 'Elige dónde mapear los centros de coste al exportar a Xero.',
- mapXeroRegionsToDescription: 'Elige dónde asignar las regiones de los empleados al exportar informes de gastos a Xero.',
+ mapTrackingCategoryTo: ({categoryName}) => `Asignar ${categoryName} de Xero a`,
+ mapTrackingCategoryToDescription: ({categoryName}) => `Elige dónde mapear ${categoryName} al exportar a Xero.`,
customers: 'Volver a facturar a los clientes',
customersDescription:
'Importar contactos de clientes. Los gastos facturables necesitan etiquetas para la exportación. Los gastos llevarán la información del cliente a Xero para las facturas de ventas.',
@@ -2147,10 +2146,12 @@ export default {
},
},
invoiceStatus: {
+ label: 'Estado de la factura de compra',
+ description: 'Qué estado deben tener las facturas de compra cuando se exportan a Xero.',
values: {
- [CONST.XERO_CONFIG.INVOICE_STATUS.AWAITING_PAYMENT]: 'Autorizado',
[CONST.XERO_CONFIG.INVOICE_STATUS.DRAFT]: 'Borrador',
- [CONST.XERO_CONFIG.INVOICE_STATUS.AWAITING_APPROVAL]: 'Enviado',
+ [CONST.XERO_CONFIG.INVOICE_STATUS.AWAITING_APPROVAL]: 'Pendiente de aprobación',
+ [CONST.XERO_CONFIG.INVOICE_STATUS.AWAITING_PAYMENT]: 'Pendiente de pago',
},
},
exportPreferredExporterNote:
@@ -2230,6 +2231,12 @@ export default {
title: 'Contabilidad',
subtitle: 'Sincroniza tu plan de cuentas y otras opciones.',
},
+ connectionsWarningModal: {
+ featureEnabledTitle: 'No tan rápido...',
+ featureEnabledText: 'Para activar o desactivar esta función, cambia la configuración de importación contable.',
+ disconnectText: 'Desconecta tu conexión contable del espacio de trabajo si deseas desactivar la Contabilidad.',
+ manageSettings: 'Gestionar la configuración',
+ },
},
reportFields: {
delete: 'Eliminar campos',
@@ -2285,6 +2292,7 @@ export default {
enable: 'Activar tasa',
enableMultiple: 'Activar tasas',
},
+ importedFromAccountingSoftware: 'Impuestos importadas desde',
},
emptyWorkspace: {
title: 'Crea un espacio de trabajo',
@@ -2353,6 +2361,17 @@ export default {
}
}
},
+ syncError: (integration?: ConnectionName): string => {
+ switch (integration) {
+ case CONST.POLICY.CONNECTIONS.NAME.QBO:
+ return 'No se puede conectar a QuickBooks Online.';
+ case CONST.POLICY.CONNECTIONS.NAME.XERO:
+ return 'No se puede conectar a Xero';
+ default: {
+ return 'No se ha podido conectar a la integración.';
+ }
+ }
+ },
accounts: 'Plan de cuentas',
taxes: 'Impuestos',
imported: 'Importado',
@@ -2409,7 +2428,9 @@ export default {
return 'Revisando conexión a QuickBooks Online';
case 'quickbooksOnlineImportMain':
return 'Importando datos desde QuickBooks Online';
- case 'startingImport':
+ case 'startingImportXero':
+ return 'Importando datos desde Xero';
+ case 'startingImportQBO':
return 'Importando datos desde QuickBooks Online';
case 'quickbooksOnlineSyncTitle':
return 'Sincronizando datos desde QuickBooks Online';
diff --git a/src/libs/API/parameters/SetPolicyTagsEnabled.ts b/src/libs/API/parameters/SetPolicyTagsEnabled.ts
index 86720b84bf8b..67928a3227e3 100644
--- a/src/libs/API/parameters/SetPolicyTagsEnabled.ts
+++ b/src/libs/API/parameters/SetPolicyTagsEnabled.ts
@@ -5,6 +5,11 @@ type SetPolicyTagsEnabled = {
* Array<{name: string; enabled: boolean}>
*/
tags: string;
+ /**
+ * When the tags are imported as multi level tags, the index of the top
+ * most tag list item
+ */
+ tagListIndex: number;
};
export default SetPolicyTagsEnabled;
diff --git a/src/libs/API/parameters/SyncPolicyToXeroParams.ts b/src/libs/API/parameters/SyncPolicyToXeroParams.ts
new file mode 100644
index 000000000000..0932f860c29e
--- /dev/null
+++ b/src/libs/API/parameters/SyncPolicyToXeroParams.ts
@@ -0,0 +1,6 @@
+type SyncPolicyToXeroParams = {
+ policyID: string;
+ idempotencyKey: string;
+};
+
+export default SyncPolicyToXeroParams;
diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts
index 52f130a28dd1..c9e2e342c5ae 100644
--- a/src/libs/API/parameters/index.ts
+++ b/src/libs/API/parameters/index.ts
@@ -13,6 +13,7 @@ export type {default as CloseAccountParams} from './CloseAccountParams';
export type {default as ConnectBankAccountParams} from './ConnectBankAccountParams';
export type {default as ConnectPolicyToAccountingIntegrationParams} from './ConnectPolicyToAccountingIntegrationParams';
export type {default as SyncPolicyToQuickbooksOnlineParams} from './SyncPolicyToQuickbooksOnlineParams';
+export type {default as SyncPolicyToXeroParams} from './SyncPolicyToXeroParams';
export type {default as DeleteContactMethodParams} from './DeleteContactMethodParams';
export type {default as DeletePaymentBankAccountParams} from './DeletePaymentBankAccountParams';
export type {default as DeletePaymentCardParams} from './DeletePaymentCardParams';
diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts
index 08bc5eddd087..40deec85bc47 100644
--- a/src/libs/API/types.ts
+++ b/src/libs/API/types.ts
@@ -442,6 +442,7 @@ const READ_COMMANDS = {
CONNECT_POLICY_TO_QUICKBOOKS_ONLINE: 'ConnectPolicyToQuickbooksOnline',
CONNECT_POLICY_TO_XERO: 'ConnectPolicyToXero',
SYNC_POLICY_TO_QUICKBOOKS_ONLINE: 'SyncPolicyToQuickbooksOnline',
+ SYNC_POLICY_TO_XERO: 'SyncPolicyToXero',
OPEN_REIMBURSEMENT_ACCOUNT_PAGE: 'OpenReimbursementAccountPage',
OPEN_WORKSPACE_VIEW: 'OpenWorkspaceView',
GET_MAPBOX_ACCESS_TOKEN: 'GetMapboxAccessToken',
@@ -488,6 +489,7 @@ type ReadCommandParameters = {
[READ_COMMANDS.CONNECT_POLICY_TO_QUICKBOOKS_ONLINE]: Parameters.ConnectPolicyToAccountingIntegrationParams;
[READ_COMMANDS.CONNECT_POLICY_TO_XERO]: Parameters.ConnectPolicyToAccountingIntegrationParams;
[READ_COMMANDS.SYNC_POLICY_TO_QUICKBOOKS_ONLINE]: Parameters.SyncPolicyToQuickbooksOnlineParams;
+ [READ_COMMANDS.SYNC_POLICY_TO_XERO]: Parameters.SyncPolicyToXeroParams;
[READ_COMMANDS.OPEN_REIMBURSEMENT_ACCOUNT_PAGE]: Parameters.OpenReimbursementAccountPageParams;
[READ_COMMANDS.OPEN_WORKSPACE_VIEW]: Parameters.OpenWorkspaceViewParams;
[READ_COMMANDS.GET_MAPBOX_ACCESS_TOKEN]: EmptyObject;
diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx
index ad437f08523c..394211ce7a9c 100644
--- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx
+++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx
@@ -307,7 +307,7 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie
getComponent={loadConciergePage}
/>
require('../../../../pages/workspace/accounting/xero/XeroTaxesConfigurationPage').default as React.ComponentType,
[SCREENS.WORKSPACE.ACCOUNTING.XERO_TRACKING_CATEGORIES]: () =>
require('../../../../pages/workspace/accounting/xero/XeroTrackingCategoryConfigurationPage').default as React.ComponentType,
- [SCREENS.WORKSPACE.ACCOUNTING.XERO_MAP_COST_CENTERS]: () => require('../../../../pages/workspace/accounting/xero/XeroMapCostCentersToConfigurationPage').default as React.ComponentType,
- [SCREENS.WORKSPACE.ACCOUNTING.XERO_MAP_REGION]: () => require('../../../../pages/workspace/accounting/xero/XeroMapRegionsToConfigurationPage').default as React.ComponentType,
+ [SCREENS.WORKSPACE.ACCOUNTING.XERO_MAP_TRACKING_CATEGORY]: () =>
+ require('../../../../pages/workspace/accounting/xero/XeroMapTrackingCategoryConfigurationPage').default as React.ComponentType,
[SCREENS.WORKSPACE.ACCOUNTING.XERO_EXPORT]: () => require('../../../../pages/workspace/accounting/xero/export/XeroExportConfigurationPage').default as React.ComponentType,
[SCREENS.WORKSPACE.ACCOUNTING.XERO_EXPORT_PURCHASE_BILL_DATE_SELECT]: () =>
require('../../../../pages/workspace/accounting/xero/export/XeroPurchaseBillDateSelectPage').default as React.ComponentType,
[SCREENS.WORKSPACE.ACCOUNTING.XERO_EXPORT_BANK_ACCOUNT_SELECT]: () =>
require('../../../../pages/workspace/accounting/xero/export/XeroBankAccountSelectPage').default as React.ComponentType,
[SCREENS.WORKSPACE.ACCOUNTING.XERO_ADVANCED]: () => require('../../../../pages/workspace/accounting/xero/advanced/XeroAdvancedPage').default as React.ComponentType,
+ [SCREENS.WORKSPACE.ACCOUNTING.XERO_BILL_STATUS_SELECTOR]: () =>
+ require('../../../../pages/workspace/accounting/xero/export/XeroPurchaseBillStatusSelectorPage').default as React.ComponentType,
[SCREENS.WORKSPACE.ACCOUNTING.XERO_INVOICE_ACCOUNT_SELECTOR]: () =>
require('../../../../pages/workspace/accounting/xero/advanced/XeroInvoiceAccountSelectorPage').default as React.ComponentType,
[SCREENS.WORKSPACE.ACCOUNTING.XERO_EXPORT_PREFERRED_EXPORTER_SELECT]: () =>
diff --git a/src/libs/Navigation/dismissModal.ts b/src/libs/Navigation/dismissModal.ts
index 7e88901e8b0b..dd0e512ea33d 100644
--- a/src/libs/Navigation/dismissModal.ts
+++ b/src/libs/Navigation/dismissModal.ts
@@ -24,7 +24,7 @@ function dismissModal(navigationRef: NavigationContainerRef)
case NAVIGATORS.ONBOARDING_MODAL_NAVIGATOR:
case NAVIGATORS.FEATURE_TRANING_MODAL_NAVIGATOR:
case SCREENS.NOT_FOUND:
- case SCREENS.REPORT_ATTACHMENTS:
+ case SCREENS.ATTACHMENTS:
case SCREENS.TRANSACTION_RECEIPT:
case SCREENS.PROFILE_AVATAR:
case SCREENS.WORKSPACE_AVATAR:
diff --git a/src/libs/Navigation/dismissModalWithReport.ts b/src/libs/Navigation/dismissModalWithReport.ts
index c0405c2c9da0..fe6fd288a2f9 100644
--- a/src/libs/Navigation/dismissModalWithReport.ts
+++ b/src/libs/Navigation/dismissModalWithReport.ts
@@ -37,7 +37,7 @@ function dismissModalWithReport(targetReport: Report | EmptyObject, navigationRe
case NAVIGATORS.LEFT_MODAL_NAVIGATOR:
case NAVIGATORS.RIGHT_MODAL_NAVIGATOR:
case SCREENS.NOT_FOUND:
- case SCREENS.REPORT_ATTACHMENTS:
+ case SCREENS.ATTACHMENTS:
case SCREENS.TRANSACTION_RECEIPT:
case SCREENS.PROFILE_AVATAR:
case SCREENS.WORKSPACE_AVATAR:
diff --git a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts
index b1dd11849833..2aaceb96f52a 100755
--- a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts
+++ b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts
@@ -45,11 +45,11 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial> = {
SCREENS.WORKSPACE.ACCOUNTING.XERO_CUSTOMER,
SCREENS.WORKSPACE.ACCOUNTING.XERO_TAXES,
SCREENS.WORKSPACE.ACCOUNTING.XERO_TRACKING_CATEGORIES,
- SCREENS.WORKSPACE.ACCOUNTING.XERO_MAP_COST_CENTERS,
- SCREENS.WORKSPACE.ACCOUNTING.XERO_MAP_REGION,
+ SCREENS.WORKSPACE.ACCOUNTING.XERO_MAP_TRACKING_CATEGORY,
SCREENS.WORKSPACE.ACCOUNTING.XERO_EXPORT,
SCREENS.WORKSPACE.ACCOUNTING.XERO_EXPORT_PURCHASE_BILL_DATE_SELECT,
SCREENS.WORKSPACE.ACCOUNTING.XERO_ADVANCED,
+ SCREENS.WORKSPACE.ACCOUNTING.XERO_BILL_STATUS_SELECTOR,
SCREENS.WORKSPACE.ACCOUNTING.XERO_INVOICE_ACCOUNT_SELECTOR,
SCREENS.WORKSPACE.ACCOUNTING.XERO_EXPORT_PREFERRED_EXPORTER_SELECT,
SCREENS.WORKSPACE.ACCOUNTING.XERO_BILL_PAYMENT_ACCOUNT_SELECTOR,
diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts
index e9b86993ad43..17f07d36367f 100644
--- a/src/libs/Navigation/linkingConfig/config.ts
+++ b/src/libs/Navigation/linkingConfig/config.ts
@@ -19,7 +19,7 @@ const config: LinkingOptions['config'] = {
[SCREENS.SIGN_IN_WITH_GOOGLE_DESKTOP]: ROUTES.GOOGLE_SIGN_IN,
[SCREENS.SAML_SIGN_IN]: ROUTES.SAML_SIGN_IN,
[SCREENS.DESKTOP_SIGN_IN_REDIRECT]: ROUTES.DESKTOP_SIGN_IN_REDIRECT,
- [SCREENS.REPORT_ATTACHMENTS]: ROUTES.REPORT_ATTACHMENTS.route,
+ [SCREENS.ATTACHMENTS]: ROUTES.ATTACHMENTS.route,
[SCREENS.PROFILE_AVATAR]: ROUTES.PROFILE_AVATAR.route,
[SCREENS.WORKSPACE_AVATAR]: ROUTES.WORKSPACE_AVATAR.route,
[SCREENS.REPORT_AVATAR]: ROUTES.REPORT_AVATAR.route,
@@ -225,7 +225,7 @@ const config: LinkingOptions['config'] = {
exact: true,
},
[SCREENS.SETTINGS.CONSOLE]: {
- path: ROUTES.SETTINGS_CONSOLE,
+ path: ROUTES.SETTINGS_CONSOLE.route,
exact: true,
},
[SCREENS.SETTINGS.SHARE_LOG]: ROUTES.SETTINGS_SHARE_LOG.route,
@@ -327,14 +327,14 @@ const config: LinkingOptions['config'] = {
[SCREENS.WORKSPACE.ACCOUNTING.XERO_CHART_OF_ACCOUNTS]: {path: ROUTES.POLICY_ACCOUNTING_XERO_CHART_OF_ACCOUNTS.route},
[SCREENS.WORKSPACE.ACCOUNTING.XERO_ORGANIZATION]: {path: ROUTES.POLICY_ACCOUNTING_XERO_ORGANIZATION.route},
[SCREENS.WORKSPACE.ACCOUNTING.XERO_TRACKING_CATEGORIES]: {path: ROUTES.POLICY_ACCOUNTING_XERO_TRACKING_CATEGORIES.route},
- [SCREENS.WORKSPACE.ACCOUNTING.XERO_MAP_COST_CENTERS]: {path: ROUTES.POLICY_ACCOUNTING_XERO_TRACKING_CATEGORIES_MAP_COST_CENTERS.route},
- [SCREENS.WORKSPACE.ACCOUNTING.XERO_MAP_REGION]: {path: ROUTES.POLICY_ACCOUNTING_XERO_TRACKING_CATEGORIES_MAP_REGION.route},
+ [SCREENS.WORKSPACE.ACCOUNTING.XERO_MAP_TRACKING_CATEGORY]: {path: ROUTES.POLICY_ACCOUNTING_XERO_TRACKING_CATEGORIES_MAP.route},
[SCREENS.WORKSPACE.ACCOUNTING.XERO_CUSTOMER]: {path: ROUTES.POLICY_ACCOUNTING_XERO_CUSTOMER.route},
[SCREENS.WORKSPACE.ACCOUNTING.XERO_TAXES]: {path: ROUTES.POLICY_ACCOUNTING_XERO_TAXES.route},
[SCREENS.WORKSPACE.ACCOUNTING.XERO_EXPORT]: {path: ROUTES.POLICY_ACCOUNTING_XERO_EXPORT.route},
[SCREENS.WORKSPACE.ACCOUNTING.XERO_EXPORT_PURCHASE_BILL_DATE_SELECT]: {path: ROUTES.POLICY_ACCOUNTING_XERO_EXPORT_PURCHASE_BILL_DATE_SELECT.route},
[SCREENS.WORKSPACE.ACCOUNTING.XERO_EXPORT_BANK_ACCOUNT_SELECT]: {path: ROUTES.POLICY_ACCOUNTING_XERO_EXPORT_BANK_ACCOUNT_SELECT.route},
[SCREENS.WORKSPACE.ACCOUNTING.XERO_ADVANCED]: {path: ROUTES.POLICY_ACCOUNTING_XERO_ADVANCED.route},
+ [SCREENS.WORKSPACE.ACCOUNTING.XERO_BILL_STATUS_SELECTOR]: {path: ROUTES.POLICY_ACCOUNTING_XERO_BILL_STATUS_SELECTOR.route},
[SCREENS.WORKSPACE.ACCOUNTING.XERO_INVOICE_ACCOUNT_SELECTOR]: {path: ROUTES.POLICY_ACCOUNTING_XERO_INVOICE_SELECTOR.route},
[SCREENS.WORKSPACE.ACCOUNTING.XERO_EXPORT_PREFERRED_EXPORTER_SELECT]: {path: ROUTES.POLICY_ACCOUNTING_XERO_PREFERRED_EXPORTER_SELECT.route},
[SCREENS.WORKSPACE.ACCOUNTING.XERO_BILL_PAYMENT_ACCOUNT_SELECTOR]: {path: ROUTES.POLICY_ACCOUNTING_XERO_BILL_PAYMENT_ACCOUNT_SELECTOR.route},
@@ -428,12 +428,14 @@ const config: LinkingOptions['config'] = {
[SCREENS.WORKSPACE.TAG_EDIT]: {
path: ROUTES.WORKSPACE_TAG_EDIT.route,
parse: {
+ orderWeight: Number,
tagName: (tagName: string) => decodeURIComponent(tagName),
},
},
[SCREENS.WORKSPACE.TAG_SETTINGS]: {
path: ROUTES.WORKSPACE_TAG_SETTINGS.route,
parse: {
+ orderWeight: Number,
tagName: (tagName: string) => decodeURIComponent(tagName),
},
},
diff --git a/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts b/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts
index 4924465145b8..e6baacc1cea9 100644
--- a/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts
+++ b/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts
@@ -4,6 +4,7 @@ import {isAnonymousUser} from '@libs/actions/Session';
import getIsNarrowLayout from '@libs/getIsNarrowLayout';
import type {BottomTabName, CentralPaneName, FullScreenName, NavigationPartialRoute, RootStackParamList} from '@libs/Navigation/types';
import {extractPolicyIDFromPath, getPathWithoutPolicyID} from '@libs/PolicyUtils';
+import CONST from '@src/CONST';
import NAVIGATORS from '@src/NAVIGATORS';
import SCREENS from '@src/SCREENS';
import CENTRAL_PANE_TO_RHP_MAPPING from './CENTRAL_PANE_TO_RHP_MAPPING';
@@ -162,8 +163,8 @@ function getAdaptedState(state: PartialState
const lhpNavigator = state.routes.find((route) => route.name === NAVIGATORS.LEFT_MODAL_NAVIGATOR);
const onboardingModalNavigator = state.routes.find((route) => route.name === NAVIGATORS.ONBOARDING_MODAL_NAVIGATOR);
const welcomeVideoModalNavigator = state.routes.find((route) => route.name === NAVIGATORS.WELCOME_VIDEO_MODAL_NAVIGATOR);
+ const attachmentsScreen = state.routes.find((route) => route.name === SCREENS.ATTACHMENTS);
const featureTrainingModalNavigator = state.routes.find((route) => route.name === NAVIGATORS.FEATURE_TRANING_MODAL_NAVIGATOR);
- const reportAttachmentsScreen = state.routes.find((route) => route.name === SCREENS.REPORT_ATTACHMENTS);
if (isNarrowLayout) {
metainfo.isFullScreenNavigatorMandatory = false;
@@ -302,25 +303,27 @@ function getAdaptedState(state: PartialState
metainfo,
};
}
- if (reportAttachmentsScreen) {
+ if (attachmentsScreen) {
// Routes
// - matching bottom tab
// - central pane (report screen) of the attachment
// - found report attachments
const routes = [];
- const reportAttachments = reportAttachmentsScreen as Route<'ReportAttachments', RootStackParamList['ReportAttachments']>;
+ const reportAttachments = attachmentsScreen as Route<'Attachments', RootStackParamList['Attachments']>;
- const matchingBottomTabRoute = getMatchingBottomTabRouteForState(state);
- routes.push(createBottomTabNavigator(matchingBottomTabRoute, policyID));
- if (!isNarrowLayout) {
- routes.push(createCentralPaneNavigator({name: SCREENS.REPORT, params: {reportID: reportAttachments.params?.reportID ?? ''}}));
- }
- routes.push(reportAttachments);
+ if (reportAttachments.params?.type === CONST.ATTACHMENT_TYPE.REPORT) {
+ const matchingBottomTabRoute = getMatchingBottomTabRouteForState(state);
+ routes.push(createBottomTabNavigator(matchingBottomTabRoute, policyID));
+ if (!isNarrowLayout) {
+ routes.push(createCentralPaneNavigator({name: SCREENS.REPORT, params: {reportID: reportAttachments.params?.reportID ?? ''}}));
+ }
+ routes.push(reportAttachments);
- return {
- adaptedState: getRoutesWithIndex(routes),
- metainfo,
- };
+ return {
+ adaptedState: getRoutesWithIndex(routes),
+ metainfo,
+ };
+ }
}
// We need to make sure that this if only handles states where we deeplink to the bottom tab directly
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index 7f597e4d2210..1e78bb261a04 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -114,10 +114,13 @@ type SettingsNavigatorParamList = {
[SCREENS.SETTINGS.TROUBLESHOOT]: undefined;
[SCREENS.SETTINGS.APP_DOWNLOAD_LINKS]: undefined;
[SCREENS.SETTINGS.TROUBLESHOOT]: undefined;
- [SCREENS.SETTINGS.CONSOLE]: undefined;
+ [SCREENS.SETTINGS.CONSOLE]: {
+ backTo: Routes;
+ };
[SCREENS.SETTINGS.SHARE_LOG]: {
/** URL of the generated file to share logs in a report */
source: string;
+ backTo: Routes;
};
[SCREENS.SETTINGS.WALLET.ROOT]: undefined;
[SCREENS.SETTINGS.WALLET.CARDS_DIGITAL_DETAILS_UPDATE_ADDRESS]: undefined;
@@ -218,6 +221,7 @@ type SettingsNavigatorParamList = {
};
[SCREENS.WORKSPACE.TAG_SETTINGS]: {
policyID: string;
+ orderWeight: number;
tagName: string;
};
[SCREENS.WORKSPACE.TAG_LIST_VIEW]: {
@@ -230,6 +234,7 @@ type SettingsNavigatorParamList = {
};
[SCREENS.WORKSPACE.TAG_EDIT]: {
policyID: string;
+ orderWeight: number;
tagName: string;
};
[SCREENS.WORKSPACE.TAXES_SETTINGS]: {
@@ -337,11 +342,10 @@ type SettingsNavigatorParamList = {
[SCREENS.WORKSPACE.ACCOUNTING.XERO_TRACKING_CATEGORIES]: {
policyID: string;
};
- [SCREENS.WORKSPACE.ACCOUNTING.XERO_MAP_COST_CENTERS]: {
- policyID: string;
- };
- [SCREENS.WORKSPACE.ACCOUNTING.XERO_MAP_REGION]: {
+ [SCREENS.WORKSPACE.ACCOUNTING.XERO_MAP_TRACKING_CATEGORY]: {
policyID: string;
+ categoryId: string;
+ categoryName: string;
};
[SCREENS.WORKSPACE.ACCOUNTING.XERO_EXPORT]: {
policyID: string;
@@ -352,6 +356,9 @@ type SettingsNavigatorParamList = {
[SCREENS.WORKSPACE.ACCOUNTING.XERO_ADVANCED]: {
policyID: string;
};
+ [SCREENS.WORKSPACE.ACCOUNTING.XERO_BILL_STATUS_SELECTOR]: {
+ policyID: string;
+ };
[SCREENS.WORKSPACE.ACCOUNTING.XERO_INVOICE_ACCOUNT_SELECTOR]: {
policyID: string;
};
@@ -798,7 +805,6 @@ type FullScreenNavigatorParamList = {
};
[SCREENS.WORKSPACE.TAGS]: {
policyID: string;
- tagName: string;
};
[SCREENS.WORKSPACE.TAXES]: {
policyID: string;
@@ -872,9 +878,11 @@ type PublicScreensParamList = SharedScreensParamList & {
type AuthScreensParamList = SharedScreensParamList & {
[NAVIGATORS.CENTRAL_PANE_NAVIGATOR]: NavigatorScreenParams;
[SCREENS.CONCIERGE]: undefined;
- [SCREENS.REPORT_ATTACHMENTS]: {
+ [SCREENS.ATTACHMENTS]: {
reportID: string;
source: string;
+ type: ValueOf;
+ accountID: string;
};
[SCREENS.PROFILE_AVATAR]: {
accountID: string;
diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts
index 2be960225a97..3b60622467e1 100644
--- a/src/libs/PolicyUtils.ts
+++ b/src/libs/PolicyUtils.ts
@@ -2,6 +2,7 @@ import Str from 'expensify-common/lib/str';
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
+import type {SelectorType} from '@components/SelectionScreen';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
@@ -420,6 +421,18 @@ function getCurrentXeroOrganizationName(policy: Policy | undefined): string | un
return findCurrentXeroOrganization(getXeroTenants(policy), policy?.connections?.xero?.config?.tenantID)?.name;
}
+function getXeroBankAccountsWithDefaultSelect(policy: Policy | undefined, selectedBankAccountId: string | undefined): SelectorType[] {
+ const bankAccounts = policy?.connections?.xero?.data?.bankAccounts ?? [];
+ const isMatchFound = bankAccounts?.some(({id}) => id === selectedBankAccountId);
+
+ return (bankAccounts ?? []).map(({id, name}, index) => ({
+ value: id,
+ text: name,
+ keyForList: id,
+ isSelected: isMatchFound ? selectedBankAccountId === id : index === 0,
+ }));
+}
+
export {
canEditTaxRate,
extractPolicyIDFromPath,
@@ -469,6 +482,7 @@ export {
getXeroTenants,
findCurrentXeroOrganization,
getCurrentXeroOrganizationName,
+ getXeroBankAccountsWithDefaultSelect,
};
export type {MemberEmailsToAccountIDs};
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index a5cafb3df5d3..a005b3d23f2c 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -81,6 +81,7 @@ import * as ReportActionsUtils from './ReportActionsUtils';
import StringUtils from './StringUtils';
import * as TransactionUtils from './TransactionUtils';
import * as Url from './Url';
+import type {AvatarSource} from './UserUtils';
import * as UserUtils from './UserUtils';
type AvatarRange = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18;
@@ -117,7 +118,7 @@ type SpendBreakdown = {
totalDisplaySpend: number;
};
-type ParticipantDetails = [number, string, UserUtils.AvatarSource, UserUtils.AvatarSource];
+type ParticipantDetails = [number, string, AvatarSource, AvatarSource];
type OptimisticAddCommentReportAction = Pick<
ReportAction,
@@ -587,7 +588,7 @@ function getLastUpdatedReport(): OnyxEntry {
return lastUpdatedReport;
}
-function getCurrentUserAvatar(): UserUtils.AvatarSource | undefined {
+function getCurrentUserAvatar(): AvatarSource | undefined {
return currentUserPersonalDetails?.avatar;
}
@@ -1721,7 +1722,7 @@ function getDefaultWorkspaceAvatarTestID(workspaceName: string): string {
return !alphaNumeric ? defaultAvatarBuildingIconTestID : `SvgDefaultAvatar_${alphaNumeric[0]} Icon`;
}
-function getWorkspaceAvatar(report: OnyxEntry): UserUtils.AvatarSource {
+function getWorkspaceAvatar(report: OnyxEntry): AvatarSource {
const workspaceName = getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`]);
const avatar = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`]?.avatarURL ?? '';
return !isEmpty(avatar) ? avatar : getDefaultWorkspaceAvatar(workspaceName);
@@ -1928,7 +1929,7 @@ function getParticipants(reportID: string) {
function getIcons(
report: OnyxEntry,
personalDetails: OnyxCollection,
- defaultIcon: UserUtils.AvatarSource | null = null,
+ defaultIcon: AvatarSource | null = null,
defaultName = '',
defaultAccountID = -1,
policy: OnyxEntry = null,
@@ -3228,6 +3229,10 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu
formattedName = getMoneyRequestReportName(report, policy);
}
+ if (isInvoiceRoom(report)) {
+ formattedName = getInvoicesChatName(report);
+ }
+
if (isArchivedRoom(report)) {
formattedName += ` (${Localize.translateLocal('common.archived')})`;
}
@@ -3236,10 +3241,6 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu
formattedName = getDisplayNameForParticipant(currentUserAccountID, undefined, undefined, true);
}
- if (isInvoiceRoom(report)) {
- formattedName = getInvoicesChatName(report);
- }
-
if (formattedName) {
return formattedName;
}
@@ -3318,7 +3319,15 @@ function getParentNavigationSubtitle(report: OnyxEntry): ParentNavigatio
}
if (isInvoiceReport(report) || isInvoiceRoom(parentReport)) {
- return {reportName: `${getPolicyName(parentReport)} & ${getInvoicePayerName(parentReport)}`};
+ let reportName = `${getPolicyName(parentReport)} & ${getInvoicePayerName(parentReport)}`;
+
+ if (isArchivedRoom(parentReport)) {
+ reportName += ` (${Localize.translateLocal('common.archived')})`;
+ }
+
+ return {
+ reportName,
+ };
}
return {
@@ -5681,28 +5690,25 @@ function temporary_getMoneyRequestOptions(
* Invoice sender, invoice receiver and auto-invited admins cannot leave
*/
function canLeaveInvoiceRoom(report: OnyxEntry): boolean {
- if (!isInvoiceRoom(report)) {
- return false;
- }
-
- const invoiceReport = getReport(report?.iouReportID ?? '');
-
- if (invoiceReport?.ownerAccountID === currentUserAccountID) {
+ if (!report || !report?.invoiceReceiver) {
return false;
}
- if (invoiceReport?.managerID === currentUserAccountID) {
+ if (report?.statusNum === CONST.REPORT.STATUS_NUM.CLOSED) {
return false;
}
- const isSenderPolicyAdmin = getPolicy(report?.policyID)?.role === CONST.POLICY.ROLE.ADMIN;
+ const isSenderPolicyAdmin = getPolicy(report.policyID)?.role === CONST.POLICY.ROLE.ADMIN;
if (isSenderPolicyAdmin) {
return false;
}
- const isReceiverPolicyAdmin =
- report?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.BUSINESS ? getPolicy(report?.invoiceReceiver?.policyID)?.role === CONST.POLICY.ROLE.ADMIN : false;
+ if (report.invoiceReceiver.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL) {
+ return report?.invoiceReceiver?.accountID !== currentUserAccountID;
+ }
+
+ const isReceiverPolicyAdmin = getPolicy(report.invoiceReceiver.policyID)?.role === CONST.POLICY.ROLE.ADMIN;
if (isReceiverPolicyAdmin) {
return false;
@@ -5726,6 +5732,10 @@ function canLeaveInvoiceRoom(report: OnyxEntry): boolean {
*/
function canLeaveRoom(report: OnyxEntry, isPolicyEmployee: boolean): boolean {
if (isInvoiceRoom(report)) {
+ if (isArchivedRoom(report)) {
+ return false;
+ }
+
const invoiceReport = getReport(report?.iouReportID ?? '');
if (invoiceReport?.ownerAccountID === currentUserAccountID) {
@@ -6653,8 +6663,8 @@ function canLeaveChat(report: OnyxEntry, policy: OnyxEntry): boo
return false;
}
- if (canLeaveInvoiceRoom(report)) {
- return true;
+ if (isInvoiceRoom(report)) {
+ return canLeaveInvoiceRoom(report);
}
return (isChatThread(report) && !!report?.notificationPreference?.length) || isUserCreatedPolicyRoom(report) || isNonAdminOrOwnerOfPolicyExpenseChat(report, policy);
diff --git a/src/libs/actions/Policy/Tag.ts b/src/libs/actions/Policy/Tag.ts
index a4e0af63512b..7760f29d6703 100644
--- a/src/libs/actions/Policy/Tag.ts
+++ b/src/libs/actions/Policy/Tag.ts
@@ -1,7 +1,7 @@
import type {NullishDeep, OnyxCollection, OnyxEntry} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
import * as API from '@libs/API';
-import type {EnablePolicyTagsParams, OpenPolicyTagsPageParams} from '@libs/API/parameters';
+import type {EnablePolicyTagsParams, OpenPolicyTagsPageParams, SetPolicyTagsEnabled} from '@libs/API/parameters';
import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
import * as ErrorUtils from '@libs/ErrorUtils';
import getIsNarrowLayout from '@libs/getIsNarrowLayout';
@@ -177,8 +177,8 @@ function createPolicyTag(policyID: string, tagName: string) {
API.write(WRITE_COMMANDS.CREATE_POLICY_TAG, parameters, onyxData);
}
-function setWorkspaceTagEnabled(policyID: string, tagsToUpdate: Record) {
- const policyTag = PolicyUtils.getTagLists(allPolicyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`] ?? {})?.[0] ?? {};
+function setWorkspaceTagEnabled(policyID: string, tagsToUpdate: Record, tagListIndex: number) {
+ const policyTag = PolicyUtils.getTagLists(allPolicyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`] ?? {})?.[tagListIndex] ?? {};
const onyxData: OnyxData = {
optimisticData: [
@@ -258,9 +258,10 @@ function setWorkspaceTagEnabled(policyID: string, tagsToUpdate: Record tagsToUpdate[key])),
+ tagListIndex,
};
API.write(WRITE_COMMANDS.SET_POLICY_TAGS_ENABLED, parameters, onyxData);
diff --git a/src/libs/actions/connections/ConnectToXero.ts b/src/libs/actions/connections/ConnectToXero.ts
index 43972e540d58..e327b218989c 100644
--- a/src/libs/actions/connections/ConnectToXero.ts
+++ b/src/libs/actions/connections/ConnectToXero.ts
@@ -12,26 +12,18 @@ const getXeroSetupLink = (policyID: string) => {
return commandURL + new URLSearchParams(params).toString();
};
-/**
- * Fetches the category object from the xero.data.trackingCategories based on the category name.
- * This is required to get Xero category object with current value stored in the xero.config.mappings
- * @param policy
- * @param key
- * @returns Filtered category matching the category name or undefined.
- */
-const getTrackingCategory = (policy: OnyxEntry, categoryName: string): (XeroTrackingCategory & {value: string}) | undefined => {
+const getTrackingCategories = (policy: OnyxEntry): Array => {
const {trackingCategories} = policy?.connections?.xero?.data ?? {};
const {mappings} = policy?.connections?.xero?.config ?? {};
- const category = trackingCategories?.find((currentCategory) => currentCategory.name.toLowerCase() === categoryName.toLowerCase());
- if (!category) {
- return undefined;
+ if (!trackingCategories) {
+ return [];
}
- return {
+ return trackingCategories.map((category) => ({
...category,
value: mappings?.[`${CONST.XERO_CONFIG.TRACKING_CATEGORY_PREFIX}${category.id}`] ?? '',
- };
+ }));
};
-export {getXeroSetupLink, getTrackingCategory};
+export {getXeroSetupLink, getTrackingCategories};
diff --git a/src/libs/actions/connections/QuickBooksOnline.ts b/src/libs/actions/connections/QuickBooksOnline.ts
index f507758e8d38..2642ad4056a9 100644
--- a/src/libs/actions/connections/QuickBooksOnline.ts
+++ b/src/libs/actions/connections/QuickBooksOnline.ts
@@ -1,41 +1,14 @@
-import type {OnyxUpdate} from 'react-native-onyx';
-import Onyx from 'react-native-onyx';
-import * as API from '@libs/API';
-import type {ConnectPolicyToAccountingIntegrationParams, SyncPolicyToQuickbooksOnlineParams} from '@libs/API/parameters';
+import type {ConnectPolicyToAccountingIntegrationParams} from '@libs/API/parameters';
import {READ_COMMANDS} from '@libs/API/types';
import {getCommandURL} from '@libs/ApiUtils';
-import CONST from '@src/CONST';
-import ONYXKEYS from '@src/ONYXKEYS';
function getQuickBooksOnlineSetupLink(policyID: string) {
const params: ConnectPolicyToAccountingIntegrationParams = {policyID};
- const commandURL = getCommandURL({command: READ_COMMANDS.CONNECT_POLICY_TO_QUICKBOOKS_ONLINE, shouldSkipWebProxy: true});
+ const commandURL = getCommandURL({
+ command: READ_COMMANDS.CONNECT_POLICY_TO_QUICKBOOKS_ONLINE,
+ shouldSkipWebProxy: true,
+ });
return commandURL + new URLSearchParams(params).toString();
}
-function syncConnection(policyID: string) {
- const optimisticData: OnyxUpdate[] = [
- {
- onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}${policyID}`,
- value: {
- stageInProgress: CONST.POLICY.CONNECTIONS.SYNC_STAGE_NAME.STARTING_IMPORT,
- connectionName: CONST.POLICY.CONNECTIONS.NAME.QBO,
- },
- },
- ];
- const failureData: OnyxUpdate[] = [
- {
- onyxMethod: Onyx.METHOD.SET,
- key: `${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}${policyID}`,
- value: null,
- },
- ];
- const parameters: SyncPolicyToQuickbooksOnlineParams = {
- policyID,
- idempotencyKey: policyID,
- };
- API.read(READ_COMMANDS.SYNC_POLICY_TO_QUICKBOOKS_ONLINE, parameters, {optimisticData, failureData});
-}
-
-export {getQuickBooksOnlineSetupLink, syncConnection};
+export default getQuickBooksOnlineSetupLink;
diff --git a/src/libs/actions/connections/index.ts b/src/libs/actions/connections/index.ts
index 4a501910548b..9b463cf1780c 100644
--- a/src/libs/actions/connections/index.ts
+++ b/src/libs/actions/connections/index.ts
@@ -1,12 +1,19 @@
+import type {OnyxEntry, OnyxUpdate} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
-import type {OnyxUpdate} from 'react-native-onyx';
import * as API from '@libs/API';
-import type {RemovePolicyConnectionParams, UpdateManyPolicyConnectionConfigurationsParams, UpdatePolicyConnectionConfigParams} from '@libs/API/parameters';
-import {WRITE_COMMANDS} from '@libs/API/types';
+import type {
+ RemovePolicyConnectionParams,
+ SyncPolicyToQuickbooksOnlineParams,
+ SyncPolicyToXeroParams,
+ UpdateManyPolicyConnectionConfigurationsParams,
+ UpdatePolicyConnectionConfigParams,
+} from '@libs/API/parameters';
+import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
import * as ErrorUtils from '@libs/ErrorUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {ConnectionName, Connections, PolicyConnectionName} from '@src/types/onyx/Policy';
+import type Policy from '@src/types/onyx/Policy';
function removePolicyConnection(policyID: string, connectionName: PolicyConnectionName) {
const optimisticData: OnyxUpdate[] = [
@@ -115,6 +122,48 @@ function updatePolicyConnectionConfig>(
policyID: string,
connectionName: TConnectionName,
@@ -183,4 +232,8 @@ function updateManyPolicyConnectionConfigs, connectionName: PolicyConnectionName): boolean {
+ return policy?.connections?.[connectionName]?.lastSync?.isSuccessful === false;
+}
+
+export {removePolicyConnection, updatePolicyConnectionConfig, updateManyPolicyConnectionConfigs, hasSynchronizationError, syncConnection};
diff --git a/src/pages/PrivateNotes/PrivateNotesListPage.tsx b/src/pages/PrivateNotes/PrivateNotesListPage.tsx
index 1893f81da2fe..286c91d628fd 100644
--- a/src/pages/PrivateNotes/PrivateNotesListPage.tsx
+++ b/src/pages/PrivateNotes/PrivateNotesListPage.tsx
@@ -1,7 +1,8 @@
-import React, {useMemo} from 'react';
+import React, {useCallback, useMemo} from 'react';
import {withOnyx} from 'react-native-onyx';
import type {OnyxCollection} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
+import {AttachmentContext} from '@components/AttachmentContext';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import ScreenWrapper from '@components/ScreenWrapper';
@@ -34,29 +35,34 @@ type NoteListItem = {
brickRoadIndicator: ValueOf | undefined;
note: string;
disabled: boolean;
+ reportID: string;
+ accountID: string;
};
function PrivateNotesListPage({report, personalDetailsList, session}: PrivateNotesListPageProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
+ const getAttachmentValue = useCallback((item: NoteListItem) => ({reportID: item.reportID, accountID: Number(item.accountID), type: CONST.ATTACHMENT_TYPE.NOTE}), []);
/**
* Gets the menu item for each workspace
*/
function getMenuItem(item: NoteListItem) {
return (
-
+
+
+
);
}
@@ -68,6 +74,8 @@ function PrivateNotesListPage({report, personalDetailsList, session}: PrivateNot
return Object.keys(report.privateNotes ?? {}).map((accountID: string) => {
const privateNote = report.privateNotes?.[Number(accountID)];
return {
+ reportID: report.reportID,
+ accountID,
title: Number(session?.accountID) === Number(accountID) ? translate('privateNotes.myNote') : personalDetailsList?.[accountID]?.login ?? '',
action: () => Navigation.navigate(ROUTES.PRIVATE_NOTES_EDIT.getRoute(report.reportID, accountID)),
brickRoadIndicator: privateNoteBrickRoadIndicator(Number(accountID)),
diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx
index bc101904a1e9..f87845d91254 100644
--- a/src/pages/home/report/ReportActionItem.tsx
+++ b/src/pages/home/report/ReportActionItem.tsx
@@ -5,6 +5,7 @@ import {InteractionManager, View} from 'react-native';
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import type {Emoji} from '@assets/emojis/types';
+import {AttachmentContext} from '@components/AttachmentContext';
import Button from '@components/Button';
import DisplayNames from '@components/DisplayNames';
import Hoverable from '@components/Hoverable';
@@ -390,6 +391,8 @@ function ReportActionItem({
[report, action, toggleContextMenuFromActiveReportAction, transactionThreadReport],
);
+ const attachmentContextValue = useMemo(() => ({reportID: report.reportID, type: CONST.ATTACHMENT_TYPE.REPORT}), [report.reportID]);
+
const actionableItemButtons: ActionableItem[] = useMemo(() => {
if (!isActionableWhisper && (!ReportActionsUtils.isActionableJoinRequest(action) || action.originalMessage.choice !== '')) {
return [];
@@ -633,50 +636,54 @@ function ReportActionItem({
!ReportActionsUtils.isPendingRemove(action);
children = (
- {draftMessage === undefined ? (
-
-
- {hasBeenFlagged && (
-
- )}
- {/**
+
+ {isHidden ? translate('moderation.revealMessage') : translate('moderation.hideMessage')}
+
+
+ )}
+ {/**
These are the actionable buttons that appear at the bottom of a Concierge message
for example: Invite a user mentioned but not a member of the room
https://github.com/Expensify/App/issues/32741
*/}
- {actionableItemButtons.length > 0 && (
-
- )}
-
- ) : (
-
- )}
+ {actionableItemButtons.length > 0 && (
+
+ )}
+
+ ) : (
+
+ )}
+
);
}
diff --git a/src/pages/home/report/ReportAttachments.tsx b/src/pages/home/report/ReportAttachments.tsx
index 82b49d1e260c..3ff472baee40 100644
--- a/src/pages/home/report/ReportAttachments.tsx
+++ b/src/pages/home/report/ReportAttachments.tsx
@@ -9,10 +9,12 @@ import * as ReportUtils from '@libs/ReportUtils';
import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
-type ReportAttachmentsProps = StackScreenProps;
+type ReportAttachmentsProps = StackScreenProps;
function ReportAttachments({route}: ReportAttachmentsProps) {
const reportID = route.params.reportID;
+ const type = route.params.type;
+ const accountID = route.params.accountID;
const report = ReportUtils.getReport(reportID);
// In native the imported images sources are of type number. Ref: https://reactnative.dev/docs/image#imagesource
@@ -20,14 +22,16 @@ function ReportAttachments({route}: ReportAttachmentsProps) {
const onCarouselAttachmentChange = useCallback(
(attachment: Attachment) => {
- const routeToNavigate = ROUTES.REPORT_ATTACHMENTS.getRoute(reportID, String(attachment.source));
+ const routeToNavigate = ROUTES.ATTACHMENTS.getRoute(reportID, type, String(attachment.source), Number(accountID));
Navigation.navigate(routeToNavigate);
},
- [reportID],
+ [reportID, accountID, type],
);
return (
{
Navigation.closeRHPFlow();
};
@@ -126,15 +121,21 @@ function IOURequestStartPage({
}
return (
-
- {({safeAreaPaddingBottomStyle}) => (
-
+
+ {({safeAreaPaddingBottomStyle}) => (
-
- )}
-
+ )}
+
+
);
}
diff --git a/src/pages/settings/AboutPage/ConsolePage.tsx b/src/pages/settings/AboutPage/ConsolePage.tsx
index 132388365ada..aee11c89f22c 100644
--- a/src/pages/settings/AboutPage/ConsolePage.tsx
+++ b/src/pages/settings/AboutPage/ConsolePage.tsx
@@ -1,3 +1,5 @@
+import type {RouteProp} from '@react-navigation/native';
+import {useRoute} from '@react-navigation/native';
import {format} from 'date-fns';
import React, {useCallback, useEffect, useMemo, useState} from 'react';
import {View} from 'react-native';
@@ -21,9 +23,11 @@ import type {Log} from '@libs/Console';
import localFileCreate from '@libs/localFileCreate';
import localFileDownload from '@libs/localFileDownload';
import Navigation from '@libs/Navigation/Navigation';
+import type {SettingsNavigatorParamList} from '@navigation/types';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
+import type SCREENS from '@src/SCREENS';
import type {CapturedLogs} from '@src/types/onyx';
type ConsolePageOnyxProps = {
@@ -44,6 +48,8 @@ function ConsolePage({capturedLogs, shouldStoreLogs}: ConsolePageProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
+ const route = useRoute>();
+
const logsList = useMemo(
() =>
Object.entries(logs ?? {})
@@ -114,7 +120,7 @@ function ConsolePage({capturedLogs, shouldStoreLogs}: ConsolePageProps) {
Navigation.goBack(ROUTES.SETTINGS_TROUBLESHOOT)}
+ onBackButtonPress={() => Navigation.goBack(route.params?.backTo)}
/>
Navigation.goBack(ROUTES.SETTINGS_CONSOLE)}
+ onBackButtonPress={() => Navigation.goBack(ROUTES.SETTINGS_CONSOLE.getRoute())}
/>
Navigation.navigate(ROUTES.SETTINGS_CONSOLE)),
+ action: waitForNavigate(() => Navigation.navigate(ROUTES.SETTINGS_CONSOLE.getRoute(ROUTES.SETTINGS_TROUBLESHOOT))),
};
const baseMenuItems: BaseMenuItem[] = [
diff --git a/src/pages/workspace/AccessOrNotFoundWrapper.tsx b/src/pages/workspace/AccessOrNotFoundWrapper.tsx
index cbc94ad37f03..a9d971d4c0f1 100644
--- a/src/pages/workspace/AccessOrNotFoundWrapper.tsx
+++ b/src/pages/workspace/AccessOrNotFoundWrapper.tsx
@@ -1,14 +1,17 @@
/* eslint-disable rulesdir/no-negated-variables */
import React, {useEffect} from 'react';
-import type {OnyxEntry} from 'react-native-onyx';
+import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import type {FullPageNotFoundViewProps} from '@components/BlockingViews/FullPageNotFoundView';
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
+import * as IOUUtils from '@libs/IOUUtils';
import Navigation from '@libs/Navigation/Navigation';
import * as PolicyUtils from '@libs/PolicyUtils';
+import * as ReportUtils from '@libs/ReportUtils';
import NotFoundPage from '@pages/ErrorPage/NotFoundPage';
import * as Policy from '@userActions/Policy/Policy';
+import type {IOUType} from '@src/CONST';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
@@ -17,13 +20,22 @@ import type {PolicyFeatureName} from '@src/types/onyx/Policy';
import callOrReturn from '@src/types/utils/callOrReturn';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
-const POLICY_ACCESS_VARIANTS = {
+const ACCESS_VARIANTS = {
[CONST.POLICY.ACCESS_VARIANTS.PAID]: (policy: OnyxEntry) => PolicyUtils.isPaidGroupPolicy(policy) && !!policy?.isPolicyExpenseChatEnabled,
[CONST.POLICY.ACCESS_VARIANTS.ADMIN]: (policy: OnyxEntry) => PolicyUtils.isPolicyAdmin(policy),
-} as const satisfies Record boolean>;
-
-type PolicyAccessVariant = keyof typeof POLICY_ACCESS_VARIANTS;
+ [CONST.IOU.ACCESS_VARIANTS.CREATE]: (policy: OnyxEntry, report: OnyxEntry, allPolicies: OnyxCollection, iouType?: IOUType) =>
+ !!iouType &&
+ IOUUtils.isValidMoneyRequestType(iouType) &&
+ // Allow the user to submit the expense if we are submitting the expense in global menu or the report can create the expense
+ (isEmptyObject(report?.reportID) || ReportUtils.canCreateRequest(report, policy, iouType)) &&
+ (iouType !== CONST.IOU.TYPE.INVOICE || PolicyUtils.canSendInvoice(allPolicies)),
+} as const satisfies Record, iouType?: IOUType) => boolean>;
+
+type AccessVariant = keyof typeof ACCESS_VARIANTS;
type AccessOrNotFoundWrapperOnyxProps = {
+ /** The report that holds the transaction */
+ report: OnyxEntry;
+
/** The report currently being looked at */
policy: OnyxEntry;
@@ -35,11 +47,14 @@ type AccessOrNotFoundWrapperProps = AccessOrNotFoundWrapperOnyxProps & {
/** The children to render */
children: ((props: AccessOrNotFoundWrapperOnyxProps) => React.ReactNode) | React.ReactNode;
+ /** The id of the report that holds the transaction */
+ reportID?: string;
+
/** The report currently being looked at */
- policyID: string;
+ policyID?: string;
/** Defines which types of access should be verified */
- accessVariants?: PolicyAccessVariant[];
+ accessVariants?: AccessVariant[];
/** The current feature name that the user tries to get access to */
featureName?: PolicyFeatureName;
@@ -49,6 +64,12 @@ type AccessOrNotFoundWrapperProps = AccessOrNotFoundWrapperOnyxProps & {
/** Whether or not to block user from accessing the page */
shouldBeBlocked?: boolean;
+
+ /** The type of the transaction */
+ iouType?: IOUType;
+
+ /** The list of all policies */
+ allPolicies?: OnyxCollection;
} & Pick;
type PageNotFoundFallbackProps = Pick & {shouldShowFullScreenFallback: boolean};
@@ -64,7 +85,7 @@ function PageNotFoundFallback({policyID, shouldShowFullScreenFallback, fullPageN
/>
) : (
Navigation.goBack(ROUTES.WORKSPACE_PROFILE.getRoute(policyID))}
+ onBackButtonPress={() => Navigation.goBack(policyID ? ROUTES.WORKSPACE_PROFILE.getRoute(policyID) : ROUTES.HOME)}
// eslint-disable-next-line react/jsx-props-no-spreading
{...fullPageNotFoundViewProps}
/>
@@ -72,9 +93,11 @@ function PageNotFoundFallback({policyID, shouldShowFullScreenFallback, fullPageN
}
function AccessOrNotFoundWrapper({accessVariants = [], fullPageNotFoundViewProps, shouldBeBlocked, ...props}: AccessOrNotFoundWrapperProps) {
- const {policy, policyID, featureName, isLoadingReportData} = props;
+ const {policy, policyID, report, iouType, allPolicies, featureName, isLoadingReportData} = props;
const isPolicyIDInRoute = !!policyID?.length;
+ const isMoneyRequest = !!iouType && IOUUtils.isValidMoneyRequestType(iouType);
+ const isFromGlobalCreate = isEmptyObject(report?.reportID);
useEffect(() => {
if (!isPolicyIDInRoute || !isEmptyObject(policy)) {
@@ -86,17 +109,17 @@ function AccessOrNotFoundWrapper({accessVariants = [], fullPageNotFoundViewProps
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isPolicyIDInRoute, policyID]);
- const shouldShowFullScreenLoadingIndicator = isLoadingReportData !== false && (!Object.entries(policy ?? {}).length || !policy?.id);
+ const shouldShowFullScreenLoadingIndicator = !isMoneyRequest && isLoadingReportData !== false && (!Object.entries(policy ?? {}).length || !policy?.id);
const isFeatureEnabled = featureName ? PolicyUtils.isPolicyFeatureEnabled(policy, featureName) : true;
const isPageAccessible = accessVariants.reduce((acc, variant) => {
- const accessFunction = POLICY_ACCESS_VARIANTS[variant];
- return acc && accessFunction(policy);
+ const accessFunction = ACCESS_VARIANTS[variant];
+ return acc && accessFunction(policy, report, allPolicies ?? null, iouType);
}, true);
- const shouldShowNotFoundPage =
- isEmptyObject(policy) || (Object.keys(policy).length === 1 && !isEmptyObject(policy.errors)) || !policy?.id || !isPageAccessible || !isFeatureEnabled || shouldBeBlocked;
+ const isPolicyNotAccessible = isEmptyObject(policy) || (Object.keys(policy).length === 1 && !isEmptyObject(policy.errors)) || !policy?.id;
+ const shouldShowNotFoundPage = (!isMoneyRequest && !isFromGlobalCreate && isPolicyNotAccessible) || !isPageAccessible || !isFeatureEnabled || shouldBeBlocked;
if (shouldShowFullScreenLoadingIndicator) {
return ;
@@ -115,11 +138,14 @@ function AccessOrNotFoundWrapper({accessVariants = [], fullPageNotFoundViewProps
return callOrReturn(props.children, props);
}
-export type {PolicyAccessVariant};
+export type {AccessVariant};
export default withOnyx({
+ report: {
+ key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
+ },
policy: {
- key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID ?? ''}`,
+ key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
},
isLoadingReportData: {
key: ONYXKEYS.IS_LOADING_REPORT_DATA,
diff --git a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx
index a3f7a65d179e..699ba9a14564 100644
--- a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx
+++ b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx
@@ -1,7 +1,8 @@
import {useFocusEffect} from '@react-navigation/native';
import type {StackScreenProps} from '@react-navigation/stack';
-import React, {useCallback} from 'react';
+import React, {useCallback, useState} from 'react';
import {View} from 'react-native';
+import ConfirmModal from '@components/ConfirmModal';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import * as Illustrations from '@components/Icon/Illustrations';
import ScreenWrapper from '@components/ScreenWrapper';
@@ -13,12 +14,14 @@ import usePermissions from '@hooks/usePermissions';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import * as ErrorUtils from '@libs/ErrorUtils';
+import Navigation from '@libs/Navigation/Navigation';
import type {FullScreenNavigatorParamList} from '@libs/Navigation/types';
import * as Category from '@userActions/Policy/Category';
import * as Policy from '@userActions/Policy/Policy';
import * as Tag from '@userActions/Policy/Tag';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
+import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
import type {Errors, PendingAction} from '@src/types/onyx/OnyxCommon';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
@@ -28,6 +31,12 @@ import type {WithPolicyAndFullscreenLoadingProps} from './withPolicyAndFullscree
import withPolicyAndFullscreenLoading from './withPolicyAndFullscreenLoading';
import ToggleSettingOptionRow from './workflows/ToggleSettingsOptionRow';
+type ItemType = 'organize' | 'integrate';
+type ConnectionWarningModalState = {
+ isOpen: boolean;
+ itemType?: ItemType;
+};
+
type WorkspaceMoreFeaturesPageProps = WithPolicyAndFullscreenLoadingProps & StackScreenProps;
type Item = {
@@ -55,6 +64,9 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
const {canUseAccountingIntegrations} = usePermissions();
const hasAccountingConnection = !!policy?.areConnectionsEnabled && !isEmptyObject(policy?.connections);
const isSyncTaxEnabled = !!policy?.connections?.quickbooksOnline?.config.syncTax || !!policy?.connections?.xero?.config.importTaxRates;
+ const policyID = policy?.id ?? '';
+
+ const [connectionWarningModalState, setConnectionWarningModalState] = useState({isOpen: false});
const spendItems: Item[] = [
{
@@ -88,6 +100,13 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
disabled: hasAccountingConnection,
pendingAction: policy?.pendingFields?.areCategoriesEnabled,
action: (isEnabled: boolean) => {
+ if (hasAccountingConnection) {
+ setConnectionWarningModalState({
+ isOpen: true,
+ itemType: 'organize',
+ });
+ return;
+ }
Category.enablePolicyCategories(policy?.id ?? '', isEnabled);
},
},
@@ -99,6 +118,13 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
disabled: hasAccountingConnection,
pendingAction: policy?.pendingFields?.areTagsEnabled,
action: (isEnabled: boolean) => {
+ if (hasAccountingConnection) {
+ setConnectionWarningModalState({
+ isOpen: true,
+ itemType: 'organize',
+ });
+ return;
+ }
Tag.enablePolicyTags(policy?.id ?? '', isEnabled);
},
},
@@ -107,9 +133,16 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
titleTranslationKey: 'workspace.moreFeatures.taxes.title',
subtitleTranslationKey: 'workspace.moreFeatures.taxes.subtitle',
isActive: (policy?.tax?.trackingEnabled ?? false) || isSyncTaxEnabled,
- disabled: isSyncTaxEnabled || policy?.connections?.quickbooksOnline?.data?.country === CONST.COUNTRY.US,
+ disabled: hasAccountingConnection,
pendingAction: policy?.pendingFields?.tax,
action: (isEnabled: boolean) => {
+ if (hasAccountingConnection) {
+ setConnectionWarningModalState({
+ isOpen: true,
+ itemType: 'organize',
+ });
+ return;
+ }
Policy.enablePolicyTaxes(policy?.id ?? '', isEnabled);
},
},
@@ -123,6 +156,13 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
isActive: !!policy?.areConnectionsEnabled,
pendingAction: policy?.pendingFields?.areConnectionsEnabled,
action: (isEnabled: boolean) => {
+ if (hasAccountingConnection) {
+ setConnectionWarningModalState({
+ isOpen: true,
+ itemType: 'integrate',
+ });
+ return;
+ }
Policy.enablePolicyConnections(policy?.id ?? '', isEnabled);
},
disabled: hasAccountingConnection,
@@ -166,7 +206,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
isActive={item.isActive}
pendingAction={item.pendingAction}
onToggle={item.action}
- disabled={item.disabled}
+ showLockIcon={item.disabled}
errors={item.errors}
onCloseError={item.onCloseError}
/>
@@ -207,6 +247,17 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
}, [fetchFeatures]),
);
+ const getConnectionWarningPrompt = useCallback(() => {
+ switch (connectionWarningModalState.itemType) {
+ case 'organize':
+ return translate('workspace.moreFeatures.connectionsWarningModal.featureEnabledText');
+ case 'integrate':
+ return translate('workspace.moreFeatures.connectionsWarningModal.disconnectText');
+ default:
+ return undefined;
+ }
+ }, [connectionWarningModalState.itemType, translate]);
+
return (
{sections.map(renderSection)}
+
+ {
+ setConnectionWarningModalState({
+ isOpen: false,
+ itemType: undefined,
+ });
+ Navigation.navigate(ROUTES.POLICY_ACCOUNTING.getRoute(policyID));
+ }}
+ onCancel={() =>
+ setConnectionWarningModalState({
+ isOpen: false,
+ itemType: undefined,
+ })
+ }
+ isVisible={connectionWarningModalState.isOpen}
+ prompt={getConnectionWarningPrompt()}
+ confirmText={translate('workspace.moreFeatures.connectionsWarningModal.manageSettings')}
+ cancelText={translate('common.cancel')}
+ />
);
diff --git a/src/pages/workspace/WorkspaceNewRoomPage.tsx b/src/pages/workspace/WorkspaceNewRoomPage.tsx
index 5716812ced16..382c1dde6d47 100644
--- a/src/pages/workspace/WorkspaceNewRoomPage.tsx
+++ b/src/pages/workspace/WorkspaceNewRoomPage.tsx
@@ -351,6 +351,7 @@ export default withOnyx;
};
@@ -101,7 +104,7 @@ function accountingIntegrationData(
}
}
-function PolicyAccountingPage({policy, connectionSyncProgress, isConnectionDataFetchNeeded}: PolicyAccountingPageProps) {
+function PolicyAccountingPage({policy, connectionSyncProgress}: PolicyAccountingPageProps) {
const theme = useTheme();
const styles = useThemeStyles();
const {translate} = useLocalize();
@@ -129,7 +132,7 @@ function PolicyAccountingPage({policy, connectionSyncProgress, isConnectionDataF
{
icon: Expensicons.Sync,
text: translate('workspace.accounting.syncNow'),
- onSelected: () => syncConnection(policyID),
+ onSelected: () => syncConnection(policyID, connectedIntegration),
disabled: isOffline,
},
{
@@ -138,10 +141,10 @@ function PolicyAccountingPage({policy, connectionSyncProgress, isConnectionDataF
onSelected: () => setIsDisconnectModalOpen(true),
},
],
- [translate, policyID, isOffline],
+ [translate, policyID, isOffline, connectedIntegration],
);
- const connectionsMenuItems: MenuItemProps[] = useMemo(() => {
+ const connectionsMenuItems: MenuItemData[] = useMemo(() => {
if (isEmptyObject(policy?.connections) && !isSyncInProgress) {
return accountingIntegrations.map((integration) => {
const integrationData = accountingIntegrationData(integration, policyID, translate);
@@ -160,16 +163,19 @@ function PolicyAccountingPage({policy, connectionSyncProgress, isConnectionDataF
if (!connectedIntegration) {
return [];
}
+ const shouldShowSynchronizationError = hasSynchronizationError(policy, connectedIntegration);
const integrationData = accountingIntegrationData(connectedIntegration, policyID, translate);
const iconProps = integrationData?.icon ? {icon: integrationData.icon, iconType: CONST.ICON_TYPE_AVATAR} : {};
return [
{
...iconProps,
interactive: false,
- wrapperStyle: [styles.sectionMenuItemTopDescription],
+ wrapperStyle: [styles.sectionMenuItemTopDescription, shouldShowSynchronizationError && styles.pb0],
shouldShowRightComponent: true,
title: integrationData?.title,
-
+ errorText: shouldShowSynchronizationError ? translate('workspace.accounting.syncError', connectedIntegration) : undefined,
+ errorTextStyle: [styles.mt5],
+ shouldShowRedDotIndicator: true,
description: isSyncInProgress
? translate('workspace.accounting.connections.syncStageName', connectionSyncProgress.stageInProgress)
: translate('workspace.accounting.lastSync'),
@@ -196,7 +202,7 @@ function PolicyAccountingPage({policy, connectionSyncProgress, isConnectionDataF
),
},
- ...(policyConnectedToXero
+ ...(policyConnectedToXero && !shouldShowSynchronizationError
? [
{
description: translate('workspace.xero.organization'),
@@ -212,10 +218,12 @@ function PolicyAccountingPage({policy, connectionSyncProgress, isConnectionDataF
}
Navigation.navigate(ROUTES.POLICY_ACCOUNTING_XERO_ORGANIZATION.getRoute(policyID, currentXeroOrganization?.id ?? ''));
},
+ pendingAction: policy?.connections?.xero?.config?.pendingFields?.tenantID,
+ brickRoadIndicator: policy?.connections?.xero?.config?.errorFields?.tenantID ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined,
},
]
: []),
- ...(isEmptyObject(policy?.connections)
+ ...(isEmptyObject(policy?.connections) || shouldShowSynchronizationError
? []
: [
{
@@ -245,21 +253,25 @@ function PolicyAccountingPage({policy, connectionSyncProgress, isConnectionDataF
]),
];
}, [
- connectedIntegration,
- connectionSyncProgress?.stageInProgress,
- currentXeroOrganization,
- currentXeroOrganizationName,
- tenants,
+ policy,
isSyncInProgress,
- overflowMenu,
- policy?.connections,
- policyConnectedToXero,
+ connectedIntegration,
policyID,
- styles,
+ translate,
+ styles.sectionMenuItemTopDescription,
+ styles.pb0,
+ styles.mt5,
+ styles.popoverMenuIcon,
+ styles.fontWeightNormal,
+ connectionSyncProgress?.stageInProgress,
theme.spinner,
+ overflowMenu,
threeDotsMenuPosition,
- translate,
+ policyConnectedToXero,
+ currentXeroOrganizationName,
+ tenants.length,
accountingIntegrations,
+ currentXeroOrganization?.id,
]);
const otherIntegrationsItems = useMemo(() => {
@@ -292,21 +304,6 @@ function PolicyAccountingPage({policy, connectionSyncProgress, isConnectionDataF
accountingIntegrations,
]);
- const headerThreeDotsMenuItems: ThreeDotsMenuProps['menuItems'] = [
- {
- icon: Expensicons.Key,
- shouldShowRightIcon: true,
- iconRight: Expensicons.NewWindow,
- text: translate('workspace.accounting.enterCredentials'),
- onSelected: () => {},
- },
- {
- icon: Expensicons.Trashcan,
- text: translate('workspace.accounting.disconnect'),
- onSelected: () => setIsDisconnectModalOpen(true),
- },
- ];
-
return (
@@ -336,30 +331,28 @@ function PolicyAccountingPage({policy, connectionSyncProgress, isConnectionDataF
titleStyles={styles.accountSettingsSectionTitle}
childrenStyles={styles.pt5}
>
- {isConnectionDataFetchNeeded ? (
-
-
-
- ) : (
- <>
+ {connectionsMenuItems.map((menuItem) => (
+
+
+
+ ))}
+ {otherIntegrationsItems && (
+
- {otherIntegrationsItems && (
-
-
-
- )}
- >
+
)}
diff --git a/src/pages/workspace/accounting/qbo/import/QuickbooksChartOfAccountsPage.tsx b/src/pages/workspace/accounting/qbo/import/QuickbooksChartOfAccountsPage.tsx
index d433977e50d5..a24e6a8c2f65 100644
--- a/src/pages/workspace/accounting/qbo/import/QuickbooksChartOfAccountsPage.tsx
+++ b/src/pages/workspace/accounting/qbo/import/QuickbooksChartOfAccountsPage.tsx
@@ -28,6 +28,7 @@ function QuickbooksChartOfAccountsPage({policy}: WithPolicyProps) {
policyID={policyID}
featureName={CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED}
contentContainerStyle={[styles.pb2, styles.ph5]}
+ connectionName={CONST.POLICY.CONNECTIONS.NAME.QBO}
>
diff --git a/src/pages/workspace/accounting/xero/XeroImportPage.tsx b/src/pages/workspace/accounting/xero/XeroImportPage.tsx
index 0d9b96c46003..3450fbe8deb3 100644
--- a/src/pages/workspace/accounting/xero/XeroImportPage.tsx
+++ b/src/pages/workspace/accounting/xero/XeroImportPage.tsx
@@ -1,15 +1,12 @@
import React, {useMemo} from 'react';
-import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import ConnectionLayout from '@components/ConnectionLayout';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
-import ScreenWrapper from '@components/ScreenWrapper';
-import ScrollView from '@components/ScrollView';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
import {getCurrentXeroOrganizationName} from '@libs/PolicyUtils';
-import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import withPolicy from '@pages/workspace/withPolicy';
import type {WithPolicyProps} from '@pages/workspace/withPolicy';
import CONST from '@src/CONST';
@@ -37,7 +34,7 @@ function XeroImportPage({policy}: WithPolicyProps) {
description: translate('workspace.xero.trackingCategories'),
action: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_XERO_TRACKING_CATEGORIES.getRoute(policyID)),
hasError: !!errorFields?.importTrackingCategories,
- title: importTrackingCategories ? translate('workspace.accounting.importTypes.TAG') : translate('workspace.xero.notImported'),
+ title: importTrackingCategories ? translate('workspace.accounting.imported') : translate('workspace.xero.notImported'),
pendingAction: pendingFields?.importTrackingCategories,
},
{
@@ -75,39 +72,34 @@ function XeroImportPage({policy}: WithPolicyProps) {
);
return (
-
-
-
-
- {translate('workspace.xero.importDescription')}
- {sections.map((section) => (
-
-
-
- ))}
-
-
-
+ {translate('workspace.xero.importDescription')}
+
+ {sections.map((section) => (
+
+
+
+ ))}
+
);
}
diff --git a/src/pages/workspace/accounting/xero/XeroMapRegionsToConfigurationPage.tsx b/src/pages/workspace/accounting/xero/XeroMapRegionsToConfigurationPage.tsx
deleted file mode 100644
index e5ec85b05c3a..000000000000
--- a/src/pages/workspace/accounting/xero/XeroMapRegionsToConfigurationPage.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-import React, {useCallback, useMemo} from 'react';
-import ConnectionLayout from '@components/ConnectionLayout';
-import SelectionList from '@components/SelectionList';
-import RadioListItem from '@components/SelectionList/RadioListItem';
-import useLocalize from '@hooks/useLocalize';
-import useThemeStyles from '@hooks/useThemeStyles';
-import * as Connections from '@libs/actions/connections';
-import {getTrackingCategory} from '@libs/actions/connections/ConnectToXero';
-import Navigation from '@libs/Navigation/Navigation';
-import type {WithPolicyProps} from '@pages/workspace/withPolicy';
-import withPolicyConnections from '@pages/workspace/withPolicyConnections';
-import CONST from '@src/CONST';
-import type {TranslationPaths} from '@src/languages/types';
-import ROUTES from '@src/ROUTES';
-
-function XeroMapRegionsToConfigurationPage({policy}: WithPolicyProps) {
- const {translate} = useLocalize();
- const styles = useThemeStyles();
-
- const policyID = policy?.id ?? '';
- const category = getTrackingCategory(policy, CONST.XERO_CONFIG.TRACKING_CATEGORY_FIELDS.REGION);
-
- const optionsList = useMemo(
- () =>
- Object.values(CONST.XERO_CONFIG.TRACKING_CATEGORY_OPTIONS).map((option) => ({
- value: option,
- text: translate(`workspace.xero.trackingCategoriesOptions.${option.toLowerCase()}` as TranslationPaths),
- keyForList: option,
- isSelected: option.toLowerCase() === category?.value?.toLowerCase(),
- })),
- [translate, category],
- );
-
- const updateMapping = useCallback(
- (option: {value: string}) => {
- if (option.value !== category?.value) {
- Connections.updatePolicyConnectionConfig(policyID, CONST.POLICY.CONNECTIONS.NAME.XERO, CONST.XERO_CONFIG.MAPPINGS, {
- ...(policy?.connections?.xero?.config?.mappings ?? {}),
- ...(category?.id ? {[`${CONST.XERO_CONFIG.TRACKING_CATEGORY_PREFIX}${category.id}`]: option.value} : {}),
- });
- }
- Navigation.goBack(ROUTES.POLICY_ACCOUNTING_XERO_TRACKING_CATEGORIES.getRoute(policyID));
- },
- [category, policyID, policy?.connections?.xero?.config?.mappings],
- );
-
- return (
-
-
-
- );
-}
-
-XeroMapRegionsToConfigurationPage.displayName = 'XeroMapRegionsToConfigurationPage';
-export default withPolicyConnections(XeroMapRegionsToConfigurationPage);
diff --git a/src/pages/workspace/accounting/xero/XeroMapCostCentersToConfigurationPage.tsx b/src/pages/workspace/accounting/xero/XeroMapTrackingCategoryConfigurationPage.tsx
similarity index 53%
rename from src/pages/workspace/accounting/xero/XeroMapCostCentersToConfigurationPage.tsx
rename to src/pages/workspace/accounting/xero/XeroMapTrackingCategoryConfigurationPage.tsx
index eec922522468..d81bd7d92865 100644
--- a/src/pages/workspace/accounting/xero/XeroMapCostCentersToConfigurationPage.tsx
+++ b/src/pages/workspace/accounting/xero/XeroMapTrackingCategoryConfigurationPage.tsx
@@ -1,3 +1,4 @@
+import {useRoute} from '@react-navigation/native';
import React, {useCallback, useMemo} from 'react';
import ConnectionLayout from '@components/ConnectionLayout';
import SelectionList from '@components/SelectionList';
@@ -5,7 +6,6 @@ import RadioListItem from '@components/SelectionList/RadioListItem';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import * as Connections from '@libs/actions/connections';
-import {getTrackingCategory} from '@libs/actions/connections/ConnectToXero';
import Navigation from '@libs/Navigation/Navigation';
import type {WithPolicyProps} from '@pages/workspace/withPolicy';
import withPolicyConnections from '@pages/workspace/withPolicyConnections';
@@ -13,13 +13,26 @@ import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import ROUTES from '@src/ROUTES';
-function XeroMapCostCentersToConfigurationPage({policy}: WithPolicyProps) {
+type RouteParams = {
+ categoryId?: string;
+ categoryName?: string;
+};
+
+const TRACKING_CATEGORIES_KEY = 'trackingCategory_';
+
+function XeroMapTrackingCategoryConfigurationPage({policy}: WithPolicyProps) {
const {translate} = useLocalize();
+ const route = useRoute();
+ const params = route.params as RouteParams;
const styles = useThemeStyles();
-
+ const categoryId = params?.categoryId ?? '';
+ const categoryName = decodeURIComponent(params?.categoryName ?? '');
const policyID = policy?.id ?? '';
+ const {trackingCategories} = policy?.connections?.xero?.data ?? {};
+ const {mappings} = policy?.connections?.xero?.config ?? {};
- const category = getTrackingCategory(policy, CONST.XERO_CONFIG.TRACKING_CATEGORY_FIELDS.COST_CENTERS);
+ const currentTrackingCategory = trackingCategories?.find((category) => category.id === categoryId);
+ const currentTrackingCategoryValue = currentTrackingCategory ? mappings?.[`${TRACKING_CATEGORIES_KEY}${currentTrackingCategory.id}`] ?? '' : '';
const optionsList = useMemo(
() =>
@@ -27,45 +40,47 @@ function XeroMapCostCentersToConfigurationPage({policy}: WithPolicyProps) {
value: option,
text: translate(`workspace.xero.trackingCategoriesOptions.${option.toLowerCase()}` as TranslationPaths),
keyForList: option,
- isSelected: option.toLowerCase() === category?.value?.toLowerCase(),
+ isSelected: option === currentTrackingCategoryValue,
})),
- [translate, category],
+ [translate, currentTrackingCategoryValue],
);
const updateMapping = useCallback(
(option: {value: string}) => {
- if (option.value !== category?.value) {
+ if (option.value !== categoryName) {
Connections.updatePolicyConnectionConfig(policyID, CONST.POLICY.CONNECTIONS.NAME.XERO, CONST.XERO_CONFIG.MAPPINGS, {
...(policy?.connections?.xero?.config?.mappings ?? {}),
- ...(category?.id ? {[`${CONST.XERO_CONFIG.TRACKING_CATEGORY_PREFIX}${category.id}`]: option.value} : {}),
+ ...(categoryId ? {[`${CONST.XERO_CONFIG.TRACKING_CATEGORY_PREFIX}${categoryId}`]: option.value} : {}),
});
}
Navigation.goBack(ROUTES.POLICY_ACCOUNTING_XERO_TRACKING_CATEGORIES.getRoute(policyID));
},
- [category, policyID, policy?.connections?.xero?.config?.mappings],
+ [categoryId, categoryName, policyID, policy?.connections?.xero?.config?.mappings],
);
return (
option.isSelected)?.keyForList}
shouldDebounceRowSelect
/>
);
}
-XeroMapCostCentersToConfigurationPage.displayName = 'XeroMapCostCentersToConfigurationPage';
-export default withPolicyConnections(XeroMapCostCentersToConfigurationPage);
+XeroMapTrackingCategoryConfigurationPage.displayName = 'XeroMapTrackingCategoryConfigurationPage';
+export default withPolicyConnections(XeroMapTrackingCategoryConfigurationPage);
diff --git a/src/pages/workspace/accounting/xero/XeroOrganizationConfigurationPage.tsx b/src/pages/workspace/accounting/xero/XeroOrganizationConfigurationPage.tsx
index 85b0f20071ab..ae882d690bd0 100644
--- a/src/pages/workspace/accounting/xero/XeroOrganizationConfigurationPage.tsx
+++ b/src/pages/workspace/accounting/xero/XeroOrganizationConfigurationPage.tsx
@@ -1,8 +1,7 @@
import type {StackScreenProps} from '@react-navigation/stack';
import React, {useMemo} from 'react';
-import HeaderWithBackButton from '@components/HeaderWithBackButton';
-import ScreenWrapper from '@components/ScreenWrapper';
-import ScrollView from '@components/ScrollView';
+import ConnectionLayout from '@components/ConnectionLayout';
+import OfflineWithFeedback from '@components/OfflineWithFeedback';
import SelectionList from '@components/SelectionList';
import RadioListItem from '@components/SelectionList/RadioListItem';
import type {ListItem} from '@components/SelectionList/types';
@@ -10,12 +9,13 @@ import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import {updatePolicyConnectionConfig} from '@libs/actions/connections';
+import * as ErrorUtils from '@libs/ErrorUtils';
import Navigation from '@libs/Navigation/Navigation';
import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
import {findCurrentXeroOrganization, getXeroTenants} from '@libs/PolicyUtils';
-import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import withPolicy from '@pages/workspace/withPolicy';
import type {WithPolicyProps} from '@pages/workspace/withPolicy';
+import * as Policy from '@userActions/Policy/Policy';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
@@ -30,7 +30,8 @@ function XeroOrganizationConfigurationPage({
const {translate} = useLocalize();
const styles = useThemeStyles();
const tenants = useMemo(() => getXeroTenants(policy ?? undefined), [policy]);
- const currentXeroOrganization = findCurrentXeroOrganization(tenants, policy?.connections?.xero?.config?.tenantID);
+ const xeroConfig = policy?.connections?.xero?.config;
+ const currentXeroOrganization = findCurrentXeroOrganization(tenants, xeroConfig?.tenantID);
const policyID = policy?.id ?? '';
@@ -46,34 +47,36 @@ function XeroOrganizationConfigurationPage({
return;
}
- updatePolicyConnectionConfig(policyID, CONST.POLICY.CONNECTIONS.NAME.XERO, 'tenantID', keyForList);
+ updatePolicyConnectionConfig(policyID, CONST.POLICY.CONNECTIONS.NAME.XERO, CONST.XERO_CONFIG.TENANT_ID, keyForList);
Navigation.goBack(ROUTES.WORKSPACE_ACCOUNTING.getRoute(policyID));
};
return (
-
- Policy.clearXeroErrorField(policyID, CONST.XERO_CONFIG.TENANT_ID)}
>
-
-
- {translate('workspace.xero.organizationDescription')}
-
-
-
-
+ {translate('workspace.xero.organizationDescription')}
+
+
+
);
}
diff --git a/src/pages/workspace/accounting/xero/XeroTaxesConfigurationPage.tsx b/src/pages/workspace/accounting/xero/XeroTaxesConfigurationPage.tsx
index e07941338c69..716ba6b17ccb 100644
--- a/src/pages/workspace/accounting/xero/XeroTaxesConfigurationPage.tsx
+++ b/src/pages/workspace/accounting/xero/XeroTaxesConfigurationPage.tsx
@@ -26,6 +26,7 @@ function XeroTaxesConfigurationPage({policy}: WithPolicyProps) {
policyID={policyID}
featureName={CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED}
contentContainerStyle={[styles.pb2, styles.ph5]}
+ connectionName={CONST.POLICY.CONNECTIONS.NAME.XERO}
>
{
- const availableCategories = [];
-
- const costCenterCategoryValue = getTrackingCategory(policy, CONST.XERO_CONFIG.TRACKING_CATEGORY_FIELDS.COST_CENTERS)?.value ?? '';
- const regionCategoryValue = getTrackingCategory(policy, CONST.XERO_CONFIG.TRACKING_CATEGORY_FIELDS.REGION)?.value ?? '';
- if (costCenterCategoryValue) {
- const isValidOption = Object.values(CONST.XERO_CONFIG.TRACKING_CATEGORY_OPTIONS).findIndex((option) => option.toLowerCase() === costCenterCategoryValue.toLowerCase()) > -1;
- availableCategories.push({
- description: translate('workspace.xero.mapXeroCostCentersTo'),
- onPress: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_XERO_TRACKING_CATEGORIES_MAP_COST_CENTERS.getRoute(policyID)),
- title: isValidOption ? translate(`workspace.xero.trackingCategoriesOptions.${costCenterCategoryValue.toLowerCase()}` as TranslationPaths) : '',
- });
- }
-
- if (regionCategoryValue) {
- const isValidOption = Object.values(CONST.XERO_CONFIG.TRACKING_CATEGORY_OPTIONS).findIndex((option) => option.toLowerCase() === regionCategoryValue.toLowerCase()) > -1;
- availableCategories.push({
- description: translate('workspace.xero.mapXeroRegionsTo'),
- onPress: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_XERO_TRACKING_CATEGORIES_MAP_REGION.getRoute(policyID)),
- title: isValidOption ? translate(`workspace.xero.trackingCategoriesOptions.${regionCategoryValue.toLowerCase()}` as TranslationPaths) : '',
- });
- }
- return availableCategories;
+ const trackingCategories = getTrackingCategories(policy);
+ return trackingCategories.map((category: XeroTrackingCategory & {value: string}) => ({
+ description: translate('workspace.xero.mapTrackingCategoryTo', {categoryName: category.name}) as TranslationPaths,
+ onPress: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_XERO_TRACKING_CATEGORIES_MAP.getRoute(policyID, category.id, category.name)),
+ title: translate(`workspace.xero.trackingCategoriesOptions.${category.value.toLowerCase()}` as TranslationPaths),
+ }));
}, [translate, policy, policyID]);
return (
@@ -58,11 +43,13 @@ function XeroTrackingCategoryConfigurationPage({policy}: WithPolicyProps) {
policyID={policyID}
featureName={CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED}
contentContainerStyle={[styles.pb2, styles.ph5]}
+ connectionName={CONST.POLICY.CONNECTIONS.NAME.XERO}
>
Connections.updatePolicyConnectionConfig(
policyID,
diff --git a/src/pages/workspace/accounting/xero/advanced/XeroAdvancedPage.tsx b/src/pages/workspace/accounting/xero/advanced/XeroAdvancedPage.tsx
index ad44a517d6b9..d337309473a8 100644
--- a/src/pages/workspace/accounting/xero/advanced/XeroAdvancedPage.tsx
+++ b/src/pages/workspace/accounting/xero/advanced/XeroAdvancedPage.tsx
@@ -28,7 +28,7 @@ function XeroAdvancedPage({policy}: WithPolicyConnectionsProps) {
const getSelectedAccountName = useMemo(
() => (accountID: string) => {
const selectedAccount = (bankAccounts ?? []).find((bank) => bank.id === accountID);
- return selectedAccount?.name ?? '';
+ return selectedAccount?.name ?? bankAccounts?.[0]?.name ?? '';
},
[bankAccounts],
);
@@ -47,6 +47,7 @@ function XeroAdvancedPage({policy}: WithPolicyConnectionsProps) {
policyID={policyID}
featureName={CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED}
contentContainerStyle={[styles.pb2, styles.ph5]}
+ connectionName={CONST.POLICY.CONNECTIONS.NAME.XERO}
>
(
- () =>
- (bankAccounts ?? []).map(({id, name}) => ({
- value: id,
- text: name,
- keyForList: id,
- isSelected: reimbursementAccountID === id,
- })),
- [reimbursementAccountID, bankAccounts],
- );
+ const xeroSelectorOptions = useMemo(() => getXeroBankAccountsWithDefaultSelect(policy ?? undefined, reimbursementAccountID), [reimbursementAccountID, policy]);
const listHeaderComponent = useMemo(
() => (
@@ -62,6 +52,7 @@ function XeroBillPaymentAccountSelectorPage({policy}: WithPolicyConnectionsProps
displayName={XeroBillPaymentAccountSelectorPage.displayName}
sections={[{data: xeroSelectorOptions}]}
listItem={RadioListItem}
+ connectionName={CONST.POLICY.CONNECTIONS.NAME.XERO}
shouldBeBlocked={!syncReimbursedReports}
onSelectRow={updateAccount}
initiallyFocusedOptionKey={initiallyFocusedOptionKey}
diff --git a/src/pages/workspace/accounting/xero/advanced/XeroInvoiceAccountSelectorPage.tsx b/src/pages/workspace/accounting/xero/advanced/XeroInvoiceAccountSelectorPage.tsx
index ba7749fef4f2..c80e604b5c81 100644
--- a/src/pages/workspace/accounting/xero/advanced/XeroInvoiceAccountSelectorPage.tsx
+++ b/src/pages/workspace/accounting/xero/advanced/XeroInvoiceAccountSelectorPage.tsx
@@ -8,6 +8,7 @@ import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import * as Connections from '@libs/actions/connections';
import Navigation from '@libs/Navigation/Navigation';
+import {getXeroBankAccountsWithDefaultSelect} from '@libs/PolicyUtils';
import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections';
import withPolicyConnections from '@pages/workspace/withPolicyConnections';
import CONST from '@src/CONST';
@@ -18,20 +19,9 @@ function XeroInvoiceAccountSelectorPage({policy}: WithPolicyConnectionsProps) {
const {translate} = useLocalize();
const policyID = policy?.id ?? '';
- const {bankAccounts} = policy?.connections?.xero?.data ?? {};
const {invoiceCollectionsAccountID, syncReimbursedReports} = policy?.connections?.xero?.config.sync ?? {};
-
- const xeroSelectorOptions = useMemo(
- () =>
- (bankAccounts ?? []).map(({id, name}) => ({
- value: id,
- text: name,
- keyForList: id,
- isSelected: invoiceCollectionsAccountID === id,
- })),
- [invoiceCollectionsAccountID, bankAccounts],
- );
+ const xeroSelectorOptions = useMemo(() => getXeroBankAccountsWithDefaultSelect(policy ?? undefined, invoiceCollectionsAccountID), [invoiceCollectionsAccountID, policy]);
const listHeaderComponent = useMemo(
() => (
@@ -62,6 +52,7 @@ function XeroInvoiceAccountSelectorPage({policy}: WithPolicyConnectionsProps) {
displayName={XeroInvoiceAccountSelectorPage.displayName}
sections={[{data: xeroSelectorOptions}]}
listItem={RadioListItem}
+ connectionName={CONST.POLICY.CONNECTIONS.NAME.XERO}
shouldBeBlocked={!syncReimbursedReports}
onSelectRow={updateAccount}
initiallyFocusedOptionKey={initiallyFocusedOptionKey}
diff --git a/src/pages/workspace/accounting/xero/export/XeroBankAccountSelectPage.tsx b/src/pages/workspace/accounting/xero/export/XeroBankAccountSelectPage.tsx
index 897ed0b37d78..508af9a89fa6 100644
--- a/src/pages/workspace/accounting/xero/export/XeroBankAccountSelectPage.tsx
+++ b/src/pages/workspace/accounting/xero/export/XeroBankAccountSelectPage.tsx
@@ -8,6 +8,7 @@ import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import * as Connections from '@libs/actions/connections';
import Navigation from '@libs/Navigation/Navigation';
+import {getXeroBankAccountsWithDefaultSelect} from '@libs/PolicyUtils';
import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections';
import withPolicyConnections from '@pages/workspace/withPolicyConnections';
import CONST from '@src/CONST';
@@ -18,20 +19,9 @@ function XeroBankAccountSelectPage({policy}: WithPolicyConnectionsProps) {
const {translate} = useLocalize();
const policyID = policy?.id ?? '';
- const {bankAccounts} = policy?.connections?.xero?.data ?? {};
const {nonReimbursableAccount: nonReimbursableAccountID} = policy?.connections?.xero?.config.export ?? {};
-
- const xeroSelectorOptions = useMemo(
- () =>
- (bankAccounts ?? []).map(({id, name}) => ({
- value: id,
- text: name,
- keyForList: id,
- isSelected: nonReimbursableAccountID === id,
- })),
- [nonReimbursableAccountID, bankAccounts],
- );
+ const xeroSelectorOptions = useMemo(() => getXeroBankAccountsWithDefaultSelect(policy ?? undefined, nonReimbursableAccountID), [nonReimbursableAccountID, policy]);
const listHeaderComponent = useMemo(
() => (
@@ -69,6 +59,7 @@ function XeroBankAccountSelectPage({policy}: WithPolicyConnectionsProps) {
headerContent={listHeaderComponent}
onBackButtonPress={() => Navigation.goBack(ROUTES.POLICY_ACCOUNTING_XERO_EXPORT.getRoute(policyID))}
title="workspace.xero.xeroBankAccount"
+ connectionName={CONST.POLICY.CONNECTIONS.NAME.XERO}
/>
);
}
diff --git a/src/pages/workspace/accounting/xero/export/XeroExportConfigurationPage.tsx b/src/pages/workspace/accounting/xero/export/XeroExportConfigurationPage.tsx
index fd0213ed0f89..4de19a4689b0 100644
--- a/src/pages/workspace/accounting/xero/export/XeroExportConfigurationPage.tsx
+++ b/src/pages/workspace/accounting/xero/export/XeroExportConfigurationPage.tsx
@@ -26,7 +26,7 @@ function XeroExportConfigurationPage({policy}: WithPolicyConnectionsProps) {
const {bankAccounts} = policy?.connections?.xero?.data ?? {};
const selectedBankAccountName = useMemo(() => {
const selectedAccount = (bankAccounts ?? []).find((bank) => bank.id === exportConfiguration?.nonReimbursableAccount);
- return selectedAccount?.name ?? '';
+ return selectedAccount?.name ?? bankAccounts?.[0]?.name ?? '';
}, [bankAccounts, exportConfiguration?.nonReimbursableAccount]);
const currentXeroOrganizationName = useMemo(() => getCurrentXeroOrganizationName(policy ?? undefined), [policy]);
@@ -59,7 +59,7 @@ function XeroExportConfigurationPage({policy}: WithPolicyConnectionsProps) {
},
{
description: translate('workspace.xero.advancedConfig.purchaseBillStatusTitle'),
- onPress: () => {},
+ onPress: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_XERO_BILL_STATUS_SELECTOR.getRoute(policyID)),
title: exportConfiguration?.billStatus?.purchase ? translate(`workspace.xero.invoiceStatus.values.${exportConfiguration.billStatus.purchase}`) : undefined,
pendingAction: pendingFields?.export,
errorText: errorFields?.purchase ? translate('common.genericErrorMessage') : undefined,
@@ -99,6 +99,7 @@ function XeroExportConfigurationPage({policy}: WithPolicyConnectionsProps) {
featureName={CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED}
contentContainerStyle={styles.pb2}
titleStyle={styles.ph5}
+ connectionName={CONST.POLICY.CONNECTIONS.NAME.XERO}
>
{menuItems.map((menuItem) => (
{
@@ -38,18 +40,26 @@ function XeroPreferredExporterSelectPage({policy}: WithPolicyConnectionsProps) {
},
];
}
- return exporters?.reduce((vendors, vendor) => {
- if (vendor.email) {
- vendors.push({
- value: vendor.email,
- text: vendor.email,
- keyForList: vendor.email,
- isSelected: exportConfiguration?.exporter === vendor.email,
- });
+
+ return exporters?.reduce((options, exporter) => {
+ if (!exporter.email) {
+ return options;
+ }
+
+ // Don't show guides if the current user is not a guide themselves or an Expensify employee
+ if (isExpensifyTeam(exporter.email) && !isExpensifyTeam(policyOwner) && !isExpensifyTeam(currentUserLogin)) {
+ return options;
}
- return vendors;
+
+ options.push({
+ value: exporter.email,
+ text: exporter.email,
+ keyForList: exporter.email,
+ isSelected: exportConfiguration?.exporter === exporter.email,
+ });
+ return options;
}, []);
- }, [exportConfiguration, exporters, policyOwner]);
+ }, [exportConfiguration, exporters, policyOwner, currentUserLogin]);
const selectExporter = useCallback(
(row: CardListItem) => {
@@ -84,6 +94,7 @@ function XeroPreferredExporterSelectPage({policy}: WithPolicyConnectionsProps) {
initiallyFocusedOptionKey={data.find((mode) => mode.isSelected)?.keyForList}
onBackButtonPress={() => Navigation.goBack(ROUTES.POLICY_ACCOUNTING_XERO_EXPORT.getRoute(policyID))}
title="workspace.xero.preferredExporter"
+ connectionName={CONST.POLICY.CONNECTIONS.NAME.XERO}
/>
);
}
diff --git a/src/pages/workspace/accounting/xero/export/XeroPurchaseBillDateSelectPage.tsx b/src/pages/workspace/accounting/xero/export/XeroPurchaseBillDateSelectPage.tsx
index 2060e48fe987..51ae82d79e3a 100644
--- a/src/pages/workspace/accounting/xero/export/XeroPurchaseBillDateSelectPage.tsx
+++ b/src/pages/workspace/accounting/xero/export/XeroPurchaseBillDateSelectPage.tsx
@@ -64,6 +64,7 @@ function XeroPurchaseBillDateSelectPage({policy}: WithPolicyConnectionsProps) {
accessVariants={[CONST.POLICY.ACCESS_VARIANTS.ADMIN]}
featureName={CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED}
onBackButtonPress={() => Navigation.goBack(ROUTES.POLICY_ACCOUNTING_XERO_EXPORT.getRoute(policyID))}
+ connectionName={CONST.POLICY.CONNECTIONS.NAME.XERO}
/>
);
}
diff --git a/src/pages/workspace/accounting/xero/export/XeroPurchaseBillStatusSelectorPage.tsx b/src/pages/workspace/accounting/xero/export/XeroPurchaseBillStatusSelectorPage.tsx
new file mode 100644
index 000000000000..0fbf1ff5c285
--- /dev/null
+++ b/src/pages/workspace/accounting/xero/export/XeroPurchaseBillStatusSelectorPage.tsx
@@ -0,0 +1,79 @@
+import {isEmpty} from 'lodash';
+import React, {useCallback, useMemo} from 'react';
+import {View} from 'react-native';
+import type {ValueOf} from 'type-fest';
+import RadioListItem from '@components/SelectionList/RadioListItem';
+import type {ListItem} from '@components/SelectionList/types';
+import SelectionScreen from '@components/SelectionScreen';
+import type {SelectorType} from '@components/SelectionScreen';
+import Text from '@components/Text';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import * as Connections from '@libs/actions/connections';
+import Navigation from '@navigation/Navigation';
+import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections';
+import withPolicyConnections from '@pages/workspace/withPolicyConnections';
+import CONST from '@src/CONST';
+import ROUTES from '@src/ROUTES';
+
+type MenuListItem = ListItem & {
+ value: ValueOf;
+};
+
+function XeroPurchaseBillStatusSelectorPage({policy}: WithPolicyConnectionsProps) {
+ const {translate} = useLocalize();
+ const policyID = policy?.id ?? '';
+ const styles = useThemeStyles();
+ const {billStatus} = policy?.connections?.xero?.config?.export ?? {};
+ const invoiceStatus = billStatus?.purchase;
+
+ const data: MenuListItem[] = Object.values(CONST.XERO_CONFIG.INVOICE_STATUS).map((status) => ({
+ value: status,
+ text: translate(`workspace.xero.invoiceStatus.values.${status}`),
+ keyForList: status,
+ isSelected: invoiceStatus === status,
+ }));
+
+ const headerContent = useMemo(
+ () => (
+
+ {translate('workspace.xero.invoiceStatus.description')}
+
+ ),
+ [translate, styles.pb5, styles.ph5],
+ );
+
+ const selectPurchaseBillStatus = useCallback(
+ (row: MenuListItem) => {
+ if (isEmpty(billStatus)) {
+ return;
+ }
+ if (row.value !== invoiceStatus) {
+ Connections.updatePolicyConnectionConfig(policyID, CONST.POLICY.CONNECTIONS.NAME.XERO, CONST.XERO_CONFIG.EXPORT, {billStatus: {...billStatus, purchase: row.value}});
+ }
+ Navigation.goBack(ROUTES.POLICY_ACCOUNTING_XERO_BILL_STATUS_SELECTOR.getRoute(policyID));
+ },
+ [billStatus, invoiceStatus, policyID],
+ );
+
+ return (
+ selectPurchaseBillStatus(selection as MenuListItem)}
+ initiallyFocusedOptionKey={data.find((mode) => mode.isSelected)?.keyForList}
+ policyID={policyID}
+ accessVariants={[CONST.POLICY.ACCESS_VARIANTS.ADMIN]}
+ featureName={CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED}
+ onBackButtonPress={() => Navigation.goBack(ROUTES.POLICY_ACCOUNTING_XERO_EXPORT.getRoute(policyID))}
+ connectionName={CONST.POLICY.CONNECTIONS.NAME.XERO}
+ />
+ );
+}
+
+XeroPurchaseBillStatusSelectorPage.displayName = 'XeroPurchaseBillStatusSelectorPage';
+
+export default withPolicyConnections(XeroPurchaseBillStatusSelectorPage);
diff --git a/src/pages/workspace/accounting/xero/import/XeroChartOfAccountsPage.tsx b/src/pages/workspace/accounting/xero/import/XeroChartOfAccountsPage.tsx
index c3df12960150..92915364932a 100644
--- a/src/pages/workspace/accounting/xero/import/XeroChartOfAccountsPage.tsx
+++ b/src/pages/workspace/accounting/xero/import/XeroChartOfAccountsPage.tsx
@@ -30,6 +30,7 @@ function XeroChartOfAccountsPage({policy}: WithPolicyProps) {
policyID={policyID}
featureName={CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED}
contentContainerStyle={[styles.pb2, styles.ph5]}
+ connectionName={CONST.POLICY.CONNECTIONS.NAME.XERO}
>
diff --git a/src/pages/workspace/accounting/xero/import/XeroCustomerConfigurationPage.tsx b/src/pages/workspace/accounting/xero/import/XeroCustomerConfigurationPage.tsx
index 19ca447ed4a6..dacdb9c288a1 100644
--- a/src/pages/workspace/accounting/xero/import/XeroCustomerConfigurationPage.tsx
+++ b/src/pages/workspace/accounting/xero/import/XeroCustomerConfigurationPage.tsx
@@ -27,6 +27,7 @@ function XeroCustomerConfigurationPage({policy}: WithPolicyProps) {
policyID={policyID}
featureName={CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED}
contentContainerStyle={[[styles.pb2, styles.ph5]]}
+ connectionName={CONST.POLICY.CONNECTIONS.NAME.XERO}
>
0;
+ const isConnectedToQbo = Boolean(policy?.connections?.quickbooksOnline);
const fetchCategories = useCallback(() => {
Category.openPolicyCategoriesPage(policyId);
@@ -245,15 +247,16 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) {
const getHeaderText = () => (
- {Object.keys(policy?.connections ?? {}).length > 0 ? (
+ {isConnectedToAccounting ? (
{`${translate('workspace.categories.importedFromAccountingSoftware')} `}
- {`${translate('workspace.accounting.qbo')} ${translate('workspace.accounting.settings')}`}
+ {`${translate(isConnectedToQbo ? 'workspace.accounting.qbo' : 'workspace.accounting.xero')} ${translate('workspace.accounting.settings')}`}
+ .
) : (
{translate('workspace.categories.subtitle')}
@@ -292,7 +295,7 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) {
danger
/>
{isSmallScreenWidth && {getHeaderButtons()}}
- {!isSmallScreenWidth && getHeaderText()}
+ {(!isSmallScreenWidth || shouldShowEmptyState || isLoading) && getHeaderText()}
{isLoading && (
) => {
const errors: FormInputErrors = {};
const tagName = values.tagName.trim();
- const {tags} = PolicyUtils.getTagList(policyTags, 0);
+ const {tags} = PolicyUtils.getTagList(policyTags, route.params.orderWeight);
if (!ValidationUtils.isRequiredFulfilled(tagName)) {
errors.tagName = 'workspace.tags.tagRequiredError';
} else if (tags?.[tagName] && currentTagName !== tagName) {
@@ -50,7 +50,7 @@ function EditTagPage({route, policyTags}: EditTagPageProps) {
return errors;
},
- [currentTagName, policyTags],
+ [route.params.orderWeight, currentTagName, policyTags],
);
const editTag = useCallback(
diff --git a/src/pages/workspace/tags/TagSettingsPage.tsx b/src/pages/workspace/tags/TagSettingsPage.tsx
index 646b0d20e9fa..a4f6940814dd 100644
--- a/src/pages/workspace/tags/TagSettingsPage.tsx
+++ b/src/pages/workspace/tags/TagSettingsPage.tsx
@@ -38,7 +38,7 @@ type TagSettingsPageProps = TagSettingsPageOnyxProps & StackScreenProps PolicyUtils.getTagList(policyTags, 0), [policyTags]);
+ const policyTag = useMemo(() => PolicyUtils.getTagList(policyTags, route.params.orderWeight), [policyTags, route.params.orderWeight]);
const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${route.params.policyID}`);
const {windowWidth} = useWindowDimensions();
@@ -66,11 +66,11 @@ function TagSettingsPage({route, policyTags, navigation}: TagSettingsPageProps)
};
const updateWorkspaceTagEnabled = (value: boolean) => {
- setWorkspaceTagEnabled(route.params.policyID, {[currentPolicyTag.name]: {name: currentPolicyTag.name, enabled: value}});
+ setWorkspaceTagEnabled(route.params.policyID, {[currentPolicyTag.name]: {name: currentPolicyTag.name, enabled: value}}, policyTag.orderWeight);
};
const navigateToEditTag = () => {
- Navigation.navigate(ROUTES.WORKSPACE_TAG_EDIT.getRoute(route.params.policyID, currentPolicyTag.name));
+ Navigation.navigate(ROUTES.WORKSPACE_TAG_EDIT.getRoute(route.params.policyID, route.params.orderWeight, currentPolicyTag.name));
};
const isThereAnyAccountingConnection = Object.keys(policy?.connections ?? {}).length !== 0;
diff --git a/src/pages/workspace/tags/WorkspaceTagsPage.tsx b/src/pages/workspace/tags/WorkspaceTagsPage.tsx
index 84640f63b063..c31dc223494a 100644
--- a/src/pages/workspace/tags/WorkspaceTagsPage.tsx
+++ b/src/pages/workspace/tags/WorkspaceTagsPage.tsx
@@ -52,6 +52,7 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) {
const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`);
const {environmentURL} = useEnvironment();
const isConnectedToAccounting = Object.keys(policy?.connections ?? {}).length > 0;
+ const isConnectedToQbo = Boolean(policy?.connections?.quickbooksOnline);
const [policyTagLists, isMultiLevelTags] = useMemo(() => [PolicyUtils.getTagLists(policyTags), PolicyUtils.isMultiLevelTags(policyTags)], [policyTags]);
const canSelectMultiple = !isMultiLevelTags;
@@ -155,11 +156,11 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) {
};
const navigateToTagSettings = (tag: TagListItem) => {
- if (tag.orderWeight != null) {
+ if (tag.orderWeight !== undefined) {
Navigation.navigate(ROUTES.WORKSPACE_TAG_LIST_VIEW.getRoute(policyID, tag.orderWeight));
return;
}
- Navigation.navigate(ROUTES.WORKSPACE_TAG_SETTINGS.getRoute(policyID, tag.value));
+ Navigation.navigate(ROUTES.WORKSPACE_TAG_SETTINGS.getRoute(policyID, 0, tag.value));
};
const selectedTagsArray = Object.keys(selectedTags).filter((key) => selectedTags[key]);
@@ -239,7 +240,7 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) {
value: CONST.POLICY.TAGS_BULK_ACTION_TYPES.DISABLE,
onSelected: () => {
setSelectedTags({});
- Tag.setWorkspaceTagEnabled(policyID, tagsToDisable);
+ Tag.setWorkspaceTagEnabled(policyID, tagsToDisable, 0);
},
});
}
@@ -251,7 +252,7 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) {
value: CONST.POLICY.TAGS_BULK_ACTION_TYPES.ENABLE,
onSelected: () => {
setSelectedTags({});
- Tag.setWorkspaceTagEnabled(policyID, tagsToEnable);
+ Tag.setWorkspaceTagEnabled(policyID, tagsToEnable, 0);
},
});
}
@@ -279,8 +280,9 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) {
style={[styles.textNormal, styles.link]}
href={`${environmentURL}/${ROUTES.POLICY_ACCOUNTING.getRoute(policyID)}`}
>
- {`${translate('workspace.accounting.qbo')} ${translate('workspace.accounting.settings')}`}
+ {`${translate(isConnectedToQbo ? 'workspace.accounting.qbo' : 'workspace.accounting.xero')} ${translate('workspace.accounting.settings')}`}
+ .
) : (
{translate('workspace.tags.subtitle')}
@@ -319,7 +321,7 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) {
cancelText={translate('common.cancel')}
danger
/>
- {!isSmallScreenWidth && getHeaderText()}
+ {(!isSmallScreenWidth || tagList.length === 0 || isLoading) && getHeaderText()}
{isLoading && (
{
- Navigation.navigate(ROUTES.WORKSPACE_TAG_SETTINGS.getRoute(policyID, tag.value));
+ Navigation.navigate(ROUTES.WORKSPACE_TAG_SETTINGS.getRoute(policyID, route.params.orderWeight, tag.value));
};
const selectedTagsArray = Object.keys(selectedTags).filter((key) => selectedTags[key]);
@@ -175,7 +175,7 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) {
value: CONST.POLICY.TAGS_BULK_ACTION_TYPES.DISABLE,
onSelected: () => {
setSelectedTags({});
- Tag.setWorkspaceTagEnabled(policyID, tagsToDisable);
+ Tag.setWorkspaceTagEnabled(policyID, tagsToDisable, route.params.orderWeight);
},
});
}
@@ -187,7 +187,7 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) {
value: CONST.POLICY.TAGS_BULK_ACTION_TYPES.ENABLE,
onSelected: () => {
setSelectedTags({});
- Tag.setWorkspaceTagEnabled(policyID, tagsToEnable);
+ Tag.setWorkspaceTagEnabled(policyID, tagsToEnable, route.params.orderWeight);
},
});
}
diff --git a/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx b/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx
index 8ccff7b4e126..c7f3c50489e7 100644
--- a/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx
+++ b/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx
@@ -86,7 +86,7 @@ function WorkspaceEditTaxPage({
([]);
const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false);
const defaultExternalID = policy?.taxRates?.defaultExternalID;
const foreignTaxDefault = policy?.taxRates?.foreignTaxDefault;
const isFocused = useIsFocused();
+ const isConnectedToAccounting = Object.keys(policy?.connections ?? {}).length > 0;
+ const isConnectedToQbo = Boolean(policy?.connections?.quickbooksOnline);
+
const fetchTaxes = useCallback(() => {
openPolicyTaxesPage(policyID);
}, [policyID]);
@@ -171,14 +177,15 @@ function WorkspaceTaxesPage({
const dropdownMenuOptions = useMemo(() => {
const isMultiple = selectedTaxesIDs.length > 1;
- const options: Array> = [
- {
+ const options: Array> = [];
+ if (!PolicyUtils.hasAccountingConnections(policy)) {
+ options.push({
icon: Expensicons.Trashcan,
text: isMultiple ? translate('workspace.taxes.actions.deleteMultiple') : translate('workspace.taxes.actions.delete'),
value: CONST.POLICY.TAX_RATES_BULK_ACTION_TYPES.DELETE,
onSelected: () => setIsDeleteModalVisible(true),
- },
- ];
+ });
+ }
// `Disable rates` when at least one enabled rate is selected.
if (selectedTaxesIDs.some((taxID) => !policy?.taxRates?.taxes[taxID]?.isDisabled)) {
@@ -200,18 +207,20 @@ function WorkspaceTaxesPage({
});
}
return options;
- }, [policy?.taxRates?.taxes, selectedTaxesIDs, toggleTaxes, translate]);
+ }, [policy, selectedTaxesIDs, toggleTaxes, translate]);
const headerButtons = !selectedTaxesIDs.length ? (
-
- Navigation.navigate(ROUTES.WORKSPACE_TAX_CREATE.getRoute(policyID))}
- icon={Expensicons.Plus}
- text={translate('workspace.taxes.addRate')}
- style={[isSmallScreenWidth && styles.flex1]}
- />
+
+ {!PolicyUtils.hasAccountingConnections(policy) && (
+ Navigation.navigate(ROUTES.WORKSPACE_TAX_CREATE.getRoute(policyID))}
+ icon={Expensicons.Plus}
+ text={translate('workspace.taxes.addRate')}
+ style={[styles.mr3, isSmallScreenWidth && styles.flex1]}
+ />
+ )}
Navigation.navigate(ROUTES.WORKSPACE_TAXES_SETTINGS.getRoute(policyID))}
@@ -235,7 +244,20 @@ function WorkspaceTaxesPage({
const getHeaderText = () => (
- {translate('workspace.taxes.subtitle')}
+ {isConnectedToAccounting ? (
+
+ {`${translate('workspace.taxes.importedFromAccountingSoftware')} `}
+
+ {`${translate(isConnectedToQbo ? 'workspace.accounting.qbo' : 'workspace.accounting.xero')} ${translate('workspace.accounting.settings')}`}
+
+ .
+
+ ) : (
+ {translate('workspace.taxes.subtitle')}
+ )}
);
@@ -259,6 +281,7 @@ function WorkspaceTaxesPage({
{!isSmallScreenWidth && headerButtons}
{isSmallScreenWidth && {headerButtons}}
+
{!isSmallScreenWidth && getHeaderText()}
{isLoading && (
void;
/** Whether the toggle should be disabled */
disabled?: boolean;
+
+ /** Whether to show the lock icon even if the switch is enabled */
+ showLockIcon?: boolean;
};
const ICON_SIZE = 48;
@@ -53,6 +56,7 @@ function ToggleSettingOptionRow({
errors,
onCloseError,
disabled = false,
+ showLockIcon = false,
}: ToggleSettingOptionRowProps) {
const styles = useThemeStyles();
@@ -90,6 +94,7 @@ function ToggleSettingOptionRow({
onToggle={onToggle}
isOn={isActive}
disabled={disabled}
+ showLockIcon={showLockIcon}
/>
{shouldPlaceSubtitleBelowSwitch && subtitle && subTitleView}
diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts
index 7fe8d4f000bc..82602efa88b3 100644
--- a/src/styles/utils/index.ts
+++ b/src/styles/utils/index.ts
@@ -1324,7 +1324,7 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({
case CONST.BUTTON_STATES.PRESSED:
return {backgroundColor: theme.buttonPressedBG};
case CONST.BUTTON_STATES.ACTIVE:
- return isMenuItem ? {backgroundColor: theme.hoverComponentBG} : {backgroundColor: theme.buttonHoveredBG};
+ return isMenuItem ? {backgroundColor: theme.border} : {backgroundColor: theme.buttonHoveredBG};
case CONST.BUTTON_STATES.DISABLED:
case CONST.BUTTON_STATES.DEFAULT:
default:
diff --git a/tests/actions/PolicyTagTest.ts b/tests/actions/PolicyTagTest.ts
index 102466b7ed9f..ad473633d876 100644
--- a/tests/actions/PolicyTagTest.ts
+++ b/tests/actions/PolicyTagTest.ts
@@ -395,7 +395,7 @@ describe('actions/Policy', () => {
Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakePolicyTags);
})
.then(() => {
- Tag.setWorkspaceTagEnabled(fakePolicy.id, tagsToUpdate);
+ Tag.setWorkspaceTagEnabled(fakePolicy.id, tagsToUpdate, 0);
return waitForBatchedUpdates();
})
.then(
@@ -468,7 +468,7 @@ describe('actions/Policy', () => {
.then(() => {
mockFetch?.fail?.();
- Tag.setWorkspaceTagEnabled(fakePolicy.id, tagsToUpdate);
+ Tag.setWorkspaceTagEnabled(fakePolicy.id, tagsToUpdate, 0);
return waitForBatchedUpdates();
})
.then(mockFetch?.resume)