diff --git a/.eslintrc.js b/.eslintrc.js index c2198da60c52..56a5236a7899 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -94,7 +94,6 @@ module.exports = { rules: { 'prefer-regex-literals': 'off', 'rulesdir/no-multiple-onyx-in-file': 'off', - 'rulesdir/onyx-props-must-have-default': 'off', 'react-native-a11y/has-accessibility-hint': ['off'], 'react/jsx-no-constructed-context-values': 'error', 'react-native-a11y/has-valid-accessibility-descriptors': [ diff --git a/.github/scripts/verifyRedirect.sh b/.github/scripts/verifyRedirect.sh new file mode 100644 index 000000000000..737d9bffacf9 --- /dev/null +++ b/.github/scripts/verifyRedirect.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# HelpDot - Verifies that redirects.csv does not have any duplicates +# Duplicate sourceURLs break redirection on cloudflare pages + +declare -r REDIRECTS_FILE="docs/redirects.csv" + +duplicates=$(awk -F, 'a[$1]++{print $1}' $REDIRECTS_FILE) + +if [[ -z "$duplicates" ]]; then + exit 0 +fi + +echo "duplicate redirects are not allowed: $duplicates" +exit 1 diff --git a/.github/workflows/deployExpensifyHelp.yml b/.github/workflows/deployExpensifyHelp.yml index d4577e112d59..699bd379fb77 100644 --- a/.github/workflows/deployExpensifyHelp.yml +++ b/.github/workflows/deployExpensifyHelp.yml @@ -36,6 +36,9 @@ jobs: - name: Create docs routes file run: ./.github/scripts/createDocsRoutes.sh + + - name: Check duplicates in redirect.csv + run: ./.github/scripts/verifyRedirect.sh - name: Build with Jekyll uses: actions/jekyll-build-pages@0143c158f4fa0c5dcd99499a5d00859d79f70b0e diff --git a/.github/workflows/platformDeploy.yml b/.github/workflows/platformDeploy.yml index d97ea2b86269..bb850e6eda10 100644 --- a/.github/workflows/platformDeploy.yml +++ b/.github/workflows/platformDeploy.yml @@ -87,11 +87,12 @@ jobs: MYAPP_UPLOAD_STORE_PASSWORD: ${{ secrets.MYAPP_UPLOAD_STORE_PASSWORD }} MYAPP_UPLOAD_KEY_PASSWORD: ${{ secrets.MYAPP_UPLOAD_KEY_PASSWORD }} - - name: Run Fastlane production - if: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} - run: bundle exec fastlane android production - env: - VERSION: ${{ env.VERSION_CODE }} + # Note: Android production deploys are temporarily disabled until https://github.com/Expensify/App/issues/40108 is resolved + # - name: Run Fastlane production + # if: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} + # run: bundle exec fastlane android production + # env: + # VERSION: ${{ env.VERSION_CODE }} - name: Archive Android sourcemaps uses: actions/upload-artifact@v3 @@ -158,7 +159,7 @@ jobs: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} GCP_GEOLOCATION_API_KEY: $${{ secrets.GCP_GEOLOCATION_API_KEY_PRODUCTION }} - + - name: Build staging desktop app if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index 156b9764bcca..f20939f9df0a 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -30,7 +30,7 @@ jobs: # - git diff is used to see the files that were added on this branch # - gh pr view is used to list files touched by this PR. Git diff may give false positives if the branch isn't up-to-date with main # - wc counts the words in the result of the intersection - count_new_js=$(comm -1 -2 <(git diff --name-only --diff-filter=A origin/main HEAD -- 'src/*.js') <(gh pr view ${{ github.event.pull_request.number }} --json files | jq -r '.files | map(.path) | .[]') | wc -l) + count_new_js=$(comm -1 -2 <(git diff --name-only --diff-filter=A origin/main HEAD -- 'src/*.js' '__mocks__/*.js' '.storybook/*.js' 'assets/*.js' 'config/*.js' 'desktop/*.js' 'jest/*.js' 'scripts/*.js' 'tests/*.js' 'web/*.js' 'workflow_tests/*.js' '.github/libs/*.js' '.github/scripts/*.js') <(gh pr view ${{ github.event.pull_request.number }} --json files | jq -r '.files | map(.path) | .[]') | wc -l) if [ "$count_new_js" -gt "0" ]; then echo "ERROR: Found new JavaScript files in the project; use TypeScript instead." exit 1 diff --git a/.well-known/apple-app-site-association b/.well-known/apple-app-site-association index b3adf0f59b9c..a2c7365f7de8 100644 --- a/.well-known/apple-app-site-association +++ b/.well-known/apple-app-site-association @@ -32,10 +32,6 @@ "/": "/iou/*", "comment": "I Owe You reports" }, - { - "/": "/request/*", - "comment": "Money request" - }, { "/": "/enable-payments/*", "comment": "Payments setup" @@ -54,11 +50,11 @@ }, { "/": "/split/*", - "comment": "Split Bill" + "comment": "Split Expense" }, { "/": "/request/*", - "comment": "Request Money" + "comment": "Submit Expense" }, { "/": "/new/*", @@ -82,7 +78,7 @@ }, { "/": "/send/*", - "comment": "Send money" + "comment": "Pay someone" }, { "/": "/money2020/*", diff --git a/README.md b/README.md index 026a63606db0..8adadfc9cbe6 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,16 @@ If you want to run the app on an actual physical iOS device, please follow the i ## Running the MacOS desktop app 🖥 * To run the **Development app**, run: `npm run desktop`, this will start a new Electron process running on your MacOS desktop in the `dist/Mac` folder. +## Receiving Notifications +To receive notifications on development build of the app while hitting the Staging or Production API, you need to use the production airship config. +### Android +1. Copy the [production config](https://github.com/Expensify/App/blob/d7c1256f952c0020344d809ee7299b49a4c70db2/android/app/src/main/assets/airshipconfig.properties#L1-L7) to the [development config](https://github.com/Expensify/App/blob/d7c1256f952c0020344d809ee7299b49a4c70db2/android/app/src/development/assets/airshipconfig.properties#L1-L8). +2. Rebuild the app. + +### iOS +1. Replace the [development key and secret](https://github.com/Expensify/App/blob/d7c1256f952c0020344d809ee7299b49a4c70db2/ios/AirshipConfig.plist#L7-L10) with the [production values](https://github.com/Expensify/App/blob/d7c1256f952c0020344d809ee7299b49a4c70db2/ios/AirshipConfig.plist#L11-L14). +2. Rebuild the app. + ## Troubleshooting 1. If you are having issues with **_Getting Started_**, please reference [React Native's Documentation](https://reactnative.dev/docs/environment-setup) 2. If you are running into CORS errors like (in the browser dev console) diff --git a/android/app/build.gradle b/android/app/build.gradle index da340184e7c8..0db4b032ec9d 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -98,8 +98,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001046210 - versionName "1.4.62-10" + versionCode 1001046300 + versionName "1.4.63-0" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/docs/articles/expensify-classic/integrations/HR-integrations/Zenefits.md b/docs/articles/expensify-classic/integrations/HR-integrations/Zenefits.md index e94d915e4dfa..07421553aeb2 100644 --- a/docs/articles/expensify-classic/integrations/HR-integrations/Zenefits.md +++ b/docs/articles/expensify-classic/integrations/HR-integrations/Zenefits.md @@ -17,7 +17,7 @@ Expensify's direct integration with Zenefits will automatically: - Every employee record in Zenefits must have a work email address since we use this as the unique identifier in Expensify. - Zenefits will add all your employees to one Expensify workspace. If your company uses multiple Expensify workspaces, you'll be given the option to choose which workspace to connect to when you're setting up the integration. -## To connect your Expensify workspace to Gusto: +## To connect your Expensify workspace to Zenefits: 1. Navigate to **Settings > Workspaces > Group > _[Workspace Name]_ > Connections** 2. Scroll down to HR Integrations, click the **Connect to Zenefits** radio button, then click **Sync with Zenefits** diff --git a/docs/redirects.csv b/docs/redirects.csv index 51c8c7515e10..af595ecc5f83 100644 --- a/docs/redirects.csv +++ b/docs/redirects.csv @@ -152,7 +152,6 @@ https://help.expensify.com/articles/expensify-classic/manage-employees-and-repor https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/Invite-Members,https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Invite-Members https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/Removing-Members,https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Removing-Members https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/User-Roles,https://help.expensify.com/expensify-classic/hubs/copilots-and-delegates/ -https://help.expensify.com/articles/expensify-classic/reports/Currency,https://help.expensify.com/articles/expensify-classic/workspaces/Currency https://help.expensify.com/articles/expensify-classic/send-payments/Reimbursing-Reports,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/Reimbursing-Reports https://help.expensify.com/articles/expensify-classic/workspace-and-domain-settings/SAML-SSO,https://help.expensify.com/articles/expensify-classic/settings/Enable-two-factor-authentication https://help.expensify.com/articles/expensify-classic/workspaces/Budgets,https://help.expensify.com/articles/expensify-classic/workspaces/Set-budgets diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 4dea8203b477..ddcc64604581 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.4.62 + 1.4.63 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.62.10 + 1.4.63.0 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 0d1e81ade440..a57ffb9500c5 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.4.62 + 1.4.63 CFBundleSignature ???? CFBundleVersion - 1.4.62.10 + 1.4.63.0 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index a2dfb017df48..3c8e8c1fb63f 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -11,9 +11,9 @@ CFBundleName $(PRODUCT_NAME) CFBundleShortVersionString - 1.4.62 + 1.4.63 CFBundleVersion - 1.4.62.10 + 1.4.63.0 NSExtension NSExtensionPointIdentifier diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 1ebfc6bb1b62..f564bfd931e4 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1835,7 +1835,7 @@ PODS: - RNGoogleSignin (10.0.1): - GoogleSignIn (~> 7.0) - React-Core - - RNLiveMarkdown (0.1.47): + - RNLiveMarkdown (0.1.62): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -1853,9 +1853,9 @@ PODS: - React-utils - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNLiveMarkdown/common (= 0.1.47) + - RNLiveMarkdown/common (= 0.1.62) - Yoga - - RNLiveMarkdown/common (0.1.47): + - RNLiveMarkdown/common (0.1.62): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -2564,7 +2564,7 @@ SPEC CHECKSUMS: RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 RNGestureHandler: 1190c218cdaaf029ee1437076a3fbbc3297d89fb RNGoogleSignin: ccaa4a81582cf713eea562c5dd9dc1961a715fd0 - RNLiveMarkdown: f172c7199283dc9d21bccf7e21ea10741fd19e1d + RNLiveMarkdown: 47dfb50244f9ba1caefbc0efc6404ba41bf6620a RNLocalize: d4b8af4e442d4bcca54e68fc687a2129b4d71a81 rnmapbox-maps: 3e273e0e867a079ec33df9ee33bb0482434b897d RNPermissions: 8990fc2c10da3640938e6db1647cb6416095b729 @@ -2581,7 +2581,7 @@ SPEC CHECKSUMS: SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Turf: 13d1a92d969ca0311bbc26e8356cca178ce95da2 VisionCamera: 3033e0dd5272d46e97bcb406adea4ae0e6907abf - Yoga: 64cd2a583ead952b0315d5135bf39e053ae9be70 + Yoga: 1b901a6d6eeba4e8a2e8f308f708691cdb5db312 PODFILE CHECKSUM: a25a81f2b50270f0c0bd0aff2e2ebe4d0b4ec06d diff --git a/package-lock.json b/package-lock.json index 203e062de680..920fefc8242b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,19 @@ { "name": "new.expensify", - "version": "1.4.62-10", + "version": "1.4.63-0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.62-10", + "version": "1.4.63-0", "hasInstallScript": true, "license": "MIT", "dependencies": { "@babel/plugin-proposal-private-methods": "^7.18.6", "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@dotlottie/react-player": "^1.6.3", - "@expensify/react-native-live-markdown": "0.1.47", + "@expensify/react-native-live-markdown": "0.1.62", "@expo/metro-runtime": "~3.1.1", "@formatjs/intl-datetimeformat": "^6.10.0", "@formatjs/intl-listformat": "^7.2.2", @@ -57,7 +57,7 @@ "date-fns-tz": "^2.0.0", "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", - "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#13de5b0606662df33fa1392ad82cc11daadff52e", + "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#c0f7f3b6558fbeda0527c80d68460d418afef219", "expo": "^50.0.3", "expo-av": "~13.10.4", "expo-image": "1.11.0", @@ -207,7 +207,7 @@ "electron-builder": "24.13.2", "eslint": "^7.6.0", "eslint-config-airbnb-typescript": "^17.1.0", - "eslint-config-expensify": "^2.0.44", + "eslint-config-expensify": "^2.0.47", "eslint-config-prettier": "^8.8.0", "eslint-plugin-import": "^2.29.1", "eslint-plugin-jest": "^24.1.0", @@ -3570,9 +3570,9 @@ } }, "node_modules/@expensify/react-native-live-markdown": { - "version": "0.1.47", - "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.47.tgz", - "integrity": "sha512-zUfwgg6qq47MnGuynamDpdHSlBYwVKFV4Zc/2wlVzFcBndQOjOyFu04Ns8YDB4Gl80LyGvfAuBT/sU+kvmMU6g==", + "version": "0.1.62", + "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.62.tgz", + "integrity": "sha512-o70/tFIGZJ1U8U8aqTQu1HAZed6nt5LYWk74mrceRxQHOqsKhZgn2q5EuEy8EMIcnCGKjwxuDyZJbuRexgHx/A==", "engines": { "node": ">= 18.0.0" }, @@ -16462,10 +16462,8 @@ }, "node_modules/classnames": { "version": "2.5.0", - "license": "MIT", - "workspaces": [ - "benchmarks" - ] + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.0.tgz", + "integrity": "sha512-FQuRlyKinxrb5gwJlfVASbSrDlikDJ07426TrfPsdGLvtochowmkbnSFdQGJ2aoXrSetq5KqGV9emvWpy+91xA==" }, "node_modules/clean-css": { "version": "5.3.2", @@ -16551,7 +16549,8 @@ }, "node_modules/clipboard": { "version": "2.0.11", - "license": "MIT", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz", + "integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==", "dependencies": { "good-listener": "^1.2.2", "select": "^1.1.2", @@ -18058,7 +18057,8 @@ }, "node_modules/delegate": { "version": "3.2.0", - "license": "MIT" + "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", + "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" }, "node_modules/delegates": { "version": "1.0.0", @@ -19250,9 +19250,10 @@ } }, "node_modules/eslint-config-expensify": { - "version": "2.0.44", + "version": "2.0.48", + "resolved": "https://registry.npmjs.org/eslint-config-expensify/-/eslint-config-expensify-2.0.48.tgz", + "integrity": "sha512-PFegJ9Wfsiu5tgevhjA1toCxsZ8Etfk6pIjtXAnwpmVj7q4CtB3QDRusJoUDyJ3HrZr8AsFKViz7CU/CBTfwOw==", "dev": true, - "license": "ISC", "dependencies": { "@lwc/eslint-plugin-lwc": "^1.7.2", "babel-eslint": "^10.1.0", @@ -20212,8 +20213,8 @@ }, "node_modules/expensify-common": { "version": "1.0.0", - "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#13de5b0606662df33fa1392ad82cc11daadff52e", - "integrity": "sha512-/NAZoAXqeqFWHvC61dueqq9VjRugF69urUtDdDhsfvu1sQE2PCnBoM7a+ACoAEWRYrnP82cyHHhdSA8e7fPuAg==", + "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#c0f7f3b6558fbeda0527c80d68460d418afef219", + "integrity": "sha512-zz0/y0apISP1orxXEQOgn+Uod45O4wVypwwtaqcDPV4dH1tC3i4L98NoLSZvLn7Y17EcceSkfN6QCEsscgFTDQ==", "license": "MIT", "dependencies": { "classnames": "2.5.0", @@ -20266,6 +20267,8 @@ }, "node_modules/expensify-common/node_modules/ua-parser-js": { "version": "1.0.37", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", + "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", "funding": [ { "type": "opencollective", @@ -20280,7 +20283,6 @@ "url": "https://github.com/sponsors/faisalman" } ], - "license": "MIT", "engines": { "node": "*" } @@ -21728,7 +21730,8 @@ }, "node_modules/good-listener": { "version": "1.2.2", - "license": "MIT", + "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", + "integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==", "dependencies": { "delegate": "^3.1.2" } @@ -22779,7 +22782,8 @@ }, "node_modules/immediate": { "version": "3.0.6", - "license": "MIT" + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" }, "node_modules/import-fresh": { "version": "3.3.0", @@ -26838,7 +26842,8 @@ }, "node_modules/lie": { "version": "3.1.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", + "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", "dependencies": { "immediate": "~3.0.5" } @@ -26981,7 +26986,8 @@ }, "node_modules/localforage": { "version": "1.10.0", - "license": "Apache-2.0", + "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", + "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", "dependencies": { "lie": "3.1.1" } @@ -33311,7 +33317,8 @@ }, "node_modules/select": { "version": "1.1.2", - "license": "MIT" + "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", + "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==" }, "node_modules/select-hose": { "version": "2.0.0", diff --git a/package.json b/package.json index 43a3ed8cae6a..20d066eabebe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.62-10", + "version": "1.4.63-0", "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.", @@ -64,7 +64,7 @@ "@babel/plugin-proposal-private-methods": "^7.18.6", "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@dotlottie/react-player": "^1.6.3", - "@expensify/react-native-live-markdown": "0.1.47", + "@expensify/react-native-live-markdown": "0.1.62", "@expo/metro-runtime": "~3.1.1", "@formatjs/intl-datetimeformat": "^6.10.0", "@formatjs/intl-listformat": "^7.2.2", @@ -108,7 +108,7 @@ "date-fns-tz": "^2.0.0", "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", - "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#13de5b0606662df33fa1392ad82cc11daadff52e", + "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#c0f7f3b6558fbeda0527c80d68460d418afef219", "expo": "^50.0.3", "expo-av": "~13.10.4", "expo-image": "1.11.0", @@ -258,7 +258,7 @@ "electron-builder": "24.13.2", "eslint": "^7.6.0", "eslint-config-airbnb-typescript": "^17.1.0", - "eslint-config-expensify": "^2.0.44", + "eslint-config-expensify": "^2.0.47", "eslint-config-prettier": "^8.8.0", "eslint-plugin-import": "^2.29.1", "eslint-plugin-jest": "^24.1.0", diff --git a/src/CONFIG.ts b/src/CONFIG.ts index 76ea18d37d5f..9ed4242d7604 100644 --- a/src/CONFIG.ts +++ b/src/CONFIG.ts @@ -48,6 +48,7 @@ export default { EXPENSIFY: { // Note: This will be EXACTLY what is set for EXPENSIFY_URL whether the proxy is enabled or not. EXPENSIFY_URL: expensifyURL, + SECURE_EXPENSIFY_URL: secureExpensifyUrl, NEW_EXPENSIFY_URL: newExpensifyURL, // The DEFAULT API is the API used by most environments, except staging, where we use STAGING (defined below) @@ -72,7 +73,7 @@ export default { IS_USING_LOCAL_WEB: useNgrok || expensifyURLRoot.includes('dev'), PUSHER: { APP_KEY: get(Config, 'PUSHER_APP_KEY', '268df511a204fbb60884'), - SUFFIX: get(Config, 'PUSHER_DEV_SUFFIX', ''), + SUFFIX: ENVIRONMENT === CONST.ENVIRONMENT.DEV ? get(Config, 'PUSHER_DEV_SUFFIX', '') : '', CLUSTER: 'mt1', }, SITE_TITLE: 'New Expensify', diff --git a/src/CONST.ts b/src/CONST.ts index f06578ef925d..1f16828783ab 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -638,9 +638,10 @@ const CONST = { LIMIT: 50, // OldDot Actions render getMessage from Web-Expensify/lib/Report/Action PHP files via getMessageOfOldDotReportAction in ReportActionsUtils.ts TYPE: { + ACTIONABLEJOINREQUEST: 'ACTIONABLEJOINREQUEST', ACTIONABLEMENTIONWHISPER: 'ACTIONABLEMENTIONWHISPER', + ACTIONABLETRACKEXPENSEWHISPER: 'ACTIONABLETRACKEXPENSEWHISPER', ADDCOMMENT: 'ADDCOMMENT', - ACTIONABLEJOINREQUEST: 'ACTIONABLEJOINREQUEST', APPROVED: 'APPROVED', CHANGEFIELD: 'CHANGEFIELD', // OldDot Action CHANGEPOLICY: 'CHANGEPOLICY', // OldDot Action @@ -773,6 +774,9 @@ const CONST = { INVITE: 'invited', NOTHING: 'nothing', }, + ACTIONABLE_TRACK_EXPENSE_WHISPER_RESOLUTION: { + NOTHING: 'nothing', + }, ACTIONABLE_MENTION_JOIN_WORKSPACE_RESOLUTION: { ACCEPT: 'accept', DECLINE: 'decline', @@ -880,7 +884,7 @@ const CONST = { }, TIMING: { CALCULATE_MOST_RECENT_LAST_MODIFIED_ACTION: 'calc_most_recent_last_modified_action', - SEARCH_RENDER: 'search_render', + CHAT_FINDER_RENDER: 'search_render', CHAT_RENDER: 'chat_render', OPEN_REPORT: 'open_report', HOMEPAGE_INITIAL_RENDER: 'homepage_initial_render', @@ -1216,9 +1220,9 @@ const CONST = { NOT_IMPORTED: 'NOT_IMPORTED', IMPORTED: 'IMPORTED', }, - QUICK_BOOKS_ONLINE: 'quickbooksOnline', + QUICKBOOKS_ONLINE: 'quickbooksOnline', - QUICK_BOOKS_IMPORTS: { + QUICKBOOKS_IMPORTS: { SYNC_CLASSES: 'syncClasses', ENABLE_NEW_CATEGORIES: 'enableNewCategories', SYNC_CUSTOMERS: 'syncCustomers', @@ -1364,7 +1368,7 @@ const CONST = { }, KYC_WALL_SOURCE: { - REPORT: 'REPORT', // The user attempted to pay a money request + REPORT: 'REPORT', // The user attempted to pay an expense ENABLE_WALLET: 'ENABLE_WALLET', // The user clicked on the `Enable wallet` button on the Wallet page TRANSFER_BALANCE: 'TRANSFER_BALANCE', // The user attempted to transfer their wallet balance to their bank account or debit card }, @@ -1400,7 +1404,7 @@ const CONST = { }, IOU: { - // This is the transactionID used when going through the create money request flow so that it mimics a real transaction (like the edit flow) + // This is the transactionID used when going through the create expense flow so that it mimics a real transaction (like the edit flow) OPTIMISTIC_TRANSACTION_ID: '1', // Note: These payment types are used when building IOU reportAction message values in the server and should // not be changed. @@ -1412,6 +1416,9 @@ const CONST = { ACTION: { EDIT: 'edit', CREATE: 'create', + MOVE: 'move', + CATEGORIZE: 'categorize', + SHARE: 'share', }, DEFAULT_AMOUNT: 0, TYPE: { @@ -1434,6 +1441,7 @@ const CONST = { DELETE: 'delete', APPROVE: 'approve', TRACK: 'track', + MOVE: 'move', }, AMOUNT_MAX_LENGTH: 10, RECEIPT_STATE: { @@ -1453,6 +1461,11 @@ const CONST = { CANCEL_REASON: { PAYMENT_EXPIRED: 'CANCEL_REASON_PAYMENT_EXPIRED', }, + SHARE: { + ROLE: { + ACCOUNTANT: 'accountant', + }, + }, }, GROWL: { @@ -1606,25 +1619,23 @@ const CONST = { GENERAL_SETTINGS: 'generalSettings', }, CONNECTIONS: { - SYNC_STATUS: { - STARTING: 'starting', - FINISHED: 'finished', - PROGRESS: 'progress', - }, NAME: { // Here we will add other connections names when we add support for them QBO: 'quickbooksOnline', }, SYNC_STAGE_NAME: { STARTING_IMPORT: 'startingImport', - QBO_CUSTOMERS: 'quickbooksOnlineImportCustomers', - QBO_EMPLOYEES: 'quickbooksOnlineImportEmployees', - QBO_ACCOUNTS: 'quickbooksOnlineImportAccounts', - QBO_CLASSES: 'quickbooksOnlineImportClasses', - QBO_LOCATIONS: 'quickbooksOnlineImportLocations', - QBO_PROCESSING: 'quickbooksOnlineImportProcessing', - QBO_PAYMENTS: 'quickbooksOnlineSyncBillPayments', - QBO_TAX_CODES: 'quickbooksOnlineSyncTaxCodes', + QBO_IMPORT_MAIN: 'quickbooksOnlineImportMain', + QBO_IMPORT_CUSTOMERS: 'quickbooksOnlineImportCustomers', + QBO_IMPORT_EMPLOYEES: 'quickbooksOnlineImportEmployees', + QBO_IMPORT_ACCOUNTS: 'quickbooksOnlineImportAccounts', + QBO_IMPORT_CLASSES: 'quickbooksOnlineImportClasses', + QBO_IMPORT_LOCATIONS: 'quickbooksOnlineImportLocations', + QBO_IMPORT_PROCESSING: 'quickbooksOnlineImportProcessing', + QBO_SYNC_PAYMENTS: 'quickbooksOnlineSyncBillPayments', + QBO_IMPORT_TAX_CODES: 'quickbooksOnlineSyncTaxCodes', + QBO_CHECK_CONNECTION: 'quickbooksOnlineCheckConnection', + JOB_DONE: 'jobDone', }, }, }, @@ -3293,6 +3304,13 @@ const CONST = { SCAN: 'scan', DISTANCE: 'distance', }, + TAB_SEARCH: { + ALL: 'all', + SENT: 'sent', + DRAFTS: 'drafts', + WAITING_ON_YOU: 'waitingOnYou', + FINISHED: 'finished', + }, STATUS_TEXT_MAX_LENGTH: 100, DROPDOWN_BUTTON_SIZE: { @@ -3543,12 +3561,11 @@ const CONST = { ONBOARDING_CONCIERGE: { [onboardingChoices.TRACK]: - "# Welcome to Expensify, let's start tracking your expenses!\n" + - "Hi there, I'm Concierge. Chat with me here for anything you need.\n" + + "# Let's start tracking your expenses!\n" + '\n' + "To track your expenses, create a workspace to keep everything in one place. Here's how:\n" + '1. From the home screen, click the green + button > New Workspace\n' + - '2. Give your workspace a name (e.g. "My business expenses”).\n' + + '2. Give your workspace a name (e.g. "My business expenses").\n' + '\n' + 'Then, add expenses to your workspace:\n' + '1. Find your workspace using the search field.\n' + @@ -3557,8 +3574,7 @@ const CONST = { '\n' + "We'll store all expenses in your new workspace for easy access. Let me know if you have any questions!", [onboardingChoices.EMPLOYER]: - '# Welcome to Expensify, the fastest way to get paid back!\n' + - "Hi there, I'm Concierge. Chat with me here for anything you need.\n" + + '# Expensify is the fastest way to get paid back!\n' + '\n' + 'To submit expenses for reimbursement:\n' + '1. From the home screen, click the green + button > Request money.\n' + @@ -3566,21 +3582,19 @@ const CONST = { '\n' + "That'll send a request to get you paid back. Let me know if you have any questions!", [onboardingChoices.MANAGE_TEAM]: - "# Welcome to Expensify, let's start managing your team's expenses!\n" + - "Hi there, I'm Concierge. Chat with me here for anything you need.\n" + + "# Let's start managing your team's expenses!\n" + '\n' + "To manage your team's expenses, create a workspace to keep everything in one place. Here's how:\n" + '1. From the home screen, click the green + button > New Workspace\n' + - '2. Give your workspace a name (e.g. “Sales team expenses”).\n' + + '2. Give your workspace a name (e.g. "Sales team expenses").\n' + '\n' + - 'Then, invite your team to your workspace via the Members pane and connect a business bank account to reimburse them. Let me know if you have any questions!', + 'Then, invite your team to your workspace via the Members pane and [connect a business bank account](https://help.expensify.com/articles/new-expensify/bank-accounts/Connect-a-Bank-Account) to reimburse them. Let me know if you have any questions!', [onboardingChoices.PERSONAL_SPEND]: - "# Welcome to Expensify, let's start tracking your expenses!\n" + - "Hi there, I'm Concierge. Chat with me here for anything you need.\n" + + "# Let's start tracking your expenses! \n" + '\n' + "To track your expenses, create a workspace to keep everything in one place. Here's how:\n" + '1. From the home screen, click the green + button > New Workspace\n' + - '2. Give your workspace a name (e.g. "My expenses”).\n' + + '2. Give your workspace a name (e.g. "My expenses").\n' + '\n' + 'Then, add expenses to your workspace:\n' + '1. Find your workspace using the search field.\n' + @@ -3589,19 +3603,13 @@ const CONST = { '\n' + "We'll store all expenses in your new workspace for easy access. Let me know if you have any questions!", [onboardingChoices.CHAT_SPLIT]: - '# Welcome to Expensify, where splitting the bill is an easy conversation!\n' + - "Hi there, I'm Concierge. Chat with me here for anything you need.\n" + + '# Splitting the bill is as easy as a conversation!\n' + '\n' + 'To split an expense:\n' + '1. From the home screen, click the green + button > Request money.\n' + '2. Enter an amount or scan a receipt, then choose who you want to split it with.\n' + '\n' + "We'll send a request to each person so they can pay you back. Let me know if you have any questions!", - [onboardingChoices.LOOKING_AROUND]: - '# Welcome to Expensify!\n' + - "Hi there, I'm Concierge. Chat with me here for anything you need.\n" + - '\n' + - "Expensify is best known for expense and corporate card management, but we do a lot more than that. Let me know what you're interested in and I'll help get you started.", }, REPORT_FIELD_TITLE_FIELD_ID: 'text_title', @@ -4345,8 +4353,10 @@ const CONST = { } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; + type IOUType = ValueOf; +type IOUAction = ValueOf; -export type {Country, IOUType}; +export type {Country, IOUAction, IOUType}; export default CONST; diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 3959f76a626f..819680db0e8a 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -126,7 +126,7 @@ const ONYXKEYS = { /** The NVP with the last payment method used per policy */ NVP_LAST_PAYMENT_METHOD: 'nvp_private_lastPaymentMethod', - /** This NVP holds to most recent waypoints that a person has used when creating a distance request */ + /** This NVP holds to most recent waypoints that a person has used when creating a distance expense */ NVP_RECENT_WAYPOINTS: 'expensify_recentWaypoints', /** This NVP will be `true` if the user has ever dismissed the engagement modal on either OldDot or NewDot. If it becomes true it should stay true forever. */ @@ -312,15 +312,19 @@ const ONYXKEYS = { COLLECTION: { DOWNLOAD: 'download_', POLICY: 'policy_', - POLICY_MEMBERS: 'policyMembers_', POLICY_DRAFTS: 'policyDrafts_', - POLICY_MEMBERS_DRAFTS: 'policyMembersDrafts_', POLICY_JOIN_MEMBER: 'policyJoinMember_', POLICY_CATEGORIES: 'policyCategories_', POLICY_RECENTLY_USED_CATEGORIES: 'policyRecentlyUsedCategories_', POLICY_TAGS: 'policyTags_', POLICY_RECENTLY_USED_TAGS: 'nvp_recentlyUsedTags_', + // Whether the policy's connection data was attempted to be fetched in + // the current user session. As this state only exists client-side, it + // should not be included as part of the policy object. The policy + // object should mirror the data as it's stored in the database. + POLICY_HAS_CONNECTIONS_DATA_BEEN_FETCHED: 'policyHasConnectionsDataBeenFetched_', OLD_POLICY_RECENTLY_USED_TAGS: 'policyRecentlyUsedTags_', + POLICY_CONNECTION_SYNC_PROGRESS: 'policyConnectionSyncProgress_', WORKSPACE_INVITE_MEMBERS_DRAFT: 'workspaceInviteMembersDraft_', WORKSPACE_INVITE_MESSAGE_DRAFT: 'workspaceInviteMessageDraft_', REPORT: 'report_', @@ -340,20 +344,17 @@ const ONYXKEYS = { TRANSACTION: 'transactions_', TRANSACTION_VIOLATIONS: 'transactionViolations_', TRANSACTION_DRAFT: 'transactionsDraft_', - - // Holds temporary transactions used during the creation and edit flow + SKIP_CONFIRMATION: 'skipConfirmation_', TRANSACTION_BACKUP: 'transactionsBackup_', SPLIT_TRANSACTION_DRAFT: 'splitTransactionDraft_', PRIVATE_NOTES_DRAFT: 'privateNotesDraft_', NEXT_STEP: 'reportNextStep_', - // Manual request tab selector + // Manual expense tab selector SELECTED_TAB: 'selectedTab_', /** This is deprecated, but needed for a migration, so we still need to include it here so that it will be initialized in Onyx.init */ DEPRECATED_POLICY_MEMBER_LIST: 'policyMemberList_', - - POLICY_CONNECTION_SYNC_PROGRESS: 'policyConnectionSyncProgress_', }, /** List of Form ids */ @@ -526,10 +527,9 @@ type OnyxCollectionValuesMapping = { [ONYXKEYS.COLLECTION.POLICY_DRAFTS]: OnyxTypes.Policy; [ONYXKEYS.COLLECTION.POLICY_CATEGORIES]: OnyxTypes.PolicyCategories; [ONYXKEYS.COLLECTION.POLICY_TAGS]: OnyxTypes.PolicyTagList; - [ONYXKEYS.COLLECTION.POLICY_MEMBERS]: OnyxTypes.PolicyMembers; - [ONYXKEYS.COLLECTION.POLICY_MEMBERS_DRAFTS]: OnyxTypes.PolicyMember; [ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES]: OnyxTypes.RecentlyUsedCategories; - [ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyMembers; + [ONYXKEYS.COLLECTION.POLICY_HAS_CONNECTIONS_DATA_BEEN_FETCHED]: boolean; + [ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyEmployeeList; [ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT]: OnyxTypes.InvitedEmailsToAccountIDs; [ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MESSAGE_DRAFT]: string; [ONYXKEYS.COLLECTION.REPORT]: OnyxTypes.Report; @@ -545,6 +545,7 @@ type OnyxCollectionValuesMapping = { [ONYXKEYS.COLLECTION.SECURITY_GROUP]: OnyxTypes.SecurityGroup; [ONYXKEYS.COLLECTION.TRANSACTION]: OnyxTypes.Transaction; [ONYXKEYS.COLLECTION.TRANSACTION_DRAFT]: OnyxTypes.Transaction; + [ONYXKEYS.COLLECTION.SKIP_CONFIRMATION]: boolean; [ONYXKEYS.COLLECTION.TRANSACTION_BACKUP]: OnyxTypes.Transaction; [ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS]: OnyxTypes.TransactionViolations; [ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT]: OnyxTypes.Transaction; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index df5c510ca954..46f2e2fef049 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1,5 +1,6 @@ import type {ValueOf} from 'type-fest'; import type CONST from './CONST'; +import type {IOUAction, IOUType} from './CONST'; import type {IOURequestType} from './libs/actions/IOU'; import type AssertTypesNotEqual from './types/utils/AssertTypesNotEqual'; @@ -22,13 +23,18 @@ const ROUTES = { ALL_SETTINGS: 'all-settings', + SEARCH: { + route: '/search/:query', + getRoute: (query: string) => `search/${query}` as const, + }, + // This is a utility route used to go to the user's concierge chat, or the sign-in page if the user's not authenticated CONCIERGE: 'concierge', FLAG_COMMENT: { route: 'flag/:reportID/:reportActionID', getRoute: (reportID: string, reportActionID: string) => `flag/${reportID}/${reportActionID}` as const, }, - SEARCH: 'search', + CHAT_FINDER: 'chat-finder', DETAILS: { route: 'details', getRoute: (login: string) => `details?login=${encodeURIComponent(login)}` as const, @@ -205,7 +211,7 @@ const ROUTES = { EDIT_REQUEST: { route: 'r/:threadReportID/edit/:field/:tagIndex?', getRoute: (threadReportID: string, field: ValueOf, tagIndex?: number) => - `r/${threadReportID}/edit/${field}${typeof tagIndex === 'number' ? `/${tagIndex}` : ''}` as const, + `r/${threadReportID}/edit/${field as string}${typeof tagIndex === 'number' ? `/${tagIndex}` : ''}` as const, }, EDIT_CURRENCY_REQUEST: { route: 'r/:threadReportID/edit/currency', @@ -274,7 +280,7 @@ const ROUTES = { EDIT_SPLIT_BILL: { route: `r/:reportID/split/:reportActionID/edit/:field/:tagIndex?`, getRoute: (reportID: string, reportActionID: string, field: ValueOf, tagIndex?: number) => - `r/${reportID}/split/${reportActionID}/edit/${field}${typeof tagIndex === 'number' ? `/${tagIndex}` : ''}` as const, + `r/${reportID}/split/${reportActionID}/edit/${field as string}${typeof tagIndex === 'number' ? `/${tagIndex}` : ''}` as const, }, TASK_TITLE: { route: 'r/:reportID/title', @@ -301,127 +307,118 @@ const ROUTES = { getRoute: (reportID: string) => `r/${reportID}/members` as const, }, ROOM_INVITE: { - route: 'r/:reportID/invite', - getRoute: (reportID: string) => `r/${reportID}/invite` as const, + route: 'r/:reportID/invite/:role?', + getRoute: (reportID: string, role?: string) => `r/${reportID}/invite/${role}` as const, }, MONEY_REQUEST_PARTICIPANTS: { route: ':iouType/new/participants/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/participants/${reportID}` as const, + getRoute: (iouType: IOUType, reportID = '') => `${iouType}/new/participants/${reportID}` as const, }, MONEY_REQUEST_HOLD_REASON: { - route: ':iouType/edit/reason/:transactionID?', - getRoute: (iouType: string, transactionID: string, reportID: string, backTo: string) => `${iouType}/edit/reason/${transactionID}?backTo=${backTo}&reportID=${reportID}` as const, + route: ':type/edit/reason/:transactionID?', + getRoute: (type: ValueOf, transactionID: string, reportID: string, backTo: string) => + `${type}/edit/reason/${transactionID}?backTo=${backTo}&reportID=${reportID}` as const, }, MONEY_REQUEST_MERCHANT: { route: ':iouType/new/merchant/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/merchant/${reportID}` as const, + getRoute: (iouType: IOUType, reportID = '') => `${iouType}/new/merchant/${reportID}` as const, }, MONEY_REQUEST_RECEIPT: { route: ':iouType/new/receipt/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/receipt/${reportID}` as const, + getRoute: (iouType: IOUType, reportID = '') => `${iouType}/new/receipt/${reportID}` as const, }, MONEY_REQUEST_CREATE: { route: ':action/:iouType/start/:transactionID/:reportID', - getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string) => - `${action}/${iouType}/start/${transactionID}/${reportID}` as const, + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string) => `${action as string}/${iouType as string}/start/${transactionID}/${reportID}` as const, }, MONEY_REQUEST_STEP_CONFIRMATION: { route: ':action/:iouType/confirmation/:transactionID/:reportID', - getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string) => - `${action}/${iouType}/confirmation/${transactionID}/${reportID}` as const, + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string) => + `${action as string}/${iouType as string}/confirmation/${transactionID}/${reportID}` as const, }, MONEY_REQUEST_STEP_AMOUNT: { route: ':action/:iouType/amount/:transactionID/:reportID', - getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => - getUrlWithBackToParam(`${action}/${iouType}/amount/${transactionID}/${reportID}`, backTo), + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`${action as string}/${iouType as string}/amount/${transactionID}/${reportID}`, backTo), }, MONEY_REQUEST_STEP_TAX_RATE: { route: ':action/:iouType/taxRate/:transactionID/:reportID?', - getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => - getUrlWithBackToParam(`${action}/${iouType}/taxRate/${transactionID}/${reportID}`, backTo), + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`${action as string}/${iouType as string}/taxRate/${transactionID}/${reportID}`, backTo), }, MONEY_REQUEST_STEP_TAX_AMOUNT: { route: ':action/:iouType/taxAmount/:transactionID/:reportID?', - getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => - getUrlWithBackToParam(`${action}/${iouType}/taxAmount/${transactionID}/${reportID}`, backTo), + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`${action as string}/${iouType as string}/taxAmount/${transactionID}/${reportID}`, backTo), }, MONEY_REQUEST_STEP_CATEGORY: { route: ':action/:iouType/category/:transactionID/:reportID/:reportActionID?', - getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, backTo = '', reportActionID?: string) => - getUrlWithBackToParam(`${action}/${iouType}/category/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}`, backTo), + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '', reportActionID?: string) => + getUrlWithBackToParam(`${action as string}/${iouType as string}/category/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}`, backTo), }, MONEY_REQUEST_STEP_CURRENCY: { route: ':action/:iouType/currency/:transactionID/:reportID/:pageIndex?', - getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, pageIndex = '', currency = '', backTo = '') => - getUrlWithBackToParam(`${action}/${iouType}/currency/${transactionID}/${reportID}/${pageIndex}?currency=${currency}`, backTo), + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, pageIndex = '', currency = '', backTo = '') => + getUrlWithBackToParam(`${action as string}/${iouType as string}/currency/${transactionID}/${reportID}/${pageIndex}?currency=${currency}`, backTo), }, MONEY_REQUEST_STEP_DATE: { route: ':action/:iouType/date/:transactionID/:reportID', - getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => - getUrlWithBackToParam(`${action}/${iouType}/date/${transactionID}/${reportID}`, backTo), + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`${action as string}/${iouType as string}/date/${transactionID}/${reportID}`, backTo), }, MONEY_REQUEST_STEP_DESCRIPTION: { route: ':action/:iouType/description/:transactionID/:reportID/:reportActionID?', - getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, backTo = '', reportActionID?: string) => - getUrlWithBackToParam(`${action}/${iouType}/description/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}`, backTo), + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '', reportActionID?: string) => + getUrlWithBackToParam(`${action as string}/${iouType as string}/description/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}`, backTo), }, MONEY_REQUEST_STEP_DISTANCE: { route: ':action/:iouType/distance/:transactionID/:reportID', - getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => - getUrlWithBackToParam(`${action}/${iouType}/distance/${transactionID}/${reportID}`, backTo), + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`${action as string}/${iouType as string}/distance/${transactionID}/${reportID}`, backTo), }, MONEY_REQUEST_STEP_MERCHANT: { route: ':action/:iouType/merchant/:transactionID/:reportID', - getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => - getUrlWithBackToParam(`${action}/${iouType}/merchant/${transactionID}/${reportID}`, backTo), + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`${action as string}/${iouType as string}/merchant/${transactionID}/${reportID}`, backTo), }, MONEY_REQUEST_STEP_PARTICIPANTS: { - route: 'create/:iouType/participants/:transactionID/:reportID', - getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => - getUrlWithBackToParam(`create/${iouType}/participants/${transactionID}/${reportID}`, backTo), + route: ':action/:iouType/participants/:transactionID/:reportID', + getRoute: (iouType: IOUType, transactionID: string, reportID: string, backTo = '', action: IOUAction = 'create') => + getUrlWithBackToParam(`${action as string}/${iouType as string}/participants/${transactionID}/${reportID}`, backTo), }, MONEY_REQUEST_STEP_SCAN: { route: ':action/:iouType/scan/:transactionID/:reportID', - getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => - getUrlWithBackToParam(`${action}/${iouType}/scan/${transactionID}/${reportID}`, backTo), + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`${action as string}/${iouType as string}/scan/${transactionID}/${reportID}`, backTo), }, MONEY_REQUEST_STEP_TAG: { route: ':action/:iouType/tag/:orderWeight/:transactionID/:reportID/:reportActionID?', - getRoute: ( - action: ValueOf, - iouType: ValueOf, - orderWeight: number, - transactionID: string, - reportID: string, - backTo = '', - reportActionID?: string, - ) => getUrlWithBackToParam(`${action}/${iouType}/tag/${orderWeight}/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}`, backTo), + getRoute: (action: IOUAction, iouType: IOUType, orderWeight: number, transactionID: string, reportID: string, backTo = '', reportActionID?: string) => + getUrlWithBackToParam(`${action as string}/${iouType as string}/tag/${orderWeight}/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}`, backTo), }, MONEY_REQUEST_STEP_WAYPOINT: { route: ':action/:iouType/waypoint/:transactionID/:reportID/:pageIndex', - getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID?: string, pageIndex = '', backTo = '') => - getUrlWithBackToParam(`${action}/${iouType}/waypoint/${transactionID}/${reportID}/${pageIndex}`, backTo), + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID?: string, pageIndex = '', backTo = '') => + getUrlWithBackToParam(`${action as string}/${iouType as string}/waypoint/${transactionID}/${reportID}/${pageIndex}`, backTo), }, // This URL is used as a redirect to one of the create tabs below. This is so that we can message users with a link // straight to those flows without needing to have optimistic transaction and report IDs. MONEY_REQUEST_START: { route: 'start/:iouType/:iouRequestType', - getRoute: (iouType: ValueOf, iouRequestType: IOURequestType) => `start/${iouType}/${iouRequestType}` as const, + getRoute: (iouType: IOUType, iouRequestType: IOURequestType) => `start/${iouType as string}/${iouRequestType}` as const, }, MONEY_REQUEST_CREATE_TAB_DISTANCE: { route: ':action/:iouType/start/:transactionID/:reportID/distance', - getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string) => - `create/${iouType}/start/${transactionID}/${reportID}/distance` as const, + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string) => `create/${iouType as string}/start/${transactionID}/${reportID}/distance` as const, }, MONEY_REQUEST_CREATE_TAB_MANUAL: { route: ':action/:iouType/start/:transactionID/:reportID/manual', - getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string) => - `${action}/${iouType}/start/${transactionID}/${reportID}/manual` as const, + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string) => + `${action as string}/${iouType as string}/start/${transactionID}/${reportID}/manual` as const, }, MONEY_REQUEST_CREATE_TAB_SCAN: { route: ':action/:iouType/start/:transactionID/:reportID/scan', - getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string) => - `create/${iouType}/start/${transactionID}/${reportID}/scan` as const, + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string) => `create/${iouType as string}/start/${transactionID}/${reportID}/scan` as const, }, MONEY_REQUEST_STATE_SELECTOR: { @@ -563,7 +560,7 @@ const ROUTES = { route: 'settings/workspaces/:policyID/members', getRoute: (policyID: string) => `settings/workspaces/${policyID}/members` as const, }, - WORKSPACE_ACCOUNTING: { + POLICY_ACCOUNTING: { route: 'settings/workspaces/:policyID/accounting', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting` as const, }, @@ -654,7 +651,7 @@ const ROUTES = { WORKSPACE_OWNER_CHANGE_CHECK: { route: 'settings/workspaces/:policyID/change-owner/:accountID/:error', getRoute: (policyID: string, accountID: number, error: ValueOf) => - `settings/workspaces/${policyID}/change-owner/${accountID}/${error}` as const, + `settings/workspaces/${policyID}/change-owner/${accountID}/${error as string}` as const, }, WORKSPACE_TAX_CREATE: { route: 'settings/workspaces/:policyID/taxes/new', @@ -707,27 +704,27 @@ const ROUTES = { route: 'r/:reportID/transaction/:transactionID/receipt', getRoute: (reportID: string, transactionID: string) => `r/${reportID}/transaction/${transactionID}/receipt` as const, }, - WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_IMPORT: { + POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_IMPORT: { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/import', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/import` as const, }, - WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_CHART_OF_ACCOUNTS: { + POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_CHART_OF_ACCOUNTS: { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/import/accounts', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/import/accounts` as const, }, - WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_CLASSES: { + POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_CLASSES: { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/import/classes', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/import/classes` as const, }, - WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_CUSTOMERS: { + POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_CUSTOMERS: { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/import/customers', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/import/customers` as const, }, - WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_LOCATIONS: { + POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_LOCATIONS: { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/import/locations', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/import/locations` as const, }, - WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_TAXES: { + POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_TAXES: { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/import/taxes', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/import/taxes` as const, }, @@ -742,7 +739,7 @@ const HYBRID_APP_ROUTES = { MONEY_REQUEST_CREATE: '/request/new/scan', } as const; -export {getUrlWithBackToParam, HYBRID_APP_ROUTES}; +export {HYBRID_APP_ROUTES, getUrlWithBackToParam}; export default ROUTES; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -762,4 +759,4 @@ type RouteIsPlainString = AssertTypesNotEqual { // Check if the file dimensions indicate corruption - // The width/height for corrupt file is -1 on android native and 0 on ios native - if (!fileData.width || !fileData.height || (fileData.width <= 0 && fileData.height <= 0)) { + // The width/height for a corrupted file is -1 on android native and 0 on ios native + // We must check only numeric values because the width/height can be undefined for non-image files + if ((typeof fileData.width === 'number' && fileData.width <= 0) || (typeof fileData.height === 'number' && fileData.height <= 0)) { showImageCorruptionAlert(); return Promise.resolve(); } diff --git a/src/components/AvatarWithDisplayName.tsx b/src/components/AvatarWithDisplayName.tsx index f6afb4dae2d6..c7a4ece0de97 100644 --- a/src/components/AvatarWithDisplayName.tsx +++ b/src/components/AvatarWithDisplayName.tsx @@ -65,7 +65,6 @@ function AvatarWithDisplayName({ const ownerPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(report?.ownerAccountID ? [report.ownerAccountID] : [], personalDetails); const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(Object.values(ownerPersonalDetails) as PersonalDetails[], false); const shouldShowSubscriptAvatar = ReportUtils.shouldReportShowSubscript(report); - const isExpenseRequest = ReportUtils.isExpenseRequest(report); const avatarBorderColor = isAnonymous ? theme.highlightBG : theme.componentBG; const actorAccountID = useRef(null); @@ -128,7 +127,7 @@ function AvatarWithDisplayName({ /> )} - + ; }; diff --git a/src/components/ConnectToQuickbooksOnlineButton/index.native.tsx b/src/components/ConnectToQuickbooksOnlineButton/index.native.tsx new file mode 100644 index 000000000000..4d482cb92ead --- /dev/null +++ b/src/components/ConnectToQuickbooksOnlineButton/index.native.tsx @@ -0,0 +1,68 @@ +import React, {useState} from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; +import {WebView} from 'react-native-webview'; +import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView'; +import Button from '@components/Button'; +import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import Modal from '@components/Modal'; +import useLocalize from '@hooks/useLocalize'; +import {getQuickBooksOnlineSetupLink} from '@libs/actions/connections/QuickBooksOnline'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {Session} from '@src/types/onyx'; +import type {ConnectToQuickbooksOnlineButtonProps} from './types'; + +type ConnectToQuickbooksOnlineButtonOnyxProps = { + /** Session info for the currently logged in user. */ + session: OnyxEntry; +}; + +const renderLoading = () => ; + +function ConnectToQuickbooksOnlineButton({policyID, session}: ConnectToQuickbooksOnlineButtonProps & ConnectToQuickbooksOnlineButtonOnyxProps) { + const [isModalOpen, setIsModalOpen] = useState(false); + const {translate} = useLocalize(); + + return ( + <> +