diff --git a/android/app/build.gradle b/android/app/build.gradle index 6119c8be71bf..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 1001047704 - versionName "1.4.77-4" + 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/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 2b5cd5d9ad51..01d7c0869775 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.77.4 + 1.4.77.8 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index a3753985f0b7..40249a9864f8 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.77.4 + 1.4.77.8 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 21fdec5da6a5..8e786dee39a1 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 1.4.77 CFBundleVersion - 1.4.77.4 + 1.4.77.8 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index e8a3ee6dab28..d9cb3daafff5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.77-4", + "version": "1.4.77-8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.77-4", + "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 dfaeea3c0729..b3a31ab149a8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.77-4", + "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 4665ec94625a..f9c1c02106ff 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -661,9 +661,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', @@ -1305,12 +1305,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', @@ -1595,6 +1596,9 @@ const CONST = { ACCOUNTANT: 'accountant', }, }, + ACCESS_VARIANTS: { + CREATE: 'create', + }, }, GROWL: { @@ -1775,7 +1779,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 903496458043..32d66e5a8a40 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -689,12 +689,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', @@ -812,17 +812,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', @@ -833,8 +830,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', @@ -848,6 +845,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..2b95449ffaab 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -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/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/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/MenuItem.tsx b/src/components/MenuItem.tsx index 6fd5b92f78d3..b6f378763659 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -153,9 +153,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; @@ -308,6 +314,8 @@ function MenuItem( helperText, helperTextStyle, errorText, + errorTextStyle, + shouldShowRedDotIndicator, hintText, success = false, focused = false, @@ -684,9 +692,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)) && ( `${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', @@ -2045,10 +2045,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.', @@ -2105,10 +2103,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.', @@ -2187,6 +2187,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', @@ -2242,6 +2248,7 @@ export default { enable: 'Enable rate', enableMultiple: 'Enable rates', }, + importedFromAccountingSoftware: 'The taxes below are imported from your', }, emptyWorkspace: { title: 'Create a workspace', @@ -2342,6 +2349,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', @@ -2398,7 +2416,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 d637c225632d..29f85edfc93e 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2077,10 +2077,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.', @@ -2140,10 +2138,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: @@ -2223,6 +2223,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', @@ -2278,6 +2284,7 @@ export default { enable: 'Activar tasa', enableMultiple: 'Activar tasas', }, + importedFromAccountingSoftware: 'Impuestos importadas desde', }, emptyWorkspace: { title: 'Crea un espacio de trabajo', @@ -2346,6 +2353,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', @@ -2402,7 +2420,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/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 462145e56907..da9081a2b46a 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -299,14 +299,16 @@ const SettingsModalStackNavigator = createModalStackNavigator 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/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 4a629e323687..541749276d25 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -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/types.ts b/src/libs/Navigation/types.ts index 3a7b8be32a01..2e0fdd540d11 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -221,6 +221,7 @@ type SettingsNavigatorParamList = { }; [SCREENS.WORKSPACE.TAG_SETTINGS]: { policyID: string; + orderWeight: number; tagName: string; }; [SCREENS.WORKSPACE.TAG_LIST_VIEW]: { @@ -233,6 +234,7 @@ type SettingsNavigatorParamList = { }; [SCREENS.WORKSPACE.TAG_EDIT]: { policyID: string; + orderWeight: number; tagName: string; }; [SCREENS.WORKSPACE.TAXES_SETTINGS]: { @@ -340,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; @@ -355,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; }; @@ -801,7 +805,6 @@ type FullScreenNavigatorParamList = { }; [SCREENS.WORKSPACE.TAGS]: { policyID: string; - tagName: string; }; [SCREENS.WORKSPACE.TAXES]: { policyID: string; diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index c77df2a9b03f..f0e1b7511aa7 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'; @@ -421,6 +422,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, @@ -470,6 +483,7 @@ export { getXeroTenants, findCurrentXeroOrganization, getCurrentXeroOrganizationName, + getXeroBankAccountsWithDefaultSelect, }; export type {MemberEmailsToAccountIDs}; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 8a59958c208b..03538dc7a860 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; } @@ -1714,7 +1715,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); @@ -1921,7 +1922,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, @@ -3216,6 +3217,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')})`; } @@ -3224,10 +3229,6 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu formattedName = getDisplayNameForParticipant(currentUserAccountID, undefined, undefined, true); } - if (isInvoiceRoom(report)) { - formattedName = getInvoicesChatName(report); - } - if (formattedName) { return formattedName; } @@ -3306,7 +3307,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 { @@ -5669,28 +5678,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; @@ -5714,6 +5720,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) { @@ -6634,8 +6644,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/iou/request/IOURequestStartPage.tsx b/src/pages/iou/request/IOURequestStartPage.tsx index 1bbf0d02a941..c1d55516b433 100644 --- a/src/pages/iou/request/IOURequestStartPage.tsx +++ b/src/pages/iou/request/IOURequestStartPage.tsx @@ -3,7 +3,6 @@ import React, {useCallback, useEffect, useRef, useState} from 'react'; import {View} from 'react-native'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import DragAndDropProvider from '@components/DragAndDrop/Provider'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; @@ -13,13 +12,12 @@ import useLocalize from '@hooks/useLocalize'; import usePermissions from '@hooks/usePermissions'; import useThemeStyles from '@hooks/useThemeStyles'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; -import * as IOUUtils from '@libs/IOUUtils'; import * as KeyDownPressListener from '@libs/KeyboardShortcut/KeyDownPressListener'; import Navigation from '@libs/Navigation/Navigation'; import OnyxTabNavigator, {TopTab} from '@libs/Navigation/OnyxTabNavigator'; -import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; +import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; import * as IOU from '@userActions/IOU'; import type {IOURequestType} from '@userActions/IOU'; import CONST from '@src/CONST'; @@ -105,9 +103,6 @@ function IOURequestStartPage({ const isExpenseReport = ReportUtils.isExpenseReport(report); const shouldDisplayDistanceRequest = (!!canUseP2PDistanceRequests || isExpenseChat || isExpenseReport || isFromGlobalCreate) && iouType !== CONST.IOU.TYPE.SPLIT; - // Allow the user to submit the expense if we are submitting the expense in global menu or the report can create the exoense - const isAllowedToCreateRequest = isEmptyObject(report?.reportID) || ReportUtils.canCreateRequest(report, policy, iouType) || PolicyUtils.canSendInvoice(allPolicies); - const navigateBack = () => { Navigation.closeRHPFlow(); }; @@ -126,15 +121,21 @@ function IOURequestStartPage({ } return ( - - {({safeAreaPaddingBottomStyle}) => ( - + + {({safeAreaPaddingBottomStyle}) => ( - - )} - + )} + + ); } diff --git a/src/pages/settings/ExitSurvey/ExitSurveyConfirmPage.tsx b/src/pages/settings/ExitSurvey/ExitSurveyConfirmPage.tsx index 7507e1015f86..672dbbb91069 100644 --- a/src/pages/settings/ExitSurvey/ExitSurveyConfirmPage.tsx +++ b/src/pages/settings/ExitSurvey/ExitSurveyConfirmPage.tsx @@ -87,7 +87,6 @@ function ExitSurveyConfirmPage({exitReason, isLoading, route, navigation}: ExitS ExitSurvey.switchToOldDot(); if (NativeModules.HybridAppModule) { - Navigation.resetToHome(); NativeModules.HybridAppModule.closeReactNativeApp(); return; } diff --git a/src/pages/workspace/AccessOrNotFoundWrapper.tsx b/src/pages/workspace/AccessOrNotFoundWrapper.tsx index cfabad927f4f..781cc2a65e58 100644 --- a/src/pages/workspace/AccessOrNotFoundWrapper.tsx +++ b/src/pages/workspace/AccessOrNotFoundWrapper.tsx @@ -1,15 +1,18 @@ /* 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 useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; +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'; @@ -18,13 +21,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, login: string) => PolicyUtils.isPolicyAdmin(policy, login), -} 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, login: string) => 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; @@ -36,11 +48,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; @@ -50,6 +65,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}; @@ -65,7 +86,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} /> @@ -73,9 +94,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 {login = ''} = useCurrentUserPersonalDetails(); const isPolicyIDInRoute = !!policyID?.length; + const isMoneyRequest = !!iouType && IOUUtils.isValidMoneyRequestType(iouType); + const isFromGlobalCreate = isEmptyObject(report?.reportID); useEffect(() => { if (!isPolicyIDInRoute || !isEmptyObject(policy)) { @@ -87,17 +110,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, login); + const accessFunction = ACCESS_VARIANTS[variant]; + return acc && accessFunction(policy, report, allPolicies ?? null, iouType, login); }, 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 ; @@ -116,11 +139,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 ? ( - -