diff --git a/.github/actions/javascript/getDeployPullRequestList/getDeployPullRequestList.ts b/.github/actions/javascript/getDeployPullRequestList/getDeployPullRequestList.ts index ecf242f00cc2..08519c40413b 100644 --- a/.github/actions/javascript/getDeployPullRequestList/getDeployPullRequestList.ts +++ b/.github/actions/javascript/getDeployPullRequestList/getDeployPullRequestList.ts @@ -21,7 +21,27 @@ async function run() { status: 'completed', event: isProductionDeploy ? 'release' : 'push', }) - ).data.workflow_runs; + ).data.workflow_runs + // Note: we filter out cancelled runs instead of looking only for success runs + // because if a build fails on even one platform, then it will have the status 'failure' + .filter((workflowRun) => workflowRun.conclusion !== 'cancelled'); + + // Find the most recent deploy workflow for which at least one of the build jobs finished successfully. + let lastSuccessfulDeploy = completedDeploys.shift(); + while ( + lastSuccessfulDeploy && + !( + await GithubUtils.octokit.actions.listJobsForWorkflowRun({ + owner: github.context.repo.owner, + repo: github.context.repo.repo, + // eslint-disable-next-line @typescript-eslint/naming-convention + run_id: lastSuccessfulDeploy.id, + filter: 'latest', + }) + ).data.jobs.some((job) => job.name.startsWith('Build and deploy') && job.conclusion === 'success') + ) { + lastSuccessfulDeploy = completedDeploys.shift(); + } const priorTag = completedDeploys[0].head_branch; console.log(`Looking for PRs deployed to ${deployEnv} between ${priorTag} and ${inputTag}`); diff --git a/.github/actions/javascript/getDeployPullRequestList/index.js b/.github/actions/javascript/getDeployPullRequestList/index.js index 6b956f17be25..cfe512076ecd 100644 --- a/.github/actions/javascript/getDeployPullRequestList/index.js +++ b/.github/actions/javascript/getDeployPullRequestList/index.js @@ -11515,7 +11515,22 @@ async function run() { workflow_id: 'platformDeploy.yml', status: 'completed', event: isProductionDeploy ? 'release' : 'push', - })).data.workflow_runs; + })).data.workflow_runs + // Note: we filter out cancelled runs instead of looking only for success runs + // because if a build fails on even one platform, then it will have the status 'failure' + .filter((workflowRun) => workflowRun.conclusion !== 'cancelled'); + // Find the most recent deploy workflow for which at least one of the build jobs finished successfully. + let lastSuccessfulDeploy = completedDeploys.shift(); + while (lastSuccessfulDeploy && + !(await GithubUtils_1.default.octokit.actions.listJobsForWorkflowRun({ + owner: github.context.repo.owner, + repo: github.context.repo.repo, + // eslint-disable-next-line @typescript-eslint/naming-convention + run_id: lastSuccessfulDeploy.id, + filter: 'latest', + })).data.jobs.some((job) => job.name.startsWith('Build and deploy') && job.conclusion === 'success')) { + lastSuccessfulDeploy = completedDeploys.shift(); + } const priorTag = completedDeploys[0].head_branch; console.log(`Looking for PRs deployed to ${deployEnv} between ${priorTag} and ${inputTag}`); const prList = await GitUtils_1.default.getPullRequestsMergedBetween(priorTag ?? '', inputTag); diff --git a/.github/workflows/e2ePerformanceTests.yml b/.github/workflows/e2ePerformanceTests.yml index 7e7d55ac5d2e..ffce73644263 100644 --- a/.github/workflows/e2ePerformanceTests.yml +++ b/.github/workflows/e2ePerformanceTests.yml @@ -184,6 +184,9 @@ jobs: - name: Copy e2e code into zip folder run: cp tests/e2e/dist/index.js zip/testRunner.ts + + - name: Copy profiler binaries into zip folder + run: cp -r node_modules/@perf-profiler/android/cpp-profiler/bin zip/bin - name: Zip everything in the zip directory up run: zip -qr App.zip ./zip diff --git a/Gemfile.lock b/Gemfile.lock index 3780235053ad..64f4d81c9e76 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -10,8 +10,8 @@ GEM i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - addressable (2.8.6) - public_suffix (>= 2.0.2, < 6.0) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) @@ -20,17 +20,17 @@ GEM artifactory (3.0.17) atomos (0.1.3) aws-eventstream (1.3.0) - aws-partitions (1.944.0) - aws-sdk-core (3.197.0) + aws-partitions (1.948.0) + aws-sdk-core (3.199.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.85.0) - aws-sdk-core (~> 3, >= 3.197.0) + aws-sdk-kms (1.87.0) + aws-sdk-core (~> 3, >= 3.199.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.152.3) - aws-sdk-core (~> 3, >= 3.197.0) + aws-sdk-s3 (1.154.0) + aws-sdk-core (~> 3, >= 3.199.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.8) aws-sigv4 (1.8.0) @@ -119,7 +119,7 @@ GEM faraday_middleware (1.2.0) faraday (~> 1.0) fastimage (2.3.1) - fastlane (2.221.0) + fastlane (2.221.1) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) diff --git a/android/app/build.gradle b/android/app/build.gradle index ea7e6bdfce45..4d15eb49cf2a 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 1009000203 - versionName "9.0.2-3" + versionCode 1009000302 + versionName "9.0.3-2" // 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/assets/images/computer.svg b/assets/images/computer.svg new file mode 100644 index 000000000000..9c2628245eb1 --- /dev/null +++ b/assets/images/computer.svg @@ -0,0 +1,216 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/integrationicons/sage-intacct-icon-square.svg b/assets/images/integrationicons/sage-intacct-icon-square.svg new file mode 100644 index 000000000000..33d86259a2d1 --- /dev/null +++ b/assets/images/integrationicons/sage-intacct-icon-square.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/articles/new-expensify/expensify-card/Enable-Expensify-Card-notifications.md b/docs/articles/new-expensify/expensify-card/Enable-Expensify-Card-notifications.md new file mode 100644 index 000000000000..4bb56b1cc54c --- /dev/null +++ b/docs/articles/new-expensify/expensify-card/Enable-Expensify-Card-notifications.md @@ -0,0 +1,57 @@ +--- +title: Enable Expensify Card notifications +description: Allow notifications from Expensify +--- +
+ +The Expensify mobile app sends you real-time notifications for spending activity on your Expensify Visa® Commercial Card, including +- Purchase notifications, including declined payments +- Fraudulent activity alerts +- Requests for purchases that require a SmartScanned receipt + +There are two steps to enable Expensify Card notifications. You’ll first enable alerts on your workspace, then you’ll enable notifications on your device. + +# Step 1: Enable alerts on your workspace + +{% include selector.html values="desktop, mobile" %} + +{% include option.html value="desktop" %} +1. From your Expensify Chat inbox, click the dropdown on the logo or avatar that is in the top left corner. +2. Select the workspace you want to update the notification settings for. +3. Click the workspace chat in your inbox (it will be the chat that has your workspace’s name as the chat title). +4. Click the header at the top of the chat. +5. Click **Settings**. +6. Click **Notify me about new messages** and select **Immediately**. +{% include end-option.html %} + +{% include option.html value="mobile" %} +1. From your Expensify Chat inbox, tap the dropdown on the logo or avatar that is in the top left corner. +2. Select the workspace you want to update the notification settings for. +3. Tap the workspace chat in your inbox (it will be the chat that has your workspace’s name as the chat title). +4. Tap the header at the top of the chat. +5. Tap **Settings**. +6. Tap **Notify me about new messages** and select **Immediately**. +{% include end-option.html %} + +{% include end-selector.html %} + +# Step 2: Enable notifications on your device + +**iPhone** + +1. Go to your device settings. +2. Find and tap **New Expensify**. +3. Tap **Notifications** and enable notifications. +4. Customize your alerts. Depending on your phone model, you may have extra options to customize the types of notifications you receive. + +**Android** + +1. Go to your device settings. +2. Tap **Notifications** and select **Apps notifications**. +3. Find and tap **New Expensify**. +4. Enable notifications. +5. Customize your alerts. Depending on your phone model, you may have extra options to customize the types of notifications you receive. + +You will now receive real-time spend notifications to your mobile device. + +
diff --git a/docs/assets/images/ExpensifyHelp-Invoice-1.png b/docs/assets/images/ExpensifyHelp-Invoice-1.png new file mode 100644 index 000000000000..e4a042afef82 Binary files /dev/null and b/docs/assets/images/ExpensifyHelp-Invoice-1.png differ diff --git a/docs/assets/images/ExpensifyHelp-QBO-1.png b/docs/assets/images/ExpensifyHelp-QBO-1.png index 7a8af4c9859e..2aa80e954f1b 100644 Binary files a/docs/assets/images/ExpensifyHelp-QBO-1.png and b/docs/assets/images/ExpensifyHelp-QBO-1.png differ diff --git a/docs/assets/images/ExpensifyHelp-QBO-2.png b/docs/assets/images/ExpensifyHelp-QBO-2.png index f7679d00582d..23419b86b6aa 100644 Binary files a/docs/assets/images/ExpensifyHelp-QBO-2.png and b/docs/assets/images/ExpensifyHelp-QBO-2.png differ diff --git a/docs/assets/images/ExpensifyHelp-QBO-3.png b/docs/assets/images/ExpensifyHelp-QBO-3.png index 0277c7e21ecb..c612cb760d58 100644 Binary files a/docs/assets/images/ExpensifyHelp-QBO-3.png and b/docs/assets/images/ExpensifyHelp-QBO-3.png differ diff --git a/docs/assets/images/ExpensifyHelp-QBO-4.png b/docs/assets/images/ExpensifyHelp-QBO-4.png new file mode 100644 index 000000000000..7fbc99503f2e Binary files /dev/null and b/docs/assets/images/ExpensifyHelp-QBO-4.png differ diff --git a/docs/assets/images/ExpensifyHelp-QBO-5.png b/docs/assets/images/ExpensifyHelp-QBO-5.png new file mode 100644 index 000000000000..600a5903c05f Binary files /dev/null and b/docs/assets/images/ExpensifyHelp-QBO-5.png differ diff --git a/fastlane/Fastfile b/fastlane/Fastfile index b7d3334c902f..af9e798d2343 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -239,35 +239,27 @@ platform :ios do } ) - begin - upload_to_testflight( - api_key_path: "./ios/ios-fastlane-json-key.json", - distribute_external: true, - notify_external_testers: true, - changelog: "Thank you for beta testing New Expensify, this version includes bug fixes and improvements.", - groups: ["Beta"], - demo_account_required: true, - beta_app_review_info: { - contact_email: ENV["APPLE_CONTACT_EMAIL"], - contact_first_name: "Andrew", - contact_last_name: "Gable", - contact_phone: ENV["APPLE_CONTACT_PHONE"], - demo_account_name: ENV["APPLE_DEMO_EMAIL"], - demo_account_password: ENV["APPLE_DEMO_PASSWORD"], - notes: "1. In the Expensify app, enter the email 'appletest.expensify@proton.me'. This will trigger a sign-in link to be sent to 'appletest.expensify@proton.me' - 2. Navigate to https://account.proton.me/login, log into Proton Mail using 'appletest.expensify@proton.me' as email and the password associated with 'appletest.expensify@proton.me', provided above - 3. Once logged into Proton Mail, navigate to your inbox and locate the email triggered in step 1. The email subject should be 'Your magic sign-in link for Expensify' - 4. Open the email and copy the 6-digit sign-in code provided within - 5. Return to the Expensify app and enter the copied 6-digit code in the designated login field" - } - ) - rescue Exception => e - if e.message.include? "Another build is in review" - UI.important("Another build is already in external beta review. Skipping external beta review submission") - else - raise - end - end + upload_to_testflight( + api_key_path: "./ios/ios-fastlane-json-key.json", + distribute_external: true, + notify_external_testers: true, + changelog: "Thank you for beta testing New Expensify, this version includes bug fixes and improvements.", + groups: ["Beta"], + demo_account_required: true, + beta_app_review_info: { + contact_email: ENV["APPLE_CONTACT_EMAIL"], + contact_first_name: "Andrew", + contact_last_name: "Gable", + contact_phone: ENV["APPLE_CONTACT_PHONE"], + demo_account_name: ENV["APPLE_DEMO_EMAIL"], + demo_account_password: ENV["APPLE_DEMO_PASSWORD"], + notes: "1. In the Expensify app, enter the email 'appletest.expensify@proton.me'. This will trigger a sign-in link to be sent to 'appletest.expensify@proton.me' + 2. Navigate to https://account.proton.me/login, log into Proton Mail using 'appletest.expensify@proton.me' as email and the password associated with 'appletest.expensify@proton.me', provided above + 3. Once logged into Proton Mail, navigate to your inbox and locate the email triggered in step 1. The email subject should be 'Your magic sign-in link for Expensify' + 4. Open the email and copy the 6-digit sign-in code provided within + 5. Return to the Expensify app and enter the copied 6-digit code in the designated login field" + } + ) upload_symbols_to_crashlytics( app_id: "1:921154746561:ios:216bd10ccc947659027c40", diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 1653edce72b6..57a582d2a22b 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 9.0.2 + 9.0.3 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 9.0.2.3 + 9.0.3.2 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 8341a5d96c13..1ba368709984 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 9.0.2 + 9.0.3 CFBundleSignature ???? CFBundleVersion - 9.0.2.3 + 9.0.3.2 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index d9fcba7e3c9d..bb9a344b3314 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -11,9 +11,9 @@ CFBundleName $(PRODUCT_NAME) CFBundleShortVersionString - 9.0.2 + 9.0.3 CFBundleVersion - 9.0.2.3 + 9.0.3.2 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index 3dedaa6b034e..61770b6737df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "9.0.2-3", + "version": "9.0.3-2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "9.0.2-3", + "version": "9.0.3-2", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -102,7 +102,7 @@ "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "2.0.53", + "react-native-onyx": "2.0.54", "react-native-pager-view": "6.2.3", "react-native-pdf": "6.7.3", "react-native-performance": "^5.1.0", @@ -155,8 +155,8 @@ "@octokit/core": "4.0.4", "@octokit/plugin-paginate-rest": "3.1.0", "@octokit/plugin-throttling": "4.1.0", - "@perf-profiler/profiler": "^0.10.9", - "@perf-profiler/reporter": "^0.8.1", + "@perf-profiler/profiler": "^0.10.10", + "@perf-profiler/reporter": "^0.9.0", "@perf-profiler/types": "^0.8.0", "@react-native-community/eslint-config": "3.2.0", "@react-native/babel-preset": "^0.73.21", @@ -186,6 +186,7 @@ "@types/react-beautiful-dnd": "^13.1.4", "@types/react-collapse": "^5.0.1", "@types/react-dom": "^18.2.4", + "@types/react-is": "^18.3.0", "@types/react-test-renderer": "^18.0.0", "@types/semver": "^7.5.4", "@types/setimmediate": "^1.0.2", @@ -235,6 +236,7 @@ "portfinder": "^1.0.28", "prettier": "^2.8.8", "pusher-js-mock": "^0.3.3", + "react-is": "^18.3.1", "react-native-clean-project": "^4.0.0-alpha4.0", "react-test-renderer": "18.2.0", "reassure": "^0.10.1", @@ -7877,13 +7879,13 @@ } }, "node_modules/@perf-profiler/android": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@perf-profiler/android/-/android-0.12.0.tgz", - "integrity": "sha512-wLI3D63drtqw3p7aKci+LCtN/ZipLJQvcw8cfmhwxqqRxTraFa8lDz5CNvNsqtCI7Zl0N9VRtnDMOj4e1W1yMQ==", + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@perf-profiler/android/-/android-0.12.1.tgz", + "integrity": "sha512-t4E2tfj9UdJw5JjhFPLMzrsu3NkKSyiZyeIyd70HX9d3anWqNK47XuQV+qkDPMjWaoU+CTlj1SuNnIOqEkCpSA==", "dev": true, "dependencies": { "@perf-profiler/logger": "^0.3.3", - "@perf-profiler/profiler": "^0.10.9", + "@perf-profiler/profiler": "^0.10.10", "@perf-profiler/types": "^0.8.0", "commander": "^12.0.0", "lodash": "^4.17.21" @@ -7902,24 +7904,24 @@ } }, "node_modules/@perf-profiler/ios": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@perf-profiler/ios/-/ios-0.3.1.tgz", - "integrity": "sha512-zRAgxLuCHzo47SYynljf+Aplh2K4DMwJ4dqIU30P8uPHiV5yHjE83eH+sTD6I7jUnUvZ8qAO1dhvp6ATJEpP/Q==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@perf-profiler/ios/-/ios-0.3.2.tgz", + "integrity": "sha512-2jYyHXFO3xe5BdvU1Ttt+Uw2nAf10B3/mcx4FauJwSdJ+nlOAKIvxmZDvMcipCZZ63uc+HWsYndhziJZVQ7VUw==", "dev": true, "dependencies": { - "@perf-profiler/ios-instruments": "^0.3.1", + "@perf-profiler/ios-instruments": "^0.3.2", "@perf-profiler/logger": "^0.3.3", "@perf-profiler/types": "^0.8.0" } }, "node_modules/@perf-profiler/ios-instruments": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@perf-profiler/ios-instruments/-/ios-instruments-0.3.1.tgz", - "integrity": "sha512-6ZiN9QTmIT8N37SslzjYNk+4+FX0X4IVuM/KiJF/DVgs056CT3MRDF8FFKF17BHsDJBi2a25QkegU8+AQdh+Qg==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@perf-profiler/ios-instruments/-/ios-instruments-0.3.2.tgz", + "integrity": "sha512-uox5arQscpRuGWfzBrTpsn6eJq0ErdjPlU0FMbN4Cv5akQC11ejKWmgV6y4FR/0YIET9uiiXMtnwyEBgUunYGQ==", "dev": true, "dependencies": { "@perf-profiler/logger": "^0.3.3", - "@perf-profiler/profiler": "^0.10.9", + "@perf-profiler/profiler": "^0.10.10", "@perf-profiler/types": "^0.8.0", "commander": "^12.0.0", "fast-xml-parser": "^4.2.7" @@ -7960,20 +7962,20 @@ } }, "node_modules/@perf-profiler/profiler": { - "version": "0.10.9", - "resolved": "https://registry.npmjs.org/@perf-profiler/profiler/-/profiler-0.10.9.tgz", - "integrity": "sha512-jhkFyqsrmkI9gCYmK7+R1e+vjWGw2a2YnqruRAUk71saOkLLvRSWKnT0MiGMqzi0aQj//ojeW9viDJgxQB86zg==", + "version": "0.10.10", + "resolved": "https://registry.npmjs.org/@perf-profiler/profiler/-/profiler-0.10.10.tgz", + "integrity": "sha512-kvVC6VQ7pBdthcWEcLTua+iDj0ZkcmYYL9gXHa9Dl7jYkZI4cOeslJZ1vuGfIcC168JwAVrB8UYhgoSgss/MWQ==", "dev": true, "dependencies": { - "@perf-profiler/android": "^0.12.0", - "@perf-profiler/ios": "^0.3.1", + "@perf-profiler/android": "^0.12.1", + "@perf-profiler/ios": "^0.3.2", "@perf-profiler/types": "^0.8.0" } }, "node_modules/@perf-profiler/reporter": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@perf-profiler/reporter/-/reporter-0.8.1.tgz", - "integrity": "sha512-lZp17uMMLAV4nuDO0JbajbPCyOoD4/ugnZVxsOEEueRo8mxB26TS3R7ANtMZYjHrpQbJry0CgfTIPxflBgtq4A==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@perf-profiler/reporter/-/reporter-0.9.0.tgz", + "integrity": "sha512-wJt6ZRVM/cL+8rv9gFYgl8ZIra0uKdesfcfvsvhmrPXtxgC0O4ZdHF9hJDMtcCiHuHb8ptVq/BmEEW84CnvRIw==", "dev": true, "dependencies": { "@perf-profiler/types": "^0.8.0", @@ -9963,6 +9965,11 @@ "react": "*" } }, + "node_modules/@react-navigation/core/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/@react-navigation/devtools": { "version": "6.0.10", "dev": true, @@ -18001,6 +18008,15 @@ "@types/react": "*" } }, + "node_modules/@types/react-is": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-18.3.0.tgz", + "integrity": "sha512-KZJpHUkAdzyKj/kUHJDc6N7KyidftICufJfOFpiG6haL/BDQNQt5i4n1XDUL/nDZAtGLHDSWRYpLzKTAKSvX6w==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react-native": { "version": "0.73.0", "deprecated": "This is a stub types definition. react-native provides its own type definitions, so you do not need this installed.", @@ -20448,6 +20464,12 @@ "node": ">= 6" } }, + "node_modules/babel-plugin-react-compiler/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + }, "node_modules/babel-plugin-react-compiler/node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -27965,6 +27987,11 @@ "react-is": "^16.7.0" } }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/hosted-git-info": { "version": "4.1.0", "dev": true, @@ -36156,10 +36183,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/pretty-format/node_modules/react-is": { - "version": "18.2.0", - "license": "MIT" - }, "node_modules/pretty-hrtime": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", @@ -36238,6 +36261,11 @@ "react-is": "^16.13.1" } }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/propagate": { "version": "2.0.1", "license": "MIT", @@ -36895,8 +36923,9 @@ } }, "node_modules/react-is": { - "version": "16.13.1", - "license": "MIT" + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" }, "node_modules/react-map-gl": { "version": "7.1.3", @@ -37248,9 +37277,9 @@ } }, "node_modules/react-native-onyx": { - "version": "2.0.53", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-2.0.53.tgz", - "integrity": "sha512-ObNk5MhLOAVkLgE0NCI04CEO3qaP5ZG+NY1Kn3UnxcHlhyLlDQb10EOiDWSLwNR2s4K3kK+ge7Xmo6N0VdMyyA==", + "version": "2.0.54", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-2.0.54.tgz", + "integrity": "sha512-cANbs0KuiwHAIUC0HY7DGNXbFMHH4ZWbTci+qhHhuNNf4aNIP0/ncJ4W8a3VCgFVtfobIFAX5ouT40dEcgBOIQ==", "dependencies": { "ascii-table": "0.0.9", "fast-equals": "^4.0.3", diff --git a/package.json b/package.json index 14bc9d32373c..2d9480d1ee20 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "9.0.2-3", + "version": "9.0.3-2", "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.", @@ -155,7 +155,7 @@ "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "2.0.53", + "react-native-onyx": "2.0.54", "react-native-pager-view": "6.2.3", "react-native-pdf": "6.7.3", "react-native-performance": "^5.1.0", @@ -208,8 +208,8 @@ "@octokit/core": "4.0.4", "@octokit/plugin-paginate-rest": "3.1.0", "@octokit/plugin-throttling": "4.1.0", - "@perf-profiler/profiler": "^0.10.9", - "@perf-profiler/reporter": "^0.8.1", + "@perf-profiler/profiler": "^0.10.10", + "@perf-profiler/reporter": "^0.9.0", "@perf-profiler/types": "^0.8.0", "@react-native-community/eslint-config": "3.2.0", "@react-native/babel-preset": "^0.73.21", @@ -239,6 +239,7 @@ "@types/react-beautiful-dnd": "^13.1.4", "@types/react-collapse": "^5.0.1", "@types/react-dom": "^18.2.4", + "@types/react-is": "^18.3.0", "@types/react-test-renderer": "^18.0.0", "@types/semver": "^7.5.4", "@types/setimmediate": "^1.0.2", @@ -288,6 +289,7 @@ "portfinder": "^1.0.28", "prettier": "^2.8.8", "pusher-js-mock": "^0.3.3", + "react-is": "^18.3.1", "react-native-clean-project": "^4.0.0-alpha4.0", "react-test-renderer": "18.2.0", "reassure": "^0.10.1", diff --git a/patches/@perf-profiler+android+0.12.0.patch b/patches/@perf-profiler+android+0.12.0.patch deleted file mode 100644 index f6ecbce9b481..000000000000 --- a/patches/@perf-profiler+android+0.12.0.patch +++ /dev/null @@ -1,54 +0,0 @@ -diff --git a/node_modules/@perf-profiler/android/dist/src/commands.js b/node_modules/@perf-profiler/android/dist/src/commands.js -old mode 100755 -new mode 100644 -diff --git a/node_modules/@perf-profiler/android/dist/src/commands/platforms/UnixProfiler.js b/node_modules/@perf-profiler/android/dist/src/commands/platforms/UnixProfiler.js -index 77b9ee0..59aeed9 100644 ---- a/node_modules/@perf-profiler/android/dist/src/commands/platforms/UnixProfiler.js -+++ b/node_modules/@perf-profiler/android/dist/src/commands/platforms/UnixProfiler.js -@@ -134,7 +134,20 @@ class UnixProfiler { - } - const subProcessesStats = (0, getCpuStatsByProcess_1.processOutput)(cpu, pid); - const ram = (0, pollRamUsage_1.processOutput)(ramStr, this.getRAMPageSize()); -- const { frameTimes, interval: atraceInterval } = frameTimeParser.getFrameTimes(atrace, pid); -+ -+ let output; -+ try { -+ output = frameTimeParser.getFrameTimes(atrace, pid); -+ } catch (e) { -+ console.error(e); -+ } -+ -+ if (!output) { -+ return; -+ } -+ -+ const { frameTimes, interval: atraceInterval } = output; -+ - if (!initialTime) { - initialTime = timestamp; - } -diff --git a/node_modules/@perf-profiler/android/src/commands/platforms/UnixProfiler.ts b/node_modules/@perf-profiler/android/src/commands/platforms/UnixProfiler.ts -index d6983c1..ccacf09 100644 ---- a/node_modules/@perf-profiler/android/src/commands/platforms/UnixProfiler.ts -+++ b/node_modules/@perf-profiler/android/src/commands/platforms/UnixProfiler.ts -@@ -136,7 +136,19 @@ export abstract class UnixProfiler implements Profiler { - const subProcessesStats = processOutput(cpu, pid); - - const ram = processRamOutput(ramStr, this.getRAMPageSize()); -- const { frameTimes, interval: atraceInterval } = frameTimeParser.getFrameTimes(atrace, pid); -+ -+ let output; -+ try { -+ output = frameTimeParser.getFrameTimes(atrace, pid); -+ } catch (e) { -+ console.error(e); -+ } -+ -+ if (!output) { -+ return; -+ } -+ -+ const { frameTimes, interval: atraceInterval } = output; - - if (!initialTime) { - initialTime = timestamp; diff --git a/patches/@perf-profiler+android+0.12.1.patch b/patches/@perf-profiler+android+0.12.1.patch new file mode 100644 index 000000000000..e6e4a90d6ab4 --- /dev/null +++ b/patches/@perf-profiler+android+0.12.1.patch @@ -0,0 +1,26 @@ +diff --git a/node_modules/@perf-profiler/android/dist/src/commands/platforms/UnixProfiler.js b/node_modules/@perf-profiler/android/dist/src/commands/platforms/UnixProfiler.js +index 59aeed9..ee1d8a6 100644 +--- a/node_modules/@perf-profiler/android/dist/src/commands/platforms/UnixProfiler.js ++++ b/node_modules/@perf-profiler/android/dist/src/commands/platforms/UnixProfiler.js +@@ -28,7 +28,7 @@ exports.CppProfilerName = `BAMPerfProfiler`; + // into the Flipper plugin directory + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error +-const binaryFolder = global.Flipper ++const binaryFolder = (global.Flipper || process.env.AWS) + ? `${__dirname}/bin` + : `${__dirname}/../../..${__dirname.includes("dist") ? "/.." : ""}/cpp-profiler/bin`; + class UnixProfiler { +diff --git a/node_modules/@perf-profiler/android/src/commands/platforms/UnixProfiler.ts b/node_modules/@perf-profiler/android/src/commands/platforms/UnixProfiler.ts +index ccacf09..1eea659 100644 +--- a/node_modules/@perf-profiler/android/src/commands/platforms/UnixProfiler.ts ++++ b/node_modules/@perf-profiler/android/src/commands/platforms/UnixProfiler.ts +@@ -26,7 +26,7 @@ export const CppProfilerName = `BAMPerfProfiler`; + // into the Flipper plugin directory + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error +-const binaryFolder = global.Flipper ++const binaryFolder = (global.Flipper || process.env.AWS) + ? `${__dirname}/bin` + : `${__dirname}/../../..${__dirname.includes("dist") ? "/.." : ""}/cpp-profiler/bin`; + diff --git a/patches/@perf-profiler+reporter+0.8.1.patch b/patches/@perf-profiler+reporter+0.8.1.patch deleted file mode 100644 index 2c918b4049c2..000000000000 --- a/patches/@perf-profiler+reporter+0.8.1.patch +++ /dev/null @@ -1,25 +0,0 @@ -diff --git a/node_modules/@perf-profiler/reporter/dist/src/index.d.ts b/node_modules/@perf-profiler/reporter/dist/src/index.d.ts -index 2f84d84..14ae688 100644 ---- a/node_modules/@perf-profiler/reporter/dist/src/index.d.ts -+++ b/node_modules/@perf-profiler/reporter/dist/src/index.d.ts -@@ -4,4 +4,6 @@ export * from "./reporting/Report"; - export * from "./utils/sanitizeProcessName"; - export * from "./utils/round"; - export * from "./reporting/cpu"; -+export * from "./reporting/ram"; -+export * from "./reporting/fps"; - export { canComputeHighCpuUsage } from "./reporting/highCpu"; -diff --git a/node_modules/@perf-profiler/reporter/dist/src/index.js b/node_modules/@perf-profiler/reporter/dist/src/index.js -index 4b50e3a..780963a 100644 ---- a/node_modules/@perf-profiler/reporter/dist/src/index.js -+++ b/node_modules/@perf-profiler/reporter/dist/src/index.js -@@ -21,6 +21,8 @@ __exportStar(require("./reporting/Report"), exports); - __exportStar(require("./utils/sanitizeProcessName"), exports); - __exportStar(require("./utils/round"), exports); - __exportStar(require("./reporting/cpu"), exports); -+__exportStar(require("./reporting/fps"), exports); -+__exportStar(require("./reporting/ram"), exports); - var highCpu_1 = require("./reporting/highCpu"); - Object.defineProperty(exports, "canComputeHighCpuUsage", { enumerable: true, get: function () { return highCpu_1.canComputeHighCpuUsage; } }); - //# sourceMappingURL=index.js.map -\ No newline at end of file diff --git a/patches/react-native-reanimated+3.8.1+003+fix-strict-mode.patch b/patches/react-native-reanimated+3.8.1+003+fix-strict-mode.patch index e36d2dd365c0..ccc208062d10 100644 --- a/patches/react-native-reanimated+3.8.1+003+fix-strict-mode.patch +++ b/patches/react-native-reanimated+3.8.1+003+fix-strict-mode.patch @@ -1,3 +1,16 @@ +diff --git a/node_modules/react-native-reanimated/Common/cpp/Fabric/ReanimatedCommitMarker.cpp b/node_modules/react-native-reanimated/Common/cpp/Fabric/ReanimatedCommitMarker.cpp +index 3404e89..b545cb6 100644 +--- a/node_modules/react-native-reanimated/Common/cpp/Fabric/ReanimatedCommitMarker.cpp ++++ b/node_modules/react-native-reanimated/Common/cpp/Fabric/ReanimatedCommitMarker.cpp +@@ -9,7 +9,7 @@ namespace reanimated { + thread_local bool ReanimatedCommitMarker::reanimatedCommitFlag_{false}; + + ReanimatedCommitMarker::ReanimatedCommitMarker() { +- react_native_assert(reanimatedCommitFlag_ != true); ++ // react_native_assert(reanimatedCommitFlag_ != true); + reanimatedCommitFlag_ = true; + } + diff --git a/node_modules/react-native-reanimated/lib/module/reanimated2/UpdateProps.js b/node_modules/react-native-reanimated/lib/module/reanimated2/UpdateProps.js index e69c581..78b7034 100644 --- a/node_modules/react-native-reanimated/lib/module/reanimated2/UpdateProps.js diff --git a/src/CONST.ts b/src/CONST.ts index 0297f7bc0d5a..233b35e6ac4b 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -601,6 +601,9 @@ const CONST = { ONFIDO_TERMS_OF_SERVICE_URL: 'https://onfido.com/terms-of-service/', LIST_OF_RESTRICTED_BUSINESSES: 'https://community.expensify.com/discussion/6191/list-of-restricted-businesses', TRAVEL_TERMS_URL: `${USE_EXPENSIFY_URL}/travelterms`, + EXPENSIFY_PACKAGE_FOR_SAGE_INTACCT: 'https://www.expensify.com/tools/integrations/downloadPackage', + EXPENSIFY_PACKAGE_FOR_SAGE_INTACCT_FILE_NAME: 'ExpensifyPackageForSageIntacct', + HOW_TO_CONNECT_TO_SAGE_INTACCT: 'https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/Sage-Intacct#how-to-connect-to-sage-intacct', PRICING: `https://www.expensify.com/pricing`, // Use Environment.getEnvironmentURL to get the complete URL with port number @@ -1793,6 +1796,13 @@ const CONST = { QBO: 'quickbooksOnline', XERO: 'xero', NETSUITE: 'netsuite', + SAGE_INTACCT: 'intacct', + }, + NAME_USER_FRIENDLY: { + netsuite: 'NetSuite', + quickbooksOnline: 'Quickbooks Online', + xero: 'Xero', + intacct: 'Sage Intacct', }, SYNC_STAGE_NAME: { STARTING_IMPORT_QBO: 'startingImportQBO', @@ -1840,6 +1850,12 @@ const CONST = { NETSUITE_SYNC_UPDATE_DATA: 'netSuiteSyncUpdateConnectionData', NETSUITE_SYNC_NETSUITE_REIMBURSED_REPORTS: 'netSuiteSyncNetSuiteReimbursedReports', NETSUITE_SYNC_EXPENSIFY_REIMBURSED_REPORTS: 'netSuiteSyncExpensifyReimbursedReports', + SAGE_INTACCT_SYNC_CHECK_CONNECTION: 'intacctCheckConnection', + SAGE_INTACCT_SYNC_IMPORT_TITLE: 'intacctImportTitle', + SAGE_INTACCT_SYNC_IMPORT_DATA: 'intacctImportData', + SAGE_INTACCT_SYNC_IMPORT_EMPLOYEES: 'intacctImportEmployees', + SAGE_INTACCT_SYNC_IMPORT_DIMENSIONS: 'intacctImportDimensions', + SAGE_INTACCT_SYNC_IMPORT_SYNC_REIMBURSED_REPORTS: 'intacctImportSyncBillPayments', }, SYNC_STAGE_TIMEOUT_MINUTES: 20, }, @@ -1916,6 +1932,15 @@ const CONST = { MONTHLY: 'monthly', FIXED: 'fixed', }, + STEP_NAMES: ['1', '2', '3', '4', '5', '6'], + STEP: { + ASSIGNEE: 'Assignee', + CARD_TYPE: 'CardType', + LIMIT_TYPE: 'LimitType', + LIMIT: 'Limit', + CARD_NAME: 'CardName', + CONFIRMATION: 'Confirmation', + }, }, AVATAR_ROW_SIZE: { DEFAULT: 4, diff --git a/src/Expensify.tsx b/src/Expensify.tsx index 458f1e3c5d24..bfe4db13d9c4 100644 --- a/src/Expensify.tsx +++ b/src/Expensify.tsx @@ -1,7 +1,7 @@ import {Audio} from 'expo-av'; import React, {useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState} from 'react'; import type {NativeEventSubscription} from 'react-native'; -import {AppState, Linking} from 'react-native'; +import {AppState, Linking, NativeModules} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import Onyx, {withOnyx} from 'react-native-onyx'; import ConfirmModal from './components/ConfirmModal'; @@ -77,9 +77,12 @@ type ExpensifyOnyxProps = { type ExpensifyProps = ExpensifyOnyxProps; -type SplashScreenHiddenContextType = {isSplashHidden?: boolean}; +// HybridApp needs access to SetStateAction in order to properly hide SplashScreen when React Native was booted before. +type SplashScreenHiddenContextType = {isSplashHidden?: boolean; setIsSplashHidden: React.Dispatch>}; -const SplashScreenHiddenContext = React.createContext({}); +const SplashScreenHiddenContext = React.createContext({ + setIsSplashHidden: () => {}, +}); function Expensify({ isCheckingPublicRoom = true, @@ -109,16 +112,6 @@ function Expensify({ const isAuthenticated = useMemo(() => !!(session?.authToken ?? null), [session]); const autoAuthState = useMemo(() => session?.autoAuthState ?? '', [session]); - const isAuthenticatedRef = useRef(false); - isAuthenticatedRef.current = isAuthenticated; - - const contextValue = useMemo( - () => ({ - isSplashHidden, - }), - [isSplashHidden], - ); - const shouldInit = isNavigationReady && hasAttemptedToOpenPublicRoom; const shouldHideSplash = shouldInit && !isSplashHidden; @@ -142,6 +135,14 @@ function Expensify({ Performance.markEnd(CONST.TIMING.SIDEBAR_LOADED); }, []); + const contextValue = useMemo( + () => ({ + isSplashHidden, + setIsSplashHidden, + }), + [isSplashHidden, setIsSplashHidden], + ); + useLayoutEffect(() => { // Initialize this client as being an active client ActiveClientManager.init(); @@ -198,8 +199,7 @@ function Expensify({ // Open chat report from a deep link (only mobile native) Linking.addEventListener('url', (state) => { - // We need to pass 'isAuthenticated' to avoid loading a non-existing profile page twice - Report.openReportFromDeepLink(state.url, !isAuthenticatedRef.current); + Report.openReportFromDeepLink(state.url); }); return () => { @@ -263,8 +263,8 @@ function Expensify({ /> )} - - {shouldHideSplash && } + {/* HybridApp has own middleware to hide SplashScreen */} + {!NativeModules.HybridAppModule && shouldHideSplash && } ); } diff --git a/src/NAVIGATORS.ts b/src/NAVIGATORS.ts index eea357322075..0b4a86c99247 100644 --- a/src/NAVIGATORS.ts +++ b/src/NAVIGATORS.ts @@ -10,5 +10,6 @@ export default { ONBOARDING_MODAL_NAVIGATOR: 'OnboardingModalNavigator', FEATURE_TRANING_MODAL_NAVIGATOR: 'FeatureTrainingModalNavigator', WELCOME_VIDEO_MODAL_NAVIGATOR: 'WelcomeVideoModalNavigator', + EXPLANATION_MODAL_NAVIGATOR: 'ExplanationModalNavigator', FULL_SCREEN_NAVIGATOR: 'FullScreenNavigator', } as const; diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 2cb615ae0af8..6f94a23acad8 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -115,6 +115,9 @@ const ONYXKEYS = { /** This NVP contains information about whether the onboarding flow was completed or not */ NVP_ONBOARDING: 'nvp_onboarding', + /** This NVP contains data associated with HybridApp */ + NVP_TRYNEWDOT: 'nvp_tryNewDot', + /** Contains the user preference for the LHN priority mode */ NVP_PRIORITY_MODE: 'nvp_priorityMode', @@ -154,6 +157,8 @@ const ONYXKEYS = { /** Whether the user has dismissed the hold educational interstitial */ NVP_DISMISSED_HOLD_USE_EXPLANATION: 'nvp_dismissedHoldUseExplanation', + /** Whether the user has seen HybridApp explanation modal */ + NVP_SEEN_NEW_USER_MODAL: 'nvp_seen_new_user_modal', /** Store the state of the subscription */ NVP_PRIVATE_SUBSCRIPTION: 'nvp_private_subscription', @@ -348,6 +353,9 @@ const ONYXKEYS = { /** Stores info during review duplicates flow */ REVIEW_DUPLICATES: 'reviewDuplicates', + /** Stores the information about the state of issuing a new card */ + ISSUE_NEW_EXPENSIFY_CARD: 'issueNewExpensifyCard', + /** Collection Keys */ COLLECTION: { DOWNLOAD: 'download_', @@ -516,6 +524,10 @@ const ONYXKEYS = { NEW_CHAT_NAME_FORM_DRAFT: 'newChatNameFormDraft', SUBSCRIPTION_SIZE_FORM: 'subscriptionSizeForm', SUBSCRIPTION_SIZE_FORM_DRAFT: 'subscriptionSizeFormDraft', + ISSUE_NEW_EXPENSIFY_CARD_FORM: 'issueNewExpensifyCardForm', + ISSUE_NEW_EXPENSIFY_CARD_FORM_DRAFT: 'issueNewExpensifyCardFormDraft', + SAGE_INTACCT_CREDENTIALS_FORM: 'sageIntacctCredentialsForm', + SAGE_INTACCT_CREDENTIALS_FORM_DRAFT: 'sageIntacctCredentialsFormDraft', }, } as const; @@ -576,6 +588,8 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.WORKSPACE_TAX_VALUE_FORM]: FormTypes.WorkspaceTaxValueForm; [ONYXKEYS.FORMS.NEW_CHAT_NAME_FORM]: FormTypes.NewChatNameForm; [ONYXKEYS.FORMS.SUBSCRIPTION_SIZE_FORM]: FormTypes.SubscriptionSizeForm; + [ONYXKEYS.FORMS.ISSUE_NEW_EXPENSIFY_CARD_FORM]: FormTypes.IssueNewExpensifyCardForm; + [ONYXKEYS.FORMS.SAGE_INTACCT_CREDENTIALS_FORM]: FormTypes.SageIntactCredentialsForm; }; type OnyxFormDraftValuesMapping = { @@ -631,6 +645,9 @@ type OnyxValuesMapping = { // NVP_ONBOARDING is an array for old users. [ONYXKEYS.NVP_ONBOARDING]: Onboarding | []; + // ONYXKEYS.NVP_TRYNEWDOT is HybridApp onboarding data + [ONYXKEYS.NVP_TRYNEWDOT]: OnyxTypes.TryNewDot; + [ONYXKEYS.ACTIVE_CLIENTS]: string[]; [ONYXKEYS.DEVICE_ID]: string; [ONYXKEYS.IS_SIDEBAR_LOADED]: boolean; @@ -673,6 +690,7 @@ type OnyxValuesMapping = { [ONYXKEYS.NVP_RECENT_WAYPOINTS]: OnyxTypes.RecentWaypoint[]; [ONYXKEYS.NVP_INTRO_SELECTED]: OnyxTypes.IntroSelected; [ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES]: OnyxTypes.LastSelectedDistanceRates; + [ONYXKEYS.NVP_SEEN_NEW_USER_MODAL]: boolean; [ONYXKEYS.PUSH_NOTIFICATIONS_ENABLED]: boolean; [ONYXKEYS.PLAID_DATA]: OnyxTypes.PlaidData; [ONYXKEYS.IS_PLAID_DISABLED]: boolean; @@ -734,6 +752,7 @@ type OnyxValuesMapping = { [ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE]: OnyxTypes.QuickAction; [ONYXKEYS.NVP_TRAVEL_SETTINGS]: OnyxTypes.TravelSettings; [ONYXKEYS.REVIEW_DUPLICATES]: OnyxTypes.ReviewDuplicates; + [ONYXKEYS.ISSUE_NEW_EXPENSIFY_CARD]: OnyxTypes.IssueNewCard; [ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL]: string; [ONYXKEYS.NVP_LAST_DAY_FREE_TRIAL]: string; [ONYXKEYS.NVP_BILLING_FUND_ID]: number; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 5c8cfdcc8a68..33eb78dc300d 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -783,6 +783,17 @@ const ROUTES = { route: 'settings/workspaces/:policyID/reportFields', getRoute: (policyID: string) => `settings/workspaces/${policyID}/reportFields` as const, }, + // TODO: uncomment after development is done + // WORKSPACE_EXPENSIFY_CARD: { + // route: 'settings/workspaces/:policyID/expensify-card', + // getRoute: (policyID: string) => `settings/workspaces/${policyID}/expensify-card` as const, + // }, + // WORKSPACE_EXPENSIFY_CARD_ISSUE_NEW: { + // route: 'settings/workspaces/:policyID/expensify-card/issues-new', + // getRoute: (policyID: string) => `settings/workspaces/${policyID}/expensify-card/issue-new` as const, + // }, + // TODO: remove after development is done - this one is for testing purposes + WORKSPACE_EXPENSIFY_CARD_ISSUE_NEW: 'settings/workspaces/expensify-card/issue-new', WORKSPACE_DISTANCE_RATES: { route: 'settings/workspaces/:policyID/distance-rates', getRoute: (policyID: string) => `settings/workspaces/${policyID}/distance-rates` as const, @@ -825,6 +836,7 @@ const ROUTES = { ONBOARDING_WORK: 'onboarding/work', ONBOARDING_PURPOSE: 'onboarding/purpose', WELCOME_VIDEO_ROOT: 'onboarding/welcome-video', + EXPLANATION_MODAL_ROOT: 'onboarding/explanation', TRANSACTION_RECEIPT: { route: 'r/:reportID/transaction/:transactionID/receipt', @@ -928,6 +940,18 @@ const ROUTES = { route: 'restricted-action/workspace/:policyID', getRoute: (policyID: string) => `restricted-action/workspace/${policyID}` as const, }, + POLICY_ACCOUNTING_SAGE_INTACCT_PREREQUISITES: { + route: 'settings/workspaces/:policyID/accounting/sage-intacct/prerequisites', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/prerequisites` as const, + }, + POLICY_ACCOUNTING_SAGE_INTACCT_ENTER_CREDENTIALS: { + route: 'settings/workspaces/:policyID/accounting/sage-intacct/enter-credentials', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/enter-credentials` as const, + }, + POLICY_ACCOUNTING_SAGE_INTACCT_EXISTING_CONNECTIONS: { + route: 'settings/workspaces/:policyID/accounting/sage-intacct/existing-connections', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/existing-connections` as const, + }, } as const; /** diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 5c5fc6c31092..1807c9bb0bab 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -273,6 +273,9 @@ const SCREENS = { XERO_BILL_PAYMENT_ACCOUNT_SELECTOR: 'Policy_Accounting_Xero_Bill_Payment_Account_Selector', XERO_EXPORT_BANK_ACCOUNT_SELECT: 'Policy_Accounting_Xero_Export_Bank_Account_Select', NETSUITE_SUBSIDIARY_SELECTOR: 'Policy_Accounting_Net_Suite_Subsidiary_Selector', + SAGE_INTACCT_PREREQUISITES: 'Policy_Accounting_Sage_Intacct_Prerequisites', + ENTER_SAGE_INTACCT_CREDENTIALS: 'Policy_Enter_Sage_Intacct_Credentials', + EXISTING_SAGE_INTACCT_CONNECTIONS: 'Policy_Existing_Sage_Intacct_Connections', }, INITIAL: 'Workspace_Initial', PROFILE: 'Workspace_Profile', @@ -281,6 +284,8 @@ const SCREENS = { RATE_AND_UNIT: 'Workspace_RateAndUnit', RATE_AND_UNIT_RATE: 'Workspace_RateAndUnit_Rate', RATE_AND_UNIT_UNIT: 'Workspace_RateAndUnit_Unit', + EXPENSIFY_CARD: 'Workspace_ExpensifyCard', + EXPENSIFY_CARD_ISSUE_NEW: 'Workspace_ExpensifyCard_New', BILLS: 'Workspace_Bills', INVOICES: 'Workspace_Invoices', TRAVEL: 'Workspace_Travel', @@ -362,6 +367,10 @@ const SCREENS = { ROOT: 'Welcome_Video_Root', }, + EXPLANATION_MODAL: { + ROOT: 'Explanation_Modal_Root', + }, + I_KNOW_A_TEACHER: 'I_Know_A_Teacher', INTRO_SCHOOL_PRINCIPAL: 'Intro_School_Principal', I_AM_A_TEACHER: 'I_Am_A_Teacher', diff --git a/src/components/AccountingConnectionConfirmationModal.tsx b/src/components/AccountingConnectionConfirmationModal.tsx new file mode 100644 index 000000000000..c472f215b6df --- /dev/null +++ b/src/components/AccountingConnectionConfirmationModal.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import useLocalize from '@hooks/useLocalize'; +import type {ConnectionName} from '@src/types/onyx/Policy'; +import ConfirmModal from './ConfirmModal'; + +type AccountingConnectionConfirmationModalProps = { + integrationToConnect: ConnectionName; + onConfirm: () => void; + onCancel: () => void; +}; + +function AccountingConnectionConfirmationModal({integrationToConnect, onCancel, onConfirm}: AccountingConnectionConfirmationModalProps) { + const {translate} = useLocalize(); + + return ( + + ); +} + +AccountingConnectionConfirmationModal.displayName = 'AccountingConnectionConfirmationModal'; +export default AccountingConnectionConfirmationModal; diff --git a/src/components/ConnectToNetSuiteButton/index.tsx b/src/components/ConnectToNetSuiteButton/index.tsx new file mode 100644 index 000000000000..fc948503a127 --- /dev/null +++ b/src/components/ConnectToNetSuiteButton/index.tsx @@ -0,0 +1,54 @@ +import React, {useState} from 'react'; +import AccountingConnectionConfirmationModal from '@components/AccountingConnectionConfirmationModal'; +import Button from '@components/Button'; +import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {removePolicyConnection} from '@libs/actions/connections'; +import Navigation from '@libs/Navigation/Navigation'; +import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; +import type {ConnectToNetSuiteButtonProps} from './types'; + +function ConnectToNetSuiteButton({policyID, shouldDisconnectIntegrationBeforeConnecting, integrationToDisconnect}: ConnectToNetSuiteButtonProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const {isOffline} = useNetwork(); + + const [isDisconnectModalOpen, setIsDisconnectModalOpen] = useState(false); + + return ( + <> +