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/.prettierrc.js b/.prettierrc.js index 3118dc378694..d981112fffae 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -6,6 +6,7 @@ module.exports = { arrowParens: 'always', printWidth: 190, singleAttributePerLine: true, + plugins: [require.resolve('@trivago/prettier-plugin-sort-imports')], /** `importOrder` should be defined in an alphabetical order. */ importOrder: [ '@assets/(.*)$', 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 fac54600f021..192537f08e3d 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 1009000200 - versionName "9.0.2-0" + versionCode 1009000306 + versionName "9.0.3-6" // 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/config/electronBuilder.config.js b/config/electronBuilder.config.js index 5a995fb5de91..ad3a23407b89 100644 --- a/config/electronBuilder.config.js +++ b/config/electronBuilder.config.js @@ -47,7 +47,7 @@ module.exports = { }, target: [ { - target: 'dmg', + target: 'default', arch: ['universal'], }, ], diff --git a/docs/_data/_routes.yml b/docs/_data/_routes.yml index 5fd65532c021..7f416951b58c 100644 --- a/docs/_data/_routes.yml +++ b/docs/_data/_routes.yml @@ -114,8 +114,8 @@ platforms: icon: /assets/images/shield.svg description: Configure rules, settings, and limits for your company’s spending. - - href: expenses - title: Expenses + - href: expenses-&-payments + title: Expenses & Payments icon: /assets/images/money-into-wallet.svg description: Learn more about expense tracking and submission. diff --git a/docs/articles/new-expensify/connections/Set-up-QuickBooks-Online-connection.md b/docs/articles/new-expensify/connections/Set-up-QuickBooks-Online-connection.md index 6bc3b0896912..155512866a8f 100644 --- a/docs/articles/new-expensify/connections/Set-up-QuickBooks-Online-connection.md +++ b/docs/articles/new-expensify/connections/Set-up-QuickBooks-Online-connection.md @@ -46,6 +46,12 @@ Log in to QuickBooks Online and ensure all of your employees are setup as either
  • Enter your Intuit login details to import your settings from QuickBooks Online to Expensify.
  • +![The toggle location to enable accounting integrations like QuickBooks Online]({{site.url}}/assets/images/ExpensifyHelp-QBO-1.png){:width="100%"} + +![How to enable accounting integrations like QuickBooks Online]({{site.url}}/assets/images/ExpensifyHelp-QBO-2.png){:width="100%"} + +![The QuickBooks Online Connect button]({{site.url}}/assets/images/ExpensifyHelp-QBO-3.png){:width="100%"} + # Step 3: Configure import settings The following steps help you determine how data will be imported from QuickBooks Online to Expensify. diff --git a/docs/articles/new-expensify/connections/Set-up-Xero-connection.md b/docs/articles/new-expensify/connections/Set-up-Xero-connection.md index 73bff6ad5862..47917f2dffc3 100644 --- a/docs/articles/new-expensify/connections/Set-up-Xero-connection.md +++ b/docs/articles/new-expensify/connections/Set-up-Xero-connection.md @@ -23,6 +23,12 @@ To set up your Xero connection, complete the 4 steps below.
  • Enter your Xero login details to import your settings from Xero to Expensify.
  • +![The toggle location to enable accounting integrations like QuickBooks Online]({{site.url}}/assets/images/ExpensifyHelp-Xero-1.png){:width="100%"} + +![How to enable accounting integrations like QuickBooks Online]({{site.url}}/assets/images/ExpensifyHelp-Xero-2.png){:width="100%"} + +![The QuickBooks Online Connect button]({{site.url}}/assets/images/ExpensifyHelp-Xero-3.png){:width="100%"} + # Step 2: Configure import settings The following steps help you determine how data will be imported from Xero to Expensify. diff --git a/docs/articles/new-expensify/expenses/Approve-and-pay-expenses.md b/docs/articles/new-expensify/expenses-&-payments/Approve-and-pay-expenses.md similarity index 93% rename from docs/articles/new-expensify/expenses/Approve-and-pay-expenses.md rename to docs/articles/new-expensify/expenses-&-payments/Approve-and-pay-expenses.md index 0cf642c76e4c..c037e8fe9cd3 100644 --- a/docs/articles/new-expensify/expenses/Approve-and-pay-expenses.md +++ b/docs/articles/new-expensify/expenses-&-payments/Approve-and-pay-expenses.md @@ -29,7 +29,11 @@ To approve an expense, {% include info.html %} Admins can modify an expense, if needed. {% include end-info.html %} - + +![The approve button in an expense]({{site.url}}/assets/images/ExpensifyHelp_ApproveExpense_1.png){:width="100%"} + +![The approve button when you click into the expense]({{site.url}}/assets/images/ExpensifyHelp_ApproveExpense_2.png){:width="100%"} + You’re now ready to pay the expense. # Hold an expense diff --git a/docs/articles/new-expensify/expenses/Connect-a-Business-Bank-Account.md b/docs/articles/new-expensify/expenses-&-payments/Connect-a-Business-Bank-Account.md similarity index 100% rename from docs/articles/new-expensify/expenses/Connect-a-Business-Bank-Account.md rename to docs/articles/new-expensify/expenses-&-payments/Connect-a-Business-Bank-Account.md diff --git a/docs/articles/new-expensify/expenses/Create-an-expense.md b/docs/articles/new-expensify/expenses-&-payments/Create-an-expense.md similarity index 100% rename from docs/articles/new-expensify/expenses/Create-an-expense.md rename to docs/articles/new-expensify/expenses-&-payments/Create-an-expense.md diff --git a/docs/articles/new-expensify/expenses/Distance-Requests.md b/docs/articles/new-expensify/expenses-&-payments/Distance-Requests.md similarity index 100% rename from docs/articles/new-expensify/expenses/Distance-Requests.md rename to docs/articles/new-expensify/expenses-&-payments/Distance-Requests.md diff --git a/docs/articles/new-expensify/expenses/Resolve-Errors-Adding-a-Bank-Account.md b/docs/articles/new-expensify/expenses-&-payments/Resolve-Errors-Adding-a-Bank-Account.md similarity index 100% rename from docs/articles/new-expensify/expenses/Resolve-Errors-Adding-a-Bank-Account.md rename to docs/articles/new-expensify/expenses-&-payments/Resolve-Errors-Adding-a-Bank-Account.md diff --git a/docs/articles/new-expensify/expenses/Send-an-invoice.md b/docs/articles/new-expensify/expenses-&-payments/Send-an-invoice.md similarity index 100% rename from docs/articles/new-expensify/expenses/Send-an-invoice.md rename to docs/articles/new-expensify/expenses-&-payments/Send-an-invoice.md diff --git a/docs/articles/new-expensify/expenses/Set-up-your-wallet.md b/docs/articles/new-expensify/expenses-&-payments/Set-up-your-wallet.md similarity index 100% rename from docs/articles/new-expensify/expenses/Set-up-your-wallet.md rename to docs/articles/new-expensify/expenses-&-payments/Set-up-your-wallet.md diff --git a/docs/articles/new-expensify/expenses/Split-an-expense.md b/docs/articles/new-expensify/expenses-&-payments/Split-an-expense.md similarity index 100% rename from docs/articles/new-expensify/expenses/Split-an-expense.md rename to docs/articles/new-expensify/expenses-&-payments/Split-an-expense.md diff --git a/docs/articles/new-expensify/expenses/Track-expenses.md b/docs/articles/new-expensify/expenses-&-payments/Track-expenses.md similarity index 100% rename from docs/articles/new-expensify/expenses/Track-expenses.md rename to docs/articles/new-expensify/expenses-&-payments/Track-expenses.md diff --git a/docs/articles/new-expensify/expenses/Unlock-a-Business-Bank-Account.md b/docs/articles/new-expensify/expenses-&-payments/Unlock-a-Business-Bank-Account.md similarity index 100% rename from docs/articles/new-expensify/expenses/Unlock-a-Business-Bank-Account.md rename to docs/articles/new-expensify/expenses-&-payments/Unlock-a-Business-Bank-Account.md diff --git a/docs/articles/new-expensify/expenses/Validate-a-Business-Bank-Account.md b/docs/articles/new-expensify/expenses-&-payments/Validate-a-Business-Bank-Account.md similarity index 100% rename from docs/articles/new-expensify/expenses/Validate-a-Business-Bank-Account.md rename to docs/articles/new-expensify/expenses-&-payments/Validate-a-Business-Bank-Account.md 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/docs/new-expensify/hubs/expenses/index.html b/docs/new-expensify/hubs/expenses-&-payments/index.html similarity index 100% rename from docs/new-expensify/hubs/expenses/index.html rename to docs/new-expensify/hubs/expenses-&-payments/index.html diff --git a/docs/redirects.csv b/docs/redirects.csv index 1c849e0aabdc..f2d9a797415b 100644 --- a/docs/redirects.csv +++ b/docs/redirects.csv @@ -203,3 +203,14 @@ https://help.expensify.com/articles/new-expensify/chat/Expensify-Chat-For-Admins https://help.expensify.com/articles/new-expensify/bank-accounts-and-payments/Connect-a-Bank-Account.html,https://help.expensify.com/articles/new-expensify/expenses/Connect-a-Business-Bank-Account https://help.expensify.com/articles/expensify-classic/travel/Coming-Soon,https://help.expensify.com/expensify-classic/hubs/travel/ https://help.expensify.com/articles/new-expensify/expenses/Manually-submit-reports-for-approval,https://help.expensify.com/new-expensify/hubs/expenses/ +https://help.expensify.com/articles/new-expensify/expenses/Approve-and-pay-expenses,https://help.expensify.com/articles/new-expensify/expenses-&-payments/Approve-and-pay-expenses +https://help.expensify.com/articles/new-expensify/expenses/Connect-a-Business-Bank-Account,https://help.expensify.com/articles/new-expensify/expenses-&-payments/Connect-a-Business-Bank-Account +https://help.expensify.com/articles/new-expensify/expenses/Create-an-expense,https://help.expensify.com/articles/new-expensify/expenses-&-payments/Create-an-expense +https://help.expensify.com/articles/new-expensify/expenses/Distance-Requests,https://help.expensify.com/articles/new-expensify/expenses-&-payments/Distance-Requests +https://help.expensify.com/articles/new-expensify/expenses/Resolve-Errors-Adding-a-Bank-Account,https://help.expensify.com/articles/new-expensify/expenses-&-payments/Resolve-Errors-Adding-a-Bank-Account +https://help.expensify.com/articles/new-expensify/expenses/Send-an-invoice,https://help.expensify.com/articles/new-expensify/expenses-&-payments/Send-an-invoice +https://help.expensify.com/articles/new-expensify/expenses/Set-up-your-wallet,https://help.expensify.com/articles/new-expensify/expenses-&-payments/Set-up-your-wallet +https://help.expensify.com/articles/new-expensify/expenses/Split-an-expense,https://help.expensify.com/articles/new-expensify/expenses-&-payments/Split-an-expense +https://help.expensify.com/articles/new-expensify/expenses/Track-expenses,https://help.expensify.com/articles/new-expensify/expenses-&-payments/Track-expenses +https://help.expensify.com/articles/new-expensify/expenses/Unlock-a-Business-Bank-Account,https://help.expensify.com/articles/new-expensify/expenses-&-payments/Unlock-a-Business-Bank-Account +https://help.expensify.com/articles/new-expensify/expenses/Validate-a-Business-Bank-Account,https://help.expensify.com/articles/new-expensify/expenses-&-payments/Validate-a-Business-Bank-Account \ No newline at end of file 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/NewApp_AdHoc.mobileprovision.gpg b/ios/NewApp_AdHoc.mobileprovision.gpg index 29d379151525..32ed6ba30059 100644 Binary files a/ios/NewApp_AdHoc.mobileprovision.gpg and b/ios/NewApp_AdHoc.mobileprovision.gpg differ diff --git a/ios/NewApp_AdHoc_Notification_Service.mobileprovision.gpg b/ios/NewApp_AdHoc_Notification_Service.mobileprovision.gpg index cf14d27d7d87..5712b0d86b19 100644 Binary files a/ios/NewApp_AdHoc_Notification_Service.mobileprovision.gpg and b/ios/NewApp_AdHoc_Notification_Service.mobileprovision.gpg differ diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index a2e274eafc4d..17eaae3cc3fc 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.0 + 9.0.3.6 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index e1f9960caa92..618d394349ed 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.0 + 9.0.3.6 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 7d83d9f3d273..d5e50828e3c7 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.0 + 9.0.3.6 NSExtension NSExtensionPointIdentifier diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 35dccc2de393..a5ffdcb4b63c 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1243,7 +1243,13 @@ PODS: - react-native-config (1.5.0): - react-native-config/App (= 1.5.0) - react-native-config/App (1.5.0): - - React-Core + - RCT-Folly + - RCTRequired + - RCTTypeSafety + - React + - React-Codegen + - React-RCTFabric + - ReactCommon/turbomodule/core - react-native-document-picker (9.1.1): - RCT-Folly - RCTRequired @@ -1974,7 +1980,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - RNScreens (3.30.1): + - RNScreens (3.32.0): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -1988,13 +1994,14 @@ PODS: - React-ImageManager - React-NativeModulesApple - React-RCTFabric + - React-RCTImage - React-rendererdebug - React-utils - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNScreens/common (= 3.30.1) + - RNScreens/common (= 3.32.0) - Yoga - - RNScreens/common (3.30.1): + - RNScreens/common (3.32.0): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -2008,6 +2015,7 @@ PODS: - React-ImageManager - React-NativeModulesApple - React-RCTFabric + - React-RCTImage - React-rendererdebug - React-utils - ReactCommon/turbomodule/bridging @@ -2552,7 +2560,7 @@ SPEC CHECKSUMS: react-native-airship: 38e2596999242b68c933959d6145512e77937ac0 react-native-blob-util: 1ddace5234c62e3e6e4e154d305ad07ef686599b react-native-cameraroll: f373bebbe9f6b7c3fd2a6f97c5171cda574cf957 - react-native-config: 5330c8258265c1e5fdb8c009d2cabd6badd96727 + react-native-config: 5ce986133b07fc258828b20b9506de0e683efc1c react-native-document-picker: 8532b8af7c2c930f9e202aac484ac785b0f4f809 react-native-geolocation: f9e92eb774cb30ac1e099f34b3a94f03b4db7eb3 react-native-image-picker: f8a13ff106bcc7eb00c71ce11fdc36aac2a44440 @@ -2612,7 +2620,7 @@ SPEC CHECKSUMS: RNPermissions: 0b61d30d21acbeafe25baaa47d9bae40a0c65216 RNReactNativeHapticFeedback: 616c35bdec7d20d4c524a7949ca9829c09e35f37 RNReanimated: 323436b1a5364dca3b5f8b1a13458455e0de9efe - RNScreens: 9ec969a95987a6caae170ef09313138abf3331e1 + RNScreens: abd354e98519ed267600b7ee64fdcb8e060b1218 RNShare: 2a4cdfc0626ad56b0ef583d424f2038f772afe58 RNSound: 6c156f925295bdc83e8e422e7d8b38d33bc71852 RNSVG: 18f1381e046be2f1c30b4724db8d0c966238089f diff --git a/package-lock.json b/package-lock.json index fb15d51d1389..9f63be958d1a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "9.0.2-0", + "version": "9.0.3-6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "9.0.2-0", + "version": "9.0.3-6", "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", @@ -115,7 +115,7 @@ "react-native-release-profiler": "^0.1.6", "react-native-render-html": "6.3.1", "react-native-safe-area-context": "4.8.2", - "react-native-screens": "3.30.1", + "react-native-screens": "3.32.0", "react-native-share": "^10.0.2", "react-native-sound": "^0.11.2", "react-native-svg": "14.1.0", @@ -155,6 +155,9 @@ "@octokit/core": "4.0.4", "@octokit/plugin-paginate-rest": "3.1.0", "@octokit/plugin-throttling": "4.1.0", + "@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", "@react-native/metro-config": "^0.73.5", @@ -183,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", @@ -232,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", @@ -7873,6 +7878,116 @@ "react-native": ">=0.70.0 <1.0.x" } }, + "node_modules/@perf-profiler/android": { + "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.10", + "@perf-profiler/types": "^0.8.0", + "commander": "^12.0.0", + "lodash": "^4.17.21" + }, + "bin": { + "perf-profiler-commands": "dist/src/commands.js" + } + }, + "node_modules/@perf-profiler/android/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@perf-profiler/ios": { + "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.2", + "@perf-profiler/logger": "^0.3.3", + "@perf-profiler/types": "^0.8.0" + } + }, + "node_modules/@perf-profiler/ios-instruments": { + "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.10", + "@perf-profiler/types": "^0.8.0", + "commander": "^12.0.0", + "fast-xml-parser": "^4.2.7" + }, + "bin": { + "flashlight-ios-poc": "dist/launchIOS.js" + } + }, + "node_modules/@perf-profiler/ios-instruments/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@perf-profiler/logger": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@perf-profiler/logger/-/logger-0.3.3.tgz", + "integrity": "sha512-iAJJ5gWhJ3zEpdMT7M2+HX0Q0UjSuCOZiEs5g8UKKPFYQjmPWwC6otHoZz6ZzRRddjiA065iD2PTytVFkpFTeQ==", + "dev": true, + "dependencies": { + "kleur": "^4.1.5", + "luxon": "^3.4.4" + }, + "bin": { + "perf-profiler-logger": "dist/bin.js" + } + }, + "node_modules/@perf-profiler/logger/node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@perf-profiler/profiler": { + "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.1", + "@perf-profiler/ios": "^0.3.2", + "@perf-profiler/types": "^0.8.0" + } + }, + "node_modules/@perf-profiler/reporter": { + "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", + "lodash": "^4.17.21" + } + }, + "node_modules/@perf-profiler/types": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@perf-profiler/types/-/types-0.8.0.tgz", + "integrity": "sha512-TFiktv00SzLjjPp1hFYYjT9O36iGIUaF6yPLd7x/UT4CuLd0YYDUj+gvX0fbXtVtV7141tTvWbXFL5HiXGx0kw==", + "dev": true + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "dev": true, @@ -9850,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, @@ -17888,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.", @@ -20335,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", @@ -27852,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, @@ -33116,6 +33256,15 @@ "node": ">=10" } }, + "node_modules/luxon": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", + "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/magic-string": { "version": "0.30.9", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.9.tgz", @@ -36034,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", @@ -36116,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", @@ -36773,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", @@ -37126,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", @@ -37355,9 +37506,9 @@ } }, "node_modules/react-native-screens": { - "version": "3.30.1", - "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-3.30.1.tgz", - "integrity": "sha512-/muEvjocCtFb+j5J3YmLvB25+f4rIU8hnnxgGTkXcAf2omPBY8uhPjJaaFUlvj64VEoEzJcRpugbXWsjfPPIFg==", + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-3.32.0.tgz", + "integrity": "sha512-wybqZAHX7v8ipOXhh90CqGLkBHw5JYqKNRBX7R/b0c2WQisTOgu0M0yGwBMM6LyXRBT+4k3NTGHdDbpJVpq0yQ==", "dependencies": { "react-freeze": "^1.0.0", "warn-once": "^0.1.0" diff --git a/package.json b/package.json index d4be691e2fc2..c61316e22030 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "9.0.2-0", + "version": "9.0.3-6", "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.", @@ -36,7 +36,7 @@ "android-build-e2edelta": "bundle exec fastlane android build_e2edelta", "test": "TZ=utc NODE_OPTIONS=--experimental-vm-modules jest", "typecheck": "tsc", - "lint": "eslint . --max-warnings=0 --cache --cache-location=node_modules/.cache/eslint", + "lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --max-warnings=0 --cache --cache-location=node_modules/.cache/eslint", "lint-changed": "eslint --fix $(git diff --diff-filter=AM --name-only main -- \"*.js\" \"*.ts\" \"*.tsx\")", "lint-watch": "npx eslint-watch --watch --changed", "shellcheck": "./scripts/shellCheck.sh", @@ -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", @@ -168,7 +168,7 @@ "react-native-release-profiler": "^0.1.6", "react-native-render-html": "6.3.1", "react-native-safe-area-context": "4.8.2", - "react-native-screens": "3.30.1", + "react-native-screens": "3.32.0", "react-native-share": "^10.0.2", "react-native-sound": "^0.11.2", "react-native-svg": "14.1.0", @@ -208,6 +208,9 @@ "@octokit/core": "4.0.4", "@octokit/plugin-paginate-rest": "3.1.0", "@octokit/plugin-throttling": "4.1.0", + "@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", "@react-native/metro-config": "^0.73.5", @@ -236,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", @@ -285,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.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/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/patches/react-native-screens+3.30.1+001+fix-screen-type.patch b/patches/react-native-screens+3.30.1+001+fix-screen-type.patch deleted file mode 100644 index f282ec58b07b..000000000000 --- a/patches/react-native-screens+3.30.1+001+fix-screen-type.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff --git a/node_modules/react-native-screens/src/components/Screen.tsx b/node_modules/react-native-screens/src/components/Screen.tsx -index 3f9a1cb..45767f7 100644 ---- a/node_modules/react-native-screens/src/components/Screen.tsx -+++ b/node_modules/react-native-screens/src/components/Screen.tsx -@@ -79,6 +79,7 @@ export class InnerScreen extends React.Component { - // Due to how Yoga resolves layout, we need to have different components for modal nad non-modal screens - const AnimatedScreen = - Platform.OS === 'android' || -+ stackPresentation === undefined || - stackPresentation === 'push' || - stackPresentation === 'containedModal' || - stackPresentation === 'containedTransparentModal' diff --git a/src/CONST.ts b/src/CONST.ts index e71ad55a452c..46782be36b62 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -364,6 +364,8 @@ const CONST = { NETSUITE_ON_NEW_EXPENSIFY: 'netsuiteOnNewExpensify', REPORT_FIELDS_FEATURE: 'reportFieldsFeature', WORKSPACE_FEEDS: 'workspaceFeeds', + NETSUITE_USA_TAX: 'netsuiteUsaTax', + INTACCT_ON_NEW_EXPENSIFY: 'intacctOnNewExpensify', }, BUTTON_STATES: { DEFAULT: 'default', @@ -601,6 +603,10 @@ 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 DEV_NEW_EXPENSIFY_URL: 'https://dev.new.expensify.com:', @@ -670,12 +676,12 @@ const CONST = { CLOSED: 'CLOSED', CREATED: 'CREATED', DELEGATE_SUBMIT: 'DELEGATESUBMIT', // OldDot Action - DELETED_ACCOUNT: 'DELETEDACCOUNT', // OldDot Action + DELETED_ACCOUNT: 'DELETEDACCOUNT', // Deprecated OldDot Action DISMISSED_VIOLATION: 'DISMISSEDVIOLATION', - DONATION: 'DONATION', // OldDot Action + DONATION: 'DONATION', // Deprecated OldDot Action EXPORTED_TO_CSV: 'EXPORTCSV', // OldDot Action EXPORTED_TO_INTEGRATION: 'EXPORTINTEGRATION', // OldDot Action - EXPORTED_TO_QUICK_BOOKS: 'EXPORTED', // OldDot Action + EXPORTED_TO_QUICK_BOOKS: 'EXPORTED', // Deprecated OldDot Action FORWARDED: 'FORWARDED', // OldDot Action HOLD: 'HOLD', HOLD_COMMENT: 'HOLDCOMMENT', @@ -695,9 +701,9 @@ const CONST = { REIMBURSEMENT_DELAYED: 'REIMBURSEMENTDELAYED', // OldDot Action REIMBURSEMENT_QUEUED: 'REIMBURSEMENTQUEUED', REIMBURSEMENT_DEQUEUED: 'REIMBURSEMENTDEQUEUED', - REIMBURSEMENT_REQUESTED: 'REIMBURSEMENTREQUESTED', // OldDot Action - REIMBURSEMENT_SETUP: 'REIMBURSEMENTSETUP', // OldDot Action - REIMBURSEMENT_SETUP_REQUESTED: 'REIMBURSEMENTSETUPREQUESTED', // OldDot Action + REIMBURSEMENT_REQUESTED: 'REIMBURSEMENTREQUESTED', // Deprecated OldDot Action + REIMBURSEMENT_SETUP: 'REIMBURSEMENTSETUP', // Deprecated OldDot Action + REIMBURSEMENT_SETUP_REQUESTED: 'REIMBURSEMENTSETUPREQUESTED', // Deprecated OldDot Action RENAMED: 'RENAMED', REPORT_PREVIEW: 'REPORTPREVIEW', SELECTED_FOR_RANDOM_AUDIT: 'SELECTEDFORRANDOMAUDIT', // OldDot Action @@ -1283,6 +1289,7 @@ const CONST = { REPORT_FIELD: 'REPORT_FIELD', NOT_IMPORTED: 'NOT_IMPORTED', IMPORTED: 'IMPORTED', + NETSUITE_DEFAULT: 'NETSUITE_DEFAULT', }, QUICKBOOKS_ONLINE: 'quickbooksOnline', @@ -1333,10 +1340,6 @@ const CONST = { }, }, - NETSUITE_CONFIG: { - SUBSIDIARY: 'subsidiary', - }, - QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE: { VENDOR_BILL: 'bill', CHECK: 'check', @@ -1349,6 +1352,135 @@ const CONST = { REPORT_SUBMITTED: 'REPORT_SUBMITTED', }, + NETSUITE_CONFIG: { + SUBSIDIARY: 'subsidiary', + EXPORTER: 'exporter', + EXPORT_DATE: 'exportDate', + REIMBURSABLE_EXPENSES_EXPORT_DESTINATION: 'reimbursableExpensesExportDestination', + NON_REIMBURSABLE_EXPENSES_EXPORT_DESTINATION: 'nonreimbursableExpensesExportDestination', + DEFAULT_VENDOR: 'defaultVendor', + REIMBURSABLE_PAYABLE_ACCOUNT: 'reimbursablePayableAccount', + PAYABLE_ACCT: 'payableAcct', + JOURNAL_POSTING_PREFERENCE: 'journalPostingPreference', + RECEIVABLE_ACCOUNT: 'receivableAccount', + INVOICE_ITEM_PREFERENCE: 'invoiceItemPreference', + INVOICE_ITEM: 'invoiceItem', + TAX_POSTING_ACCOUNT: 'taxPostingAccount', + PROVINCIAL_TAX_POSTING_ACCOUNT: 'provincialTaxPostingAccount', + ALLOW_FOREIGN_CURRENCY: 'allowForeignCurrency', + EXPORT_TO_NEXT_OPEN_PERIOD: 'exportToNextOpenPeriod', + IMPORT_FIELDS: ['departments', 'classes', 'locations', 'customers', 'jobs'], + IMPORT_CUSTOM_FIELDS: ['customSegments', 'customLists'], + SYNC_OPTIONS: { + SYNC_TAX: 'syncTax', + }, + }, + + NETSUITE_EXPORT_DATE: { + LAST_EXPENSE: 'LAST_EXPENSE', + EXPORTED: 'EXPORTED', + SUBMITTED: 'SUBMITTED', + }, + + NETSUITE_EXPORT_DESTINATION: { + EXPENSE_REPORT: 'EXPENSE_REPORT', + VENDOR_BILL: 'VENDOR_BILL', + JOURNAL_ENTRY: 'JOURNAL_ENTRY', + }, + + NETSUITE_INVOICE_ITEM_PREFERENCE: { + CREATE: 'create', + SELECT: 'select', + }, + + NETSUITE_JOURNAL_POSTING_PREFERENCE: { + JOURNALS_POSTING_INDIVIDUAL_LINE: 'JOURNALS_POSTING_INDIVIDUAL_LINE', + JOURNALS_POSTING_TOTAL_LINE: 'JOURNALS_POSTING_TOTAL_LINE', + }, + + NETSUITE_EXPENSE_TYPE: { + REIMBURSABLE: 'reimbursable', + NON_REIMBURSABLE: 'nonreimbursable', + }, + + /** + * Countries where tax setting is permitted (Strings are in the format of Netsuite's Country type/enum) + * + * Should mirror the list on the OldDot. + */ + NETSUITE_TAX_COUNTRIES: [ + '_canada', + '_unitedKingdomGB', + '_unitedKingdom', + '_australia', + '_southAfrica', + '_india', + '_france', + '_netherlands', + '_germany', + '_singapore', + '_spain', + '_ireland', + '_denmark', + '_brazil', + '_japan', + '_philippines', + '_china', + '_argentina', + '_newZealand', + '_switzerland', + '_sweden', + '_portugal', + '_mexico', + '_israel', + '_thailand', + '_czechRepublic', + '_egypt', + '_ghana', + '_indonesia', + '_iranIslamicRepublicOf', + '_jordan', + '_kenya', + '_kuwait', + '_lebanon', + '_malaysia', + '_morocco', + '_myanmar', + '_nigeria', + '_pakistan', + '_saudiArabia', + '_sriLanka', + '_unitedArabEmirates', + '_vietnam', + '_austria', + '_bulgaria', + '_greece', + '_cyprus', + '_norway', + '_romania', + '_poland', + '_hongKong', + '_luxembourg', + '_lithuania', + '_malta', + '_finland', + '_koreaRepublicOf', + '_italy', + '_georgia', + '_hungary', + '_latvia', + '_estonia', + '_slovenia', + '_serbia', + '_croatiaHrvatska', + '_belgium', + '_turkey', + '_taiwan', + '_azerbaijan', + '_slovakRepublic', + '_costaRica', + ] as string[], + QUICKBOOKS_EXPORT_DATE: { LAST_EXPENSE: 'LAST_EXPENSE', REPORT_EXPORTED: 'REPORT_EXPORTED', @@ -1724,6 +1856,7 @@ const CONST = { ARE_WORKFLOWS_ENABLED: 'areWorkflowsEnabled', ARE_REPORT_FIELDS_ENABLED: 'areReportFieldsEnabled', ARE_CONNECTIONS_ENABLED: 'areConnectionsEnabled', + ARE_EXPENSIFY_CARDS_ENABLED: 'areExpensifyCardsEnabled', ARE_TAXES_ENABLED: 'tax', }, CATEGORIES_BULK_ACTION_TYPES: { @@ -1792,6 +1925,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', @@ -1839,6 +1979,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, }, @@ -1915,6 +2061,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, @@ -2032,6 +2187,7 @@ const CONST = { WORKSPACE_INVOICES: 'WorkspaceSendInvoices', WORKSPACE_TRAVEL: 'WorkspaceBookTravel', WORKSPACE_MEMBERS: 'WorkspaceManageMembers', + WORKSPACE_EXPENSIFY_CARD: 'WorkspaceExpensifyCard', WORKSPACE_WORKFLOWS: 'WorkspaceWorkflows', WORKSPACE_BANK_ACCOUNT: 'WorkspaceBankAccount', WORKSPACE_SETTINGS: 'WorkspaceSettings', @@ -4873,6 +5029,14 @@ const CONST = { ACTION: 'action', TAX_AMOUNT: 'taxAmount', }, + BULK_ACTION_TYPES: { + DELETE: 'delete', + HOLD: 'hold', + UNHOLD: 'unhold', + SUBMIT: 'submit', + APPROVE: 'approve', + PAY: 'pay', + }, }, REFERRER: { @@ -4889,10 +5053,6 @@ const CONST = { }, SUBSCRIPTION_PRICE_FACTOR: 2, - SUBSCRIPTION_POSSIBLE_COST_SAVINGS: { - COLLECT_PLAN: 10, - CONTROL_PLAN: 18, - }, FEEDBACK_SURVEY_OPTIONS: { TOO_LIMITED: { ID: 'tooLimited', @@ -4912,6 +5072,12 @@ const CONST = { }, }, + WORKSPACE_CARDS_LIST_LABEL_TYPE: { + CURRENT_BALANCE: 'currentBalance', + REMAINING_LIMIT: 'remainingLimit', + CASH_BACK: 'cashBack', + }, + EXCLUDE_FROM_LAST_VISITED_PATH: [SCREENS.NOT_FOUND, SCREENS.SAML_SIGN_IN, SCREENS.VALIDATE_LOGIN] as string[], } as const; 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 46d8be0f7e82..5088c1d3158f 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,9 +157,20 @@ 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', + /** Store the stripe id status */ + NVP_PRIVATE_STRIPE_CUSTOMER_ID: 'nvp_private_stripeCustomerID', + + /** Store the billing dispute status */ + NVP_PRIVATE_BILLING_DISPUTE_PENDING: 'nvp_private_billingDisputePending', + + /** Store the billing status */ + NVP_PRIVATE_BILLING_STATUS: 'nvp_private_billingStatus', + /** Store preferred skintone for emoji */ PREFERRED_EMOJI_SKIN_TONE: 'nvp_expensify_preferredEmojiSkinTone', @@ -182,7 +196,7 @@ const ONYXKEYS = { NVP_BILLING_FUND_ID: 'nvp_expensify_billingFundID', /** The amount owed by the workspace’s owner. */ - NVP_PRIVATE_AMOUNT_OWNED: 'nvp_private_amountOwed', + NVP_PRIVATE_AMOUNT_OWED: 'nvp_private_amountOwed', /** The end date (epoch timestamp) of the workspace owner’s grace period after the free trial ends. */ NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END: 'nvp_private_billingGracePeriodEnd', @@ -339,12 +353,24 @@ const ONYXKEYS = { // Paths of PDF file that has been cached during one session CACHED_PDF_PATHS: 'cachedPDFPaths', + /** Stores iframe link to verify 3DS flow for subscription */ + VERIFY_3DS_SUBSCRIPTION: 'verify3dsSubscription', + /** Holds the checks used while transferring the ownership of the workspace */ POLICY_OWNERSHIP_CHANGE_CHECKS: 'policyOwnershipChangeChecks', + /** Indicates whether ClearOutstandingBalance failed */ + SUBSCRIPTION_RETRY_BILLING_STATUS_FAILED: 'subscriptionRetryBillingStatusFailed', + + /** Indicates whether ClearOutstandingBalance was successful */ + SUBSCRIPTION_RETRY_BILLING_STATUS_SUCCESSFUL: 'subscriptionRetryBillingStatusSuccessful', + /** 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_', @@ -401,12 +427,21 @@ const ONYXKEYS = { // Shared NVPs /** Collection of objects where each object represents the owner of the workspace that is past due billing AND the user is a member of. */ SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END: 'sharedNVP_private_billingGracePeriodEnd_', + + /** Expensify cards settings */ + SHARED_NVP_PRIVATE_EXPENSIFY_CARD_SETTINGS: 'sharedNVP_private_expensifyCardSettings_', + + /** + * Stores the card list for a given fundID and feed in the format: card__ + * So for example: card_12345_Expensify Card + */ + WORKSPACE_CARDS_LIST: 'card_', }, /** List of Form ids */ FORMS: { - ADD_DEBIT_CARD_FORM: 'addDebitCardForm', - ADD_DEBIT_CARD_FORM_DRAFT: 'addDebitCardFormDraft', + ADD_PAYMENT_CARD_FORM: 'addPaymentCardForm', + ADD_PAYMENT_CARD_FORM_DRAFT: 'addPaymentCardFormDraft', WORKSPACE_SETTINGS_FORM: 'workspaceSettingsForm', WORKSPACE_CATEGORY_FORM: 'workspaceCategoryForm', WORKSPACE_CATEGORY_FORM_DRAFT: 'workspaceCategoryFormDraft', @@ -475,6 +510,8 @@ const ONYXKEYS = { SETTINGS_STATUS_SET_CLEAR_AFTER_FORM_DRAFT: 'settingsStatusSetClearAfterFormDraft', SETTINGS_STATUS_CLEAR_DATE_FORM: 'settingsStatusClearDateForm', SETTINGS_STATUS_CLEAR_DATE_FORM_DRAFT: 'settingsStatusClearDateFormDraft', + CHANGE_BILLING_CURRENCY_FORM: 'changeBillingCurrencyForm', + CHANGE_BILLING_CURRENCY_FORM_DRAFT: 'changeBillingCurrencyFormDraft', PRIVATE_NOTES_FORM: 'privateNotesForm', PRIVATE_NOTES_FORM_DRAFT: 'privateNotesFormDraft', I_KNOW_A_TEACHER_FORM: 'iKnowTeacherForm', @@ -511,13 +548,17 @@ 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; type AllOnyxKeys = DeepValueOf; type OnyxFormValuesMapping = { - [ONYXKEYS.FORMS.ADD_DEBIT_CARD_FORM]: FormTypes.AddDebitCardForm; + [ONYXKEYS.FORMS.ADD_PAYMENT_CARD_FORM]: FormTypes.AddPaymentCardForm; [ONYXKEYS.FORMS.WORKSPACE_SETTINGS_FORM]: FormTypes.WorkspaceSettingsForm; [ONYXKEYS.FORMS.WORKSPACE_CATEGORY_FORM]: FormTypes.WorkspaceCategoryForm; [ONYXKEYS.FORMS.WORKSPACE_TAG_FORM]: FormTypes.WorkspaceTagForm; @@ -549,6 +590,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.WAYPOINT_FORM]: FormTypes.WaypointForm; [ONYXKEYS.FORMS.SETTINGS_STATUS_SET_FORM]: FormTypes.SettingsStatusSetForm; [ONYXKEYS.FORMS.SETTINGS_STATUS_CLEAR_DATE_FORM]: FormTypes.SettingsStatusClearDateForm; + [ONYXKEYS.FORMS.CHANGE_BILLING_CURRENCY_FORM]: FormTypes.ChangeBillingCurrencyForm; [ONYXKEYS.FORMS.SETTINGS_STATUS_SET_CLEAR_AFTER_FORM]: FormTypes.SettingsStatusSetClearAfterForm; [ONYXKEYS.FORMS.PRIVATE_NOTES_FORM]: FormTypes.PrivateNotesForm; [ONYXKEYS.FORMS.I_KNOW_A_TEACHER_FORM]: FormTypes.IKnowTeacherForm; @@ -570,6 +612,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 = { @@ -615,6 +659,8 @@ type OnyxCollectionValuesMapping = { [ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS]: OnyxTypes.PolicyConnectionSyncProgress; [ONYXKEYS.COLLECTION.SNAPSHOT]: OnyxTypes.SearchResults; [ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END]: OnyxTypes.BillingGraceEndPeriod; + [ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_EXPENSIFY_CARD_SETTINGS]: OnyxTypes.ExpensifyCardSettings; + [ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST]: OnyxTypes.WorkspaceCardsList; }; type OnyxValuesMapping = { @@ -625,6 +671,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; @@ -667,6 +716,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; @@ -678,6 +728,9 @@ type OnyxValuesMapping = { [ONYXKEYS.NVP_DISMISSED_REFERRAL_BANNERS]: OnyxTypes.DismissedReferralBanners; [ONYXKEYS.NVP_HAS_SEEN_TRACK_TRAINING]: boolean; [ONYXKEYS.NVP_PRIVATE_SUBSCRIPTION]: OnyxTypes.PrivateSubscription; + [ONYXKEYS.NVP_PRIVATE_STRIPE_CUSTOMER_ID]: OnyxTypes.StripeCustomerID; + [ONYXKEYS.NVP_PRIVATE_BILLING_DISPUTE_PENDING]: number; + [ONYXKEYS.NVP_PRIVATE_BILLING_STATUS]: OnyxTypes.BillingStatus; [ONYXKEYS.USER_WALLET]: OnyxTypes.UserWallet; [ONYXKEYS.WALLET_ONFIDO]: OnyxTypes.WalletOnfido; [ONYXKEYS.WALLET_ADDITIONAL_DETAILS]: OnyxTypes.WalletAdditionalDetails; @@ -704,6 +757,7 @@ type OnyxValuesMapping = { [ONYXKEYS.IS_CHECKING_PUBLIC_ROOM]: boolean; [ONYXKEYS.MY_DOMAIN_SECURITY_GROUPS]: Record; [ONYXKEYS.LAST_OPENED_PUBLIC_ROOM_ID]: string; + [ONYXKEYS.VERIFY_3DS_SUBSCRIPTION]: string; [ONYXKEYS.PREFERRED_THEME]: ValueOf; [ONYXKEYS.MAPBOX_ACCESS_TOKEN]: OnyxTypes.MapboxAccessToken; [ONYXKEYS.ONYX_UPDATES_FROM_SERVER]: OnyxTypes.OnyxUpdatesFromServer; @@ -725,12 +779,15 @@ type OnyxValuesMapping = { [ONYXKEYS.CACHED_PDF_PATHS]: Record; [ONYXKEYS.POLICY_OWNERSHIP_CHANGE_CHECKS]: Record; [ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE]: OnyxTypes.QuickAction; + [ONYXKEYS.SUBSCRIPTION_RETRY_BILLING_STATUS_FAILED]: boolean; + [ONYXKEYS.SUBSCRIPTION_RETRY_BILLING_STATUS_SUCCESSFUL]: boolean; [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; - [ONYXKEYS.NVP_PRIVATE_AMOUNT_OWNED]: number; + [ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED]: number; [ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END]: number; }; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index c38ec192127e..45c56abc71d5 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -95,6 +95,7 @@ const ROUTES = { WORKSPACE_SWITCHER: 'workspace-switcher', SETTINGS: 'settings', SETTINGS_PROFILE: 'settings/profile', + SETTINGS_CHANGE_CURRENCY: 'settings/add-payment-card/change-currency', SETTINGS_SHARE_CODE: 'settings/shareCode', SETTINGS_DISPLAY_NAME: 'settings/profile/display-name', SETTINGS_TIMEZONE: 'settings/profile/timezone', @@ -107,6 +108,8 @@ const ROUTES = { getRoute: (canChangeSize: 0 | 1) => `settings/subscription/subscription-size?canChangeSize=${canChangeSize}` as const, }, SETTINGS_SUBSCRIPTION_ADD_PAYMENT_CARD: 'settings/subscription/add-payment-card', + SETTINGS_SUBSCRIPTION_CHANGE_BILLING_CURRENCY: 'settings/subscription/change-billing-currency', + SETTINGS_SUBSCRIPTION_CHANGE_PAYMENT_CURRENCY: 'settings/subscription/add-payment-card/change-payment-currency', SETTINGS_SUBSCRIPTION_DISABLE_AUTO_RENEW_SURVEY: 'settings/subscription/disable-auto-renew-survey', SETTINGS_PRIORITY_MODE: 'settings/preferences/priority-mode', SETTINGS_LANGUAGE: 'settings/preferences/language', @@ -780,6 +783,17 @@ const ROUTES = { route: 'settings/workspaces/:policyID/reportFields', getRoute: (policyID: string) => `settings/workspaces/${policyID}/reportFields` as const, }, + WORKSPACE_EXPENSIFY_CARD: { + route: 'settings/workspaces/:policyID/expensify-card', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/expensify-card` as const, + }, + // TODO: uncomment after development is done + // 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, @@ -822,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', @@ -917,14 +932,87 @@ const ROUTES = { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/import/taxes', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/import/taxes` as const, }, - POLICY_ACCOUNTING_NETSUITE_SUBSIDIARY_SELECTOR: { - route: 'settings/workspaces/:policyID/accounting/net-suite/subsidiary-selector', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/net-suite/subsidiary-selector` as const, - }, RESTRICTED_ACTION: { route: 'restricted-action/workspace/:policyID', getRoute: (policyID: string) => `restricted-action/workspace/${policyID}` as const, }, + POLICY_ACCOUNTING_NETSUITE_SUBSIDIARY_SELECTOR: { + route: 'settings/workspaces/:policyID/accounting/netsuite/subsidiary-selector', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/netsuite/subsidiary-selector` as const, + }, + POLICY_ACCOUNTING_NETSUITE_IMPORT: { + route: 'settings/workspaces/:policyID/accounting/netsuite/import', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/netsuite/import` as const, + }, + POLICY_ACCOUNTING_NETSUITE_EXPORT: { + route: 'settings/workspaces/:policyID/connections/netsuite/export/', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/connections/netsuite/export/` as const, + }, + POLICY_ACCOUNTING_NETSUITE_PREFERRED_EXPORTER_SELECT: { + route: 'settings/workspaces/:policyID/connections/netsuite/export/preferred-exporter/select', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/connections/netsuite/export/preferred-exporter/select` as const, + }, + POLICY_ACCOUNTING_NETSUITE_DATE_SELECT: { + route: 'settings/workspaces/:policyID/connections/netsuite/export/date/select', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/connections/netsuite/export/date/select` as const, + }, + POLICY_ACCOUNTING_NETSUITE_EXPORT_EXPENSES: { + route: 'settings/workspaces/:policyID/connections/netsuite/export/expenses/:expenseType', + getRoute: (policyID: string, expenseType: ValueOf) => + `settings/workspaces/${policyID}/connections/netsuite/export/expenses/${expenseType}` as const, + }, + POLICY_ACCOUNTING_NETSUITE_EXPORT_EXPENSES_DESTINATION_SELECT: { + route: 'settings/workspaces/:policyID/connections/netsuite/export/expenses/:expenseType/destination/select', + getRoute: (policyID: string, expenseType: ValueOf) => + `settings/workspaces/${policyID}/connections/netsuite/export/expenses/${expenseType}/destination/select` as const, + }, + POLICY_ACCOUNTING_NETSUITE_EXPORT_EXPENSES_VENDOR_SELECT: { + route: 'settings/workspaces/:policyID/connections/netsuite/export/expenses/:expenseType/vendor/select', + getRoute: (policyID: string, expenseType: ValueOf) => + `settings/workspaces/${policyID}/connections/netsuite/export/expenses/${expenseType}/vendor/select` as const, + }, + POLICY_ACCOUNTING_NETSUITE_EXPORT_EXPENSES_PAYABLE_ACCOUNT_SELECT: { + route: 'settings/workspaces/:policyID/connections/netsuite/export/expenses/:expenseType/payable-account/select', + getRoute: (policyID: string, expenseType: ValueOf) => + `settings/workspaces/${policyID}/connections/netsuite/export/expenses/${expenseType}/payable-account/select` as const, + }, + POLICY_ACCOUNTING_NETSUITE_EXPORT_EXPENSES_JOURNAL_POSTING_PREFERENCE_SELECT: { + route: 'settings/workspaces/:policyID/connections/netsuite/export/expenses/:expenseType/journal-posting-preference/select', + getRoute: (policyID: string, expenseType: ValueOf) => + `settings/workspaces/${policyID}/connections/netsuite/export/expenses/${expenseType}/journal-posting-preference/select` as const, + }, + POLICY_ACCOUNTING_NETSUITE_RECEIVABLE_ACCOUNT_SELECT: { + route: 'settings/workspaces/:policyID/connections/netsuite/export/receivable-account/select', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/connections/netsuite/export/receivable-account/select` as const, + }, + POLICY_ACCOUNTING_NETSUITE_INVOICE_ITEM_PREFERENCE_SELECT: { + route: 'settings/workspaces/:policyID/connections/netsuite/export/invoice-item-preference/select', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/connections/netsuite/export/invoice-item-preference/select` as const, + }, + POLICY_ACCOUNTING_NETSUITE_INVOICE_ITEM_SELECT: { + route: 'settings/workspaces/:policyID/connections/netsuite/export/invoice-item-preference/invoice-item/select', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/connections/netsuite/export/invoice-item-preference/invoice-item/select` as const, + }, + POLICY_ACCOUNTING_NETSUITE_TAX_POSTING_ACCOUNT_SELECT: { + route: 'settings/workspaces/:policyID/connections/netsuite/export/tax-posting-account/select', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/connections/netsuite/export/tax-posting-account/select` as const, + }, + POLICY_ACCOUNTING_NETSUITE_PROVINCIAL_TAX_POSTING_ACCOUNT_SELECT: { + route: 'settings/workspaces/:policyID/connections/netsuite/export/provincial-tax-posting-account/select', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/connections/netsuite/export/provincial-tax-posting-account/select` 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 6e3d1f3276e9..8214c04cef75 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -41,6 +41,7 @@ const SCREENS = { SAVE_THE_WORLD: 'Settings_TeachersUnite', APP_DOWNLOAD_LINKS: 'Settings_App_Download_Links', ADD_DEBIT_CARD: 'Settings_Add_Debit_Card', + ADD_PAYMENT_CARD_CHANGE_CURRENCY: 'Settings_Add_Payment_Card_Change_Currency', ADD_BANK_ACCOUNT: 'Settings_Add_Bank_Account', CLOSE: 'Settings_Close', TWO_FACTOR_AUTH: 'Settings_TwoFactorAuth', @@ -104,6 +105,8 @@ const SCREENS = { SIZE: 'Settings_Subscription_Size', ADD_PAYMENT_CARD: 'Settings_Subscription_Add_Payment_Card', DISABLE_AUTO_RENEW_SURVEY: 'Settings_Subscription_DisableAutoRenewSurvey', + CHANGE_BILLING_CURRENCY: 'Settings_Subscription_Change_Billing_Currency', + CHANGE_PAYMENT_CURRENCY: 'Settings_Subscription_Change_Payment_Currency', }, }, SAVE_THE_WORLD: { @@ -269,7 +272,24 @@ const SCREENS = { XERO_EXPORT_PREFERRED_EXPORTER_SELECT: 'Workspace_Accounting_Xero_Export_Preferred_Exporter_Select', 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', + NETSUITE_SUBSIDIARY_SELECTOR: 'Policy_Accounting_NetSuite_Subsidiary_Selector', + NETSUITE_IMPORT: 'Policy_Accounting_NetSuite_Import', + NETSUITE_EXPORT: 'Policy_Accounting_NetSuite_Export', + NETSUITE_PREFERRED_EXPORTER_SELECT: 'Policy_Accounting_NetSuite_Preferred_Exporter_Select', + NETSUITE_DATE_SELECT: 'Policy_Accounting_NetSuite_Date_Select', + NETSUITE_EXPORT_EXPENSES: 'Policy_Accounting_NetSuite_Export_Expenses', + NETSUITE_EXPORT_EXPENSES_DESTINATION_SELECT: 'Policy_Accounting_NetSuite_Export_Expenses_Destination_Select', + NETSUITE_EXPORT_EXPENSES_VENDOR_SELECT: 'Policy_Accounting_NetSuite_Export_Expenses_Vendor_Select', + NETSUITE_EXPORT_EXPENSES_PAYABLE_ACCOUNT_SELECT: 'Policy_Accounting_NetSuite_Export_Expenses_Payable_Account_Select', + NETSUITE_EXPORT_EXPENSES_JOURNAL_POSTING_PREFERENCE_SELECT: 'Policy_Accounting_NetSuite_Export_Expenses_Journal_Posting_Preference_Select', + NETSUITE_RECEIVABLE_ACCOUNT_SELECT: 'Policy_Accounting_NetSuite_Receivable_Account_Select', + NETSUITE_INVOICE_ITEM_PREFERENCE_SELECT: 'Policy_Accounting_NetSuite_Invoice_Item_Preference_Select', + NETSUITE_INVOICE_ITEM_SELECT: 'Policy_Accounting_NetSuite_Invoice_Item_Select', + NETSUITE_TAX_POSTING_ACCOUNT_SELECT: 'Policy_Accounting_NetSuite_Tax_Posting_Account_Select', + NETSUITE_PROVINCIAL_TAX_POSTING_ACCOUNT_SELECT: 'Policy_Accounting_NetSuite_Provincial_Tax_Posting_Account_Select', + 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', @@ -278,6 +298,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', @@ -359,6 +381,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/AddPaymentCard/PaymentCardChangeCurrencyForm.tsx b/src/components/AddPaymentCard/PaymentCardChangeCurrencyForm.tsx new file mode 100644 index 000000000000..f967272ac63c --- /dev/null +++ b/src/components/AddPaymentCard/PaymentCardChangeCurrencyForm.tsx @@ -0,0 +1,142 @@ +import React, {useCallback, useMemo, useState} from 'react'; +import {View} from 'react-native'; +import type {ValueOf} from 'type-fest'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; +import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import SelectionList from '@components/SelectionList'; +import RadioListItem from '@components/SelectionList/RadioListItem'; +import TextInput from '@components/TextInput'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as ValidationUtils from '@libs/ValidationUtils'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import INPUT_IDS from '@src/types/form/ChangeBillingCurrencyForm'; +import PaymentCardCurrencyHeader from './PaymentCardCurrencyHeader'; +import PaymentCardCurrencyModal from './PaymentCardCurrencyModal'; + +type PaymentCardFormProps = { + initialCurrency?: ValueOf; + isSecurityCodeRequired?: boolean; + changeBillingCurrency: (currency?: ValueOf, values?: FormOnyxValues) => void; +}; + +const REQUIRED_FIELDS = [INPUT_IDS.SECURITY_CODE]; + +function PaymentCardChangeCurrencyForm({changeBillingCurrency, isSecurityCodeRequired, initialCurrency}: PaymentCardFormProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const [isCurrencyModalVisible, setIsCurrencyModalVisible] = useState(false); + const [currency, setCurrency] = useState>(initialCurrency ?? CONST.PAYMENT_CARD_CURRENCY.USD); + + const validate = (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, REQUIRED_FIELDS); + + if (values.securityCode && !ValidationUtils.isValidSecurityCode(values.securityCode)) { + errors.securityCode = translate('addPaymentCardPage.error.securityCode'); + } + + return errors; + }; + + const {sections} = useMemo( + () => ({ + sections: [ + { + data: (Object.keys(CONST.PAYMENT_CARD_CURRENCY) as Array>).map((currencyItem) => ({ + text: currencyItem, + value: currencyItem, + keyForList: currencyItem, + isSelected: currencyItem === currency, + })), + }, + ], + }), + [currency], + ); + + const showCurrenciesModal = useCallback(() => { + setIsCurrencyModalVisible(true); + }, []); + + const changeCurrency = useCallback((selectedCurrency: ValueOf) => { + setCurrency(selectedCurrency); + setIsCurrencyModalVisible(false); + }, []); + + const selectCurrency = useCallback( + (selectedCurrency: ValueOf) => { + setCurrency(selectedCurrency); + changeBillingCurrency(selectedCurrency); + }, + [changeBillingCurrency], + ); + + if (isSecurityCodeRequired) { + return ( + changeBillingCurrency(currency, values)} + submitButtonText={translate('common.save')} + scrollContextEnabled + style={[styles.mh5, styles.flexGrow1]} + > + + <> + + + + + + >} + currentCurrency={currency} + onCurrencyChange={changeCurrency} + onClose={() => setIsCurrencyModalVisible(false)} + /> + + ); + } + + return ( + + } + initiallyFocusedOptionKey={currency} + containerStyle={[styles.mhn5]} + sections={sections} + onSelectRow={(option) => { + selectCurrency(option.value); + }} + showScrollIndicator + shouldStopPropagation + shouldUseDynamicMaxToRenderPerBatch + ListItem={RadioListItem} + /> + + ); +} + +PaymentCardChangeCurrencyForm.displayName = 'PaymentCardChangeCurrencyForm'; + +export default PaymentCardChangeCurrencyForm; diff --git a/src/components/AddPaymentCard/PaymentCardCurrencyHeader.tsx b/src/components/AddPaymentCard/PaymentCardCurrencyHeader.tsx new file mode 100644 index 000000000000..e5142aec8efc --- /dev/null +++ b/src/components/AddPaymentCard/PaymentCardCurrencyHeader.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import {View} from 'react-native'; +import Text from '@components/Text'; +import TextLink from '@components/TextLink'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import CONST from '@src/CONST'; + +function PaymentCardCurrencyHeader({isSectionList}: {isSectionList?: boolean}) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + return ( + + + {`${translate('billingCurrency.note')}`}{' '} + {`${translate('billingCurrency.noteLink')}`}{' '} + {`${translate('billingCurrency.noteDetails')}`} + + + ); +} + +PaymentCardCurrencyHeader.displayName = 'PaymentCardCurrencyHeader'; + +export default PaymentCardCurrencyHeader; diff --git a/src/components/AddPaymentCard/PaymentCardCurrencyModal.tsx b/src/components/AddPaymentCard/PaymentCardCurrencyModal.tsx index 60fa838b0577..c3c38c4aec72 100644 --- a/src/components/AddPaymentCard/PaymentCardCurrencyModal.tsx +++ b/src/components/AddPaymentCard/PaymentCardCurrencyModal.tsx @@ -1,4 +1,5 @@ import React, {useMemo} from 'react'; +import type {ValueOf} from 'type-fest'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import Modal from '@components/Modal'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -13,20 +14,20 @@ type PaymentCardCurrencyModalProps = { /** Whether the modal is visible */ isVisible: boolean; - /** The list of years to render */ - currencies: Array; + /** The list of currencies to render */ + currencies: Array>; - /** Currently selected year */ - currentCurrency: keyof typeof CONST.CURRENCY; + /** Currently selected currency */ + currentCurrency: ValueOf; - /** Function to call when the user selects a year */ - onCurrencyChange?: (currency: keyof typeof CONST.CURRENCY) => void; + /** Function to call when the user selects a currency */ + onCurrencyChange?: (currency: ValueOf) => void; - /** Function to call when the user closes the year picker */ + /** Function to call when the user closes the currency picker */ onClose?: () => void; }; -function PaymentCardCurrencyModal({isVisible, currencies, currentCurrency = CONST.CURRENCY.USD, onCurrencyChange, onClose}: PaymentCardCurrencyModalProps) { +function PaymentCardCurrencyModal({isVisible, currencies, currentCurrency = CONST.PAYMENT_CARD_CURRENCY.USD, onCurrencyChange, onClose}: PaymentCardCurrencyModalProps) { const {isSmallScreenWidth} = useWindowDimensions(); const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -57,7 +58,7 @@ function PaymentCardCurrencyModal({isVisible, currencies, currentCurrency = CONS useNativeDriver > , currency?: ValueOf) => void; + addPaymentCard: (values: FormOnyxValues, currency?: ValueOf) => void; submitButtonText: string; /** Custom content to display in the footer after card form */ footerContent?: ReactNode; /** Custom content to display in the header before card form */ headerContent?: ReactNode; + /** object to get currency route details from */ + currencySelectorRoute?: typeof ROUTES.SETTINGS_SUBSCRIPTION_CHANGE_PAYMENT_CURRENCY; }; function IAcceptTheLabel() { @@ -61,6 +62,7 @@ const REQUIRED_FIELDS = [ INPUT_IDS.SECURITY_CODE, INPUT_IDS.ADDRESS_ZIP_CODE, INPUT_IDS.ADDRESS_STATE, + INPUT_IDS.CURRENCY, ]; const CARD_TYPES = { @@ -127,42 +129,44 @@ function PaymentCardForm({ showStateSelector, footerContent, headerContent, + currencySelectorRoute, }: PaymentCardFormProps) { const styles = useThemeStyles(); + const [data] = useOnyx(ONYXKEYS.FORMS.ADD_PAYMENT_CARD_FORM); + const {translate} = useLocalize(); const route = useRoute(); const label = CARD_LABELS[isDebitCard ? CARD_TYPES.DEBIT_CARD : CARD_TYPES.PAYMENT_CARD]; const cardNumberRef = useRef(null); - const [isCurrencyModalVisible, setIsCurrencyModalVisible] = useState(false); - const [currency, setCurrency] = useState(CONST.CURRENCY.USD); + const [cardNumber, setCardNumber] = useState(''); - const validate = (values: FormOnyxValues): FormInputErrors => { + const validate = (values: FormOnyxValues): FormInputErrors => { const errors = ValidationUtils.getFieldRequiredErrors(values, REQUIRED_FIELDS); if (values.nameOnCard && !ValidationUtils.isValidLegalName(values.nameOnCard)) { - errors.nameOnCard = translate('addDebitCardPage.error.invalidName'); + errors.nameOnCard = translate(label.error.nameOnCard); } if (values.cardNumber && !ValidationUtils.isValidDebitCard(values.cardNumber.replace(/ /g, ''))) { - errors.cardNumber = translate('addDebitCardPage.error.debitCardNumber'); + errors.cardNumber = translate(label.error.cardNumber); } if (values.expirationDate && !ValidationUtils.isValidExpirationDate(values.expirationDate)) { - errors.expirationDate = translate('addDebitCardPage.error.expirationDate'); + errors.expirationDate = translate(label.error.expirationDate); } if (values.securityCode && !ValidationUtils.isValidSecurityCode(values.securityCode)) { - errors.securityCode = translate('addDebitCardPage.error.securityCode'); + errors.securityCode = translate(label.error.securityCode); } if (values.addressStreet && !ValidationUtils.isValidAddress(values.addressStreet)) { - errors.addressStreet = translate('addDebitCardPage.error.addressStreet'); + errors.addressStreet = translate(label.error.addressStreet); } if (values.addressZipCode && !ValidationUtils.isValidZipCode(values.addressZipCode)) { - errors.addressZipCode = translate('addDebitCardPage.error.addressZipCode'); + errors.addressZipCode = translate(label.error.addressZipCode); } if (!values.acceptTerms) { @@ -172,13 +176,21 @@ function PaymentCardForm({ return errors; }; - const showCurrenciesModal = useCallback(() => { - setIsCurrencyModalVisible(true); - }, []); + const onChangeCardNumber = useCallback((newValue: string) => { + // replace all characters that are not spaces or digits + let validCardNumber = newValue.replace(/[^\d ]/g, ''); + + // gets only the first 16 digits if the inputted number have more digits than that + validCardNumber = validCardNumber.match(/(?:\d *){1,16}/)?.[0] ?? ''; - const changeCurrency = useCallback((newCurrency: keyof typeof CONST.CURRENCY) => { - setCurrency(newCurrency); - setIsCurrencyModalVisible(false); + // add the spacing between every 4 digits + validCardNumber = + validCardNumber + .replace(/ /g, '') + .match(/.{1,4}/g) + ?.join(' ') ?? ''; + + setCardNumber(validCardNumber); }, []); if (!shouldShowPaymentCardForm) { @@ -189,9 +201,9 @@ function PaymentCardForm({ <> {headerContent} addPaymentCard(formData, currency)} + onSubmit={addPaymentCard} submitButtonText={submitButtonText} scrollContextEnabled style={[styles.mh5, styles.flexGrow1]} @@ -199,15 +211,19 @@ function PaymentCardForm({ )} {!!showCurrencyField && ( - - {(isHovered) => ( - - )} - + + + )} {!!showAcceptTerms && ( @@ -298,19 +309,11 @@ function PaymentCardForm({ 'common.privacyPolicy', )}`} inputID={INPUT_IDS.ACCEPT_TERMS} - defaultValue={false} + defaultValue={!!data?.acceptTerms} LabelComponent={IAcceptTheLabel} /> )} - - } - currentCurrency={currency} - onCurrencyChange={changeCurrency} - onClose={() => setIsCurrencyModalVisible(false)} - /> {footerContent} diff --git a/src/components/ButtonWithDropdownMenu/types.ts b/src/components/ButtonWithDropdownMenu/types.ts index 1ad2ccb0d717..702f0380ceef 100644 --- a/src/components/ButtonWithDropdownMenu/types.ts +++ b/src/components/ButtonWithDropdownMenu/types.ts @@ -23,6 +23,10 @@ type DropdownOption = { iconDescription?: string; onSelected?: () => void; disabled?: boolean; + iconFill?: string; + interactive?: boolean; + numberOfLinesTitle?: number; + titleStyle?: ViewStyle; }; type ButtonWithDropdownMenuProps = { diff --git a/src/components/Composer/index.tsx b/src/components/Composer/index.tsx index 3a8a4e724948..f4a5174c2602 100755 --- a/src/components/Composer/index.tsx +++ b/src/components/Composer/index.tsx @@ -5,7 +5,7 @@ import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {flushSync} from 'react-dom'; // eslint-disable-next-line no-restricted-imports import type {DimensionValue, NativeSyntheticEvent, Text as RNText, TextInput, TextInputKeyPressEventData, TextInputSelectionChangeEventData, TextStyle} from 'react-native'; -import {StyleSheet, View} from 'react-native'; +import {DeviceEventEmitter, StyleSheet, View} from 'react-native'; import type {AnimatedMarkdownTextInputRef} from '@components/RNMarkdownTextInput'; import RNMarkdownTextInput from '@components/RNMarkdownTextInput'; import Text from '@components/Text'; @@ -74,7 +74,7 @@ function Composer( }, isReportActionCompose = false, isComposerFullSize = false, - shouldContainScroll = false, + shouldContainScroll = true, isGroupPolicyReport = false, ...props }: ComposerProps, @@ -105,6 +105,7 @@ function Composer( const [isRendered, setIsRendered] = useState(false); const isScrollBarVisible = useIsScrollBarVisible(textInput, value ?? ''); const [prevScroll, setPrevScroll] = useState(); + const isReportFlatListScrolling = useRef(false); useEffect(() => { if (!shouldClear) { @@ -249,6 +250,29 @@ function Composer( }; }, []); + useEffect(() => { + const scrollingListener = DeviceEventEmitter.addListener(CONST.EVENTS.SCROLLING, (scrolling) => { + isReportFlatListScrolling.current = scrolling; + }); + + return () => scrollingListener.remove(); + }, []); + + useEffect(() => { + const handleWheel = (e: MouseEvent) => { + if (isReportFlatListScrolling.current) { + e.preventDefault(); + return; + } + e.stopPropagation(); + }; + textInput.current?.addEventListener('wheel', handleWheel, {passive: false}); + + return () => { + textInput.current?.removeEventListener('wheel', handleWheel); + }; + }, []); + useEffect(() => { if (!textInput.current || prevScroll === undefined) { return; 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 ( + <> +